Setting the file. One moment.
Blob Recheck · Foundry Iq · microsoft/azure-skills · Skills Docs
ContentsBack to the top of the page File Cu Canary
— line 218
This file
Number 10.35
Position 35 of 77
Type Python
Size 67 KB
Lines 1,131 helpers/ blob_recheck.py
Python · 1,131 lines · 67 KB
13
from
datetime
import
datetime, timezone
14 from pathlib import Path
15 from urllib.parse import urlsplit
16
17 try :
18 from . import _bootstrap_io as private_io
19 from ._progress import Progress, add_progress_argument, reporting
20 from . import blob_inventory, blob_source, search_reconcile, source_vector, _indexer_observation as indexer
21 from . import _blob_observation as semantic
22 from ._common import (
23 HelperFailure, SEARCH_AUDIENCE , azure_cli_token, blocked_result, digest,
24 emit_result, http_request, reject_secrets,
25 )
26 except ImportError :
27 import _bootstrap_io as private_io
28 from _progress import Progress, add_progress_argument, reporting
29 import blob_inventory, blob_source, search_reconcile, source_vector
30 import _indexer_observation as indexer
31 import _blob_observation as semantic
32 from _common import (
33 HelperFailure, SEARCH_AUDIENCE , azure_cli_token, blocked_result, digest,
34 emit_result, http_request, reject_secrets,
35 )
36
37
38 fail = blob_source._failure
39 COLLECTIONS = { "datasource" : "datasources" , "indexer" : "indexers" ,
40 "skillset" : "skillsets" , "index" : "indexes" }
41
42
43 def _json (raw):
44 def unique (pairs):
45 value = {}
46 for key, child in pairs:
47 if key in value:
48 raise ValueError ( "Duplicate field" )
49 value[key] = child
50 return value
51 try :
52 value = json.loads(raw, object_pairs_hook = unique)
53 json.dumps(value, allow_nan = False , ensure_ascii = False ).encode( "utf-8" )
54 if not isinstance (value, dict ):
55 raise ValueError ( "Expected object" )
56 return value
57 except ( ValueError , UnicodeError , RecursionError ) as exc:
58 raise fail( "recheck-evidence-invalid" , "Retain bounded, unmodified UTF-8 object evidence." ) from exc
59
60
61 def read_private (path):
62 path = Path(path)
63 private_io.private_directory( str (path.parent))
64 try :
65 selected = path.lstat()
66 if ( not stat.S_ISREG(selected.st_mode) or selected.st_nlink != 1
67 or getattr (selected, "st_file_attributes" , 0 ) & 0x 400 ):
68 raise OSError ( "Not an ordinary private file" )
69 if os.name == "nt" :
70 private_io._windows_private(path)
71 elif selected.st_uid != os.getuid() or stat.S_IMODE(selected.st_mode) & 0o 077 :
72 raise OSError ( "Not private" )
73 descriptor = os.open(path, os. O_RDONLY | getattr (os, "O_NOFOLLOW" , 0 ))
74 with os.fdopen(descriptor, "rb" ) as handle:
75 opened = os.fstat(handle.fileno())
76 if (selected.st_dev, selected.st_ino) != (opened.st_dev, opened.st_ino):
77 raise OSError ( "Evidence changed identity" )
78 raw = handle.read(private_io. MAX_BYTES + 1 )
79 if len (raw) > private_io. MAX_BYTES :
80 raise OSError ( "Evidence exceeds bound" )
81 return _json(raw.decode( "utf-8" ))
82 except ( OSError , UnicodeError ) as exc:
83 raise fail( "recheck-evidence-unreadable" , "Select existing private, unlinked evidence files; no permissions were changed." ) from exc
84
85
86 def account_context ():
87 code, stdout, _ = private_io.run_cli([ "account" , "show" ], 30 )
88 if code:
89 raise fail( "recheck-auth-context-unavailable" , "Current signed-in CLI context is inaccessible; details withheld." )
90 account = _json(stdout)
91 user = account.get( "user" )
92 if (account.get( "environmentName" ) != "AzureCloud" or account.get( "state" ) != "Enabled"
93 or not isinstance (user, dict ) or user.get( "type" ) not in ( "user" , "servicePrincipal" )
94 or any ( not isinstance (value, str ) or not value.strip() for value in
95 (account.get( "id" ), account.get( "tenantId" ), user.get( "name" )))):
96 raise fail( "recheck-auth-context-unavailable" , "An enabled public-cloud tenant/subscription/principal context is required." )
97 return digest({key: account[key] for key in ( "id" , "tenantId" , "environmentName" )}
98 | { "principal" : { "name" : user[ "name" ], "type" : user[ "type" ]}})
99
100
101 def _supported (plan):
102 try :
103 source, _ = blob_source._validate_plan(plan)
104 ingestion = source[ "desired" ][ "azureBlobParameters" ][ "ingestionParameters" ]
105 except ( KeyError , TypeError , AttributeError , RecursionError ) as exc:
106 raise fail( "recheck-evidence-invalid" , "Original Blob creation plan is malformed." ) from exc
107 if (ingestion.get( "contentExtractionMode" ) not in ( "minimal" , "standard" ) or ingestion.get( "identity" ) is not None
108 or source[ "api_version" ] not in { "2026-04-01" , "2026-08-01-preview" }
109 or plan[ "boundary" ][ "is_adls" ] and source[ "api_version" ] != "2026-08-01-preview" ):
110 raise fail( "recheck-scope-unsupported" , "Checkpointing requires supported Blob extraction and provable system-assigned authentication." )
111 schedule = ingestion.get( "ingestionSchedule" )
112 indexer.schedule(schedule)
113
114
115 def _etag (value):
116 etag = value.get( "@odata.etag" ) if isinstance (value, dict ) else None
117 if not isinstance (etag, str ) or not etag.strip():
118 raise fail( "recheck-evidence-missing" , "Every source/generated readback requires a nonempty string ETag." )
119 return etag
120
121
122 def _processing_readback (plan, current):
123 desired = plan[ "source" ][ "desired" ][ "azureBlobParameters" ][ "ingestionParameters" ]
124 if desired.get( "contentExtractionMode" ) == "standard" :
125 ai = desired.get( "aiServices" )
126 if not isinstance (ai, dict ) or not isinstance (ai.get( "uri" ), str ) or not ai[ "uri" ].strip():
127 raise fail( "recheck-processing-unverified" , "Standard extraction needs its original CU endpoint evidence." )
128 blob_source.verify_content_understanding_readback({ "endpoint" : ai[ "uri" ]}, current)
129 source_vector.verify_source_readback(plan.get( "embedding" ), current)
130 model = desired.get( "embeddingModel" )
131 if model is not None and "embedding" not in plan:
132 parameters = model.get( "azureOpenAIParameters" ) if isinstance (model, dict ) else None
133 if ( not isinstance (parameters, dict ) or model.get( "kind" ) != "azureOpenAI"
134 or any ( not isinstance (parameters.get(key), str ) or not parameters[key].strip()
135 for key in ( "resourceUri" , "deploymentId" , "modelName" ))):
136 raise fail( "recheck-processing-unverified" , "Legacy embedding configuration cannot be verified by this client." )
137 source_vector.verify_source_readback({
138 "endpoint" : parameters[ "resourceUri" ], "deployment" : parameters[ "deploymentId" ],
139 "model" : parameters[ "modelName" ],
140 }, current)
141
142
143 def _binding (plan, record, current, generated, binding_receipts = None ):
144 source = plan[ "source" ]
145 observed = { "type" : "knowledge-source" , "name" : source[ "name" ], "etag" : _etag(current),
146 "definition_digest" : digest(search_reconcile._definition(current))}
147 if source[ "action" ] == "create" :
148 blob_source._verify_creation_binding(source, plan[ "boundary" ], current, generated, (plan, record))
149 elif (record[ "verification" ][ "readback" ] != observed
150 or not search_reconcile.definitions_match(source[ "desired" ], current)
151 or source.get( "expected_etag" , observed[ "etag" ]) != observed[ "etag" ]):
152 raise fail( "source-binding-unverified" , "Reused source does not match its retained read-only identity/configuration." )
153 if (blob_source.generated_resources(current, strict = True ) != generated
154 or plan.get( "expected_generated" , generated) != generated):
155 raise fail( "definition-drift" , "Generated source identities changed." )
156 if binding_receipts is not None and (
157 binding_receipts[ 0 ][ "owner" ] != plan[ "owner" ]
158 or binding_receipts[ 0 ][ "inventory_digest" ] != plan[ "inventory_digest" ]
159 ):
160 raise fail( "source-binding-unverified" , "Retained creation must bind the same owner and Storage inventory." )
161 blob_source._verify_storage_binding(
162 source, plan[ "boundary" ], current, generated,
163 lambda : (plan, record) if source[ "action" ] == "create" else binding_receipts,
164 )
165 _processing_readback(plan, current)
166
167
168 def _sanitized_datasource_url (url):
169 parsed = urlsplit(url)
170 search_reconcile.validate_search_endpoint( f " { parsed.scheme } :// { parsed.netloc } " )
171 if (parsed.fragment or parsed.query not in {
172 f "api-version= { version } " for version in search_reconcile. SUPPORTED_API_VERSIONS }
173 or re.fullmatch( r "/datasources \( ' [ a-zA-Z0-9 ][ a-zA-Z0-9_- ] {0,127} ' \) " , parsed.path) is None ):
174 raise fail( "recheck-read-url-invalid" , "Sanitized binding requires one exact datasource identity and the bound supported API version." )
175 return url + "&includeConnectionString=true"
176
177
178 def _reader (transport, token_provider, * , warnings = None ):
179 recovery = None
180 recovery_warnings = warnings if warnings is not None else []
181
182 def tracked (method, url, token, ** kwargs):
183 try :
184 parsed = urlsplit(url)
185 search_reconcile.validate_search_endpoint( f " { parsed.scheme } :// { parsed.netloc } " )
186 if parsed.fragment:
187 raise fail( "recheck-read-url-invalid" , "Read-only Search URLs cannot contain fragments." )
188 if parsed.query not in {
189 f "api-version= { version } " for version in search_reconcile. SUPPORTED_API_VERSIONS }:
190 suffix = "&includeConnectionString=true"
191 if not url.endswith(suffix) or _sanitized_datasource_url(url[: - len (suffix)]) != url:
192 raise fail( "recheck-read-url-invalid" , "Only the exact sanitized datasource GET option is permitted." )
193 kwargs[ "timeout" ] = min ( 60 , kwargs.get( "timeout" , 60 ))
194 if recovery is None :
195 response = transport(method, url, token, ** kwargs)
196 else :
197 response = recovery.get(
198 url, token, transport = transport,
199 max_requests = 1 if url.endswith( "&includeConnectionString=true" ) else 2 ,
200 ** kwargs,
201 )
202 ids.extend(recovery.request_ids[: - 1 ])
203 if method == "GET" and response.status == 200 and isinstance (response.body, dict ):
204 etag = search_reconcile.resolve_etag(search_reconcile.response_etags(response), response.request_id)
205 if etag is not None :
206 response = search_reconcile.HttpResult(
207 response.status, { ** response.body, "@odata.etag" : etag}, response.headers, response.etag_values,
208 )
209 return response
210 except HelperFailure as failure:
211 if recovery is not None :
212 ids.extend(recovery.request_ids)
213 elif failure.request_id:
214 ids.append(failure.request_id)
215 raise
216 read, original_get, ids = source_vector._reader(tracked, token_provider)
217
218 def get (url):
219 nonlocal recovery
220 recovery = search_reconcile.ReadRecovery()
221 try :
222 result = original_get(url)
223 get.request_id = ids[ - 1 ] if ids else None
224 return result
225 except HelperFailure as failure:
226 if failure.code in { "vector-resource-missing" , "vector-evidence-missing" , "etag-invalid" }:
227 missing = failure.code == "vector-resource-missing"
228 raise HelperFailure(
229 "recheck-resource-missing" if missing else "recheck-evidence-missing" ,
230 "An exact original source/generated definition or ETag is unavailable." ,
231 blocked_at = "verification" , status = 404 if missing else failure.http_status,
232 request_id = ids[ - 1 ] if ids else failure.request_id,
233 ) from failure
234 raise
235 finally :
236 recovery_warnings.extend(recovery.warnings)
237 recovery = None
238 get.recovery_warnings = recovery_warnings
239 return read, get, ids
240
241
242 def _configuration (plan, creation, generated, get, * , binding_receipts = None , expected = None ,
243 diagnostics = None , observations = None , binding_observations = None ):
244 diagnostics = diagnostics if diagnostics is not None else []
245 source = plan[ "source" ]
246 current = get(search_reconcile.resource_url(source))
247 _binding(plan, creation, current, generated, binding_receipts)
248 names = {item[ "type" ]: item[ "name" ] for item in generated}
249 snapshots = {}
250 failures = []
251 for kind, collection in COLLECTIONS .items():
252 name = names[kind]
253 url = f " { source[ 'endpoint' ].rstrip( '/' ) } / { collection } (' { name } ')?api-version= { source[ 'api_version' ] } "
254 try :
255 snapshots[kind] = _child_configuration(
256 plan, kind, name, names, get, url, expected, diagnostics, observations, binding_observations,
257 )
258 except HelperFailure as failure:
259 if failure.request_id is None :
260 failure.request_id = getattr (get, "request_id" , None )
261 semantic.note(diagnostics, kind, "error" , failure.code, "definition" , failure.request_id)
262 failures.append(failure)
263 else :
264 semantic.note(diagnostics, kind, "info" , "generated-configuration-verified" , "definition" ,
265 getattr (get, "request_id" , None ))
266 refreshed = get(search_reconcile.resource_url(source))
267 _binding(plan, creation, refreshed, generated, binding_receipts)
268 if failures:
269 raise failures[ 0 ]
270 return snapshots
271
272
273 def _child_configuration (plan, kind, name, names, get, url, expected, diagnostics, observations,
274 binding_observations = None ):
275 source = plan[ "source" ]
276 child = get(url)
277 etag = _etag(child)
278 if child.get( "name" ) != name:
279 raise fail( "definition-drift" , "Generated configuration returned an unrelated identity." )
280 if kind == "datasource" :
281 boundary = plan[ "boundary" ]
282 container, credentials = child.get( "container" ), child.get( "credentials" )
283 invariants = {
284 "type" : child.get( "type" ) == ( "adlsgen2" if boundary[ "is_adls" ] else "azureblob" ),
285 "container.name" : isinstance (container, dict ) and container.get( "name" ) == boundary[ "container" ],
286 "container.query" : isinstance (container, dict ) and container.get( "query" ) in
287 (boundary[ "prefix" ], None if not boundary[ "prefix" ] else boundary[ "prefix" ]),
288 "credentials" : credentials is None or
289 isinstance (credentials, dict ) and not set (credentials) - { "connectionString" },
290 "identity" : child.get( "identity" ) is None ,
291 }
292 for field, valid in invariants.items():
293 if not valid:
294 semantic.note(diagnostics, kind, "error" , "source-binding-unverified" , field,
295 getattr (get, "request_id" , None ))
296 if not all (invariants.values()):
297 raise fail( "source-binding-unverified" , "Generated datasource type/container/prefix/identity or credential shape conflicts with the bound source." ,
298 request_id = getattr (get, "request_id" , None ))
299 try :
300 binding = blob_source._connection_binding(
301 credentials.get( "connectionString" ) if isinstance (credentials, dict ) else None , boundary,
302 )
303 except HelperFailure as failure:
304 failure.request_id = getattr (get, "request_id" , None )
305 semantic.note(diagnostics, kind, "error" , failure.code, "credentials.connectionString" , failure.request_id)
306 raise
307 if kind == "indexer" :
308 request_id = getattr (get, "request_id" , None )
309 for field, target in (( "dataSourceName" , "datasource" ), ( "targetIndexName" , "index" ), ( "skillsetName" , "skillset" )):
310 if child.get(field) != names[target]:
311 raise indexer.failure(diagnostics, "indexer-binding-mismatch" , field,
312 f "Generated indexer { field } does not match the bound resource; details withheld." , request_id)
313 observed = indexer.observe(child, source[ "desired" ][ "azureBlobParameters" ][ "ingestionParameters" ].get( "ingestionSchedule" ),
314 diagnostics, request_id)
315 if observations is not None :
316 observations.append({ "request_id" : request_id, "etag_digest" : digest(etag),
317 ** {key: value for key, value in observed.items() if key != "etag" }})
318 snapshot = observed if expected is None or indexer. PROJECTION_FIELDS <= set (expected[kind]) else {
319 "etag" : etag, "digest" : digest(child),
320 }
321 if expected is None or semantic. PROJECTION_FIELDS <= set (expected[kind]):
322 snapshot.update(semantic.observe(child, kind))
323 if expected is not None :
324 if semantic. PROJECTION_FIELDS <= set (expected[kind]):
325 semantic.compare(snapshot, expected[kind], kind, diagnostics, request_id)
326 # A core-equal ETag-only revision is not schedule or configuration drift.
327 if snapshot[ "schedule_raw_digest" ] != expected[kind][ "schedule_raw_digest" ]:
328 indexer.compare(snapshot, expected[kind], diagnostics, request_id)
329 else :
330 indexer.compare(snapshot, expected[kind], diagnostics, request_id)
331 return snapshot
332 # Keep only integrity observations, never generated credentials or skill text.
333 snapshot = { "etag" : etag, "digest" : digest(child)}
334 if expected is None or semantic. PROJECTION_FIELDS <= set (expected[kind]):
335 snapshot.update(semantic.observe(child, kind))
336 if kind == "datasource" :
337 snapshot[ "binding_proof" ] = "resource-id" if binding == "visible" else "unverified"
338 if expected is not None :
339 semantic.compare(snapshot, expected[kind], kind, diagnostics, getattr (get, "request_id" , None ))
340 if kind == "datasource" and binding == "concealed" :
341 _current_datasource_binding(get, url, snapshot, plan[ "boundary" ], diagnostics,
342 binding_observations)
343 elif kind == "datasource" and binding == "concealed" :
344 semantic.note(diagnostics, kind, "info" , "datasource-credential-projection-concealed" ,
345 "credentials.connectionString" , getattr (get, "request_id" , None ))
346 return snapshot
347 visible_preimage = False
348 if kind == "datasource" and expected and expected[kind][ "etag" ] == etag:
349 # Only concealed/verified credentials may vary; every other byte and the ETag stay bound.
350 for credentials in (
351 None , {}, { "connectionString" : None }, { "connectionString" : "" },
352 { "connectionString" : "<redacted>" }, { "connectionString" : "<REDACTED>" },
353 { "connectionString" : f "ResourceId= { plan[ 'boundary' ][ 'storage_id' ] } " },
354 { "connectionString" : f "ResourceId= { plan[ 'boundary' ][ 'storage_id' ] } ;" },
355 ):
356 candidate = { ** child, "credentials" : credentials}
357 if digest(candidate) == expected[kind][ "digest" ]:
358 snapshot[ "digest" ] = expected[kind][ "digest" ]
359 if isinstance (credentials, dict ) and isinstance (credentials.get( "connectionString" ), str ):
360 visible_preimage = credentials[ "connectionString" ].startswith( "ResourceId=" )
361 candidate = {key: value for key, value in child.items() if key != "credentials" }
362 if digest(candidate) == expected[kind][ "digest" ]:
363 snapshot[ "digest" ] = expected[kind][ "digest" ]
364 if snapshot != expected[kind]:
365 raise fail( "generated-legacy-evidence-insufficient" ,
366 "Legacy generated full-hash evidence differs; no core projection was retained." ,
367 request_id = getattr (get, "request_id" , None ))
368 if kind == "datasource" and binding == "concealed" :
369 if visible_preimage:
370 semantic.note(diagnostics, kind, "warning" , "datasource-credential-projection-changed" ,
371 "credentials" , getattr (get, "request_id" , None ))
372 else :
373 _current_datasource_binding(get, url, { "etag" : etag, "digest" : digest(child), ** semantic.observe(child, kind)},
374 plan[ "boundary" ], diagnostics, binding_observations)
375 return snapshot
376
377
378 def _current_datasource_binding (get, url, snapshot, boundary, diagnostics, observations = None ):
379 observation = {
380 "schema_version" : "1.0" , "status" : "unverified" , "request_id" : None ,
381 "plain" : { "etag_digest" : digest(snapshot[ "etag" ]), "definition_digest" : snapshot[ "digest" ],
382 "core_digest" : snapshot[ "core_digest" ]},
383 "sanitized" : None ,
384 }
385 if observations is not None :
386 observations.append(observation)
387 # The service sanitizes internally; this option never requests raw keys or SAS.
388 try :
389 current = get(_sanitized_datasource_url(url))
390 except HelperFailure as failure:
391 observation[ "request_id" ] = failure.request_id
392 raise
393 credentials = current.get( "credentials" )
394 request_id = getattr (get, "request_id" , None )
395 observation[ "request_id" ] = request_id
396 projection = semantic.observe(current, "datasource" )
397 observation[ "sanitized" ] = {
398 "etag_digest" : digest(_etag(current)), "definition_digest" : digest(current),
399 "core_digest" : projection[ "core_digest" ],
400 }
401 if (_etag(current) != snapshot[ "etag" ]
402 or observation[ "sanitized" ][ "core_digest" ] != snapshot[ "core_digest" ]):
403 fields = projection[ "field_digests" ].keys() | snapshot[ "field_digests" ].keys()
404 changed = [field for field in sorted (fields)
405 if projection[ "field_digests" ].get(field) != snapshot[ "field_digests" ].get(field)]
406 if _etag(current) != snapshot[ "etag" ]:
407 changed.append( "@odata.etag" )
408 for field in changed:
409 semantic.note(diagnostics, "datasource" , "error" , "datasource-binding-unverified" , field, request_id)
410 raise fail( "datasource-binding-unverified" ,
411 "Independent datasource binding readback changed revision or configuration; no stable current proof is available." ,
412 request_id = request_id)
413 try :
414 visible = ( isinstance (credentials, dict ) and set (credentials) == { "connectionString" }
415 and blob_source._connection_binding(credentials[ "connectionString" ], boundary) == "visible" )
416 except HelperFailure:
417 visible = False
418 if not visible:
419 semantic.note(diagnostics, "datasource" , "error" , "datasource-binding-unverified" ,
420 "credentials.connectionString" , request_id)
421 raise fail( "datasource-binding-unverified" ,
422 "Sanitized current datasource readback does not prove the exact selected keyless ResourceId; values withheld. Retain resources and investigate this evidence gap read-only." ,
423 request_id = request_id)
424 observation[ "status" ] = "verified"
425 semantic.note(diagnostics, "datasource" , "info" , "datasource-current-binding-verified" ,
426 "credentials.connectionString" , request_id)
427
428
429 def _baseline_cycle (source, read, token_provider):
430 url = search_reconcile.resource_url(source).replace( ")?" , ")/status?" )
431 response = read( "GET" , url, token_provider( SEARCH_AUDIENCE ))
432 body = response.body
433 if response.status != 200 or not isinstance (body, dict ) or body.get( "kind" ) != "azureBlob" :
434 raise HelperFailure( "ingestion-inaccessible" , "Initial reuse status is unavailable." ,
435 blocked_at = "verification" , status = response.status, request_id = response.request_id)
436 last = body.get( "lastSynchronizationState" )
437 if last is None :
438 return None
439 if not isinstance (last, dict ):
440 raise fail( "ingestion-status-invalid" , "Initial reuse synchronization must be an object." )
441 if last.get( "endTime" ) is None :
442 blob_source._timestamp(last.get( "startTime" ))
443 return None
444 cycle = [last.get( "startTime" ), last.get( "endTime" )]
445 _validate_cycle(cycle)
446 return cycle
447
448
449 def _validate_cycle (cycle):
450 if cycle is None :
451 return
452 if not isinstance (cycle, list ) or len (cycle) != 2 :
453 raise fail( "recheck-evidence-invalid" , "Retain the original observed reuse cycle, not a reconstructed bound." )
454 if blob_source._timestamp(cycle[ 1 ]) < blob_source._timestamp(cycle[ 0 ]):
455 raise fail( "ingestion-status-invalid" , "Initial synchronization interval is invalid." )
456
457
458 def _binding_receipts (paths):
459 if paths is None :
460 return None
461 if ( not isinstance (paths, ( tuple , list )) or len (paths) != 2
462 or any ( not isinstance (path, ( str , Path)) or not str (path).strip() for path in paths)):
463 raise fail( "reuse-evidence-invalid" , "Select both existing private creation evidence files." )
464 _, prior, fingerprint = _document(paths[ 0 ])
465 result = read_private(paths[ 1 ])
466 blob_source._validate_reuse_receipts(prior, fingerprint, result)
467 return prior, result
468
469
470 class Checkpoint :
471 def __init__ (self, directory, plan, * , context_provider = account_context, binding_receipts = None ):
472 _supported(plan)
473 self .directory = private_io.private_directory( str (directory))
474 self .context_provider = context_provider
475 self .context = context_provider()
476 self .plan_digest = digest(plan)
477 self .operation_id = uuid.uuid4().hex
478 self .summary = None
479 self .excluded_cycle = None
480 self .request_ids = []
481 self .recovery_warnings = []
482 self .acknowledgement = None
483 self .write_acknowledgement = None
484 self .write_observation = None
485 self .binding_receipts = binding_receipts
486 self .diagnostics = []
487 self .observations = []
488 self .binding_observations = []
489 self .configuration = None
490 self .creation = None
491
492 def acknowledge (self, plan, response, not_before, * , url, body, headers):
493 if (digest(plan) != self .plan_digest or plan[ "source" ][ "action" ] != "create"
494 or response.status not in { 200 , 201 }
495 or url != search_reconcile.resource_url(plan[ "source" ])
496 or headers.get( "If-None-Match" ) != "*" or "If-Match" in headers
497 or body != search_reconcile.canonical_bytes(plan[ "source" ][ "desired" ])):
498 raise fail( "recheck-ownership-unproven" , "Only the exact successful conditional create can retain a write acknowledgement." )
499 self .write_observation = {
500 "schema_version" : "1.0" , "state" : "acknowledged" , "type" : "knowledge-source" ,
501 "name" : plan[ "source" ][ "name" ], "http_status" : response.status, "request_id" : response.request_id,
502 }
503 if self .context_provider() != self .context:
504 raise fail( "recheck-auth-context-drift" , "CLI context changed before write acknowledgement retention." )
505 receipt = {
506 "schema_version" : "1.0" , "kind" : "blob-write-acknowledgement" ,
507 "operation_id" : self .operation_id, "plan_digest" : self .plan_digest,
508 "not_before" : not_before.isoformat(), "context_digest" : self .context,
509 "write" : {
510 "method" : "PUT" , "url" : url, "if_none_match" : headers[ "If-None-Match" ],
511 "body_digest" : digest(plan[ "source" ][ "desired" ]), "status" : response.status,
512 "request_id" : response.request_id, "response_etags" : search_reconcile.response_etags(response),
513 "generated" : blob_source.generated_resources(response.body) if isinstance (response.body, dict ) else [],
514 },
515 }
516 reject_secrets(receipt)
517 receipt[ "integrity" ] = digest(receipt)
518 path = private_io.private_file( self .directory, self .operation_id + ".blob-write.json" , receipt)
519 self .write_acknowledgement = { "operation_id" : self .operation_id, "receipt_file" : path.name,
520 "evidence_digest" : receipt[ "integrity" ], "status" : "retained" }
521
522 def persist (self, plan, result, generated, not_before, * , token_provider, transport):
523 reused = plan[ "source" ][ "action" ] == "reuse"
524 selected, other = ( "reused" , "created" ) if reused else ( "created" , "reused" )
525 owned = "reused_not_owned" if reused else "run_owned"
526 if (digest(plan) != self .plan_digest or len (result[ "resources" ][selected]) != 1
527 or result[ "resources" ][other] or result[ "resources" ][selected] != result[ "ownership" ][owned]):
528 raise fail( "recheck-ownership-unproven" , "Retain exact acknowledged creation or read-only reuse; never adopt shared resources." )
529 generated = copy.deepcopy(generated)
530 creation = {key: copy.deepcopy(result[key]) for key in
531 ( "approved_plan" , "resources" , "verification" , "ownership" )}
532 creation[ "source" ] = { "generated" : generated}
533 seed = {
534 "schema_version" : "1.0" , "kind" : "blob-source-acknowledgement" ,
535 "operation_id" : self .operation_id, "plan_digest" : digest(plan),
536 "not_before" : not_before.isoformat(), "context_digest" : self .context,
537 "creation" : creation, "request_ids" : [],
538 }
539 if not reused:
540 if self .context_provider() != self .context:
541 raise fail( "recheck-auth-context-drift" , "CLI context changed before acknowledgement retention." )
542 seed[ "integrity" ] = digest(seed)
543 path = private_io.private_file( self .directory, self .operation_id + ".blob-ack.json" , seed)
544 self .acknowledgement = { "operation_id" : self .operation_id, "receipt_file" : path.name,
545 "evidence_digest" : seed[ "integrity" ], "status" : "retained" }
546 read, get, ids = _reader(transport, token_provider, warnings = self .recovery_warnings)
547 self .request_ids = ids
548 configuration = _configuration(plan, creation, generated, get, binding_receipts = self .binding_receipts,
549 diagnostics = self .diagnostics, observations = self .observations)
550 if reused:
551 self .excluded_cycle = _baseline_cycle(plan[ "source" ], read, token_provider)
552 if self .context_provider() != self .context:
553 raise fail( "recheck-auth-context-drift" , "CLI context changed during checkpoint capture; no checkpoint was retained." )
554 receipt = {
555 "schema_version" : "3.1" if reused else "3.0" , "kind" : "blob-readiness-checkpoint" ,
556 "operation_id" : self .operation_id, "plan_digest" : digest(plan),
557 "not_before" : not_before.isoformat(), "context_digest" : self .context,
558 "creation" : creation, "configuration" : configuration, "request_ids" : ids,
559 }
560 if reused:
561 receipt[ "excluded_cycle" ] = self .excluded_cycle
562 if self .binding_receipts is not None :
563 receipt[ "schema_version" ] = "3.2"
564 receipt[ "binding_digest" ] = digest( self .binding_receipts)
565 receipt[ "integrity" ] = digest(receipt)
566 path = private_io.private_file( self .directory, self .operation_id + ".blob-readiness.json" , receipt)
567 self .request_ids = ids
568 self .summary = { "operation_id" : self .operation_id, "receipt_file" : path.name,
569 "evidence_digest" : receipt[ "integrity" ], "status" : "retained" }
570 self .configuration, self .creation = configuration, creation
571
572 def verify (self, plan, * , token_provider, transport):
573 _, get, ids = _reader(transport, token_provider, warnings = self .recovery_warnings)
574 try :
575 _configuration(plan, self .creation, self .creation[ "source" ][ "generated" ], get,
576 expected = self .configuration, binding_receipts = self .binding_receipts,
577 diagnostics = self .diagnostics, observations = self .observations,
578 binding_observations = self .binding_observations)
579 finally :
580 self .request_ids.extend(ids)
581 if self .context_provider() != self .context:
582 raise fail( "recheck-auth-context-drift" , "CLI context changed during generated readback." )
583
584 def finish (self, result):
585 record = { "schema_version" : "1.0" , "kind" : "blob-operation-result" ,
586 "operation_id" : self .operation_id, "plan_digest" : self .plan_digest,
587 "result" : copy.deepcopy(result)}
588 reject_secrets(record)
589 record[ "integrity" ] = digest(record)
590 path = private_io.private_file( self .directory, self .operation_id + ".blob-result.json" , record)
591 result[ "result_evidence" ] = { "receipt_file" : path.name, "status" : "retained" }
592
593
594 def _document (input_path):
595 document = read_private(input_path)
596 reject_secrets(document)
597 if set (document) != { "schema_version" , "plan" , "approval" } or document[ "schema_version" ] != "1.0" :
598 raise fail( "recheck-evidence-invalid" , "Retain the original source envelope." )
599 plan = document[ "plan" ]
600 if not isinstance (plan, dict ):
601 raise fail( "recheck-evidence-invalid" , "The original plan is unavailable." )
602 _supported(plan)
603 fingerprint = digest(plan)
604 approval = document[ "approval" ]
605 if ( not isinstance (approval, dict ) or set (approval) != { "confirmed" , "fingerprint" }
606 or type (approval[ "confirmed" ]) is not bool or approval[ "fingerprint" ] != fingerprint
607 or plan[ "source" ][ "action" ] == "create" and approval[ "confirmed" ] is not True ):
608 raise fail( "approval-mismatch" , "Retain unchanged creation consent or the fingerprinted read-only reuse plan." )
609 return document, plan, fingerprint
610
611
612 def _load (input_path, receipt_path, * , acknowledgement = False , binding_receipts = None ):
613 document, plan, fingerprint = _document(input_path)
614 receipt = read_private(receipt_path)
615 reject_secrets(receipt)
616 if acknowledgement and receipt.get( "kind" ) == "blob-write-acknowledgement" :
617 _validate_write(plan, receipt)
618 return plan, receipt
619 reused = plan[ "source" ][ "action" ] == "reuse"
620 version = receipt.get( "schema_version" )
621 if not isinstance (version, str ):
622 raise fail( "recheck-evidence-invalid" , "Checkpoint schema version must be a supported string." )
623 core_projected = version in { "3.0" , "3.1" , "3.2" } and not acknowledgement
624 projected = (version in { "2.0" , "2.1" , "2.2" } or core_projected) and not acknowledgement
625 bound = reused and version in { "1.2" , "2.2" , "3.2" }
626 fields = { "schema_version" , "kind" , "operation_id" , "plan_digest" , "not_before" ,
627 "context_digest" , "creation" , "configuration" , "request_ids" , "integrity" }
628 if reused:
629 fields.add( "excluded_cycle" )
630 if bound:
631 fields.add( "binding_digest" )
632 if acknowledgement:
633 fields.remove( "configuration" )
634 expected_version = ( "3" if core_projected else "2" if projected else "1" ) + ( ".2" if bound else ".1" if reused else ".0" )
635 if ( set (receipt) != fields or version != expected_version
636 or receipt[ "kind" ] != ( "blob-source-acknowledgement" if acknowledgement else "blob-readiness-checkpoint" )
637 or acknowledgement and reused
638 or bound and (binding_receipts is None or receipt[ "binding_digest" ] != digest(binding_receipts))
639 or receipt[ "plan_digest" ] != fingerprint
640 or not isinstance (receipt[ "operation_id" ], str )
641 or re.fullmatch( "[0-9a-f] {32} " , receipt[ "operation_id" ]) is None
642 or receipt[ "integrity" ] != digest({k: v for k, v in receipt.items() if k != "integrity" })):
643 raise fail( "recheck-evidence-invalid" , "Original operation/cutoff/checkpoint integrity is missing or changed; never reconstruct it." )
644 blob_source._timestamp(receipt[ "not_before" ])
645 if reused:
646 _validate_cycle(receipt[ "excluded_cycle" ])
647 creation = receipt[ "creation" ]
648 try :
649 observed = creation[ "verification" ][ "readback" ]
650 generated = creation[ "source" ][ "generated" ]
651 expected = { "type" : "knowledge-source" , "name" : plan[ "source" ][ "name" ],
652 "etag" : observed[ "etag" ],
653 "definition_digest" : digest(search_reconcile._definition(plan[ "source" ][ "desired" ]))}
654 valid = (
655 set (creation) == { "approved_plan" , "resources" , "verification" , "ownership" , "source" }
656 and set (creation[ "source" ]) == { "generated" }
657 and set (creation[ "verification" ]) == { "readback" , "absence" , "request_ids" , "idempotency" }
658 and creation[ "verification" ][ "absence" ] is False
659 and creation[ "verification" ][ "idempotency" ] == "exact readback is zero-write"
660 and isinstance (creation[ "verification" ][ "request_ids" ], list )
661 and all ( isinstance (item, str ) for item in creation[ "verification" ][ "request_ids" ])
662 and creation[ "approved_plan" ] == document[ "approval" ]
663 and creation[ "resources" ] == { "created" : [] if reused else [expected],
664 "reused" : [expected] if reused else [], "updated" : [], "skipped" : []}
665 and creation[ "ownership" ] == { "run_owned" : [] if reused else [expected],
666 "reused_not_owned" : [expected] if reused else [], "owner" : plan[ "owner" ]}
667 and observed == expected and isinstance (expected[ "etag" ], str ) and bool (expected[ "etag" ].strip())
668 and generated == blob_source.generated_resources(
669 { "azureBlobParameters" : { "createdResources" : {item[ "type" ]: item[ "name" ] for item in generated}}},
670 strict = True ,
671 )
672 and (acknowledgement or set (receipt[ "configuration" ]) == set ( COLLECTIONS ))
673 and isinstance (receipt[ "request_ids" ], list )
674 and all ( isinstance (item, str ) for item in receipt[ "request_ids" ])
675 and isinstance (receipt[ "context_digest" ], str )
676 and search_reconcile. SHA256 .fullmatch(receipt[ "context_digest" ])
677 )
678 for kind, item in ({} if acknowledgement else receipt[ "configuration" ]).items():
679 if core_projected:
680 valid = valid and semantic.valid(item, kind, search_reconcile. SHA256 )
681 if kind == "indexer" :
682 valid = valid and item.get( "core_digest" ) == item.get( "non_schedule_digest" )
683 if kind == "datasource" and item.get( "binding_proof" ) == "resource-id" :
684 valid = valid and item[ "credential_digest" ] in {
685 digest({ "present" : True , "value" : { "connectionString" : f "ResourceId= { plan[ 'boundary' ][ 'storage_id' ] }{ suffix } " }})
686 for suffix in ( "" , ";" )
687 }
688 continue
689 hashes = { "digest" } | (indexer. PROJECTION_FIELDS if projected and kind == "indexer" else set ())
690 valid = valid and set (item) == { "etag" } | hashes and isinstance (item[ "etag" ], str ) and bool (item[ "etag" ].strip())
691 valid = valid and all ( isinstance (item[key], str ) and search_reconcile. SHA256 .fullmatch(item[key]) for key in hashes)
692 except ( KeyError , TypeError , AttributeError ):
693 valid = False
694 if not valid:
695 raise fail( "recheck-ownership-unproven" , "Checkpoint must retain exact acknowledged ownership, generated configuration and provenance." )
696 return plan, receipt
697
698
699 def _historical_result (receipt_path, receipt):
700 path = Path(receipt_path).parent / (receipt[ "operation_id" ] + ".blob-result.json" )
701 if not path.exists():
702 return "not-recorded-by-pre-monitor-checkpoint"
703 record = read_private(path)
704 reject_secrets(record)
705 if ( set (record) != { "schema_version" , "kind" , "operation_id" , "plan_digest" , "result" , "integrity" }
706 or record[ "schema_version" ] != "1.0" or record[ "kind" ] != "blob-operation-result"
707 or record[ "operation_id" ] != receipt[ "operation_id" ] or record[ "plan_digest" ] != receipt[ "plan_digest" ]
708 or record[ "integrity" ] != digest({key: value for key, value in record.items() if key != "integrity" })
709 or not isinstance (record[ "result" ], dict )):
710 raise fail( "recheck-evidence-invalid" , "Historical operation result is malformed or changed; preserve original evidence." )
711 result = record[ "result" ]
712 readiness = result.get( "readiness" , {})
713 if not isinstance (readiness, dict ):
714 raise fail( "recheck-evidence-invalid" , "Historical readiness must be an object." )
715 watch = readiness.get( "watch" )
716 if watch is not None and (
717 not isinstance (watch, dict ) or watch.get( "schema_version" ) != "1.0"
718 or not isinstance (watch.get( "state" ), str )
719 or watch.get( "state" ) not in { "paused" , "completed" , "blocked" }):
720 raise fail( "recheck-evidence-invalid" , "Historical watch metadata is malformed or unsupported." )
721 return { "receipt_file" : path.name, "status" : result.get( "status" ),
722 "first_failure" : result.get( "first_failure" , result.get( "first_blocker" )),
723 "watch" : copy.deepcopy(watch),
724 "writes_performed" : result.get( "writes_performed" , result.get( "completed_writes" , []))}
725
726
727 def _validate_write (plan, receipt):
728 try :
729 write = receipt[ "write" ]
730 valid = (
731 set (receipt) == { "schema_version" , "kind" , "operation_id" , "plan_digest" , "not_before" ,
732 "context_digest" , "write" , "integrity" }
733 and receipt[ "schema_version" ] == "1.0" and plan[ "source" ][ "action" ] == "create"
734 and receipt[ "plan_digest" ] == digest(plan)
735 and isinstance (receipt[ "operation_id" ], str )
736 and re.fullmatch( "[0-9a-f] {32} " , receipt[ "operation_id" ]) is not None
737 and isinstance (receipt[ "context_digest" ], str )
738 and search_reconcile. SHA256 .fullmatch(receipt[ "context_digest" ]) is not None
739 and receipt[ "integrity" ] == digest({k: v for k, v in receipt.items() if k != "integrity" })
740 and set (write) == { "method" , "url" , "if_none_match" , "body_digest" , "status" ,
741 "request_id" , "response_etags" , "generated" }
742 and write[ "method" ] == "PUT" and write[ "if_none_match" ] == "*"
743 and type (write[ "status" ]) is int and write[ "status" ] in { 200 , 201 }
744 and write[ "url" ] == search_reconcile.resource_url(plan[ "source" ])
745 and write[ "body_digest" ] == digest(plan[ "source" ][ "desired" ])
746 and isinstance (write[ "request_id" ], str ) and bool (write[ "request_id" ].strip())
747 and set (write[ "response_etags" ]) == { "body" , "headers" }
748 and isinstance (write[ "response_etags" ][ "headers" ], list )
749 and isinstance (write[ "generated" ], list )
750 )
751 if write[ "generated" ]:
752 valid = valid and write[ "generated" ] == blob_source.generated_resources(
753 { "azureBlobParameters" : { "createdResources" : {
754 item[ "type" ]: item[ "name" ] for item in write[ "generated" ]}}}, strict = True ,
755 )
756 except ( KeyError , TypeError , AttributeError ):
757 valid = False
758 if not valid:
759 raise fail( "recheck-ownership-unproven" , "Retain the private authenticated conditional-write receipt; input booleans or observed existence cannot replace it." )
760 blob_source._timestamp(receipt[ "not_before" ])
761 if search_reconcile.resolve_etag(write[ "response_etags" ], write[ "request_id" ]) is None :
762 raise fail( "creation-version-unproven" , "Write acknowledgement has no response ETag; never borrow a later GET version." ,
763 request_id = write[ "request_id" ])
764
765
766 @reporting ( "blob-capture" )
767 def capture (input_path, directory, * , token_provider = azure_cli_token, transport = http_request,
768 storage_transport = http_request, context_provider = account_context,
769 now =lambda : datetime.now(timezone.utc), progress: Progress | None = None ,
770 binding_paths = None ):
771 progress.update( "evidence-validation" )
772 document, plan, fingerprint = _document(input_path)
773 if plan[ "source" ][ "action" ] != "reuse" :
774 raise fail( "recheck-scope-unsupported" , "Fresh capture accepts only a reuse plan; it cannot recover missing original creation proof." )
775 binding_receipts = _binding_receipts(binding_paths)
776 progress.update( "context-check" )
777 checkpoint = Checkpoint(directory, plan, context_provider = context_provider, binding_receipts = binding_receipts)
778 cutoff = now()
779 progress.update( "source-binding" )
780 _, get, ids = _reader(transport, token_provider)
781 current = get(search_reconcile.resource_url(plan[ "source" ]))
782 generated = blob_source.generated_resources(current, strict = True )
783 result = search_reconcile._completed(
784 "blob-readiness-capture" , fingerprint, plan[ "source" ], action = "reused" ,
785 readback = current, request_ids = ids, absence = False ,
786 )
787 result[ "approved_plan" ] = document[ "approval" ]
788 progress.update( "blob-inventory" )
789 inventory = blob_inventory.discover(plan[ "boundary" ], plan[ "inventory_limits" ],
790 token_provider = token_provider, transport = storage_transport)
791 if inventory[ "inventory_digest" ] != plan[ "inventory_digest" ]:
792 raise fail( "source-drift" , "Selected Storage/ACL evidence differs from the reuse plan." )
793 result[ "verification" ][ "request_ids" ].extend(inventory[ "request_ids" ])
794 progress.update( "checkpoint" )
795 checkpoint.persist(plan, result, generated, cutoff, token_provider = token_provider, transport = transport)
796 return {
797 "status" : "completed" , "outcome" : "blob-readiness-capture" ,
798 "recheck_checkpoint" : checkpoint.summary, "writes_performed" : [],
799 "readiness" : { "status" : "unverified" }, "retrieval" : "unverified" , "knowledge_base" : "not-verified" ,
800 "ownership" : result[ "ownership" ], "cleanup" : { "separate_confirmation_required" : True },
801 "read_only_evidence" : { "request_ids" : result[ "verification" ][ "request_ids" ] + checkpoint.request_ids},
802 "indexer_diagnostics" : semantic.indexer_only(checkpoint.diagnostics),
803 "generated_diagnostics" : checkpoint.diagnostics, "indexer_observations" : checkpoint.observations,
804 "warnings" : [blob_source. SNAPSHOT_WARNING , "Fresh reuse observation is not recovered creation ownership or ingestion proof." ,
805 * get.recovery_warnings, * checkpoint.recovery_warnings,
806 * indexer.warnings(checkpoint.diagnostics)],
807 }
808
809
810 @reporting ( "blob-capture" )
811 def recover (input_path, acknowledgement_path, directory, * , token_provider = azure_cli_token,
812 transport = http_request, storage_transport = http_request, context_provider = account_context,
813 progress: Progress | None = None ):
814 progress.update( "evidence-validation" )
815 plan, acknowledgement = _load(input_path, acknowledgement_path, acknowledgement = True )
816 historical = _historical_result(acknowledgement_path, acknowledgement)
817 directory = private_io.private_directory( str (directory))
818 progress.update( "context-check" )
819 if context_provider() != acknowledgement[ "context_digest" ]:
820 raise fail( "recheck-auth-context-drift" , "Current context differs from the acknowledged original run." )
821 progress.update( "source-binding" )
822 _, get, ids = _reader(transport, token_provider)
823 diagnostics, observations, binding_observations = [], [], []
824 if acknowledgement[ "kind" ] == "blob-write-acknowledgement" :
825 write = acknowledgement[ "write" ]
826 current = get(search_reconcile.resource_url(plan[ "source" ]))
827 if (_etag(current) != search_reconcile.resolve_etag(write[ "response_etags" ], write[ "request_id" ])
828 or search_reconcile._definition(current) != search_reconcile._definition(plan[ "source" ][ "desired" ])):
829 raise fail( "definition-drift" , "Current source differs from the acknowledged conditional-write version/definition." ,
830 request_id = getattr (get, "request_id" , None ))
831 generated = blob_source.generated_resources(current, strict = True )
832 if write[ "generated" ] and generated != write[ "generated" ]:
833 raise fail( "definition-drift" , "Generated identities differ from the create response." )
834 verified = search_reconcile._completed(
835 "create-blob-knowledge-source" , acknowledgement[ "plan_digest" ], plan[ "source" ],
836 action = "created" , readback = current, request_ids = [write[ "request_id" ], * ids], absence = False ,
837 )
838 creation = {key: verified[key] for key in ( "approved_plan" , "resources" , "verification" , "ownership" )}
839 creation[ "source" ] = { "generated" : generated}
840 else :
841 creation = acknowledgement[ "creation" ]
842 configuration = _configuration(plan, creation, creation[ "source" ][ "generated" ], get,
843 diagnostics = diagnostics, observations = observations)
844 progress.update( "blob-inventory" )
845 inventory = blob_inventory.discover(plan[ "boundary" ], plan[ "inventory_limits" ],
846 token_provider = token_provider, transport = storage_transport)
847 ids.extend(inventory[ "request_ids" ])
848 if inventory[ "inventory_digest" ] != plan[ "inventory_digest" ]:
849 raise fail( "source-drift" , "Storage/ACL inventory differs from the acknowledged original run." )
850 progress.update( "checkpoint" )
851 _configuration(plan, creation, creation[ "source" ][ "generated" ], get, expected = configuration,
852 diagnostics = diagnostics, observations = observations, binding_observations = binding_observations)
853 if context_provider() != acknowledgement[ "context_digest" ]:
854 raise fail( "recheck-auth-context-drift" , "CLI context changed during recovery observation." )
855 receipt = { ** acknowledgement, "schema_version" : "3.0" , "kind" : "blob-readiness-checkpoint" , "configuration" : configuration,
856 "creation" : creation, "request_ids" : ids}
857 receipt.pop( "write" , None )
858 receipt.pop( "integrity" )
859 receipt[ "integrity" ] = digest(receipt)
860 path = private_io.private_file(directory, receipt[ "operation_id" ] + ".blob-readiness.json" , receipt)
861 return {
862 "status" : "completed" , "outcome" : "blob-readiness-recovery-capture" , "writes_performed" : [],
863 "recheck_checkpoint" : { "operation_id" : receipt[ "operation_id" ], "receipt_file" : path.name,
864 "evidence_digest" : receipt[ "integrity" ], "status" : "retained" },
865 "readiness" : { "status" : "unverified" }, "retrieval" : "unverified" , "knowledge_base" : "not-verified" ,
866 "ownership" : { "run_owned" : [], "reused_not_owned" : []},
867 "original_run" : { "ownership" : creation[ "ownership" ], "not_before" : receipt[ "not_before" ],
868 "historical_created" : copy.deepcopy(creation[ "resources" ][ "created" ]),
869 "original_failure" : historical,
870 "acknowledgement_digest" : acknowledgement[ "integrity" ]},
871 "read_only_evidence" : { "request_ids" : ids},
872 "indexer_diagnostics" : semantic.indexer_only(diagnostics),
873 "generated_diagnostics" : diagnostics, "indexer_observations" : observations,
874 "datasource_binding_observations" : binding_observations,
875 "safe_next_decision" : "Run blob_recheck.py --input with the unchanged original input and --receipt with this checkpoint; then return to the KB/retrieval owner. Preserve the original first failure separately." ,
876 "warnings" : [blob_source. SNAPSHOT_WARNING , "Configuration was observed during recovery; no earlier generated revision or new ownership is asserted." ,
877 * get.recovery_warnings,
878 * indexer.warnings(diagnostics)],
879 }
880
881
882 @reporting ( "blob-recheck" )
883 def recheck (input_path, receipt_path, * , token_provider = azure_cli_token,
884 transport = http_request, storage_transport = http_request,
885 context_provider = account_context, monotonic = time.monotonic, sleep = time.sleep,
886 now =lambda : datetime.now(timezone.utc), progress: Progress | None = None ,
887 binding_paths = None , watch_limits = None , cancelled =lambda : False ):
888 progress.update( "evidence-validation" )
889 binding_receipts = _binding_receipts(binding_paths)
890 plan, receipt = _load(input_path, receipt_path, binding_receipts = binding_receipts)
891 limits = blob_source._poll_limits(watch_limits if watch_limits is not None else plan[ "poll" ])
892 creation = receipt[ "creation" ]
893 generated = creation[ "source" ][ "generated" ]
894 readiness = { "status" : "unverified" }
895 historical = "not-recorded-by-pre-monitor-checkpoint"
896 ids = []
897 recovery_warnings = []
898 diagnostics, observations, binding_observations = [], [], []
899 try :
900 historical = _historical_result(receipt_path, receipt)
901 progress.update( "context-check" )
902 if context_provider() != receipt[ "context_digest" ]:
903 raise fail( "recheck-auth-context-drift" , "Current CLI tenant/subscription/principal differs from the original run; no auth changes were made." )
904 read, get, ids = _reader(transport, token_provider)
905 recovery_warnings = get.recovery_warnings
906
907 def inventory ():
908 inventory = blob_inventory.discover(
909 plan[ "boundary" ], plan[ "inventory_limits" ], token_provider = token_provider,
910 transport = storage_transport,
911 )
912 ids.extend(inventory[ "request_ids" ])
913 if inventory[ "inventory_digest" ] != plan[ "inventory_digest" ]:
914 raise fail( "source-drift" , "Selected Storage/ACL evidence differs from original creation." )
915
916 for stage in ( "before" , "after" ):
917 if stage == "after" :
918 progress.update( "blob-readback" )
919 inventory()
920 progress.update( "source-binding" if stage == "before" else "source-readback" )
921 _configuration(plan, creation, generated, get, binding_receipts = binding_receipts,
922 expected = receipt[ "configuration" ], diagnostics = diagnostics, observations = observations,
923 binding_observations = binding_observations)
924 if stage == "before" :
925 progress.update( "blob-inventory" )
926 inventory()
927 readiness = blob_source.monitor(
928 plan[ "source" ], not_before = blob_source._timestamp(receipt[ "not_before" ]),
929 limits = limits, token_provider = token_provider, transport = read,
930 monotonic = monotonic, sleep = sleep, progress = progress,
931 excluded_cycle = receipt.get( "excluded_cycle" ),
932 cancelled = cancelled,
933 indexer_name = next (item[ "name" ] for item in generated if item[ "type" ] == "indexer" ),
934 )
935 if readiness[ "status" ] != "verified" :
936 raise HelperFailure(
937 readiness[ "code" ], "Original-run ingestion remains unverified." ,
938 blocked_at = "verification" , request_id = readiness.get( "request_id" ),
939 status = readiness.get( "http_status" ),
940 )
941 cycle = readiness[ "synchronization" ]
942 if (cycle[ "itemsUpdatesProcessed" ] == 0 or cycle[ "itemsSkipped" ]
943 or blob_source._timestamp(cycle[ "endTime" ]) > now()):
944 raise fail( "ingestion-unverified" , "A checkpoint is not prior ingestion proof; nonempty zero-skip completion is required." )
945 progress.update( "context-readback" )
946 if context_provider() != receipt[ "context_digest" ]:
947 raise fail( "recheck-auth-context-drift" , "CLI context changed during recheck." )
948 except HelperFailure as failure:
949 recovery_warnings.extend(failure.warnings)
950 if failure.request_id and failure.request_id not in ids:
951 ids.append(failure.request_id)
952 safe_failure = HelperFailure(
953 failure.code, failure.message if failure.blocked_at == "indexer-verification" else
954 "Read-only evidence could not verify readiness; service details withheld." ,
955 blocked_at = failure.blocked_at, status = failure.http_status, request_id = failure.request_id,
956 )
957 result = blocked_result(safe_failure, outcome = "blob-readiness-recheck" , fingerprint = None )
958 result[ "safe_next_decision" ] = "Preserve the first failure. GET the exact source/generated definitions; for a concealed source binding supply --reuse-input-file/--reuse-result-file from its successful creation. Fix access or investigate actual drift read-only; do not replay creation, run/reset an indexer or default to cleanup."
959 if failure.code == "datasource-binding-unverified" :
960 result[ "first_blocker" ][ "message" ] = (
961 "The datasource revision lacks independently observed current Storage binding; "
962 "this is unverified, not evidence of misconfiguration."
963 )
964 result[ "safe_next_decision" ] = (
965 "Retain source and checkpoint. One exact datasource GET with includeConnectionString=true did not "
966 "provide stable sanitized ResourceId proof. "
967 "Ask the service owner for authoritative current child binding evidence; root identity and historical "
968 "receipts are insufficient. Resume the same GET-only recheck if that readback becomes available. "
969 "Do not retrieve keys, patch/recreate the source, run/reset an indexer, or delete resources."
970 )
971 if failure.code == "indexer-legacy-evidence-insufficient" :
972 result[ "safe_next_decision" ] = "Retain the legacy checkpoint unchanged; GET current definitions and compare any legitimately retained preimage. This helper cannot infer its missing projection. A separately planned --capture reuse operation may observe fresh readiness, not recover historical configuration/ownership. Do not replay writes."
973 readiness = { ** readiness, "status" : "unverified" }
974 if readiness.get( "watch" , {}).get( "state" ) == "paused" :
975 result[ "safe_next_decision" ] = readiness[ "safe_next_decision" ]
976 else :
977 result = { "status" : "completed" , "outcome" : "blob-readiness-recheck" , "writes_performed" : []}
978 result.update(
979 readiness = readiness, retrieval = "unverified" , knowledge_base = "not-verified" ,
980 original_run = { "operation_id" : receipt[ "operation_id" ], "plan_digest" : receipt[ "plan_digest" ],
981 "source_action" : plan[ "source" ][ "action" ],
982 "evidence_digest" : receipt[ "integrity" ], "not_before" : receipt[ "not_before" ],
983 "request_ids" : creation[ "verification" ][ "request_ids" ],
984 "checkpoint_request_ids" : receipt[ "request_ids" ],
985 "original_failure" : historical,
986 "historical_created" : copy.deepcopy(creation[ "resources" ][ "created" ]),
987 "ownership" : copy.deepcopy(creation[ "ownership" ]), "generated" : generated},
988 ownership = { "run_owned" : [], "reused_not_owned" : []},
989 read_only_evidence = { "request_ids" : ids},
990 indexer_diagnostics = semantic.indexer_only(diagnostics),
991 generated_diagnostics = diagnostics, indexer_observations = observations,
992 datasource_binding_observations = binding_observations,
993 cleanup = { "status" : "not-requested" , "separate_confirmation_required" : True },
994 recheck_checkpoint = { "operation_id" : receipt[ "operation_id" ], "receipt_file" : Path(receipt_path).name,
995 "evidence_digest" : receipt[ "integrity" ], "status" : "retained" },
996 warnings = [blob_source. SNAPSHOT_WARNING ,
997 * recovery_warnings,
998 "Local checkpoint integrity is not a service signature or new ownership/cleanup authorization." ,
999 * indexer.warnings(diagnostics)],
1000 )
1001 return result
1002
1003
1004 def compact_result (result, directory):
1005 """Opt-in versioned presentation; retain the native result privately first."""
1006 reject_secrets(result)
1007 directory = private_io.private_directory( str (directory))
1008 path = private_io.private_file(directory, uuid.uuid4().hex + ".blob-result.json" , result)
1009 ids = []
1010
1011 def collect (value, key = None ):
1012 if isinstance (value, dict ):
1013 for field, child in value.items():
1014 collect(child, field)
1015 elif isinstance (value, list ):
1016 for child in value:
1017 collect(child, key)
1018 elif (key in { "request_id" , "request_ids" } or isinstance (key, str ) and key.endswith( "_request_ids" )) and isinstance (value, str ):
1019 ids.append(value)
1020
1021 collect(result)
1022 failure = result.get( "first_failure" , result.get( "first_blocker" ))
1023 original = result.get( "original_run" , {})
1024 writes = result.get( "writes_performed" , result.get( "completed_writes" , []))
1025 readiness = result.get( "readiness" , { "status" : "unverified" })
1026 item_error = readiness.get( "first_error" )
1027 if readiness.get( "code" ) == "ingestion-timeout" and readiness.get( "watch" , {}).get( "state" ) == "paused" :
1028 failure = None
1029 elif failure and isinstance (item_error, dict ) and item_error.get( "request_id" ):
1030 failure = { ** failure, "request_id" : item_error[ "request_id" ]}
1031 historical = original.get( "original_failure" )
1032 historical_failure = historical.get( "first_failure" ) if isinstance (historical, dict ) else None
1033 historical_watch = historical.get( "watch" ) if isinstance (historical, dict ) else None
1034 if (historical_failure and historical_failure.get( "code" ) == "ingestion-timeout"
1035 and isinstance (historical_watch, dict ) and historical_watch.get( "schema_version" ) == "1.0"
1036 and historical_watch.get( "state" ) == "paused" ):
1037 historical_failure = None
1038 generated = original.get( "generated" , result.get( "source" , {}).get( "generated" , []))
1039 diagnostic_keys = ( "severity" , "code" , "field" )
1040 diagnostics = dict .fromkeys(
1041 tuple (item[key] for key in diagnostic_keys)
1042 for item in result.get( "generated_diagnostics" , result.get( "indexer_diagnostics" , []))
1043 )
1044 return {
1045 "schema_version" : "1.1" if "progress" in readiness else "1.0" , "kind" : "blob-operation-summary" ,
1046 "status" : result[ "status" ], "outcome" : result[ "outcome" ],
1047 "writes_performed" : writes,
1048 "creation_acknowledgement" : result.get( "creation_acknowledgement" ),
1049 "historical_created" : [
1050 { "type" : item[ "type" ], "name" : item[ "name" ]}
1051 for item in original.get( "historical_created" , [])
1052 ],
1053 "generated_resources" : [{ "type" : item[ "type" ], "name" : item[ "name" ]} for item in generated],
1054 "readiness" : { "status" : readiness[ "status" ], "watch" : readiness.get( "watch" ),
1055 ** ({ "progress" : readiness[ "progress" ]} if "progress" in readiness else {})},
1056 "first_failure" : ({key: failure.get(key) for key in ( "code" , "status" , "request_id" )}
1057 if failure else None ),
1058 "historical_failure" : ({key: historical_failure.get(key) for key in ( "code" , "status" , "request_id" )}
1059 if historical_failure else None ),
1060 "first_retry" : readiness.get( "first_retry" ),
1061 "diagnostics" : [ dict ( zip (diagnostic_keys, item)) for item in diagnostics],
1062 "request_id_count" : len ( set (ids)), "evidence_file" : path.name,
1063 "checkpoint_file" : result.get( "recheck_checkpoint" , {}).get( "receipt_file" ),
1064 "retrieval" : "unverified" , "knowledge_base" : "not-verified" ,
1065 "safe_next_decision" : result.get( "safe_next_decision" ,
1066 "Return to the KB/retrieval owner; no cleanup or new writes are authorized."
1067 if readiness[ "status" ] == "verified" else
1068 "Retain resources and inspect the named source/indexer and private evidence. "
1069 "Use receipt-backed GET-only recheck when evidence is available; no write replay or cleanup." ),
1070 }
1071
1072
1073 def main (argv = None ):
1074 parser = argparse.ArgumentParser()
1075 modes = parser.add_mutually_exclusive_group( required = True )
1076 modes.add_argument( "--input" , type = Path)
1077 modes.add_argument( "--capture" , type = Path)
1078 modes.add_argument( "--recover" , type = Path)
1079 parser.add_argument( "--receipt" , type = Path)
1080 parser.add_argument( "--receipt-dir" , type = Path)
1081 parser.add_argument( "--reuse-input-file" , type = Path)
1082 parser.add_argument( "--reuse-result-file" , type = Path)
1083 parser.add_argument( "--watch-seconds" , type = int )
1084 parser.add_argument( "--watch-max-requests" , type = int )
1085 parser.add_argument( "--watch-interval" , type = int )
1086 parser.add_argument( "--compact" , action = "store_true" )
1087 add_progress_argument(parser)
1088 args = parser.parse_args(argv)
1089 if (args.capture and ( not args.receipt_dir or args.receipt)
1090 or args.input and ( not args.receipt or args.receipt_dir)
1091 or args.recover and ( not args.receipt or not args.receipt_dir)):
1092 parser.error( "--capture needs --receipt-dir; --input needs --receipt; --recover needs both." )
1093 if bool (args.reuse_input_file) != bool (args.reuse_result_file) or args.recover and args.reuse_input_file:
1094 parser.error( "Select both reuse evidence files, only with --capture or --input." )
1095 options = { "binding_paths" : (args.reuse_input_file, args.reuse_result_file)} if args.reuse_input_file else {}
1096 watch = (args.watch_seconds, args.watch_max_requests, args.watch_interval)
1097 if any (value is not None for value in watch):
1098 if not args.input or any (value is None for value in watch):
1099 parser.error( "Explicit GET-only watch needs --input and all three --watch-* limits." )
1100 options[ "watch_limits" ] = dict ( zip (( "deadline_seconds" , "max_requests" , "interval_seconds" ), watch))
1101 try :
1102 if args.capture:
1103 result = capture(args.capture, args.receipt_dir, progress = Progress( "blob-capture" , enabled = args.progress), ** options)
1104 elif args.recover:
1105 result = recover(args.recover, args.receipt, args.receipt_dir,
1106 progress = Progress( "blob-capture" , enabled = args.progress))
1107 else :
1108 result = recheck(args.input, args.receipt, progress = Progress( "blob-recheck" , enabled = args.progress), ** options)
1109 except HelperFailure as failure:
1110 outcome = "blob-readiness-recovery-capture" if args.recover else "blob-readiness-capture" if args.capture else "blob-readiness-recheck"
1111 result = blocked_result(failure, outcome = outcome, fingerprint = None )
1112 result[ "safe_next_decision" ] = (
1113 "Preserve original error/resources. Inspect the exact source and genuine private evidence: "
1114 "--recover needs its retained acknowledgement; --capture needs a reuse plan and any required "
1115 "creation proof. If a checkpoint already exists, inspect it and use --input/--receipt. "
1116 "No write replay, new ownership or cleanup is authorized."
1117 )
1118 if args.compact:
1119 try :
1120 result = compact_result(result, args.receipt_dir or args.receipt.parent)
1121 except HelperFailure as failure:
1122 result.setdefault( "warnings" , []).append( "compact-evidence-persistence-failed: full native result retained in output." )
1123 result[ "presentation_failure" ] = { "code" : failure.code}
1124 emit_result(result)
1125 return 2
1126 emit_result(result)
1127 return 0 if result[ "status" ] == "completed" else 2
1128
1129
1130 if __name__ == "__main__" :
1131 sys.exit(main())