99
1010Fail-closed application of the control-plane-delivered policy:
1111
12- * :meth:`ByocJob.ensure_governed` must pass (policy present, callback token
13- present, bundle resolvable) or the job is refused before any GUI is touched;
12+ * :meth:`ByocJob.ensure_governed` must pass (policy, callback token, closed
13+ substrate, and exact archive SHA present) or the job is refused before any
14+ GUI is touched; customer storage must then return the exact approved ZIP
15+ bytes, whose digest is checked before safe extraction;
1416* if the org enabled a grounding rung (``grounding_model.enabled``) whose
1517 ``api_key_env`` is NOT set in the Connector's own environment, the job is
1618 refused — the org required a governed control this machine cannot honor, so we
3133
3234from __future__ import annotations
3335
36+ import hashlib
3437import json
3538import os
3639import subprocess
4245
4346from openadapt_flow .connector .config import ConnectorSettings
4447from openadapt_flow .connector .protocol import ByocGovernanceError , ByocJob
45- from openadapt_flow .connector .storage import CustomerStorage
48+ from openadapt_flow .connector .storage import CustomerStorage , extract_bundle_archive
4649
4750#: A run-gate refusal (fail-closed admission denied) exits 2 before the replay
4851#: creates report.json.
@@ -69,6 +72,7 @@ class ExecutionResult:
6972 halt : Optional [dict [str , Any ]]
7073 report_ref : Optional [str ]
7174 error : Optional [str ] = None
75+ verified_bundle_sha256 : Optional [str ] = None
7276
7377
7478def _grounding_env_available (job : ByocJob ) -> bool :
@@ -82,6 +86,14 @@ def _grounding_env_available(job: ByocJob) -> bool:
8286 return bool (os .environ .get (gm .api_key_env ))
8387
8488
89+ def _sha256_file (path : Path ) -> str :
90+ digest = hashlib .sha256 ()
91+ with path .open ("rb" ) as handle :
92+ for chunk in iter (lambda : handle .read (1024 * 1024 ), b"" ):
93+ digest .update (chunk )
94+ return digest .hexdigest ()
95+
96+
8597def build_run_argv (
8698 job : ByocJob ,
8799 settings : ConnectorSettings ,
@@ -248,18 +260,27 @@ def execute_job(
248260
249261 with tempfile .TemporaryDirectory (prefix = "oa-byoc-" ) as tmp :
250262 tmp_path = Path (tmp )
263+ bundle_archive = tmp_path / "approved-bundle.zip"
251264 bundle_scratch = tmp_path / "bundle"
252265 run_dir = tmp_path / "run"
253266 bundle_scratch .mkdir (parents = True , exist_ok = True )
254267 run_dir .mkdir (parents = True , exist_ok = True )
255268
256269 _write_policy_audit (job , run_dir )
257270
258- # 2. Resolve the bundle from the CUSTOMER'S OWN storage (never our URL).
271+ # 2. Copy and verify the EXACT approved archive bytes from the customer's
272+ # storage before extraction. The content digest inside workflow.json is
273+ # a separate semantic binding; neither can substitute for this signed
274+ # archive-byte identity.
259275 try :
260- bundle_dir = storage .fetch_bundle (
261- job .storage .bundle_ref if job .storage else None , bundle_scratch
276+ fetched_archive = storage .fetch_bundle_archive (
277+ job .storage .bundle_ref if job .storage else None , bundle_archive
262278 )
279+ observed_sha256 = _sha256_file (fetched_archive )
280+ if observed_sha256 != job .bundle_sha256 :
281+ raise RuntimeError ("bundle archive SHA-256 does not match dispatch" )
282+ extract_bundle_archive (fetched_archive , bundle_scratch )
283+ bundle_dir = bundle_scratch
263284 except Exception as exc : # storage failure — fail closed, PHI-free msg
264285 return ExecutionResult (
265286 "failed" ,
@@ -269,11 +290,21 @@ def execute_job(
269290 f"customer-storage bundle fetch failed: { type (exc ).__name__ } " ,
270291 )
271292
272- params_file = _write_params_file (job .params , run_dir )
293+ try :
294+ params_file = _write_params_file (job .params , run_dir )
273295
274- # 3. The governed, fail-closed child invocation.
275- argv = build_run_argv (job , settings , Path (bundle_dir ), run_dir , params_file )
276- outcome = runner (argv , run_dir )
296+ # 3. The governed, fail-closed child invocation.
297+ argv = build_run_argv (job , settings , Path (bundle_dir ), run_dir , params_file )
298+ outcome = runner (argv , run_dir )
299+ except Exception as exc :
300+ return ExecutionResult (
301+ "failed" ,
302+ {},
303+ None ,
304+ job .report_ref (),
305+ f"governed child invocation failed: { type (exc ).__name__ } " ,
306+ verified_bundle_sha256 = observed_sha256 ,
307+ )
277308
278309 report = outcome .report or {}
279310 status = status_from_report (outcome .returncode , report )
@@ -291,13 +322,14 @@ def execute_job(
291322 report_ref = job .report_ref ()
292323 try :
293324 storage .write_report (report_ref , report )
294- except Exception as exc : # pragma: no cover - report persist best-effort
325+ except Exception as exc : # no durable report means the run is not complete
295326 return ExecutionResult (
296- status ,
327+ "failed" ,
297328 metrics_from_report (report ),
298329 halt_object (report ),
299330 report_ref ,
300331 f"customer-storage report write failed: { type (exc ).__name__ } " ,
332+ verified_bundle_sha256 = observed_sha256 ,
301333 )
302334
303335 return ExecutionResult (
@@ -306,4 +338,5 @@ def execute_job(
306338 halt = halt_object (report ),
307339 report_ref = report_ref ,
308340 error = outcome_error ,
341+ verified_bundle_sha256 = observed_sha256 ,
309342 )
0 commit comments