From 8ccf2cf3360b7d7720af04c36eee4f8e756bdf6e Mon Sep 17 00:00:00 2001 From: abrichr Date: Wed, 4 Nov 2026 20:10:13 -0500 Subject: [PATCH 01/10] test(flow): freeze historical bundle compatibility seals Signed-off-by: abrichr --- .../2.2.0/linear/canonical.json | 1 + .../2.2.0/linear/manifest.json | 22 + .../2.2.0/linear/templates/button.png | Bin 0 -> 579 bytes .../2.2.0/linear/workflow.json | 103 +++ .../2.2.0/program/canonical.json | 1 + .../2.2.0/program/manifest.json | 22 + .../2.2.0/program/templates/button.png | Bin 0 -> 579 bytes .../2.2.0/program/workflow.json | 133 ++++ .../2.2.0/provenance.json | 505 +++++++++++++ .../2.5.0/linear/canonical.json | 1 + .../2.5.0/linear/manifest.json | 22 + .../2.5.0/linear/templates/button.png | Bin 0 -> 579 bytes .../2.5.0/linear/workflow.json | 112 +++ .../2.5.0/program/canonical.json | 1 + .../2.5.0/program/manifest.json | 22 + .../2.5.0/program/templates/button.png | Bin 0 -> 579 bytes .../2.5.0/program/workflow.json | 142 ++++ .../2.5.0/provenance.json | 673 ++++++++++++++++++ .../2.6.0/linear/canonical.json | 1 + .../2.6.0/linear/manifest.json | 22 + .../2.6.0/linear/templates/button.png | Bin 0 -> 579 bytes .../2.6.0/linear/workflow.json | 112 +++ .../2.6.0/program/canonical.json | 1 + .../2.6.0/program/manifest.json | 22 + .../2.6.0/program/templates/button.png | Bin 0 -> 579 bytes .../2.6.0/program/workflow.json | 142 ++++ .../2.6.0/provenance.json | 660 +++++++++++++++++ .../fixtures/bundle_compatibility/README.md | 29 + .../generate_release_fixture.py | 146 ++++ 29 files changed, 2895 insertions(+) create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/canonical.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/manifest.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/templates/button.png create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/workflow.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/canonical.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/manifest.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/templates/button.png create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/workflow.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.2.0/provenance.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/canonical.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/manifest.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/templates/button.png create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/workflow.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/canonical.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/manifest.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/templates/button.png create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/workflow.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.5.0/provenance.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/canonical.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/manifest.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/templates/button.png create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/workflow.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/canonical.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/manifest.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/templates/button.png create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/workflow.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/2.6.0/provenance.json create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/README.md create mode 100644 packages/flow/tests/fixtures/bundle_compatibility/generate_release_fixture.py diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/canonical.json b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/canonical.json new file mode 100644 index 0000000000..4bc7b3c700 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/canonical.json @@ -0,0 +1 @@ +{"files":{"templates/button.png":"fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493"},"workflow":{"contains_phi":false,"created_at":"2026-01-02T03:04:05+00:00","data_sources":{},"encrypted":false,"name":"release-compatibility","param_specs":{},"params":{},"phi_scrubbed":false,"program":null,"recording_id":null,"schema_version":2,"secret_params":[],"steps":[{"action":"click","anchor":{"click_point":[80,70],"context_text":null,"identifier_crop":null,"identifier_region":null,"identity_template":null,"landmarks":[],"ocr_text":"Apply","region":[40,50,80,40],"search_pad":80,"structural":{"automation_id":null,"name":null,"role":null,"selector":"#apply","window_name":null},"structured_identity":null,"template":"templates/button.png"},"api_binding":null,"effects":[],"expect":[],"guard":null,"id":"apply","identifier_crop_missing_reason":null,"identity_armed":null,"identity_unarmed_reason":null,"intent":"apply synthetic change","key":null,"param":null,"risk":"reversible","scroll_dx":null,"scroll_dy":null,"secret":false,"text":null,"timeout_s":10.0,"wait_until":null}],"subflows":{},"viewport":[320,200]}} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/manifest.json b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/manifest.json new file mode 100644 index 0000000000..fa6a38875a --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/manifest.json @@ -0,0 +1,22 @@ +{ + "schema_version": 2, + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.2.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/templates/button.png b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/templates/button.png new file mode 100644 index 0000000000000000000000000000000000000000..797e0782c988cdc7fe1b99c3613d3c826f48477b GIT binary patch literal 579 zcmV-J0=)f+P)TsqS%X#kV3K3Ld!q|A%z19K_Xa*jd%|ME3vh;u(PvL!Td;wHd=^RV4+|d z#a2)W1ixZnSzMQIVMlj~^Hh_=WH=8q2`S$;8;mSyYpdN!Ly2yM69y9tKp`ORhn0LSAol}cT&SF+tOgoRDh6a*m_i&>Us z+cp5$ww+8SbzQGiDzYr!O)!!qDT)FBhGFFMd5_0Kwi|}9u%alvUQZB&{eC~6&vBT` z%HJ8uNnavW#9Ue9JT05DB+JRWB<8HQoJUN4^D z^Z5YabAkzn!wkdhc00?m5{U%CVjm&7!;8ft9*^TO$8nR%1OObz!7s4EV8HYI-Q=A@ zp&-k$AP59o-9S=UQIvE#jmLpNAQ%j;Rx7{XKO7DbLj8Wf(P#j`=Oh}9wpuL!5Jhn` z8a?hj1KA_pY&Jh$;s+7{+U@qu%$sC7o#Mgea^W~G5{VFOb;BR}($X}oR4U;;e;g!t z_%4=Z{WrlNgb+fH?(>Is_&HEvw?l>94i$DgRM_oMVYfqt-OkGidtU9grGL*i#q_=y RoT~r;002ovPDHLkV1k8;0VMzc literal 0 HcmV?d00001 diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/workflow.json b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/workflow.json new file mode 100644 index 0000000000..57f4091e52 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/linear/workflow.json @@ -0,0 +1,103 @@ +{ + "schema_version": 2, + "name": "release-compatibility", + "recording_id": null, + "contains_phi": false, + "phi_scrubbed": false, + "encrypted": false, + "created_at": "2026-01-02T03:04:05+00:00", + "viewport": [ + 320, + 200 + ], + "params": {}, + "param_specs": {}, + "secret_params": [], + "steps": [ + { + "id": "apply", + "intent": "apply synthetic change", + "action": "click", + "anchor": { + "template": "templates/button.png", + "structural": { + "selector": "#apply", + "frame_path": null, + "role": null, + "name": null, + "automation_id": null, + "window_name": null + }, + "region": [ + 40, + 50, + 80, + 40 + ], + "click_point": [ + 80, + 70 + ], + "ocr_text": "Apply", + "context_text": null, + "structured_identity": null, + "identity_template": null, + "identifier_crop": null, + "identifier_region": null, + "landmarks": [], + "search_pad": 80 + }, + "text": null, + "param": null, + "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, + "key": null, + "modifiers": [], + "drag_end_anchor": null, + "scroll_dx": null, + "scroll_dy": null, + "expect": [], + "effects": [], + "api_binding": null, + "risk": "reversible", + "risk_explanation": null, + "risk_review_required": false, + "wait_until": null, + "guard": null, + "timeout_s": 10.0, + "identity_armed": null, + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "submit_binding": null, + "submit_binding_unbound_reason": null + } + ], + "interstitials": [], + "program": null, + "subflows": {}, + "data_sources": {}, + "manifest": { + "schema_version": 2, + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.2.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false + } +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/canonical.json b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/canonical.json new file mode 100644 index 0000000000..0ee4c36a3b --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/canonical.json @@ -0,0 +1 @@ +{"files":{"templates/button.png":"fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493"},"workflow":{"contains_phi":false,"created_at":"2026-01-02T03:04:05+00:00","data_sources":{},"encrypted":false,"name":"release-compatibility","param_specs":{},"params":{},"phi_scrubbed":false,"program":{"entry":"apply","states":{"apply":{"id":"apply","kind":"action","loop":null,"on_exception":null,"outcome":null,"reason":"","step":{"action":"click","anchor":{"click_point":[80,70],"context_text":null,"identifier_crop":null,"identifier_region":null,"identity_template":null,"landmarks":[],"ocr_text":"Apply","region":[40,50,80,40],"search_pad":80,"structural":{"automation_id":null,"name":null,"role":null,"selector":"#apply","window_name":null},"structured_identity":null,"template":"templates/button.png"},"api_binding":null,"effects":[],"expect":[],"guard":null,"id":"apply","identifier_crop_missing_reason":null,"identity_armed":null,"identity_unarmed_reason":null,"intent":"apply synthetic change","key":null,"param":null,"risk":"reversible","scroll_dx":null,"scroll_dy":null,"secret":false,"text":null,"timeout_s":10.0,"wait_until":null},"subflow":null,"transitions":[{"guard":null,"label":"","target":"done"}]},"done":{"id":"done","kind":"terminal","loop":null,"on_exception":null,"outcome":"success","reason":"","step":null,"subflow":null,"transitions":[]}}},"recording_id":null,"schema_version":2,"secret_params":[],"steps":[],"subflows":{},"viewport":[320,200]}} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/manifest.json b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/manifest.json new file mode 100644 index 0000000000..7f449a30b0 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/manifest.json @@ -0,0 +1,22 @@ +{ + "schema_version": 2, + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.2.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/templates/button.png b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/templates/button.png new file mode 100644 index 0000000000000000000000000000000000000000..797e0782c988cdc7fe1b99c3613d3c826f48477b GIT binary patch literal 579 zcmV-J0=)f+P)TsqS%X#kV3K3Ld!q|A%z19K_Xa*jd%|ME3vh;u(PvL!Td;wHd=^RV4+|d z#a2)W1ixZnSzMQIVMlj~^Hh_=WH=8q2`S$;8;mSyYpdN!Ly2yM69y9tKp`ORhn0LSAol}cT&SF+tOgoRDh6a*m_i&>Us z+cp5$ww+8SbzQGiDzYr!O)!!qDT)FBhGFFMd5_0Kwi|}9u%alvUQZB&{eC~6&vBT` z%HJ8uNnavW#9Ue9JT05DB+JRWB<8HQoJUN4^D z^Z5YabAkzn!wkdhc00?m5{U%CVjm&7!;8ft9*^TO$8nR%1OObz!7s4EV8HYI-Q=A@ zp&-k$AP59o-9S=UQIvE#jmLpNAQ%j;Rx7{XKO7DbLj8Wf(P#j`=Oh}9wpuL!5Jhn` z8a?hj1KA_pY&Jh$;s+7{+U@qu%$sC7o#Mgea^W~G5{VFOb;BR}($X}oR4U;;e;g!t z_%4=Z{WrlNgb+fH?(>Is_&HEvw?l>94i$DgRM_oMVYfqt-OkGidtU9grGL*i#q_=y RoT~r;002ovPDHLkV1k8;0VMzc literal 0 HcmV?d00001 diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/workflow.json b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/workflow.json new file mode 100644 index 0000000000..9d677ab2c4 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/program/workflow.json @@ -0,0 +1,133 @@ +{ + "schema_version": 2, + "name": "release-compatibility", + "recording_id": null, + "contains_phi": false, + "phi_scrubbed": false, + "encrypted": false, + "created_at": "2026-01-02T03:04:05+00:00", + "viewport": [ + 320, + 200 + ], + "params": {}, + "param_specs": {}, + "secret_params": [], + "steps": [], + "interstitials": [], + "program": { + "entry": "apply", + "states": { + "apply": { + "id": "apply", + "kind": "action", + "step": { + "id": "apply", + "intent": "apply synthetic change", + "action": "click", + "anchor": { + "template": "templates/button.png", + "structural": { + "selector": "#apply", + "frame_path": null, + "role": null, + "name": null, + "automation_id": null, + "window_name": null + }, + "region": [ + 40, + 50, + 80, + 40 + ], + "click_point": [ + 80, + 70 + ], + "ocr_text": "Apply", + "context_text": null, + "structured_identity": null, + "identity_template": null, + "identifier_crop": null, + "identifier_region": null, + "landmarks": [], + "search_pad": 80 + }, + "text": null, + "param": null, + "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, + "key": null, + "modifiers": [], + "drag_end_anchor": null, + "scroll_dx": null, + "scroll_dy": null, + "expect": [], + "effects": [], + "api_binding": null, + "risk": "reversible", + "risk_explanation": null, + "risk_review_required": false, + "wait_until": null, + "guard": null, + "timeout_s": 10.0, + "identity_armed": null, + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "submit_binding": null, + "submit_binding_unbound_reason": null + }, + "loop": null, + "subflow": null, + "transitions": [ + { + "guard": null, + "target": "done", + "label": "" + } + ], + "on_exception": null, + "outcome": null, + "reason": "" + }, + "done": { + "id": "done", + "kind": "terminal", + "step": null, + "loop": null, + "subflow": null, + "transitions": [], + "on_exception": null, + "outcome": "success", + "reason": "" + } + } + }, + "subflows": {}, + "data_sources": {}, + "manifest": { + "schema_version": 2, + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.2.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false + } +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/provenance.json b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/provenance.json new file mode 100644 index 0000000000..511183f1b7 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.2.0/provenance.json @@ -0,0 +1,505 @@ +{ + "bundles": [ + { + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "files": { + "canonical.json": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "manifest.json": "e2867647468248a79343d3360464b146463a5d1bf8bbf7416d3e120ca83a8bc8", + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493", + "workflow.json": "6d3d8d97495c8469c9f4aacf373d1a7f0b2917bbfe1c4e8089fd04c4851fb15e" + }, + "shape": "linear" + }, + { + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "files": { + "canonical.json": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "manifest.json": "4e3888284ed3e7fdf38a65df0fef51f13861bd4ddf1023e427f7524fd66356aa", + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493", + "workflow.json": "015310b2acac4bcc38d3d4ad51a312f4f0539c2eb22ad2499deb45b54fbf4936" + }, + "shape": "program" + } + ], + "commit": "98c437f12b04f11e44fbc864e051252e480a848e", + "generator_sha256": "d995443471ee4a6fd6854e53a15da4d3681c798a5af9ffde7d3506d8c5015e28", + "original_release_load_verified": true, + "pydantic": "2.13.4", + "python": "3.12.16", + "release": "2.2.0", + "source_hashes": { + "packages/flow/openadapt_flow/__init__.py": "4fd8206219079fa507c86fd5de4a76a7ceb6f37c0cbe52b6c6bf93fe8f674205", + "packages/flow/openadapt_flow/__main__.py": "edb75bbd97a4e7089f2a5848fbe2a0e9cb076c5dab5f32c59cd417ae250c1acd", + "packages/flow/openadapt_flow/_browser_setup.py": "d259bd323de12fa03b4bf529d77ccb6350d50f665309c377e6df182525c37e6f", + "packages/flow/openadapt_flow/_prewarm.py": "4024f33b41a2fac44b995dfc85a922366b98c18697f6a2b623cb26d72264ed48", + "packages/flow/openadapt_flow/action_evidence.py": "d965cd391032e66d42c207330e1ae242ff3b4cb32756b1d167488cdc3346f317", + "packages/flow/openadapt_flow/adapters/__init__.py": "2eca3615b0a12e39a4a51fd7bf2c274785014c548e609597029ef7d8299c9a09", + "packages/flow/openadapt_flow/adapters/capture.py": "1bb420989121dfb787e3224fd77a87e124cb8404c655836748de823e3d04069f", + "packages/flow/openadapt_flow/adapters/desktop_recorder.py": "7f05b9bf03bc1da900d0acceae4cd4ae0a82efc4a3b9ef011c6649e1044b3454", + "packages/flow/openadapt_flow/admitted_composition.py": "0fcf3b21a233c9ccf0ee71fec2848a80f755fa941ba5f462a47dd544e8aeeeff", + "packages/flow/openadapt_flow/agent/__init__.py": "a08c79b09e45150bd065c5008a2585081ac6c10a1d9c4489dad01718fbb94973", + "packages/flow/openadapt_flow/agent/__main__.py": "5780b9a3b365c96d71fa42a6ea977a1518d711ed5c4b4f37b54b16ffbc9d0419", + "packages/flow/openadapt_flow/agent/approval.py": "a345a90e0e3fa788b520fd26150971a0a5aaff16173a663541f92845ecf409d7", + "packages/flow/openadapt_flow/agent/attended.py": "5fa4c761de375be9ce0f6cf7f72f0230253ccd7546658382a267da2f01cb0f1b", + "packages/flow/openadapt_flow/agent/authoring.py": "4726bc89fd69f9db972db851381b209c3862f6e4dee5632d4bfa59d4230d0127", + "packages/flow/openadapt_flow/agent/bridge.py": "a5e1e04977d373a5b1474bff1aeecafb0f25ce34a03878ad19dbff2c527e4646", + "packages/flow/openadapt_flow/agent/bundles.py": "a3e2d63576adc99fd8e4d53e0fa64f1bec914a070d5c0151ba9278ea923809c9", + "packages/flow/openadapt_flow/agent/cards.py": "45ecfb3da287aeb5de1160d1adbcc3590c6cc8c20f7b1b7ab983f643228f62b5", + "packages/flow/openadapt_flow/agent/cli.py": "5b67586e704f9ff28a76d83079d9ae15e2196bdb99f7ecd4e44792c2826a17ff", + "packages/flow/openadapt_flow/agent/contract.py": "008abebad7df519224e3fc952727186a1f65b042ac92c6da9f94e712a81722c4", + "packages/flow/openadapt_flow/agent/copy.py": "9afe666441689375fcb5aaf54632db2e39e0a32edeb95570789f0d7ce4b55423", + "packages/flow/openadapt_flow/agent/delegated.py": "49414a62704b93999fcf668db4df737121f4b5d9d22f1b36a94de705d171371e", + "packages/flow/openadapt_flow/agent/flow_service.py": "b5bb10bf1cb819bb0aba9185de9e9d1246a823ff343996a7464d8e481785f2dd", + "packages/flow/openadapt_flow/agent/mailbox.py": "410999800e632f47507929adc3426d68ebfd6625c1303893a23891cd4184f4a3", + "packages/flow/openadapt_flow/agent/mcp.py": "1be16fcbaaf82ddcc95716f1104adb4b88d26b496916bee9abdeba5d6e06399e", + "packages/flow/openadapt_flow/agent/mcpb_entry.py": "23db9f1b3af8bed52ee472b36b0272430d11526f045179602724addc92a22ac8", + "packages/flow/openadapt_flow/agent/repair.py": "d8490f2524e210a34158627a9f714601f933be925e9d3e5fd0a4713eafd3e614", + "packages/flow/openadapt_flow/agent/runner.py": "7f3440168984ec9af708190837b627480c5f1c9c8330d24509c3fe0ad209cb63", + "packages/flow/openadapt_flow/agent/runner_bind.py": "91d4006e99378c61af2eb50fc9c03ebfb477cdf9f0d27fa070ee9936fb073e96", + "packages/flow/openadapt_flow/agent/runs.py": "abd7041cd1eae1b6017c208e0cf4b083caf965cd2147d0497e896a013cd5cb25", + "packages/flow/openadapt_flow/agent/sandbox.py": "5f8d2b611a38a7cc00b255e2c32ccb67f54971b54acac373f13e48d77a8b34c9", + "packages/flow/openadapt_flow/agent/seal.py": "7d28c6df1e95e71bb6812889214fd48e477e6c1703070104360e22001d19ed4f", + "packages/flow/openadapt_flow/agent/seal_cli.py": "fcae30891db50c28e6a23282ff94a0dce9796c15c7fea46b8fdc34694ff8acc1", + "packages/flow/openadapt_flow/agent/service.py": "c8c0b6d83ec0c5f25933339802124369da66dfde9ab1ce942b3f622d8ec9960b", + "packages/flow/openadapt_flow/agent/skill.py": "7f1637d4bcfdb54ff5920b06dd3bbf913e6fdbc0030605b455e9223672ad2173", + "packages/flow/openadapt_flow/agent/tutorial.py": "ac42a64169cb23fac79943a0ef8e4678922d120d51aa97944e2be707addb3232", + "packages/flow/openadapt_flow/api.py": "7a34b5b4a7d5b3b815c4008d9d3efab1badddb77dac7477e17308602d1e532b1", + "packages/flow/openadapt_flow/approval_mode/__init__.py": "7d802c15089999853ed4a0b55bdaa1962998f2b8e9ee0170695c7de2ec5c3e10", + "packages/flow/openadapt_flow/approval_mode/approver.py": "401c5971cb4e330d8791e5e4e1b89101e730ae2b46701c6e3166a8f06691fe71", + "packages/flow/openadapt_flow/approval_mode/coverage.py": "1d91811ed3afeab6fdc5aab965e1076cf36fd8356da790e51fc3c1902cb0e84b", + "packages/flow/openadapt_flow/approval_mode/durable_carry.py": "89ae4481314e6da7b693765b6809f57852ccb934df23fe55379000b9e0c4b0a7", + "packages/flow/openadapt_flow/approval_mode/form_state.py": "f2548b7f2527e9d648623fc5bafa841feabe8272d6a5822e23d4c937e910ae53", + "packages/flow/openadapt_flow/approval_mode/gate.py": "b01d10ba090151c71295a605fac2d9ec26c1a7614ee302b2f7f8df4cbe88841e", + "packages/flow/openadapt_flow/approval_mode/item_approval.py": "7479ccc08c25c87cdcfcea4b46617a42e4cb508edcd6e27fbb62220c31e13e27", + "packages/flow/openadapt_flow/approval_mode/model.py": "42f121b2a7fc992bc1201ccb8c587d7134fec6fdbc54eb12dacd71ff4a00774a", + "packages/flow/openadapt_flow/approval_mode/post.py": "954d2114ae019bdb2291c05e188e231144c400592338a47a3b7c47ee20dfe165", + "packages/flow/openadapt_flow/approval_mode/requirements.py": "4f8ba1eff008e45651365929bbbaee873301d993c714168e78456009b06b402b", + "packages/flow/openadapt_flow/approval_mode/signed_policy.py": "4dc065c007d6c3112de2af004e36ecd8559ceac8a76eaf9853c1d49f7363c757", + "packages/flow/openadapt_flow/approval_mode/switch.py": "d52deeeba1eb097dcc2df472946da375c72e03407fb68e5fe580efe2317e7b57", + "packages/flow/openadapt_flow/assurance.py": "5b042e814da9a7aaa448d5d0247cf33f89fd67558dbb2e686e99d9c73cb5d318", + "packages/flow/openadapt_flow/attest_bridge.py": "449502e879bc629b851087b86353a0dbd5761508daec92989083f30fdad14fee", + "packages/flow/openadapt_flow/authoring.py": "54e35e803a0b70d6d3147f6663625d63baea8269d957723013cb58ec184fbb93", + "packages/flow/openadapt_flow/backend.py": "1c8e9ea8524e1bd8172f8dd1b96a55a1a75f2855c64f30d727b297e32af749ae", + "packages/flow/openadapt_flow/backends/__init__.py": "c76aa599a2ea0205a3fffb4a33124dbe6637e4b93f0f14d4c604ffa05c2a1869", + "packages/flow/openadapt_flow/backends/_browser_process.py": "1f90beb147f32ddc276884c3e6488e3e533a5bb374ccf675fadc16754cb4e137", + "packages/flow/openadapt_flow/backends/_page_quiescence.py": "2a8b70efd7f6b006fa8141b02a7e59fe5733e7552eeacfc7e34326c0dde147c0", + "packages/flow/openadapt_flow/backends/citrix_workspace.py": "2bc912cfecac2d7d1c4c543cda17a2cbe4b25af195fd8a1d015f6b02661e9665", + "packages/flow/openadapt_flow/backends/factory.py": "0d504c4763bcc11a21b838ff3d00c56d7894e7cd68798b48ed6b54c389936552", + "packages/flow/openadapt_flow/backends/linux_backend.py": "ff1d40d12c841088e827c572f1f0f886015b831bd2a23c95ceaf1223d1000420", + "packages/flow/openadapt_flow/backends/macos_backend.py": "03670f73a133f19edccee54a0be8c52dcf08391223ec9e902683b239a94e5423", + "packages/flow/openadapt_flow/backends/parallels_vm.py": "e724f9ec49210e5220d1cc508ab8a1ef984ac43854535b90548b209985109d0c", + "packages/flow/openadapt_flow/backends/playwright_backend.py": "35eb3030d07796ac9b5cbe6314b3e52ac3d0258e3260447be389ac2f90084990", + "packages/flow/openadapt_flow/backends/rdp_backend.py": "f0275c34c544c469bceca130021a0d2bfecdf42e1c1a5318edfafe57e20468b0", + "packages/flow/openadapt_flow/backends/remote_display.py": "838482cb5e45c921e9c70cb76b2b4c053c9d249947e39135dd40d82f0e79bc6c", + "packages/flow/openadapt_flow/backends/win32_window_client.py": "17003fee023495f45d18043c07768d5a569d8e0b615003822cfdb96c921315b8", + "packages/flow/openadapt_flow/backends/win_agent/__init__.py": "cdba9a2b13137a43d83ff03e4eb02c9e250fbaee84ddd6f174c72d5902da553a", + "packages/flow/openadapt_flow/backends/win_agent/server.py": "121a738e1349707212cf4c0534ea5604f066e7fc729f06b35580426fb3db7363", + "packages/flow/openadapt_flow/backends/win_agent/tls.py": "51f2af9d258c3acfd095d5a7c83d40d3719bbd2161be7963c03c84687f8922cc", + "packages/flow/openadapt_flow/backends/windows_backend.py": "0c37d81c063bf245d52cb281a846b37cc1faca46a17522517b6d78819d64d683", + "packages/flow/openadapt_flow/backends/xfreerdp.py": "d0605a82901222b870ae433cd6d2ec8ab49123917526af9663c1d177feb8325f", + "packages/flow/openadapt_flow/bench.py": "e90fc71ac1c2033a42b06b872de09550fdd9db02b175d176d250e91c207cedba", + "packages/flow/openadapt_flow/benchmark/__init__.py": "c5d84d097f5e007ebd1a4f86a3ae02c6a897939cdfa11f030b263e5e12c0278d", + "packages/flow/openadapt_flow/benchmark/agent_baseline.py": "74ac7a8df7b5ca25f31b53062fb89bce4154e10f1c01f2e09c801d87d663df53", + "packages/flow/openadapt_flow/benchmark/chart_fonts.py": "e60660a947e09a566567b6d84332d009e4b969f4a450b2cecc81299c5b07ba2e", + "packages/flow/openadapt_flow/benchmark/desktop_benchmark.py": "8ed16bb044b338db4814374ddadda57b151d1e7e2a4cb2474d45ce6703116e54", + "packages/flow/openadapt_flow/benchmark/dom_arm.py": "62dd7e06840a286d859b442b79ea9b7bd05df56cdc700f140cc2e5cf37d195cc", + "packages/flow/openadapt_flow/benchmark/effectbench/__init__.py": "045dfe7dbace5245e7f6ff742984b73cca9e36d973ac0ce4736167a04ae764e6", + "packages/flow/openadapt_flow/benchmark/effectbench/metrics.py": "65f74dbfeac3d8761265531b04618270cddca773fb30cbabd49ab79d2f01c95f", + "packages/flow/openadapt_flow/benchmark/effectbench/oracle.py": "ec8628819a2c2f29efe36124175b2d349499936efcfe2cedaec5ebb6da9b9fbd", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/__init__.py": "a1153140a9bd236aa7afbb551edd305e0d16e14e999ba575dc609d24760c2030", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/__main__.py": "eb36799a1ff3168eb049d25a500cca903d3a5e439283d84ade20f21300f3d312", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/arms.py": "8cc93711483ffa21e06796b2b9f100effc6811c33c3204e5e9ffd35137b06cd5", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/baselines.py": "9d235db2891ce32c088d89edba3553fbbc2911031d29a9ada5caa00c964dd7bd", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/compound.py": "c407b37ba39bf066214aa62014c920815be16dffef934f7f3478ee20ef3dc341", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/harness.py": "e7848000829d040a35f25ccbbb82041fd460664492dfecee1173092ab492671c", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/reference_tasks.py": "c588c8f654e44d035a091b4ca0c861c8e916592d63f387b75ba9702af8da3d7b", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/substrate.py": "0cb036fc7c0b97ff776e946de714a1904d0cc36846fcd1c86b4e7820f5c90542", + "packages/flow/openadapt_flow/benchmark/effectbench/schema.py": "640848a9ab5932006d2a65f9975b3369146aa1b892b2bd44b879dbe7fe11ef47", + "packages/flow/openadapt_flow/benchmark/frappe_lending.py": "4b9c17ab23191dba8ca81d03b924c14e3fdd12280fe9aabe7d4a7e7691229ef0", + "packages/flow/openadapt_flow/benchmark/hybrid_benchmark.py": "f37c53370526fe0b3c47a728c3003d6d59aa23b71471f3fc51ae545adaf931ea", + "packages/flow/openadapt_flow/benchmark/openemr_benchmark.py": "acb8bc88dc7a175f1f5eb90a6f83371a26e4082b8e1903a69f7d35f8d6737244", + "packages/flow/openadapt_flow/benchmark/openemr_local.py": "2f1e9156719eccb745a96d9ef16dc185d73346710d649046512c024b89a1d65a", + "packages/flow/openadapt_flow/benchmark/reliability.py": "4510a7983bf200528ebc9795436d15ad5d8cc19bbd9cd88266a17ef8d8d1ae5b", + "packages/flow/openadapt_flow/benchmark/run_benchmark.py": "6f001c4c53d7aa6abb744053c347f489c1ce343b117e76f347371179bfc3ace2", + "packages/flow/openadapt_flow/benchmark/silent_wrong_action.py": "072d3c16360ff5a1f66786196560d8c62948c38d1600ec99e78ba547b2fa90c5", + "packages/flow/openadapt_flow/benchmark/verify.py": "d823f244fdf423381cd30b7f6a2bb6322bf6ed4ee79634c670ae583f5bc48bbc", + "packages/flow/openadapt_flow/bundle_sealing.py": "a5b3d8f5ff9f350ee455b929bc34cb436e1945795997d59ab0db6b0ee90d94cb", + "packages/flow/openadapt_flow/bundle_validation.py": "1919432f951cc7a8ee035262e15d4d60329ffc5b22c2a71fca38330b786707a3", + "packages/flow/openadapt_flow/check_level.py": "114517071438d4445a2251eebe99e2d3a5379efe49d25d9c51a340cc88699ee8", + "packages/flow/openadapt_flow/cli_admit.py": "013025769ae2fcdfe4a91d8b8d5f3cbe80db42e345b300fbfaf26d5937c29699", + "packages/flow/openadapt_flow/cli_check.py": "12cdfc5c3ef3d1e900c1e0fb577fc46203016c1c5cb9405acda6dfc8156a2425", + "packages/flow/openadapt_flow/cli_compose.py": "965a63427ec37964451cdb00fad92065833b6f7cab936c80526c05984b5a68ec", + "packages/flow/openadapt_flow/cli_config.py": "a68b1b9db2c60feee3ec0b4ea079bbacc7b90bb052f9660c87b2e7067c593557", + "packages/flow/openadapt_flow/cli_fleet.py": "a4678b9b48c6c4817673fcccbd18be21966ae6d64dd89fb143bf26f3dce3c7ff", + "packages/flow/openadapt_flow/cli_hints.py": "de2a50cf8968951e13d4e328a5a2d89bc0d79741f3e2c1b628dbc18b12085dce", + "packages/flow/openadapt_flow/cli_known_ids.py": "ed965f2369489a87cefb27ea9c9241bdc9b6058c72fc56450006be9951737897", + "packages/flow/openadapt_flow/cli_local_seal.py": "a6bd43499da2bae9b26feb29b8914a8a919e47f1c38c383026ff1f23b07f06b3", + "packages/flow/openadapt_flow/cli_no_outbound.py": "1afedf45d1874e4aeeabc8cf1d36572f6df8b7ddf54bfcb75189d0bb0f149b47", + "packages/flow/openadapt_flow/cli_process.py": "9986948521929660937af2fbd37d0fd396acbc9fc613563d5363e0b1b9e89bbf", + "packages/flow/openadapt_flow/cli_service.py": "172c06cc25ada99b0532dee35aa4e4510671cc7e7b24871e8b7294abed8cea0e", + "packages/flow/openadapt_flow/cloud_seal.py": "dd473bf2ce29f708e1c1638d70f8b54a3feaf140f27aada14481f31c1d2517ad", + "packages/flow/openadapt_flow/compiler/__init__.py": "e38510f5baea14d9d357ab594954ff42f5aff608589ea8ae5d7060e8cf0fa380", + "packages/flow/openadapt_flow/compiler/annotate.py": "672ae37ffc79429a812ac607faec3adfd52d272d7fd386121bc728044d8f89de", + "packages/flow/openadapt_flow/compiler/binding_admission.py": "5113f27876d3109ebad880c75255e138c67375a7846d4966ac0ab9d9e2f69ee9", + "packages/flow/openadapt_flow/compiler/codegen.py": "8d986264d3e5ef17aa9bcde6b0b0e18ab312df9d5fbd9bce9e556fb96a292d2a", + "packages/flow/openadapt_flow/compiler/compile.py": "b6bc3d726698f070472f35bcd01dd219af616aa509c22e7fca70c340144e9289", + "packages/flow/openadapt_flow/compiler/compose_authoring.py": "84132f230f6cd811d86a1588d08809375282b61227e40f64f5f9fcf2807cfaa1", + "packages/flow/openadapt_flow/compiler/disambiguation.py": "2761afc6683f1b72b1126a2482bf1e02f34651489dd28f71a2f57aaddced7fb5", + "packages/flow/openadapt_flow/compiler/distill.py": "777d85d85072ef9e1a43fac066aa6767a9ff57a7c7c6ad0724db473342bc91bf", + "packages/flow/openadapt_flow/compiler/effect_mining.py": "5e2f472b0d7399e60a03af00103b8a215df3304e26dbe19d971c6619414010eb", + "packages/flow/openadapt_flow/compiler/fixup_passes.py": "166d6ac64aa6c5346428b479c4b874e557785bf2043aa71e7f6c2c4b61f84449", + "packages/flow/openadapt_flow/compiler/induction.py": "657f20a215735cbcd98dc2f3083d6f514d55bdecc6b046c83a6566764836ff50", + "packages/flow/openadapt_flow/compiler/loop_authoring.py": "f9ac89cc50e238a35411741aebb3eee7d76c2b263008957d21cdcb49e89b0ee9", + "packages/flow/openadapt_flow/compiler/param_confirm.py": "169fc5abea5ee1554342e6bd050b7fb271e4992767e86a471cc866216c6914eb", + "packages/flow/openadapt_flow/compiler/qualification_pins.py": "cd13d0909442a5ee0af9300731b25e313b70f2d69f2dc8441b660cfdd1e8c4c9", + "packages/flow/openadapt_flow/compiler/qualification_proposer.py": "d2a27d01b3fc5adf9042052bbcd8acff6381a45201bc87e217a0d12935aed73b", + "packages/flow/openadapt_flow/compiler/readback_authoring.py": "add4754707c7f9fdd6a49786bf66dc2995858175b51fe8cc1292d140dc925672", + "packages/flow/openadapt_flow/composition.py": "fefffc27473a38c1284b58e298ee15e099d848741aeb9ad50876516034fdcec7", + "packages/flow/openadapt_flow/connector/__init__.py": "ea1c554a0de295db995f57f837669419855c9fa1d2fcf958ae4e2feeec37106e", + "packages/flow/openadapt_flow/connector/client.py": "1f55a56123fda690c4bb4dfbd6407f4a96c85c2575eb8928cb7aed30b3a20145", + "packages/flow/openadapt_flow/connector/config.py": "8dd9f662151ce609e21ead89d4019c77a4ab185a0f1b5690df20dac166701c4d", + "packages/flow/openadapt_flow/connector/daemon.py": "3ea5444af51cb28dc84addc4a5db15a6a75486f4ecee93d3b6c09ed7e7ce1366", + "packages/flow/openadapt_flow/connector/executor.py": "463aeafed9092b16a654191611445bb5688334ca87ac0703699d6dfa7f449748", + "packages/flow/openadapt_flow/connector/protocol.py": "963162e5dc20fe0e220fda67e59d138b791fcc6bbaf626776ab192d6cfe3a203", + "packages/flow/openadapt_flow/connector/storage.py": "4ba816b98860b5f6973909b6b6d30138b429a8bcd120423408bf0ade7cc1a916", + "packages/flow/openadapt_flow/console/__init__.py": "3a5ad4c010c98c3afee9c00a4fb7b4209e914b27e48d0cef09d8d4d3eccbbb57", + "packages/flow/openadapt_flow/console/actions.py": "097dd73b8cc61d19cb9cb5752103663a413cae0b822cb708419966413d188850", + "packages/flow/openadapt_flow/console/app.py": "c1d814387a178d0762827a098ee5624a4d5ca611efa8e41484cf002fdeebb678", + "packages/flow/openadapt_flow/console/attention.py": "cd4dff1f27d8c0ef022dc92d64eabf2976fcff3ea0a06a5c363af169899f9cef", + "packages/flow/openadapt_flow/console/data.py": "80359fd5e16d13b73a821034b4ac12de7a52cf4dd81c4211c18b2b23154a86e1", + "packages/flow/openadapt_flow/console/decision_context.py": "27bbda92c8479f6be02ee9855a85f8f192a24d690c80bdf6ba5716419b2efa33", + "packages/flow/openadapt_flow/console/decision_relay.py": "d5320404d4342dbb52be1f2ead1cdb5cf0e22877feaaa1e47974b301df697292", + "packages/flow/openadapt_flow/console/decision_supervisor.py": "a5496779de6c05a21c03fb91cf3a3565951bf97922952cf6606a2c185dea6d69", + "packages/flow/openadapt_flow/console/halt_detail.py": "aec2d5554f62c99645a82d350802d574d6d50e88e5adadea908307b1258faf56", + "packages/flow/openadapt_flow/console/human_decisions.py": "715e1cf4e2cb800cf19aba4ddda0e0774e8b8eb85b5952ac6997c5a3814966ff", + "packages/flow/openadapt_flow/console/json_artifacts.py": "e908d47f34bc040e83b257b953c55a7f3cec9b2b74a4519369d85c3a12e6cb7a", + "packages/flow/openadapt_flow/console/save_approvals.py": "b4cc6c6e03730becbc88ca61b27b78a4dd6ee9f2b1c5c9b8edda44a5fa18f4f3", + "packages/flow/openadapt_flow/console/server.py": "07fea6ba09c3b1e6de7a43d9fd1e27ebd70ae6028944bc41d22029ea33dabb5f", + "packages/flow/openadapt_flow/contracts/__init__.py": "1a977515b318b47ec75a5d645ac42120261321d159d1e5efc906c73c407e2c10", + "packages/flow/openadapt_flow/contracts/_compat.py": "ee3d2026b24aea3480971ac13ec271aa2d07ebba5347c02d4fcba3a547fc88b5", + "packages/flow/openadapt_flow/contracts/action.py": "0ec10f7e6a840c09a48492abbf501d3ebe807fd06671657ae4f4c62137fba61e", + "packages/flow/openadapt_flow/contracts/approval.py": "408228cd1595b260b72f30b314d594dc07278b4c48a8072a5f969fa3bb94c194", + "packages/flow/openadapt_flow/contracts/authentication.py": "5aa591e0d8b7df2c5933eede186dcb34e5d04807718d2ab4441195d8e22c8073", + "packages/flow/openadapt_flow/contracts/authoring.py": "f83894d07ccec6ebc70c715da30b25f2060a5c0e6fc0b570783f739d403e6fc9", + "packages/flow/openadapt_flow/contracts/benchmark.py": "59fbd6ea07e38794239eababc408e724418d99982ef8b39d5ad4f5b36aefcf2a", + "packages/flow/openadapt_flow/contracts/business_decision.py": "55f7a89917369b360a0d29c30d851e36f883f120c7c00b89baada6e992cfa804", + "packages/flow/openadapt_flow/contracts/clinic_job.py": "4719877cb2a1dee680d74d66a1d2f182bab8f86fbb7d81c3e82baf1bdab79d07", + "packages/flow/openadapt_flow/contracts/computer_state.py": "c2063f22364e9012ee852ff36f8a2a13f5015655a0b0d634fcd5755f813dbfa7", + "packages/flow/openadapt_flow/contracts/control_overlay.py": "3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790", + "packages/flow/openadapt_flow/contracts/control_overlay_tracking.py": "bd5a5965e70839567c22314984b8046797308723512c3b032d6582c7dc7ce0fa", + "packages/flow/openadapt_flow/contracts/episode.py": "5ace88d9e278d099c0e7939cc19e99bf18cbbb80d6ea7d606e3950c027baa8a9", + "packages/flow/openadapt_flow/contracts/execute.py": "abbf796c7d906a18e84676b72f2cf6a0f8595e960841fb5197ff495d772bdece", + "packages/flow/openadapt_flow/contracts/execute_client.py": "9d3d151ae28b01db64a2bc45f116e0bb3177196883561f935b09519910be5200", + "packages/flow/openadapt_flow/contracts/execute_openapi.py": "213aafc74f50e9a6fa1447535069bc2a29965db8dcfca4f60921f4cb45f9d473", + "packages/flow/openadapt_flow/contracts/execution_requirements.py": "405b79b6fd84c5b606969808c13b07806be177b0e086ef49c123e6711caa7897", + "packages/flow/openadapt_flow/contracts/failure.py": "26efb6e04d17c10cae8bbd688249ee44bcd7844a62be6736de984a6e881fa0bf", + "packages/flow/openadapt_flow/contracts/human_decision.py": "0bb39a3575f511e10d01d92858b340982adee2f0fc20d2409441dbc0b2c55c4f", + "packages/flow/openadapt_flow/contracts/oracle.py": "14baea846ad123e588cfe46f02af05b9bc15a78bf8ff6129356265fafc32c255", + "packages/flow/openadapt_flow/contracts/parsing.py": "70088df15ad4b0bcdca120a242d6d1fdf8f5fa433be5f99973a6b3324e72b8bd", + "packages/flow/openadapt_flow/contracts/plain_results.py": "6eaced64df53ed29f594435ae346eacf898e3a0eac10d10d2dcc733c0263f9ac", + "packages/flow/openadapt_flow/contracts/process_capability.py": "d3aceb649b7eb9cb11413c2c4948b9f2709d11b6e2eee85e60371301a0d7703d", + "packages/flow/openadapt_flow/contracts/production_lifecycle.py": "046f30559dac66e4105ad830a274cfb31e0374f95a7a8b0adc1b7dff2aba9449", + "packages/flow/openadapt_flow/contracts/reward.py": "5d6b41aee1c7b533505b16d8e76fa921ea57369e4304297fca2c2898127f5c9b", + "packages/flow/openadapt_flow/contracts/runner_capability.py": "26fd3c1d96a5558c70886cf203c999d524b9bf927cde23d90735c90be9e2b427", + "packages/flow/openadapt_flow/contracts/schemas/__init__.py": "965365ce3faee699154d06428c2dd83414c18632ca3ba39516ef513523b8d618", + "packages/flow/openadapt_flow/contracts/seal.py": "2d342e90350348ce887b2c65a7fa8a0505793528e9051d8338eb56c29276a6d2", + "packages/flow/openadapt_flow/contracts/vocabulary/__init__.py": "ad80651c5dfd169faa3d478ca69d6f56518c7b70d8fd474f2ef6337586e5a886", + "packages/flow/openadapt_flow/contracts/vocabulary/__main__.py": "aa13a269d89f443dbdac5e3c4950b0b870aec327b0b200881cd0023a95342b8c", + "packages/flow/openadapt_flow/contracts/vocabulary/lint.py": "5678a73947249cbcab4fdee7b1ecd26b804b039a0720a5eca1a01523bf068a89", + "packages/flow/openadapt_flow/countersign/__init__.py": "727c18b4914e681256d727bf6a13285bd9b43c84430c7cbb610efd6721d7edba", + "packages/flow/openadapt_flow/countersign/cli.py": "bbb2f1b833297dab3d90fab4ee8bc60b71598b93d0da1dcd30baa6de6dfe8886", + "packages/flow/openadapt_flow/countersign/commitments.py": "2ec3967924a7142facbaee575d6c76c0241f9187e4ce2c4b497f479b5e432339", + "packages/flow/openadapt_flow/countersign/document.py": "4006e95e33af2523d13f42a20e215ef71e41f0d53696ec66326910a7f23ade7e", + "packages/flow/openadapt_flow/countersign/dossier.py": "1ea1151f03b55a6b8a73333b46079cf2cc31d338e6b413490636d2b3ef16033b", + "packages/flow/openadapt_flow/countersign/exceptions.py": "26733b2322d2a9ac1a99cf6bb34f27fbf300862b3e89c1884a1441cb7b30ac6c", + "packages/flow/openadapt_flow/countersign/render.py": "daabc152f67f1279e978c6b80a1f83477d1a7aa4895738ff77d262416394349f", + "packages/flow/openadapt_flow/countersign/tsa.py": "c3597122c33e80b3eb2cb129551e1cd50b6021f45cbad01c20869f13699f8200", + "packages/flow/openadapt_flow/countersign/vault.py": "71ea45df4993a00a384791c227328eab67a2e5d3d1d75896e7c841765d5ed1bc", + "packages/flow/openadapt_flow/crypto.py": "e7df0c8ba675532234424b5beeb0ee51faea571bcd65764809c6fd9cf175b04a", + "packages/flow/openadapt_flow/decision/__init__.py": "32b2b08051ab45571c55af1d44f2b68fd4106eec9ceb078d6769c2d306403550", + "packages/flow/openadapt_flow/decision/calibration.py": "51b2adc7ee624d78120a12ebf02d6eb6b9408ce90642e064647fe678e5578051", + "packages/flow/openadapt_flow/decision/cli.py": "b0f453ad2616e4b0f99249e983967d1fa7b929e4692662b8adab47053cdf027d", + "packages/flow/openadapt_flow/decision/eligibility.py": "86e77ae372f8eef0c31eb9ff694e17cadc7f278155c76d46f0a40be0dc766cb8", + "packages/flow/openadapt_flow/decision/feature_model.py": "af7207d471e531377ea653448fd2487f1988a4e8c0b3c7c36d7983a369621077", + "packages/flow/openadapt_flow/decision/features.py": "cc5e21ecf0ca93982645791724fd41afcb9e77ed4c526cfa2ea7d3c6cee3931d", + "packages/flow/openadapt_flow/decision/labels.py": "924fa2fa32cdde259a2900d0c8af3c3f54b07c30bcbbb9f4af7289663a2889c2", + "packages/flow/openadapt_flow/decision/minimize.py": "9c0695866985ecda25a0c96d251d3358d15ab7ee57e66a3b4528e66f4df65ace", + "packages/flow/openadapt_flow/decision/provider.py": "3e14c3b013533925a62e67bdd9b82ddf99f969a2552914db1630498ee7f4bcbf", + "packages/flow/openadapt_flow/decision/review.py": "47822513196daa1cb3e827e3cb846763f41165738c3576c2df946f6135676098", + "packages/flow/openadapt_flow/decision/rules.py": "59a33e0bec5f19fc35621652c96c79ee6d6cc719854ff402853c9cf9c143b31c", + "packages/flow/openadapt_flow/decision/synthetic.py": "2ee2fc309a5342a5a3e050a47054b6b1730e21d8d20592956f7bc83f8e7389c6", + "packages/flow/openadapt_flow/decision/triage.py": "201235c57ce21e631c4e2f5990628f40ce3bae95e239f0bba29ce7738d95f96b", + "packages/flow/openadapt_flow/decision_delivery.py": "e3fd372d84ecb8a13fd811e115255362fae2f96e5b073e59d1d7f123c2bd85a8", + "packages/flow/openadapt_flow/demo_driver.py": "96d2dae078eee394b9c92a050cda498f98a61ad7926040a19996f31612007639", + "packages/flow/openadapt_flow/demo_proof.py": "5fa9ac103d839fba9c8b8dc08ff0aaa29d4eaed5d972b7db6dbf1d2d53b01694", + "packages/flow/openadapt_flow/deployment.py": "25fa8dfd76e2efdefaafbb559530510131266b0eadd1ec4a4924b0715976d6e9", + "packages/flow/openadapt_flow/desktop_record.py": "a162df0e593b9f437bca86aa723be013836dd5e0c93acd5fcff7876ba16e3110", + "packages/flow/openadapt_flow/eligibility/__init__.py": "c4b10f081ff9c6d3ec3a3017d8a12aa7eb67b41488ae931000a8f84f08d9c54c", + "packages/flow/openadapt_flow/eligibility/artifact.py": "686eae18906a58b42fb6219fe6c4fa7b98638192db8169f0098601403ab274b9", + "packages/flow/openadapt_flow/eligibility/client.py": "9ddd6ca4512b64b8ad7ce7fad6c31d037209f3a19e9c52c605e9eb508c0f63e6", + "packages/flow/openadapt_flow/eligibility/waterfall.py": "15e15b5c8efed40cdeba907b74a279c217434ccf62a378f2ed4a05feba0d44ec", + "packages/flow/openadapt_flow/emit/__init__.py": "f9d028ad70f3eda13321fdab9d23845016781cb7360160d71eab82a1752419cb", + "packages/flow/openadapt_flow/emit/l1_artifact.py": "8e590b6013d1aedb6b722745d616bc394451c703642a1b7b0159359d94a4a868", + "packages/flow/openadapt_flow/emit/mcp_tool.py": "604f48f64f5e012094f9cb609ecf73fa49a9e4dec4f81a17f981921d59786a80", + "packages/flow/openadapt_flow/emit/skill.py": "b1975e4ff587a3b4e6531738052d19eaf3e6078bed67665709871c33212c18de", + "packages/flow/openadapt_flow/execute/__init__.py": "769684783844a34810850275aba3647c303acc2cd6eced1d8c350c4a2e774d54", + "packages/flow/openadapt_flow/execute/app.py": "5957329f8c4cad30a59f8c0a9484fae1e54a6138f91d9414c7ac8ee6e8d4469a", + "packages/flow/openadapt_flow/execute/dispatch.py": "56ea5111a17472ca46a40ae2a851b7de1072f09901b9b806add011a45cffa8cb", + "packages/flow/openadapt_flow/execute/keys.py": "f43c622ef1df57efb3ed52db6aafc9d98d78100f2e25fff50dcc8b75489f9750", + "packages/flow/openadapt_flow/execute/models.py": "bfbac12c75f4fc52c0f9bf89652e876828f208c13a7827dab8bc741b112a5e6a", + "packages/flow/openadapt_flow/execute/registry.py": "d687121bec318dd4b56faa1db195a181501fde92263f7b851fb33607de903642", + "packages/flow/openadapt_flow/execute/service.py": "88f6ae53c6843de0769f31686b68a908e6154c869d530a29459d34bce0a9c286", + "packages/flow/openadapt_flow/execution_profiles.py": "9da970b34f1c88da46485eb1983a85c88ed50bd21d495527c4e30a04b863b46c", + "packages/flow/openadapt_flow/failure_signals.py": "63090631a784bf6626859f3a2f7c95551b019d1baac79573255fb41c98f7d8c9", + "packages/flow/openadapt_flow/fleet/__init__.py": "3196cac4fbcf1ac5974955e8d9f4dbd0295b516ad5b758e1f67e4f6f545a95b7", + "packages/flow/openadapt_flow/fleet/_lock.py": "b877076335ab50d6b240e3b6b0f131d0c699ba706e4af738858320c3fbfe45a1", + "packages/flow/openadapt_flow/fleet/canary.py": "275a8acf693d566f17858a2656df9d1b6315becc6c9153149046902bede9c1db", + "packages/flow/openadapt_flow/fleet/export.py": "f3c4385716ceb71c0f2e93e4552a32aa22fc31561d0497f9b4c5f13f09c64937", + "packages/flow/openadapt_flow/fleet/feed.py": "f229589e292195b387f9c756fbd549eacede2cbaaac2a43d782ae89fc69a7a34", + "packages/flow/openadapt_flow/fleet/fingerprint.py": "f641b80642a5bdabf97e44b7f9796ab413c8c5e1ec64355c333ecf91ccda6e00", + "packages/flow/openadapt_flow/fleet/hub.py": "5605dd4b5cc620f400bf101c8a94b3fd386dcdbea91329ce9fd2dac087e12f8a", + "packages/flow/openadapt_flow/fleet/intake.py": "c6390b6814eff7f0dc424711a6502145baf13bc7ba32031de827f1458184b267", + "packages/flow/openadapt_flow/fleet/optin.py": "c6a02feef8c31dfda435fa7b5c9ae2313775979cf68ba0643fe1f284142d3eb5", + "packages/flow/openadapt_flow/fleet/patch.py": "50fbd5f28c25c5af1a74ab79534bfc45d80335bf50971d88937d6f48a8047caa", + "packages/flow/openadapt_flow/fleet/policy.py": "d9a6dfc09ace5a6446f421728f636d77ac40c4e8e3068685d54cf12a52091729", + "packages/flow/openadapt_flow/fleet/signing.py": "9761c1e080a04ddcbb0f54f75ca37fd74a1801399be32218e527ef1c5b71bc89", + "packages/flow/openadapt_flow/fleet/simulator.py": "bb9deae25e4e6b6df6503df08b175d146b73cdf347f2997cff32ef34bd94af50", + "packages/flow/openadapt_flow/fleet/status.py": "2be462c6f00fda3b783e7825c12a5a031346b11e34c10c6465de75fa79c650a5", + "packages/flow/openadapt_flow/hosted.py": "232f7cb2d68e0e1f57ec7b4caa10b866da16a5555c6867d41d978a89bc946683", + "packages/flow/openadapt_flow/identity_signals.py": "46e1392fe0fdc7473bc28bb25562f31a6dcc6c90f95d7900de255bc750bf7287", + "packages/flow/openadapt_flow/image_hash.py": "4dea419ca5617104d6072ebddf8141e8f9809227aaf833969e390eb3d5c7d73e", + "packages/flow/openadapt_flow/instrument/__init__.py": "8bcf6fb0892a9dcdd6cdd3a787a084548479d2ac2ac4e439a772e45dbd924250", + "packages/flow/openadapt_flow/instrument/competitor_drift.py": "f124cc086c52eec9b930088b1eb9bf264075cc121f9d06a09ca98ea76527de56", + "packages/flow/openadapt_flow/intake/__init__.py": "76815b0b2ecd679c7b076f28f1bb48c66a6be007f208fe8a6ab2746477b3ddc0", + "packages/flow/openadapt_flow/intake/cli.py": "0572e2e12addba1cfc36ef92dc2c331d60b96945dd77790f5e432b26b56dc160", + "packages/flow/openadapt_flow/intake/document.py": "b71b232d18d509e336d9d950063b14c6dd703383319a25b845e08fa5731b6f32", + "packages/flow/openadapt_flow/intake/extract.py": "d2497ca3f25d6d54c48012f5553237ad22ae6e8e1c2bec5044fdeb62b547d2cb", + "packages/flow/openadapt_flow/interactive_recorder.py": "fd88ce0bd851a5414a2f6647543aac78ec94b8eb1a0e857e29d5ce135490a76f", + "packages/flow/openadapt_flow/interop/__init__.py": "803d6e9b00f130d0282e8d3c26a8d3087033c0fc299a1817b51191431ff4a960", + "packages/flow/openadapt_flow/interop/business_decision.py": "98cdcd8357452d322360ff59cf0e26175e23131abad559374e6ab82a16427dc6", + "packages/flow/openadapt_flow/interop/business_decision_cloud.py": "825b76362877a90db6a3e2fe3a0f081d67414e285fbe4940a54dcf09bda8edea", + "packages/flow/openadapt_flow/interop/business_decision_supervisor.py": "d39d6bf9cfd1d4c84804b5565a99f0ac9560ac15e2838deee4c81426503b521b", + "packages/flow/openadapt_flow/interop/decision_relay_transport.py": "31a2f2ecea1d39f108747dbb81b123d2d7d1b0421fe75b97c6abac1d94652a51", + "packages/flow/openadapt_flow/interop/types.py": "24cd2d528026400014798c78e6ea1655220466b4bc3c8348abb5c1441592419c", + "packages/flow/openadapt_flow/ir.py": "8eddc936cd049d17e48d453e663f67972094a6639b3c341608dccbd436f66ad2", + "packages/flow/openadapt_flow/jit/__init__.py": "52f8c8cd9ce8e72cb92e637a30edb34a67b7d3288da6b956e2da75f90515e168", + "packages/flow/openadapt_flow/jit/adapters.py": "2b821bb69bf9a08c82d23f3cc5061b6b1adba97706bbebb99ee6c069b6207f61", + "packages/flow/openadapt_flow/jit/authority.py": "d4e20fffe1fd61363da76ea5192d98a4fefda06ef4622909b4ab75a662ad4171", + "packages/flow/openadapt_flow/jit/cli.py": "daf17cfe4a6258153b53d2dee6c24689496ef04d1906782246860c77e3b6e003", + "packages/flow/openadapt_flow/jit/compile_agent.py": "05417355deda4e6a584816478221bc9037f8386edd69ddbdfca3f3e50ed224c3", + "packages/flow/openadapt_flow/jit/keys.py": "8d4b0674a351a69011e590ca9bbe5e09ccfd3c55868ff03db7584bbc6e2d530d", + "packages/flow/openadapt_flow/jit/lifecycle.py": "df252f3021b23f0e0c95e9474003d4c98bcc4f24559d86f932c5b94010c4f3c0", + "packages/flow/openadapt_flow/jit/lift.py": "3a6d5fb196628d77273503a785cda62fc180a5f6e1822c716cdbbdcc0a7a2c51", + "packages/flow/openadapt_flow/jit/recorded.py": "3dac2027d3bc1d0e7028dc39f85e7c072c115656ef556d3e21e003f9cdb2a8aa", + "packages/flow/openadapt_flow/jit/signature.py": "b32222f42a8b653c67e495693c6ba2a04b261f453a2456bff67c5fd686240075", + "packages/flow/openadapt_flow/judgment_cases.py": "021f312ad721f06c6ee0f99cd2061d32c82eea7bfe6f87c4b182e36a4f445db9", + "packages/flow/openadapt_flow/learning/__init__.py": "3a47506779959eafc8b85ca26a779fadded553711544021ca4915a87504857d4", + "packages/flow/openadapt_flow/learning/branch_teach.py": "7a22ff5f12ef1e04198bd3b4826516e4afe1c8a2465d21fa74574eca5b9dfed9", + "packages/flow/openadapt_flow/learning/clustering.py": "50173d350b1c936e2f667db1612dae7721df7a81b40c07eef11abf10c9c0f1c4", + "packages/flow/openadapt_flow/learning/gate.py": "f34fd9f3ec74d7a5cb7f111be658db9debd032ceaa12d9254e2adc25f0becbf5", + "packages/flow/openadapt_flow/learning/halt_loop.py": "7db231e6902ee3b7fcfde914b2d1ce9c93a61f371d87b31c0156991b664b4566", + "packages/flow/openadapt_flow/learning/interpreter.py": "606d336216a899b11651c2e4eb341748d8cc91a10508bd33a63c7d50a44318aa", + "packages/flow/openadapt_flow/learning/library.py": "bd3443f46255070368827dc766ff89b9d77cbb92c828b5a0dec06de4581fc7e7", + "packages/flow/openadapt_flow/learning/loop.py": "f3e5c1127b4fa21f3e112516670edce15daf05322c554642b8057bf268adb7cb", + "packages/flow/openadapt_flow/learning/synth_stream.py": "23ab8b60ed0ba8c3f241c480fddb3901a18aee4bbce79e8828d87c41c1178c80", + "packages/flow/openadapt_flow/learning/teach.py": "f03226cef5666ce7940c954cd381e34e1df38de3c97ede3b318b6955a6c1c851", + "packages/flow/openadapt_flow/learning/trace.py": "323bc17cfa3fc3876ac030abda028c73e8af02d5e30b2cbe9c9b9faa4639c524", + "packages/flow/openadapt_flow/lookup_capabilities.py": "6f8068a05f005e0eac994d14d0ca3e6b1ce7a4e532471bf4b8d9dc15c0fe1b4a", + "packages/flow/openadapt_flow/mockloan/__init__.py": "06beb30be301dde52a522064ed192864a4c2bc1f167ebb5800c173eaa72f4735", + "packages/flow/openadapt_flow/mockloan/fault_server.py": "0f6f6e4ca4622c5674dca6e49b9dc67360f34b9071ba1346727683aed16682ca", + "packages/flow/openadapt_flow/mockloan/server.py": "6d81bda0dd715a71e54306c92d739b49ca3b3a75fac65ee7a70a3d18027ecaa7", + "packages/flow/openadapt_flow/mockmed/__init__.py": "223bb05087540a7e0e90f72b83a5dd3634318b95dc0a6874cbc82fd38ce5ee9b", + "packages/flow/openadapt_flow/mockmed/chart_pages.py": "38db2fb4d34d14d8745c7c9b31a6c729a4e0afc545333c164296fde5044bf395", + "packages/flow/openadapt_flow/mockmed/fault_server.py": "d07cad7aaa74bbd54028744b65b9e74384e73c1e18b2fd1807b138e48a4bf025", + "packages/flow/openadapt_flow/mockmed/server.py": "9fbeaf2b0c873dcdce478b4f54990b954844c057a0760ab763705e6aa3f202ab", + "packages/flow/openadapt_flow/network_scope.py": "c64d2288c98fde49c437a0a44b53af4a4b1884a8130b99bee098c2046e249033", + "packages/flow/openadapt_flow/no_outbound.py": "af14df863d40d81d046acbbcc102e2f96912b24114111e052ebf5440348673ec", + "packages/flow/openadapt_flow/no_outbound_allow.py": "b60aa14551bd39ed333389290ae4a9069863b370532e1016d74441a8dde5dd2a", + "packages/flow/openadapt_flow/offline_seal.py": "9f9895ae7cfd254eeb7ea5931c61ebba62ff7c08a3f8793988f52ee1fca1bfb3", + "packages/flow/openadapt_flow/onprem_reboot.py": "5897eda697e7111ef3ad944c7e397b64ed39abdb225085f9eea0f47b6630879e", + "packages/flow/openadapt_flow/onprem_service.py": "2edbde7b9a179b1c2bf63d1b955113a9d9fa3f909e4d9db875e7b626b0e9a792", + "packages/flow/openadapt_flow/pf_firewall.py": "adff9c485d303b5b07efa910c45d39762e12285c843f8cd782ffecac5631e0de", + "packages/flow/openadapt_flow/plain_outcome.py": "bb5005e368de8b70a355e3c1290b38a8da2519ad0a1d1a5828c83e51b3539270", + "packages/flow/openadapt_flow/policy.py": "fd198457b1a34edcdecaaddb3d0d92a6af5e89bf1216ccde7af909dda22c3631", + "packages/flow/openadapt_flow/policy_packs.py": "8df13eaf8e7e4ac7492c2f8c25063ce387833174f3c82dfec3f8c68de4bbaaf7", + "packages/flow/openadapt_flow/privacy.py": "81734c0bc6ae78ca0a54545e44bd2bf58755aaec677a594b7ddb0167200fcc53", + "packages/flow/openadapt_flow/private_file.py": "eae5ffdae6a76c57bc999278baae8ad0bbabfe87813818777e825beddc22f135", + "packages/flow/openadapt_flow/production_qualification.py": "c5c9911c7fcaa12bbd513fe737c4c17a3bd64522b98100880c1a05fddfff3aef", + "packages/flow/openadapt_flow/qualification.py": "6d2ac075bc373b03b007518bccb5c89506ee1ae5d282d22dc10ad121898f10e7", + "packages/flow/openadapt_flow/qualification_admission.py": "73a43db18fd383fde7c5e11823ace50046e0784e662b56c76b82289f61616cf0", + "packages/flow/openadapt_flow/qualification_admission_v2.py": "10d8241a38b24767953e6b98986c838773a02399652924a53e53c7ec7c7c5e56", + "packages/flow/openadapt_flow/qualification_admission_v4.py": "5a11934e42fdea85c7ff792ab6d33754b974a719783154e8e6144e9a90db1292", + "packages/flow/openadapt_flow/qualification_campaign_authority.py": "cea191889f3b3f8da5c078e0cb020e7e0ebe9c4100035f5f1bb2b17ace75c3d7", + "packages/flow/openadapt_flow/qualification_campaign_permit.py": "1468882d9627f0355673873cd493c2709ab7cec2506dac3b1efa32d1c16f0637", + "packages/flow/openadapt_flow/qualification_dev_signer.py": "db70cdacae8235acc73f13a3281e667a7a5e502c246151854db4dd921caf8643", + "packages/flow/openadapt_flow/qualification_drills.py": "71c9627c1ad14f178c785121af656392d4ec71ef6ae5c331fee875747eda173e", + "packages/flow/openadapt_flow/qualification_environment.py": "842f5dd827750f55d9e1046613a804fa6b27c2a41981d4d7fbd6b04d9acc8131", + "packages/flow/openadapt_flow/qualification_faults.py": "ff4661bab2327788598b8d1616d81d9e5c09b2165b1e1b07979083945ce7a800", + "packages/flow/openadapt_flow/qualification_identity_evidence.py": "6aede36f2bb8c91ee8ef077a1e4af1751893f13237ef70415858bfee0e3bdee8", + "packages/flow/openadapt_flow/qualification_oracle_gate.py": "39e12170e33cece3aa72f463afed7d6c342909995afbe3d861592dc47c912d77", + "packages/flow/openadapt_flow/qualification_proposal.py": "92eb5e6a4a530aed87db7a40aacafe48347d5cfdd4cc8a68039154d1711949be", + "packages/flow/openadapt_flow/qualification_web_session.py": "3766e5b5f03a92f91a5f51988b1764542b74aeaf634d72e18c9a9b1b6ffc4fdd", + "packages/flow/openadapt_flow/qualified_business_decisions.py": "9b56b3d0923440b589e6ff010dda1cab88c893aa01e92861e796e6cae2c83c34", + "packages/flow/openadapt_flow/qualify/__init__.py": "a3d7b8e30c06fb77be32b75996ce1f3ea8f96264e74edef2646f122ebbbdc1f6", + "packages/flow/openadapt_flow/qualify/campaign.py": "4ddd41a30d63b74c24f26183d0a0c3c3533dcd709296c648e2cf04bfda57df10", + "packages/flow/openadapt_flow/qualify/config.py": "58271767026adfe6e1268ca172cfcf7e330c961e435590aaea71eab862d9505c", + "packages/flow/openadapt_flow/qualify/environment.py": "ebfb7128e9292715d5af31e8625cf8e548b25e7201455a80589f460f20b5f74c", + "packages/flow/openadapt_flow/qualify/faults.py": "69f6cde4c844c22bbee45383ae1a545497936fc9dc688e97c409f07c6cfcc192", + "packages/flow/openadapt_flow/qualify/keys.py": "fb2e94913ad0db0fcc697943faaeef3f3391a83fe045af703d249b9344afc556", + "packages/flow/openadapt_flow/qualify/result.py": "7877e368072eff08d4e62e527500fe88b0c7f56e53f80ec2c0a7e63a88c5668f", + "packages/flow/openadapt_flow/qualify/session.py": "281c003e690606d7d68cdf365a4042424ab3fc1b2cded916c4ae10c3d16ef1f0", + "packages/flow/openadapt_flow/receipt.py": "d118970b58510fd3b5fad7ee0eb67872a004607f4351c14318cddc35e3759c1c", + "packages/flow/openadapt_flow/record_check_binding.py": "0102012ebea2f78e09e610310c32a555ea95b9484099181ba6409265b7773be4", + "packages/flow/openadapt_flow/record_sor.py": "f3d083dd2875b9c31bf3b5e31332b1d2a946bffc7079f034b030e15273545b4d", + "packages/flow/openadapt_flow/recorder.py": "d4501500f3d689ce7b3d44dbedead74df482ecb365c853e4d63b0de7ee005f04", + "packages/flow/openadapt_flow/registry.py": "586eb7e8380ba33055ceff29c164e005aec441ace73a7a70e1001f3f9cb02824", + "packages/flow/openadapt_flow/release_admission.py": "5afdf674f30073b10ed5b4ee06fd3818db26e74cc935c649548256cb60160f7a", + "packages/flow/openadapt_flow/remote_frame_contract.py": "745c8e9a6731ea89b722092183f760527d9e6f329a5a7ef61152870114e77406", + "packages/flow/openadapt_flow/repair/__init__.py": "db1e673372764eb9ba76be658980a94b9747a0d716b5ec1a8d5f74d69ca909ee", + "packages/flow/openadapt_flow/repair/agent_teach.py": "ec9995ba2d3ccaee0ee623e77cd30f0d30a565235befcfbb003fb81d4b7f2920", + "packages/flow/openadapt_flow/repair/campaign.py": "520e08184f5abe0bb53a4e08eb2d314e89ad3cc0636069573ca26bdfa82f20db", + "packages/flow/openadapt_flow/repair/candidate.py": "e5ec96fbcc6fbad6ebb9477d46bf975712cfc9122cf1fb78b127b9a736435f5f", + "packages/flow/openadapt_flow/repair/cli.py": "aff7dad26018f77bd8c25fada6c8d614c33a730034ca28bca0025c15c4147c16", + "packages/flow/openadapt_flow/repair/cloud_review.py": "eae37867caa636115ae63adb6018987c322bfceb78a25c4178f4287bd13bc6d9", + "packages/flow/openadapt_flow/repair/dialog_fixture.py": "7969fe9c3e4373e32873a237867e182f8c2933397e2c47614216c5ced59a4e9f", + "packages/flow/openadapt_flow/repair/dialog_identity.py": "77e828471626051c74e502c29bce3ec2356926b38b6ddd83489b7bdc3fff3dd8", + "packages/flow/openadapt_flow/repair/hold.py": "48169c9c5d4e0099f64f36278435eca64b11d49783b7dcf0273478c331af746f", + "packages/flow/openadapt_flow/repair/invariants.py": "c645ffc37b7135018e0783955b77a067e719e99765d70ccfc26812b3e0336d45", + "packages/flow/openadapt_flow/repair/lifecycle.py": "19130a17a32b5da0a8d6c0015f7106a1c7e0535d3eba02dd5b99f85cd96cb3b3", + "packages/flow/openadapt_flow/repair/registration.py": "2eebf2d1683568c2893615eb47041467d6f8943c76de81c1c9a9582f9c61e8cc", + "packages/flow/openadapt_flow/report.py": "50ef36ec2721f6c279ebf669a3f11bb825783ea07987117f70b38db886e5df59", + "packages/flow/openadapt_flow/reward/__init__.py": "a16f606b5333fa2a0732ba0a3ae643125e6831f8c923fd17940194fab25eff76", + "packages/flow/openadapt_flow/reward/calibration.py": "b91fbbf5de6022d6f37e3ed4b9ab06dc04ec51e17060af500dd07b86b938e44d", + "packages/flow/openadapt_flow/reward/callables.py": "3526b4ec44fb82d7697c1f94c3a91e28a56f66c275d575bda9eda1058d3c37c8", + "packages/flow/openadapt_flow/reward/models.py": "b497ad11cae280c28deed0bf1dde5952528b20de51937d44dc0a443b4590532f", + "packages/flow/openadapt_flow/reward/oracles.py": "93572f5ada086d441e467651e9509e7352f5c97b576751dc6db2d53380b1230a", + "packages/flow/openadapt_flow/reward/seed.py": "1f84a01302b03dffe41d56c1d63b6421f9b47019ab055ccd9eea2542289f2462", + "packages/flow/openadapt_flow/reward/serve.py": "c8bf963939d991c1ad55ca7ac4ba50d3d632f70693793b92a2fc07fbb003e5da", + "packages/flow/openadapt_flow/reward/worker.py": "cb9e330242f0a7cc1eb4dba137b37e77fcb286165b80382c762a789b316d948d", + "packages/flow/openadapt_flow/risk.py": "258584ef460de27eb199abf57c3e334fca2368d222d844ec17aa5aa4c781da80", + "packages/flow/openadapt_flow/run_gate.py": "b3ab89e10c8d558a4e36e57ee36150ec9042780186126d72ae9abf2722f50299", + "packages/flow/openadapt_flow/runner/__init__.py": "1111900db86ee0f61faa093996a6e64a1405c9052bb74f186afa1a84a60ffcc0", + "packages/flow/openadapt_flow/runner/business_decision_service.py": "97bab36f3c9dbc9974129a34a5c636dba08e7fbf3c56fc522ed507feae216bd4", + "packages/flow/openadapt_flow/runner/commands.py": "92a62d244b406c405cdfd51c3189a835878f0e334fc74e16e98a1acfbca47ba3", + "packages/flow/openadapt_flow/runner/config.py": "ba84ed0b0638ee12de51f40fe3fa4da13c14effafbbba7bceab256e56664fc2d", + "packages/flow/openadapt_flow/runner/dispatch_envelope.py": "4a867de3740e46b67ecf90b74120f53ad038d7c0aae9b17c6bcefef998ec674d", + "packages/flow/openadapt_flow/runner/evidence.py": "4f884203b69f3368b56e7bd0a31df123a28f5c7b464fa73a69b1da742fd7fe76", + "packages/flow/openadapt_flow/runner/flow_release_receipt.py": "1e80b5d956de0debc12e5a14f4595256eb13caba16d387e8193ad1737be4ae18", + "packages/flow/openadapt_flow/runner/hosted_adapter.py": "06f84a0357c5f40bc6949828aab76214fcb3405fe2b8241566a648c109979d66", + "packages/flow/openadapt_flow/runner/inputs.py": "711464bd021afbde06b9c4d132ea782c3b6a321ea48bcb24c3f565f8609f8a9d", + "packages/flow/openadapt_flow/runner/lease.py": "9ba80da689f4eeec85d44a860a27851e2408e3772b135bd01e4c4a0996d77c60", + "packages/flow/openadapt_flow/runner/outbox.py": "19454f29bb30bd8707f452a00e6fcb3511a1fce1b70ee2401d5d40d2e11e496e", + "packages/flow/openadapt_flow/runner/product_release.py": "072ad1a818fd1ccd7bfbe2995ee90825111890dfb4c1bd9b72275350107adab3", + "packages/flow/openadapt_flow/runner/protocol.py": "02ab8b7ce4a936d8533cf99a58f68fb5281b2afce2f17b99dbba27b0eeaf7e82", + "packages/flow/openadapt_flow/runner/verify.py": "473bab048ac39adfd9020816e81ea969c05856993e4029e17ce9bc9ba237d92c", + "packages/flow/openadapt_flow/runtime/__init__.py": "fa78a3356ae6470aaab3c3093c57502ac8aef9b1ecb03ec2f4ccbc2a0c0b9f0b", + "packages/flow/openadapt_flow/runtime/actuators/__init__.py": "597b6f56b00bb79c7f3a4452f602d1746aa0acc99bebc1683ce71ceece5af59d", + "packages/flow/openadapt_flow/runtime/actuators/api.py": "05fb4e35a9cbb2c85874bdc3d5df1aeaf4f5ba1e011ec0e4ae8b02aaaab78a13", + "packages/flow/openadapt_flow/runtime/admitted_composition.py": "1518f9c4791fb6f1c1169d6414e4bf2e456dd1121d4c02796d2388c6a539627e", + "packages/flow/openadapt_flow/runtime/authorization.py": "006e9d67a27bca1f900d676781e7863f953050f90dc592e0743532839f7ba02f", + "packages/flow/openadapt_flow/runtime/composition.py": "cc10476194e0a5f44e9421476317b72b1233f44eeb0bf95196426f9f609e752e", + "packages/flow/openadapt_flow/runtime/control_overlay.py": "bd07ab6981d9e49dd212221b87c98cbfcc5f885224f80034ad8721900be23e06", + "packages/flow/openadapt_flow/runtime/durable/__init__.py": "32d5846e0775819d84e903485f1c32a14774d2ee3d4f3322335182afd68f8826", + "packages/flow/openadapt_flow/runtime/durable/approval.py": "6388bfb60298c9579dbc196657707e9653b33b2b4acddddc4e9d8087db24f7d4", + "packages/flow/openadapt_flow/runtime/durable/attended.py": "7af7a9b4be2e7b162b8abb734166e32369909870537b5097ad11b9ff0e4c28c6", + "packages/flow/openadapt_flow/runtime/durable/attended_service.py": "13b45a376e37ff032c072334df0d3585318dbda3472c5b671b398c4ca6b30f47", + "packages/flow/openadapt_flow/runtime/durable/authority.py": "e9c2f928ac8a0345afcb80cfdc89ce3de4d90012007ded7f10128ec8e59e5f3d", + "packages/flow/openadapt_flow/runtime/durable/business_decision.py": "dff9bb63163561045318f2416420e9d96c1c69fc6f1f31eaa00e50fc41083e63", + "packages/flow/openadapt_flow/runtime/durable/checkpoint.py": "24694ab18b1f6a142d90f66dcf63d3591986f210c20396f6da8c969d1aadcdfc", + "packages/flow/openadapt_flow/runtime/durable/continuation.py": "f9d3248848417f9c137886cb24befaa3755c444c926b8991aecc2d4f6ca330a6", + "packages/flow/openadapt_flow/runtime/durable/controller.py": "fa65769ca2ac99351996718cf561baaad58dcb75b78d537b43703e14102f9e28", + "packages/flow/openadapt_flow/runtime/durable/program_checkpoint.py": "1945df32ff1d418c5eac7f9f02fe29297da315d6e07e395cb706312ab1d2cf53", + "packages/flow/openadapt_flow/runtime/durable/resume.py": "bce914258a73b5d4faa9e275b1f4f631d3dba967093cb901c241636de7885eba", + "packages/flow/openadapt_flow/runtime/effects/__init__.py": "ca3e5432400acf4dabc353f08f8c9b44ea8a23157c1020b10794ab6666962ee0", + "packages/flow/openadapt_flow/runtime/effects/_common.py": "e235206c1353bb5d58d43ec47c9be7e4ffea4f830ac0ee27a8645a3cece197b6", + "packages/flow/openadapt_flow/runtime/effects/adapter.py": "6b44cda61a78ce3934db28a6b2ce1ccadac5a52a99f8bbc225346f602116b5a4", + "packages/flow/openadapt_flow/runtime/effects/auth.py": "af0c2be6ab49f998aaf404800a0f5c8a3a35f30f08dda609cafa8b49ea622f91", + "packages/flow/openadapt_flow/runtime/effects/compensation.py": "3e6feb4924e61ffb67802151907218b47c627923362206f5c83aea86a00da3db", + "packages/flow/openadapt_flow/runtime/effects/document_arrival.py": "1f62bb1c46f0f9521610a152fe68340a96014bc4a8d293d6681ef5099acc1307", + "packages/flow/openadapt_flow/runtime/effects/document_hash.py": "5c64f1ffc187cd03114d61dff3518080e59b817f8b1c3424dbc4b57db30e1f04", + "packages/flow/openadapt_flow/runtime/effects/effect.py": "89652d08269cbe36fdba23d323f2e7265c2a56b54774ce872f58d72e6fa19641", + "packages/flow/openadapt_flow/runtime/effects/email_delivery.py": "bca21113e2c8fc3f3820fd1c0e27c16f2c5db920ccdd05275477bd08884b6ce2", + "packages/flow/openadapt_flow/runtime/effects/fhir.py": "446fb684445c6eed847dd124e0729f3538a749592c36ceeb4f7bc79de434b94a", + "packages/flow/openadapt_flow/runtime/effects/file_arrival.py": "8a5dcb94534e9f3ef40fa820cc18ae73e45f626137f0de856941c3adcfc235e5", + "packages/flow/openadapt_flow/runtime/effects/graphql.py": "cebae9844af5046963fdaba3abaed3ed1f9b1084dedd1a8948ac454b64990e26", + "packages/flow/openadapt_flow/runtime/effects/onscreen.py": "87285b4dc7e58435b1c3ad24f0a3283e32d2bb18eb644cc871b01b3227f1bfe6", + "packages/flow/openadapt_flow/runtime/effects/readback_values.py": "7d9bce37cbb3d6a6dcdbdf5337a61becbaf54b611177928a024e63869a84ab2e", + "packages/flow/openadapt_flow/runtime/effects/rest.py": "44af95a068db190bec496f81140a23698bdf6883c96c30185d063d7fb9c3e104", + "packages/flow/openadapt_flow/runtime/effects/smart_auth.py": "57dc86cbd4249d528148d6c47a0bb5eec56d9c2e854ede8e0a97a7dbe8cda15d", + "packages/flow/openadapt_flow/runtime/effects/sql.py": "53ead9fbc0ac87e831abbde14dceccfed5d05c917b5752a6aa6d8c59a8f9830b", + "packages/flow/openadapt_flow/runtime/effects/ui_readback.py": "9f4b2eab01eb854066cc45a21c405e959aa652a101ea5f0fa860c329189cff6f", + "packages/flow/openadapt_flow/runtime/grounder.py": "f9d6916310a7ce4a184b363fb073f99e783f09f7546a3d3c68574a5d403b7ea2", + "packages/flow/openadapt_flow/runtime/heal.py": "73567f8f02cadd52137736934618ceef8873b5d09ca0bb8e1d2f2ecb24efd516", + "packages/flow/openadapt_flow/runtime/healing/__init__.py": "7f2b58eb0e8d53394d9764066378e228f4dcbd8fd48b143b3497c11167b6ad80", + "packages/flow/openadapt_flow/runtime/healing/governance.py": "f3a755d46bbd7eedbe6b0ebc962cc2073714551eee34c7f5f2faf002623d3222", + "packages/flow/openadapt_flow/runtime/healing/patch.py": "2a918bfbe9523c7946085581465bc6fae5735ba9d9e46e2a0a04e4a841fdadb7", + "packages/flow/openadapt_flow/runtime/healing/perturbation.py": "18159ae5a273b33b90ee677164e44fe794bff4c8cfa96a9d3d0f9f8d12166f18", + "packages/flow/openadapt_flow/runtime/healing/pipeline.py": "100adcd9230ffa734c2ad85146c1c4af5e065c5c882ab1a36cc7a4391a516909", + "packages/flow/openadapt_flow/runtime/identity.py": "b467655b862df4b6d68ad753e25cda8b9290ad1f82605b9733ff2ce934e84154", + "packages/flow/openadapt_flow/runtime/identity_template.py": "2b08165c1b59f7cb8503f959ad2953c2ab9161f6dbff1715081e9e6ae2f83dcd", + "packages/flow/openadapt_flow/runtime/identity_vlm.py": "be1151e8bdb3cafe428b5dd25579d6c5d7ef7b47c18ff3f9687d56f94a229428", + "packages/flow/openadapt_flow/runtime/known_ids.py": "b92cd3dbbce15a2d0e14ee3a828a8796785632e2d6cc7d26e67bc399e29a8d28", + "packages/flow/openadapt_flow/runtime/layout_drift.py": "09bbcfce5564a95ab383ac455d68ff065fb274f8100fa240a9a0e0d7fa338fc4", + "packages/flow/openadapt_flow/runtime/process_v1.py": "b6fc68e4abb2e6555002194f7af1c2a4e21be27d6f5075b491e14341b6d3e84a", + "packages/flow/openadapt_flow/runtime/program_predicates.py": "3e76056ee036464a35dcd0a001f167d00b78eb97e73aa2100270dc6ea6563106", + "packages/flow/openadapt_flow/runtime/remote_vlm.py": "cc00a062f99e2f4abe608346fef912da5f2eaed9d90fc2d6a4c4f17debf7fd97", + "packages/flow/openadapt_flow/runtime/replayer.py": "1cbff1714df1586f11e2068b502997091c46e6e3a638d9d28ff387865bfc9e47", + "packages/flow/openadapt_flow/runtime/resolver.py": "9f6aa55a51197ae7ed9865469bd3516c67784eba7c5225f36782b4ab2e9a987f", + "packages/flow/openadapt_flow/runtime_validation.py": "18675d6b4c40458394febb36cbe28e99f410cb65b4cab138b68df3cacc6f7dd4", + "packages/flow/openadapt_flow/sanitized_artifact.py": "4271c2e9b21f666d5010122176d7884571a6f12ff153356eea7eb9413224806a", + "packages/flow/openadapt_flow/scaffold_verifier.py": "2451720bdc8ae78cf41e06636d2968d00cce92c165db2b97def7c689b9640f40", + "packages/flow/openadapt_flow/seal_batch.py": "c93b91fb0401d7fb3810b304c1e99a5817b82c346927c83c96cfd77892d31415", + "packages/flow/openadapt_flow/services/__init__.py": "f38b556ba34b1fc674267a2c386edf9beb542d5a98c8e266b70f61ec6a617ba4", + "packages/flow/openadapt_flow/services/vlm_service/__init__.py": "da71deb7edc5ea6c30c250faf4cb39b4a76b532801debe6f64598b5d2ed8978e", + "packages/flow/openadapt_flow/services/vlm_service/__main__.py": "3844e4479038b670ef897697c71c05bb8e9f92136425ff899137501b3fc60966", + "packages/flow/openadapt_flow/services/vlm_service/app.py": "808094e2285b70965cc7f84a2c6037749ef29304318e1986c3dbbf7e4a1ec9c0", + "packages/flow/openadapt_flow/services/vlm_service/backends.py": "771df17902dc1c31e44517880706468f0c3dd13ee8dedc161ca60f159e1d9255", + "packages/flow/openadapt_flow/services/vlm_service/batching.py": "545ceb328637acbbaf343566059bcd23ba61ae9b385c6d32b14844a08c074971", + "packages/flow/openadapt_flow/services/vlm_service/config.py": "0bf363d7be1419ed1dd1597230a2be87d880473e5d19df4a9b8ef5e2f33a1bee", + "packages/flow/openadapt_flow/services/vlm_service/prompts.py": "f3af38485a7f61ad96fdaf066387c4809cf34b60cb96147ac186caa828cff1c3", + "packages/flow/openadapt_flow/services/vlm_service/schemas.py": "ca2de9068dcdfc45ca2b33a73fe5b51edaef0c27299b901ec5b340aa0143462f", + "packages/flow/openadapt_flow/submit_binding.py": "d7e24dbc08795e500dadb3272d50fd0ddf6afcd290d95434387c68ed2c805657", + "packages/flow/openadapt_flow/surface_selection.py": "228f4737a19d6aa1c3364166bbab72a3f87235f3ff1687ea843f6a40a4213bb5", + "packages/flow/openadapt_flow/terminal_verification_v2.py": "3bc44d80abc3679cebdeec123b34bcc5656cd2624b1c8943bb22352c0efc3261", + "packages/flow/openadapt_flow/transaction.py": "c3bd88b18d8c5851970cc22021c0e721ddcc67e9ba0075727186c4d7c542d150", + "packages/flow/openadapt_flow/traversal.py": "4bec0fcd5877649e71f7273a17a2073d56c83af6cb7fcd595d0eb9bd7f7a387b", + "packages/flow/openadapt_flow/tutorial.py": "870a3de4ea966830d24902920290f2fe25f51d3e6b2df605800030d452658b42", + "packages/flow/openadapt_flow/validation/__init__.py": "d7f94f2027a19347364cf9491a94f9e6d8c497cd4a67e555e9d200d406e35e0f", + "packages/flow/openadapt_flow/validation/dense_surface.py": "926327c242a21f32bf06500ec43229e10bbcaceaa3dab8d9506c831e638c6512", + "packages/flow/openadapt_flow/validation/governed_run.py": "e5385fb7dda147a162d8e5d3f41999068fd0f424ab901bc94824c1e39ef97407", + "packages/flow/openadapt_flow/validation/hardening/__init__.py": "f54bdcaf0e0e1c8706e1262d983b7d57dba12e7c3912c50aaec148cc4a6a352a", + "packages/flow/openadapt_flow/validation/hardening/__main__.py": "6a2637d07c62e55e6d46dbd277e7c5d499426044120e949532010efe988aeb75", + "packages/flow/openadapt_flow/validation/hardening/corpus.py": "927d3f60d1971b82175c6a40a4b45116ba1339e8bb644da5750981e3770e068f", + "packages/flow/openadapt_flow/validation/hardening/fixtures.py": "44e85d3c03f03bd46c017a15e46ebff0fee483aed0d76045120e3c43e631318b", + "packages/flow/openadapt_flow/validation/hardening/harness.py": "f214c456ded5c2b27eec1fbe1810c5c7d0467960459baecb04c810df4f978e18", + "packages/flow/openadapt_flow/validation/hardening/perturbations.py": "fe634c8e6878766beea377bd0c62a6ea681d7ff883ff2896c98941a6f3112583", + "packages/flow/openadapt_flow/validation/identity_ladder.py": "834dd30ecbd3ab1195beac4e7fd50e5d3d9df539c7250de76d2bc2d19eae2735", + "packages/flow/openadapt_flow/validation/identity_rates.py": "d0648e8e94b96677366ac5372453de797f6221a8aa5c799fe2327674d90c851c", + "packages/flow/openadapt_flow/validation/known_ids_dense.py": "9dfcc761b649e83f781e3390077b1c7eea60b4bd16c92e2dae9dd02173192835", + "packages/flow/openadapt_flow/validation/pixel_identity_aligned.py": "dff6c6f9d7ae1788f906c42b2cf443e34f93baf3d26d645413b2c84b54933053", + "packages/flow/openadapt_flow/validation/pixel_identity_probe.py": "66095f52fe8de431a3ab0cf68ba02202bb459b46927e98d7ae6bcf4c68d7ef49", + "packages/flow/openadapt_flow/validation/structural_action.py": "1f231ebe02d241ad34b3d9867be26850748a487fb586f646350192d0487dda49", + "packages/flow/openadapt_flow/validation/vlm_identity_probe.py": "f3641e289f0a02e80fcb31ff76c34c0ed150c647fa9954252425db2b7659b713", + "packages/flow/openadapt_flow/verification.py": "0bb9f564b4d3dccce7fbebad64abaae1cbd4bd600ffa1b58ec18d9025df52ff2", + "packages/flow/openadapt_flow/verify_run.py": "c4495eedc9622b76d3084f237f206f453cc56f1f27196d4610604bb47baf2020", + "packages/flow/openadapt_flow/vision/__init__.py": "f41b931a97bf4ac5a8f1f141dc905bec4367bb350e8ccc90446842eb63765bd5", + "packages/flow/openadapt_flow/vision/hashing.py": "60d5b56a991100310163007d6778d6bec8521826c3f586f6b88e6e1a76f1d70e", + "packages/flow/openadapt_flow/vision/imgops.py": "9cd252bcb3a8bab46795098bc29cd5cf988d77c96abd9687ba93b9252e3f53c9", + "packages/flow/openadapt_flow/vision/match.py": "24b301455f7418155ce2757baf126548d0fc663b806b200a1c2c6d866e077458", + "packages/flow/openadapt_flow/vision/ocr.py": "2e6482a3069b324c1b7686ac1ba8cd073165ea9527b4171f840d488356ee9eda", + "packages/flow/openadapt_flow/vision/ocr_backends.py": "c49ef78611856dcd0b11aff0836b1683ae1ed734e33e5e7289a71779aee8e0f9", + "packages/flow/openadapt_flow/vision/settle.py": "f9cc521dc55ffddefedf529d4c748715b03d27d45b40521a819a7e255044e0c5", + "packages/flow/openadapt_flow/visualize/__init__.py": "a55d38dcb0fac704b35136a4134d89ffcc3343000f50b2624b8bc7093ce34d90", + "packages/flow/openadapt_flow/visualize/admitted_composition.py": "9758fb49a8d3e587204e3df921a42cf823921cb25f8c66e08cfec748337be8d7", + "packages/flow/openadapt_flow/visualize/builder.py": "b655235b48739e81e0bed3d0c9d01511c302d582952455175610744c23778fff", + "packages/flow/openadapt_flow/visualize/composition.py": "52d42000d0747af577a6f4e2af0b4326c22790c3cf7fc2e6ebd05b83baef91d0", + "packages/flow/openadapt_flow/visualize/projection.py": "bb8801775904799de53d4b40eacfe65c4661ecb6478569e70af6f08a7508547a", + "packages/flow/openadapt_flow/visualize/render.py": "400589d8b3dcd7bbb9daa738aac2a338675aad80095b116922ce10de645f565f", + "packages/flow/openadapt_flow/visualize/spec.py": "fc9bb2dd0fe5cefbb20aa66d90794aff50fa7983784a8582ee862712c28b27c0", + "packages/flow/openadapt_flow/volatility.py": "117c9df0dcbeff9669f78b27455c5bc1ada762798c9a64393c30fdab5c29dfa1", + "packages/flow/pyproject.toml": "00e8071c36123320d4c2bbe009ea6cdde50f047d82a45ddcc21aca1869e1ea1b", + "packages/flow/uv.lock": "90f90b155fbcbdd826e204e4fd2293b4a24e095777a470638e2247311bd918cd", + "packages/types/openadapt_types/__init__.py": "9359d2e723c383afbc214e6dde7964505eb2b21acc52631e2349f7a50f3acfbf", + "packages/types/pyproject.toml": "5b81ba48e6555c4c218393d9d46f5e4126fa9e60de1d2d6cde96a6a6b2ccc687" + } +} diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/canonical.json b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/canonical.json new file mode 100644 index 0000000000..4bc7b3c700 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/canonical.json @@ -0,0 +1 @@ +{"files":{"templates/button.png":"fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493"},"workflow":{"contains_phi":false,"created_at":"2026-01-02T03:04:05+00:00","data_sources":{},"encrypted":false,"name":"release-compatibility","param_specs":{},"params":{},"phi_scrubbed":false,"program":null,"recording_id":null,"schema_version":2,"secret_params":[],"steps":[{"action":"click","anchor":{"click_point":[80,70],"context_text":null,"identifier_crop":null,"identifier_region":null,"identity_template":null,"landmarks":[],"ocr_text":"Apply","region":[40,50,80,40],"search_pad":80,"structural":{"automation_id":null,"name":null,"role":null,"selector":"#apply","window_name":null},"structured_identity":null,"template":"templates/button.png"},"api_binding":null,"effects":[],"expect":[],"guard":null,"id":"apply","identifier_crop_missing_reason":null,"identity_armed":null,"identity_unarmed_reason":null,"intent":"apply synthetic change","key":null,"param":null,"risk":"reversible","scroll_dx":null,"scroll_dy":null,"secret":false,"text":null,"timeout_s":10.0,"wait_until":null}],"subflows":{},"viewport":[320,200]}} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/manifest.json b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/manifest.json new file mode 100644 index 0000000000..978a6ef5f9 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/manifest.json @@ -0,0 +1,22 @@ +{ + "schema_version": 2, + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.5.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/templates/button.png b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/templates/button.png new file mode 100644 index 0000000000000000000000000000000000000000..797e0782c988cdc7fe1b99c3613d3c826f48477b GIT binary patch literal 579 zcmV-J0=)f+P)TsqS%X#kV3K3Ld!q|A%z19K_Xa*jd%|ME3vh;u(PvL!Td;wHd=^RV4+|d z#a2)W1ixZnSzMQIVMlj~^Hh_=WH=8q2`S$;8;mSyYpdN!Ly2yM69y9tKp`ORhn0LSAol}cT&SF+tOgoRDh6a*m_i&>Us z+cp5$ww+8SbzQGiDzYr!O)!!qDT)FBhGFFMd5_0Kwi|}9u%alvUQZB&{eC~6&vBT` z%HJ8uNnavW#9Ue9JT05DB+JRWB<8HQoJUN4^D z^Z5YabAkzn!wkdhc00?m5{U%CVjm&7!;8ft9*^TO$8nR%1OObz!7s4EV8HYI-Q=A@ zp&-k$AP59o-9S=UQIvE#jmLpNAQ%j;Rx7{XKO7DbLj8Wf(P#j`=Oh}9wpuL!5Jhn` z8a?hj1KA_pY&Jh$;s+7{+U@qu%$sC7o#Mgea^W~G5{VFOb;BR}($X}oR4U;;e;g!t z_%4=Z{WrlNgb+fH?(>Is_&HEvw?l>94i$DgRM_oMVYfqt-OkGidtU9grGL*i#q_=y RoT~r;002ovPDHLkV1k8;0VMzc literal 0 HcmV?d00001 diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/workflow.json b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/workflow.json new file mode 100644 index 0000000000..872d60f9cc --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/linear/workflow.json @@ -0,0 +1,112 @@ +{ + "schema_version": 2, + "name": "release-compatibility", + "recording_id": null, + "contains_phi": false, + "phi_scrubbed": false, + "encrypted": false, + "created_at": "2026-01-02T03:04:05+00:00", + "viewport": [ + 320, + 200 + ], + "params": {}, + "param_specs": {}, + "secret_params": [], + "steps": [ + { + "id": "apply", + "intent": "apply synthetic change", + "action": "click", + "anchor": { + "template": "templates/button.png", + "structural": { + "selector": "#apply", + "frame_path": null, + "role": null, + "name": null, + "automation_id": null, + "window_name": null + }, + "region": [ + 40, + 50, + 80, + 40 + ], + "click_point": [ + 80, + 70 + ], + "ocr_text": "Apply", + "context_text": null, + "structured_identity": null, + "identity_template": null, + "identifier_crop": null, + "identifier_region": null, + "form_identity": null, + "landmarks": [], + "search_pad": 80 + }, + "text": null, + "param": null, + "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, + "key": null, + "modifiers": [], + "drag_end_anchor": null, + "scroll_dx": null, + "scroll_dy": null, + "expect": [], + "effects": [], + "api_binding": null, + "risk": "reversible", + "risk_explanation": null, + "risk_review_required": false, + "wait_until": null, + "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, + "timeout_s": 10.0, + "identity_armed": null, + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null + } + ], + "interstitials": [], + "program": null, + "subflows": {}, + "data_sources": {}, + "manifest": { + "schema_version": 2, + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.5.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false + } +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/canonical.json b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/canonical.json new file mode 100644 index 0000000000..0ee4c36a3b --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/canonical.json @@ -0,0 +1 @@ +{"files":{"templates/button.png":"fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493"},"workflow":{"contains_phi":false,"created_at":"2026-01-02T03:04:05+00:00","data_sources":{},"encrypted":false,"name":"release-compatibility","param_specs":{},"params":{},"phi_scrubbed":false,"program":{"entry":"apply","states":{"apply":{"id":"apply","kind":"action","loop":null,"on_exception":null,"outcome":null,"reason":"","step":{"action":"click","anchor":{"click_point":[80,70],"context_text":null,"identifier_crop":null,"identifier_region":null,"identity_template":null,"landmarks":[],"ocr_text":"Apply","region":[40,50,80,40],"search_pad":80,"structural":{"automation_id":null,"name":null,"role":null,"selector":"#apply","window_name":null},"structured_identity":null,"template":"templates/button.png"},"api_binding":null,"effects":[],"expect":[],"guard":null,"id":"apply","identifier_crop_missing_reason":null,"identity_armed":null,"identity_unarmed_reason":null,"intent":"apply synthetic change","key":null,"param":null,"risk":"reversible","scroll_dx":null,"scroll_dy":null,"secret":false,"text":null,"timeout_s":10.0,"wait_until":null},"subflow":null,"transitions":[{"guard":null,"label":"","target":"done"}]},"done":{"id":"done","kind":"terminal","loop":null,"on_exception":null,"outcome":"success","reason":"","step":null,"subflow":null,"transitions":[]}}},"recording_id":null,"schema_version":2,"secret_params":[],"steps":[],"subflows":{},"viewport":[320,200]}} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/manifest.json b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/manifest.json new file mode 100644 index 0000000000..4d87825396 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/manifest.json @@ -0,0 +1,22 @@ +{ + "schema_version": 2, + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.5.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/templates/button.png b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/templates/button.png new file mode 100644 index 0000000000000000000000000000000000000000..797e0782c988cdc7fe1b99c3613d3c826f48477b GIT binary patch literal 579 zcmV-J0=)f+P)TsqS%X#kV3K3Ld!q|A%z19K_Xa*jd%|ME3vh;u(PvL!Td;wHd=^RV4+|d z#a2)W1ixZnSzMQIVMlj~^Hh_=WH=8q2`S$;8;mSyYpdN!Ly2yM69y9tKp`ORhn0LSAol}cT&SF+tOgoRDh6a*m_i&>Us z+cp5$ww+8SbzQGiDzYr!O)!!qDT)FBhGFFMd5_0Kwi|}9u%alvUQZB&{eC~6&vBT` z%HJ8uNnavW#9Ue9JT05DB+JRWB<8HQoJUN4^D z^Z5YabAkzn!wkdhc00?m5{U%CVjm&7!;8ft9*^TO$8nR%1OObz!7s4EV8HYI-Q=A@ zp&-k$AP59o-9S=UQIvE#jmLpNAQ%j;Rx7{XKO7DbLj8Wf(P#j`=Oh}9wpuL!5Jhn` z8a?hj1KA_pY&Jh$;s+7{+U@qu%$sC7o#Mgea^W~G5{VFOb;BR}($X}oR4U;;e;g!t z_%4=Z{WrlNgb+fH?(>Is_&HEvw?l>94i$DgRM_oMVYfqt-OkGidtU9grGL*i#q_=y RoT~r;002ovPDHLkV1k8;0VMzc literal 0 HcmV?d00001 diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/workflow.json b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/workflow.json new file mode 100644 index 0000000000..59c2a9c9d1 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/program/workflow.json @@ -0,0 +1,142 @@ +{ + "schema_version": 2, + "name": "release-compatibility", + "recording_id": null, + "contains_phi": false, + "phi_scrubbed": false, + "encrypted": false, + "created_at": "2026-01-02T03:04:05+00:00", + "viewport": [ + 320, + 200 + ], + "params": {}, + "param_specs": {}, + "secret_params": [], + "steps": [], + "interstitials": [], + "program": { + "entry": "apply", + "states": { + "apply": { + "id": "apply", + "kind": "action", + "step": { + "id": "apply", + "intent": "apply synthetic change", + "action": "click", + "anchor": { + "template": "templates/button.png", + "structural": { + "selector": "#apply", + "frame_path": null, + "role": null, + "name": null, + "automation_id": null, + "window_name": null + }, + "region": [ + 40, + 50, + 80, + 40 + ], + "click_point": [ + 80, + 70 + ], + "ocr_text": "Apply", + "context_text": null, + "structured_identity": null, + "identity_template": null, + "identifier_crop": null, + "identifier_region": null, + "form_identity": null, + "landmarks": [], + "search_pad": 80 + }, + "text": null, + "param": null, + "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, + "key": null, + "modifiers": [], + "drag_end_anchor": null, + "scroll_dx": null, + "scroll_dy": null, + "expect": [], + "effects": [], + "api_binding": null, + "risk": "reversible", + "risk_explanation": null, + "risk_review_required": false, + "wait_until": null, + "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, + "timeout_s": 10.0, + "identity_armed": null, + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null + }, + "loop": null, + "subflow": null, + "transitions": [ + { + "guard": null, + "target": "done", + "label": "" + } + ], + "on_exception": null, + "outcome": null, + "reason": "" + }, + "done": { + "id": "done", + "kind": "terminal", + "step": null, + "loop": null, + "subflow": null, + "transitions": [], + "on_exception": null, + "outcome": "success", + "reason": "" + } + } + }, + "subflows": {}, + "data_sources": {}, + "manifest": { + "schema_version": 2, + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.5.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false + } +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/provenance.json b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/provenance.json new file mode 100644 index 0000000000..809b75f13c --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.5.0/provenance.json @@ -0,0 +1,673 @@ +{ + "bundles": [ + { + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "files": { + "canonical.json": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "manifest.json": "ef26a2860b09b15fbfeff39f31f0ef58c45fc129f71dddd37b672e2877e6c0f5", + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493", + "workflow.json": "c16508dea245f5291a21c08231a4afddbce9c727aae8db81d3f1cb34d35e1689" + }, + "shape": "linear" + }, + { + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "files": { + "canonical.json": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "manifest.json": "b458dd746697c74200d3b21eb285b3278d26adc690db2fd6fc003e2a34528c56", + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493", + "workflow.json": "a8095810ee4a0b1657d65c73b67d72b57e70c335e4e8fe90be6bf6688620e54f" + }, + "shape": "program" + } + ], + "commit": "5a3bf49a0bf2d662430386295a1c34036c064d58", + "generator_sha256": "d995443471ee4a6fd6854e53a15da4d3681c798a5af9ffde7d3506d8c5015e28", + "original_release_load_verified": true, + "pydantic": "2.13.4", + "python": "3.12.16", + "release": "2.5.0", + "source_hashes": { + "packages/flow/openadapt_flow/__init__.py": "52562986a73b76e4b8b8e6e2ad784f63a6d1c88eb4949a63f314c80456ebb51d", + "packages/flow/openadapt_flow/__main__.py": "6637fe40f77b88e55d51eec353302c393e25b5db7694e998d548ac0a4e8db9c2", + "packages/flow/openadapt_flow/_browser_setup.py": "d259bd323de12fa03b4bf529d77ccb6350d50f665309c377e6df182525c37e6f", + "packages/flow/openadapt_flow/_prewarm.py": "4024f33b41a2fac44b995dfc85a922366b98c18697f6a2b623cb26d72264ed48", + "packages/flow/openadapt_flow/action_evidence.py": "8f361a151589fcea66c392bf4913ae22b20844921941fe9ab1f2135e7bdd8b0a", + "packages/flow/openadapt_flow/adapters/__init__.py": "2eca3615b0a12e39a4a51fd7bf2c274785014c548e609597029ef7d8299c9a09", + "packages/flow/openadapt_flow/adapters/capture.py": "8a2c0af76e13cc968015852296d32b0621cb626823a69c6011abcd7391072825", + "packages/flow/openadapt_flow/adapters/desktop_recorder.py": "7f05b9bf03bc1da900d0acceae4cd4ae0a82efc4a3b9ef011c6649e1044b3454", + "packages/flow/openadapt_flow/admit_pipeline.py": "8caea372aeae2d40624fad965c75efca1cab4dd3ce0d733428bfc2fa03b9afc3", + "packages/flow/openadapt_flow/admitted_composition.py": "0fcf3b21a233c9ccf0ee71fec2848a80f755fa941ba5f462a47dd544e8aeeeff", + "packages/flow/openadapt_flow/after_stop.py": "6395400f6a578d0013fe284141b6778c0d77321cc16918c7bf42ae2a36153de2", + "packages/flow/openadapt_flow/agent/__init__.py": "a08c79b09e45150bd065c5008a2585081ac6c10a1d9c4489dad01718fbb94973", + "packages/flow/openadapt_flow/agent/__main__.py": "5780b9a3b365c96d71fa42a6ea977a1518d711ed5c4b4f37b54b16ffbc9d0419", + "packages/flow/openadapt_flow/agent/admission.py": "2fc9add3c7dc983d49e583d920dd145a95b5152c01e6215bc6dee6a2fd5dcf3e", + "packages/flow/openadapt_flow/agent/approval.py": "a345a90e0e3fa788b520fd26150971a0a5aaff16173a663541f92845ecf409d7", + "packages/flow/openadapt_flow/agent/attended.py": "5fa4c761de375be9ce0f6cf7f72f0230253ccd7546658382a267da2f01cb0f1b", + "packages/flow/openadapt_flow/agent/authoring.py": "87afc6deb8fd2ed6a1e670c37978648fd633deea8c380b0ca937b369bbb452af", + "packages/flow/openadapt_flow/agent/bridge.py": "8675f0c4df081f7573e6df6aa6d943be72103cb8669ab7bae4611560f21d1f79", + "packages/flow/openadapt_flow/agent/browser_teach.py": "4c12fc8dbc62b3efc5009f8e8ab40218923ebb095823c9f662eada6ea806d843", + "packages/flow/openadapt_flow/agent/bundles.py": "a3e2d63576adc99fd8e4d53e0fa64f1bec914a070d5c0151ba9278ea923809c9", + "packages/flow/openadapt_flow/agent/cards.py": "45ecfb3da287aeb5de1160d1adbcc3590c6cc8c20f7b1b7ab983f643228f62b5", + "packages/flow/openadapt_flow/agent/cli.py": "1a6c15f8371a39acfd958bb3f82f182866d097b4ed281f95b95de2d055340394", + "packages/flow/openadapt_flow/agent/contract.py": "4a265a3a961578dcd4cd5e26be85d6eb0799ac4f557924a36b00e17ffdc39c67", + "packages/flow/openadapt_flow/agent/copy.py": "9afe666441689375fcb5aaf54632db2e39e0a32edeb95570789f0d7ce4b55423", + "packages/flow/openadapt_flow/agent/delegated.py": "49414a62704b93999fcf668db4df737121f4b5d9d22f1b36a94de705d171371e", + "packages/flow/openadapt_flow/agent/example_app.py": "847ba77d93cc438e7e54143f5e7686d53733499154e2cde979e20e5f4b871f7e", + "packages/flow/openadapt_flow/agent/flow_service.py": "b5bb10bf1cb819bb0aba9185de9e9d1246a823ff343996a7464d8e481785f2dd", + "packages/flow/openadapt_flow/agent/guard_tools.py": "6c61c36f77ed394797d07466a430387bbe8e9a383877833d769514b13d7dd843", + "packages/flow/openadapt_flow/agent/ledger.py": "e0911cb91a52570a8244ae4e72c9985c0110f30eeacf3d1a19a1c394e2938a8b", + "packages/flow/openadapt_flow/agent/ledger_demo.py": "de6fca47b663553749c6e432c3c96897dad3aa9313d1c1752162d2ab5b707eb8", + "packages/flow/openadapt_flow/agent/mailbox.py": "c5c8c9a305ddbe8caa985066e0c6f587df4d3703f3d67c7e6643076ab2a709fc", + "packages/flow/openadapt_flow/agent/mcp.py": "6c180e6d5310ec5b242a7757e42d469bacfb6cf7d4a5bbd72485b1bbeb8594ee", + "packages/flow/openadapt_flow/agent/mcpb_entry.py": "23db9f1b3af8bed52ee472b36b0272430d11526f045179602724addc92a22ac8", + "packages/flow/openadapt_flow/agent/repair.py": "31c92c511930b602b90f43e66623e97ada890eb91c4954da49e77ca793b78628", + "packages/flow/openadapt_flow/agent/runner.py": "a8c30b966efe0532cd9f114b5f5773eac6107d2785df98cd7562d200343d8378", + "packages/flow/openadapt_flow/agent/runner_bind.py": "91d4006e99378c61af2eb50fc9c03ebfb477cdf9f0d27fa070ee9936fb073e96", + "packages/flow/openadapt_flow/agent/runs.py": "abd7041cd1eae1b6017c208e0cf4b083caf965cd2147d0497e896a013cd5cb25", + "packages/flow/openadapt_flow/agent/sandbox.py": "ee78c7959d8213dbd3d4b75eb64ecb2a797147410f90516eef57e6c2bb9edb01", + "packages/flow/openadapt_flow/agent/seal.py": "ea0585e2564f23a59ccc41763b58357a176e53363f3107ab7b54639d902de439", + "packages/flow/openadapt_flow/agent/seal_cli.py": "fcae30891db50c28e6a23282ff94a0dce9796c15c7fea46b8fdc34694ff8acc1", + "packages/flow/openadapt_flow/agent/service.py": "275da34a73890b0c85fb5c06965403162adcacfc499a7493736aab236b5538ac", + "packages/flow/openadapt_flow/agent/skill.py": "7f1637d4bcfdb54ff5920b06dd3bbf913e6fdbc0030605b455e9223672ad2173", + "packages/flow/openadapt_flow/agent/teach.py": "e68371be5b7986097f70b9241c5d0e1ce7576e21fe55ed35eae677ee0ac85640", + "packages/flow/openadapt_flow/agent/tutorial.py": "ac42a64169cb23fac79943a0ef8e4678922d120d51aa97944e2be707addb3232", + "packages/flow/openadapt_flow/agent/verify_tools.py": "057b72b0456a88b802afc87d219853ad5c19717ab8237243660cb3aaadb933e4", + "packages/flow/openadapt_flow/agent_guard/__init__.py": "e1f1ec33d219e845e1de18b61752fe06fff4ba1e12d403870b58d4ef5094c1fa", + "packages/flow/openadapt_flow/agent_guard/cdp.py": "e0a59d4f49dee5a9b0e8b6d3c16db794c923a5b4c8af4fa1418e8da13478d8ca", + "packages/flow/openadapt_flow/agent_guard/cli.py": "e4792c7ce115eeace17871dd5c8cee569a6b9e7a5365acc53d82946677cddd9b", + "packages/flow/openadapt_flow/agent_guard/commit_guard.py": "bcf7cc8d91ce7ab804745766ee4b838b648095364467f2a203f78864ba09090c", + "packages/flow/openadapt_flow/agent_guard/config.py": "b25c4e327836f3d2c9167d421eb85d72fbca5c7e8bf7f31de25da3625470caf1", + "packages/flow/openadapt_flow/agent_guard/handoff.py": "7f84e49d42717d304f60257a429b36305c4a32cb7e14566185d4789634283ff5", + "packages/flow/openadapt_flow/agent_guard/requests.py": "92e42c8462ff7a6effc0e7437dd01ace72f02e04c3072ae29495902787cd0d26", + "packages/flow/openadapt_flow/agent_guard/session.py": "9a419ef69572516f5da12efe0d07b72e8effe15634fe1d57635b257abd28a8b4", + "packages/flow/openadapt_flow/anthropic_defaults.py": "e22dec3d87dae9724a5a4d7b547fccc60fffccf952f4d951ff2fa76b312e6778", + "packages/flow/openadapt_flow/api.py": "7a34b5b4a7d5b3b815c4008d9d3efab1badddb77dac7477e17308602d1e532b1", + "packages/flow/openadapt_flow/app_profiles/__init__.py": "c01c098dec84e3831e31628afa6806f4830f9efffd037b4af7e9e70a0a4c8ce4", + "packages/flow/openadapt_flow/approval_mode/__init__.py": "7d802c15089999853ed4a0b55bdaa1962998f2b8e9ee0170695c7de2ec5c3e10", + "packages/flow/openadapt_flow/approval_mode/approver.py": "2e314d0ae6432f81972cdc84a8db1635161a6d17dd8fe0e9cb656fd254db7197", + "packages/flow/openadapt_flow/approval_mode/approver_roster.py": "85fb6593d6fb418d8ccb180207cab84d20cfdee59326a8df22a90c0c5c442aef", + "packages/flow/openadapt_flow/approval_mode/coverage.py": "1e5875a9d2d2d87791dcc27af30617c8cf6ed1d8a559f733e5fb634898604b91", + "packages/flow/openadapt_flow/approval_mode/demo_approver.py": "463281495181c3f97969517391a6912a4464a9431ef7c4fd49ba451f61155b80", + "packages/flow/openadapt_flow/approval_mode/durable_carry.py": "0f0c5c204fe6cfabef54bd988a3050b58ed6a0a13b178c2615cbeafc2fb7b7d8", + "packages/flow/openadapt_flow/approval_mode/form_state.py": "f2548b7f2527e9d648623fc5bafa841feabe8272d6a5822e23d4c937e910ae53", + "packages/flow/openadapt_flow/approval_mode/gate.py": "f1e35292d0ca73d7273ade289d5cc838580ecb643e13c9a454c3ebf2a346a20c", + "packages/flow/openadapt_flow/approval_mode/human_attestation.py": "eca372e1fa68d9a626ac80046dbf6ab401e97b08a185bd162b278003505883f3", + "packages/flow/openadapt_flow/approval_mode/item_approval.py": "98874c9985f0ead33c5e5cc84d3853f9c5f2b809f56d8c7e6018053d9d30f688", + "packages/flow/openadapt_flow/approval_mode/model.py": "42f121b2a7fc992bc1201ccb8c587d7134fec6fdbc54eb12dacd71ff4a00774a", + "packages/flow/openadapt_flow/approval_mode/post.py": "91134523827840f5d4de7652513b4a70a906abf64ddda9c367b9fb814045a006", + "packages/flow/openadapt_flow/approval_mode/requirements.py": "4f8ba1eff008e45651365929bbbaee873301d993c714168e78456009b06b402b", + "packages/flow/openadapt_flow/approval_mode/signed_policy.py": "4dc065c007d6c3112de2af004e36ecd8559ceac8a76eaf9853c1d49f7363c757", + "packages/flow/openadapt_flow/approval_mode/switch.py": "2fa2cc31eef6b27e490897eb7997f8679e843cb86508f41b72cc8b5fbe67cbf8", + "packages/flow/openadapt_flow/assurance.py": "fba477ecf901039ebefb8d2d9be340accf9bcfa96314a243febc35157ba48fd5", + "packages/flow/openadapt_flow/attest_bridge.py": "449502e879bc629b851087b86353a0dbd5761508daec92989083f30fdad14fee", + "packages/flow/openadapt_flow/authoring.py": "ad9f67263b07a748e2752c87025556f797fea0b0d3eaf53902168553cbae84b7", + "packages/flow/openadapt_flow/authorization_summary.py": "1057cfc2a597b02bd230654c1df030b3680ce8b6300619b145fcf0e994be866f", + "packages/flow/openadapt_flow/backend.py": "c7d80e64fa012afa5a6a78b2e0e52390f164a335e1f7dfc8c30ff05f32819858", + "packages/flow/openadapt_flow/backends/__init__.py": "c76aa599a2ea0205a3fffb4a33124dbe6637e4b93f0f14d4c604ffa05c2a1869", + "packages/flow/openadapt_flow/backends/_browser_process.py": "1f90beb147f32ddc276884c3e6488e3e533a5bb374ccf675fadc16754cb4e137", + "packages/flow/openadapt_flow/backends/_page_quiescence.py": "2a8b70efd7f6b006fa8141b02a7e59fe5733e7552eeacfc7e34326c0dde147c0", + "packages/flow/openadapt_flow/backends/_server_responses.py": "d20bfd20078304288462c56c17e1348707df9b3dc56388e292822405776900bb", + "packages/flow/openadapt_flow/backends/citrix_workspace.py": "2bc912cfecac2d7d1c4c543cda17a2cbe4b25af195fd8a1d015f6b02661e9665", + "packages/flow/openadapt_flow/backends/factory.py": "0702f58a5c3ac8b00c110f2064d59a2c31469839023a6a12dc70510f18bb26f6", + "packages/flow/openadapt_flow/backends/linux_backend.py": "c7c333cc1a592a1db64ed0ef5f1e410080469ae22080142329a90b780899d525", + "packages/flow/openadapt_flow/backends/macos_backend.py": "f6f2e25c03403340a756ecf9e18c4a2b30fb028881b122b54370450d36c8ea8a", + "packages/flow/openadapt_flow/backends/parallels_vm.py": "e724f9ec49210e5220d1cc508ab8a1ef984ac43854535b90548b209985109d0c", + "packages/flow/openadapt_flow/backends/playwright_backend.py": "f2221cec049a0e378c76fdfaa5b4b70c8465257ca6c6e1487032f283d6335552", + "packages/flow/openadapt_flow/backends/rdp_backend.py": "f6ea77fdbef04bf6064ba68466dba85c627fd633bf188c14d9988b5f0e7a4242", + "packages/flow/openadapt_flow/backends/remote_display.py": "c834ea454565c99978f381317e3874c081c5dce7dec19b35d796f6b51df705aa", + "packages/flow/openadapt_flow/backends/win32_window_client.py": "ac4a3a324b2ec2fa9f2fa64a7a1687b78ccab8e522d5c81e2c3b8accb79988ef", + "packages/flow/openadapt_flow/backends/win_agent/__init__.py": "cdba9a2b13137a43d83ff03e4eb02c9e250fbaee84ddd6f174c72d5902da553a", + "packages/flow/openadapt_flow/backends/win_agent/server.py": "68a3fd16e64fba41f5d290cda7713c9e2deb4ba1790738950d3ddfd561bfaa52", + "packages/flow/openadapt_flow/backends/win_agent/tls.py": "51f2af9d258c3acfd095d5a7c83d40d3719bbd2161be7963c03c84687f8922cc", + "packages/flow/openadapt_flow/backends/windows_backend.py": "24f6d760963f91f5e6d7d9a2c5568812952b4e9eaa968553e01ea3537ff967d8", + "packages/flow/openadapt_flow/backends/xfreerdp.py": "fce4e624ba91a39a08b6dec0cc367251b8e04bff3b356879e644ffadda595b56", + "packages/flow/openadapt_flow/bench.py": "e90fc71ac1c2033a42b06b872de09550fdd9db02b175d176d250e91c207cedba", + "packages/flow/openadapt_flow/benchmark/__init__.py": "c5d84d097f5e007ebd1a4f86a3ae02c6a897939cdfa11f030b263e5e12c0278d", + "packages/flow/openadapt_flow/benchmark/agent_baseline.py": "af2857e65b9393812180eed8ec6c721e6848a3209d0b57e637038262fddf1bc7", + "packages/flow/openadapt_flow/benchmark/chart_fonts.py": "e60660a947e09a566567b6d84332d009e4b969f4a450b2cecc81299c5b07ba2e", + "packages/flow/openadapt_flow/benchmark/desktop_benchmark.py": "8ed16bb044b338db4814374ddadda57b151d1e7e2a4cb2474d45ce6703116e54", + "packages/flow/openadapt_flow/benchmark/dom_arm.py": "62dd7e06840a286d859b442b79ea9b7bd05df56cdc700f140cc2e5cf37d195cc", + "packages/flow/openadapt_flow/benchmark/effectbench/__init__.py": "045dfe7dbace5245e7f6ff742984b73cca9e36d973ac0ce4736167a04ae764e6", + "packages/flow/openadapt_flow/benchmark/effectbench/metrics.py": "65f74dbfeac3d8761265531b04618270cddca773fb30cbabd49ab79d2f01c95f", + "packages/flow/openadapt_flow/benchmark/effectbench/oracle.py": "ec8628819a2c2f29efe36124175b2d349499936efcfe2cedaec5ebb6da9b9fbd", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/__init__.py": "a1153140a9bd236aa7afbb551edd305e0d16e14e999ba575dc609d24760c2030", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/__main__.py": "eb36799a1ff3168eb049d25a500cca903d3a5e439283d84ade20f21300f3d312", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/arms.py": "8cc93711483ffa21e06796b2b9f100effc6811c33c3204e5e9ffd35137b06cd5", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/baselines.py": "9d235db2891ce32c088d89edba3553fbbc2911031d29a9ada5caa00c964dd7bd", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/compound.py": "c407b37ba39bf066214aa62014c920815be16dffef934f7f3478ee20ef3dc341", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/harness.py": "e7848000829d040a35f25ccbbb82041fd460664492dfecee1173092ab492671c", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/reference_tasks.py": "c588c8f654e44d035a091b4ca0c861c8e916592d63f387b75ba9702af8da3d7b", + "packages/flow/openadapt_flow/benchmark/effectbench/runner/substrate.py": "0cb036fc7c0b97ff776e946de714a1904d0cc36846fcd1c86b4e7820f5c90542", + "packages/flow/openadapt_flow/benchmark/effectbench/schema.py": "640848a9ab5932006d2a65f9975b3369146aa1b892b2bd44b879dbe7fe11ef47", + "packages/flow/openadapt_flow/benchmark/frappe_lending.py": "4b9c17ab23191dba8ca81d03b924c14e3fdd12280fe9aabe7d4a7e7691229ef0", + "packages/flow/openadapt_flow/benchmark/hybrid_benchmark.py": "f37c53370526fe0b3c47a728c3003d6d59aa23b71471f3fc51ae545adaf931ea", + "packages/flow/openadapt_flow/benchmark/openemr_benchmark.py": "acb8bc88dc7a175f1f5eb90a6f83371a26e4082b8e1903a69f7d35f8d6737244", + "packages/flow/openadapt_flow/benchmark/openemr_local.py": "2f1e9156719eccb745a96d9ef16dc185d73346710d649046512c024b89a1d65a", + "packages/flow/openadapt_flow/benchmark/reliability.py": "20851d3dfebbb647f8db689e87c9ca35b0364c3127262305f6b361bc42d43d1c", + "packages/flow/openadapt_flow/benchmark/run_benchmark.py": "6f001c4c53d7aa6abb744053c347f489c1ce343b117e76f347371179bfc3ace2", + "packages/flow/openadapt_flow/benchmark/silent_wrong_action.py": "072d3c16360ff5a1f66786196560d8c62948c38d1600ec99e78ba547b2fa90c5", + "packages/flow/openadapt_flow/benchmark/verify.py": "d823f244fdf423381cd30b7f6a2bb6322bf6ed4ee79634c670ae583f5bc48bbc", + "packages/flow/openadapt_flow/bundle_sealing.py": "b0487d600888d7b467e00898c99b9991d242e5dbf84f5528b14c91509b9ded80", + "packages/flow/openadapt_flow/bundle_validation.py": "63532fb8e49f25a8aba8dccca505362a9293b8db9cbc0f2904dd8f9a6e23fea8", + "packages/flow/openadapt_flow/check_level.py": "cb5a4bb2bc25f70c7de05ee1814b8b8a422ebf307809d96ed74c8106f8e3a9d5", + "packages/flow/openadapt_flow/cli_admit.py": "e337d734c1e69b28dd80609f55293040e07798e92028b5ab7afe24d6c613b29b", + "packages/flow/openadapt_flow/cli_api.py": "f1fc34f3fc53056b561a3e5573591f031763d5ae85a730839e4f6ad6a937599a", + "packages/flow/openadapt_flow/cli_check.py": "1206d3821e98e418379c03287e5f9c8cac6d9487dd6b21337f8782e8433736eb", + "packages/flow/openadapt_flow/cli_cleanup.py": "74e6dbd940e5b6cc7dacf5e68a5089aa18ebbb108e3f59f7fa56a69e08ab820f", + "packages/flow/openadapt_flow/cli_compose.py": "965a63427ec37964451cdb00fad92065833b6f7cab936c80526c05984b5a68ec", + "packages/flow/openadapt_flow/cli_config.py": "a68b1b9db2c60feee3ec0b4ea079bbacc7b90bb052f9660c87b2e7067c593557", + "packages/flow/openadapt_flow/cli_deferred.py": "058cc58d256276f36db2b1e4f6e0105b505c88c1f65ca9f8e01e5f37f79ddeab", + "packages/flow/openadapt_flow/cli_fleet.py": "a4678b9b48c6c4817673fcccbd18be21966ae6d64dd89fb143bf26f3dce3c7ff", + "packages/flow/openadapt_flow/cli_hints.py": "6589faad4aefc66d6a80fe7875909cb9d335fa5624d9c761a95d5afe772a260f", + "packages/flow/openadapt_flow/cli_known_ids.py": "ed965f2369489a87cefb27ea9c9241bdc9b6058c72fc56450006be9951737897", + "packages/flow/openadapt_flow/cli_local_seal.py": "33c1f1406afa3de7c85bd661befad02b51fc5018f5eefa94546bd6381bf5c37f", + "packages/flow/openadapt_flow/cli_no_outbound.py": "1afedf45d1874e4aeeabc8cf1d36572f6df8b7ddf54bfcb75189d0bb0f149b47", + "packages/flow/openadapt_flow/cli_process.py": "700238b5cfb5edef883d2e57a209d9628fdb12fdc7aee6ea7c4a519999d28d64", + "packages/flow/openadapt_flow/cli_redact_report.py": "3e9d35b83f8d74e7b085e670787de410aebcb70a3a0de4ff1760bc44c91e48cf", + "packages/flow/openadapt_flow/cli_rollback.py": "f9f80f3d84b1e50241527a5fd09736941f8eac0782fb05acbab2e4765b9747e9", + "packages/flow/openadapt_flow/cli_sections.py": "21d0a362860703f030fea5a2ddcbd0a14a48a4799fcbf117d526a38fcccb04ea", + "packages/flow/openadapt_flow/cli_self_repair.py": "759088c91cc14ae9b8422d6a0859088e40f18e21da120ce32da61f42d3176ea7", + "packages/flow/openadapt_flow/cli_service.py": "172c06cc25ada99b0532dee35aa4e4510671cc7e7b24871e8b7294abed8cea0e", + "packages/flow/openadapt_flow/cli_shadow.py": "be027e80721d1b59bcc0bb2ba03048fbe2b864a5df43f521de61899fb45f6e1a", + "packages/flow/openadapt_flow/cli_undo.py": "93b527aaffe833b02ea1e990bf03c908fa88c3f60d764007130a00449b75185d", + "packages/flow/openadapt_flow/cli_variants.py": "4d214beab05bdf5765559004c8e83bada54567624ac1ae704d0a979e127f15c6", + "packages/flow/openadapt_flow/cli_worklist_pool.py": "74b4164f79865db4ae7a41b063ba2f671077c02645021fc2e42424f628431777", + "packages/flow/openadapt_flow/cloud_seal.py": "dd473bf2ce29f708e1c1638d70f8b54a3feaf140f27aada14481f31c1d2517ad", + "packages/flow/openadapt_flow/compatibility.py": "c869b05daece5b5bca489a27f71d2de07354624913d7480135ca0d6354e3516c", + "packages/flow/openadapt_flow/compile_agent/__init__.py": "1f70909ee8780e62513f41d717b8c07cee89dbdc430bf84ea08904998502af3d", + "packages/flow/openadapt_flow/compile_agent/agents.py": "6dcb2a0f9a49999e0fd289c488b642dac5280602609bbdf7b6c97630c2262c6c", + "packages/flow/openadapt_flow/compile_agent/cli.py": "fb3480bc92c925c0f647f8b02b3f1de0562cac48d1006859160042b60bed0c58", + "packages/flow/openadapt_flow/compile_agent/code_action.py": "f0f2cc963dbd1199eec285571c3441f506fcb3a9dec719db9e1bc6ee938c8edc", + "packages/flow/openadapt_flow/compile_agent/driven.py": "16dad5aa77be22181920343ae7dba069576c3a293bfa1dd0d292fb71f7d71ae8", + "packages/flow/openadapt_flow/compile_agent/emit.py": "405411124cfc94b52c45c90c195537ea53b0ef3b242cebf212102c49c0111403", + "packages/flow/openadapt_flow/compile_agent/harness.py": "d528543c8411b2d7403929d3a71e056bd5f93e33bdf16fb741bf14bb7c5e8106", + "packages/flow/openadapt_flow/compile_agent/observe.py": "b2a82ff94383acdf113ae4f4c9e75114df3e1c0e7ef1782495eaeed249e41206", + "packages/flow/openadapt_flow/compile_agent/pipeline.py": "3f52eb024386ef046c61cb29642fd463575ad6a7bd1fc28474591170338979db", + "packages/flow/openadapt_flow/compile_agent/sandbox.py": "0df910a8f3c7f9a4d1a76c2ad97d62cab868694ee67f5ec70ddb2ca0f9d26e97", + "packages/flow/openadapt_flow/compiler/__init__.py": "e38510f5baea14d9d357ab594954ff42f5aff608589ea8ae5d7060e8cf0fa380", + "packages/flow/openadapt_flow/compiler/annotate.py": "0cd0afb521e50c82906c5d47e12bca09a12d0caa9127bae433f830339b41622a", + "packages/flow/openadapt_flow/compiler/api_proposal.py": "9d4aaf53c753a62fa33adf7a2268dbbb2e40738301a6d14871eb67750a198978", + "packages/flow/openadapt_flow/compiler/binding_admission.py": "5113f27876d3109ebad880c75255e138c67375a7846d4966ac0ab9d9e2f69ee9", + "packages/flow/openadapt_flow/compiler/codegen.py": "327bcc7ca46c1249ba11caad31305171dc36fd11f02073ef6484aa4939f4772c", + "packages/flow/openadapt_flow/compiler/compile.py": "daedc14eb1caa6be95c2d5037a92141f4fa0b871b1a6280783b220935d048a93", + "packages/flow/openadapt_flow/compiler/compose_authoring.py": "84132f230f6cd811d86a1588d08809375282b61227e40f64f5f9fcf2807cfaa1", + "packages/flow/openadapt_flow/compiler/disambiguation.py": "2761afc6683f1b72b1126a2482bf1e02f34651489dd28f71a2f57aaddced7fb5", + "packages/flow/openadapt_flow/compiler/distill.py": "3169f571e0f8c63c14f324c626400ae78ad55aab83c03e54b8396e304757fc75", + "packages/flow/openadapt_flow/compiler/effect_mining.py": "5f37b4e4a6191141a9cf80c3c2738de0d4db7849f2981dc44ac325eaa8f0c02a", + "packages/flow/openadapt_flow/compiler/fixup_passes.py": "153276b86f37fb1108d332497328f27af11d1ea0418c7ee7baa1050606d25b30", + "packages/flow/openadapt_flow/compiler/induction.py": "dbe2a0dd41d733f686cd0f86f82583fe2ca32557518d99d2b5d01d414c8c28d9", + "packages/flow/openadapt_flow/compiler/local_edit.py": "867f0ca4d79dccc88367da10e75b180727438a1aeae30a49d407ef5cdff04981", + "packages/flow/openadapt_flow/compiler/lookup_detect.py": "9515fde8bf759f19e5698e3c00f0fe918a790abb0f30f9db911e03d12e84b402", + "packages/flow/openadapt_flow/compiler/loop_authoring.py": "f9ac89cc50e238a35411741aebb3eee7d76c2b263008957d21cdcb49e89b0ee9", + "packages/flow/openadapt_flow/compiler/loop_shapes.py": "b46f39bd5c4c3216c87ed52ebdb919ad1fa87ca0df29ccac376cfb107ec36a8e", + "packages/flow/openadapt_flow/compiler/motif_induction.py": "920c2fb7a409ce90437376ccea3261cf71fd1b029baea8f5a6f2d8de7aefe360", + "packages/flow/openadapt_flow/compiler/narration_rules.py": "2be904090c00ce014a4f1854393770d3b4172943e4d2ec1b35e1e58e5a26d199", + "packages/flow/openadapt_flow/compiler/param_confirm.py": "4cc6c8402a4f30626098e09156bc283f7755d68b6b108c28f2db4c069e5a7cd9", + "packages/flow/openadapt_flow/compiler/probing.py": "e6ee7dedc5f7a8bfa768bf0a0cb6b1271ed6da2b1c1a956968ad34dfbb3f8b48", + "packages/flow/openadapt_flow/compiler/qualification_pins.py": "8392cb44ef2cb33956ae58e235a8611295f07553a0c1ea2aa5b2ef010a997787", + "packages/flow/openadapt_flow/compiler/qualification_proposer.py": "ab4bfa3e51aca8dd5fb2360e5be9c3b454197e09d6365ef20ba0632210997d30", + "packages/flow/openadapt_flow/compiler/readback_authoring.py": "f9b170cf4a7b8a66bd0a19803a2d2f540b30e9729fff66e4d48be091ac034a20", + "packages/flow/openadapt_flow/composition.py": "fefffc27473a38c1284b58e298ee15e099d848741aeb9ad50876516034fdcec7", + "packages/flow/openadapt_flow/connector/__init__.py": "ea1c554a0de295db995f57f837669419855c9fa1d2fcf958ae4e2feeec37106e", + "packages/flow/openadapt_flow/connector/client.py": "1f55a56123fda690c4bb4dfbd6407f4a96c85c2575eb8928cb7aed30b3a20145", + "packages/flow/openadapt_flow/connector/config.py": "8dd9f662151ce609e21ead89d4019c77a4ab185a0f1b5690df20dac166701c4d", + "packages/flow/openadapt_flow/connector/daemon.py": "3ea5444af51cb28dc84addc4a5db15a6a75486f4ecee93d3b6c09ed7e7ce1366", + "packages/flow/openadapt_flow/connector/executor.py": "463aeafed9092b16a654191611445bb5688334ca87ac0703699d6dfa7f449748", + "packages/flow/openadapt_flow/connector/protocol.py": "963162e5dc20fe0e220fda67e59d138b791fcc6bbaf626776ab192d6cfe3a203", + "packages/flow/openadapt_flow/connector/storage.py": "4ba816b98860b5f6973909b6b6d30138b429a8bcd120423408bf0ade7cc1a916", + "packages/flow/openadapt_flow/console/__init__.py": "3a5ad4c010c98c3afee9c00a4fb7b4209e914b27e48d0cef09d8d4d3eccbbb57", + "packages/flow/openadapt_flow/console/actions.py": "097dd73b8cc61d19cb9cb5752103663a413cae0b822cb708419966413d188850", + "packages/flow/openadapt_flow/console/app.py": "c85d6e1038cbe1ec0737cc51deca93b490f83496a55b0b3b60681575dd07e386", + "packages/flow/openadapt_flow/console/attention.py": "cd4dff1f27d8c0ef022dc92d64eabf2976fcff3ea0a06a5c363af169899f9cef", + "packages/flow/openadapt_flow/console/data.py": "80359fd5e16d13b73a821034b4ac12de7a52cf4dd81c4211c18b2b23154a86e1", + "packages/flow/openadapt_flow/console/decision_context.py": "e5d7297c636d5e5c429558d6ed54ce294b4a3e97629150346369409bfa309d8c", + "packages/flow/openadapt_flow/console/decision_relay.py": "d5320404d4342dbb52be1f2ead1cdb5cf0e22877feaaa1e47974b301df697292", + "packages/flow/openadapt_flow/console/decision_supervisor.py": "a5496779de6c05a21c03fb91cf3a3565951bf97922952cf6606a2c185dea6d69", + "packages/flow/openadapt_flow/console/halt_detail.py": "08b88a082568b45038b47e09549aaeb5e9f2f74dd1942d6db372871f82b17226", + "packages/flow/openadapt_flow/console/human_decisions.py": "02651ad8888a5c17bfff6fb47165ba861193243dc0f7c951f8b94f544ca19477", + "packages/flow/openadapt_flow/console/json_artifacts.py": "e908d47f34bc040e83b257b953c55a7f3cec9b2b74a4519369d85c3a12e6cb7a", + "packages/flow/openadapt_flow/console/save_approvals.py": "1c180720a593e160720421a72cb5676a13f399a5e2bd3fd07ccc063b5bd8c00b", + "packages/flow/openadapt_flow/console/server.py": "d250a4e486053343eb92e38887fdb59c55c5c3cde64b69756e432b3b074f7367", + "packages/flow/openadapt_flow/console/standard_changes.py": "29f34f8fb06b4b412e825892fa340bea11f07e21c6560ae4936c0bab19972505", + "packages/flow/openadapt_flow/contracts/__init__.py": "cc4213ad8ee43060894281df685a4d2554b9e0869bcd267f0509b965d81d19cc", + "packages/flow/openadapt_flow/countersign/__init__.py": "727c18b4914e681256d727bf6a13285bd9b43c84430c7cbb610efd6721d7edba", + "packages/flow/openadapt_flow/countersign/cli.py": "bbb2f1b833297dab3d90fab4ee8bc60b71598b93d0da1dcd30baa6de6dfe8886", + "packages/flow/openadapt_flow/countersign/commitments.py": "2ec3967924a7142facbaee575d6c76c0241f9187e4ce2c4b497f479b5e432339", + "packages/flow/openadapt_flow/countersign/document.py": "3e4760c7ad0f6949b8cde441217d647c5caf7ea13f63c25bfeffee92c0c6e375", + "packages/flow/openadapt_flow/countersign/dossier.py": "1ea1151f03b55a6b8a73333b46079cf2cc31d338e6b413490636d2b3ef16033b", + "packages/flow/openadapt_flow/countersign/exceptions.py": "effb4425752f60d44e133ee7b2d704f25473bcb46e168fd17780f50320cf1ee7", + "packages/flow/openadapt_flow/countersign/render.py": "754b6858cb5a3de7db6489103c5225f8f5a62f7ec91af5027b68c0d0c9e7e5cc", + "packages/flow/openadapt_flow/countersign/tsa.py": "c3597122c33e80b3eb2cb129551e1cd50b6021f45cbad01c20869f13699f8200", + "packages/flow/openadapt_flow/countersign/vault.py": "71ea45df4993a00a384791c227328eab67a2e5d3d1d75896e7c841765d5ed1bc", + "packages/flow/openadapt_flow/crypto.py": "88f8f0b462775b4f06d89ab63e84954de763292a8a8bcd356f40f15f1a56b9dd", + "packages/flow/openadapt_flow/decision/__init__.py": "32b2b08051ab45571c55af1d44f2b68fd4106eec9ceb078d6769c2d306403550", + "packages/flow/openadapt_flow/decision/calibration.py": "51b2adc7ee624d78120a12ebf02d6eb6b9408ce90642e064647fe678e5578051", + "packages/flow/openadapt_flow/decision/cli.py": "b0f453ad2616e4b0f99249e983967d1fa7b929e4692662b8adab47053cdf027d", + "packages/flow/openadapt_flow/decision/eligibility.py": "86e77ae372f8eef0c31eb9ff694e17cadc7f278155c76d46f0a40be0dc766cb8", + "packages/flow/openadapt_flow/decision/feature_model.py": "af7207d471e531377ea653448fd2487f1988a4e8c0b3c7c36d7983a369621077", + "packages/flow/openadapt_flow/decision/features.py": "cc5e21ecf0ca93982645791724fd41afcb9e77ed4c526cfa2ea7d3c6cee3931d", + "packages/flow/openadapt_flow/decision/labels.py": "924fa2fa32cdde259a2900d0c8af3c3f54b07c30bcbbb9f4af7289663a2889c2", + "packages/flow/openadapt_flow/decision/minimize.py": "9c0695866985ecda25a0c96d251d3358d15ab7ee57e66a3b4528e66f4df65ace", + "packages/flow/openadapt_flow/decision/provider.py": "3e14c3b013533925a62e67bdd9b82ddf99f969a2552914db1630498ee7f4bcbf", + "packages/flow/openadapt_flow/decision/review.py": "47822513196daa1cb3e827e3cb846763f41165738c3576c2df946f6135676098", + "packages/flow/openadapt_flow/decision/rules.py": "59a33e0bec5f19fc35621652c96c79ee6d6cc719854ff402853c9cf9c143b31c", + "packages/flow/openadapt_flow/decision/synthetic.py": "2ee2fc309a5342a5a3e050a47054b6b1730e21d8d20592956f7bc83f8e7389c6", + "packages/flow/openadapt_flow/decision/triage.py": "201235c57ce21e631c4e2f5990628f40ce3bae95e239f0bba29ce7738d95f96b", + "packages/flow/openadapt_flow/decision_delivery.py": "e3fd372d84ecb8a13fd811e115255362fae2f96e5b073e59d1d7f123c2bd85a8", + "packages/flow/openadapt_flow/demo_driver.py": "96d2dae078eee394b9c92a050cda498f98a61ad7926040a19996f31612007639", + "packages/flow/openadapt_flow/demo_proof.py": "dd9d11d97e4ed264593ecd33052910bea4784934ac575a5365d5cd4c8099f110", + "packages/flow/openadapt_flow/deployment.py": "24a8bb0e5f3ac6036a69d5bd32a66bb74fde73fee45e3bbb335c62882c9b0ae2", + "packages/flow/openadapt_flow/desktop_record.py": "a162df0e593b9f437bca86aa723be013836dd5e0c93acd5fcff7876ba16e3110", + "packages/flow/openadapt_flow/ed25519ph.py": "c8bb9e5d64adc44715e27c06f437e7801f6cac56aca1f15d8a577e629c649282", + "packages/flow/openadapt_flow/eligibility/__init__.py": "c4b10f081ff9c6d3ec3a3017d8a12aa7eb67b41488ae931000a8f84f08d9c54c", + "packages/flow/openadapt_flow/eligibility/artifact.py": "686eae18906a58b42fb6219fe6c4fa7b98638192db8169f0098601403ab274b9", + "packages/flow/openadapt_flow/eligibility/client.py": "9ddd6ca4512b64b8ad7ce7fad6c31d037209f3a19e9c52c605e9eb508c0f63e6", + "packages/flow/openadapt_flow/eligibility/waterfall.py": "15e15b5c8efed40cdeba907b74a279c217434ccf62a378f2ed4a05feba0d44ec", + "packages/flow/openadapt_flow/emit/__init__.py": "f9d028ad70f3eda13321fdab9d23845016781cb7360160d71eab82a1752419cb", + "packages/flow/openadapt_flow/emit/l1_artifact.py": "8e590b6013d1aedb6b722745d616bc394451c703642a1b7b0159359d94a4a868", + "packages/flow/openadapt_flow/emit/mcp_tool.py": "7852e44e2451103612a282b8fe3224a631c4b7261203418636949f82e80b4ba7", + "packages/flow/openadapt_flow/emit/skill.py": "43bd4116c93a662ef628a07a81c0ee2777351b6eb790ddc58b91151df672267e", + "packages/flow/openadapt_flow/ensure_record.py": "78e64c9298bc29c3f01a74be6b47a0ffa7be5df615eab383d36b8b8810dae21d", + "packages/flow/openadapt_flow/execute/__init__.py": "769684783844a34810850275aba3647c303acc2cd6eced1d8c350c4a2e774d54", + "packages/flow/openadapt_flow/execute/app.py": "fde39916331f18c8ec84948cebce74b2e1c31d3c06cdaadcdb8225eda4df92d5", + "packages/flow/openadapt_flow/execute/dispatch.py": "9cea78d3955ca6ef30e7ebe74b8ad752e6bafcba0289f2db4a746342ba1bf2f9", + "packages/flow/openadapt_flow/execute/keys.py": "f43c622ef1df57efb3ed52db6aafc9d98d78100f2e25fff50dcc8b75489f9750", + "packages/flow/openadapt_flow/execute/models.py": "bfbac12c75f4fc52c0f9bf89652e876828f208c13a7827dab8bc741b112a5e6a", + "packages/flow/openadapt_flow/execute/registry.py": "d687121bec318dd4b56faa1db195a181501fde92263f7b851fb33607de903642", + "packages/flow/openadapt_flow/execute/service.py": "1453344697293a8b76ef7a5788930408b19e60981e8770e8680c852dd307af88", + "packages/flow/openadapt_flow/execution_profiles.py": "2de96a2fa6fcdaa17303d336a74f0c54bb702289575cd4f0e3e83229b48503d3", + "packages/flow/openadapt_flow/failure_signals.py": "63090631a784bf6626859f3a2f7c95551b019d1baac79573255fb41c98f7d8c9", + "packages/flow/openadapt_flow/fault_gate.py": "e667d86a5eecc8e28da6e61c28c9c1034b00e2c9143533eb0a031757bdcade8f", + "packages/flow/openadapt_flow/fleet/__init__.py": "3196cac4fbcf1ac5974955e8d9f4dbd0295b516ad5b758e1f67e4f6f545a95b7", + "packages/flow/openadapt_flow/fleet/_lock.py": "b877076335ab50d6b240e3b6b0f131d0c699ba706e4af738858320c3fbfe45a1", + "packages/flow/openadapt_flow/fleet/canary.py": "275a8acf693d566f17858a2656df9d1b6315becc6c9153149046902bede9c1db", + "packages/flow/openadapt_flow/fleet/export.py": "f3c4385716ceb71c0f2e93e4552a32aa22fc31561d0497f9b4c5f13f09c64937", + "packages/flow/openadapt_flow/fleet/feed.py": "f229589e292195b387f9c756fbd549eacede2cbaaac2a43d782ae89fc69a7a34", + "packages/flow/openadapt_flow/fleet/fingerprint.py": "f641b80642a5bdabf97e44b7f9796ab413c8c5e1ec64355c333ecf91ccda6e00", + "packages/flow/openadapt_flow/fleet/hub.py": "5605dd4b5cc620f400bf101c8a94b3fd386dcdbea91329ce9fd2dac087e12f8a", + "packages/flow/openadapt_flow/fleet/intake.py": "c6390b6814eff7f0dc424711a6502145baf13bc7ba32031de827f1458184b267", + "packages/flow/openadapt_flow/fleet/optin.py": "c6a02feef8c31dfda435fa7b5c9ae2313775979cf68ba0643fe1f284142d3eb5", + "packages/flow/openadapt_flow/fleet/patch.py": "50fbd5f28c25c5af1a74ab79534bfc45d80335bf50971d88937d6f48a8047caa", + "packages/flow/openadapt_flow/fleet/policy.py": "d9a6dfc09ace5a6446f421728f636d77ac40c4e8e3068685d54cf12a52091729", + "packages/flow/openadapt_flow/fleet/signing.py": "9761c1e080a04ddcbb0f54f75ca37fd74a1801399be32218e527ef1c5b71bc89", + "packages/flow/openadapt_flow/fleet/simulator.py": "bb9deae25e4e6b6df6503df08b175d146b73cdf347f2997cff32ef34bd94af50", + "packages/flow/openadapt_flow/fleet/status.py": "2be462c6f00fda3b783e7825c12a5a031346b11e34c10c6465de75fa79c650a5", + "packages/flow/openadapt_flow/generate/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "packages/flow/openadapt_flow/generate/demo_analysis.py": "414ceb12ebb29cf11a66e1c625c0ad386714d7cd16647bf845a17dfd23b26aa7", + "packages/flow/openadapt_flow/generate/ensure_contract.py": "fad470bcceb721f83b7d5285058c31c6c51a49abfee88b5c3d16de44e9ef394f", + "packages/flow/openadapt_flow/generate/fhir_tables.py": "efe58cb353bb8e660c69773f4b057410165b74adf782d1a49e39e676a7a904ca", + "packages/flow/openadapt_flow/generate/sql_store.py": "25ab4c17799a59d4affc667fd6efb84f79cfd2577e647179a936336dd40aa2fb", + "packages/flow/openadapt_flow/generate/verify_contract.py": "e3050d86dfc0465eb2bc8645637ae10e76f5c35e2df7a185b3d23aff96ea9ff0", + "packages/flow/openadapt_flow/guard.py": "9a002384f9564d7bf379fbe855cabbb4484ff2c18a7ba5412502c818f11213cc", + "packages/flow/openadapt_flow/guard_demo.py": "b60288fb0ab86afdca9d12c34bd61dcf41f8905629e6c812d151426060e71931", + "packages/flow/openadapt_flow/halt_cleanup.py": "c1e0e58b170519c683faf97593fb36e91523392f17a1726223d90b47738c3cae", + "packages/flow/openadapt_flow/hosted.py": "232f7cb2d68e0e1f57ec7b4caa10b866da16a5555c6867d41d978a89bc946683", + "packages/flow/openadapt_flow/identity_signals.py": "46e1392fe0fdc7473bc28bb25562f31a6dcc6c90f95d7900de255bc750bf7287", + "packages/flow/openadapt_flow/image_hash.py": "4dea419ca5617104d6072ebddf8141e8f9809227aaf833969e390eb3d5c7d73e", + "packages/flow/openadapt_flow/instrument/__init__.py": "8bcf6fb0892a9dcdd6cdd3a787a084548479d2ac2ac4e439a772e45dbd924250", + "packages/flow/openadapt_flow/instrument/competitor_drift.py": "f124cc086c52eec9b930088b1eb9bf264075cc121f9d06a09ca98ea76527de56", + "packages/flow/openadapt_flow/intake/__init__.py": "76815b0b2ecd679c7b076f28f1bb48c66a6be007f208fe8a6ab2746477b3ddc0", + "packages/flow/openadapt_flow/intake/cli.py": "332e14ae4367185c4cb51f3f704c4de63cfb277e22689972bfae7ad841971465", + "packages/flow/openadapt_flow/intake/document.py": "b71b232d18d509e336d9d950063b14c6dd703383319a25b845e08fa5731b6f32", + "packages/flow/openadapt_flow/intake/extract.py": "61a1db7580d8fcd47943337ece067980527a541520929a62e0de321c23fd6f37", + "packages/flow/openadapt_flow/integrations/__init__.py": "f963112195eca4e46543778de92d5e5ad6e3afffccd999fb94377f4c75199606", + "packages/flow/openadapt_flow/integrations/agents/__init__.py": "280e431a4b07bb961e3ff7ddd8f2e8225faaad139fe00d091eda18ddc77b2bdd", + "packages/flow/openadapt_flow/integrations/agents/claude_sdk.py": "c2b4fe00a75c8ea95652a29b438269608c2f5f3cfd7cdbe80538b3bc87570120", + "packages/flow/openadapt_flow/integrations/agents/crewai.py": "a93a5a2222c20e77e89161c3587cc9e90769739465e1045f2666e80da91b7718", + "packages/flow/openadapt_flow/integrations/agents/langgraph.py": "ffe07ab4063cf8d053d89d63586fe114babec53e75274f2b406179de845c90b4", + "packages/flow/openadapt_flow/integrations/agents/openai_agents.py": "0a5d6e04f3bbe6e568c9eda5b63f76f86343c686f6ab103e209b6d410605ffe8", + "packages/flow/openadapt_flow/integrations/agents/skills.py": "0dca940f3f8f046df12d580599ea8ef909c5c0b2116dbcf2208000ab7aaf0e84", + "packages/flow/openadapt_flow/integrations/harness2/__init__.py": "1675bc0ab764041b3d579205435dd4bac547709c17016cc7805ed158e9fe4ead", + "packages/flow/openadapt_flow/integrations/harness2/adapter.py": "a02f3f471346106d3a70a7132c1cf14760ffbab41adaf93a21cb0a41e56f5916", + "packages/flow/openadapt_flow/integrations/harness2/agent.py": "bf8960db7127ec06e0d24e108893012ffa60a7aeaa521e5c7f354d9a85bd90fb", + "packages/flow/openadapt_flow/integrations/harness2/apps.py": "127522d89a834eaaf533a9fc4b5b90ec69cfa7cd179685b50b50f0c6a8dd00c7", + "packages/flow/openadapt_flow/integrations/harness2/llm.py": "7e2300d3ab7b5a9d4291fc8d6996e7e0cab0332d6b2d17cc86bd1b302bca4623", + "packages/flow/openadapt_flow/integrations/harness2/pixels.py": "be07c7a4ad99c5ea6a28458a49338425ccffb71e2b4b88d25aebe47cb8f3c641", + "packages/flow/openadapt_flow/integrations/harness2/proposers.py": "e554fcad5900a9fcc9c0c134e832f5ac7f02042a356e29b5b4856593920f647e", + "packages/flow/openadapt_flow/integrations/harness2/rollout.py": "4519c4c180ffa259cddebe0dcab6d00d2706b91cb911dd5f3d974a7d45bf5fa4", + "packages/flow/openadapt_flow/integrations/harness2/scorer.py": "c4a07d140d31920a57ad518b40817d1e78c87f20bde6810cbe3036d309df9ead", + "packages/flow/openadapt_flow/integrations/harness2/selection.py": "e08682bf22e8fd3fa698c146ef36d42de1d6b7c436769b98e5e2e7e87f9ae0d7", + "packages/flow/openadapt_flow/integrations/harness2/tasks.py": "a95df9907d871fbaead65763e853f62676f9208098a9664505b9de19fea2b8f2", + "packages/flow/openadapt_flow/integrations/harness2/traces.py": "91a71bf087a51494032fe347e439ff5014853fe73307eadb6e81ecae62b1383d", + "packages/flow/openadapt_flow/interactive_recorder.py": "69cb3d8a3c448e5e0749a17fda751ed3ada89093da7289df43c40fba008f495d", + "packages/flow/openadapt_flow/interchange/__init__.py": "b40b98268f7317e7539f123a6fca9b507d6492280d6f679525673493dbe24718", + "packages/flow/openadapt_flow/interchange/__main__.py": "8eba812ea2fcab0984b05def3fcc596cc4e523756ea3d8daecd23eb4a5c2cd1b", + "packages/flow/openadapt_flow/interchange/conformance.py": "50d7192c78d9b1fffad09fa37dc9abe7b33e84d66a395af955fa852912a99adb", + "packages/flow/openadapt_flow/interchange/l1.py": "438d2b59a856017cdc2f0256e6efd7e605c68ce54ecf9825282a34f09b24c7e3", + "packages/flow/openadapt_flow/interchange/model.py": "eedfe5e24508b82b24fabc05edf003fe3fc3c89e414d0436d6ca4c0d682c7444", + "packages/flow/openadapt_flow/interop/__init__.py": "fe8f034e12327da65230f887cd17fef5a21b8ec350a77d22fa0693f728f11d03", + "packages/flow/openadapt_flow/interop/business_decision.py": "264c35835b5d7e5693d140a0b6fc6515c719b73d42aba57bbb685f1dd1140743", + "packages/flow/openadapt_flow/interop/business_decision_cloud.py": "85b3a01a0642f28bdf2ec704b8b39bb8f81b5e16ab716791b56ea25990a890f2", + "packages/flow/openadapt_flow/interop/business_decision_supervisor.py": "d39d6bf9cfd1d4c84804b5565a99f0ac9560ac15e2838deee4c81426503b521b", + "packages/flow/openadapt_flow/interop/decision_relay_transport.py": "31a2f2ecea1d39f108747dbb81b123d2d7d1b0421fe75b97c6abac1d94652a51", + "packages/flow/openadapt_flow/interop/types.py": "1f2a45fcf456d31cf15534ab6d1417fd3c52612c3ab948bdb14f5a0b6f814945", + "packages/flow/openadapt_flow/ir.py": "efb7fdce6fa625f631901389ca767b016f4db3111caa2905321ff3da675e376d", + "packages/flow/openadapt_flow/jit/__init__.py": "351ae6f83405a8dedb9585ea031ad068416a8595504edb21b39e623a67a1f489", + "packages/flow/openadapt_flow/jit/adapters.py": "c7af4267e8a1704e1390dee295a2ec3696ae9c932e506a2785023ac88a71c879", + "packages/flow/openadapt_flow/jit/authority.py": "421e369b815c16578f5aa54158065c8c0700105124a32b6e8a0aa0020ed1de60", + "packages/flow/openadapt_flow/jit/cli.py": "d530b05349a076d8aa2a044f2fd98e4b0f491f274c96c1cfbdf23d6610871c33", + "packages/flow/openadapt_flow/jit/compile_agent.py": "fdb65297497d3855a925c43e73dd85391fed9aa37c6db88d79af21d36726b2e9", + "packages/flow/openadapt_flow/jit/keys.py": "8d4b0674a351a69011e590ca9bbe5e09ccfd3c55868ff03db7584bbc6e2d530d", + "packages/flow/openadapt_flow/jit/lifecycle.py": "3fac468fd4ddfc77a9e5e83651430f8a3393d5d7ebcfa716dbcc90ad2c78b0de", + "packages/flow/openadapt_flow/jit/lift.py": "dccb48c58846119218ddf2da17263aa4f72637a53e50565bc5df4b2102dbb7aa", + "packages/flow/openadapt_flow/jit/recorded.py": "3dac2027d3bc1d0e7028dc39f85e7c072c115656ef556d3e21e003f9cdb2a8aa", + "packages/flow/openadapt_flow/jit/signature.py": "b7f68b753c10c89fe57ef6912baf2e857637a5cdde5c23dfeb105f27c43c67e1", + "packages/flow/openadapt_flow/judgment_cases.py": "021f312ad721f06c6ee0f99cd2061d32c82eea7bfe6f87c4b182e36a4f445db9", + "packages/flow/openadapt_flow/learning/__init__.py": "3a47506779959eafc8b85ca26a779fadded553711544021ca4915a87504857d4", + "packages/flow/openadapt_flow/learning/branch_teach.py": "521659c92f3498a43983e5f444ec3981c3cff7326036bef47cd190f32f3228f3", + "packages/flow/openadapt_flow/learning/clustering.py": "50173d350b1c936e2f667db1612dae7721df7a81b40c07eef11abf10c9c0f1c4", + "packages/flow/openadapt_flow/learning/gate.py": "f34fd9f3ec74d7a5cb7f111be658db9debd032ceaa12d9254e2adc25f0becbf5", + "packages/flow/openadapt_flow/learning/halt_loop.py": "7db231e6902ee3b7fcfde914b2d1ce9c93a61f371d87b31c0156991b664b4566", + "packages/flow/openadapt_flow/learning/interpreter.py": "606d336216a899b11651c2e4eb341748d8cc91a10508bd33a63c7d50a44318aa", + "packages/flow/openadapt_flow/learning/library.py": "bd3443f46255070368827dc766ff89b9d77cbb92c828b5a0dec06de4581fc7e7", + "packages/flow/openadapt_flow/learning/loop.py": "f3e5c1127b4fa21f3e112516670edce15daf05322c554642b8057bf268adb7cb", + "packages/flow/openadapt_flow/learning/synth_stream.py": "23ab8b60ed0ba8c3f241c480fddb3901a18aee4bbce79e8828d87c41c1178c80", + "packages/flow/openadapt_flow/learning/teach.py": "f03226cef5666ce7940c954cd381e34e1df38de3c97ede3b318b6955a6c1c851", + "packages/flow/openadapt_flow/learning/trace.py": "323bc17cfa3fc3876ac030abda028c73e8af02d5e30b2cbe9c9b9faa4639c524", + "packages/flow/openadapt_flow/lookup_capabilities.py": "6f8068a05f005e0eac994d14d0ca3e6b1ce7a4e532471bf4b8d9dc15c0fe1b4a", + "packages/flow/openadapt_flow/mockloan/__init__.py": "06beb30be301dde52a522064ed192864a4c2bc1f167ebb5800c173eaa72f4735", + "packages/flow/openadapt_flow/mockloan/fault_server.py": "0f6f6e4ca4622c5674dca6e49b9dc67360f34b9071ba1346727683aed16682ca", + "packages/flow/openadapt_flow/mockloan/server.py": "6d81bda0dd715a71e54306c92d739b49ca3b3a75fac65ee7a70a3d18027ecaa7", + "packages/flow/openadapt_flow/mockmed/__init__.py": "223bb05087540a7e0e90f72b83a5dd3634318b95dc0a6874cbc82fd38ce5ee9b", + "packages/flow/openadapt_flow/mockmed/chart_pages.py": "38db2fb4d34d14d8745c7c9b31a6c729a4e0afc545333c164296fde5044bf395", + "packages/flow/openadapt_flow/mockmed/fault_server.py": "acdd45ef82111d50e1ebbd242d55ad14094f0a0c457c43049855be5efa8ecb70", + "packages/flow/openadapt_flow/mockmed/server.py": "9fbeaf2b0c873dcdce478b4f54990b954844c057a0760ab763705e6aa3f202ab", + "packages/flow/openadapt_flow/mockmed/teach_scope.py": "50d70c32aed4a582343d7f05ed4ee927a47de51d0420e22b3d48554303c287eb", + "packages/flow/openadapt_flow/narration.py": "1293e9c0858ba925275bf67a42eb602e1e803e544d068a3ef66b2fb628ec2d2f", + "packages/flow/openadapt_flow/network_capture.py": "d291ed5754d64a8a0bf635018ba7ecd50d8bd74121c5098d4cdf7f528a423a33", + "packages/flow/openadapt_flow/network_scope.py": "c64d2288c98fde49c437a0a44b53af4a4b1884a8130b99bee098c2046e249033", + "packages/flow/openadapt_flow/no_outbound.py": "af14df863d40d81d046acbbcc102e2f96912b24114111e052ebf5440348673ec", + "packages/flow/openadapt_flow/no_outbound_allow.py": "b60aa14551bd39ed333389290ae4a9069863b370532e1016d74441a8dde5dd2a", + "packages/flow/openadapt_flow/offline_seal.py": "cc7b0a2dd8d946e6cae740af7d7fb250253a58276c11de4f8a9122b91819d54b", + "packages/flow/openadapt_flow/onprem_reboot.py": "5897eda697e7111ef3ad944c7e397b64ed39abdb225085f9eea0f47b6630879e", + "packages/flow/openadapt_flow/onprem_service.py": "2edbde7b9a179b1c2bf63d1b955113a9d9fa3f909e4d9db875e7b626b0e9a792", + "packages/flow/openadapt_flow/operator_profile.py": "88e013511d1a7fcb7b8e46841c6d7291433a631cb3baec349da42e7a300c5934", + "packages/flow/openadapt_flow/pf_firewall.py": "adff9c485d303b5b07efa910c45d39762e12285c843f8cd782ffecac5631e0de", + "packages/flow/openadapt_flow/plain_outcome.py": "1a74681faaf317377af006d37557e114f0ba7f5bb561a33331565da0addb4de2", + "packages/flow/openadapt_flow/policy.py": "730c8a74c8c1d2e65454407665b62517a48eecb7bcebc9bfb7b6ca3f8fb25079", + "packages/flow/openadapt_flow/policy_packs.py": "8df13eaf8e7e4ac7492c2f8c25063ce387833174f3c82dfec3f8c68de4bbaaf7", + "packages/flow/openadapt_flow/privacy.py": "73f9ac693a9d1b9a819fb0a2c3fae77bf9f702f782246d1c910f9d1420113288", + "packages/flow/openadapt_flow/private_file.py": "eae5ffdae6a76c57bc999278baae8ad0bbabfe87813818777e825beddc22f135", + "packages/flow/openadapt_flow/production_qualification.py": "c5c9911c7fcaa12bbd513fe737c4c17a3bd64522b98100880c1a05fddfff3aef", + "packages/flow/openadapt_flow/provenance.py": "0865fd3f0362eb6a7b4ced98070b5e399b91e1530288b9a002bc933c86d90879", + "packages/flow/openadapt_flow/qualification.py": "d46302cdb42e1e44f179eea69ac12ad5f8b07eee618369efb909e12843104f10", + "packages/flow/openadapt_flow/qualification_admission.py": "73a43db18fd383fde7c5e11823ace50046e0784e662b56c76b82289f61616cf0", + "packages/flow/openadapt_flow/qualification_admission_v2.py": "10d8241a38b24767953e6b98986c838773a02399652924a53e53c7ec7c7c5e56", + "packages/flow/openadapt_flow/qualification_admission_v4.py": "5a11934e42fdea85c7ff792ab6d33754b974a719783154e8e6144e9a90db1292", + "packages/flow/openadapt_flow/qualification_campaign_authority.py": "cea191889f3b3f8da5c078e0cb020e7e0ebe9c4100035f5f1bb2b17ace75c3d7", + "packages/flow/openadapt_flow/qualification_campaign_permit.py": "1468882d9627f0355673873cd493c2709ab7cec2506dac3b1efa32d1c16f0637", + "packages/flow/openadapt_flow/qualification_dev_signer.py": "db70cdacae8235acc73f13a3281e667a7a5e502c246151854db4dd921caf8643", + "packages/flow/openadapt_flow/qualification_drills.py": "6fecf639ee1db59b8f15f857f01765d2e5b4dd116da44f664aef0350fc715173", + "packages/flow/openadapt_flow/qualification_environment.py": "842f5dd827750f55d9e1046613a804fa6b27c2a41981d4d7fbd6b04d9acc8131", + "packages/flow/openadapt_flow/qualification_faults.py": "b4e437aae822c648fca54f8a57b722ca9c940d56f6a906a52292877ba16cc9ff", + "packages/flow/openadapt_flow/qualification_identity_evidence.py": "7b94bde4ec7e2d1eb9522bd456aec407ef7d1063f5cc7518a21b40be1464bdb4", + "packages/flow/openadapt_flow/qualification_oracle_gate.py": "39e12170e33cece3aa72f463afed7d6c342909995afbe3d861592dc47c912d77", + "packages/flow/openadapt_flow/qualification_proposal.py": "1c13d44a189dd798244dbd69186aa78856d304e0b21213d5262450bb9c3861a6", + "packages/flow/openadapt_flow/qualification_web_session.py": "3766e5b5f03a92f91a5f51988b1764542b74aeaf634d72e18c9a9b1b6ffc4fdd", + "packages/flow/openadapt_flow/qualified_business_decisions.py": "9b56b3d0923440b589e6ff010dda1cab88c893aa01e92861e796e6cae2c83c34", + "packages/flow/openadapt_flow/qualify/__init__.py": "a3d7b8e30c06fb77be32b75996ce1f3ea8f96264e74edef2646f122ebbbdc1f6", + "packages/flow/openadapt_flow/qualify/campaign.py": "c9e520fc316c59e9c6d77cebb83125f729b6c863e42552a008c82bd47bf25755", + "packages/flow/openadapt_flow/qualify/config.py": "508c4bc77d022425b57d4fb9b6659ca9c0829161ce6296a1d4102fc1f9dd507c", + "packages/flow/openadapt_flow/qualify/environment.py": "be85934985a000c6dfef8d1f35f3d790c1c4974a14bea893d58386e533ec45da", + "packages/flow/openadapt_flow/qualify/faults.py": "4a78131403af4aff905bdfaf1056c332f890acee1b991283f3c7e2424a9539a5", + "packages/flow/openadapt_flow/qualify/keys.py": "fb2e94913ad0db0fcc697943faaeef3f3391a83fe045af703d249b9344afc556", + "packages/flow/openadapt_flow/qualify/result.py": "ae2b8b1198e2ff0e36c54875a9b6d0e6005f50f36f2f16aeedd789cbf8af4c31", + "packages/flow/openadapt_flow/qualify/session.py": "029a8876e81649514d0dbc9862ba6c80eb6fb00eb283ff3ef7302929a69749c9", + "packages/flow/openadapt_flow/readback_ladder.py": "7706e14c0531e31677ccaffeefc5e78734ba2434b1ba43360d7144ce44d08ea2", + "packages/flow/openadapt_flow/receipt.py": "b90c51a048182475d49db793b00afe5396ac03933042d2ca74b5661375957755", + "packages/flow/openadapt_flow/record_check_binding.py": "eb63af93699861344ec27013c52a5ea0a1308081e73c99e6b01d032fe9c155f1", + "packages/flow/openadapt_flow/record_check_identity.py": "2538298c5343919a80fd5bfe3eb6db0143faaf833f05a1f132c38ab1145c4864", + "packages/flow/openadapt_flow/record_check_proof.py": "26104f4de1283d01dd6646768ed50ef624bb143293cb30c62e6427efec9a2111", + "packages/flow/openadapt_flow/record_check_synthesis.py": "c71614f451dc2e50332bbf45c4463ba4cb6570fc25fef7491c42e21f38648fdf", + "packages/flow/openadapt_flow/record_sor.py": "8ee6cc9530482a50712f61441622d1cd9ce787e3460309173744e178819bc008", + "packages/flow/openadapt_flow/recorder.py": "17a543d79da468d57e7313a729148f14f0023bf577d02aff201f68ef98ae3e48", + "packages/flow/openadapt_flow/records/__init__.py": "5d05312df045601b1b65183587135e6f7ba908cc3d880b18a4b74487a8d06a45", + "packages/flow/openadapt_flow/records/export.py": "644bbae324cf146e24f2c97e8ba1c959ebfacfd33b58056e6819909b9dfd4abf", + "packages/flow/openadapt_flow/records/read.py": "09b8d3948f4bd9b75b12ded13e98ae22d4846ff67a8db250e69941a62fe2dc3b", + "packages/flow/openadapt_flow/records/review.py": "de530c3d3617e40d0c0254c9f04d6b70ee453877a130cfb4dfc27348704e1945", + "packages/flow/openadapt_flow/records/run.py": "a9ec68f5ca19b2396aea6e9d9e0198cdb5e63b63fbc0fe2854773e9a7888c6b2", + "packages/flow/openadapt_flow/records/schema.py": "abcd330ec4ddcf2660a509c2b0485fafb30601dadff2a36f821c325397a94d3e", + "packages/flow/openadapt_flow/registry.py": "6203135d2768776f95a8a5d46765aa76cc2e16904b46b06ba83852d18416d215", + "packages/flow/openadapt_flow/registry_attest.py": "8a8375762ac6bc2a2f717359c1002d220295997f01c012b5c42b7dcd09709676", + "packages/flow/openadapt_flow/release_admission.py": "5afdf674f30073b10ed5b4ee06fd3818db26e74cc935c649548256cb60160f7a", + "packages/flow/openadapt_flow/remote_frame_contract.py": "745c8e9a6731ea89b722092183f760527d9e6f329a5a7ef61152870114e77406", + "packages/flow/openadapt_flow/repair/__init__.py": "db1e673372764eb9ba76be658980a94b9747a0d716b5ec1a8d5f74d69ca909ee", + "packages/flow/openadapt_flow/repair/agent_teach.py": "ec9995ba2d3ccaee0ee623e77cd30f0d30a565235befcfbb003fb81d4b7f2920", + "packages/flow/openadapt_flow/repair/auto_repair.py": "dd4270718c906eef6d89939d5bfdc5ae63d842ab78bdc55f45b5afd425507c79", + "packages/flow/openadapt_flow/repair/campaign.py": "520e08184f5abe0bb53a4e08eb2d314e89ad3cc0636069573ca26bdfa82f20db", + "packages/flow/openadapt_flow/repair/candidate.py": "4cbe98e2ea47727906a8c46139ecec4923befd35426a81d5816a5196796ffe2e", + "packages/flow/openadapt_flow/repair/cli.py": "b600b9920a3dc6e993f96c38d4914fc44458a9b9bcdfee807f58660a41b342dc", + "packages/flow/openadapt_flow/repair/cloud_review.py": "eae37867caa636115ae63adb6018987c322bfceb78a25c4178f4287bd13bc6d9", + "packages/flow/openadapt_flow/repair/dialog_fixture.py": "7969fe9c3e4373e32873a237867e182f8c2933397e2c47614216c5ced59a4e9f", + "packages/flow/openadapt_flow/repair/dialog_identity.py": "77e828471626051c74e502c29bce3ec2356926b38b6ddd83489b7bdc3fff3dd8", + "packages/flow/openadapt_flow/repair/element_identity.py": "7598d14cc362585fc6c3fb5be341b3a1b64599ed343e42e4bb30f8fe9cc3f5ee", + "packages/flow/openadapt_flow/repair/hold.py": "9b34c9d1bb9fb12d4077d18bf19e9e389ca98ae0e2ea8faecc40e2da267d11ec", + "packages/flow/openadapt_flow/repair/invariants.py": "c645ffc37b7135018e0783955b77a067e719e99765d70ccfc26812b3e0336d45", + "packages/flow/openadapt_flow/repair/lifecycle.py": "b12e47d7ca37d21631616d94292b09195364268e540e0900a0c22dbc9ccd53ca", + "packages/flow/openadapt_flow/repair/preflight_queue.py": "9ca1d23b744218745a660fd3b57233f41366c72c9603673347326854cca49b5d", + "packages/flow/openadapt_flow/repair/readmission.py": "39a35fcc0a2c804b2465054aa0a8803a6b934d2abb8427fc7f79bd5c59629630", + "packages/flow/openadapt_flow/repair/registration.py": "2eebf2d1683568c2893615eb47041467d6f8943c76de81c1c9a9582f9c61e8cc", + "packages/flow/openadapt_flow/repair/relabel.py": "a1b870b14175f073153fbf9b80733d7fd34ac766921a2924e292f4e86a96921f", + "packages/flow/openadapt_flow/repair/self_repair.py": "b7a136c7a1d7076739c71980b75d40ce5eee25f7e00a61512783e1047889b92c", + "packages/flow/openadapt_flow/repair/standard_change.py": "b67523f9d78a4a544a90fb79694e321a90601e9885ade8f302f09d39d96ff741", + "packages/flow/openadapt_flow/report.py": "288b62601328e4630f2db0f1a7af48b3fcf9db36340c370c51628dfe889ca373", + "packages/flow/openadapt_flow/report_redaction.py": "c232d124f4f97f109e46b93dd21748c5b0ce41f2303fced29a13a7a9a0632933", + "packages/flow/openadapt_flow/reports/__init__.py": "51b2ae3769609912e56518619972b551128a0ae9bd2af0e7661ed32629ce16bc", + "packages/flow/openadapt_flow/reports/parse.py": "5732213e6b7290ad8339bd9559d2e25394fe631c0ee289a5bf34d32991f79817", + "packages/flow/openadapt_flow/reward/__init__.py": "a16f606b5333fa2a0732ba0a3ae643125e6831f8c923fd17940194fab25eff76", + "packages/flow/openadapt_flow/reward/calibration.py": "5d54500f75cbd442e5194cde11674099bf3428d72611d8e8aea2a49e1c0a8a61", + "packages/flow/openadapt_flow/reward/callables.py": "a5736681e02b8862da15a81c95be78112d2d61741177f3895ae05941e5c4b913", + "packages/flow/openadapt_flow/reward/models.py": "dd560ca283720d68159c4fd733f2fa865e9d3eefd2a63b73937f69f2436b8ca8", + "packages/flow/openadapt_flow/reward/oracles.py": "26d890de9154ac8714f125e54bdb6dcbf763f45de8a3e0a0df1649ea44f6c00e", + "packages/flow/openadapt_flow/reward/seed.py": "f2286355ff807f8548cfc77d244abf110ccf88d42864f2c071284ffc09cce19c", + "packages/flow/openadapt_flow/reward/serve.py": "c8bf963939d991c1ad55ca7ac4ba50d3d632f70693793b92a2fc07fbb003e5da", + "packages/flow/openadapt_flow/reward/worker.py": "af50ba23db77b40d156302a8565c46efa4bd219779563152713d58cd4e04e379", + "packages/flow/openadapt_flow/risk.py": "98d412586a8b7bc51c377aa2fc37b51457017c911515359e9b856889c17e1ebb", + "packages/flow/openadapt_flow/rollback.py": "524627a9a27a636880f27fcce42d3b25aded6c125c63cbbc625d9d8d92df246e", + "packages/flow/openadapt_flow/rollback_qualify.py": "81d47df7b8f669d029833772365f6ef507aaa95f8a7bdfcb0c2cd1765352ef85", + "packages/flow/openadapt_flow/rollback_state.py": "b229722eafde96741d21fc01e5e2cd0ec81a029a15f57f81eec7aff082324f6e", + "packages/flow/openadapt_flow/run_authorization.py": "eef848a71340e8951d80f421872d82ffc79098025fe9e9d4d3d2aecb50d93464", + "packages/flow/openadapt_flow/run_gate.py": "2297a7c84efe4b10788808d2e0fdd189d6be3a5a20dfb33be338365fad7306cf", + "packages/flow/openadapt_flow/runner/__init__.py": "1111900db86ee0f61faa093996a6e64a1405c9052bb74f186afa1a84a60ffcc0", + "packages/flow/openadapt_flow/runner/business_decision_service.py": "97bab36f3c9dbc9974129a34a5c636dba08e7fbf3c56fc522ed507feae216bd4", + "packages/flow/openadapt_flow/runner/commands.py": "92a62d244b406c405cdfd51c3189a835878f0e334fc74e16e98a1acfbca47ba3", + "packages/flow/openadapt_flow/runner/config.py": "ba84ed0b0638ee12de51f40fe3fa4da13c14effafbbba7bceab256e56664fc2d", + "packages/flow/openadapt_flow/runner/dispatch_envelope.py": "4a867de3740e46b67ecf90b74120f53ad038d7c0aae9b17c6bcefef998ec674d", + "packages/flow/openadapt_flow/runner/evidence.py": "4f884203b69f3368b56e7bd0a31df123a28f5c7b464fa73a69b1da742fd7fe76", + "packages/flow/openadapt_flow/runner/flow_release_receipt.py": "1e80b5d956de0debc12e5a14f4595256eb13caba16d387e8193ad1737be4ae18", + "packages/flow/openadapt_flow/runner/hosted_adapter.py": "0e2f0e5e96a609dac3e78e516ceb8e6a0050f6406a8a6e4e74f93381781e3eb2", + "packages/flow/openadapt_flow/runner/inputs.py": "711464bd021afbde06b9c4d132ea782c3b6a321ea48bcb24c3f565f8609f8a9d", + "packages/flow/openadapt_flow/runner/lease.py": "9ba80da689f4eeec85d44a860a27851e2408e3772b135bd01e4c4a0996d77c60", + "packages/flow/openadapt_flow/runner/outbox.py": "19454f29bb30bd8707f452a00e6fcb3511a1fce1b70ee2401d5d40d2e11e496e", + "packages/flow/openadapt_flow/runner/product_release.py": "072ad1a818fd1ccd7bfbe2995ee90825111890dfb4c1bd9b72275350107adab3", + "packages/flow/openadapt_flow/runner/protocol.py": "02ab8b7ce4a936d8533cf99a58f68fb5281b2afce2f17b99dbba27b0eeaf7e82", + "packages/flow/openadapt_flow/runner/verify.py": "473bab048ac39adfd9020816e81ea969c05856993e4029e17ce9bc9ba237d92c", + "packages/flow/openadapt_flow/runtime/__init__.py": "fa78a3356ae6470aaab3c3093c57502ac8aef9b1ecb03ec2f4ccbc2a0c0b9f0b", + "packages/flow/openadapt_flow/runtime/actuators/__init__.py": "597b6f56b00bb79c7f3a4452f602d1746aa0acc99bebc1683ce71ceece5af59d", + "packages/flow/openadapt_flow/runtime/actuators/api.py": "05fb4e35a9cbb2c85874bdc3d5df1aeaf4f5ba1e011ec0e4ae8b02aaaab78a13", + "packages/flow/openadapt_flow/runtime/actuators/web_form.py": "ceab0e7909c601a2e69a18af012d1448830ac4e1999aac540d9afc897fac3808", + "packages/flow/openadapt_flow/runtime/admitted_composition.py": "cbe722002872533c37797183d6efd3b42a5e65f95759e0e8d28a5dbc1794ec07", + "packages/flow/openadapt_flow/runtime/authorization.py": "c2abcc4dde938c401e6b470d3732a96716f3e251c7895088f80ad7f1a3cbc88f", + "packages/flow/openadapt_flow/runtime/composition.py": "cc10476194e0a5f44e9421476317b72b1233f44eeb0bf95196426f9f609e752e", + "packages/flow/openadapt_flow/runtime/control_overlay.py": "b141f20afa76036f7ab20ae19d97293f7dc0ee20b0aea2c7d23061dca4bb3aa1", + "packages/flow/openadapt_flow/runtime/durable/__init__.py": "32d5846e0775819d84e903485f1c32a14774d2ee3d4f3322335182afd68f8826", + "packages/flow/openadapt_flow/runtime/durable/approval.py": "2b3e5eb95ae02d11dfe75d3970cbb65f8b04cf8c7f066ed8b8aa1bba3da034ba", + "packages/flow/openadapt_flow/runtime/durable/attended.py": "a5405650f6d1ec6b054ddf2de6725efa4dbed5c0c399a0fab07d33488d84c99f", + "packages/flow/openadapt_flow/runtime/durable/attended_service.py": "d621f4785ec37afa9baf1ab18622745cae498eda0f91d903a7e3b8ffd54f2426", + "packages/flow/openadapt_flow/runtime/durable/authority.py": "9d10239dbe97ae34476c996f69685d0a2aff5d2f996dde3df38c4e6b73fce39a", + "packages/flow/openadapt_flow/runtime/durable/business_decision.py": "dff9bb63163561045318f2416420e9d96c1c69fc6f1f31eaa00e50fc41083e63", + "packages/flow/openadapt_flow/runtime/durable/checkpoint.py": "186179fd070087acaa9c5cde3fb64378dd3ec465eea16ed04a26bd5457db9662", + "packages/flow/openadapt_flow/runtime/durable/continuation.py": "5eb5319b3b5a1a52c73d6472418aeefbf961fff41743412a9c82afde9df9b542", + "packages/flow/openadapt_flow/runtime/durable/controller.py": "80e5c6f25b6171475660055708bdfd37e29f8a52065f56c29e059f212f22da61", + "packages/flow/openadapt_flow/runtime/durable/program_checkpoint.py": "1945df32ff1d418c5eac7f9f02fe29297da315d6e07e395cb706312ab1d2cf53", + "packages/flow/openadapt_flow/runtime/durable/resume.py": "195d252291db265cbfa9a8ba6cd6a12a0f6bef6f826e2d494ceb245ad41de82c", + "packages/flow/openadapt_flow/runtime/effects/__init__.py": "26ee73ae1d836050ff796e5211f649d89a416ba171a9aa7449ff8079795cfd6d", + "packages/flow/openadapt_flow/runtime/effects/_common.py": "98c528f40bf94344a951531751fd5c4f382673b76d88bb1c32b35184e3c58eda", + "packages/flow/openadapt_flow/runtime/effects/adapter.py": "6b44cda61a78ce3934db28a6b2ce1ccadac5a52a99f8bbc225346f602116b5a4", + "packages/flow/openadapt_flow/runtime/effects/auth.py": "af0c2be6ab49f998aaf404800a0f5c8a3a35f30f08dda609cafa8b49ea622f91", + "packages/flow/openadapt_flow/runtime/effects/compensation.py": "3e6feb4924e61ffb67802151907218b47c627923362206f5c83aea86a00da3db", + "packages/flow/openadapt_flow/runtime/effects/db_snapshot.py": "49da015f0357c4d18f748a292219488915a8f46cf5ffc7d511dbb233b2fcfcb2", + "packages/flow/openadapt_flow/runtime/effects/deferred_report.py": "f0e94087724acedad688e46c6a6375b1e6c24aa42abeafb10682eee71d9b9526", + "packages/flow/openadapt_flow/runtime/effects/desktop_readback.py": "6d6861ad7d15f17f0c031275006b630cf7dab9210d0c0d3f9910fd1fcb30493a", + "packages/flow/openadapt_flow/runtime/effects/desktop_readback_sessions.py": "85cf7364d4aaa4fdc6673610c4ff387e602a349ff5082acda446f048a82fbf92", + "packages/flow/openadapt_flow/runtime/effects/document_arrival.py": "1f62bb1c46f0f9521610a152fe68340a96014bc4a8d293d6681ef5099acc1307", + "packages/flow/openadapt_flow/runtime/effects/document_hash.py": "5c64f1ffc187cd03114d61dff3518080e59b817f8b1c3424dbc4b57db30e1f04", + "packages/flow/openadapt_flow/runtime/effects/effect.py": "57bd51878348da55b0dc8ff4b95ab1019fa85a0c92e979970da03778bd3e471f", + "packages/flow/openadapt_flow/runtime/effects/email_delivery.py": "bca21113e2c8fc3f3820fd1c0e27c16f2c5db920ccdd05275477bd08884b6ce2", + "packages/flow/openadapt_flow/runtime/effects/fhir.py": "1ab6f6f65170463c57c289dad3a455f81dff5da3e2e71a80785629026d4031bb", + "packages/flow/openadapt_flow/runtime/effects/file_arrival.py": "8a5dcb94534e9f3ef40fa820cc18ae73e45f626137f0de856941c3adcfc235e5", + "packages/flow/openadapt_flow/runtime/effects/frame.py": "2e5b98bcaa8f5611e9875ed88d7686b3e05100794236f09046cc8052768dded4", + "packages/flow/openadapt_flow/runtime/effects/fresh_session.py": "49ad12c9ae70e5191e99c1039743b1c6a9857ea8a523adaf4ad2ad622aaffc3d", + "packages/flow/openadapt_flow/runtime/effects/graphql.py": "cebae9844af5046963fdaba3abaed3ed1f9b1084dedd1a8948ac454b64990e26", + "packages/flow/openadapt_flow/runtime/effects/late_write.py": "66e97de5eddd1f8094f7cf71f0d7ef49de4f77605c7a42d86b11a5c7c3611e6c", + "packages/flow/openadapt_flow/runtime/effects/onscreen.py": "39de63f4ea9a75b45670edd1fd2573a10066dfae890ba9e71ec02fa1c2ab5ec7", + "packages/flow/openadapt_flow/runtime/effects/readback_values.py": "447e06cc67ea338fce7a5c2b1bb826bdcce325d874a2e8ec5e48c65374ab932c", + "packages/flow/openadapt_flow/runtime/effects/rest.py": "3db9cbda4babee7267bbea4425edec6324d3c0b6643b69eeba241fdfbff8f295", + "packages/flow/openadapt_flow/runtime/effects/sealed.py": "d763c49a9e4382c4160e33e0f8e6de2e1c86b59ea9cccd9b41a7328264c6f141", + "packages/flow/openadapt_flow/runtime/effects/smart_auth.py": "57dc86cbd4249d528148d6c47a0bb5eec56d9c2e854ede8e0a97a7dbe8cda15d", + "packages/flow/openadapt_flow/runtime/effects/sql.py": "42210633b40a3dcc0f9ec6742c10ddb6656db2a1e2c90b9c92281488ab4ad330", + "packages/flow/openadapt_flow/runtime/effects/surfaces.py": "5841fc1d7e97dd481964e354df5185136aa8759cd90c94d5ef2e71a21cec423d", + "packages/flow/openadapt_flow/runtime/effects/ui_readback.py": "94a7291c95e672d8611949a4d923a26061d5b938a74d706159644b23ce7eb476", + "packages/flow/openadapt_flow/runtime/effects/versions.py": "4050203e4bbdcb7e142a581e151d554d0f0be2e022a347e2b07c8becadc7e27d", + "packages/flow/openadapt_flow/runtime/grounder.py": "75e90667e536b4fd10612a29fe19b183da72bf8ef08f8939c59e1425b1339e4f", + "packages/flow/openadapt_flow/runtime/heal.py": "73567f8f02cadd52137736934618ceef8873b5d09ca0bb8e1d2f2ecb24efd516", + "packages/flow/openadapt_flow/runtime/healing/__init__.py": "7f2b58eb0e8d53394d9764066378e228f4dcbd8fd48b143b3497c11167b6ad80", + "packages/flow/openadapt_flow/runtime/healing/governance.py": "f3a755d46bbd7eedbe6b0ebc962cc2073714551eee34c7f5f2faf002623d3222", + "packages/flow/openadapt_flow/runtime/healing/patch.py": "2a918bfbe9523c7946085581465bc6fae5735ba9d9e46e2a0a04e4a841fdadb7", + "packages/flow/openadapt_flow/runtime/healing/perturbation.py": "18159ae5a273b33b90ee677164e44fe794bff4c8cfa96a9d3d0f9f8d12166f18", + "packages/flow/openadapt_flow/runtime/healing/pipeline.py": "100adcd9230ffa734c2ad85146c1c4af5e065c5c882ab1a36cc7a4391a516909", + "packages/flow/openadapt_flow/runtime/hover_evidence.py": "bafe2cb6ff8ee205a510cca79bf1324a316d0ebc5728e5b9e8581290fcdd0790", + "packages/flow/openadapt_flow/runtime/identity.py": "420fa5e0478493da4145517be1cb0ffc9b40cd4b6ead3fae9decb34ec40c11f5", + "packages/flow/openadapt_flow/runtime/identity_census.py": "690cd1dc5083b53d18ef5d8d7d768dde12b0c4a87dafe769f7883eff3f903be3", + "packages/flow/openadapt_flow/runtime/identity_looks.py": "3a80471cb4b3c71f5d1a6e0be455ad7bff3d05c19a3be278233672b5c4674eaf", + "packages/flow/openadapt_flow/runtime/identity_template.py": "3146c491b30325bf2b20526f9e69476e21c2446930709bcd78a6bb5bb5ad26c4", + "packages/flow/openadapt_flow/runtime/identity_vlm.py": "be1151e8bdb3cafe428b5dd25579d6c5d7ef7b47c18ff3f9687d56f94a229428", + "packages/flow/openadapt_flow/runtime/known_ids.py": "42e719c1f2618431a5a722b50e6d832873f059534e22922fe57f6454c543ead1", + "packages/flow/openadapt_flow/runtime/layout_drift.py": "b385273433c782841482c331ceb3e1819d5a9362a1acdbc168d1da395f9ad56a", + "packages/flow/openadapt_flow/runtime/list_guard.py": "9471aa4b414fd795a156726758ffbb12998a8d7b40766c005c7475a29db495dd", + "packages/flow/openadapt_flow/runtime/list_source.py": "603aa2ba02841f9ab8f7c71911d7288302edac3acbca87bb54046111cc8b158d", + "packages/flow/openadapt_flow/runtime/local_grounder.py": "97820b33e69270f07c84873a224a93d5e5cf78c80ee6e0e8fb8e1be4992ee83d", + "packages/flow/openadapt_flow/runtime/process_v1.py": "8c4c42640d2051cc84965353af63382101d4ee04d2b11b2b120a9587702ca1b9", + "packages/flow/openadapt_flow/runtime/program_predicates.py": "3e76056ee036464a35dcd0a001f167d00b78eb97e73aa2100270dc6ea6563106", + "packages/flow/openadapt_flow/runtime/readback_pipeline.py": "8251eb238d5f33efaa561ffac7c8cdbd0d5e02d9f651e31308693b2c65069296", + "packages/flow/openadapt_flow/runtime/remote_vlm.py": "cc00a062f99e2f4abe608346fef912da5f2eaed9d90fc2d6a4c4f17debf7fd97", + "packages/flow/openadapt_flow/runtime/render_scale.py": "81cf507214e432ac9279bb09398dc20ac187c1d749913afc8586af49accd5638", + "packages/flow/openadapt_flow/runtime/replayer.py": "645983f97fcb51f8213b9a9aafa65f1611825b37c13bb7761b1a4a263782b7d6", + "packages/flow/openadapt_flow/runtime/resolver.py": "ced3df05eb587ea25e970053314154ac97c22b333e596d5b14f0962d10a7fa23", + "packages/flow/openadapt_flow/runtime/speed.py": "3ea92c67afa3766fb0e1aacc34b3d2bcc5fcc1d397a23904cc20d10adab3d088", + "packages/flow/openadapt_flow/runtime/step_timing.py": "8d0f0f851eeb4c4c331b52c9376e6527a3e40134f2500204804cc1a9ce269ef8", + "packages/flow/openadapt_flow/runtime/surfaces.py": "34e5142a4138cfae191a404950c04d8efe48b7526e8a4dce3b05953f4191cb28", + "packages/flow/openadapt_flow/runtime/target_param.py": "43de5d0653b793d90066c6b386c4e64bef87a70a903623480a380a2f87ae7bf3", + "packages/flow/openadapt_flow/runtime/variant_router.py": "9012abb40a0c71c0ce04c4b144ab6851d42b3d0d8026d8688e9eb2dda7951e26", + "packages/flow/openadapt_flow/runtime/variant_stamp.py": "9bba7d2fa3b81008040cbfdae52311b6c79ac24bb85e3799e3e136c08491809b", + "packages/flow/openadapt_flow/runtime/worklist_pool.py": "1b7c81f738dc7997e0ff174052ce98b757b0f6f00bb1ecf0c96fa3df62bd110f", + "packages/flow/openadapt_flow/runtime_validation.py": "a5a220f570c5ee5769ac2f21d8d140bb69373d4de2ccc0ebfd08540122883f0e", + "packages/flow/openadapt_flow/sanitized_artifact.py": "fcb0a49a2ffe8f4785b4e221bd023c6341846ba4e17d19004f6c59f5461563ac", + "packages/flow/openadapt_flow/scaffold_verifier.py": "f95b2a577f5f5ba4c4ad3934f4820282e3c164351ad96917663ade04d247e938", + "packages/flow/openadapt_flow/seal_anchor.py": "97d63252915648d78ff81c044397ade698a35d748401279c814956d9fe004713", + "packages/flow/openadapt_flow/seal_batch.py": "c93b91fb0401d7fb3810b304c1e99a5817b82c346927c83c96cfd77892d31415", + "packages/flow/openadapt_flow/seal_keys.py": "63c80bad5675fd97808fe0093fd1a01b0c9919c3545fd11b30197feb86ad7e71", + "packages/flow/openadapt_flow/seal_timestamp.py": "9d39f36b4dd56bdc5f7a0ceb526e9208a790f648e7108a5684fd24670e4f6f66", + "packages/flow/openadapt_flow/sealed_set.py": "ade6e8a77cf05a342f355e2aca55f69ef4a5eb2f320ff0f627ad56c9fbef1a72", + "packages/flow/openadapt_flow/section_capture.py": "cbb98dd22dc57ad01b887180f9dc47d4af616a5f556eb225d91eddae005d07fa", + "packages/flow/openadapt_flow/sections.py": "4001315490d7a921f2ab7d08b47f248ba26facb4dcbc858d79a357cb91fb7c6a", + "packages/flow/openadapt_flow/services/__init__.py": "f38b556ba34b1fc674267a2c386edf9beb542d5a98c8e266b70f61ec6a617ba4", + "packages/flow/openadapt_flow/services/vlm_service/__init__.py": "da71deb7edc5ea6c30c250faf4cb39b4a76b532801debe6f64598b5d2ed8978e", + "packages/flow/openadapt_flow/services/vlm_service/__main__.py": "3844e4479038b670ef897697c71c05bb8e9f92136425ff899137501b3fc60966", + "packages/flow/openadapt_flow/services/vlm_service/app.py": "808094e2285b70965cc7f84a2c6037749ef29304318e1986c3dbbf7e4a1ec9c0", + "packages/flow/openadapt_flow/services/vlm_service/backends.py": "771df17902dc1c31e44517880706468f0c3dd13ee8dedc161ca60f159e1d9255", + "packages/flow/openadapt_flow/services/vlm_service/batching.py": "545ceb328637acbbaf343566059bcd23ba61ae9b385c6d32b14844a08c074971", + "packages/flow/openadapt_flow/services/vlm_service/config.py": "0bf363d7be1419ed1dd1597230a2be87d880473e5d19df4a9b8ef5e2f33a1bee", + "packages/flow/openadapt_flow/services/vlm_service/prompts.py": "f3af38485a7f61ad96fdaf066387c4809cf34b60cb96147ac186caa828cff1c3", + "packages/flow/openadapt_flow/services/vlm_service/schemas.py": "ca2de9068dcdfc45ca2b33a73fe5b51edaef0c27299b901ec5b340aa0143462f", + "packages/flow/openadapt_flow/shadow.py": "4b37bc04380ff4d8e40c654bad306b8731cfec8d73fe895f606b3bebd5c31696", + "packages/flow/openadapt_flow/shadow_report.py": "ea79a2129bc99189171c418931fdb57fee55fe8a638977cc88dd21733167e428", + "packages/flow/openadapt_flow/skills.py": "6a2d779837cc9890f971b34b2f2ebde13d33d05f8dc11e1f1d4f4831a6813660", + "packages/flow/openadapt_flow/ssh_sig.py": "095ab479ed182611a416b442a25bbb86c811f1cc671b08e99e631ae8ef157d88", + "packages/flow/openadapt_flow/submit_binding.py": "985fe6d313ef281d3360e104cd12c65f72838e1940daebfb6ea40851d4641369", + "packages/flow/openadapt_flow/surface_bindings.py": "6b5f02db23ce8ec01bce2eecad6f822d743445547f62a0fe9c6286d50f36d762", + "packages/flow/openadapt_flow/surface_selection.py": "150481be4c3509a07e69a5e2a9ec2ebc8d8f48578d16c14f1633feb5a5b613fd", + "packages/flow/openadapt_flow/teach_scope.py": "56e709f4ff3740bc58e51f3145bb595894537456d6830dcbb73661ae226a7165", + "packages/flow/openadapt_flow/terminal_verification_v2.py": "3bc44d80abc3679cebdeec123b34bcc5656cd2624b1c8943bb22352c0efc3261", + "packages/flow/openadapt_flow/transaction.py": "2d1da876951335d06301757d01607d588dac9146e51e55ca212f120542dde179", + "packages/flow/openadapt_flow/traversal.py": "4bec0fcd5877649e71f7273a17a2073d56c83af6cb7fcd595d0eb9bd7f7a387b", + "packages/flow/openadapt_flow/tutorial.py": "51a21f2bd8aa99ba1d97a3f0a3e8f2cefc856e3c52d8294449b9eabab576c6d8", + "packages/flow/openadapt_flow/undo_proposal.py": "e09883af1fd9741ed876c75a3209ef16bae42b99790c4ca0506aac559f5ae7e9", + "packages/flow/openadapt_flow/validation/__init__.py": "d7f94f2027a19347364cf9491a94f9e6d8c497cd4a67e555e9d200d406e35e0f", + "packages/flow/openadapt_flow/validation/behavioral_generalization.py": "1540c508721eb77fcf1ae5f4b8dc19d88c59e65ec93a71e4ddb88e86878bda3e", + "packages/flow/openadapt_flow/validation/dense_surface.py": "c851d7d90704c59eb2bae9bf1c589df44956190989961ef001a60ac7434211f6", + "packages/flow/openadapt_flow/validation/governed_run.py": "d7fefab39d409381c338b015b9b44f4c757f6bcff693699eef78d73f1f2a9659", + "packages/flow/openadapt_flow/validation/hardening/__init__.py": "f54bdcaf0e0e1c8706e1262d983b7d57dba12e7c3912c50aaec148cc4a6a352a", + "packages/flow/openadapt_flow/validation/hardening/__main__.py": "6a2637d07c62e55e6d46dbd277e7c5d499426044120e949532010efe988aeb75", + "packages/flow/openadapt_flow/validation/hardening/corpus.py": "927d3f60d1971b82175c6a40a4b45116ba1339e8bb644da5750981e3770e068f", + "packages/flow/openadapt_flow/validation/hardening/fixtures.py": "44e85d3c03f03bd46c017a15e46ebff0fee483aed0d76045120e3c43e631318b", + "packages/flow/openadapt_flow/validation/hardening/harness.py": "f214c456ded5c2b27eec1fbe1810c5c7d0467960459baecb04c810df4f978e18", + "packages/flow/openadapt_flow/validation/hardening/perturbations.py": "fe634c8e6878766beea377bd0c62a6ea681d7ff883ff2896c98941a6f3112583", + "packages/flow/openadapt_flow/validation/identity_ladder.py": "4fcb0e3313de20fa3a148d2917aa2b3517d4438561aa140b244b931dc5081880", + "packages/flow/openadapt_flow/validation/identity_looks_dense.py": "ae482b821207e4dc5e6ea825f2d4bedf3cd8528dbbf516e732edbaaca6fb57ef", + "packages/flow/openadapt_flow/validation/identity_rates.py": "d0648e8e94b96677366ac5372453de797f6221a8aa5c799fe2327674d90c851c", + "packages/flow/openadapt_flow/validation/known_ids_dense.py": "9dfcc761b649e83f781e3390077b1c7eea60b4bd16c92e2dae9dd02173192835", + "packages/flow/openadapt_flow/validation/pixel_identity_aligned.py": "dff6c6f9d7ae1788f906c42b2cf443e34f93baf3d26d645413b2c84b54933053", + "packages/flow/openadapt_flow/validation/pixel_identity_probe.py": "66095f52fe8de431a3ab0cf68ba02202bb459b46927e98d7ae6bcf4c68d7ef49", + "packages/flow/openadapt_flow/validation/structural_action.py": "1f231ebe02d241ad34b3d9867be26850748a487fb586f646350192d0487dda49", + "packages/flow/openadapt_flow/validation/vlm_identity_probe.py": "f3641e289f0a02e80fcb31ff76c34c0ed150c647fa9954252425db2b7659b713", + "packages/flow/openadapt_flow/variants.py": "f28513f0bc42968b608a6f0957bc8c8117572dc7835fb0838f0734015cdf97ac", + "packages/flow/openadapt_flow/verification.py": "0bb9f564b4d3dccce7fbebad64abaae1cbd4bd600ffa1b58ec18d9025df52ff2", + "packages/flow/openadapt_flow/verifier.py": "5d0dd45d84592772acc96fcd5245b8defb27fd79ffa7bac5420e2fefbd9583cd", + "packages/flow/openadapt_flow/verify.py": "26b3e327a1dec09f8c9a968133f090dababb58cf6178be3e89b11101bc9478f7", + "packages/flow/openadapt_flow/verify_run.py": "f5f262fd0418e31cc37b9b1a1e9a372655af1e9eef6a68bad6447e8d17a31725", + "packages/flow/openadapt_flow/vision/__init__.py": "f41b931a97bf4ac5a8f1f141dc905bec4367bb350e8ccc90446842eb63765bd5", + "packages/flow/openadapt_flow/vision/hashing.py": "60d5b56a991100310163007d6778d6bec8521826c3f586f6b88e6e1a76f1d70e", + "packages/flow/openadapt_flow/vision/imgops.py": "9cd252bcb3a8bab46795098bc29cd5cf988d77c96abd9687ba93b9252e3f53c9", + "packages/flow/openadapt_flow/vision/match.py": "b4d34620ef7aa9f68e922659554c02f51e2c20f9469febd88457d158163d213e", + "packages/flow/openadapt_flow/vision/ocr.py": "9a60f5f3c760592a5e7874de1949189c1d2f65028b6fe6f2a927a667b8cd6714", + "packages/flow/openadapt_flow/vision/ocr_backends.py": "3b38a83eeeb4a9191ad729c1f8e15ecb239ff7d061239b2fd1c3b65c98e35b26", + "packages/flow/openadapt_flow/vision/settle.py": "d81049501c6dc41a4713ddc3d4d8fbc506f4e2320ff7e5ca6a4a769e6a7a18f6", + "packages/flow/openadapt_flow/visualize/__init__.py": "8d791c4ae4fff8a56e5860fdd95c42e441ae462eb029cdc268d771eb0ade4c96", + "packages/flow/openadapt_flow/visualize/admitted_composition.py": "9758fb49a8d3e587204e3df921a42cf823921cb25f8c66e08cfec748337be8d7", + "packages/flow/openadapt_flow/visualize/builder.py": "53749366b83db4a49596cecd41859213ef38bb62f8fd98ed41c3b53dd7ea5211", + "packages/flow/openadapt_flow/visualize/composition.py": "52d42000d0747af577a6f4e2af0b4326c22790c3cf7fc2e6ebd05b83baef91d0", + "packages/flow/openadapt_flow/visualize/projection.py": "8993aa3c33779eac81490f055e31143f09120d047cadd43e94e226b6dadba9ae", + "packages/flow/openadapt_flow/visualize/render.py": "87887e31698e1ffbda8984f5212de5e2743fcd6fd637f5b20643cc8fc5a2c65a", + "packages/flow/openadapt_flow/visualize/spec.py": "68db48c9d4ffba499b9901a2016d2e95bf7f78f89af17d521ed47da16381e6d0", + "packages/flow/openadapt_flow/volatility.py": "117c9df0dcbeff9669f78b27455c5bc1ada762798c9a64393c30fdab5c29dfa1", + "packages/flow/openadapt_flow/wire_check.py": "f3ee4c8c5313b02428129128478c1fd30152f11fa5f77e5592debbceeea2fca7", + "packages/flow/openadapt_flow/write_ledger.py": "d4a57b4fdbb22840dce0eda7659b7a4f58ff9f4e4fef2d23a509fd2fd28160f9", + "packages/flow/pyproject.toml": "95f434d5e62c07a32afe5f84a17a04e6e7a7fb48bef18c3f0d449ef5e545f739", + "packages/flow/uv.lock": "45cc4434d0f1ba6df316b2dff1f08eaf5359ca2a141ec215345480d49833af20", + "packages/types/openadapt_types/__init__.py": "8cc924b5be7b9b926263a1bdb9b4d9120af85c805fb2762c877b3a28632e2351", + "packages/types/openadapt_types/_compat.py": "b4889a9291687aec404b5b6a83d98c4090f98f9d1aabf270798df0252d998147", + "packages/types/openadapt_types/action.py": "0ec10f7e6a840c09a48492abbf501d3ebe807fd06671657ae4f4c62137fba61e", + "packages/types/openadapt_types/approval.py": "27ed4f73dcb7c71dca80acd51e8b1087b55c6105c8c1e12c9796e94c98a0f0b9", + "packages/types/openadapt_types/authentication.py": "5a9d88de3de6f659e41ad3817abfc40902b3f3bbdcb6d55906a0fc1d83ffd3f1", + "packages/types/openadapt_types/authoring.py": "658383bed9a542b0e7ecc9dd451bc7de3e0771646e9da826acbcf5d2e69231b8", + "packages/types/openadapt_types/benchmark.py": "23640632a9c95cee35cd664d75aa8aa8ca5585259ead7221aaaff610f469d321", + "packages/types/openadapt_types/business_decision.py": "05932e2d37641390e2693520673bf926d5ceba388f8d702d9213582f66b0a3c6", + "packages/types/openadapt_types/check_level.py": "c999b800924fbeaf872b829607b453b63be8d5d4207d79733f641d6eac6c4ad0", + "packages/types/openadapt_types/clinic_job.py": "4719877cb2a1dee680d74d66a1d2f182bab8f86fbb7d81c3e82baf1bdab79d07", + "packages/types/openadapt_types/computer_state.py": "c2063f22364e9012ee852ff36f8a2a13f5015655a0b0d634fcd5755f813dbfa7", + "packages/types/openadapt_types/control_overlay.py": "3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790", + "packages/types/openadapt_types/control_overlay_tracking.py": "af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755", + "packages/types/openadapt_types/episode.py": "5ace88d9e278d099c0e7939cc19e99bf18cbbb80d6ea7d606e3950c027baa8a9", + "packages/types/openadapt_types/execute.py": "a577cdd2d2c4e5db235acdb38ee92d15521fcba60267b8835d3bf68d228d346f", + "packages/types/openadapt_types/execute_client.py": "ed73a52a1f364646263b25132a2cd60db68deec2b33dfae2b92230402afccb57", + "packages/types/openadapt_types/execute_openapi.py": "4600dd2c8995e909c797e2022c22d7745e522123d28627d61e3dab9e7bf405c9", + "packages/types/openadapt_types/execution_requirements.py": "58a3fdb2a43b06af0f27b01abd812ccef6b226b6cfa5307f0b6ceff5f197fba2", + "packages/types/openadapt_types/failure.py": "26efb6e04d17c10cae8bbd688249ee44bcd7844a62be6736de984a6e881fa0bf", + "packages/types/openadapt_types/human_decision.py": "0bb39a3575f511e10d01d92858b340982adee2f0fc20d2409441dbc0b2c55c4f", + "packages/types/openadapt_types/oracle.py": "47a3060d5a49529d4f6506fc3116a0bf902e8b30431456154604ce8371195cde", + "packages/types/openadapt_types/parsing.py": "aae2ceaeead749c3b67619a271b87ecf2388496d37d5bf524834a884ff87a720", + "packages/types/openadapt_types/plain_results.py": "a1fc8b54434af25314e179fbd2670c60f9e52a2a08a4757c8b16d281cbc68951", + "packages/types/openadapt_types/process_capability.py": "8fa70ad62c586534884a73290710c9706291160d63397cb5543de1f2646114d9", + "packages/types/openadapt_types/production_lifecycle.py": "046f30559dac66e4105ad830a274cfb31e0374f95a7a8b0adc1b7dff2aba9449", + "packages/types/openadapt_types/reward.py": "17376a9797ab7136d54285c0ecb9f87792a64b7b0ea9cbd2d802896e5b0ecf6f", + "packages/types/openadapt_types/runner_capability.py": "26fd3c1d96a5558c70886cf203c999d524b9bf927cde23d90735c90be9e2b427", + "packages/types/openadapt_types/schemas/__init__.py": "965365ce3faee699154d06428c2dd83414c18632ca3ba39516ef513523b8d618", + "packages/types/openadapt_types/seal.py": "bf4ac2e28b561b3ed80b3e1da2bb5cb0eb005a0cdc151dcf805fd02157bf5143", + "packages/types/openadapt_types/vocabulary/__init__.py": "b5f060d92bea3c0d8b1c15468c1564c3cf5ee864ee4bddf2393debca8f269bb4", + "packages/types/openadapt_types/vocabulary/__main__.py": "0d132037f99fe1d1588a9233ed179df7de0dcbf2d36ffb0fec294c98bd1d7acf", + "packages/types/openadapt_types/vocabulary/lint.py": "ee2a8c6279aa3c737fa2f49e09092c7f652dedaef513e59baf63dd19ba661060", + "packages/types/pyproject.toml": "4904ddc18342be5bf7da91f315d1bed91e60b35dc55d1aa3bdfb149af13415c6" + } +} diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/canonical.json b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/canonical.json new file mode 100644 index 0000000000..4bc7b3c700 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/canonical.json @@ -0,0 +1 @@ +{"files":{"templates/button.png":"fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493"},"workflow":{"contains_phi":false,"created_at":"2026-01-02T03:04:05+00:00","data_sources":{},"encrypted":false,"name":"release-compatibility","param_specs":{},"params":{},"phi_scrubbed":false,"program":null,"recording_id":null,"schema_version":2,"secret_params":[],"steps":[{"action":"click","anchor":{"click_point":[80,70],"context_text":null,"identifier_crop":null,"identifier_region":null,"identity_template":null,"landmarks":[],"ocr_text":"Apply","region":[40,50,80,40],"search_pad":80,"structural":{"automation_id":null,"name":null,"role":null,"selector":"#apply","window_name":null},"structured_identity":null,"template":"templates/button.png"},"api_binding":null,"effects":[],"expect":[],"guard":null,"id":"apply","identifier_crop_missing_reason":null,"identity_armed":null,"identity_unarmed_reason":null,"intent":"apply synthetic change","key":null,"param":null,"risk":"reversible","scroll_dx":null,"scroll_dy":null,"secret":false,"text":null,"timeout_s":10.0,"wait_until":null}],"subflows":{},"viewport":[320,200]}} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/manifest.json b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/manifest.json new file mode 100644 index 0000000000..208fac00f3 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/manifest.json @@ -0,0 +1,22 @@ +{ + "schema_version": 2, + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.6.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/templates/button.png b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/templates/button.png new file mode 100644 index 0000000000000000000000000000000000000000..797e0782c988cdc7fe1b99c3613d3c826f48477b GIT binary patch literal 579 zcmV-J0=)f+P)TsqS%X#kV3K3Ld!q|A%z19K_Xa*jd%|ME3vh;u(PvL!Td;wHd=^RV4+|d z#a2)W1ixZnSzMQIVMlj~^Hh_=WH=8q2`S$;8;mSyYpdN!Ly2yM69y9tKp`ORhn0LSAol}cT&SF+tOgoRDh6a*m_i&>Us z+cp5$ww+8SbzQGiDzYr!O)!!qDT)FBhGFFMd5_0Kwi|}9u%alvUQZB&{eC~6&vBT` z%HJ8uNnavW#9Ue9JT05DB+JRWB<8HQoJUN4^D z^Z5YabAkzn!wkdhc00?m5{U%CVjm&7!;8ft9*^TO$8nR%1OObz!7s4EV8HYI-Q=A@ zp&-k$AP59o-9S=UQIvE#jmLpNAQ%j;Rx7{XKO7DbLj8Wf(P#j`=Oh}9wpuL!5Jhn` z8a?hj1KA_pY&Jh$;s+7{+U@qu%$sC7o#Mgea^W~G5{VFOb;BR}($X}oR4U;;e;g!t z_%4=Z{WrlNgb+fH?(>Is_&HEvw?l>94i$DgRM_oMVYfqt-OkGidtU9grGL*i#q_=y RoT~r;002ovPDHLkV1k8;0VMzc literal 0 HcmV?d00001 diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/workflow.json b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/workflow.json new file mode 100644 index 0000000000..5a5aad4b5c --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/linear/workflow.json @@ -0,0 +1,112 @@ +{ + "schema_version": 2, + "name": "release-compatibility", + "recording_id": null, + "contains_phi": false, + "phi_scrubbed": false, + "encrypted": false, + "created_at": "2026-01-02T03:04:05+00:00", + "viewport": [ + 320, + 200 + ], + "params": {}, + "param_specs": {}, + "secret_params": [], + "steps": [ + { + "id": "apply", + "intent": "apply synthetic change", + "action": "click", + "anchor": { + "template": "templates/button.png", + "structural": { + "selector": "#apply", + "frame_path": null, + "role": null, + "name": null, + "automation_id": null, + "window_name": null + }, + "region": [ + 40, + 50, + 80, + 40 + ], + "click_point": [ + 80, + 70 + ], + "ocr_text": "Apply", + "context_text": null, + "structured_identity": null, + "identity_template": null, + "identifier_crop": null, + "identifier_region": null, + "form_identity": null, + "landmarks": [], + "search_pad": 80 + }, + "text": null, + "param": null, + "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, + "key": null, + "modifiers": [], + "drag_end_anchor": null, + "scroll_dx": null, + "scroll_dy": null, + "expect": [], + "effects": [], + "api_binding": null, + "risk": "reversible", + "risk_explanation": null, + "risk_review_required": false, + "wait_until": null, + "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, + "timeout_s": 10.0, + "identity_armed": null, + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null + } + ], + "interstitials": [], + "program": null, + "subflows": {}, + "data_sources": {}, + "manifest": { + "schema_version": 2, + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.6.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false + } +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/canonical.json b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/canonical.json new file mode 100644 index 0000000000..0ee4c36a3b --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/canonical.json @@ -0,0 +1 @@ +{"files":{"templates/button.png":"fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493"},"workflow":{"contains_phi":false,"created_at":"2026-01-02T03:04:05+00:00","data_sources":{},"encrypted":false,"name":"release-compatibility","param_specs":{},"params":{},"phi_scrubbed":false,"program":{"entry":"apply","states":{"apply":{"id":"apply","kind":"action","loop":null,"on_exception":null,"outcome":null,"reason":"","step":{"action":"click","anchor":{"click_point":[80,70],"context_text":null,"identifier_crop":null,"identifier_region":null,"identity_template":null,"landmarks":[],"ocr_text":"Apply","region":[40,50,80,40],"search_pad":80,"structural":{"automation_id":null,"name":null,"role":null,"selector":"#apply","window_name":null},"structured_identity":null,"template":"templates/button.png"},"api_binding":null,"effects":[],"expect":[],"guard":null,"id":"apply","identifier_crop_missing_reason":null,"identity_armed":null,"identity_unarmed_reason":null,"intent":"apply synthetic change","key":null,"param":null,"risk":"reversible","scroll_dx":null,"scroll_dy":null,"secret":false,"text":null,"timeout_s":10.0,"wait_until":null},"subflow":null,"transitions":[{"guard":null,"label":"","target":"done"}]},"done":{"id":"done","kind":"terminal","loop":null,"on_exception":null,"outcome":"success","reason":"","step":null,"subflow":null,"transitions":[]}}},"recording_id":null,"schema_version":2,"secret_params":[],"steps":[],"subflows":{},"viewport":[320,200]}} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/manifest.json b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/manifest.json new file mode 100644 index 0000000000..db82032f59 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/manifest.json @@ -0,0 +1,22 @@ +{ + "schema_version": 2, + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.6.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/templates/button.png b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/templates/button.png new file mode 100644 index 0000000000000000000000000000000000000000..797e0782c988cdc7fe1b99c3613d3c826f48477b GIT binary patch literal 579 zcmV-J0=)f+P)TsqS%X#kV3K3Ld!q|A%z19K_Xa*jd%|ME3vh;u(PvL!Td;wHd=^RV4+|d z#a2)W1ixZnSzMQIVMlj~^Hh_=WH=8q2`S$;8;mSyYpdN!Ly2yM69y9tKp`ORhn0LSAol}cT&SF+tOgoRDh6a*m_i&>Us z+cp5$ww+8SbzQGiDzYr!O)!!qDT)FBhGFFMd5_0Kwi|}9u%alvUQZB&{eC~6&vBT` z%HJ8uNnavW#9Ue9JT05DB+JRWB<8HQoJUN4^D z^Z5YabAkzn!wkdhc00?m5{U%CVjm&7!;8ft9*^TO$8nR%1OObz!7s4EV8HYI-Q=A@ zp&-k$AP59o-9S=UQIvE#jmLpNAQ%j;Rx7{XKO7DbLj8Wf(P#j`=Oh}9wpuL!5Jhn` z8a?hj1KA_pY&Jh$;s+7{+U@qu%$sC7o#Mgea^W~G5{VFOb;BR}($X}oR4U;;e;g!t z_%4=Z{WrlNgb+fH?(>Is_&HEvw?l>94i$DgRM_oMVYfqt-OkGidtU9grGL*i#q_=y RoT~r;002ovPDHLkV1k8;0VMzc literal 0 HcmV?d00001 diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/workflow.json b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/workflow.json new file mode 100644 index 0000000000..6f9eb3feb4 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/program/workflow.json @@ -0,0 +1,142 @@ +{ + "schema_version": 2, + "name": "release-compatibility", + "recording_id": null, + "contains_phi": false, + "phi_scrubbed": false, + "encrypted": false, + "created_at": "2026-01-02T03:04:05+00:00", + "viewport": [ + 320, + 200 + ], + "params": {}, + "param_specs": {}, + "secret_params": [], + "steps": [], + "interstitials": [], + "program": { + "entry": "apply", + "states": { + "apply": { + "id": "apply", + "kind": "action", + "step": { + "id": "apply", + "intent": "apply synthetic change", + "action": "click", + "anchor": { + "template": "templates/button.png", + "structural": { + "selector": "#apply", + "frame_path": null, + "role": null, + "name": null, + "automation_id": null, + "window_name": null + }, + "region": [ + 40, + 50, + 80, + 40 + ], + "click_point": [ + 80, + 70 + ], + "ocr_text": "Apply", + "context_text": null, + "structured_identity": null, + "identity_template": null, + "identifier_crop": null, + "identifier_region": null, + "form_identity": null, + "landmarks": [], + "search_pad": 80 + }, + "text": null, + "param": null, + "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, + "key": null, + "modifiers": [], + "drag_end_anchor": null, + "scroll_dx": null, + "scroll_dy": null, + "expect": [], + "effects": [], + "api_binding": null, + "risk": "reversible", + "risk_explanation": null, + "risk_review_required": false, + "wait_until": null, + "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, + "timeout_s": 10.0, + "identity_armed": null, + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null + }, + "loop": null, + "subflow": null, + "transitions": [ + { + "guard": null, + "target": "done", + "label": "" + } + ], + "on_exception": null, + "outcome": null, + "reason": "" + }, + "done": { + "id": "done", + "kind": "terminal", + "step": null, + "loop": null, + "subflow": null, + "transitions": [], + "on_exception": null, + "outcome": "success", + "reason": "" + } + } + }, + "subflows": {}, + "data_sources": {}, + "manifest": { + "schema_version": 2, + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "file_hashes": { + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493" + }, + "provenance": { + "compiler_version": "2.6.0", + "source_recording_sha256": null, + "compiler_config_sha256": null, + "created_at": "2026-01-02T03:04:05+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false + } +} \ No newline at end of file diff --git a/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/provenance.json b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/provenance.json new file mode 100644 index 0000000000..2aa9d0cd12 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/2.6.0/provenance.json @@ -0,0 +1,660 @@ +{ + "bundles": [ + { + "content_digest": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "files": { + "canonical.json": "f44b31062baf7e119b04bea142ad87120568cf5c9f94712a0c3e137a123edf7b", + "manifest.json": "0607eb43b30f75aa4c2cc818bd393cb31fe888b0fef486b407af144aa5f532f7", + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493", + "workflow.json": "e6dd7f1136bb635dd1722561f4bdba27d4e525d9fd87a43c9e8e25e280e59e79" + }, + "shape": "linear" + }, + { + "content_digest": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "files": { + "canonical.json": "7694ed3afd395c21a26e3fbe1b675aa1770b3dc9e2937843f84d48e935b686e2", + "manifest.json": "888a65a3ec8852537794a80beb82aa86222ecdb02a6ddc0869571da567489ec4", + "templates/button.png": "fa53ee8e6f7730a17a6b8ea30ebab860d2811526204fcf2c76946850b582b493", + "workflow.json": "95372f2287548b78c018de503e6fc6a3c188abf13e452de2bfdf1fa432ac81e8" + }, + "shape": "program" + } + ], + "commit": "1c832365d11e9dbe2a166485b13f7b8effd6d8d0", + "generator_sha256": "d995443471ee4a6fd6854e53a15da4d3681c798a5af9ffde7d3506d8c5015e28", + "original_release_load_verified": true, + "pydantic": "2.13.4", + "python": "3.12.16", + "release": "2.6.0", + "source_hashes": { + "packages/flow/openadapt_flow/__init__.py": "4861ba1a0e969d169fe649ef20dd8bd56a2a5815ac1c15444ecd57ec5b2a4e65", + "packages/flow/openadapt_flow/__main__.py": "e8be5ac631fbf313bb7088cb3fde43694cf0fbefc85a0760eda2fd2446abe007", + "packages/flow/openadapt_flow/_browser_setup.py": "d259bd323de12fa03b4bf529d77ccb6350d50f665309c377e6df182525c37e6f", + "packages/flow/openadapt_flow/_prewarm.py": "4024f33b41a2fac44b995dfc85a922366b98c18697f6a2b623cb26d72264ed48", + "packages/flow/openadapt_flow/_windows_directory.py": "8c65c3ec7a3482a8ca600b81b6340014a0d181d6fdcfdbedd91ab34acd7544bc", + "packages/flow/openadapt_flow/action_evidence.py": "72e32ee09d2adb1d71e585aeaeb24277257a76915e7b98f753db2874e3624b52", + "packages/flow/openadapt_flow/adapters/__init__.py": "2eca3615b0a12e39a4a51fd7bf2c274785014c548e609597029ef7d8299c9a09", + "packages/flow/openadapt_flow/adapters/capture.py": "8a2c0af76e13cc968015852296d32b0621cb626823a69c6011abcd7391072825", + "packages/flow/openadapt_flow/adapters/desktop_recorder.py": "7f05b9bf03bc1da900d0acceae4cd4ae0a82efc4a3b9ef011c6649e1044b3454", + "packages/flow/openadapt_flow/admit_pipeline.py": "81014276a1749064b07959ac4cbd2ce35361d8f82d881a35fafcb7c852a49fd4", + "packages/flow/openadapt_flow/admitted_composition.py": "e52d45e89fccde58bc345ea420afec1b7f334239b957a0e73b257d0094596803", + "packages/flow/openadapt_flow/after_stop.py": "6395400f6a578d0013fe284141b6778c0d77321cc16918c7bf42ae2a36153de2", + "packages/flow/openadapt_flow/agent/__init__.py": "a08c79b09e45150bd065c5008a2585081ac6c10a1d9c4489dad01718fbb94973", + "packages/flow/openadapt_flow/agent/__main__.py": "5780b9a3b365c96d71fa42a6ea977a1518d711ed5c4b4f37b54b16ffbc9d0419", + "packages/flow/openadapt_flow/agent/admission.py": "2fc9add3c7dc983d49e583d920dd145a95b5152c01e6215bc6dee6a2fd5dcf3e", + "packages/flow/openadapt_flow/agent/approval.py": "a345a90e0e3fa788b520fd26150971a0a5aaff16173a663541f92845ecf409d7", + "packages/flow/openadapt_flow/agent/attended.py": "5fa4c761de375be9ce0f6cf7f72f0230253ccd7546658382a267da2f01cb0f1b", + "packages/flow/openadapt_flow/agent/authoring.py": "87afc6deb8fd2ed6a1e670c37978648fd633deea8c380b0ca937b369bbb452af", + "packages/flow/openadapt_flow/agent/bridge.py": "8675f0c4df081f7573e6df6aa6d943be72103cb8669ab7bae4611560f21d1f79", + "packages/flow/openadapt_flow/agent/browser_teach.py": "ecd9bcd0e2d0c1b7b5c454327d6fc89e24dfde87eb0839ee8f2412b33d3b0350", + "packages/flow/openadapt_flow/agent/bundles.py": "a3e2d63576adc99fd8e4d53e0fa64f1bec914a070d5c0151ba9278ea923809c9", + "packages/flow/openadapt_flow/agent/cards.py": "45ecfb3da287aeb5de1160d1adbcc3590c6cc8c20f7b1b7ab983f643228f62b5", + "packages/flow/openadapt_flow/agent/cli.py": "861da55c75558c4636e66dc748125e7a374b1f141f3002b90941b2dd10d057cd", + "packages/flow/openadapt_flow/agent/contract.py": "4a265a3a961578dcd4cd5e26be85d6eb0799ac4f557924a36b00e17ffdc39c67", + "packages/flow/openadapt_flow/agent/copy.py": "9afe666441689375fcb5aaf54632db2e39e0a32edeb95570789f0d7ce4b55423", + "packages/flow/openadapt_flow/agent/delegated.py": "49414a62704b93999fcf668db4df737121f4b5d9d22f1b36a94de705d171371e", + "packages/flow/openadapt_flow/agent/example_app.py": "847ba77d93cc438e7e54143f5e7686d53733499154e2cde979e20e5f4b871f7e", + "packages/flow/openadapt_flow/agent/flow_service.py": "b5bb10bf1cb819bb0aba9185de9e9d1246a823ff343996a7464d8e481785f2dd", + "packages/flow/openadapt_flow/agent/guard_tools.py": "6c61c36f77ed394797d07466a430387bbe8e9a383877833d769514b13d7dd843", + "packages/flow/openadapt_flow/agent/ledger.py": "e0911cb91a52570a8244ae4e72c9985c0110f30eeacf3d1a19a1c394e2938a8b", + "packages/flow/openadapt_flow/agent/mailbox.py": "c5c8c9a305ddbe8caa985066e0c6f587df4d3703f3d67c7e6643076ab2a709fc", + "packages/flow/openadapt_flow/agent/mcp.py": "780409af40aa212e12997c75d41f6873d17583c70e2b13585344675340032296", + "packages/flow/openadapt_flow/agent/mcpb_entry.py": "23db9f1b3af8bed52ee472b36b0272430d11526f045179602724addc92a22ac8", + "packages/flow/openadapt_flow/agent/repair.py": "235045291bbcf525d19ee2d186b0d61e0f505e2097721361ffe54466604e970a", + "packages/flow/openadapt_flow/agent/runner.py": "a8c30b966efe0532cd9f114b5f5773eac6107d2785df98cd7562d200343d8378", + "packages/flow/openadapt_flow/agent/runner_bind.py": "91d4006e99378c61af2eb50fc9c03ebfb477cdf9f0d27fa070ee9936fb073e96", + "packages/flow/openadapt_flow/agent/runs.py": "abd7041cd1eae1b6017c208e0cf4b083caf965cd2147d0497e896a013cd5cb25", + "packages/flow/openadapt_flow/agent/sandbox.py": "ee78c7959d8213dbd3d4b75eb64ecb2a797147410f90516eef57e6c2bb9edb01", + "packages/flow/openadapt_flow/agent/seal.py": "ea0585e2564f23a59ccc41763b58357a176e53363f3107ab7b54639d902de439", + "packages/flow/openadapt_flow/agent/seal_cli.py": "fcae30891db50c28e6a23282ff94a0dce9796c15c7fea46b8fdc34694ff8acc1", + "packages/flow/openadapt_flow/agent/service.py": "275da34a73890b0c85fb5c06965403162adcacfc499a7493736aab236b5538ac", + "packages/flow/openadapt_flow/agent/skill.py": "7f1637d4bcfdb54ff5920b06dd3bbf913e6fdbc0030605b455e9223672ad2173", + "packages/flow/openadapt_flow/agent/teach.py": "1b7523c08d05ca72ee18c349f0bdf3e2eccd0cc1f060335a6ca989b18cca3d6c", + "packages/flow/openadapt_flow/agent/tutorial.py": "ac42a64169cb23fac79943a0ef8e4678922d120d51aa97944e2be707addb3232", + "packages/flow/openadapt_flow/agent/verify_tools.py": "2cb850a7368d517335639c00377130625e7a3d3f8381a2fb4d9bb770e562d657", + "packages/flow/openadapt_flow/agent_guard/__init__.py": "e1f1ec33d219e845e1de18b61752fe06fff4ba1e12d403870b58d4ef5094c1fa", + "packages/flow/openadapt_flow/agent_guard/cdp.py": "e0a59d4f49dee5a9b0e8b6d3c16db794c923a5b4c8af4fa1418e8da13478d8ca", + "packages/flow/openadapt_flow/agent_guard/cli.py": "e4792c7ce115eeace17871dd5c8cee569a6b9e7a5365acc53d82946677cddd9b", + "packages/flow/openadapt_flow/agent_guard/commit_guard.py": "bcf7cc8d91ce7ab804745766ee4b838b648095364467f2a203f78864ba09090c", + "packages/flow/openadapt_flow/agent_guard/config.py": "b25c4e327836f3d2c9167d421eb85d72fbca5c7e8bf7f31de25da3625470caf1", + "packages/flow/openadapt_flow/agent_guard/handoff.py": "7f84e49d42717d304f60257a429b36305c4a32cb7e14566185d4789634283ff5", + "packages/flow/openadapt_flow/agent_guard/requests.py": "92e42c8462ff7a6effc0e7437dd01ace72f02e04c3072ae29495902787cd0d26", + "packages/flow/openadapt_flow/agent_guard/session.py": "9a419ef69572516f5da12efe0d07b72e8effe15634fe1d57635b257abd28a8b4", + "packages/flow/openadapt_flow/anthropic_defaults.py": "46ae2109269142f49be399c719347f84794c9e1ce7fe176d647d0485b9750579", + "packages/flow/openadapt_flow/api.py": "7a34b5b4a7d5b3b815c4008d9d3efab1badddb77dac7477e17308602d1e532b1", + "packages/flow/openadapt_flow/app_profiles/__init__.py": "8fb0cd9f3f5cd6a7003a0cb5cc009314f45b45e2aba085f8698c0a906ffd52c3", + "packages/flow/openadapt_flow/approval_mode/__init__.py": "7d802c15089999853ed4a0b55bdaa1962998f2b8e9ee0170695c7de2ec5c3e10", + "packages/flow/openadapt_flow/approval_mode/approver.py": "2e314d0ae6432f81972cdc84a8db1635161a6d17dd8fe0e9cb656fd254db7197", + "packages/flow/openadapt_flow/approval_mode/approver_roster.py": "85fb6593d6fb418d8ccb180207cab84d20cfdee59326a8df22a90c0c5c442aef", + "packages/flow/openadapt_flow/approval_mode/coverage.py": "1e5875a9d2d2d87791dcc27af30617c8cf6ed1d8a559f733e5fb634898604b91", + "packages/flow/openadapt_flow/approval_mode/demo_approver.py": "463281495181c3f97969517391a6912a4464a9431ef7c4fd49ba451f61155b80", + "packages/flow/openadapt_flow/approval_mode/durable_carry.py": "0f0c5c204fe6cfabef54bd988a3050b58ed6a0a13b178c2615cbeafc2fb7b7d8", + "packages/flow/openadapt_flow/approval_mode/form_state.py": "f2548b7f2527e9d648623fc5bafa841feabe8272d6a5822e23d4c937e910ae53", + "packages/flow/openadapt_flow/approval_mode/gate.py": "f1e35292d0ca73d7273ade289d5cc838580ecb643e13c9a454c3ebf2a346a20c", + "packages/flow/openadapt_flow/approval_mode/human_attestation.py": "eca372e1fa68d9a626ac80046dbf6ab401e97b08a185bd162b278003505883f3", + "packages/flow/openadapt_flow/approval_mode/item_approval.py": "5e452cbeb32a98963c553bbd0e96153ea2690d3294abf70ffedb4eafb79571ab", + "packages/flow/openadapt_flow/approval_mode/model.py": "42f121b2a7fc992bc1201ccb8c587d7134fec6fdbc54eb12dacd71ff4a00774a", + "packages/flow/openadapt_flow/approval_mode/post.py": "91134523827840f5d4de7652513b4a70a906abf64ddda9c367b9fb814045a006", + "packages/flow/openadapt_flow/approval_mode/requirements.py": "4f8ba1eff008e45651365929bbbaee873301d993c714168e78456009b06b402b", + "packages/flow/openadapt_flow/approval_mode/signed_policy.py": "4dc065c007d6c3112de2af004e36ecd8559ceac8a76eaf9853c1d49f7363c757", + "packages/flow/openadapt_flow/approval_mode/switch.py": "2fa2cc31eef6b27e490897eb7997f8679e843cb86508f41b72cc8b5fbe67cbf8", + "packages/flow/openadapt_flow/artifacts.py": "15acc61097ac814d1de0320a0a11db7d03da89ee70d68eac1cbd59aed63e3ac0", + "packages/flow/openadapt_flow/assurance.py": "fba477ecf901039ebefb8d2d9be340accf9bcfa96314a243febc35157ba48fd5", + "packages/flow/openadapt_flow/at_rest.py": "6a8a43e959fd44a90c813fd2727e3c8d23706f8536f9b4f2567f49f7487d32bc", + "packages/flow/openadapt_flow/attest_bridge.py": "449502e879bc629b851087b86353a0dbd5761508daec92989083f30fdad14fee", + "packages/flow/openadapt_flow/authoring.py": "ce9f98d8e5e7a294b53d761014569e6531ec857f1c4b33eba8121de66a567d5b", + "packages/flow/openadapt_flow/authorization_summary.py": "1057cfc2a597b02bd230654c1df030b3680ce8b6300619b145fcf0e994be866f", + "packages/flow/openadapt_flow/backend.py": "71e7fdc2cf1d7f8555f9f2182d36957d8d7d0a9cf133e5bc8774a4e19550ee77", + "packages/flow/openadapt_flow/backends/__init__.py": "c76aa599a2ea0205a3fffb4a33124dbe6637e4b93f0f14d4c604ffa05c2a1869", + "packages/flow/openadapt_flow/backends/_browser_process.py": "1f90beb147f32ddc276884c3e6488e3e533a5bb374ccf675fadc16754cb4e137", + "packages/flow/openadapt_flow/backends/_page_quiescence.py": "2a8b70efd7f6b006fa8141b02a7e59fe5733e7552eeacfc7e34326c0dde147c0", + "packages/flow/openadapt_flow/backends/_server_responses.py": "d20bfd20078304288462c56c17e1348707df9b3dc56388e292822405776900bb", + "packages/flow/openadapt_flow/backends/citrix_workspace.py": "2bc912cfecac2d7d1c4c543cda17a2cbe4b25af195fd8a1d015f6b02661e9665", + "packages/flow/openadapt_flow/backends/factory.py": "0702f58a5c3ac8b00c110f2064d59a2c31469839023a6a12dc70510f18bb26f6", + "packages/flow/openadapt_flow/backends/linux_backend.py": "bad95c06151d66ed66833a00384a032749de5b1ccd9e581ce6ddbd3d5075bd4b", + "packages/flow/openadapt_flow/backends/macos_backend.py": "4ecaab0930039fbd2b2dcba320974eb17bda337e9c0b9324789eab25912cbbc7", + "packages/flow/openadapt_flow/backends/parallels_vm.py": "e724f9ec49210e5220d1cc508ab8a1ef984ac43854535b90548b209985109d0c", + "packages/flow/openadapt_flow/backends/playwright_backend.py": "17dfa88e10dc873a5a2d9b1a0d72360be60f27d5dd3f17f80d6b2f275d76fa5a", + "packages/flow/openadapt_flow/backends/rdp_backend.py": "f6ea77fdbef04bf6064ba68466dba85c627fd633bf188c14d9988b5f0e7a4242", + "packages/flow/openadapt_flow/backends/remote_display.py": "f17e9eae67ed966e9938a2d9287ce287c1ca6aa4efd86f4a5773ffac0d3f9b73", + "packages/flow/openadapt_flow/backends/win32_window_client.py": "ac4a3a324b2ec2fa9f2fa64a7a1687b78ccab8e522d5c81e2c3b8accb79988ef", + "packages/flow/openadapt_flow/backends/win_agent/__init__.py": "cdba9a2b13137a43d83ff03e4eb02c9e250fbaee84ddd6f174c72d5902da553a", + "packages/flow/openadapt_flow/backends/win_agent/server.py": "eb0bfab1f3e4e442e3db184efb92418e2f60c9860616b598cdff974d942270cd", + "packages/flow/openadapt_flow/backends/win_agent/tls.py": "51f2af9d258c3acfd095d5a7c83d40d3719bbd2161be7963c03c84687f8922cc", + "packages/flow/openadapt_flow/backends/windows_backend.py": "77b3d9c931e290afcaa94bdbc191c904e2ca454d99712836760d46fdd06efa82", + "packages/flow/openadapt_flow/backends/xfreerdp.py": "b529b6e1a6683b5a4829a3574e963c514c6079f8e2d273a8f94b816231d11443", + "packages/flow/openadapt_flow/bench.py": "e90fc71ac1c2033a42b06b872de09550fdd9db02b175d176d250e91c207cedba", + "packages/flow/openadapt_flow/bundle_sealing.py": "b8c6c54d53d1e80ced9ce99307af08b670f3690051552488c32afe14334f85ca", + "packages/flow/openadapt_flow/bundle_validation.py": "63532fb8e49f25a8aba8dccca505362a9293b8db9cbc0f2904dd8f9a6e23fea8", + "packages/flow/openadapt_flow/check_level.py": "cb5a4bb2bc25f70c7de05ee1814b8b8a422ebf307809d96ed74c8106f8e3a9d5", + "packages/flow/openadapt_flow/cli_admit.py": "e337d734c1e69b28dd80609f55293040e07798e92028b5ab7afe24d6c613b29b", + "packages/flow/openadapt_flow/cli_api.py": "c110d5247ef49389e5fa1e7ca28c3e5eda6ba5cd34524c8e9b5d160d6ada1203", + "packages/flow/openadapt_flow/cli_check.py": "33485cd4d0e1d58b937475a00a348033c630ddd07a568b2982918cb912eb70d9", + "packages/flow/openadapt_flow/cli_cleanup.py": "74e6dbd940e5b6cc7dacf5e68a5089aa18ebbb108e3f59f7fa56a69e08ab820f", + "packages/flow/openadapt_flow/cli_compose.py": "965a63427ec37964451cdb00fad92065833b6f7cab936c80526c05984b5a68ec", + "packages/flow/openadapt_flow/cli_config.py": "a68b1b9db2c60feee3ec0b4ea079bbacc7b90bb052f9660c87b2e7067c593557", + "packages/flow/openadapt_flow/cli_deferred.py": "058cc58d256276f36db2b1e4f6e0105b505c88c1f65ca9f8e01e5f37f79ddeab", + "packages/flow/openadapt_flow/cli_fleet.py": "a4678b9b48c6c4817673fcccbd18be21966ae6d64dd89fb143bf26f3dce3c7ff", + "packages/flow/openadapt_flow/cli_hints.py": "6589faad4aefc66d6a80fe7875909cb9d335fa5624d9c761a95d5afe772a260f", + "packages/flow/openadapt_flow/cli_known_ids.py": "ed965f2369489a87cefb27ea9c9241bdc9b6058c72fc56450006be9951737897", + "packages/flow/openadapt_flow/cli_local_seal.py": "33c1f1406afa3de7c85bd661befad02b51fc5018f5eefa94546bd6381bf5c37f", + "packages/flow/openadapt_flow/cli_no_outbound.py": "1afedf45d1874e4aeeabc8cf1d36572f6df8b7ddf54bfcb75189d0bb0f149b47", + "packages/flow/openadapt_flow/cli_process.py": "700238b5cfb5edef883d2e57a209d9628fdb12fdc7aee6ea7c4a519999d28d64", + "packages/flow/openadapt_flow/cli_receipt.py": "c887eed402e7e2ac7979410cccd58068dc2a666f507d004e93d4a37e28e6749e", + "packages/flow/openadapt_flow/cli_redact_report.py": "3e9d35b83f8d74e7b085e670787de410aebcb70a3a0de4ff1760bc44c91e48cf", + "packages/flow/openadapt_flow/cli_rollback.py": "f9f80f3d84b1e50241527a5fd09736941f8eac0782fb05acbab2e4765b9747e9", + "packages/flow/openadapt_flow/cli_sections.py": "21d0a362860703f030fea5a2ddcbd0a14a48a4799fcbf117d526a38fcccb04ea", + "packages/flow/openadapt_flow/cli_self_repair.py": "3d75c98c8078de6d74b98f75274a82ba4d865b5585ca4ffd2e38aaa566cb2322", + "packages/flow/openadapt_flow/cli_service.py": "172c06cc25ada99b0532dee35aa4e4510671cc7e7b24871e8b7294abed8cea0e", + "packages/flow/openadapt_flow/cli_shadow.py": "8b419d698a5a46b3133b942832b493edcff39bad527b7705b68f0b54c04329d5", + "packages/flow/openadapt_flow/cli_undo.py": "93b527aaffe833b02ea1e990bf03c908fa88c3f60d764007130a00449b75185d", + "packages/flow/openadapt_flow/cli_variants.py": "4d214beab05bdf5765559004c8e83bada54567624ac1ae704d0a979e127f15c6", + "packages/flow/openadapt_flow/cli_worklist_pool.py": "74b4164f79865db4ae7a41b063ba2f671077c02645021fc2e42424f628431777", + "packages/flow/openadapt_flow/cloud_seal.py": "dd473bf2ce29f708e1c1638d70f8b54a3feaf140f27aada14481f31c1d2517ad", + "packages/flow/openadapt_flow/compatibility.py": "c869b05daece5b5bca489a27f71d2de07354624913d7480135ca0d6354e3516c", + "packages/flow/openadapt_flow/compile_agent/__init__.py": "1f70909ee8780e62513f41d717b8c07cee89dbdc430bf84ea08904998502af3d", + "packages/flow/openadapt_flow/compile_agent/agents.py": "c7b7375b27ed3d7925ce035782092c00ee56ada4c2fd3461c3feee6427aa59b1", + "packages/flow/openadapt_flow/compile_agent/cli.py": "1ad904e8732ab6c65a0b33df8d85f56eae384c47beefdf2e2de8a1cb29920e6d", + "packages/flow/openadapt_flow/compile_agent/code_action.py": "ec0ebc2ec7b41ac36bbe36c7deab28c38d84e46c8515b04799ebd79379f8acb8", + "packages/flow/openadapt_flow/compile_agent/driven.py": "06aff15b79c938ba13065669317ee37101e8cfeffcb0c00e693ea3eef781bf6f", + "packages/flow/openadapt_flow/compile_agent/emit.py": "405411124cfc94b52c45c90c195537ea53b0ef3b242cebf212102c49c0111403", + "packages/flow/openadapt_flow/compile_agent/harness.py": "3eb4543f919c6dbf6778612829c5eb773078b480dbe89e857a771bae0b78722f", + "packages/flow/openadapt_flow/compile_agent/observe.py": "b2a82ff94383acdf113ae4f4c9e75114df3e1c0e7ef1782495eaeed249e41206", + "packages/flow/openadapt_flow/compile_agent/pipeline.py": "2715b6fa231a0c0ac94726133734da4e38dd3e9255cff46bb6e5ec0129091431", + "packages/flow/openadapt_flow/compile_agent/sandbox.py": "0df910a8f3c7f9a4d1a76c2ad97d62cab868694ee67f5ec70ddb2ca0f9d26e97", + "packages/flow/openadapt_flow/compiler/__init__.py": "e38510f5baea14d9d357ab594954ff42f5aff608589ea8ae5d7060e8cf0fa380", + "packages/flow/openadapt_flow/compiler/annotate.py": "0cd0afb521e50c82906c5d47e12bca09a12d0caa9127bae433f830339b41622a", + "packages/flow/openadapt_flow/compiler/api_proposal.py": "89f5ed6889c8f5d6d78cbc70b96f3aca14db416aee903731c9a42c002b8d2e45", + "packages/flow/openadapt_flow/compiler/binding_admission.py": "a288a9d32f7e48631197682b4c886b8da15c2833c31cffa097776a5d6e95d71b", + "packages/flow/openadapt_flow/compiler/codegen.py": "327bcc7ca46c1249ba11caad31305171dc36fd11f02073ef6484aa4939f4772c", + "packages/flow/openadapt_flow/compiler/compile.py": "77f574eebec81f39482a8a7e976a7836131efbb10b1ef79a2185f70f3659f0f1", + "packages/flow/openadapt_flow/compiler/compose_authoring.py": "8ebb7649c73a4bf250dd98db3dbf6e9dccca317130e199632398102e57070a6d", + "packages/flow/openadapt_flow/compiler/disambiguation.py": "2761afc6683f1b72b1126a2482bf1e02f34651489dd28f71a2f57aaddced7fb5", + "packages/flow/openadapt_flow/compiler/distill.py": "3169f571e0f8c63c14f324c626400ae78ad55aab83c03e54b8396e304757fc75", + "packages/flow/openadapt_flow/compiler/effect_mining.py": "c35a6339c75323c00104aa370ea7070631e93c9b0ae978d7b4d59b26f7e51122", + "packages/flow/openadapt_flow/compiler/empty_result_guard.py": "7511f2aa9adec3ed244eb21cad8c382a1954f36b625497ba3267c0e314e833a1", + "packages/flow/openadapt_flow/compiler/fixup_passes.py": "daab90b0f9b4441a7d430e8038813e69d2676504936f0ab86e6197ed14cc86ae", + "packages/flow/openadapt_flow/compiler/induction.py": "c7e5d8e4f7dfa4ecd78cb831b1b98f9424b5a267936a1ed66dfb5df2f96ef160", + "packages/flow/openadapt_flow/compiler/local_edit.py": "867f0ca4d79dccc88367da10e75b180727438a1aeae30a49d407ef5cdff04981", + "packages/flow/openadapt_flow/compiler/lookup_detect.py": "9515fde8bf759f19e5698e3c00f0fe918a790abb0f30f9db911e03d12e84b402", + "packages/flow/openadapt_flow/compiler/loop_authoring.py": "f9ac89cc50e238a35411741aebb3eee7d76c2b263008957d21cdcb49e89b0ee9", + "packages/flow/openadapt_flow/compiler/loop_shapes.py": "b46f39bd5c4c3216c87ed52ebdb919ad1fa87ca0df29ccac376cfb107ec36a8e", + "packages/flow/openadapt_flow/compiler/motif_induction.py": "920c2fb7a409ce90437376ccea3261cf71fd1b029baea8f5a6f2d8de7aefe360", + "packages/flow/openadapt_flow/compiler/narration_rules.py": "2be904090c00ce014a4f1854393770d3b4172943e4d2ec1b35e1e58e5a26d199", + "packages/flow/openadapt_flow/compiler/param_confirm.py": "4cc6c8402a4f30626098e09156bc283f7755d68b6b108c28f2db4c069e5a7cd9", + "packages/flow/openadapt_flow/compiler/probing.py": "e6ee7dedc5f7a8bfa768bf0a0cb6b1271ed6da2b1c1a956968ad34dfbb3f8b48", + "packages/flow/openadapt_flow/compiler/qualification_pins.py": "8392cb44ef2cb33956ae58e235a8611295f07553a0c1ea2aa5b2ef010a997787", + "packages/flow/openadapt_flow/compiler/qualification_proposer.py": "ab4bfa3e51aca8dd5fb2360e5be9c3b454197e09d6365ef20ba0632210997d30", + "packages/flow/openadapt_flow/compiler/readback_authoring.py": "6020b8ef5fc22b0ffa8ede900c6b60b37b60f35dc61b288f5cba238deef5053c", + "packages/flow/openadapt_flow/composition.py": "cc79d2e4215f77b6c3daf2370a853d0cba410c9da3df209d59e1879222af3ea6", + "packages/flow/openadapt_flow/connector/__init__.py": "ea1c554a0de295db995f57f837669419855c9fa1d2fcf958ae4e2feeec37106e", + "packages/flow/openadapt_flow/connector/client.py": "1f55a56123fda690c4bb4dfbd6407f4a96c85c2575eb8928cb7aed30b3a20145", + "packages/flow/openadapt_flow/connector/config.py": "8dd9f662151ce609e21ead89d4019c77a4ab185a0f1b5690df20dac166701c4d", + "packages/flow/openadapt_flow/connector/daemon.py": "3ea5444af51cb28dc84addc4a5db15a6a75486f4ecee93d3b6c09ed7e7ce1366", + "packages/flow/openadapt_flow/connector/executor.py": "463aeafed9092b16a654191611445bb5688334ca87ac0703699d6dfa7f449748", + "packages/flow/openadapt_flow/connector/protocol.py": "963162e5dc20fe0e220fda67e59d138b791fcc6bbaf626776ab192d6cfe3a203", + "packages/flow/openadapt_flow/connector/storage.py": "4ba816b98860b5f6973909b6b6d30138b429a8bcd120423408bf0ade7cc1a916", + "packages/flow/openadapt_flow/console/__init__.py": "3a5ad4c010c98c3afee9c00a4fb7b4209e914b27e48d0cef09d8d4d3eccbbb57", + "packages/flow/openadapt_flow/console/actions.py": "097dd73b8cc61d19cb9cb5752103663a413cae0b822cb708419966413d188850", + "packages/flow/openadapt_flow/console/app.py": "c85d6e1038cbe1ec0737cc51deca93b490f83496a55b0b3b60681575dd07e386", + "packages/flow/openadapt_flow/console/attention.py": "cd4dff1f27d8c0ef022dc92d64eabf2976fcff3ea0a06a5c363af169899f9cef", + "packages/flow/openadapt_flow/console/data.py": "80359fd5e16d13b73a821034b4ac12de7a52cf4dd81c4211c18b2b23154a86e1", + "packages/flow/openadapt_flow/console/decision_context.py": "e5d7297c636d5e5c429558d6ed54ce294b4a3e97629150346369409bfa309d8c", + "packages/flow/openadapt_flow/console/decision_relay.py": "d5320404d4342dbb52be1f2ead1cdb5cf0e22877feaaa1e47974b301df697292", + "packages/flow/openadapt_flow/console/decision_supervisor.py": "a5496779de6c05a21c03fb91cf3a3565951bf97922952cf6606a2c185dea6d69", + "packages/flow/openadapt_flow/console/halt_detail.py": "03d136781ae0324f0fafa16af5cbae52af15360e73963a01ac116967df5c6c12", + "packages/flow/openadapt_flow/console/human_decisions.py": "02651ad8888a5c17bfff6fb47165ba861193243dc0f7c951f8b94f544ca19477", + "packages/flow/openadapt_flow/console/json_artifacts.py": "e908d47f34bc040e83b257b953c55a7f3cec9b2b74a4519369d85c3a12e6cb7a", + "packages/flow/openadapt_flow/console/save_approvals.py": "1c180720a593e160720421a72cb5676a13f399a5e2bd3fd07ccc063b5bd8c00b", + "packages/flow/openadapt_flow/console/server.py": "d250a4e486053343eb92e38887fdb59c55c5c3cde64b69756e432b3b074f7367", + "packages/flow/openadapt_flow/console/standard_changes.py": "29f34f8fb06b4b412e825892fa340bea11f07e21c6560ae4936c0bab19972505", + "packages/flow/openadapt_flow/contracts/__init__.py": "cc4213ad8ee43060894281df685a4d2554b9e0869bcd267f0509b965d81d19cc", + "packages/flow/openadapt_flow/countersign/__init__.py": "727c18b4914e681256d727bf6a13285bd9b43c84430c7cbb610efd6721d7edba", + "packages/flow/openadapt_flow/countersign/cli.py": "bbb2f1b833297dab3d90fab4ee8bc60b71598b93d0da1dcd30baa6de6dfe8886", + "packages/flow/openadapt_flow/countersign/commitments.py": "2ec3967924a7142facbaee575d6c76c0241f9187e4ce2c4b497f479b5e432339", + "packages/flow/openadapt_flow/countersign/document.py": "3e4760c7ad0f6949b8cde441217d647c5caf7ea13f63c25bfeffee92c0c6e375", + "packages/flow/openadapt_flow/countersign/dossier.py": "1ea1151f03b55a6b8a73333b46079cf2cc31d338e6b413490636d2b3ef16033b", + "packages/flow/openadapt_flow/countersign/exceptions.py": "effb4425752f60d44e133ee7b2d704f25473bcb46e168fd17780f50320cf1ee7", + "packages/flow/openadapt_flow/countersign/render.py": "754b6858cb5a3de7db6489103c5225f8f5a62f7ec91af5027b68c0d0c9e7e5cc", + "packages/flow/openadapt_flow/countersign/tsa.py": "c3597122c33e80b3eb2cb129551e1cd50b6021f45cbad01c20869f13699f8200", + "packages/flow/openadapt_flow/countersign/vault.py": "71ea45df4993a00a384791c227328eab67a2e5d3d1d75896e7c841765d5ed1bc", + "packages/flow/openadapt_flow/crypto.py": "4428317caed40074c5210bdb1d54c67a3d29c7fa1fe4bb0da0fd1abcb48db526", + "packages/flow/openadapt_flow/decision/__init__.py": "32b2b08051ab45571c55af1d44f2b68fd4106eec9ceb078d6769c2d306403550", + "packages/flow/openadapt_flow/decision/calibration.py": "51b2adc7ee624d78120a12ebf02d6eb6b9408ce90642e064647fe678e5578051", + "packages/flow/openadapt_flow/decision/cli.py": "b0f453ad2616e4b0f99249e983967d1fa7b929e4692662b8adab47053cdf027d", + "packages/flow/openadapt_flow/decision/eligibility.py": "86e77ae372f8eef0c31eb9ff694e17cadc7f278155c76d46f0a40be0dc766cb8", + "packages/flow/openadapt_flow/decision/feature_model.py": "af7207d471e531377ea653448fd2487f1988a4e8c0b3c7c36d7983a369621077", + "packages/flow/openadapt_flow/decision/features.py": "cc5e21ecf0ca93982645791724fd41afcb9e77ed4c526cfa2ea7d3c6cee3931d", + "packages/flow/openadapt_flow/decision/labels.py": "924fa2fa32cdde259a2900d0c8af3c3f54b07c30bcbbb9f4af7289663a2889c2", + "packages/flow/openadapt_flow/decision/minimize.py": "9c0695866985ecda25a0c96d251d3358d15ab7ee57e66a3b4528e66f4df65ace", + "packages/flow/openadapt_flow/decision/provider.py": "3e14c3b013533925a62e67bdd9b82ddf99f969a2552914db1630498ee7f4bcbf", + "packages/flow/openadapt_flow/decision/review.py": "47822513196daa1cb3e827e3cb846763f41165738c3576c2df946f6135676098", + "packages/flow/openadapt_flow/decision/rules.py": "59a33e0bec5f19fc35621652c96c79ee6d6cc719854ff402853c9cf9c143b31c", + "packages/flow/openadapt_flow/decision/synthetic.py": "2ee2fc309a5342a5a3e050a47054b6b1730e21d8d20592956f7bc83f8e7389c6", + "packages/flow/openadapt_flow/decision/triage.py": "201235c57ce21e631c4e2f5990628f40ce3bae95e239f0bba29ce7738d95f96b", + "packages/flow/openadapt_flow/decision_delivery.py": "e3fd372d84ecb8a13fd811e115255362fae2f96e5b073e59d1d7f123c2bd85a8", + "packages/flow/openadapt_flow/demo_driver.py": "84976fda59fe901ab8b0e787bfccf46153f2794367e9655b6583a2ce485fff82", + "packages/flow/openadapt_flow/demo_proof.py": "dd9d11d97e4ed264593ecd33052910bea4784934ac575a5365d5cd4c8099f110", + "packages/flow/openadapt_flow/deployment.py": "f2e39de0c5bbfad89d6fe1f6f83c801ee5ffe8cb192ff40a03ba4d14690f3263", + "packages/flow/openadapt_flow/desktop_record.py": "a162df0e593b9f437bca86aa723be013836dd5e0c93acd5fcff7876ba16e3110", + "packages/flow/openadapt_flow/ed25519ph.py": "c8bb9e5d64adc44715e27c06f437e7801f6cac56aca1f15d8a577e629c649282", + "packages/flow/openadapt_flow/eligibility/__init__.py": "c4b10f081ff9c6d3ec3a3017d8a12aa7eb67b41488ae931000a8f84f08d9c54c", + "packages/flow/openadapt_flow/eligibility/artifact.py": "686eae18906a58b42fb6219fe6c4fa7b98638192db8169f0098601403ab274b9", + "packages/flow/openadapt_flow/eligibility/client.py": "9ddd6ca4512b64b8ad7ce7fad6c31d037209f3a19e9c52c605e9eb508c0f63e6", + "packages/flow/openadapt_flow/eligibility/waterfall.py": "15e15b5c8efed40cdeba907b74a279c217434ccf62a378f2ed4a05feba0d44ec", + "packages/flow/openadapt_flow/emit/__init__.py": "f9d028ad70f3eda13321fdab9d23845016781cb7360160d71eab82a1752419cb", + "packages/flow/openadapt_flow/emit/l1_artifact.py": "8e590b6013d1aedb6b722745d616bc394451c703642a1b7b0159359d94a4a868", + "packages/flow/openadapt_flow/emit/mcp_tool.py": "7852e44e2451103612a282b8fe3224a631c4b7261203418636949f82e80b4ba7", + "packages/flow/openadapt_flow/emit/skill.py": "43bd4116c93a662ef628a07a81c0ee2777351b6eb790ddc58b91151df672267e", + "packages/flow/openadapt_flow/ensure_record.py": "d009dbce1ea5e1802fd2ff1c4ab5e93ce9aa4cfec489f17af463220d13bdd1b5", + "packages/flow/openadapt_flow/execute/__init__.py": "769684783844a34810850275aba3647c303acc2cd6eced1d8c350c4a2e774d54", + "packages/flow/openadapt_flow/execute/app.py": "fde39916331f18c8ec84948cebce74b2e1c31d3c06cdaadcdb8225eda4df92d5", + "packages/flow/openadapt_flow/execute/dispatch.py": "9cea78d3955ca6ef30e7ebe74b8ad752e6bafcba0289f2db4a746342ba1bf2f9", + "packages/flow/openadapt_flow/execute/keys.py": "f43c622ef1df57efb3ed52db6aafc9d98d78100f2e25fff50dcc8b75489f9750", + "packages/flow/openadapt_flow/execute/models.py": "bfbac12c75f4fc52c0f9bf89652e876828f208c13a7827dab8bc741b112a5e6a", + "packages/flow/openadapt_flow/execute/registry.py": "d687121bec318dd4b56faa1db195a181501fde92263f7b851fb33607de903642", + "packages/flow/openadapt_flow/execute/service.py": "1453344697293a8b76ef7a5788930408b19e60981e8770e8680c852dd307af88", + "packages/flow/openadapt_flow/execution_profiles.py": "53b27ddb96edba887a6e8c1af231975f25bafc23b55dea191fbcd08db4956de5", + "packages/flow/openadapt_flow/failure_signals.py": "63090631a784bf6626859f3a2f7c95551b019d1baac79573255fb41c98f7d8c9", + "packages/flow/openadapt_flow/fault_gate.py": "e4db640d84d1310557d7bfc82e4a0849323a11a2757035ceb70dda354bb9a046", + "packages/flow/openadapt_flow/fleet/__init__.py": "3196cac4fbcf1ac5974955e8d9f4dbd0295b516ad5b758e1f67e4f6f545a95b7", + "packages/flow/openadapt_flow/fleet/_lock.py": "b877076335ab50d6b240e3b6b0f131d0c699ba706e4af738858320c3fbfe45a1", + "packages/flow/openadapt_flow/fleet/canary.py": "275a8acf693d566f17858a2656df9d1b6315becc6c9153149046902bede9c1db", + "packages/flow/openadapt_flow/fleet/export.py": "f3c4385716ceb71c0f2e93e4552a32aa22fc31561d0497f9b4c5f13f09c64937", + "packages/flow/openadapt_flow/fleet/feed.py": "f229589e292195b387f9c756fbd549eacede2cbaaac2a43d782ae89fc69a7a34", + "packages/flow/openadapt_flow/fleet/fingerprint.py": "f641b80642a5bdabf97e44b7f9796ab413c8c5e1ec64355c333ecf91ccda6e00", + "packages/flow/openadapt_flow/fleet/hub.py": "5605dd4b5cc620f400bf101c8a94b3fd386dcdbea91329ce9fd2dac087e12f8a", + "packages/flow/openadapt_flow/fleet/intake.py": "c6390b6814eff7f0dc424711a6502145baf13bc7ba32031de827f1458184b267", + "packages/flow/openadapt_flow/fleet/optin.py": "c6a02feef8c31dfda435fa7b5c9ae2313775979cf68ba0643fe1f284142d3eb5", + "packages/flow/openadapt_flow/fleet/patch.py": "50fbd5f28c25c5af1a74ab79534bfc45d80335bf50971d88937d6f48a8047caa", + "packages/flow/openadapt_flow/fleet/policy.py": "d9a6dfc09ace5a6446f421728f636d77ac40c4e8e3068685d54cf12a52091729", + "packages/flow/openadapt_flow/fleet/signing.py": "9761c1e080a04ddcbb0f54f75ca37fd74a1801399be32218e527ef1c5b71bc89", + "packages/flow/openadapt_flow/fleet/simulator.py": "5c6ef1603f7bb500e3845d85edb8c971c5515db45feecf934003cf0be0d75fdd", + "packages/flow/openadapt_flow/fleet/status.py": "2be462c6f00fda3b783e7825c12a5a031346b11e34c10c6465de75fa79c650a5", + "packages/flow/openadapt_flow/generate/__init__.py": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "packages/flow/openadapt_flow/generate/demo_analysis.py": "5d8b08a6b73c91a6a0fcdeda965f1086288fa728e0a47a11b373f2fa004cb74a", + "packages/flow/openadapt_flow/generate/ensure_contract.py": "b225841620cf86040065890a7585d2b0481529059546cd2f8bb087624d1edea9", + "packages/flow/openadapt_flow/generate/fhir_tables.py": "efe58cb353bb8e660c69773f4b057410165b74adf782d1a49e39e676a7a904ca", + "packages/flow/openadapt_flow/generate/sql_store.py": "f0fdc8a8ab7941a6e4ac8fd45195c2d652ab54fe73bc423a685c4cfcc088f474", + "packages/flow/openadapt_flow/generate/verify_contract.py": "5366153fd16a5c1e617295ac2a2c7e6e248892d83ce654f1c5841daca2b5c040", + "packages/flow/openadapt_flow/generate/zero_touch.py": "e5f8d39fd4ea2df9cc0e53993d6872280fd1a753f7631fa7adde864038acabf9", + "packages/flow/openadapt_flow/guard.py": "9a002384f9564d7bf379fbe855cabbb4484ff2c18a7ba5412502c818f11213cc", + "packages/flow/openadapt_flow/halt_cleanup.py": "c1e0e58b170519c683faf97593fb36e91523392f17a1726223d90b47738c3cae", + "packages/flow/openadapt_flow/hosted.py": "30e7d7517f638b89fad598f1cbf6c314024ebc7e43c86918fcc9d6d57e2555cd", + "packages/flow/openadapt_flow/identity_signals.py": "46e1392fe0fdc7473bc28bb25562f31a6dcc6c90f95d7900de255bc750bf7287", + "packages/flow/openadapt_flow/image_hash.py": "4dea419ca5617104d6072ebddf8141e8f9809227aaf833969e390eb3d5c7d73e", + "packages/flow/openadapt_flow/intake/__init__.py": "76815b0b2ecd679c7b076f28f1bb48c66a6be007f208fe8a6ab2746477b3ddc0", + "packages/flow/openadapt_flow/intake/cli.py": "332e14ae4367185c4cb51f3f704c4de63cfb277e22689972bfae7ad841971465", + "packages/flow/openadapt_flow/intake/document.py": "b71b232d18d509e336d9d950063b14c6dd703383319a25b845e08fa5731b6f32", + "packages/flow/openadapt_flow/intake/extract.py": "61a1db7580d8fcd47943337ece067980527a541520929a62e0de321c23fd6f37", + "packages/flow/openadapt_flow/integrations/__init__.py": "f963112195eca4e46543778de92d5e5ad6e3afffccd999fb94377f4c75199606", + "packages/flow/openadapt_flow/integrations/agents/__init__.py": "280e431a4b07bb961e3ff7ddd8f2e8225faaad139fe00d091eda18ddc77b2bdd", + "packages/flow/openadapt_flow/integrations/agents/claude_sdk.py": "c2b4fe00a75c8ea95652a29b438269608c2f5f3cfd7cdbe80538b3bc87570120", + "packages/flow/openadapt_flow/integrations/agents/crewai.py": "a93a5a2222c20e77e89161c3587cc9e90769739465e1045f2666e80da91b7718", + "packages/flow/openadapt_flow/integrations/agents/langgraph.py": "ffe07ab4063cf8d053d89d63586fe114babec53e75274f2b406179de845c90b4", + "packages/flow/openadapt_flow/integrations/agents/openai_agents.py": "0a5d6e04f3bbe6e568c9eda5b63f76f86343c686f6ab103e209b6d410605ffe8", + "packages/flow/openadapt_flow/integrations/agents/skills.py": "862d688e35a13ae69b6402c3feed6cf18ad98c637771f64ad5f4a9aa74e558f6", + "packages/flow/openadapt_flow/interactive_recorder.py": "69cb3d8a3c448e5e0749a17fda751ed3ada89093da7289df43c40fba008f495d", + "packages/flow/openadapt_flow/interchange/__init__.py": "b40b98268f7317e7539f123a6fca9b507d6492280d6f679525673493dbe24718", + "packages/flow/openadapt_flow/interchange/__main__.py": "8eba812ea2fcab0984b05def3fcc596cc4e523756ea3d8daecd23eb4a5c2cd1b", + "packages/flow/openadapt_flow/interchange/conformance.py": "50d7192c78d9b1fffad09fa37dc9abe7b33e84d66a395af955fa852912a99adb", + "packages/flow/openadapt_flow/interchange/l1.py": "438d2b59a856017cdc2f0256e6efd7e605c68ce54ecf9825282a34f09b24c7e3", + "packages/flow/openadapt_flow/interchange/model.py": "eedfe5e24508b82b24fabc05edf003fe3fc3c89e414d0436d6ca4c0d682c7444", + "packages/flow/openadapt_flow/interop/__init__.py": "fe8f034e12327da65230f887cd17fef5a21b8ec350a77d22fa0693f728f11d03", + "packages/flow/openadapt_flow/interop/business_decision.py": "264c35835b5d7e5693d140a0b6fc6515c719b73d42aba57bbb685f1dd1140743", + "packages/flow/openadapt_flow/interop/business_decision_cloud.py": "85b3a01a0642f28bdf2ec704b8b39bb8f81b5e16ab716791b56ea25990a890f2", + "packages/flow/openadapt_flow/interop/business_decision_supervisor.py": "d39d6bf9cfd1d4c84804b5565a99f0ac9560ac15e2838deee4c81426503b521b", + "packages/flow/openadapt_flow/interop/decision_relay_transport.py": "31a2f2ecea1d39f108747dbb81b123d2d7d1b0421fe75b97c6abac1d94652a51", + "packages/flow/openadapt_flow/interop/types.py": "1f2a45fcf456d31cf15534ab6d1417fd3c52612c3ab948bdb14f5a0b6f814945", + "packages/flow/openadapt_flow/ir.py": "509e9ef8666ec65496b4b7afdd0b2bc8af4ba0663e64a4079ce1813950bf028e", + "packages/flow/openadapt_flow/jit/__init__.py": "351ae6f83405a8dedb9585ea031ad068416a8595504edb21b39e623a67a1f489", + "packages/flow/openadapt_flow/jit/adapters.py": "c7af4267e8a1704e1390dee295a2ec3696ae9c932e506a2785023ac88a71c879", + "packages/flow/openadapt_flow/jit/authority.py": "421e369b815c16578f5aa54158065c8c0700105124a32b6e8a0aa0020ed1de60", + "packages/flow/openadapt_flow/jit/cli.py": "36374e4032c91969af3d39d56bfd3788cd3997f0c2e7aed4be09e240c92e223a", + "packages/flow/openadapt_flow/jit/compile_agent.py": "55a56073827e3a2ec2d0fb13beabd6a727edf304721e8b4ee974c5debe90e09e", + "packages/flow/openadapt_flow/jit/keys.py": "8d4b0674a351a69011e590ca9bbe5e09ccfd3c55868ff03db7584bbc6e2d530d", + "packages/flow/openadapt_flow/jit/lifecycle.py": "395ea2b8261a1b3b26bb9a7faded23780c482efdf55871ea47e1f931743e794f", + "packages/flow/openadapt_flow/jit/lift.py": "75a87c70262f626532568ad414e5ecbbcdddb43c8b5c5c605f637030640c4f07", + "packages/flow/openadapt_flow/jit/recorded.py": "3dac2027d3bc1d0e7028dc39f85e7c072c115656ef556d3e21e003f9cdb2a8aa", + "packages/flow/openadapt_flow/jit/signature.py": "b7f68b753c10c89fe57ef6912baf2e857637a5cdde5c23dfeb105f27c43c67e1", + "packages/flow/openadapt_flow/judgment_cases.py": "021f312ad721f06c6ee0f99cd2061d32c82eea7bfe6f87c4b182e36a4f445db9", + "packages/flow/openadapt_flow/learn.py": "9972f0c1edd58180aafa44793ae1152b3d53fd0fe74604cb9efb2fe3836f4e8a", + "packages/flow/openadapt_flow/learning/__init__.py": "fb5b8f2f0b59009c55662f56ed7957e94859feeace15c2e3c671998447cb9de6", + "packages/flow/openadapt_flow/learning/branch_teach.py": "60b03c5a0f0f28bc21ae4bd21377735a89bf166e17519e7cc949ffc2260cc171", + "packages/flow/openadapt_flow/learning/clustering.py": "50173d350b1c936e2f667db1612dae7721df7a81b40c07eef11abf10c9c0f1c4", + "packages/flow/openadapt_flow/learning/gate.py": "f34fd9f3ec74d7a5cb7f111be658db9debd032ceaa12d9254e2adc25f0becbf5", + "packages/flow/openadapt_flow/learning/halt_loop.py": "c39cc99409dd9b1631086f3d4f09b7997d3f6bda17861871169599f982d2e76f", + "packages/flow/openadapt_flow/learning/induce.py": "57a7018ddc2399c4810cc4f1b371ccf7ef128b1779c731f5e13df13f7ab873a5", + "packages/flow/openadapt_flow/learning/interpreter.py": "606d336216a899b11651c2e4eb341748d8cc91a10508bd33a63c7d50a44318aa", + "packages/flow/openadapt_flow/learning/library.py": "ec7f5ac69c5a75c081a1c5c0089c3e0f77c8fcc630b5ab7e2aeae0cb38221740", + "packages/flow/openadapt_flow/learning/loop.py": "f3e5c1127b4fa21f3e112516670edce15daf05322c554642b8057bf268adb7cb", + "packages/flow/openadapt_flow/learning/teach.py": "bb64ee5a7bbd14cb872a3f82fb2de4601d5b0d7f7e1a45c1e205f6d23dfcbde5", + "packages/flow/openadapt_flow/learning/trace.py": "323bc17cfa3fc3876ac030abda028c73e8af02d5e30b2cbe9c9b9faa4639c524", + "packages/flow/openadapt_flow/lookup_capabilities.py": "6f8068a05f005e0eac994d14d0ca3e6b1ce7a4e532471bf4b8d9dc15c0fe1b4a", + "packages/flow/openadapt_flow/mockloan/__init__.py": "06beb30be301dde52a522064ed192864a4c2bc1f167ebb5800c173eaa72f4735", + "packages/flow/openadapt_flow/mockloan/fault_server.py": "0f6f6e4ca4622c5674dca6e49b9dc67360f34b9071ba1346727683aed16682ca", + "packages/flow/openadapt_flow/mockloan/server.py": "6d81bda0dd715a71e54306c92d739b49ca3b3a75fac65ee7a70a3d18027ecaa7", + "packages/flow/openadapt_flow/mockmed/__init__.py": "223bb05087540a7e0e90f72b83a5dd3634318b95dc0a6874cbc82fd38ce5ee9b", + "packages/flow/openadapt_flow/mockmed/chart_pages.py": "38db2fb4d34d14d8745c7c9b31a6c729a4e0afc545333c164296fde5044bf395", + "packages/flow/openadapt_flow/mockmed/fault_server.py": "acdd45ef82111d50e1ebbd242d55ad14094f0a0c457c43049855be5efa8ecb70", + "packages/flow/openadapt_flow/mockmed/server.py": "9fbeaf2b0c873dcdce478b4f54990b954844c057a0760ab763705e6aa3f202ab", + "packages/flow/openadapt_flow/narration.py": "9e23870f1f9c34aa9f3d4ed65706129e6c77e9cdd3ade8f2d0940383e2fcae8b", + "packages/flow/openadapt_flow/network_capture.py": "4b3a27d297cf109dfe375c78621f4438c02694408ea57414a6f5237b739d7df2", + "packages/flow/openadapt_flow/network_scope.py": "c64d2288c98fde49c437a0a44b53af4a4b1884a8130b99bee098c2046e249033", + "packages/flow/openadapt_flow/no_outbound.py": "220f5ee084a4c7948f331cf08b064bf7291bd5a45c575d234fd43697121c5081", + "packages/flow/openadapt_flow/no_outbound_allow.py": "b60aa14551bd39ed333389290ae4a9069863b370532e1016d74441a8dde5dd2a", + "packages/flow/openadapt_flow/offline_seal.py": "cc7b0a2dd8d946e6cae740af7d7fb250253a58276c11de4f8a9122b91819d54b", + "packages/flow/openadapt_flow/onprem_reboot.py": "5897eda697e7111ef3ad944c7e397b64ed39abdb225085f9eea0f47b6630879e", + "packages/flow/openadapt_flow/onprem_service.py": "efa4da8f836f2e88d88e3353324ce810066c945cb62cd21e6bde5d8d0aa06aed", + "packages/flow/openadapt_flow/operator_profile.py": "88e013511d1a7fcb7b8e46841c6d7291433a631cb3baec349da42e7a300c5934", + "packages/flow/openadapt_flow/pf_firewall.py": "adff9c485d303b5b07efa910c45d39762e12285c843f8cd782ffecac5631e0de", + "packages/flow/openadapt_flow/plain_outcome.py": "1a74681faaf317377af006d37557e114f0ba7f5bb561a33331565da0addb4de2", + "packages/flow/openadapt_flow/policy.py": "730c8a74c8c1d2e65454407665b62517a48eecb7bcebc9bfb7b6ca3f8fb25079", + "packages/flow/openadapt_flow/policy_packs.py": "8df13eaf8e7e4ac7492c2f8c25063ce387833174f3c82dfec3f8c68de4bbaaf7", + "packages/flow/openadapt_flow/privacy.py": "d999942cfc463c8d0501a5c5c5ed987c1312a7ec16be9f97b7c527f23b8ae0c1", + "packages/flow/openadapt_flow/private_file.py": "eae5ffdae6a76c57bc999278baae8ad0bbabfe87813818777e825beddc22f135", + "packages/flow/openadapt_flow/production_qualification.py": "c5c9911c7fcaa12bbd513fe737c4c17a3bd64522b98100880c1a05fddfff3aef", + "packages/flow/openadapt_flow/provenance.py": "0865fd3f0362eb6a7b4ced98070b5e399b91e1530288b9a002bc933c86d90879", + "packages/flow/openadapt_flow/qualification.py": "ca86f0249c0a9c6abc92c67a8df03d4a94dbb90055b8bad7b4de4dd6625c6ee8", + "packages/flow/openadapt_flow/qualification_admission.py": "73a43db18fd383fde7c5e11823ace50046e0784e662b56c76b82289f61616cf0", + "packages/flow/openadapt_flow/qualification_admission_v2.py": "10d8241a38b24767953e6b98986c838773a02399652924a53e53c7ec7c7c5e56", + "packages/flow/openadapt_flow/qualification_admission_v4.py": "5a11934e42fdea85c7ff792ab6d33754b974a719783154e8e6144e9a90db1292", + "packages/flow/openadapt_flow/qualification_campaign_authority.py": "cea191889f3b3f8da5c078e0cb020e7e0ebe9c4100035f5f1bb2b17ace75c3d7", + "packages/flow/openadapt_flow/qualification_campaign_permit.py": "1468882d9627f0355673873cd493c2709ab7cec2506dac3b1efa32d1c16f0637", + "packages/flow/openadapt_flow/qualification_dev_signer.py": "db70cdacae8235acc73f13a3281e667a7a5e502c246151854db4dd921caf8643", + "packages/flow/openadapt_flow/qualification_drills.py": "6fecf639ee1db59b8f15f857f01765d2e5b4dd116da44f664aef0350fc715173", + "packages/flow/openadapt_flow/qualification_environment.py": "842f5dd827750f55d9e1046613a804fa6b27c2a41981d4d7fbd6b04d9acc8131", + "packages/flow/openadapt_flow/qualification_faults.py": "b4e437aae822c648fca54f8a57b722ca9c940d56f6a906a52292877ba16cc9ff", + "packages/flow/openadapt_flow/qualification_identity_evidence.py": "7b94bde4ec7e2d1eb9522bd456aec407ef7d1063f5cc7518a21b40be1464bdb4", + "packages/flow/openadapt_flow/qualification_oracle_gate.py": "75a6c16a7a314ac92b05a457302076e5b79478fc737907e1dd1854a1a77c2243", + "packages/flow/openadapt_flow/qualification_proposal.py": "1c13d44a189dd798244dbd69186aa78856d304e0b21213d5262450bb9c3861a6", + "packages/flow/openadapt_flow/qualification_web_session.py": "3766e5b5f03a92f91a5f51988b1764542b74aeaf634d72e18c9a9b1b6ffc4fdd", + "packages/flow/openadapt_flow/qualified_business_decisions.py": "9b56b3d0923440b589e6ff010dda1cab88c893aa01e92861e796e6cae2c83c34", + "packages/flow/openadapt_flow/qualify/__init__.py": "a3d7b8e30c06fb77be32b75996ce1f3ea8f96264e74edef2646f122ebbbdc1f6", + "packages/flow/openadapt_flow/qualify/campaign.py": "3634f6bb501b83c09325dffa63463b52b85eb6684518c8c47c1179157a8ccc62", + "packages/flow/openadapt_flow/qualify/config.py": "04d6e12d00141f1c0c4a6825956ad6d7cd4c5fc108d3d7732118fe2d6acce086", + "packages/flow/openadapt_flow/qualify/environment.py": "be85934985a000c6dfef8d1f35f3d790c1c4974a14bea893d58386e533ec45da", + "packages/flow/openadapt_flow/qualify/faults.py": "4a78131403af4aff905bdfaf1056c332f890acee1b991283f3c7e2424a9539a5", + "packages/flow/openadapt_flow/qualify/keys.py": "fb2e94913ad0db0fcc697943faaeef3f3391a83fe045af703d249b9344afc556", + "packages/flow/openadapt_flow/qualify/result.py": "018ccf0d1c6fbc6c91621bd76b3124d5cf09cc4f92edd4f67f2bbff3a1f81b09", + "packages/flow/openadapt_flow/qualify/session.py": "b8f367480033e63af4706398ce062c08b56ff02cbf99a844f78c3c3d9fcee920", + "packages/flow/openadapt_flow/readback_ladder.py": "7706e14c0531e31677ccaffeefc5e78734ba2434b1ba43360d7144ce44d08ea2", + "packages/flow/openadapt_flow/receipt.py": "b90c51a048182475d49db793b00afe5396ac03933042d2ca74b5661375957755", + "packages/flow/openadapt_flow/receipt_log.py": "c6b31acf2139ac803389a8dd0d7a1769e2039ff9a51069e0fd02e2e8c8520562", + "packages/flow/openadapt_flow/record_check_binding.py": "ff608c3361e92f9f10c863c1ad17a06fd737657b978d6b966e223fa75c1f9b06", + "packages/flow/openadapt_flow/record_check_identity.py": "2538298c5343919a80fd5bfe3eb6db0143faaf833f05a1f132c38ab1145c4864", + "packages/flow/openadapt_flow/record_check_proof.py": "26104f4de1283d01dd6646768ed50ef624bb143293cb30c62e6427efec9a2111", + "packages/flow/openadapt_flow/record_check_synthesis.py": "c71614f451dc2e50332bbf45c4463ba4cb6570fc25fef7491c42e21f38648fdf", + "packages/flow/openadapt_flow/record_sor.py": "101a1235c46891da5d0a07bad2c1ba72c4960b7685af772961e4b1e7a629ee34", + "packages/flow/openadapt_flow/recorder.py": "ed1e659886a6d9d9e3975754133e7bfe9b07cfced75b8c312598eba332d605a9", + "packages/flow/openadapt_flow/records/__init__.py": "5d05312df045601b1b65183587135e6f7ba908cc3d880b18a4b74487a8d06a45", + "packages/flow/openadapt_flow/records/export.py": "644bbae324cf146e24f2c97e8ba1c959ebfacfd33b58056e6819909b9dfd4abf", + "packages/flow/openadapt_flow/records/read.py": "ac88614c9cd3524cb29ba8c8c44725e4dd6ab24afc8aca9479d875ee2aa0ee29", + "packages/flow/openadapt_flow/records/review.py": "de530c3d3617e40d0c0254c9f04d6b70ee453877a130cfb4dfc27348704e1945", + "packages/flow/openadapt_flow/records/run.py": "a9ec68f5ca19b2396aea6e9d9e0198cdb5e63b63fbc0fe2854773e9a7888c6b2", + "packages/flow/openadapt_flow/records/schema.py": "3d9d3b4fc1fcb3b4835e6dced9ef49ce08e5c2e61448aa4e1859e56f09ae7b67", + "packages/flow/openadapt_flow/registry.py": "6203135d2768776f95a8a5d46765aa76cc2e16904b46b06ba83852d18416d215", + "packages/flow/openadapt_flow/registry_attest.py": "8a8375762ac6bc2a2f717359c1002d220295997f01c012b5c42b7dcd09709676", + "packages/flow/openadapt_flow/release_admission.py": "5afdf674f30073b10ed5b4ee06fd3818db26e74cc935c649548256cb60160f7a", + "packages/flow/openadapt_flow/remote_frame_contract.py": "745c8e9a6731ea89b722092183f760527d9e6f329a5a7ef61152870114e77406", + "packages/flow/openadapt_flow/repair/__init__.py": "db1e673372764eb9ba76be658980a94b9747a0d716b5ec1a8d5f74d69ca909ee", + "packages/flow/openadapt_flow/repair/agent_teach.py": "ec9995ba2d3ccaee0ee623e77cd30f0d30a565235befcfbb003fb81d4b7f2920", + "packages/flow/openadapt_flow/repair/auto_repair.py": "dd4270718c906eef6d89939d5bfdc5ae63d842ab78bdc55f45b5afd425507c79", + "packages/flow/openadapt_flow/repair/campaign.py": "520e08184f5abe0bb53a4e08eb2d314e89ad3cc0636069573ca26bdfa82f20db", + "packages/flow/openadapt_flow/repair/candidate.py": "84c8a1da1d052c33ef4ade3e441644f108abbe0fea9834413bdddcbd450f7d5f", + "packages/flow/openadapt_flow/repair/cli.py": "b600b9920a3dc6e993f96c38d4914fc44458a9b9bcdfee807f58660a41b342dc", + "packages/flow/openadapt_flow/repair/cloud_review.py": "eae37867caa636115ae63adb6018987c322bfceb78a25c4178f4287bd13bc6d9", + "packages/flow/openadapt_flow/repair/dialog_fixture.py": "7969fe9c3e4373e32873a237867e182f8c2933397e2c47614216c5ced59a4e9f", + "packages/flow/openadapt_flow/repair/dialog_identity.py": "77e828471626051c74e502c29bce3ec2356926b38b6ddd83489b7bdc3fff3dd8", + "packages/flow/openadapt_flow/repair/element_identity.py": "337dc232e727b7388221c042e0eb2ddd9b7b11b3de73b1c3e88c6f80f23c16ed", + "packages/flow/openadapt_flow/repair/hold.py": "9b34c9d1bb9fb12d4077d18bf19e9e389ca98ae0e2ea8faecc40e2da267d11ec", + "packages/flow/openadapt_flow/repair/invariants.py": "c645ffc37b7135018e0783955b77a067e719e99765d70ccfc26812b3e0336d45", + "packages/flow/openadapt_flow/repair/lifecycle.py": "98758330dd9cfbf8b53d39071848b6319f2ae3b328a914be84ae44873abd64df", + "packages/flow/openadapt_flow/repair/preflight_queue.py": "9ca1d23b744218745a660fd3b57233f41366c72c9603673347326854cca49b5d", + "packages/flow/openadapt_flow/repair/readmission.py": "39a35fcc0a2c804b2465054aa0a8803a6b934d2abb8427fc7f79bd5c59629630", + "packages/flow/openadapt_flow/repair/registration.py": "0d2166fae57ee43aec1361645f73b9472cf709c5ba63749f51cca6bd3f84ed24", + "packages/flow/openadapt_flow/repair/relabel.py": "a1b870b14175f073153fbf9b80733d7fd34ac766921a2924e292f4e86a96921f", + "packages/flow/openadapt_flow/repair/self_repair.py": "771ad3af601bf3385865b1335202194dbd8c300c3fac3d3e5cc82ecd7895f62f", + "packages/flow/openadapt_flow/repair/standard_change.py": "b67523f9d78a4a544a90fb79694e321a90601e9885ade8f302f09d39d96ff741", + "packages/flow/openadapt_flow/report.py": "288b62601328e4630f2db0f1a7af48b3fcf9db36340c370c51628dfe889ca373", + "packages/flow/openadapt_flow/report_redaction.py": "fb5f25259dbe76c42e2e541749a9db941c43f536b0c59b92221791afd915b6a2", + "packages/flow/openadapt_flow/reports/__init__.py": "51b2ae3769609912e56518619972b551128a0ae9bd2af0e7661ed32629ce16bc", + "packages/flow/openadapt_flow/reports/parse.py": "5732213e6b7290ad8339bd9559d2e25394fe631c0ee289a5bf34d32991f79817", + "packages/flow/openadapt_flow/reward/__init__.py": "a16f606b5333fa2a0732ba0a3ae643125e6831f8c923fd17940194fab25eff76", + "packages/flow/openadapt_flow/reward/bundle.py": "46e2a272f7592534e6f27945eeda2a1fdad27da6a309d035913eb1ba5e1bfa1b", + "packages/flow/openadapt_flow/reward/calibration.py": "5d54500f75cbd442e5194cde11674099bf3428d72611d8e8aea2a49e1c0a8a61", + "packages/flow/openadapt_flow/reward/callables.py": "a5736681e02b8862da15a81c95be78112d2d61741177f3895ae05941e5c4b913", + "packages/flow/openadapt_flow/reward/models.py": "343c3bfb3158e05325f364c063b1198c47c8725a39151779c42d3a7242d600ac", + "packages/flow/openadapt_flow/reward/oracles.py": "55c5e0c667f0777e0599a2330355a655dd9c77da0dc4b65282f56d30d01515c2", + "packages/flow/openadapt_flow/reward/seed.py": "1024757f781c9dd06d969138a098651aa57c6a9fbab11b07831e22f47911687f", + "packages/flow/openadapt_flow/reward/serve.py": "c8bf963939d991c1ad55ca7ac4ba50d3d632f70693793b92a2fc07fbb003e5da", + "packages/flow/openadapt_flow/reward/worker.py": "9986fb2fd3280056fed914170d7bf5ed6a5ad45d51a30ab2be87e9c8963e520a", + "packages/flow/openadapt_flow/risk.py": "f63459a5a55b24b1890edfee22e575d1ec6e1cbdb5f4a430ad27d009902472ec", + "packages/flow/openadapt_flow/rl_env/__init__.py": "2fe82b5386d41a7907880a929b9c025db5a14620a555322e0a81f16dc0f5f243", + "packages/flow/openadapt_flow/rl_env/agents.py": "8b36ed11d8bb28a1cc443695925142a3a4ec4f6971d2637c6c60d89e4df19825", + "packages/flow/openadapt_flow/rl_env/desktop.py": "75262271ae87ced93ce898a16498492a66985ed80f891800962b8021f2066948", + "packages/flow/openadapt_flow/rl_env/environment.py": "532db6c47d41d5007c0300801930577ea56de3a6335707dedd1db1195edbebbf", + "packages/flow/openadapt_flow/rl_env/parallel.py": "13095c72ed367279dccb6360f945ee1b38d13f57c6b464e840913c263fa5e874", + "packages/flow/openadapt_flow/rl_env/reward.py": "59796f51df0cf0f85bf896c03edd4f5457d3af4a78359d6691d6d1ab14ae14f6", + "packages/flow/openadapt_flow/rl_env/server.py": "469f84e5a0adfbd5e08f20a39d81a142afad67408135f3482dab23758bee5013", + "packages/flow/openadapt_flow/rl_env/tasks.py": "cc075797af232227ed5fcf434d5c8f0da3e1f42705cc1648eaacd29907202582", + "packages/flow/openadapt_flow/rl_env/typed.py": "b468b29f6c2ba64b23b7f9641b804637245c65ae1f9146360b87ce6b6b43521a", + "packages/flow/openadapt_flow/rl_env/validate.py": "fda4f484e1bbd8d6cd05df6f89c1f56a734513448f07079357387300603f8603", + "packages/flow/openadapt_flow/rl_env/validate_desktop.py": "2419d37924fe9b502f718fa49681a5b69a4d871a820c27d03fd2e7c7eb9238bd", + "packages/flow/openadapt_flow/rl_env/windows_guest.py": "106ba1189718296ef594f15207815191feee4e07225533b49daab187bafe9200", + "packages/flow/openadapt_flow/rollback.py": "1ed498b261add28c5faabb9bd75d8a3c1f114bbd69931bcfa46d0e4fb18aac98", + "packages/flow/openadapt_flow/rollback_durable.py": "88302d9158fce87884be891cca59db21a4b2ae303fee93dad3f45fe37cdb4677", + "packages/flow/openadapt_flow/rollback_qualify.py": "81d47df7b8f669d029833772365f6ef507aaa95f8a7bdfcb0c2cd1765352ef85", + "packages/flow/openadapt_flow/rollback_state.py": "a59a85a4101d5188c400e34d716859c7031db12b0d861a5649a6c2c7943131ec", + "packages/flow/openadapt_flow/run_authorization.py": "eef848a71340e8951d80f421872d82ffc79098025fe9e9d4d3d2aecb50d93464", + "packages/flow/openadapt_flow/run_gate.py": "2297a7c84efe4b10788808d2e0fdd189d6be3a5a20dfb33be338365fad7306cf", + "packages/flow/openadapt_flow/runner/__init__.py": "1111900db86ee0f61faa093996a6e64a1405c9052bb74f186afa1a84a60ffcc0", + "packages/flow/openadapt_flow/runner/business_decision_service.py": "97bab36f3c9dbc9974129a34a5c636dba08e7fbf3c56fc522ed507feae216bd4", + "packages/flow/openadapt_flow/runner/commands.py": "92a62d244b406c405cdfd51c3189a835878f0e334fc74e16e98a1acfbca47ba3", + "packages/flow/openadapt_flow/runner/config.py": "ba84ed0b0638ee12de51f40fe3fa4da13c14effafbbba7bceab256e56664fc2d", + "packages/flow/openadapt_flow/runner/dispatch_envelope.py": "4a867de3740e46b67ecf90b74120f53ad038d7c0aae9b17c6bcefef998ec674d", + "packages/flow/openadapt_flow/runner/evidence.py": "4f884203b69f3368b56e7bd0a31df123a28f5c7b464fa73a69b1da742fd7fe76", + "packages/flow/openadapt_flow/runner/flow_release_receipt.py": "1e80b5d956de0debc12e5a14f4595256eb13caba16d387e8193ad1737be4ae18", + "packages/flow/openadapt_flow/runner/hosted_adapter.py": "0e2f0e5e96a609dac3e78e516ceb8e6a0050f6406a8a6e4e74f93381781e3eb2", + "packages/flow/openadapt_flow/runner/inputs.py": "711464bd021afbde06b9c4d132ea782c3b6a321ea48bcb24c3f565f8609f8a9d", + "packages/flow/openadapt_flow/runner/lease.py": "9ba80da689f4eeec85d44a860a27851e2408e3772b135bd01e4c4a0996d77c60", + "packages/flow/openadapt_flow/runner/outbox.py": "19454f29bb30bd8707f452a00e6fcb3511a1fce1b70ee2401d5d40d2e11e496e", + "packages/flow/openadapt_flow/runner/product_release.py": "072ad1a818fd1ccd7bfbe2995ee90825111890dfb4c1bd9b72275350107adab3", + "packages/flow/openadapt_flow/runner/protocol.py": "02ab8b7ce4a936d8533cf99a58f68fb5281b2afce2f17b99dbba27b0eeaf7e82", + "packages/flow/openadapt_flow/runner/verify.py": "473bab048ac39adfd9020816e81ea969c05856993e4029e17ce9bc9ba237d92c", + "packages/flow/openadapt_flow/runtime/__init__.py": "fa78a3356ae6470aaab3c3093c57502ac8aef9b1ecb03ec2f4ccbc2a0c0b9f0b", + "packages/flow/openadapt_flow/runtime/actuators/__init__.py": "597b6f56b00bb79c7f3a4452f602d1746aa0acc99bebc1683ce71ceece5af59d", + "packages/flow/openadapt_flow/runtime/actuators/api.py": "05fb4e35a9cbb2c85874bdc3d5df1aeaf4f5ba1e011ec0e4ae8b02aaaab78a13", + "packages/flow/openadapt_flow/runtime/actuators/form_eval.py": "39ade168ee3029b501cc4eecc6f33d6b23a3b48c51b3f3f9bb3c9391422f495a", + "packages/flow/openadapt_flow/runtime/actuators/web_form.py": "5f69d401eefaec75f1a8042f5178462ac1cbb468cc787c2549c7ffd956cfee6f", + "packages/flow/openadapt_flow/runtime/admitted_composition.py": "cbe722002872533c37797183d6efd3b42a5e65f95759e0e8d28a5dbc1794ec07", + "packages/flow/openadapt_flow/runtime/authorization.py": "18876e85bbdede3aa6bd98068cff49170a908f10d2374a755e7b0986d039f461", + "packages/flow/openadapt_flow/runtime/composition.py": "888011575a90153a22a474e0316f8eb41d76e2424a020f235557da80deb6e9e6", + "packages/flow/openadapt_flow/runtime/control_overlay.py": "b141f20afa76036f7ab20ae19d97293f7dc0ee20b0aea2c7d23061dca4bb3aa1", + "packages/flow/openadapt_flow/runtime/durable/__init__.py": "32d5846e0775819d84e903485f1c32a14774d2ee3d4f3322335182afd68f8826", + "packages/flow/openadapt_flow/runtime/durable/approval.py": "2b3e5eb95ae02d11dfe75d3970cbb65f8b04cf8c7f066ed8b8aa1bba3da034ba", + "packages/flow/openadapt_flow/runtime/durable/attended.py": "061f871aa82088506da046d2705ada74fe7a246772df8d20e56518c41d674441", + "packages/flow/openadapt_flow/runtime/durable/attended_service.py": "d621f4785ec37afa9baf1ab18622745cae498eda0f91d903a7e3b8ffd54f2426", + "packages/flow/openadapt_flow/runtime/durable/attended_state.py": "6ee0b25ef54e88338b3e580283fa8fdcf820fd016451a822969498d8803c8d96", + "packages/flow/openadapt_flow/runtime/durable/authority.py": "57f2312f27e7bafe09513807e76298d29876aec32e03f74d82bededbcb600dd8", + "packages/flow/openadapt_flow/runtime/durable/business_decision.py": "dff9bb63163561045318f2416420e9d96c1c69fc6f1f31eaa00e50fc41083e63", + "packages/flow/openadapt_flow/runtime/durable/checkpoint.py": "c00e83092e95e632a1773185498d409f9d31a0e71762f66f63ef24d185661e2b", + "packages/flow/openadapt_flow/runtime/durable/continuation.py": "5eb5319b3b5a1a52c73d6472418aeefbf961fff41743412a9c82afde9df9b542", + "packages/flow/openadapt_flow/runtime/durable/controller.py": "f6644d8c4c657e7d847cca846f6d89722fe8ffc2a19ee427cebab6ce9b60ec21", + "packages/flow/openadapt_flow/runtime/durable/program_checkpoint.py": "1945df32ff1d418c5eac7f9f02fe29297da315d6e07e395cb706312ab1d2cf53", + "packages/flow/openadapt_flow/runtime/durable/resume.py": "1d1f3939714fc7f2b74aa3ee1cafb56db182db1c25d8589d07ba5f4ff483f1b3", + "packages/flow/openadapt_flow/runtime/effects/__init__.py": "26ee73ae1d836050ff796e5211f649d89a416ba171a9aa7449ff8079795cfd6d", + "packages/flow/openadapt_flow/runtime/effects/_common.py": "fe80ff93215a0f8126e0aab4a39c1eb4cf67a6352efb7ce33d76a0be0fe9a878", + "packages/flow/openadapt_flow/runtime/effects/adapter.py": "6b44cda61a78ce3934db28a6b2ce1ccadac5a52a99f8bbc225346f602116b5a4", + "packages/flow/openadapt_flow/runtime/effects/auth.py": "af0c2be6ab49f998aaf404800a0f5c8a3a35f30f08dda609cafa8b49ea622f91", + "packages/flow/openadapt_flow/runtime/effects/compensation.py": "d162cf54bd6b7cd8759486a8876efd072e29146d81a0c14d9e76770a61783293", + "packages/flow/openadapt_flow/runtime/effects/db_snapshot.py": "8bf04887ad611146c26dfdf094ce47552b0b158ed6eb0220ffe1fb455c69928e", + "packages/flow/openadapt_flow/runtime/effects/deferred_report.py": "f0e94087724acedad688e46c6a6375b1e6c24aa42abeafb10682eee71d9b9526", + "packages/flow/openadapt_flow/runtime/effects/desktop_readback.py": "6ac9ab08c1a4f385c34475fc90f9368a8730fa9b8e59b22c2d8b067dad46be61", + "packages/flow/openadapt_flow/runtime/effects/desktop_readback_sessions.py": "85cf7364d4aaa4fdc6673610c4ff387e602a349ff5082acda446f048a82fbf92", + "packages/flow/openadapt_flow/runtime/effects/document_arrival.py": "1f62bb1c46f0f9521610a152fe68340a96014bc4a8d293d6681ef5099acc1307", + "packages/flow/openadapt_flow/runtime/effects/document_hash.py": "5c64f1ffc187cd03114d61dff3518080e59b817f8b1c3424dbc4b57db30e1f04", + "packages/flow/openadapt_flow/runtime/effects/effect.py": "9fe46e7533979621a7c3c08c35a7665e4846e59424cfc69c8cb62d7724c0a814", + "packages/flow/openadapt_flow/runtime/effects/email_delivery.py": "bca21113e2c8fc3f3820fd1c0e27c16f2c5db920ccdd05275477bd08884b6ce2", + "packages/flow/openadapt_flow/runtime/effects/fhir.py": "20590b70e9dfde931f560695d49490ff10318f87439877657e793c1df89ec9c7", + "packages/flow/openadapt_flow/runtime/effects/file_arrival.py": "8a5dcb94534e9f3ef40fa820cc18ae73e45f626137f0de856941c3adcfc235e5", + "packages/flow/openadapt_flow/runtime/effects/frame.py": "e984cee555c61e01e5e2c6c2033d4513859d4afc5927fcc72ae876cc9a545fe2", + "packages/flow/openadapt_flow/runtime/effects/fresh_session.py": "0603645b9f18b5d7a7fd6eec50387b7f1a6aa07d44dd36993afc89a78353dee7", + "packages/flow/openadapt_flow/runtime/effects/graphql.py": "cebae9844af5046963fdaba3abaed3ed1f9b1084dedd1a8948ac454b64990e26", + "packages/flow/openadapt_flow/runtime/effects/late_write.py": "a158374ccba1861df4ee23bc1330b57d141df64cf5c5b83c0b9410a5c570e577", + "packages/flow/openadapt_flow/runtime/effects/learned.py": "e1a2de2f30f12adb12db2888b6be5a5a993ab73ea3c7b86ddfb3bc46e191c833", + "packages/flow/openadapt_flow/runtime/effects/onscreen.py": "a9767dfca331d425e7f9b60a147305087cfc53499754ea4cc866c06307dd6a17", + "packages/flow/openadapt_flow/runtime/effects/readback_values.py": "783239fefb4bcbbe76d195de4a94eb4cd246f8e9e6f4117aa8c78dbf78ec0b5d", + "packages/flow/openadapt_flow/runtime/effects/rest.py": "6e1d80a9cad6c50bed7d42241db2aaa8e9e929ba37cd89d2335e07c7ae7f3add", + "packages/flow/openadapt_flow/runtime/effects/sealed.py": "d763c49a9e4382c4160e33e0f8e6de2e1c86b59ea9cccd9b41a7328264c6f141", + "packages/flow/openadapt_flow/runtime/effects/smart_auth.py": "57dc86cbd4249d528148d6c47a0bb5eec56d9c2e854ede8e0a97a7dbe8cda15d", + "packages/flow/openadapt_flow/runtime/effects/sql.py": "42210633b40a3dcc0f9ec6742c10ddb6656db2a1e2c90b9c92281488ab4ad330", + "packages/flow/openadapt_flow/runtime/effects/surfaces.py": "5841fc1d7e97dd481964e354df5185136aa8759cd90c94d5ef2e71a21cec423d", + "packages/flow/openadapt_flow/runtime/effects/ui_readback.py": "94a7291c95e672d8611949a4d923a26061d5b938a74d706159644b23ce7eb476", + "packages/flow/openadapt_flow/runtime/effects/versions.py": "970e6ed27c5246ffe082cf1e68a9e86a3e677152c19247ebcefe286c33fa55c0", + "packages/flow/openadapt_flow/runtime/grounder.py": "75e90667e536b4fd10612a29fe19b183da72bf8ef08f8939c59e1425b1339e4f", + "packages/flow/openadapt_flow/runtime/heal.py": "edda48b9a0e0d38ff4b639d2a3f1d986d3b7b24671e1c20f064366ce96021f44", + "packages/flow/openadapt_flow/runtime/healing/__init__.py": "7f2b58eb0e8d53394d9764066378e228f4dcbd8fd48b143b3497c11167b6ad80", + "packages/flow/openadapt_flow/runtime/healing/governance.py": "f3a755d46bbd7eedbe6b0ebc962cc2073714551eee34c7f5f2faf002623d3222", + "packages/flow/openadapt_flow/runtime/healing/patch.py": "2a918bfbe9523c7946085581465bc6fae5735ba9d9e46e2a0a04e4a841fdadb7", + "packages/flow/openadapt_flow/runtime/healing/perturbation.py": "18159ae5a273b33b90ee677164e44fe794bff4c8cfa96a9d3d0f9f8d12166f18", + "packages/flow/openadapt_flow/runtime/healing/pipeline.py": "d31fe159c6a9d029d0f9c7ec6d9b71cd006a51e71945bd4bd493b405075beef6", + "packages/flow/openadapt_flow/runtime/hover_evidence.py": "bafe2cb6ff8ee205a510cca79bf1324a316d0ebc5728e5b9e8581290fcdd0790", + "packages/flow/openadapt_flow/runtime/identity.py": "5da03135b8dc171882cfe6f40d3256baaf9729d8cb210e1463af9fdb7fed6b0f", + "packages/flow/openadapt_flow/runtime/identity_census.py": "690cd1dc5083b53d18ef5d8d7d768dde12b0c4a87dafe769f7883eff3f903be3", + "packages/flow/openadapt_flow/runtime/identity_looks.py": "3a80471cb4b3c71f5d1a6e0be455ad7bff3d05c19a3be278233672b5c4674eaf", + "packages/flow/openadapt_flow/runtime/identity_template.py": "9d9303cb49a194f5530d4daf7ade24fc9839efe01fe0a5bc44456aa7e9024b8f", + "packages/flow/openadapt_flow/runtime/identity_vlm.py": "be1151e8bdb3cafe428b5dd25579d6c5d7ef7b47c18ff3f9687d56f94a229428", + "packages/flow/openadapt_flow/runtime/known_ids.py": "42e719c1f2618431a5a722b50e6d832873f059534e22922fe57f6454c543ead1", + "packages/flow/openadapt_flow/runtime/layout_drift.py": "b385273433c782841482c331ceb3e1819d5a9362a1acdbc168d1da395f9ad56a", + "packages/flow/openadapt_flow/runtime/list_guard.py": "9471aa4b414fd795a156726758ffbb12998a8d7b40766c005c7475a29db495dd", + "packages/flow/openadapt_flow/runtime/list_source.py": "603aa2ba02841f9ab8f7c71911d7288302edac3acbca87bb54046111cc8b158d", + "packages/flow/openadapt_flow/runtime/local_grounder.py": "97820b33e69270f07c84873a224a93d5e5cf78c80ee6e0e8fb8e1be4992ee83d", + "packages/flow/openadapt_flow/runtime/process_v1.py": "ee6259ec2ec31cba34511db6a628b7d52a9c8ef5977a1e18ee3961fd82df60bb", + "packages/flow/openadapt_flow/runtime/program_predicates.py": "7d3f387a3ef0c2eb7119f46567904b65e52dd4ec6ea91857aeebcdeeb87d5a79", + "packages/flow/openadapt_flow/runtime/readback_pipeline.py": "bc00ae9d8d37a561646f31b18ff6acc94e6a1f8802a56f7514dd63e6c9ceb304", + "packages/flow/openadapt_flow/runtime/remote_vlm.py": "cc00a062f99e2f4abe608346fef912da5f2eaed9d90fc2d6a4c4f17debf7fd97", + "packages/flow/openadapt_flow/runtime/render_scale.py": "81cf507214e432ac9279bb09398dc20ac187c1d749913afc8586af49accd5638", + "packages/flow/openadapt_flow/runtime/replayer.py": "a457dc1866e16189e7de86992a52890ac819da4bfe59b18a7205bc8611aa1749", + "packages/flow/openadapt_flow/runtime/resolver.py": "e080e0c4640fe4371d3e57cd1a7a0e69f07576abf3ae31cf0f98037251152b66", + "packages/flow/openadapt_flow/runtime/speed.py": "3ea92c67afa3766fb0e1aacc34b3d2bcc5fcc1d397a23904cc20d10adab3d088", + "packages/flow/openadapt_flow/runtime/step_timing.py": "8d0f0f851eeb4c4c331b52c9376e6527a3e40134f2500204804cc1a9ce269ef8", + "packages/flow/openadapt_flow/runtime/surfaces.py": "34e5142a4138cfae191a404950c04d8efe48b7526e8a4dce3b05953f4191cb28", + "packages/flow/openadapt_flow/runtime/target_param.py": "43de5d0653b793d90066c6b386c4e64bef87a70a903623480a380a2f87ae7bf3", + "packages/flow/openadapt_flow/runtime/variant_router.py": "f48e93dbb1b6a0d8b36c6279df734cc7f4b99b939e7a8be6d9a9c5679105b413", + "packages/flow/openadapt_flow/runtime/variant_stamp.py": "9bba7d2fa3b81008040cbfdae52311b6c79ac24bb85e3799e3e136c08491809b", + "packages/flow/openadapt_flow/runtime/worklist_pool.py": "f58ee9837fe7ce459dd1b774523283739df408224d1998291b9385aa38c80845", + "packages/flow/openadapt_flow/runtime_validation.py": "a5a220f570c5ee5769ac2f21d8d140bb69373d4de2ccc0ebfd08540122883f0e", + "packages/flow/openadapt_flow/safe_errors.py": "2e04e744a32e4a5621d22c229b1257f6e38c26543ad449485eb87cafe9479da1", + "packages/flow/openadapt_flow/sanitized_artifact.py": "4464041bae067c9330ce797d0c659c9638db00df4f6a88bbc9ac6aaf0bce2ae6", + "packages/flow/openadapt_flow/scaffold_verifier.py": "8df9652aded917e9660ffc0d779fecfe09d1f98b7e77cae10e211dca13eb9357", + "packages/flow/openadapt_flow/seal_anchor.py": "e54b0fba6ec9d390ecb3d81bf2f5049a20b7c9b36fe8dec7dc8b741cd660347a", + "packages/flow/openadapt_flow/seal_batch.py": "c93b91fb0401d7fb3810b304c1e99a5817b82c346927c83c96cfd77892d31415", + "packages/flow/openadapt_flow/seal_keys.py": "63c80bad5675fd97808fe0093fd1a01b0c9919c3545fd11b30197feb86ad7e71", + "packages/flow/openadapt_flow/seal_timestamp.py": "9d39f36b4dd56bdc5f7a0ceb526e9208a790f648e7108a5684fd24670e4f6f66", + "packages/flow/openadapt_flow/sealed_set.py": "8000dc6e370011b85574d75496a299ed3c12626477038fc8341612091e805fcd", + "packages/flow/openadapt_flow/section_capture.py": "cbb98dd22dc57ad01b887180f9dc47d4af616a5f556eb225d91eddae005d07fa", + "packages/flow/openadapt_flow/sections.py": "4001315490d7a921f2ab7d08b47f248ba26facb4dcbc858d79a357cb91fb7c6a", + "packages/flow/openadapt_flow/services/__init__.py": "f38b556ba34b1fc674267a2c386edf9beb542d5a98c8e266b70f61ec6a617ba4", + "packages/flow/openadapt_flow/services/vlm_service/__init__.py": "da71deb7edc5ea6c30c250faf4cb39b4a76b532801debe6f64598b5d2ed8978e", + "packages/flow/openadapt_flow/services/vlm_service/__main__.py": "3844e4479038b670ef897697c71c05bb8e9f92136425ff899137501b3fc60966", + "packages/flow/openadapt_flow/services/vlm_service/app.py": "808094e2285b70965cc7f84a2c6037749ef29304318e1986c3dbbf7e4a1ec9c0", + "packages/flow/openadapt_flow/services/vlm_service/backends.py": "771df17902dc1c31e44517880706468f0c3dd13ee8dedc161ca60f159e1d9255", + "packages/flow/openadapt_flow/services/vlm_service/batching.py": "545ceb328637acbbaf343566059bcd23ba61ae9b385c6d32b14844a08c074971", + "packages/flow/openadapt_flow/services/vlm_service/config.py": "0bf363d7be1419ed1dd1597230a2be87d880473e5d19df4a9b8ef5e2f33a1bee", + "packages/flow/openadapt_flow/services/vlm_service/prompts.py": "f3af38485a7f61ad96fdaf066387c4809cf34b60cb96147ac186caa828cff1c3", + "packages/flow/openadapt_flow/services/vlm_service/schemas.py": "ca2de9068dcdfc45ca2b33a73fe5b51edaef0c27299b901ec5b340aa0143462f", + "packages/flow/openadapt_flow/shadow.py": "9e4c276ff4f189bf429dff7053ef9160fcff161b0b82746fed232a197a566cee", + "packages/flow/openadapt_flow/shadow_report.py": "ea79a2129bc99189171c418931fdb57fee55fe8a638977cc88dd21733167e428", + "packages/flow/openadapt_flow/skills.py": "9f99447dd927226fcf5122497b1b607b5a098b911ceb00d44f7bdbf0e22fb051", + "packages/flow/openadapt_flow/ssh_sig.py": "095ab479ed182611a416b442a25bbb86c811f1cc671b08e99e631ae8ef157d88", + "packages/flow/openadapt_flow/submit_binding.py": "985fe6d313ef281d3360e104cd12c65f72838e1940daebfb6ea40851d4641369", + "packages/flow/openadapt_flow/surface_bindings.py": "6b5f02db23ce8ec01bce2eecad6f822d743445547f62a0fe9c6286d50f36d762", + "packages/flow/openadapt_flow/surface_selection.py": "150481be4c3509a07e69a5e2a9ec2ebc8d8f48578d16c14f1633feb5a5b613fd", + "packages/flow/openadapt_flow/teach_authorization.py": "b7de7d85e79266b27efabf13d60e442e0f54b6d0e2e9c1ad9feb11cf4a195e81", + "packages/flow/openadapt_flow/teach_authorization_fixture.py": "1b392eb547257871ace4b0576964ff5e726c3665ac3b8274833d0e08a3400f6d", + "packages/flow/openadapt_flow/teach_scope.py": "0befa9f4692b27980123622507c5de76cf22ce65415e4a078634c1d84531d475", + "packages/flow/openadapt_flow/terminal_verification_v2.py": "3bc44d80abc3679cebdeec123b34bcc5656cd2624b1c8943bb22352c0efc3261", + "packages/flow/openadapt_flow/transaction.py": "2d1da876951335d06301757d01607d588dac9146e51e55ca212f120542dde179", + "packages/flow/openadapt_flow/traversal.py": "4bec0fcd5877649e71f7273a17a2073d56c83af6cb7fcd595d0eb9bd7f7a387b", + "packages/flow/openadapt_flow/tutorial.py": "51a21f2bd8aa99ba1d97a3f0a3e8f2cefc856e3c52d8294449b9eabab576c6d8", + "packages/flow/openadapt_flow/undo_proposal.py": "e09883af1fd9741ed876c75a3209ef16bae42b99790c4ca0506aac559f5ae7e9", + "packages/flow/openadapt_flow/validation/__init__.py": "d7f94f2027a19347364cf9491a94f9e6d8c497cd4a67e555e9d200d406e35e0f", + "packages/flow/openadapt_flow/validation/behavioral_generalization.py": "1540c508721eb77fcf1ae5f4b8dc19d88c59e65ec93a71e4ddb88e86878bda3e", + "packages/flow/openadapt_flow/validation/dense_surface.py": "c851d7d90704c59eb2bae9bf1c589df44956190989961ef001a60ac7434211f6", + "packages/flow/openadapt_flow/validation/governed_run.py": "d7fefab39d409381c338b015b9b44f4c757f6bcff693699eef78d73f1f2a9659", + "packages/flow/openadapt_flow/validation/hardening/__init__.py": "f54bdcaf0e0e1c8706e1262d983b7d57dba12e7c3912c50aaec148cc4a6a352a", + "packages/flow/openadapt_flow/validation/hardening/__main__.py": "6a2637d07c62e55e6d46dbd277e7c5d499426044120e949532010efe988aeb75", + "packages/flow/openadapt_flow/validation/hardening/corpus.py": "927d3f60d1971b82175c6a40a4b45116ba1339e8bb644da5750981e3770e068f", + "packages/flow/openadapt_flow/validation/hardening/fixtures.py": "44e85d3c03f03bd46c017a15e46ebff0fee483aed0d76045120e3c43e631318b", + "packages/flow/openadapt_flow/validation/hardening/harness.py": "f214c456ded5c2b27eec1fbe1810c5c7d0467960459baecb04c810df4f978e18", + "packages/flow/openadapt_flow/validation/hardening/perturbations.py": "fe634c8e6878766beea377bd0c62a6ea681d7ff883ff2896c98941a6f3112583", + "packages/flow/openadapt_flow/validation/identity_ladder.py": "4fcb0e3313de20fa3a148d2917aa2b3517d4438561aa140b244b931dc5081880", + "packages/flow/openadapt_flow/validation/identity_looks_dense.py": "ae482b821207e4dc5e6ea825f2d4bedf3cd8528dbbf516e732edbaaca6fb57ef", + "packages/flow/openadapt_flow/validation/identity_rates.py": "d0648e8e94b96677366ac5372453de797f6221a8aa5c799fe2327674d90c851c", + "packages/flow/openadapt_flow/validation/known_ids_dense.py": "9dfcc761b649e83f781e3390077b1c7eea60b4bd16c92e2dae9dd02173192835", + "packages/flow/openadapt_flow/validation/pixel_identity_aligned.py": "dff6c6f9d7ae1788f906c42b2cf443e34f93baf3d26d645413b2c84b54933053", + "packages/flow/openadapt_flow/validation/pixel_identity_probe.py": "66095f52fe8de431a3ab0cf68ba02202bb459b46927e98d7ae6bcf4c68d7ef49", + "packages/flow/openadapt_flow/validation/structural_action.py": "1cb3117f7c2efc9bd6d6a6358d5be167cbbf6b2050df6c133b90f441b75e6118", + "packages/flow/openadapt_flow/validation/vlm_identity_probe.py": "f3641e289f0a02e80fcb31ff76c34c0ed150c647fa9954252425db2b7659b713", + "packages/flow/openadapt_flow/variants.py": "3f434bf5caa78dadc908085a3b9a3586177928660be0ed8854301edcd6f2a2ec", + "packages/flow/openadapt_flow/verification.py": "0bb9f564b4d3dccce7fbebad64abaae1cbd4bd600ffa1b58ec18d9025df52ff2", + "packages/flow/openadapt_flow/verifier.py": "b18c244e18e1e0a69c4ab333a80805542987e9ab68e70decfd3357dd3d05c3ef", + "packages/flow/openadapt_flow/verify.py": "26b3e327a1dec09f8c9a968133f090dababb58cf6178be3e89b11101bc9478f7", + "packages/flow/openadapt_flow/verify_run.py": "95ee80146238aed7c1c0c12b023e61264c1491c34e0320d8a7e3aee200e50bea", + "packages/flow/openadapt_flow/vision/__init__.py": "488c92f581189c81f2ba1e98ddb267119a0407b2c1497822ffa9596c02cd0147", + "packages/flow/openadapt_flow/vision/hashing.py": "60d5b56a991100310163007d6778d6bec8521826c3f586f6b88e6e1a76f1d70e", + "packages/flow/openadapt_flow/vision/imgops.py": "9cd252bcb3a8bab46795098bc29cd5cf988d77c96abd9687ba93b9252e3f53c9", + "packages/flow/openadapt_flow/vision/match.py": "b4d34620ef7aa9f68e922659554c02f51e2c20f9469febd88457d158163d213e", + "packages/flow/openadapt_flow/vision/ocr.py": "e42d4f2b84790ae16b80e2bb5c8c8108b2582a6a3ded8407686e8957d954eded", + "packages/flow/openadapt_flow/vision/ocr_backends.py": "3b38a83eeeb4a9191ad729c1f8e15ecb239ff7d061239b2fd1c3b65c98e35b26", + "packages/flow/openadapt_flow/vision/settle.py": "d81049501c6dc41a4713ddc3d4d8fbc506f4e2320ff7e5ca6a4a769e6a7a18f6", + "packages/flow/openadapt_flow/visualize/__init__.py": "8d791c4ae4fff8a56e5860fdd95c42e441ae462eb029cdc268d771eb0ade4c96", + "packages/flow/openadapt_flow/visualize/admitted_composition.py": "9758fb49a8d3e587204e3df921a42cf823921cb25f8c66e08cfec748337be8d7", + "packages/flow/openadapt_flow/visualize/builder.py": "53749366b83db4a49596cecd41859213ef38bb62f8fd98ed41c3b53dd7ea5211", + "packages/flow/openadapt_flow/visualize/composition.py": "52d42000d0747af577a6f4e2af0b4326c22790c3cf7fc2e6ebd05b83baef91d0", + "packages/flow/openadapt_flow/visualize/projection.py": "8993aa3c33779eac81490f055e31143f09120d047cadd43e94e226b6dadba9ae", + "packages/flow/openadapt_flow/visualize/render.py": "87887e31698e1ffbda8984f5212de5e2743fcd6fd637f5b20643cc8fc5a2c65a", + "packages/flow/openadapt_flow/visualize/spec.py": "68db48c9d4ffba499b9901a2016d2e95bf7f78f89af17d521ed47da16381e6d0", + "packages/flow/openadapt_flow/volatility.py": "117c9df0dcbeff9669f78b27455c5bc1ada762798c9a64393c30fdab5c29dfa1", + "packages/flow/openadapt_flow/wire_check.py": "f3ee4c8c5313b02428129128478c1fd30152f11fa5f77e5592debbceeea2fca7", + "packages/flow/openadapt_flow/write_ledger.py": "627a4e924ded7571280e57d2f54b81bd705a4d33fe8667ef1e7ab337c5db47e7", + "packages/flow/openadapt_flow/write_receipt.py": "a46af4d09ef42b9f48db8b0905d6bf787002b8e48dc3493b40ad4b22a1d0ad03", + "packages/flow/pyproject.toml": "7e05357e58b1a1b56fd7896b2aa0ff4b132f2616a9c934940daf156d8a6ca373", + "packages/flow/uv.lock": "005f88098f5acc2ae30f09b6f4a645e3fd8dc39db95750e3d70e9f18d7db50c0", + "packages/types/openadapt_types/__init__.py": "8cc924b5be7b9b926263a1bdb9b4d9120af85c805fb2762c877b3a28632e2351", + "packages/types/openadapt_types/_compat.py": "b4889a9291687aec404b5b6a83d98c4090f98f9d1aabf270798df0252d998147", + "packages/types/openadapt_types/action.py": "0ec10f7e6a840c09a48492abbf501d3ebe807fd06671657ae4f4c62137fba61e", + "packages/types/openadapt_types/approval.py": "27ed4f73dcb7c71dca80acd51e8b1087b55c6105c8c1e12c9796e94c98a0f0b9", + "packages/types/openadapt_types/authentication.py": "5a9d88de3de6f659e41ad3817abfc40902b3f3bbdcb6d55906a0fc1d83ffd3f1", + "packages/types/openadapt_types/authoring.py": "658383bed9a542b0e7ecc9dd451bc7de3e0771646e9da826acbcf5d2e69231b8", + "packages/types/openadapt_types/benchmark.py": "23640632a9c95cee35cd664d75aa8aa8ca5585259ead7221aaaff610f469d321", + "packages/types/openadapt_types/business_decision.py": "05932e2d37641390e2693520673bf926d5ceba388f8d702d9213582f66b0a3c6", + "packages/types/openadapt_types/check_level.py": "c999b800924fbeaf872b829607b453b63be8d5d4207d79733f641d6eac6c4ad0", + "packages/types/openadapt_types/clinic_job.py": "4719877cb2a1dee680d74d66a1d2f182bab8f86fbb7d81c3e82baf1bdab79d07", + "packages/types/openadapt_types/computer_state.py": "c2063f22364e9012ee852ff36f8a2a13f5015655a0b0d634fcd5755f813dbfa7", + "packages/types/openadapt_types/control_overlay.py": "3aa01f89b43e12478c9e2ff1a9e61689397370691d15247ded9dcd5b05242790", + "packages/types/openadapt_types/control_overlay_tracking.py": "af8d934f0572c5f4e9c63ba720d4a1d0e73c8cdf3f7b87cc4562c01a8a551755", + "packages/types/openadapt_types/episode.py": "5ace88d9e278d099c0e7939cc19e99bf18cbbb80d6ea7d606e3950c027baa8a9", + "packages/types/openadapt_types/execute.py": "a577cdd2d2c4e5db235acdb38ee92d15521fcba60267b8835d3bf68d228d346f", + "packages/types/openadapt_types/execute_client.py": "ed73a52a1f364646263b25132a2cd60db68deec2b33dfae2b92230402afccb57", + "packages/types/openadapt_types/execute_openapi.py": "4600dd2c8995e909c797e2022c22d7745e522123d28627d61e3dab9e7bf405c9", + "packages/types/openadapt_types/execution_requirements.py": "58a3fdb2a43b06af0f27b01abd812ccef6b226b6cfa5307f0b6ceff5f197fba2", + "packages/types/openadapt_types/failure.py": "26efb6e04d17c10cae8bbd688249ee44bcd7844a62be6736de984a6e881fa0bf", + "packages/types/openadapt_types/human_decision.py": "0bb39a3575f511e10d01d92858b340982adee2f0fc20d2409441dbc0b2c55c4f", + "packages/types/openadapt_types/oracle.py": "47a3060d5a49529d4f6506fc3116a0bf902e8b30431456154604ce8371195cde", + "packages/types/openadapt_types/parsing.py": "aae2ceaeead749c3b67619a271b87ecf2388496d37d5bf524834a884ff87a720", + "packages/types/openadapt_types/plain_results.py": "a1fc8b54434af25314e179fbd2670c60f9e52a2a08a4757c8b16d281cbc68951", + "packages/types/openadapt_types/process_capability.py": "8fa70ad62c586534884a73290710c9706291160d63397cb5543de1f2646114d9", + "packages/types/openadapt_types/production_lifecycle.py": "046f30559dac66e4105ad830a274cfb31e0374f95a7a8b0adc1b7dff2aba9449", + "packages/types/openadapt_types/reward.py": "9a5f8d012a8d81f75ed2ac75fc356d4db713edc604ea3bc67070e7f32fec93b5", + "packages/types/openadapt_types/runner_capability.py": "26fd3c1d96a5558c70886cf203c999d524b9bf927cde23d90735c90be9e2b427", + "packages/types/openadapt_types/schemas/__init__.py": "965365ce3faee699154d06428c2dd83414c18632ca3ba39516ef513523b8d618", + "packages/types/openadapt_types/seal.py": "bf4ac2e28b561b3ed80b3e1da2bb5cb0eb005a0cdc151dcf805fd02157bf5143", + "packages/types/openadapt_types/vocabulary/__init__.py": "b5f060d92bea3c0d8b1c15468c1564c3cf5ee864ee4bddf2393debca8f269bb4", + "packages/types/openadapt_types/vocabulary/__main__.py": "0d132037f99fe1d1588a9233ed179df7de0dcbf2d36ffb0fec294c98bd1d7acf", + "packages/types/openadapt_types/vocabulary/lint.py": "ee2a8c6279aa3c737fa2f49e09092c7f652dedaef513e59baf63dd19ba661060", + "packages/types/pyproject.toml": "4904ddc18342be5bf7da91f315d1bed91e60b35dc55d1aa3bdfb149af13415c6" + } +} diff --git a/packages/flow/tests/fixtures/bundle_compatibility/README.md b/packages/flow/tests/fixtures/bundle_compatibility/README.md new file mode 100644 index 0000000000..59490be47f --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/README.md @@ -0,0 +1,29 @@ +# Frozen bundle compatibility fixtures + +These synthetic linear and program bundles were emitted by the actual Flow +2.2.0, 2.5.0 and 2.6.0 serializers at the source commits recorded in each +provenance file. Each producing release loaded and verified its own output +before the bytes were frozen. The current serializer does not generate the +expected bundles or digests. + +`canonical.json` contains the producing release's manifest-free canonical +payload and sealed asset hashes. Its SHA-256 is the original manifest content +digest. This is content-integrity evidence, not a claim of cryptographic signing. +No signing credential or private data is used. + +The generator refuses another package version or source tree, checks pinned +source hashes, and writes only a new output directory. Run it in the exact +historical release's locked environment, then freeze provenance and every output +hash before running the current loader. The tests copy fixtures to temporary +storage, verify original seals, reject tampering and replay one exact button +click in an owned synthetic headless Chromium page. + +Changes to a fixture require new release provenance. Do not regenerate a golden +with the current runtime to make a compatibility test pass. + +Flow 2.2.0's original lockfile failed uv 0.13.7's freshness check. Its fixture +run used `uv sync --frozen` with that unchanged lockfile; it did not resolve or +update dependencies. The 2.5.0 and 2.6.0 runs passed `uv sync --locked`. Each +provenance file pins the producing source, project metadata, lockfile, Python +and Pydantic version. These runs reproduce the pinned release source serializers; +they do not claim to reproduce the original release build environment. diff --git a/packages/flow/tests/fixtures/bundle_compatibility/generate_release_fixture.py b/packages/flow/tests/fixtures/bundle_compatibility/generate_release_fixture.py new file mode 100644 index 0000000000..f72a7839f3 --- /dev/null +++ b/packages/flow/tests/fixtures/bundle_compatibility/generate_release_fixture.py @@ -0,0 +1,146 @@ +"""Run only in the exact historical release checkout, before current-loader tests.""" + +from __future__ import annotations + +import argparse +import hashlib +import importlib.metadata +import json +import sys +from pathlib import Path + +from PIL import Image, ImageDraw + +import openadapt_flow +from openadapt_flow import bundle_validation as bundles +from openadapt_flow.ir import ( + ActionKind, + Anchor, + BundleManifest, + BundleProvenance, + ProgramGraph, + State, + StateKind, + Step, + StructuralLocator, + Transition, + Workflow, +) + +FIXED_TIME = "2026-01-02T03:04:05+00:00" + + +def sha(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def main() -> None: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--source-root", type=Path, required=True) + parser.add_argument("--source-manifest", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + source = args.source_root.resolve() + expected = json.loads(args.source_manifest.read_text()) + assert openadapt_flow.__version__ == expected["release"] + assert ( + Path(openadapt_flow.__file__).resolve().is_relative_to(source / "packages/flow") + ) + for relative, digest in expected["source_hashes"].items(): + assert sha(source / relative) == digest, relative + args.output.mkdir(parents=True, exist_ok=False) + summaries = [] + for shape in ("linear", "program"): + bundle = args.output / shape + (bundle / "templates").mkdir(parents=True) + image = Image.new("RGB", (80, 40), "#f0f0f0") + draw = ImageDraw.Draw(image) + draw.rectangle((0, 0, 79, 39), outline="#555555") + draw.text((25, 12), "Apply", fill="black") + image.save(bundle / "templates/button.png") + step = Step( + id="apply", + intent="apply synthetic change", + action=ActionKind.CLICK, + anchor=Anchor( + template="templates/button.png", + region=(40, 50, 80, 40), + click_point=(80, 70), + ocr_text="Apply", + structural=StructuralLocator(selector="#apply"), + ), + ) + values = { + "name": "release-compatibility", + "created_at": FIXED_TIME, + "viewport": (320, 200), + "manifest": BundleManifest( + provenance=BundleProvenance(created_at=FIXED_TIME) + ), + } + if shape == "linear": + workflow = Workflow(steps=[step], **values) + else: + workflow = Workflow( + program=ProgramGraph( + entry="apply", + states={ + "apply": State( + id="apply", + kind=StateKind.ACTION, + step=step, + transitions=[Transition(target="done")], + ), + "done": State( + id="done", kind=StateKind.TERMINAL, outcome="success" + ), + }, + ), + **values, + ) + workflow.save(bundle) + canonical = json.dumps( + { + "workflow": bundles._workflow_content(workflow), + "files": dict(sorted(workflow.manifest.file_hashes.items())), + }, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ).encode() + digest = hashlib.sha256(canonical).hexdigest() + assert digest == workflow.manifest.content_digest + # Validate with the producing release, not with the implementation under test. + loaded = Workflow.load(bundle) + bundles.verify_integrity(loaded, bundle, loaded.manifest) + assert loaded.manifest.content_digest == digest + (bundle / "canonical.json").write_bytes(canonical) + summary = { + "shape": shape, + "content_digest": digest, + "files": { + path.relative_to(bundle).as_posix(): sha(path) + for path in sorted(bundle.rglob("*")) + if path.is_file() + }, + } + summaries.append(summary) + (args.output / "provenance.json").write_text( + json.dumps( + { + **expected, + "generator_sha256": sha(Path(__file__)), + "python": sys.version.split()[0], + "pydantic": importlib.metadata.version("pydantic"), + "original_release_load_verified": True, + "bundles": summaries, + }, + indent=2, + sort_keys=True, + ) + + "\n" + ) + + +if __name__ == "__main__": + main() From c8dd517ef7adaaa7a9b36bd576df60fc9ba1b019 Mon Sep 17 00:00:00 2001 From: abrichr Date: Wed, 4 Nov 2026 20:42:42 -0500 Subject: [PATCH 02/10] fix(flow): verify original bundle seals before schema upgrades Signed-off-by: abrichr --- packages/flow/docs/BUNDLE_SCHEMA.md | 40 +++ .../flow/openadapt_flow/admit_pipeline.py | 4 +- .../openadapt_flow/admitted_composition.py | 4 +- .../openadapt_flow/authorization_summary.py | 10 +- .../flow/openadapt_flow/bundle_validation.py | 225 +++++++++++-- packages/flow/openadapt_flow/ir.py | 75 ++++- packages/flow/openadapt_flow/run_gate.py | 10 +- .../openadapt_flow/runtime/authorization.py | 15 +- .../flow/tests/test_bundle_original_format.py | 302 ++++++++++++++++++ .../test_bundle_release_compatibility.py | 120 +++++++ 10 files changed, 751 insertions(+), 54 deletions(-) create mode 100644 packages/flow/docs/BUNDLE_SCHEMA.md create mode 100644 packages/flow/tests/test_bundle_original_format.py create mode 100644 packages/flow/tests/test_bundle_release_compatibility.py diff --git a/packages/flow/docs/BUNDLE_SCHEMA.md b/packages/flow/docs/BUNDLE_SCHEMA.md new file mode 100644 index 0000000000..2312742855 --- /dev/null +++ b/packages/flow/docs/BUNDLE_SCHEMA.md @@ -0,0 +1,40 @@ +# Bundle schema and sealed identity + +`Workflow.load` checks the original bundle's manifest-free canonical payload +before upgrading its schema or constructing the current model. Each supported +schema has one canonical projection. A digest mismatch refuses the bundle. +Schema 2's reviewed default omissions apply only at explicit IR ownership +positions; arbitrary request bodies and mappings remain sealed content. + +A missing schema version means legacy schema 1. Upgrades run one registered +step at a time on a copy. Malformed versions, future versions and missing +upgrade steps refuse loading. A breaking format change requires a schema bump, +a canonical format rule and an explicit upgrader. + +Loading preserves the original files and content identity. The loaded object +retains the original canonical bytes plus a fingerprint of the upgraded model +and its integrity-manifest fields. Later verification checks both representations +and the sealed assets. Changing the workflow, asset hashes, integrity manifest +or asset bytes requires a new seal. Loading with `verify_integrity=False` does +not make a broken original seal pass a later integrity check. + +Admission and authorization use `content_identity_digest` to retain a verified +original identity across model upgrades. It checks the loaded model, original +payload, integrity manifest and supplied asset hashes before returning that +identity. Callers checking live assets must hash their bytes or call +`verify_integrity` as well. + +`Workflow.save(..., seal_manifest=True)` creates a seal over the current +serialization; this is also the default save behavior. Saving a loaded sealed +object with `seal_manifest=False` refuses. A format upgrade can change the new +seal's identity, so existing admissions must be evaluated against the new seal. +`compute_content_digest` continues to calculate current-format content for new +seals. + +The frozen compatibility fixtures cover synthetic linear and program bundles +produced and self-verified by the pinned 2.2.0, 2.5.0 and 2.6.0 source releases. +Their original bytes and provenance were frozen before testing the current +loader. The fixture README records the historical environment limitations. +Current controls verify loading, integrity, tamper refusal and one exact input +in an owned synthetic headless Chromium page. Content digests provide integrity; +run authorization still applies its policy, identity and effect checks. diff --git a/packages/flow/openadapt_flow/admit_pipeline.py b/packages/flow/openadapt_flow/admit_pipeline.py index 0d63dfe9ec..f8ae813231 100644 --- a/packages/flow/openadapt_flow/admit_pipeline.py +++ b/packages/flow/openadapt_flow/admit_pipeline.py @@ -206,15 +206,15 @@ def _now() -> str: def _bundle_digest(bundle: Path) -> str: from openadapt_flow import sealed_set from openadapt_flow.bundle_validation import ( - compute_content_digest, compute_file_hashes, + content_identity_digest, ) from openadapt_flow.ir import Workflow workflow = Workflow.load(bundle) # A sealed bundle's crops are ciphertext on disk; hash the plaintext. assets = workflow.decrypted_templates() if sealed_set.is_sealed(bundle) else None - return compute_content_digest( + return content_identity_digest( workflow, compute_file_hashes(workflow, bundle, assets=assets) ) diff --git a/packages/flow/openadapt_flow/admitted_composition.py b/packages/flow/openadapt_flow/admitted_composition.py index ed716e825d..dc6f9065e6 100644 --- a/packages/flow/openadapt_flow/admitted_composition.py +++ b/packages/flow/openadapt_flow/admitted_composition.py @@ -401,8 +401,8 @@ def live_bundle_content_digest(workflow: Workflow, bundle_dir: Path | str) -> st """SHA-256 of the live admitted bundle, independent of the envelope.""" from openadapt_flow.bundle_validation import ( - compute_content_digest, compute_file_hashes, + content_identity_digest, ) bundle = Path(bundle_dir) @@ -410,7 +410,7 @@ def live_bundle_content_digest(workflow: Workflow, bundle_dir: Path | str) -> st file_hashes = dict(workflow.manifest.file_hashes) else: file_hashes = compute_file_hashes(workflow, bundle) - return compute_content_digest(workflow, file_hashes) + return content_identity_digest(workflow, file_hashes) def load_child_envelope( diff --git a/packages/flow/openadapt_flow/authorization_summary.py b/packages/flow/openadapt_flow/authorization_summary.py index a8e71e1eea..6ce5648b25 100644 --- a/packages/flow/openadapt_flow/authorization_summary.py +++ b/packages/flow/openadapt_flow/authorization_summary.py @@ -51,17 +51,15 @@ def __init__(self, code: str, detail: str = "") -> None: def bundle_digest(bundle_dir: Path) -> str: - """Recompute a bundle's content digest from its files (not from its manifest).""" + """Verify content identity against the original seal and current asset bytes.""" from openadapt_flow.bundle_validation import ( - compute_content_digest, compute_file_hashes, + content_identity_digest, ) from openadapt_flow.ir import Workflow - workflow = Workflow.model_validate_json( - (Path(bundle_dir) / "workflow.json").read_text(encoding="utf-8") - ) - return compute_content_digest(workflow, compute_file_hashes(workflow, bundle_dir)) + workflow = Workflow.load(bundle_dir) + return content_identity_digest(workflow, compute_file_hashes(workflow, bundle_dir)) def matching_bundle(digest: str, candidates: Sequence[Path]) -> Optional[Path]: diff --git a/packages/flow/openadapt_flow/bundle_validation.py b/packages/flow/openadapt_flow/bundle_validation.py index 78c3044234..115f9ee8db 100644 --- a/packages/flow/openadapt_flow/bundle_validation.py +++ b/packages/flow/openadapt_flow/bundle_validation.py @@ -39,6 +39,7 @@ from __future__ import annotations +import copy import hashlib import json from dataclasses import dataclass @@ -375,6 +376,147 @@ def _apply_sealed_canonical_omissions( ) +# Schema-2 ownership is a format rule, not a recursive key-name filter. Only +# these typed IR positions can own a compatibility omission; arbitrary mapping +# values (for example an API request body) remain byte-semantically sealed. +_SOURCE_V2_CHILDREN = { + "Workflow": { + "steps": ("Step", "list"), + "program": ("ProgramGraph", "one"), + "subflows": ("ProgramGraph", "map"), + "undo_steps": ("UndoStep", "list"), + "interstitials": ("Interstitial", "list"), + "halt_cleanup": ("HaltCleanup", "one"), + }, + "ProgramGraph": {"states": ("State", "map")}, + "State": { + "step": ("Step", "one"), + "transitions": ("Transition", "list"), + "decision": ("BusinessDecisionSpec", "one"), + }, + "BusinessDecisionSpec": {"revalidation": ("Predicate", "list")}, + "Transition": {"guard": ("Predicate", "one")}, + "UndoStep": {"steps": ("Step", "list")}, + "HaltCleanup": {"steps": ("Step", "list"), "start_state": ("Predicate", "one")}, + "Interstitial": { + "detect": ("Predicate", "one"), + "clearance": ("Predicate", "one"), + "dismiss_anchor": ("Anchor", "one"), + }, + "Step": { + "anchor": ("Anchor", "one"), + "drag_end_anchor": ("Anchor", "one"), + "effects": ("Effect", "list"), + "api_binding": ("ApiBinding", "one"), + "wait_until": ("Predicate", "one"), + "guard": ("Guard", "one"), + }, + "Guard": {"predicate": ("Predicate", "one")}, + "Predicate": {"anchor": ("Anchor", "one"), "operands": ("Predicate", "list")}, + "Anchor": {"structural": ("StructuralLocator", "one")}, + "ApiBinding": {"effects": ("Effect", "list")}, +} + + +def _source_v2_omissions(content: object, owner: str = "Workflow") -> None: + if not isinstance(content, dict): + return + for rule in _SEALED_CANONICAL_OMISSIONS_BY_VERSION[2]: + if ( + rule.owner.__name__ == owner + and rule.field_name in content + and any(content[rule.field_name] == value for value in rule.omitted_values) + ): + content.pop(rule.field_name) + for field_name, (child_owner, shape) in _SOURCE_V2_CHILDREN.get(owner, {}).items(): + child = content.get(field_name) + if shape == "one": + _source_v2_omissions(child, child_owner) + elif shape == "list" and isinstance(child, list): + for value in child: + _source_v2_omissions(value, child_owner) + elif shape == "map" and isinstance(child, dict): + for value in child.values(): + _source_v2_omissions(value, child_owner) + + +def source_canonical_payload(raw: dict, file_hashes: dict[str, str]) -> bytes: + """The original format's canonical payload, before parsing or migration. + + No current Workflow is constructed or serialized here. Every format has + exactly one canonical projection; a mismatch never tries another digest. + """ + version = bundle_schema_version(raw) + content = copy.deepcopy(raw) + content.pop("manifest", None) + if version == 2: + _source_v2_omissions(content) + elif version != 1: + raise BundleSchemaError(f"no canonical format rule for schema {version}") + return json.dumps( + {"workflow": content, "files": dict(sorted(file_hashes.items()))}, + sort_keys=True, + separators=(",", ":"), + ensure_ascii=False, + ).encode("utf-8") + + +@dataclass(frozen=True) +class _LoadedSeal: + original_payload: bytes + runtime_digest: str + content_digest: str + file_hashes: tuple[tuple[str, str], ...] + schema_version: int + encrypted: bool + + +def verify_original_content(raw: dict, stored: BundleManifest) -> bytes: + payload = source_canonical_payload(raw, stored.file_hashes) + if stored.content_digest and _sha256_bytes(payload) != stored.content_digest: + raise BundleIntegrityError( + "bundle original content digest mismatch before migration" + ) + return payload + + +def remember_original_content( + workflow: "Workflow", payload: bytes, stored: BundleManifest +) -> None: + # The original payload is immutable; this second fingerprint detects later + # mutation of the upgraded runtime object without reserializing the source. + workflow._loaded_seal = _LoadedSeal( + payload, + compute_content_digest(workflow, stored.file_hashes), + stored.content_digest, + tuple(sorted(stored.file_hashes.items())), + stored.schema_version, + stored.encrypted, + ) + + +def _verify_workflow_content(workflow: "Workflow", stored: BundleManifest) -> None: + loaded = workflow._loaded_seal + expected = stored.content_digest + if loaded is not None: + if ( + stored.content_digest != loaded.content_digest + or tuple(sorted(stored.file_hashes.items())) != loaded.file_hashes + or stored.schema_version != loaded.schema_version + or stored.encrypted != loaded.encrypted + or _sha256_bytes(loaded.original_payload) != stored.content_digest + ): + raise BundleIntegrityError( + "bundle original manifest integrity changed after loading" + ) + expected = loaded.runtime_digest + recomputed = compute_content_digest(workflow, stored.file_hashes) + if recomputed != expected: + raise BundleIntegrityError( + "bundle content digest mismatch: the workflow was modified after the manifest was sealed" + ) + + def _referenced_asset_paths(workflow: "Workflow") -> set[str]: """Every bundle-relative image path the IR references (templates, identifier crops, postcondition template crops), across the linear steps AND every @@ -504,6 +646,28 @@ def compute_content_digest(workflow: "Workflow", file_hashes: dict[str, str]) -> return _sha256_bytes(canonical.encode("utf-8")) +def content_identity_digest(workflow: "Workflow", file_hashes: dict[str, str]) -> str: + """Return current content identity or an unchanged loaded seal's identity. + + A loaded original seal is reusable only while its original payload, upgraded + model, integrity manifest and supplied asset hashes still verify. This does + not read assets: callers that need a disk check supply freshly hashed files + or separately call verify_integrity. New serialization/build_manifest always + uses compute_content_digest instead, so a new seal covers its new format. + """ + loaded = workflow._loaded_seal + if loaded is None: + return compute_content_digest(workflow, file_hashes) + if workflow.manifest is None: + raise BundleIntegrityError("loaded bundle lost its original manifest") + _verify_workflow_content(workflow, workflow.manifest) + if tuple(sorted(file_hashes.items())) != loaded.file_hashes: + raise BundleIntegrityError( + "bundle asset hashes changed since the original seal" + ) + return loaded.content_digest + + def build_runtime_parameter_schema(workflow: "Workflow") -> list[dict]: """Return the canonical hosted parameter contract without runtime values. @@ -604,25 +768,46 @@ def build_manifest( from openadapt_flow.ir import SCHEMA_VERSION # noqa: E402 -def migrate_bundle_dict(data: dict) -> dict: - """Migrate a raw ``workflow.json`` dict to the current schema version. - - v2 is a strict, ADDITIVE superset of v1 (typed params, program graph, - manifest, ... all default-empty), so migrating a v1 bundle needs no field - transforms -- it only stamps the current ``schema_version`` so downstream - code can rely on it. A bundle with no ``schema_version`` key is treated as - the original v1 shape. Returns the SAME dict, mutated in place, for - convenience. Forward-compatible: a bundle already at (or above) the current - version is left untouched.""" +class BundleSchemaError(ValueError): + """The bundle format is unsupported or lacks a deterministic upgrade step.""" + + +def bundle_schema_version(data: dict) -> int: + if not isinstance(data, dict): + raise BundleSchemaError("workflow.json must contain an object") version = data.get("schema_version", 1) - if not isinstance(version, int): - version = 1 - if version < SCHEMA_VERSION: - # --- future field transforms would live here, gated on `version` --- - data["schema_version"] = SCHEMA_VERSION + if type(version) is not int or not 1 <= version <= SCHEMA_VERSION: + raise BundleSchemaError(f"unsupported bundle schema version: {version!r}") + return version + + +def _upgrade_v1_to_v2(data: dict) -> dict: + # Schema 1 has no integrity manifest. Schema 2 is additive, so the only + # representation change is the explicit version stamp, on a copy. + data["schema_version"] = 2 return data +_BUNDLE_UPGRADERS = {1: _upgrade_v1_to_v2} + + +def migrate_bundle_dict(data: dict) -> dict: + """Upgrade one explicit format step at a time without changing the source.""" + version = bundle_schema_version(data) + upgraded = copy.deepcopy(data) + while version < SCHEMA_VERSION: + upgrader = _BUNDLE_UPGRADERS.get(version) + if upgrader is None: + raise BundleSchemaError(f"no upgrader from bundle schema {version}") + upgraded = upgrader(upgraded) + if bundle_schema_version(upgraded) != version + 1: + raise BundleSchemaError( + f"upgrader from schema {version} did not advance one step" + ) + version += 1 + return upgraded + + # --------------------------------------------------------------------------- # Integrity # --------------------------------------------------------------------------- @@ -660,19 +845,13 @@ def verify_integrity( Only meaningful for a bundle that carries a persisted manifest with a non-empty digest; legacy (pre-v2) bundles have no sealed digest to compare against, and the caller skips this check for them.""" - if not stored.content_digest: + if not stored.content_digest and workflow._loaded_seal is None: return # 1. Recompute the digest over the loaded workflow content using the SEALED # file-hash list (NOT re-hashed from disk), so post-seal template # additions do not move it -- only a workflow.json edit (or a change to # the sealed asset set) breaks this. - recomputed = compute_content_digest(workflow, stored.file_hashes) - if recomputed != stored.content_digest: - raise BundleIntegrityError( - "bundle content digest mismatch: expected " - f"{stored.content_digest[:16]}..., recomputed {recomputed[:16]}... " - "-- the workflow.json was modified after the manifest was sealed" - ) + _verify_workflow_content(workflow, stored) # 2. Each sealed asset must still hash to its recorded value. bundle = Path(bundle_dir) for rel, expected in stored.file_hashes.items(): diff --git a/packages/flow/openadapt_flow/ir.py b/packages/flow/openadapt_flow/ir.py index 494fa4722f..95f2ea4d56 100644 --- a/packages/flow/openadapt_flow/ir.py +++ b/packages/flow/openadapt_flow/ir.py @@ -3169,15 +3169,9 @@ def _verify_sealed_template_integrity( mismatch. Skipped for a bundle with no sealed digest.""" from openadapt_flow import bundle_validation as _bv - if not stored.content_digest: + if not stored.content_digest and workflow._loaded_seal is None: return - recomputed = _bv.compute_content_digest(workflow, stored.file_hashes) - if recomputed != stored.content_digest: - raise _bv.BundleIntegrityError( - "bundle content digest mismatch on decrypt: expected " - f"{stored.content_digest[:16]}..., recomputed {recomputed[:16]}... " - "-- the workflow.json was modified after the manifest was sealed" - ) + _bv._verify_workflow_content(workflow, stored) for rel, expected in stored.file_hashes.items(): data = decrypted.get(rel) if data is None: @@ -3345,6 +3339,8 @@ class Workflow(BaseModel): # from ``model_dump`` / the content digest (a private attribute). The # resolver consumes a decrypted crop via :meth:`decrypted_template`. _decrypted_templates: dict[str, bytes] = PrivateAttr(default_factory=dict) + # Immutable source seal + upgraded-model fingerprint; never serialized. + _loaded_seal: Any = PrivateAttr(default=None) @model_validator(mode="after") def _undo_ids_are_unique(self) -> "Workflow": @@ -3467,6 +3463,13 @@ def save( Returns the path actually written (``workflow.json`` or, when encrypted, ``workflow.json.enc``). """ + from openadapt_flow import bundle_validation as _bv + + _bv.bundle_schema_version({"schema_version": self.schema_version}) + if self._loaded_seal is not None and not seal_manifest: + raise _bv.BundleIntegrityError( + "a loaded sealed bundle requires explicit resealing before serialization" + ) do_encrypt = encrypt or key is not None bundle = Path(bundle_dir) from openadapt_flow import sealed_set as _sealed @@ -3493,6 +3496,15 @@ def save( self.manifest = _bv.build_manifest(self, bundle) serialized = self.model_dump_json(indent=2) + resealed_payload = None + if ( + self._loaded_seal is not None + and seal_manifest + and self.manifest is not None + ): + resealed_payload = _bv.verify_original_content( + json.loads(serialized), self.manifest + ) plaintext_path = bundle / "workflow.json" encrypted_path = bundle / "workflow.json.enc" if do_encrypt: @@ -3523,6 +3535,8 @@ def save( (bundle / "manifest.json").write_bytes( self.manifest.model_dump_json(indent=2).encode("utf-8") ) + if resealed_payload is not None and self.manifest is not None: + _bv.remember_original_content(self, resealed_payload, self.manifest) return path def _save_sealed_set( @@ -3557,7 +3571,19 @@ def _save_sealed_set( from openadapt_flow import bundle_validation as _bv self.manifest = _bv.build_manifest(self, bundle, assets=assets) - writer.write("workflow.json", self.model_dump_json(indent=2).encode("utf-8")) + serialized = self.model_dump_json(indent=2).encode("utf-8") + resealed_payload = None + if ( + self._loaded_seal is not None + and seal_manifest + and self.manifest is not None + ): + from openadapt_flow import bundle_validation as _bv + + resealed_payload = _bv.verify_original_content( + json.loads(serialized), self.manifest + ) + writer.write("workflow.json", serialized) if self.manifest is not None: writer.write( "manifest.json", self.manifest.model_dump_json(indent=2).encode("utf-8") @@ -3565,6 +3591,8 @@ def _save_sealed_set( self._decrypted_templates = dict(assets) if owned: writer.finalize() + if resealed_payload is not None and self.manifest is not None: + _bv.remember_original_content(self, resealed_payload, self.manifest) return bundle / ("workflow.json" + _sealed.SEALED_SUFFIX) @classmethod @@ -3629,17 +3657,30 @@ def load( raw = json.loads(decrypted) else: raw = json.loads(plaintext_path.read_bytes()) - raw = _bv.migrate_bundle_dict(raw) - # A manifest may be embedded in workflow.json OR sit in a sidecar; the - # embedded one wins, else the sidecar, else it is computed fresh. + _bv.bundle_schema_version(raw) + # Verify the original canonical representation before upgrading or + # constructing today's model. Embedded manifest precedence is unchanged. persisted = raw.get("manifest") - wf = cls.model_validate(raw) - - if wf.manifest is None: + if persisted is None: sidecar = bundle / "manifest.json" if sidecar.is_file(): - wf.manifest = BundleManifest.model_validate_json(sidecar.read_text()) - persisted = wf.manifest + persisted = json.loads(sidecar.read_bytes()) + stored = None + original_payload = None + if persisted is not None: + _bv.bundle_schema_version(persisted) + stored = BundleManifest.model_validate(persisted) + if stored.content_digest: + original_payload = ( + _bv.verify_original_content(raw, stored) + if verify_integrity + else _bv.source_canonical_payload(raw, stored.file_hashes) + ) + wf = cls.model_validate(_bv.migrate_bundle_dict(raw)) + if stored is not None: + wf.manifest = stored + if original_payload is not None and stored is not None: + _bv.remember_original_content(wf, original_payload, stored) if sealed_assets is not None: wf._decrypted_templates = sealed_assets diff --git a/packages/flow/openadapt_flow/run_gate.py b/packages/flow/openadapt_flow/run_gate.py index 22f86aa973..5b4197ac00 100644 --- a/packages/flow/openadapt_flow/run_gate.py +++ b/packages/flow/openadapt_flow/run_gate.py @@ -1089,9 +1089,15 @@ def build_runtime_authorization( if report.policy_contract_sha256 is None: raise ValueError("run gate report has no exact admitted policy digest") - from openadapt_flow.bundle_validation import compute_content_digest + from openadapt_flow.bundle_validation import ( + BundleIntegrityError, + content_identity_digest, + ) - recomputed = compute_content_digest(workflow, workflow.manifest.file_hashes) + try: + recomputed = content_identity_digest(workflow, workflow.manifest.file_hashes) + except BundleIntegrityError as exc: + raise ValueError("workflow changed after the run gate evaluated it") from exc if recomputed != report.bundle_content_digest: raise ValueError("workflow changed after the run gate evaluated it") diff --git a/packages/flow/openadapt_flow/runtime/authorization.py b/packages/flow/openadapt_flow/runtime/authorization.py index 807ead3e0b..75aec110ed 100644 --- a/packages/flow/openadapt_flow/runtime/authorization.py +++ b/packages/flow/openadapt_flow/runtime/authorization.py @@ -862,9 +862,20 @@ def validate_workflow(self, workflow: Workflow) -> str | None: "workflow effect-verification minimum changed after authorization" ) - from openadapt_flow.bundle_validation import compute_content_digest + from openadapt_flow.bundle_validation import ( + BundleIntegrityError, + content_identity_digest, + ) - recomputed = compute_content_digest(workflow, workflow.manifest.file_hashes) + try: + recomputed = content_identity_digest( + workflow, workflow.manifest.file_hashes + ) + except BundleIntegrityError: + return ( + "governed run authorization no longer matches the current " + "in-memory workflow semantics" + ) if recomputed != self.bundle_content_digest: return ( "governed run authorization no longer matches the current " diff --git a/packages/flow/tests/test_bundle_original_format.py b/packages/flow/tests/test_bundle_original_format.py new file mode 100644 index 0000000000..59d868a919 --- /dev/null +++ b/packages/flow/tests/test_bundle_original_format.py @@ -0,0 +1,302 @@ +"""Original-format verification is independent of current model serialization.""" + +from __future__ import annotations + +import copy +import json + +import pytest +from PIL import Image + +from openadapt_flow import bundle_validation as bundles +from openadapt_flow.ir import ActionKind, Anchor, Step, Workflow + + +def saved_bundle(tmp_path): + bundle = tmp_path / "bundle" + (bundle / "templates").mkdir(parents=True) + Image.new("RGB", (20, 20), "white").save(bundle / "templates/button.png") + workflow = Workflow( + name="original", + steps=[ + Step( + id="click", + intent="select", + action=ActionKind.CLICK, + anchor=Anchor( + template="templates/button.png", + region=(0, 0, 20, 20), + click_point=(10, 10), + ), + ) + ], + ) + workflow.save(bundle) + return bundle + + +@pytest.mark.parametrize("version", [0, -1, 3, True, False, "2", 2.0, None]) +def test_unknown_or_malformed_schema_refuses_before_parsing(tmp_path, version): + (tmp_path / "workflow.json").write_text( + json.dumps({"schema_version": version, "name": "bad"}) + ) + with pytest.raises(bundles.BundleSchemaError): + Workflow.load(tmp_path) + + +def test_upgrade_is_explicit_deterministic_and_does_not_mutate_source(monkeypatch): + original = {"name": "legacy", "steps": [], "nested": {"schema_version": 77}} + snapshot = copy.deepcopy(original) + upgraded = bundles.migrate_bundle_dict(original) + assert original == snapshot and upgraded["schema_version"] == 2 + assert upgraded["nested"]["schema_version"] == 77 + assert bundles.migrate_bundle_dict(original) == upgraded + assert bundles.migrate_bundle_dict(upgraded) == upgraded + monkeypatch.setattr(bundles, "_BUNDLE_UPGRADERS", {}) + with pytest.raises(bundles.BundleSchemaError, match="no upgrader"): + bundles.migrate_bundle_dict(original) + + +def test_original_seal_is_checked_before_current_model_construction( + tmp_path, monkeypatch +): + bundle = saved_bundle(tmp_path) + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw["name"] = "changed" + (bundle / "workflow.json").write_text(json.dumps(raw)) + + def forbidden(*args, **kwargs): + raise AssertionError( + "current Workflow constructed before original seal refusal" + ) + + monkeypatch.setattr(Workflow, "model_validate", forbidden) + with pytest.raises(bundles.BundleIntegrityError, match="before migration"): + Workflow.load(bundle) + + +def test_future_serializer_default_does_not_invalidate_original_seal( + tmp_path, monkeypatch +): + bundle = saved_bundle(tmp_path) + original_bytes = (bundle / "workflow.json").read_bytes() + expected = json.loads(original_bytes)["manifest"]["content_digest"] + current = bundles._workflow_content + + def future_serializer(workflow): + return {**current(workflow), "future_empty_default": None} + + monkeypatch.setattr(bundles, "_workflow_content", future_serializer) + loaded = Workflow.load(bundle) + assert loaded.manifest.content_digest == expected + bundles.verify_integrity(loaded, bundle, loaded.manifest) + assert (bundle / "workflow.json").read_bytes() == original_bytes + loaded.name = "later mutation" + with pytest.raises(bundles.BundleIntegrityError): + bundles.verify_integrity(loaded, bundle, loaded.manifest) + + +def test_source_projection_omits_only_the_format_owners(): + shadow = { + "frame_path": [], + "form_identity": None, + "new_records": [], + "identity_field": "id", + } + raw = { + "schema_version": 2, + "name": "owned", + "steps": [ + { + "anchor": {"form_identity": None, "structural": {"frame_path": []}}, + "api_binding": { + "body_template": {"nested": shadow}, + "effects": [ + { + "new_records": [], + "identity_field": "id", + "forbid_collateral_change": False, + } + ], + }, + } + ], + } + before = copy.deepcopy(raw) + content = json.loads(bundles.source_canonical_payload(raw, {}))["workflow"] + assert raw == before + step = content["steps"][0] + assert step["anchor"] == {"structural": {}} + assert step["api_binding"]["effects"] == [{}] + assert step["api_binding"]["body_template"]["nested"] == shadow + + +def test_decision_revalidation_uses_its_owned_predicate_projection(): + raw = { + "schema_version": 2, + "name": "decision", + "program": { + "states": { + "choose": { + "decision": { + "revalidation": [ + { + "anchor": { + "form_identity": None, + "structural": {"frame_path": []}, + } + } + ] + } + } + } + }, + } + content = json.loads(bundles.source_canonical_payload(raw, {}))["workflow"] + predicate = content["program"]["states"]["choose"]["decision"]["revalidation"][0] + assert predicate["anchor"] == {"structural": {}} + + +@pytest.mark.parametrize( + "mutation", + ["name", "point", "hashes", "empty_digest", "digest", "schema", "encrypted"], +) +def test_loaded_integrity_catches_model_and_manifest_mutation(tmp_path, mutation): + bundle = saved_bundle(tmp_path) + loaded = Workflow.load(bundle) + if mutation == "name": + loaded.name = "changed" + elif mutation == "point": + loaded.steps[0].anchor.click_point = (11, 10) + elif mutation == "hashes": + loaded.manifest.file_hashes = {} + elif mutation == "empty_digest": + loaded.manifest.content_digest = "" + elif mutation == "digest": + loaded.manifest.content_digest = "0" * 64 + elif mutation == "schema": + loaded.manifest.schema_version = 3 + else: + loaded.manifest.encrypted = not loaded.manifest.encrypted + with pytest.raises(bundles.BundleIntegrityError): + bundles.verify_integrity(loaded, bundle, loaded.manifest) + + +def test_loaded_asset_tamper_refuses(tmp_path): + bundle = saved_bundle(tmp_path) + loaded = Workflow.load(bundle) + (bundle / "templates/button.png").write_bytes(b"changed synthetic asset") + with pytest.raises(bundles.BundleIntegrityError): + bundles.verify_integrity(loaded, bundle, loaded.manifest) + + +def test_loaded_serialization_requires_explicit_reseal(tmp_path): + bundle = saved_bundle(tmp_path) + loaded = Workflow.load(bundle) + original = (bundle / "workflow.json").read_bytes() + loaded.name = "approved new content" + with pytest.raises(bundles.BundleIntegrityError, match="explicit resealing"): + loaded.save(bundle, seal_manifest=False) + assert (bundle / "workflow.json").read_bytes() == original + loaded.save(bundle) + bundles.verify_integrity(loaded, bundle, loaded.manifest) + assert Workflow.load(bundle).name == loaded.name + + +def test_unknown_manifest_version_refuses(tmp_path): + bundle = saved_bundle(tmp_path) + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw["manifest"]["schema_version"] = 3 + (bundle / "workflow.json").write_text(json.dumps(raw)) + with pytest.raises(bundles.BundleSchemaError): + Workflow.load(bundle) + + +def test_unknown_unsealed_extension_cannot_hide_in_current_model_defaults(tmp_path): + bundle = saved_bundle(tmp_path) + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw["future_behavior"] = "changed" + (bundle / "workflow.json").write_text(json.dumps(raw)) + with pytest.raises(bundles.BundleIntegrityError, match="before migration"): + Workflow.load(bundle) + + +def test_unverified_load_cannot_launder_a_broken_original_seal(tmp_path): + bundle = saved_bundle(tmp_path) + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw["name"] = "tampered before unchecked load" + (bundle / "workflow.json").write_text(json.dumps(raw)) + loaded = Workflow.load(bundle, verify_integrity=False) + with pytest.raises(bundles.BundleIntegrityError): + bundles.verify_integrity(loaded, bundle, loaded.manifest) + with pytest.raises(bundles.BundleIntegrityError): + bundles.content_identity_digest(loaded, loaded.manifest.file_hashes) + + +def test_loaded_identity_refuses_replaced_asset_hashes_or_missing_manifest(tmp_path): + bundle = saved_bundle(tmp_path) + loaded = Workflow.load(bundle) + with pytest.raises(bundles.BundleIntegrityError): + bundles.content_identity_digest(loaded, {}) + loaded.manifest = None + with pytest.raises(bundles.BundleIntegrityError): + bundles.content_identity_digest(loaded, {}) + + +@pytest.mark.parametrize("consumer", ["pipeline", "summary", "composition"]) +def test_digest_consumers_retain_verified_original_identity( + tmp_path, monkeypatch, consumer +): + bundle = saved_bundle(tmp_path) + expected = json.loads((bundle / "workflow.json").read_bytes())["manifest"][ + "content_digest" + ] + current = bundles._workflow_content + monkeypatch.setattr( + bundles, + "_workflow_content", + lambda workflow: { + **current(workflow), + "future_empty_default": None, + }, + ) + if consumer == "pipeline": + from openadapt_flow.admit_pipeline import _bundle_digest + + actual = _bundle_digest(bundle) + elif consumer == "summary": + from openadapt_flow.authorization_summary import bundle_digest + + actual = bundle_digest(bundle) + else: + from openadapt_flow.admitted_composition import live_bundle_content_digest + + actual = live_bundle_content_digest(Workflow.load(bundle), bundle) + assert actual == expected + + +def test_governed_identity_retains_original_seal_and_refuses_later_mutation( + tmp_path, monkeypatch +): + from openadapt_flow.run_gate import build_runtime_authorization + from tests.test_run_gate import _KEY, _good_workflow, _run, _seal + + _, bundle = _seal(_good_workflow(), tmp_path) + current = bundles._workflow_content + monkeypatch.setattr( + bundles, + "_workflow_content", + lambda workflow: { + **current(workflow), + "future_empty_default": None, + }, + ) + loaded = Workflow.load(bundle, key=_KEY) + report = _run(loaded, bundle) + assert report.passed, report.render() + authorization = build_runtime_authorization(loaded, report) + assert authorization.validate_workflow(loaded) is None + loaded.name = "later unauthorized change" + assert authorization.validate_workflow(loaded) is not None + with pytest.raises(ValueError, match="workflow changed"): + build_runtime_authorization(loaded, report) diff --git a/packages/flow/tests/test_bundle_release_compatibility.py b/packages/flow/tests/test_bundle_release_compatibility.py new file mode 100644 index 0000000000..1e85ef2050 --- /dev/null +++ b/packages/flow/tests/test_bundle_release_compatibility.py @@ -0,0 +1,120 @@ +"""Frozen release-produced bundles load, retain their seal, and drive a real browser.""" + +from __future__ import annotations + +import hashlib +import json +import shutil +from pathlib import Path + +import pytest + +from openadapt_flow import bundle_validation as bundles +from openadapt_flow.ir import Workflow + +FIXTURES = Path(__file__).parent / "fixtures/bundle_compatibility" +RELEASES = { + "2.2.0": "98c437f12b04f11e44fbc864e051252e480a848e", + "2.5.0": "5a3bf49a0bf2d662430386295a1c34036c064d58", + "2.6.0": "1c832365d11e9dbe2a166485b13f7b8effd6d8d0", +} +CASES = [(release, shape) for release in RELEASES for shape in ("linear", "program")] + + +def hashes(directory): + return { + path.relative_to(directory).as_posix(): hashlib.sha256( + path.read_bytes() + ).hexdigest() + for path in directory.rglob("*") + if path.is_file() + } + + +@pytest.fixture(params=CASES, ids=["-".join(case) for case in CASES]) +def frozen_bundle(request, tmp_path): + release, shape = request.param + provenance = json.loads((FIXTURES / release / "provenance.json").read_bytes()) + assert provenance["commit"] == RELEASES[release] + assert provenance["release"] == release + assert provenance["original_release_load_verified"] is True + expected = next(item for item in provenance["bundles"] if item["shape"] == shape) + source = FIXTURES / release / shape + assert hashes(source) == expected["files"] + bundle = tmp_path / "bundle" + shutil.copytree(source, bundle) + return bundle, expected + + +def test_frozen_bundle_original_seal_loads_without_rewriting(frozen_bundle): + bundle, expected = frozen_bundle + before = hashes(bundle) + canonical = (bundle / "canonical.json").read_bytes() + assert hashlib.sha256(canonical).hexdigest() == expected["content_digest"] + workflow = Workflow.load(bundle) + assert workflow.manifest.content_digest == expected["content_digest"] + bundles.verify_integrity(workflow, bundle, workflow.manifest) + assert hashes(bundle) == before + + +@pytest.mark.parametrize("attack", ["workflow", "version", "asset"]) +def test_frozen_bundle_tampering_refuses(frozen_bundle, attack): + bundle, _ = frozen_bundle + if attack == "asset": + (bundle / "templates/button.png").write_bytes(b"different synthetic pixels") + else: + raw = json.loads((bundle / "workflow.json").read_bytes()) + if attack == "workflow": + raw["name"] = "changed after sealing" + else: + raw["schema_version"] = 1 + (bundle / "workflow.json").write_text(json.dumps(raw)) + with pytest.raises(bundles.BundleIntegrityError): + Workflow.load(bundle) + + +@pytest.fixture(scope="module") +def chromium(): + from playwright.sync_api import sync_playwright + + with sync_playwright() as playwright: + browser = playwright.chromium.launch(headless=True) + try: + yield browser + finally: + browser.close() + + +def test_frozen_bundle_replays_one_exact_input_in_real_chromium( + frozen_bundle, chromium, tmp_path +): + from openadapt_flow.backends.playwright_backend import ( + PlaywrightBackend, + prepare_ws_guard, + ) + from openadapt_flow.runtime import Replayer + + bundle, expected = frozen_bundle + before = hashes(bundle) + context = chromium.new_context(viewport={"width": 320, "height": 200}) + try: + prepare_ws_guard(context) + page = context.new_page() + page.set_content(""" + +0""") + workflow = Workflow.load(bundle) + report = Replayer(PlaywrightBackend(page)).run( + workflow, bundle_dir=bundle, run_dir=tmp_path / "run" + ) + assert report.success, [ + (result.step_id, result.error) for result in report.results + ] + assert page.locator("#count").inner_text() == "1" + assert len(report.results) == 1 + assert report.results[0].resolution.rung == "structural" + assert workflow.manifest.content_digest == expected["content_digest"] + assert hashes(bundle) == before + finally: + context.close() From 1bdf7b23db3ea3539ef0147d2963623964ff13f9 Mon Sep 17 00:00:00 2001 From: abrichr Date: Thu, 5 Nov 2026 15:45:26 -0500 Subject: [PATCH 03/10] Prevent resealing unverified bundles Signed-off-by: abrichr --- .../openadapt_flow/authorization_summary.py | 11 ++- .../flow/openadapt_flow/bundle_validation.py | 91 ++++++++++++++----- packages/flow/openadapt_flow/ir.py | 12 +++ .../openadapt_flow/qualification_proposal.py | 41 ++++++++- .../flow/tests/test_bundle_original_format.py | 46 ++++++++++ .../test_bundle_release_compatibility.py | 10 ++ .../flow/tests/test_qualification_proposal.py | 26 ++++++ 7 files changed, 209 insertions(+), 28 deletions(-) diff --git a/packages/flow/openadapt_flow/authorization_summary.py b/packages/flow/openadapt_flow/authorization_summary.py index 6ce5648b25..54e605927f 100644 --- a/packages/flow/openadapt_flow/authorization_summary.py +++ b/packages/flow/openadapt_flow/authorization_summary.py @@ -53,13 +53,22 @@ def __init__(self, code: str, detail: str = "") -> None: def bundle_digest(bundle_dir: Path) -> str: """Verify content identity against the original seal and current asset bytes.""" from openadapt_flow.bundle_validation import ( + BundleIntegrityError, compute_file_hashes, content_identity_digest, ) from openadapt_flow.ir import Workflow workflow = Workflow.load(bundle_dir) - return content_identity_digest(workflow, compute_file_hashes(workflow, bundle_dir)) + if workflow._loaded_seal is None or not workflow._loaded_seal.content_digest: + raise BundleIntegrityError( + "authorization summary requires a persisted bundle integrity seal" + ) + assets = workflow.decrypted_templates() if workflow.encrypted else None + return content_identity_digest( + workflow, + compute_file_hashes(workflow, bundle_dir, assets=assets), + ) def matching_bundle(digest: str, candidates: Sequence[Path]) -> Optional[Path]: diff --git a/packages/flow/openadapt_flow/bundle_validation.py b/packages/flow/openadapt_flow/bundle_validation.py index 115f9ee8db..fb4c448e62 100644 --- a/packages/flow/openadapt_flow/bundle_validation.py +++ b/packages/flow/openadapt_flow/bundle_validation.py @@ -517,6 +517,40 @@ def _verify_workflow_content(workflow: "Workflow", stored: BundleManifest) -> No ) +def verify_loaded_source_integrity( + workflow: "Workflow", + bundle_dir: Path | str, + stored: BundleManifest, + *, + decrypted_assets: Optional[dict[str, bytes]] = None, +) -> None: + """Verify the immutable source seal before a loaded workflow is resealed. + + Unlike :func:`verify_integrity`, this intentionally does not compare the + current runtime model fingerprint. A caller may have deliberately edited a + previously verified workflow in memory and then save that as new content. + The original payload, manifest identity, and original assets must still + verify first, so an unchecked or tampered load cannot be laundered by that + new seal. + """ + loaded = workflow._loaded_seal + if loaded is None or not loaded.content_digest: + raise BundleIntegrityError( + "cannot reseal a loaded bundle without a persisted integrity seal" + ) + if ( + stored.content_digest != loaded.content_digest + or tuple(sorted(stored.file_hashes.items())) != loaded.file_hashes + or stored.schema_version != loaded.schema_version + or stored.encrypted != loaded.encrypted + or _sha256_bytes(loaded.original_payload) != stored.content_digest + ): + raise BundleIntegrityError( + "bundle original manifest integrity changed after loading" + ) + _verify_asset_hashes(bundle_dir, stored, decrypted_assets=decrypted_assets) + + def _referenced_asset_paths(workflow: "Workflow") -> set[str]: """Every bundle-relative image path the IR references (templates, identifier crops, postcondition template crops), across the linear steps AND every @@ -819,6 +853,38 @@ class BundleIntegrityError(Exception): written.""" +def _verify_asset_hashes( + bundle_dir: Path | str, + stored: BundleManifest, + *, + decrypted_assets: Optional[dict[str, bytes]] = None, +) -> None: + bundle = Path(bundle_dir) + for rel, expected in stored.file_hashes.items(): + if decrypted_assets is not None: + data = decrypted_assets.get(rel) + if data is None: + raise BundleIntegrityError( + f"manifest lists sealed asset {rel!r} but its authenticated " + "plaintext is unavailable" + ) + if _sha256_bytes(data) != expected: + raise BundleIntegrityError( + f"sealed asset {rel!r} plaintext hash mismatch " + "(tampered or corrupted)" + ) + continue + path = bundle / rel + if not path.is_file(): + raise BundleIntegrityError( + f"manifest lists asset {rel!r} but it is missing from the bundle" + ) + if _sha256_file(path) != expected: + raise BundleIntegrityError( + f"asset {rel!r} hash mismatch (tampered or corrupted)" + ) + + def verify_integrity( workflow: "Workflow", bundle_dir: Path | str, @@ -853,30 +919,7 @@ def verify_integrity( # the sealed asset set) breaks this. _verify_workflow_content(workflow, stored) # 2. Each sealed asset must still hash to its recorded value. - bundle = Path(bundle_dir) - for rel, expected in stored.file_hashes.items(): - if decrypted_assets is not None: - data = decrypted_assets.get(rel) - if data is None: - raise BundleIntegrityError( - f"manifest lists sealed asset {rel!r} but its authenticated " - "plaintext is unavailable" - ) - if _sha256_bytes(data) != expected: - raise BundleIntegrityError( - f"sealed asset {rel!r} plaintext hash mismatch " - "(tampered or corrupted)" - ) - continue - path = bundle / rel - if not path.is_file(): - raise BundleIntegrityError( - f"manifest lists asset {rel!r} but it is missing from the bundle" - ) - if _sha256_file(path) != expected: - raise BundleIntegrityError( - f"asset {rel!r} hash mismatch (tampered or corrupted)" - ) + _verify_asset_hashes(bundle_dir, stored, decrypted_assets=decrypted_assets) # --------------------------------------------------------------------------- diff --git a/packages/flow/openadapt_flow/ir.py b/packages/flow/openadapt_flow/ir.py index 151d6fd0bf..804ff699e7 100644 --- a/packages/flow/openadapt_flow/ir.py +++ b/packages/flow/openadapt_flow/ir.py @@ -3529,6 +3529,18 @@ def save( bundle = Path(bundle_dir) from openadapt_flow import sealed_set as _sealed + if self._loaded_seal is not None: + if self.manifest is None: + raise _bv.BundleIntegrityError( + "loaded bundle lost its original integrity manifest" + ) + _bv.verify_loaded_source_integrity( + self, + bundle, + self.manifest, + decrypted_assets=(self._decrypted_templates if self.encrypted else None), + ) + if _sealed.active_writer(bundle) is not None or _sealed.is_sealed(bundle): # A sealed bundle stays sealed whatever ``encrypt`` says: many # commands re-save a bundle with a bare ``save(bundle)``, and that diff --git a/packages/flow/openadapt_flow/qualification_proposal.py b/packages/flow/openadapt_flow/qualification_proposal.py index f5fab34c79..e99b2c9fa9 100644 --- a/packages/flow/openadapt_flow/qualification_proposal.py +++ b/packages/flow/openadapt_flow/qualification_proposal.py @@ -433,17 +433,52 @@ def admit_local_dev( raise QualificationProposalError( "initialize qualification before local-dev admission" ) + stored_manifest = workflow.manifest + if stored_manifest is None: + raise QualificationProposalError( + "local-dev admission requires a persisted bundle integrity seal" + ) + from openadapt_flow import sealed_set from openadapt_flow.bundle_validation import ( + BundleIntegrityError, compute_content_digest, compute_file_hashes, + verify_loaded_source_integrity, ) bundle = Path(bundle_dir) + try: + verify_loaded_source_integrity( + workflow, + bundle, + stored_manifest, + decrypted_assets=( + workflow.decrypted_templates() + if workflow.encrypted or sealed_set.is_sealed(bundle) + else None + ), + ) + except BundleIntegrityError as exc: + raise QualificationProposalError( + "local-dev admission requires a verified persisted bundle seal" + ) from exc file_hashes = ( - dict(workflow.manifest.file_hashes) - if workflow.manifest is not None and workflow.manifest.file_hashes - else compute_file_hashes(workflow, bundle) + dict(stored_manifest.file_hashes) + if stored_manifest.file_hashes + else compute_file_hashes( + workflow, + bundle, + assets=( + workflow.decrypted_templates() + if workflow.encrypted or sealed_set.is_sealed(bundle) + else None + ), + ) ) + # The accepted proposal has intentionally changed qualification fields in + # memory. Hash that proposed new content only after the original on-disk + # seal and its assets have been verified above; save_accepted_bundle then + # reseals the same content before it is used. digest = compute_content_digest(workflow, file_hashes) project = workflow.qualification identity_contract = hashlib.sha256( diff --git a/packages/flow/tests/test_bundle_original_format.py b/packages/flow/tests/test_bundle_original_format.py index 59d868a919..1b78c77935 100644 --- a/packages/flow/tests/test_bundle_original_format.py +++ b/packages/flow/tests/test_bundle_original_format.py @@ -233,6 +233,52 @@ def test_unverified_load_cannot_launder_a_broken_original_seal(tmp_path): bundles.content_identity_digest(loaded, loaded.manifest.file_hashes) +@pytest.mark.parametrize("attack", ["workflow", "asset"]) +@pytest.mark.parametrize("copy_loaded", [False, True]) +def test_unverified_load_cannot_reseal_tampered_source( + tmp_path, attack, copy_loaded +): + bundle = saved_bundle(tmp_path) + if attack == "workflow": + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw["name"] = "tampered before unchecked load" + (bundle / "workflow.json").write_text(json.dumps(raw)) + else: + (bundle / "templates/button.png").write_bytes(b"tampered asset") + before = { + path.relative_to(bundle): path.read_bytes() + for path in bundle.rglob("*") + if path.is_file() + } + + loaded = Workflow.load(bundle, verify_integrity=False) + candidate = loaded.model_copy(deep=True) if copy_loaded else loaded + with pytest.raises(bundles.BundleIntegrityError): + candidate.save(bundle) + + after = { + path.relative_to(bundle): path.read_bytes() + for path in bundle.rglob("*") + if path.is_file() + } + assert after == before + + +def test_authorization_summary_refuses_manifestless_bundle(tmp_path): + bundle = saved_bundle(tmp_path) + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw.pop("manifest") + (bundle / "workflow.json").write_text(json.dumps(raw)) + (bundle / "manifest.json").unlink() + + from openadapt_flow.authorization_summary import bundle_digest + + with pytest.raises( + bundles.BundleIntegrityError, match="persisted bundle integrity seal" + ): + bundle_digest(bundle) + + def test_loaded_identity_refuses_replaced_asset_hashes_or_missing_manifest(tmp_path): bundle = saved_bundle(tmp_path) loaded = Workflow.load(bundle) diff --git a/packages/flow/tests/test_bundle_release_compatibility.py b/packages/flow/tests/test_bundle_release_compatibility.py index 1e85ef2050..5afb5a59db 100644 --- a/packages/flow/tests/test_bundle_release_compatibility.py +++ b/packages/flow/tests/test_bundle_release_compatibility.py @@ -57,6 +57,16 @@ def test_frozen_bundle_original_seal_loads_without_rewriting(frozen_bundle): assert hashes(bundle) == before +def test_verified_historical_bundle_can_be_resaved(frozen_bundle): + bundle, _expected = frozen_bundle + workflow = Workflow.load(bundle) + + workflow.save(bundle) + + reloaded = Workflow.load(bundle) + bundles.verify_integrity(reloaded, bundle, reloaded.manifest) + + @pytest.mark.parametrize("attack", ["workflow", "version", "asset"]) def test_frozen_bundle_tampering_refuses(frozen_bundle, attack): bundle, _ = frozen_bundle diff --git a/packages/flow/tests/test_qualification_proposal.py b/packages/flow/tests/test_qualification_proposal.py index e759b4c00d..78c0b77bf9 100644 --- a/packages/flow/tests/test_qualification_proposal.py +++ b/packages/flow/tests/test_qualification_proposal.py @@ -204,6 +204,32 @@ def test_accept_confirms_pins_and_keeps_break_it(tmp_path: Path) -> None: assert local.issuer_workflow == LOCAL_DEV_ISSUER_WORKFLOW +@pytest.mark.parametrize("source", ["unchecked_tamper", "manifestless"]) +def test_local_dev_admission_requires_a_verified_persisted_seal( + tmp_path: Path, source: str +) -> None: + workflow, recording, bundle = _write_workflow(tmp_path, _workflow()) + if source == "unchecked_tamper": + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw["name"] = "tampered before unchecked load" + (bundle / "workflow.json").write_text(json.dumps(raw), encoding="utf-8") + workflow = Workflow.load(bundle, verify_integrity=False) + else: + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw.pop("manifest") + (bundle / "workflow.json").write_text(json.dumps(raw), encoding="utf-8") + (bundle / "manifest.json").unlink() + workflow = Workflow.load(bundle) + + proposal = propose_qualification(workflow, recording_dir=recording) + accepted = accept_proposal(workflow, proposal) + with pytest.raises( + QualificationProposalError, + match="requires a verified persisted bundle seal", + ): + admit_local_dev(workflow, accepted, bundle_dir=bundle) + + def test_local_dev_key_cannot_enter_a_production_trust_map() -> None: registry = { LOCAL_DEV_KEY_ID: { From 3d27f48e6bdc456aaf538f7a654dfc3172b6f286 Mon Sep 17 00:00:00 2001 From: abrichr Date: Thu, 5 Nov 2026 16:09:34 -0500 Subject: [PATCH 04/10] Keep original bundle seals valid in privacy tests Signed-off-by: abrichr --- packages/flow/tests/test_artifact_reads.py | 13 ++++++++++++- packages/flow/tests/test_compiler.py | 13 +++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/flow/tests/test_artifact_reads.py b/packages/flow/tests/test_artifact_reads.py index ab6f10facc..737c199117 100644 --- a/packages/flow/tests/test_artifact_reads.py +++ b/packages/flow/tests/test_artifact_reads.py @@ -20,6 +20,7 @@ from __future__ import annotations import ast +import hashlib import json import os from pathlib import Path @@ -838,11 +839,21 @@ def test_push_in_strict_mode_seals_the_review_copy_of_a_plain_recording( def _bundle_with_a_bad_action(source: Path, out: Path, *, key=None) -> Path: - """A copy of ``source`` whose first action is the private marker.""" + """A sealed copy whose first action is invalid but integrity-consistent.""" + from openadapt_flow import bundle_validation + opened = sealed_set.open_set(source, KEY) files = {rel: opened.read_bytes(rel) for rel in opened.files("")} doc = json.loads(files["workflow.json"]) doc["steps"][0]["action"] = _MARKER + stored = doc["manifest"] + digest = hashlib.sha256( + bundle_validation.source_canonical_payload(doc, stored["file_hashes"]) + ).hexdigest() + stored["content_digest"] = digest + sidecar = json.loads(files["manifest.json"]) + sidecar["content_digest"] = digest + files["manifest.json"] = json.dumps(sidecar, indent=2, sort_keys=True).encode() files["workflow.json"] = json.dumps(doc).encode() if key is None: for rel, data in files.items(): diff --git a/packages/flow/tests/test_compiler.py b/packages/flow/tests/test_compiler.py index 9820671032..821777e2a3 100644 --- a/packages/flow/tests/test_compiler.py +++ b/packages/flow/tests/test_compiler.py @@ -871,11 +871,20 @@ def test_corner_click_clamped_no_change(self, compiled) -> None: assert step.expect == [] def test_bundle_roundtrip(self, compiled) -> None: + from openadapt_flow import bundle_validation + wf, bundle = compiled["workflow"], compiled["bundle"] loaded = Workflow.load(bundle) - assert loaded == wf + # A loaded workflow intentionally carries private original-seal state; + # compare the public model and validate that persisted seal separately. + assert loaded.model_dump(mode="json") == wf.model_dump(mode="json") + bundle_validation.verify_integrity(loaded, bundle, loaded.manifest) + loaded.save(bundle) + reloaded = Workflow.load(bundle) + assert reloaded.model_dump(mode="json") == wf.model_dump(mode="json") + bundle_validation.verify_integrity(reloaded, bundle, reloaded.manifest) # every referenced template exists in the bundle - for step in loaded.steps: + for step in reloaded.steps: if step.anchor: assert (bundle / step.anchor.template).exists() From 96fe2f76dff130625048bae84ab5cf777a33ea54 Mon Sep 17 00:00:00 2001 From: abrichr Date: Thu, 5 Nov 2026 16:53:45 -0500 Subject: [PATCH 05/10] Prevent resealing unverified Flow bundles Signed-off-by: abrichr --- .../openadapt_flow/authorization_summary.py | 26 ++-- .../flow/openadapt_flow/bundle_validation.py | 131 +++++++++++++++-- packages/flow/openadapt_flow/ir.py | 136 ++++++++++++++---- packages/flow/openadapt_flow/qualification.py | 7 + .../openadapt_flow/qualification_proposal.py | 4 + .../flow/openadapt_flow/sanitized_artifact.py | 20 ++- .../flow/tests/test_bundle_original_format.py | 82 ++++++++++- packages/flow/tests/test_bundle_schema_v2.py | 10 +- packages/flow/tests/test_business_decision.py | 21 +++ .../flow/tests/test_qualification_proposal.py | 5 +- .../flow/tests/test_sanitized_artifact.py | 5 + 11 files changed, 390 insertions(+), 57 deletions(-) diff --git a/packages/flow/openadapt_flow/authorization_summary.py b/packages/flow/openadapt_flow/authorization_summary.py index 54e605927f..cb73791b38 100644 --- a/packages/flow/openadapt_flow/authorization_summary.py +++ b/packages/flow/openadapt_flow/authorization_summary.py @@ -59,7 +59,12 @@ def bundle_digest(bundle_dir: Path) -> str: ) from openadapt_flow.ir import Workflow - workflow = Workflow.load(bundle_dir) + try: + workflow = Workflow.load(bundle_dir) + except BundleIntegrityError as exc: + raise BundleIntegrityError( + "authorization summary requires a persisted bundle integrity seal" + ) from exc if workflow._loaded_seal is None or not workflow._loaded_seal.content_digest: raise BundleIntegrityError( "authorization summary requires a persisted bundle integrity seal" @@ -124,9 +129,7 @@ def outline(bundle_dir: Path) -> ProgramOutline: """Sections (compiled ones if present, else one per field filled), writes, stops.""" from openadapt_flow.ir import Workflow - wf = Workflow.model_validate_json( - (Path(bundle_dir) / "workflow.json").read_text(encoding="utf-8") - ) + wf = Workflow.load(bundle_dir) steps = list(wf.steps) env = getattr(getattr(wf, "qualification", None), "environment", None) app = str(getattr(env, "application", "") or "") or wf.name.split("-", 1)[0] @@ -196,16 +199,21 @@ def _covered_by_digest(bundle_dir: Path, report: Path) -> bool: """True only when the report's exact bytes are a hashed file of the bundle.""" import hashlib - from openadapt_flow.bundle_validation import compute_file_hashes + from openadapt_flow.bundle_validation import ( + BundleIntegrityError, + compute_file_hashes, + ) from openadapt_flow.ir import Workflow try: rel = report.resolve().relative_to(bundle_dir.resolve()).as_posix() - wf = Workflow.model_validate_json( - (bundle_dir / "workflow.json").read_text("utf-8") + wf = Workflow.load(bundle_dir) + hashes = compute_file_hashes( + wf, + bundle_dir, + assets=wf.decrypted_templates() if wf.encrypted else None, ) - hashes = compute_file_hashes(wf, bundle_dir) - except (ValueError, OSError): + except (BundleIntegrityError, OSError, ValueError): return False return hashes.get(rel) == hashlib.sha256(report.read_bytes()).hexdigest() diff --git a/packages/flow/openadapt_flow/bundle_validation.py b/packages/flow/openadapt_flow/bundle_validation.py index fb4c448e62..abb62405e9 100644 --- a/packages/flow/openadapt_flow/bundle_validation.py +++ b/packages/flow/openadapt_flow/bundle_validation.py @@ -18,9 +18,8 @@ a certified bundle -- the policy it was certified against, its certification status, and an optional expiry. * **Migration** (:func:`migrate_bundle_dict`): a v1 bundle (or one with no - ``schema_version``) migrates cleanly to v2 in memory on read. v2 is a strict - superset of v1, so migration only stamps the new version and back-fills the - manifest if it is absent -- an existing bundle keeps replaying byte-for-byte. + ``schema_version``) migrates cleanly to v2 in memory when a caller explicitly + opts into unsealed legacy migration. Ordinary loads require a persisted seal. * **Load-time structural validation** (:func:`validate_workflow`): reject a malformed or uncertifiable graph with clear, per-issue errors BEFORE the replayer touches it. Rules span graph well-formedness (entry exists, every @@ -42,8 +41,9 @@ import copy import hashlib import json +import weakref from dataclasses import dataclass -from pathlib import Path +from pathlib import Path, PurePosixPath from typing import TYPE_CHECKING, Any, Iterable, Iterator, Literal, Optional from pydantic import BaseModel, Field @@ -88,6 +88,25 @@ def _sha256_file(path: Path) -> str: return h.hexdigest() +def bundle_asset_path(bundle_dir: Path | str, rel: str) -> Path: + """Resolve a manifest asset only when it stays inside the bundle.""" + posix = PurePosixPath(rel) + if ( + not rel + or "\\" in rel + or posix.is_absolute() + or posix.as_posix() != rel + or any(part in {"", ".", ".."} for part in posix.parts) + ): + raise BundleIntegrityError(f"unsafe bundle asset path {rel!r}") + root = Path(bundle_dir).resolve() + candidate = root.joinpath(*posix.parts) + resolved = candidate.resolve() + if not resolved.is_relative_to(root) or candidate.is_symlink(): + raise BundleIntegrityError(f"bundle asset path escapes its directory: {rel!r}") + return candidate + + @dataclass(frozen=True) class _SealedCanonicalOmission: """One reviewed compatibility rule for a historical digest schema. @@ -469,6 +488,75 @@ class _LoadedSeal: file_hashes: tuple[tuple[str, str], ...] schema_version: int encrypted: bool + source_bundle_dir: Path + + +# Load authority is process-local and bound to the exact object. Pydantic +# private attributes are omitted from dumps but copied by model_copy, so the +# registry prevents a clone or model_validate(model_dump()) from inheriting it. +_verified_loads: dict[int, tuple[weakref.ReferenceType, object]] = {} + + +def has_verified_load_capability(workflow: "Workflow") -> bool: + entry = _verified_loads.get(id(workflow)) + return ( + entry is not None + and entry[0]() is workflow + and getattr(workflow, "_verification_capability", None) is entry[1] + ) + + +def _grant_verified_load_capability(workflow: "Workflow") -> None: + identity = id(workflow) + + def discard(reference: weakref.ReferenceType, *, identity: int = identity) -> None: + current = _verified_loads.get(identity) + if current is not None and current[0] is reference: + _verified_loads.pop(identity, None) + + reference = weakref.ref(workflow, discard) + token = object() + workflow._verification_capability = token + _verified_loads[identity] = (reference, token) + + +def transfer_verified_load_capability(source: "Workflow", target: "Workflow") -> None: + """Keep verified authority across an explicit in-memory model_copy only.""" + if has_verified_load_capability(source): + _grant_verified_load_capability(target) + + +def authorize_sanitized_derivative( + workflow: "Workflow", verified_source: "Workflow", source_bundle_dir: Path | str +) -> str: + """Authorize the sanitizer's verified-source → scrub → reseal transition.""" + source_manifest = verified_source.manifest + if ( + source_manifest is None + or not source_manifest.content_digest + or verified_source._loaded_seal is None + or not has_verified_load_capability(verified_source) + ): + raise BundleIntegrityError( + "sanitized bundle reseal requires a verified sealed source bundle" + ) + verify_loaded_source_integrity( + verified_source, + source_bundle_dir, + source_manifest, + decrypted_assets=( + verified_source.decrypted_templates() if verified_source.encrypted else None + ), + ) + if workflow.manifest is None: + raise BundleIntegrityError( + "sanitized bundle reseal requires the source manifest" + ) + workflow.manifest.provenance.sanitized_from_digest = source_manifest.content_digest + workflow._loaded_seal = None + workflow._unverified_load = False + _grant_verified_load_capability(workflow) + return source_manifest.content_digest def verify_original_content(raw: dict, stored: BundleManifest) -> bytes: @@ -481,7 +569,11 @@ def verify_original_content(raw: dict, stored: BundleManifest) -> bytes: def remember_original_content( - workflow: "Workflow", payload: bytes, stored: BundleManifest + workflow: "Workflow", + payload: bytes, + stored: BundleManifest, + *, + source_bundle_dir: Path | str, ) -> None: # The original payload is immutable; this second fingerprint detects later # mutation of the upgraded runtime object without reserializing the source. @@ -492,7 +584,9 @@ def remember_original_content( tuple(sorted(stored.file_hashes.items())), stored.schema_version, stored.encrypted, + Path(source_bundle_dir).resolve(), ) + _grant_verified_load_capability(workflow) def _verify_workflow_content(workflow: "Workflow", stored: BundleManifest) -> None: @@ -548,7 +642,13 @@ def verify_loaded_source_integrity( raise BundleIntegrityError( "bundle original manifest integrity changed after loading" ) - _verify_asset_hashes(bundle_dir, stored, decrypted_assets=decrypted_assets) + if not has_verified_load_capability(workflow): + raise BundleIntegrityError( + "loaded bundle verification capability is missing or was not preserved" + ) + _verify_asset_hashes( + loaded.source_bundle_dir, stored, decrypted_assets=decrypted_assets + ) def _referenced_asset_paths(workflow: "Workflow") -> set[str]: @@ -644,11 +744,11 @@ def compute_file_hashes( paths.add(f.relative_to(bundle).as_posix()) for rel in _referenced_asset_paths(workflow): - candidate = bundle / rel + candidate = bundle_asset_path(bundle, rel) if candidate.is_file(): - paths.add(Path(rel).as_posix()) + paths.add(rel) - return {rel: _sha256_file(bundle / rel) for rel in sorted(paths)} + return {rel: _sha256_file(bundle_asset_path(bundle, rel)) for rel in sorted(paths)} def _workflow_content(workflow: "Workflow") -> dict: @@ -689,6 +789,10 @@ def content_identity_digest(workflow: "Workflow", file_hashes: dict[str, str]) - or separately call verify_integrity. New serialization/build_manifest always uses compute_content_digest instead, so a new seal covers its new format. """ + if workflow._unverified_load: + raise BundleIntegrityError( + "an unchecked bundle load has no verified content identity" + ) loaded = workflow._loaded_seal if loaded is None: return compute_content_digest(workflow, file_hashes) @@ -784,6 +888,9 @@ def build_manifest( governed_authorization_template=( prior_prov.governed_authorization_template if prior_prov else None ), + sanitized_from_digest=( + prior_prov.sanitized_from_digest if prior_prov else None + ), ) return BundleManifest( content_digest=digest, @@ -861,6 +968,7 @@ def _verify_asset_hashes( ) -> None: bundle = Path(bundle_dir) for rel, expected in stored.file_hashes.items(): + path = bundle_asset_path(bundle, rel) if decrypted_assets is not None: data = decrypted_assets.get(rel) if data is None: @@ -874,7 +982,6 @@ def _verify_asset_hashes( "(tampered or corrupted)" ) continue - path = bundle / rel if not path.is_file(): raise BundleIntegrityError( f"manifest lists asset {rel!r} but it is missing from the bundle" @@ -909,8 +1016,8 @@ def verify_integrity( (re-saving reseals them). Only meaningful for a bundle that carries a persisted manifest with a - non-empty digest; legacy (pre-v2) bundles have no sealed digest to compare - against, and the caller skips this check for them.""" + non-empty digest. An explicit legacy migration opt-in creates a new seal + before the caller can save it.""" if not stored.content_digest and workflow._loaded_seal is None: return # 1. Recompute the digest over the loaded workflow content using the SEALED diff --git a/packages/flow/openadapt_flow/ir.py b/packages/flow/openadapt_flow/ir.py index 804ff699e7..e5188032b7 100644 --- a/packages/flow/openadapt_flow/ir.py +++ b/packages/flow/openadapt_flow/ir.py @@ -3009,6 +3009,11 @@ class BundleProvenance(BaseModel): description="Exact approved sanitized recording archive used for compilation", pattern="^[a-f0-9]{64}$", ) + sanitized_from_digest: Optional[str] = Field( + default=None, + description="Verified source bundle content digest for a sanitized derivative", + pattern="^[a-f0-9]{64}$", + ) compiler_config_sha256: Optional[str] = Field( default=None, description="Canonical digest of the compiler options used for this bundle", @@ -3097,7 +3102,8 @@ class BundleManifest(BaseModel): AES-256-GCM -- both ``workflow.json`` and every ``templates/*.png`` crop. The ``file_hashes`` are always digests over the PLAINTEXT asset (sealed BEFORE encryption), so integrity re-verifies against the decrypted crops. - Additive: a v1 bundle carries no manifest and one is computed on read. + Additive: a v1 bundle carries no manifest; computing a replacement seal + requires the caller to opt into legacy migration explicitly. """ schema_version: int = SCHEMA_VERSION @@ -3211,7 +3217,10 @@ def _decrypt_template_assets(bundle: Path, key: Optional[str]) -> dict[str, byte def _verify_sealed_template_integrity( - workflow: "Workflow", stored: "BundleManifest", decrypted: dict[str, bytes] + workflow: "Workflow", + bundle: Path, + stored: "BundleManifest", + decrypted: dict[str, bytes], ) -> None: """Integrity check for an ENCRYPTED bundle, run against the DECRYPTED crops in memory (the on-disk assets are ciphertext, so the disk-based @@ -3228,6 +3237,7 @@ def _verify_sealed_template_integrity( return _bv._verify_workflow_content(workflow, stored) for rel, expected in stored.file_hashes.items(): + _bv.bundle_asset_path(bundle, rel) data = decrypted.get(rel) if data is None: raise _bv.BundleIntegrityError( @@ -3380,8 +3390,8 @@ class Workflow(BaseModel): # Sealed on ``save`` (per-asset hashes, a whole-bundle content digest, the # compiler version, and -- for a certified bundle -- the certifying policy + # status + optional expiry) and re-verified on ``load``. Additive and - # backward-compatible: a v1 bundle carries no manifest, so one is computed - # on read (see ``openadapt_flow.bundle_validation``). Excluded from the + # backward-compatible: a v1 bundle carries no manifest, and callers must + # explicitly opt into the migration path to compute one on read. Excluded from the # content digest itself (the digest lives INSIDE it). manifest: Optional["BundleManifest"] = None @@ -3396,6 +3406,20 @@ class Workflow(BaseModel): _decrypted_templates: dict[str, bytes] = PrivateAttr(default_factory=dict) # Immutable source seal + upgraded-model fingerprint; never serialized. _loaded_seal: Any = PrivateAttr(default=None) + # Process-local authority granted only after integrity verification or by + # the sanitizer's verified-source transformation. Never serialized. + _verification_capability: Any = PrivateAttr(default=None) + # Explicitly unchecked loads remain un-saveable even if a caller attaches a + # fresh, empty manifest by hand. + _unverified_load: bool = PrivateAttr(default=False) + _legacy_migration: bool = PrivateAttr(default=False) + + def model_copy(self, *, update: Any = None, deep: bool = False) -> "Workflow": + clone = super().model_copy(update=update, deep=deep) + from openadapt_flow.bundle_validation import transfer_verified_load_capability + + transfer_verified_load_capability(self, clone) + return clone @model_validator(mode="after") def _undo_ids_are_unique(self) -> "Workflow": @@ -3470,10 +3494,12 @@ def _sync_disk_templates(self, bundle: Path) -> None: encrypted save removed from disk. A no-op for a freshly-compiled bundle (no in-memory crops; the compiler already wrote the plaintext PNGs).""" for rel, data in self._decrypted_templates.items(): - path = bundle / rel + from openadapt_flow.bundle_validation import bundle_asset_path + + path = bundle_asset_path(bundle, rel) path.parent.mkdir(parents=True, exist_ok=True) path.write_bytes(data) - enc = bundle / f"{rel}.enc" + enc = bundle_asset_path(bundle, f"{rel}.enc") if enc.exists(): enc.unlink() @@ -3521,6 +3547,19 @@ def save( from openadapt_flow import bundle_validation as _bv _bv.bundle_schema_version({"schema_version": self.schema_version}) + if self._unverified_load: + raise _bv.BundleIntegrityError( + "an unchecked bundle load cannot be saved or resealed" + ) + if ( + self.manifest is not None + and self.manifest.content_digest + and not _bv.has_verified_load_capability(self) + ): + raise _bv.BundleIntegrityError( + "a manifest-bearing workflow needs its non-serializable " + "verified-load capability before it can be saved" + ) if self._loaded_seal is not None and not seal_manifest: raise _bv.BundleIntegrityError( "a loaded sealed bundle requires explicit resealing before serialization" @@ -3538,8 +3577,25 @@ def save( self, bundle, self.manifest, - decrypted_assets=(self._decrypted_templates if self.encrypted else None), + decrypted_assets=( + self._decrypted_templates if self.encrypted else None + ), ) + source = self._loaded_seal.source_bundle_dir + if source != bundle.resolve() and not self._loaded_seal.encrypted: + for rel, expected in self._loaded_seal.file_hashes: + source_asset = _bv.bundle_asset_path(source, rel) + target_asset = _bv.bundle_asset_path(bundle, rel) + if ( + not source_asset.is_file() + or _bv._sha256_file(source_asset) != expected + ): + raise _bv.BundleIntegrityError( + f"verified source asset {rel!r} changed before save-as" + ) + if not target_asset.exists(): + target_asset.parent.mkdir(parents=True, exist_ok=True) + target_asset.write_bytes(source_asset.read_bytes()) if _sealed.active_writer(bundle) is not None or _sealed.is_sealed(bundle): # A sealed bundle stays sealed whatever ``encrypt`` says: many @@ -3564,11 +3620,7 @@ def save( self.manifest = _bv.build_manifest(self, bundle) serialized = self.model_dump_json(indent=2) resealed_payload = None - if ( - self._loaded_seal is not None - and seal_manifest - and self.manifest is not None - ): + if seal_manifest and self.manifest is not None: resealed_payload = _bv.verify_original_content( json.loads(serialized), self.manifest ) @@ -3603,7 +3655,12 @@ def save( self.manifest.model_dump_json(indent=2).encode("utf-8") ) if resealed_payload is not None and self.manifest is not None: - _bv.remember_original_content(self, resealed_payload, self.manifest) + _bv.remember_original_content( + self, + resealed_payload, + self.manifest, + source_bundle_dir=bundle, + ) return path def _save_sealed_set( @@ -3640,11 +3697,7 @@ def _save_sealed_set( self.manifest = _bv.build_manifest(self, bundle, assets=assets) serialized = self.model_dump_json(indent=2).encode("utf-8") resealed_payload = None - if ( - self._loaded_seal is not None - and seal_manifest - and self.manifest is not None - ): + if seal_manifest and self.manifest is not None: from openadapt_flow import bundle_validation as _bv resealed_payload = _bv.verify_original_content( @@ -3659,7 +3712,12 @@ def _save_sealed_set( if owned: writer.finalize() if resealed_payload is not None and self.manifest is not None: - _bv.remember_original_content(self, resealed_payload, self.manifest) + _bv.remember_original_content( + self, + resealed_payload, + self.manifest, + source_bundle_dir=bundle, + ) return bundle / ("workflow.json" + _sealed.SEALED_SUFFIX) @classmethod @@ -3669,6 +3727,7 @@ def load( *, validate: bool = True, verify_integrity: bool = True, + allow_unsealed_legacy: bool = False, key: Optional[str] = None, ) -> "Workflow": """Load a bundle, migrating v1 -> v2, validating structure, and (for a @@ -3681,10 +3740,10 @@ def load( the *structural* category raises here; the effect-verification safety finding is surfaced by lint/certify, not the load path, so an existing uncertified-but-well-formed bundle still loads. - - ``verify_integrity`` (default True): if the bundle carries a sealed - manifest digest, recompute it and reject a tampered bundle. A legacy - (pre-v2) bundle has no sealed digest, so its manifest is computed - fresh and nothing is rejected. + - ``verify_integrity`` (default True): recompute a persisted sealed + digest and reject tampered content. Bundles without a persisted + digest are refused unless ``allow_unsealed_legacy=True`` is supplied + by a caller intentionally migrating a legacy artifact. - ``key`` (default None): decryption passphrase for an ENCRYPTED bundle (one saved with ``save(encrypt=True)``, present on disk as ``workflow.json.enc`` + ``templates/*.png.enc``). Resolved from ``key`` @@ -3743,12 +3802,20 @@ def load( if verify_integrity else _bv.source_canonical_payload(raw, stored.file_hashes) ) + if verify_integrity and (stored is None or not stored.content_digest): + if not allow_unsealed_legacy: + raise _bv.BundleIntegrityError( + "bundle has no persisted integrity seal; pass " + "allow_unsealed_legacy=True only for intentional legacy migration" + ) wf = cls.model_validate(_bv.migrate_bundle_dict(raw)) + wf._unverified_load = not verify_integrity if stored is not None: wf.manifest = stored - if original_payload is not None and stored is not None: - _bv.remember_original_content(wf, original_payload, stored) - + if original_payload is not None and stored is not None and verify_integrity: + _bv.remember_original_content( + wf, original_payload, stored, source_bundle_dir=bundle + ) if sealed_assets is not None: wf._decrypted_templates = sealed_assets elif bundle_encrypted: @@ -3757,12 +3824,27 @@ def load( # them via ``decrypted_template``; nothing cleartext lands on disk. wf._decrypted_templates = _decrypt_template_assets(bundle, key) + if original_payload is None and verify_integrity and allow_unsealed_legacy: + # Explicit legacy migration creates a new seal only after the + # caller accepted that the source had no integrity claim. For an + # encrypted bundle, hash the decrypted plaintext assets under + # their original names rather than the on-disk ciphertext files. + migration_assets = wf._decrypted_templates if bundle_encrypted else None + wf.manifest = _bv.build_manifest(wf, bundle, assets=migration_assets) + wf._legacy_migration = True + original_payload = _bv.source_canonical_payload( + wf.model_dump(mode="json"), wf.manifest.file_hashes + ) + _bv.remember_original_content( + wf, original_payload, wf.manifest, source_bundle_dir=bundle + ) + if verify_integrity and persisted is not None and wf.manifest is not None: if bundle_encrypted: # On-disk crops are ciphertext, so verify the sealed asset # digests against the decrypted plaintext held in memory. _verify_sealed_template_integrity( - wf, wf.manifest, wf._decrypted_templates + wf, bundle, wf.manifest, wf._decrypted_templates ) else: _bv.verify_integrity(wf, bundle, wf.manifest) diff --git a/packages/flow/openadapt_flow/qualification.py b/packages/flow/openadapt_flow/qualification.py index 9c660f640d..4920db33e4 100644 --- a/packages/flow/openadapt_flow/qualification.py +++ b/packages/flow/openadapt_flow/qualification.py @@ -1956,6 +1956,13 @@ def set_business_decision( raise QualificationError( f"the authored business decision makes the workflow invalid: {exc}" ) from exc + # This authoring API starts from a verified source, makes a deliberate + # in-memory derivative, and validates its complete serialized shape. Keep + # that source authority on the validated derivative; arbitrary callers of + # model_validate(model_dump()) remain unverified. + from openadapt_flow.bundle_validation import transfer_verified_load_capability + + transfer_verified_load_capability(candidate, validated) validation = validate_workflow(validated) if not validation.structural_ok: details = "; ".join( diff --git a/packages/flow/openadapt_flow/qualification_proposal.py b/packages/flow/openadapt_flow/qualification_proposal.py index e99b2c9fa9..f8da87b3b9 100644 --- a/packages/flow/openadapt_flow/qualification_proposal.py +++ b/packages/flow/openadapt_flow/qualification_proposal.py @@ -433,6 +433,10 @@ def admit_local_dev( raise QualificationProposalError( "initialize qualification before local-dev admission" ) + if workflow._unverified_load or workflow._legacy_migration: + raise QualificationProposalError( + "local-dev admission requires a verified persisted bundle seal" + ) stored_manifest = workflow.manifest if stored_manifest is None: raise QualificationProposalError( diff --git a/packages/flow/openadapt_flow/sanitized_artifact.py b/packages/flow/openadapt_flow/sanitized_artifact.py index a5114a6a1f..d1d7c49587 100644 --- a/packages/flow/openadapt_flow/sanitized_artifact.py +++ b/packages/flow/openadapt_flow/sanitized_artifact.py @@ -788,13 +788,18 @@ def _tree_rows(dest: Path) -> list[tuple[str, str]]: ] -def _reseal_sanitized_bundle(bundle: Path) -> None: +def _reseal_sanitized_bundle( + bundle: Path, verified_source: Any, source_bundle: Path +) -> None: """Rebuild generated bundle surfaces after a load-bearing transformation.""" from openadapt_flow.compiler.codegen import render_workflow_py from openadapt_flow.ir import Workflow workflow = Workflow.load(bundle, verify_integrity=False) + from openadapt_flow.bundle_validation import authorize_sanitized_derivative + + authorize_sanitized_derivative(workflow, verified_source, source_bundle) if workflow.manifest is not None: provenance = workflow.manifest.provenance provenance.policy_name = None @@ -869,6 +874,17 @@ def sanitize_artifact( artifacts.is_file(source / name) for name in ("workflow.json", "workflow.py", "manifest.json") ) + verified_source = None + if canonical_bundle: + from openadapt_flow.bundle_validation import BundleIntegrityError + from openadapt_flow.ir import Workflow + + try: + verified_source = Workflow.load(source) + except (BundleIntegrityError, OSError, ValueError, TypeError) as exc: + raise SanitizationError( + f"Cannot verify source bundle before sanitizing: {exc}" + ) from exc scrubber = privacy.get_scrubber() if scrubber is None: raise SanitizationError( @@ -992,7 +1008,7 @@ def sanitize_artifact( } ) if canonical_bundle and semantics == "not-preserved": - _reseal_sanitized_bundle(staging) + _reseal_sanitized_bundle(staging, verified_source, source) for entry in entries: derivative = staging / entry["path"] entry["derivative_sha256"] = _sha256_file(derivative) diff --git a/packages/flow/tests/test_bundle_original_format.py b/packages/flow/tests/test_bundle_original_format.py index 1b78c77935..10aa063990 100644 --- a/packages/flow/tests/test_bundle_original_format.py +++ b/packages/flow/tests/test_bundle_original_format.py @@ -57,6 +57,25 @@ def test_upgrade_is_explicit_deterministic_and_does_not_mutate_source(monkeypatc bundles.migrate_bundle_dict(original) +@pytest.mark.parametrize( + "asset", ["../outside.png", "/outside.png", "templates\\x.png"] +) +def test_manifest_asset_paths_cannot_escape_bundle(tmp_path, asset): + with pytest.raises(bundles.BundleIntegrityError, match="unsafe bundle asset path"): + bundles.bundle_asset_path(tmp_path / "bundle", asset) + + +def test_manifest_asset_paths_reject_symlink_escape(tmp_path): + bundle = tmp_path / "bundle" + bundle.mkdir() + outside = tmp_path / "outside" + outside.mkdir() + (bundle / "templates").symlink_to(outside, target_is_directory=True) + + with pytest.raises(bundles.BundleIntegrityError, match="escapes its directory"): + bundles.bundle_asset_path(bundle, "templates/crop.png") + + def test_original_seal_is_checked_before_current_model_construction( tmp_path, monkeypatch ): @@ -235,9 +254,7 @@ def test_unverified_load_cannot_launder_a_broken_original_seal(tmp_path): @pytest.mark.parametrize("attack", ["workflow", "asset"]) @pytest.mark.parametrize("copy_loaded", [False, True]) -def test_unverified_load_cannot_reseal_tampered_source( - tmp_path, attack, copy_loaded -): +def test_unverified_load_cannot_reseal_tampered_source(tmp_path, attack, copy_loaded): bundle = saved_bundle(tmp_path) if attack == "workflow": raw = json.loads((bundle / "workflow.json").read_bytes()) @@ -279,6 +296,65 @@ def test_authorization_summary_refuses_manifestless_bundle(tmp_path): bundle_digest(bundle) +def test_missing_seal_requires_explicit_legacy_migration_opt_in(tmp_path): + bundle = saved_bundle(tmp_path) + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw.pop("manifest") + (bundle / "workflow.json").write_text(json.dumps(raw)) + (bundle / "manifest.json").unlink() + + with pytest.raises( + bundles.BundleIntegrityError, match="no persisted integrity seal" + ): + Workflow.load(bundle) + + loaded = Workflow.load(bundle, allow_unsealed_legacy=True) + assert loaded.manifest is not None + assert loaded.manifest.content_digest + target = tmp_path / "migrated" + loaded.save(target) + reloaded = Workflow.load(target) + bundles.verify_integrity(reloaded, target, reloaded.manifest) + + +def test_model_validate_dump_does_not_recover_verified_load_capability(tmp_path): + bundle = saved_bundle(tmp_path) + loaded = Workflow.load(bundle) + clone = Workflow.model_validate(loaded.model_dump()) + + with pytest.raises(bundles.BundleIntegrityError, match="non-serializable"): + clone.save(bundle) + + +def test_authorization_display_paths_verify_bundle_before_reading(tmp_path): + bundle = saved_bundle(tmp_path) + raw = json.loads((bundle / "workflow.json").read_bytes()) + raw["name"] = "tampered display input" + (bundle / "workflow.json").write_text(json.dumps(raw)) + + from openadapt_flow.authorization_summary import _covered_by_digest, outline + + with pytest.raises(bundles.BundleIntegrityError): + outline(bundle) + report = bundle / "QUALIFICATION.md" + report.write_text("synthetic report") + assert not _covered_by_digest(bundle, report) + + +def test_verified_load_can_save_as_and_carries_its_assets(tmp_path): + source = saved_bundle(tmp_path) + loaded = Workflow.load(source) + target = tmp_path / "save-as" + + loaded.save(target) + + assert (target / "templates/button.png").read_bytes() == ( + source / "templates/button.png" + ).read_bytes() + reloaded = Workflow.load(target) + bundles.verify_integrity(reloaded, target, reloaded.manifest) + + def test_loaded_identity_refuses_replaced_asset_hashes_or_missing_manifest(tmp_path): bundle = saved_bundle(tmp_path) loaded = Workflow.load(bundle) diff --git a/packages/flow/tests/test_bundle_schema_v2.py b/packages/flow/tests/test_bundle_schema_v2.py index ae254c499e..e216484377 100644 --- a/packages/flow/tests/test_bundle_schema_v2.py +++ b/packages/flow/tests/test_bundle_schema_v2.py @@ -318,8 +318,7 @@ def test_fresh_workflow_defaults_to_v2(): def test_v1_bundle_loads_and_migrates(tmp_path): - """A raw v1 workflow.json (schema_version 1, no manifest) loads, migrates to - v2 in memory, and gets a manifest computed on read -- without breaking.""" + """Legacy bundles require explicit migration before they can be resealed.""" b = _write_bundle_dir(tmp_path) (b / "workflow.json").write_text( json.dumps( @@ -341,12 +340,17 @@ def test_v1_bundle_loads_and_migrates(tmp_path): } ) ) - wf = Workflow.load(b) + with pytest.raises(bv.BundleIntegrityError, match="no persisted integrity seal"): + Workflow.load(b) + + wf = Workflow.load(b, allow_unsealed_legacy=True) assert wf.schema_version == 2 # migrated on read assert wf.name == "legacy" assert wf.manifest is not None # computed because absent assert wf.manifest.content_digest # non-empty assert "templates/btn.png" in wf.manifest.file_hashes + wf.save(b) + assert Workflow.load(b).manifest.content_digest == wf.manifest.content_digest def test_migrate_bundle_dict_is_additive_noop_on_fields(): diff --git a/packages/flow/tests/test_business_decision.py b/packages/flow/tests/test_business_decision.py index b99c56d161..09227d6de6 100644 --- a/packages/flow/tests/test_business_decision.py +++ b/packages/flow/tests/test_business_decision.py @@ -301,6 +301,27 @@ def test_qualification_authoring_adds_one_typed_decision_and_advances_revision() assert authored.qualification.last_certification is None +def test_business_decision_authoring_preserves_verified_source_for_save(tmp_path): + workflow = _authoring_workflow() + init_project(workflow, environment=_qualification_environment()) + bundle = tmp_path / "bundle" + workflow.save(bundle) + verified = Workflow.load(bundle) + + authored = set_business_decision( + verified, + graph_id="__program__", + state_id="review", + decision=_authoring_spec(), + insert_before_state_id="accept", + ) + authored.save(bundle) + + reloaded = Workflow.load(bundle) + assert reloaded.program is not None + assert "review" in reloaded.program.states + + def test_qualification_cli_authors_and_inspects_one_exact_decision( tmp_path: Path, capsys: pytest.CaptureFixture[str], diff --git a/packages/flow/tests/test_qualification_proposal.py b/packages/flow/tests/test_qualification_proposal.py index 78c0b77bf9..25e58e073f 100644 --- a/packages/flow/tests/test_qualification_proposal.py +++ b/packages/flow/tests/test_qualification_proposal.py @@ -16,6 +16,7 @@ from PIL import Image from openadapt_flow.__main__ import main +from openadapt_flow.bundle_validation import BundleIntegrityError from openadapt_flow.ir import ( ActionKind, Anchor, @@ -219,7 +220,9 @@ def test_local_dev_admission_requires_a_verified_persisted_seal( raw.pop("manifest") (bundle / "workflow.json").write_text(json.dumps(raw), encoding="utf-8") (bundle / "manifest.json").unlink() - workflow = Workflow.load(bundle) + with pytest.raises(BundleIntegrityError, match="no persisted integrity seal"): + Workflow.load(bundle) + workflow = Workflow.load(bundle, allow_unsealed_legacy=True) proposal = propose_qualification(workflow, recording_dir=recording) accepted = accept_proposal(workflow, proposal) diff --git a/packages/flow/tests/test_sanitized_artifact.py b/packages/flow/tests/test_sanitized_artifact.py index e4ab761b5e..691d182a8f 100644 --- a/packages/flow/tests/test_sanitized_artifact.py +++ b/packages/flow/tests/test_sanitized_artifact.py @@ -349,6 +349,7 @@ def test_phi_bearing_canonical_bundle_is_scrubbed_resealed_and_loadable(tmp_path ) workflow.save(bundle) (bundle / "workflow.py").write_text(render_workflow_py(workflow), encoding="utf-8") + source_digest = Workflow.load(bundle).manifest.content_digest destination = tmp_path / "sanitized" manifest = sanitize_artifact(bundle, destination, kind="bundle") @@ -362,6 +363,10 @@ def test_phi_bearing_canonical_bundle_is_scrubbed_resealed_and_loadable(tmp_path assert b"Jane Doe" not in text_bytes assert b"MRN-123" not in text_bytes assert Workflow.load(destination).steps[0].anchor is not None + assert ( + Workflow.load(destination).manifest.provenance.sanitized_from_digest + == source_digest + ) sanitized_hints = Workflow.load(destination).backend_hints assert sanitized_hints is not None assert sanitized_hints.rdp_window_title == "[PERSON] - Appointments" From ee005ad7265ff753182017c35dce4d697d3c604f Mon Sep 17 00:00:00 2001 From: abrichr Date: Thu, 5 Nov 2026 22:18:02 -0500 Subject: [PATCH 06/10] Fix verified loop derivative resealing Signed-off-by: abrichr --- .../openadapt_flow/compiler/loop_authoring.py | 5 ++--- packages/flow/tests/test_loop_authoring.py | 22 +++++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/packages/flow/openadapt_flow/compiler/loop_authoring.py b/packages/flow/openadapt_flow/compiler/loop_authoring.py index 67f037b3f3..b4aa47e373 100644 --- a/packages/flow/openadapt_flow/compiler/loop_authoring.py +++ b/packages/flow/openadapt_flow/compiler/loop_authoring.py @@ -263,6 +263,8 @@ def author_data_driven_loop( }, ) + # Keep the verified source manifest until Workflow.save verifies the + # original bundle, then reseals the authored derivative. return body.model_copy( deep=True, update={ @@ -279,8 +281,5 @@ def author_data_driven_loop( ), ), }, - # Drop the source bundle's sealed manifest -- ``save`` reseals a - # fresh integrity/provenance manifest over the looped bundle. - "manifest": None, }, ) diff --git a/packages/flow/tests/test_loop_authoring.py b/packages/flow/tests/test_loop_authoring.py index bf7f5949bb..56c7eafefd 100644 --- a/packages/flow/tests/test_loop_authoring.py +++ b/packages/flow/tests/test_loop_authoring.py @@ -354,6 +354,28 @@ def test_authored_bundle_round_trips_through_save_load(dirs): assert len(reloaded.data_sources["worklist"].rows) == 2 +def test_verified_loaded_body_can_be_saved_as_loop_derivative(tmp_path): + from openadapt_flow import bundle_validation + + source = tmp_path / "source" + original = _typed_note_body() + original.save(source) + body = Workflow.load(source) + assert body.manifest is not None and body.manifest.content_digest + + looped = author_data_driven_loop(body, [{"note": "a"}]) + destination = tmp_path / "loop" + looped.save(destination) + + saved = Workflow.load(destination) + assert saved.manifest is not None + assert saved.manifest.content_digest != body.manifest.content_digest + assert saved.program is not None + assert len(saved.data_sources["worklist"].rows) == 1 + bundle_validation.verify_integrity(saved, destination, saved.manifest) + assert Workflow.load(source).program is None + + # =========================================================================== # 2. real-Replayer evidence: healthy multi-record run + poisoned-record HALT # =========================================================================== From 3af925d8cb094a7fa8fa4cc64b6c15d7dc8588c7 Mon Sep 17 00:00:00 2001 From: abrichr Date: Thu, 5 Nov 2026 22:46:31 -0500 Subject: [PATCH 07/10] Add verified template replacement authoring Signed-off-by: abrichr --- packages/flow/openadapt_flow/ir.py | 166 ++++++++++++++---- .../openadapt_flow/qualification_proposal.py | 2 +- .../flow/tests/test_authorization_sign.py | 5 +- .../flow/tests/test_bundle_original_format.py | 121 ++++++++++++- packages/flow/tests/test_bundle_schema_v2.py | 13 +- .../flow/tests/test_qualification_proposal.py | 2 +- packages/flow/tests/test_standard_change.py | 8 +- 7 files changed, 260 insertions(+), 57 deletions(-) diff --git a/packages/flow/openadapt_flow/ir.py b/packages/flow/openadapt_flow/ir.py index e5188032b7..811551461a 100644 --- a/packages/flow/openadapt_flow/ir.py +++ b/packages/flow/openadapt_flow/ir.py @@ -3404,6 +3404,9 @@ class Workflow(BaseModel): # from ``model_dump`` / the content digest (a private attribute). The # resolver consumes a decrypted crop via :meth:`decrypted_template`. _decrypted_templates: dict[str, bytes] = PrivateAttr(default_factory=dict) + # Authorized in-memory edits to existing template assets. The verified + # source crop cache above remains unchanged until the destination is saved. + _pending_template_replacements: dict[str, bytes] = PrivateAttr(default_factory=dict) # Immutable source seal + upgraded-model fingerprint; never serialized. _loaded_seal: Any = PrivateAttr(default=None) # Process-local authority granted only after integrity verification or by @@ -3412,7 +3415,6 @@ class Workflow(BaseModel): # Explicitly unchecked loads remain un-saveable even if a caller attaches a # fresh, empty manifest by hand. _unverified_load: bool = PrivateAttr(default=False) - _legacy_migration: bool = PrivateAttr(default=False) def model_copy(self, *, update: Any = None, deep: bool = False) -> "Workflow": clone = super().model_copy(update=update, deep=deep) @@ -3487,6 +3489,91 @@ def decrypted_templates(self) -> dict[str, bytes]: -> PNG bytes); empty for a plaintext bundle.""" return dict(self._decrypted_templates) + def stage_verified_template_replacement(self, rel: str, data: bytes) -> None: + """Stage bytes for an existing template on a verified loaded bundle. + + The original bundle and its in-memory decrypted asset cache are never + changed here. ``save`` verifies the source again before applying the + staged bytes to the destination and sealing the new content. + """ + from openadapt_flow import bundle_validation as _bv + + if not isinstance(data, bytes): + raise _bv.BundleIntegrityError("template replacement data must be bytes") + if ( + self._unverified_load + or self.manifest is None + or not self.manifest.content_digest + or self._loaded_seal is None + or not _bv.has_verified_load_capability(self) + ): + raise _bv.BundleIntegrityError( + "template replacement requires a verified loaded bundle" + ) + if not isinstance(rel, str) or not rel.startswith("templates/"): + raise _bv.BundleIntegrityError( + "template replacement must name a manifest-listed template asset" + ) + source = self._loaded_seal.source_bundle_dir + source_asset = _bv.bundle_asset_path(source, rel) + source_has_asset = ( + rel in self._decrypted_templates + if self._loaded_seal.encrypted + else source_asset.is_file() + ) + if rel not in self.manifest.file_hashes or not source_has_asset: + raise _bv.BundleIntegrityError( + "template replacement must name an existing manifest-listed asset" + ) + _bv.verify_loaded_source_integrity( + self, + source, + self.manifest, + decrypted_assets=( + self._decrypted_templates if self._loaded_seal.encrypted else None + ), + ) + self._pending_template_replacements = { + **self._pending_template_replacements, + rel: bytes(data), + } + + def _checked_template_replacements(self) -> dict[str, bytes]: + """Return staged replacements only while their loaded source is valid.""" + from openadapt_flow import bundle_validation as _bv + + replacements = dict(self._pending_template_replacements) + if not replacements: + return {} + if ( + self._unverified_load + or self.manifest is None + or not self.manifest.content_digest + or self._loaded_seal is None + or not _bv.has_verified_load_capability(self) + ): + raise _bv.BundleIntegrityError( + "staged template replacements require a verified loaded bundle" + ) + for rel in replacements: + if not isinstance(rel, str) or not rel.startswith("templates/"): + raise _bv.BundleIntegrityError( + "template replacement must name a manifest-listed template asset" + ) + source_asset = _bv.bundle_asset_path( + self._loaded_seal.source_bundle_dir, rel + ) + source_has_asset = ( + rel in self._decrypted_templates + if self._loaded_seal.encrypted + else source_asset.is_file() + ) + if rel not in self.manifest.file_hashes or not source_has_asset: + raise _bv.BundleIntegrityError( + "template replacement must name an existing manifest-listed asset" + ) + return replacements + def _sync_disk_templates(self, bundle: Path) -> None: """Materialize any in-memory plaintext crops to disk (removing a stale ``.enc`` sibling) BEFORE the manifest is (re)sealed, so a re-save hashes @@ -3547,6 +3634,11 @@ def save( from openadapt_flow import bundle_validation as _bv _bv.bundle_schema_version({"schema_version": self.schema_version}) + template_replacements = self._checked_template_replacements() + if template_replacements and not seal_manifest: + raise _bv.BundleIntegrityError( + "template replacements require a newly sealed manifest" + ) if self._unverified_load: raise _bv.BundleIntegrityError( "an unchecked bundle load cannot be saved or resealed" @@ -3568,24 +3660,24 @@ def save( bundle = Path(bundle_dir) from openadapt_flow import sealed_set as _sealed + source_assets: dict[str, bytes] = {} if self._loaded_seal is not None: if self.manifest is None: raise _bv.BundleIntegrityError( "loaded bundle lost its original integrity manifest" ) + source = self._loaded_seal.source_bundle_dir _bv.verify_loaded_source_integrity( self, - bundle, + source, self.manifest, decrypted_assets=( self._decrypted_templates if self.encrypted else None ), ) - source = self._loaded_seal.source_bundle_dir if source != bundle.resolve() and not self._loaded_seal.encrypted: for rel, expected in self._loaded_seal.file_hashes: source_asset = _bv.bundle_asset_path(source, rel) - target_asset = _bv.bundle_asset_path(bundle, rel) if ( not source_asset.is_file() or _bv._sha256_file(source_asset) != expected @@ -3593,21 +3685,36 @@ def save( raise _bv.BundleIntegrityError( f"verified source asset {rel!r} changed before save-as" ) - if not target_asset.exists(): - target_asset.parent.mkdir(parents=True, exist_ok=True) - target_asset.write_bytes(source_asset.read_bytes()) + source_assets[rel] = source_asset.read_bytes() if _sealed.active_writer(bundle) is not None or _sealed.is_sealed(bundle): # A sealed bundle stays sealed whatever ``encrypt`` says: many # commands re-save a bundle with a bare ``save(bundle)``, and that # must never turn a sealed bundle back into plaintext. - return self._save_sealed_set(bundle, key, seal_manifest=seal_manifest) + path = self._save_sealed_set( + bundle, + key, + seal_manifest=seal_manifest, + source_assets=source_assets, + template_replacements=template_replacements, + ) + self._pending_template_replacements = {} + return path bundle.mkdir(parents=True, exist_ok=True) (bundle / "templates").mkdir(exist_ok=True) + for rel, data in source_assets.items(): + target_asset = _bv.bundle_asset_path(bundle, rel) + if not target_asset.exists(): + target_asset.parent.mkdir(parents=True, exist_ok=True) + target_asset.write_bytes(data) # Restore any in-memory crops (from a prior encrypted load/save) to disk # as plaintext BEFORE the manifest hashes them; harmless no-op for a # freshly-compiled bundle whose crops are already on disk. self._sync_disk_templates(bundle) + for rel, data in template_replacements.items(): + target_asset = _bv.bundle_asset_path(bundle, rel) + target_asset.parent.mkdir(parents=True, exist_ok=True) + target_asset.write_bytes(data) if self.schema_version < SCHEMA_VERSION: self.schema_version = SCHEMA_VERSION # Reflect the at-rest state in the workflow BEFORE the manifest is sealed, @@ -3661,10 +3768,17 @@ def save( self.manifest, source_bundle_dir=bundle, ) + self._pending_template_replacements = {} return path def _save_sealed_set( - self, bundle: Path, key: Optional[str], *, seal_manifest: bool + self, + bundle: Path, + key: Optional[str], + *, + seal_manifest: bool, + source_assets: Optional[dict[str, bytes]] = None, + template_replacements: Optional[dict[str, bytes]] = None, ) -> Path: """Save into a sealed bundle (see :mod:`openadapt_flow.sealed_set`). @@ -3684,10 +3798,18 @@ def _save_sealed_set( ) writer = _sealed.SealedSetWriter(bundle, "bundle", passphrase, resume=True) assets = {rel: writer.read_bytes(rel) for rel in writer.files("templates/")} + for rel, data in (source_assets or {}).items(): + if assets.get(rel) != data: + writer.write(rel, data) + assets[rel] = data for rel, data in self._decrypted_templates.items(): if assets.get(rel) != data: writer.write(rel, data) assets[rel] = data + for rel, data in (template_replacements or {}).items(): + if assets.get(rel) != data: + writer.write(rel, data) + assets[rel] = data if self.schema_version < SCHEMA_VERSION: self.schema_version = SCHEMA_VERSION self.encrypted = True @@ -3727,7 +3849,6 @@ def load( *, validate: bool = True, verify_integrity: bool = True, - allow_unsealed_legacy: bool = False, key: Optional[str] = None, ) -> "Workflow": """Load a bundle, migrating v1 -> v2, validating structure, and (for a @@ -3742,8 +3863,8 @@ def load( uncertified-but-well-formed bundle still loads. - ``verify_integrity`` (default True): recompute a persisted sealed digest and reject tampered content. Bundles without a persisted - digest are refused unless ``allow_unsealed_legacy=True`` is supplied - by a caller intentionally migrating a legacy artifact. + digest are refused by default. ``verify_integrity=False`` permits + read-only inspection without granting verified-source capability. - ``key`` (default None): decryption passphrase for an ENCRYPTED bundle (one saved with ``save(encrypt=True)``, present on disk as ``workflow.json.enc`` + ``templates/*.png.enc``). Resolved from ``key`` @@ -3803,11 +3924,7 @@ def load( else _bv.source_canonical_payload(raw, stored.file_hashes) ) if verify_integrity and (stored is None or not stored.content_digest): - if not allow_unsealed_legacy: - raise _bv.BundleIntegrityError( - "bundle has no persisted integrity seal; pass " - "allow_unsealed_legacy=True only for intentional legacy migration" - ) + raise _bv.BundleIntegrityError("bundle has no persisted integrity seal") wf = cls.model_validate(_bv.migrate_bundle_dict(raw)) wf._unverified_load = not verify_integrity if stored is not None: @@ -3824,21 +3941,6 @@ def load( # them via ``decrypted_template``; nothing cleartext lands on disk. wf._decrypted_templates = _decrypt_template_assets(bundle, key) - if original_payload is None and verify_integrity and allow_unsealed_legacy: - # Explicit legacy migration creates a new seal only after the - # caller accepted that the source had no integrity claim. For an - # encrypted bundle, hash the decrypted plaintext assets under - # their original names rather than the on-disk ciphertext files. - migration_assets = wf._decrypted_templates if bundle_encrypted else None - wf.manifest = _bv.build_manifest(wf, bundle, assets=migration_assets) - wf._legacy_migration = True - original_payload = _bv.source_canonical_payload( - wf.model_dump(mode="json"), wf.manifest.file_hashes - ) - _bv.remember_original_content( - wf, original_payload, wf.manifest, source_bundle_dir=bundle - ) - if verify_integrity and persisted is not None and wf.manifest is not None: if bundle_encrypted: # On-disk crops are ciphertext, so verify the sealed asset diff --git a/packages/flow/openadapt_flow/qualification_proposal.py b/packages/flow/openadapt_flow/qualification_proposal.py index f8da87b3b9..d934fd22d8 100644 --- a/packages/flow/openadapt_flow/qualification_proposal.py +++ b/packages/flow/openadapt_flow/qualification_proposal.py @@ -433,7 +433,7 @@ def admit_local_dev( raise QualificationProposalError( "initialize qualification before local-dev admission" ) - if workflow._unverified_load or workflow._legacy_migration: + if workflow._unverified_load: raise QualificationProposalError( "local-dev admission requires a verified persisted bundle seal" ) diff --git a/packages/flow/tests/test_authorization_sign.py b/packages/flow/tests/test_authorization_sign.py index 5f001bc3fb..057030d7bf 100644 --- a/packages/flow/tests/test_authorization_sign.py +++ b/packages/flow/tests/test_authorization_sign.py @@ -89,7 +89,10 @@ def _bundle(root: Path) -> Path: ), ] wf = Workflow(name="clinic-intake-referral", steps=steps) - (bundle / "workflow.json").write_text(wf.model_dump_json(), encoding="utf-8") + wf.save(bundle) + verified = Workflow.load(bundle) + assert verified._loaded_seal is not None + assert verified.manifest is not None and verified.manifest.content_digest (bundle.parent / "QUALIFICATION.md").write_text(QUALIFICATION, encoding="utf-8") return bundle diff --git a/packages/flow/tests/test_bundle_original_format.py b/packages/flow/tests/test_bundle_original_format.py index 10aa063990..6bf70d0854 100644 --- a/packages/flow/tests/test_bundle_original_format.py +++ b/packages/flow/tests/test_bundle_original_format.py @@ -9,6 +9,8 @@ from PIL import Image from openadapt_flow import bundle_validation as bundles +from openadapt_flow import crypto, sealed_set +from openadapt_flow.bundle_sealing import seal_bundle from openadapt_flow.ir import ActionKind, Anchor, Step, Workflow @@ -209,6 +211,113 @@ def test_loaded_asset_tamper_refuses(tmp_path): bundles.verify_integrity(loaded, bundle, loaded.manifest) +def _bundle_files(bundle): + return { + path.relative_to(bundle): path.read_bytes() + for path in bundle.rglob("*") + if path.is_file() + } + + +def test_verified_template_replacement_saves_plaintext_derivative(tmp_path): + source = saved_bundle(tmp_path) + original_asset = (source / "templates/button.png").read_bytes() + replacement = b"replacement crop bytes" + loaded = Workflow.load(source) + derivative = loaded.model_copy() + derivative.stage_verified_template_replacement("templates/button.png", replacement) + + assert (source / "templates/button.png").read_bytes() == original_asset + assert "_pending_template_replacements" not in derivative.model_dump() + derivative.save(tmp_path / "derivative") + loaded.save(tmp_path / "unchanged") + + assert (source / "templates/button.png").read_bytes() == original_asset + assert (tmp_path / "derivative/templates/button.png").read_bytes() == replacement + assert (tmp_path / "unchanged/templates/button.png").read_bytes() == original_asset + assert Workflow.load(tmp_path / "derivative").manifest.content_digest + + +def test_verified_template_replacement_saves_encrypted_derivative( + tmp_path, monkeypatch +): + monkeypatch.setenv(crypto.ENV_KEY, "correct horse battery staple") + source = saved_bundle(tmp_path) + encrypted_source = tmp_path / "encrypted-source" + seal_bundle(source, encrypted_source) + source_snapshot = _bundle_files(encrypted_source) + replacement = b"encrypted replacement crop bytes" + loaded = Workflow.load(encrypted_source) + loaded.stage_verified_template_replacement("templates/button.png", replacement) + + destination = tmp_path / "encrypted-derivative" + with sealed_set.writing(destination, "bundle", "correct horse battery staple"): + loaded.save(destination, key="correct horse battery staple") + + assert _bundle_files(encrypted_source) == source_snapshot + assert not (destination / "templates/button.png").exists() + sealed_set.open_set(destination, "correct horse battery staple").verify_all() + reopened = Workflow.load(destination, key="correct horse battery staple") + assert reopened.decrypted_template("templates/button.png") == replacement + assert reopened.manifest.content_digest + + +def test_staged_template_replacement_refuses_changed_source(tmp_path): + source = saved_bundle(tmp_path) + loaded = Workflow.load(source) + loaded.stage_verified_template_replacement("templates/button.png", b"replacement") + (source / "templates/button.png").write_bytes(b"tampered after staging") + + destination = tmp_path / "must-not-be-created" + with pytest.raises(bundles.BundleIntegrityError): + loaded.save(destination) + assert not destination.exists() + + +@pytest.mark.parametrize("data", [7, "replacement", bytearray(b"replacement")]) +def test_template_replacement_requires_bytes(tmp_path, data): + source = saved_bundle(tmp_path) + loaded = Workflow.load(source) + with pytest.raises(bundles.BundleIntegrityError, match="data must be bytes"): + loaded.stage_verified_template_replacement("templates/button.png", data) + + +def test_template_replacement_refuses_save_without_new_seal(tmp_path): + source = saved_bundle(tmp_path) + loaded = Workflow.load(source) + loaded.stage_verified_template_replacement("templates/button.png", b"replacement") + destination = tmp_path / "must-not-be-created" + + with pytest.raises( + bundles.BundleIntegrityError, match="require a newly sealed manifest" + ): + loaded.save(destination, seal_manifest=False) + assert not destination.exists() + + +@pytest.mark.parametrize( + "rel", ["workflow.json", "templates/missing.png", "templates/../workflow.json"] +) +def test_template_replacement_requires_existing_safe_template(tmp_path, rel): + source = saved_bundle(tmp_path) + loaded = Workflow.load(source) + with pytest.raises(bundles.BundleIntegrityError): + loaded.stage_verified_template_replacement(rel, b"replacement") + + +def test_template_replacement_refuses_new_and_unchecked_workflows(tmp_path): + source = saved_bundle(tmp_path) + with pytest.raises(bundles.BundleIntegrityError): + Workflow(name="new", steps=[]).stage_verified_template_replacement( + "templates/button.png", b"replacement" + ) + unchecked = Workflow.load(source, verify_integrity=False) + with pytest.raises(bundles.BundleIntegrityError): + unchecked.stage_verified_template_replacement( + "templates/button.png", b"replacement" + ) + + def test_loaded_serialization_requires_explicit_reseal(tmp_path): bundle = saved_bundle(tmp_path) loaded = Workflow.load(bundle) @@ -248,6 +357,8 @@ def test_unverified_load_cannot_launder_a_broken_original_seal(tmp_path): loaded = Workflow.load(bundle, verify_integrity=False) with pytest.raises(bundles.BundleIntegrityError): bundles.verify_integrity(loaded, bundle, loaded.manifest) + with pytest.raises(bundles.BundleIntegrityError, match="unchecked bundle load"): + loaded.save(bundle) with pytest.raises(bundles.BundleIntegrityError): bundles.content_identity_digest(loaded, loaded.manifest.file_hashes) @@ -296,7 +407,7 @@ def test_authorization_summary_refuses_manifestless_bundle(tmp_path): bundle_digest(bundle) -def test_missing_seal_requires_explicit_legacy_migration_opt_in(tmp_path): +def test_missing_seal_cannot_be_migrated_by_loading_and_resaving(tmp_path): bundle = saved_bundle(tmp_path) raw = json.loads((bundle / "workflow.json").read_bytes()) raw.pop("manifest") @@ -308,14 +419,6 @@ def test_missing_seal_requires_explicit_legacy_migration_opt_in(tmp_path): ): Workflow.load(bundle) - loaded = Workflow.load(bundle, allow_unsealed_legacy=True) - assert loaded.manifest is not None - assert loaded.manifest.content_digest - target = tmp_path / "migrated" - loaded.save(target) - reloaded = Workflow.load(target) - bundles.verify_integrity(reloaded, target, reloaded.manifest) - def test_model_validate_dump_does_not_recover_verified_load_capability(tmp_path): bundle = saved_bundle(tmp_path) diff --git a/packages/flow/tests/test_bundle_schema_v2.py b/packages/flow/tests/test_bundle_schema_v2.py index e216484377..b7183e428b 100644 --- a/packages/flow/tests/test_bundle_schema_v2.py +++ b/packages/flow/tests/test_bundle_schema_v2.py @@ -317,8 +317,8 @@ def test_fresh_workflow_defaults_to_v2(): assert Workflow(name="x").schema_version == 2 -def test_v1_bundle_loads_and_migrates(tmp_path): - """Legacy bundles require explicit migration before they can be resealed.""" +def test_v1_bundle_without_a_seal_is_refused(tmp_path): + """Legacy bundles without a persisted seal are refused closed.""" b = _write_bundle_dir(tmp_path) (b / "workflow.json").write_text( json.dumps( @@ -343,15 +343,6 @@ def test_v1_bundle_loads_and_migrates(tmp_path): with pytest.raises(bv.BundleIntegrityError, match="no persisted integrity seal"): Workflow.load(b) - wf = Workflow.load(b, allow_unsealed_legacy=True) - assert wf.schema_version == 2 # migrated on read - assert wf.name == "legacy" - assert wf.manifest is not None # computed because absent - assert wf.manifest.content_digest # non-empty - assert "templates/btn.png" in wf.manifest.file_hashes - wf.save(b) - assert Workflow.load(b).manifest.content_digest == wf.manifest.content_digest - def test_migrate_bundle_dict_is_additive_noop_on_fields(): raw = {"name": "x", "steps": []} # no schema_version at all -> treated as v1 diff --git a/packages/flow/tests/test_qualification_proposal.py b/packages/flow/tests/test_qualification_proposal.py index 25e58e073f..fb3d8e50b1 100644 --- a/packages/flow/tests/test_qualification_proposal.py +++ b/packages/flow/tests/test_qualification_proposal.py @@ -222,7 +222,7 @@ def test_local_dev_admission_requires_a_verified_persisted_seal( (bundle / "manifest.json").unlink() with pytest.raises(BundleIntegrityError, match="no persisted integrity seal"): Workflow.load(bundle) - workflow = Workflow.load(bundle, allow_unsealed_legacy=True) + workflow = Workflow.load(bundle, verify_integrity=False) proposal = propose_qualification(workflow, recording_dir=recording) accepted = accept_proposal(workflow, proposal) diff --git a/packages/flow/tests/test_standard_change.py b/packages/flow/tests/test_standard_change.py index 546e662263..25766acd15 100644 --- a/packages/flow/tests/test_standard_change.py +++ b/packages/flow/tests/test_standard_change.py @@ -517,7 +517,9 @@ def test_a_changed_save_template_goes_to_a_person(mockmed, tmp_path) -> None: def edit(workflow: Workflow) -> None: _relabel(workflow) - (out / template).write_bytes((out / template).read_bytes() + b"\0") + workflow.stage_verified_template_replacement( + template, (out / template).read_bytes() + b"\0" + ) patched = _copy(bundle, out, edit) verdict = classify_change( @@ -900,7 +902,9 @@ def test_a_changed_file_of_another_step_goes_to_a_person(mockmed, tmp_path) -> N def edit(workflow: Workflow) -> None: _relabel(workflow) - (out / template).write_bytes((out / template).read_bytes() + b"\0") + workflow.stage_verified_template_replacement( + template, (out / template).read_bytes() + b"\0" + ) patched = _copy(bundle, out, edit) verdict = classify_change( From ec1b306e6a4c88a100d2f590dd2c0bcb73ff675f Mon Sep 17 00:00:00 2001 From: abrichr Date: Thu, 5 Nov 2026 23:16:34 -0500 Subject: [PATCH 08/10] Defer optional MCP transport until serving starts Signed-off-by: abrichr --- packages/flow/openadapt_flow/agent/cli.py | 6 +- .../flow/tests/test_agent_serve_imports.py | 133 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 packages/flow/tests/test_agent_serve_imports.py diff --git a/packages/flow/openadapt_flow/agent/cli.py b/packages/flow/openadapt_flow/agent/cli.py index caf1d279a4..66e9f6c048 100644 --- a/packages/flow/openadapt_flow/agent/cli.py +++ b/packages/flow/openadapt_flow/agent/cli.py @@ -766,10 +766,14 @@ def _legacy_runs_dir(args: argparse.Namespace) -> Optional[Path]: def _cmd_serve(args: argparse.Namespace) -> int: from openadapt_flow.agent.bridge import AgentBridge from openadapt_flow.agent.flow_service import open_attended_service - from openadapt_flow.agent.mcp import serve from openadapt_flow.agent.runner import default_flow_cli from openadapt_flow.agent.tutorial import TutorialError + def serve(*bridges: Any, **options: Any) -> None: + from openadapt_flow.agent.mcp import serve as serve_mcp + + serve_mcp(*bridges, **options) + if args.guard: return _serve_guard(args, serve) if args.verify: diff --git a/packages/flow/tests/test_agent_serve_imports.py b/packages/flow/tests/test_agent_serve_imports.py new file mode 100644 index 0000000000..cc4ef45477 --- /dev/null +++ b/packages/flow/tests/test_agent_serve_imports.py @@ -0,0 +1,133 @@ +"""Invalid serve modes need no optional transport; valid modes still delegate.""" + +from __future__ import annotations + +import builtins +import sys +from pathlib import Path +from types import ModuleType +from typing import Any + +import pytest + +from openadapt_flow.agent.cli import main + + +@pytest.mark.parametrize( + "argv", + [ + ["serve", "--teach-browser", "--url", "http://127.0.0.1:1"], + ["serve", "--teach-browser", "--bundles", "x"], + ["serve", "--tools-dir", "x"], + ], +) +def test_invalid_modes_never_attempt_optional_transport_import( + monkeypatch: pytest.MonkeyPatch, capsys: Any, argv: list[str] +) -> None: + original = builtins.__import__ + attempts: list[str] = [] + + def without_mcp(name: str, *args: Any, **kwargs: Any) -> Any: + if ( + name == "openadapt_flow.agent.mcp" + or name == "mcp" + or name.startswith("mcp.") + ): + attempts.append(name) + raise ModuleNotFoundError("synthetic optional transport unavailable") + return original(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", without_mcp) + assert main(argv) == 2 + assert "--teach-browser" in capsys.readouterr().err + assert attempts == [] + + +@pytest.fixture() +def browser_bridge(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> tuple: + events: list[str] = [] + instances: list[Any] = [] + + class BrowserBridge: + def __init__(self, *_args: Any, **_kwargs: Any) -> None: + self.programs = {} + self.tools_dir = tmp_path / "tools" + self.files_dir = tmp_path / "files" + instances.append(self) + events.append("bridge") + + def close(self) -> None: + events.append("closed") + + monkeypatch.setattr( + "openadapt_flow.agent.browser_teach.BrowserTeachBridge", BrowserBridge + ) + return events, instances + + +def _serve_args(tmp_path: Path) -> list[str]: + return [ + "serve", + "--teach-browser", + "--tools-dir", + str(tmp_path / "tools"), + "--files-dir", + str(tmp_path / "files"), + ] + + +def test_valid_mode_delegates_exact_bridges_to_transport( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path, browser_bridge: tuple +) -> None: + events, instances = browser_bridge + calls: list[tuple] = [] + transport = ModuleType("openadapt_flow.agent.mcp") + + def serve(*bridges: Any, **options: Any) -> None: + events.append("transport") + calls.append((bridges, options)) + + transport.serve = serve + monkeypatch.setitem(sys.modules, transport.__name__, transport) + assert main(_serve_args(tmp_path)) == 0 + assert calls == [((None, None, None, instances[0]), {})] + assert events == ["bridge", "transport", "closed"] + + +def test_valid_mode_preserves_missing_transport_failure_and_closes_bridge( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path, browser_bridge: tuple +) -> None: + events, _instances = browser_bridge + original = builtins.__import__ + + def without_mcp(name: str, *args: Any, **kwargs: Any) -> Any: + if name == "openadapt_flow.agent.mcp": + events.append("import") + raise ModuleNotFoundError("synthetic optional transport unavailable") + return original(name, *args, **kwargs) + + monkeypatch.setattr(builtins, "__import__", without_mcp) + with pytest.raises( + ModuleNotFoundError, match="synthetic optional transport unavailable" + ): + main(_serve_args(tmp_path)) + assert events == ["bridge", "import", "closed"] + + +def test_valid_mode_preserves_transport_error_and_closes_bridge( + monkeypatch: pytest.MonkeyPatch, tmp_path: Path, browser_bridge: tuple +) -> None: + events, instances = browser_bridge + transport = ModuleType("openadapt_flow.agent.mcp") + + def serve(*bridges: Any, **options: Any) -> None: + assert bridges == (None, None, None, instances[0]) + assert options == {} + events.append("transport") + raise RuntimeError("synthetic transport failure") + + transport.serve = serve + monkeypatch.setitem(sys.modules, transport.__name__, transport) + with pytest.raises(RuntimeError, match="synthetic transport failure"): + main(_serve_args(tmp_path)) + assert events == ["bridge", "transport", "closed"] From 4ba8dd35bb172e25b84d20eea0e32e00f0a6f8bd Mon Sep 17 00:00:00 2001 From: abrichr Date: Fri, 6 Nov 2026 00:02:13 -0500 Subject: [PATCH 09/10] fix: regenerate synthetic showcase with current bundle manifest Signed-off-by: abrichr --- .../showcase-openemr/bundle/manifest.json | 47 + .../showcase-openemr/bundle/sections.json | 139 ++ .../bundle/templates/identifiers/step_005.png | Bin 0 -> 27538 bytes .../bundle/templates/identifiers/step_008.png | Bin 0 -> 7647 bytes .../bundle/templates/identifiers/step_014.png | Bin 0 -> 8644 bytes .../bundle/templates/layout_fingerprint.json | 1373 +++++++++++++++++ .../showcase-openemr/bundle/workflow.json | 753 +++++++-- .../docs/showcase-openemr/bundle/workflow.py | 14 +- 8 files changed, 2220 insertions(+), 106 deletions(-) create mode 100644 packages/flow/docs/showcase-openemr/bundle/manifest.json create mode 100644 packages/flow/docs/showcase-openemr/bundle/sections.json create mode 100644 packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_005.png create mode 100644 packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_008.png create mode 100644 packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_014.png create mode 100644 packages/flow/docs/showcase-openemr/bundle/templates/layout_fingerprint.json diff --git a/packages/flow/docs/showcase-openemr/bundle/manifest.json b/packages/flow/docs/showcase-openemr/bundle/manifest.json new file mode 100644 index 0000000000..0078685fb1 --- /dev/null +++ b/packages/flow/docs/showcase-openemr/bundle/manifest.json @@ -0,0 +1,47 @@ +{ + "schema_version": 2, + "content_digest": "6b41860d6e3b78cdba8511ffceabd0aeb11c6571ea870820b8f6cfa352aacfe3", + "file_hashes": { + "templates/identifiers/step_005.png": "ab6795dd012009449c7bc346ffec33db8a2c54c53cfbf47f5172b9d02e50f167", + "templates/identifiers/step_008.png": "8143294215bc1fc9fc09674c15e76c57b3949992e14b4d156f06c3daf2313c3f", + "templates/identifiers/step_014.png": "883eb0b5d10bf27fbf5b4a2892560787f89f811b06f00c0c9d6d15ec795a6afb", + "templates/layout_fingerprint.json": "240259c63576a467d974ea547f37b5c02ae955ff11e8ec5ffb9d88de2ef58446", + "templates/step_000.png": "f1828b7d1644fbe316c20d5e85904149f582bc851516a4f57ab87854e09d91f6", + "templates/step_000_expect.png": "a191ba28eaf2691ea2bdbde6043f87eeea0bda377ed1dbd92ed8f99e4645c38e", + "templates/step_001_expect.png": "a368fb929154c409069b8529c4159e95009de1cd90847ef8c9e6e7217383112e", + "templates/step_002.png": "edc6f6ff053c14c201f17cca8b46ae4b91b8e80858c203f8f47425552f4a2aea", + "templates/step_002_expect.png": "b588f9b3a2ac3c47e9f3b5482cb73e841bb1fceda2dcec1f25d76639c2d7885b", + "templates/step_003_expect.png": "5d0d7095f8556037dca2486a9f65b811afc778e56424c60596f2701e749d8d20", + "templates/step_004.png": "e92c4f9a9641f85ab09240761747f296a259514da6f0fa5dcb553f4cd24973c3", + "templates/step_005.png": "e21c6ca4622199cd03c4ec569d79992e275321dd633a532a8e95f9839692cd7c", + "templates/step_005_expect.png": "c5667abd0a4634138fe14c45fb9e9db24915730f9833b380a468c192b9705553", + "templates/step_006_expect.png": "f7e7bea1e80633fdfb6d59c0fe8a21e555258467771a4aae77e04e0ae5638237", + "templates/step_007_expect.png": "b00f01ef23bb84596f854e770197f88b4a573dc595dec10f4641d5853dfae8f6", + "templates/step_008.png": "daa4ed0af4be54f6c834124d428c3cc53782bf47f078e2aed3ac66b1821cc001", + "templates/step_008_expect.png": "eb616c03c24974233e5926db0ce325efd28ba09bf7d7294b2654182056a64001", + "templates/step_013.png": "44bcea01c29ffce089359a39defec0e9e54083af01410e9c4a3f7c0d25eb3cab", + "templates/step_013_expect.png": "49573416a7ebe7aff262d82ee44dd54ee21cb4a76ba99a07e666e46b5a721446", + "templates/step_014.png": "7feb81b1126ebf4da47b3d3cde66852d45df9c5c0de92271fd5967aa2191befb", + "templates/step_014_expect.png": "4b82d961d0ab7706325dc0aaae70e0432eb7f6130871cd9bbd48fe8b26a11774", + "templates/step_015.png": "a525c1b81d24176746463b435cfa870f27ae8851918ace8d667a91cbd429c58e", + "templates/step_015_expect.png": "5e7fba0ba58b1d27497f4a1148934be51886a92c94edaa50582ac3045642f74f", + "templates/step_017.png": "1952c2c78b8fbea7f97276e5ceb9a80a1f559deadca13ebb8e1526f272c8935f", + "templates/step_017_expect.png": "3bb8ad7cd2002f295c44a7d5e57a268d7dc2c712519bccb169cf8ca8f50392b7" + }, + "provenance": { + "compiler_version": "2.6.0", + "source_recording_sha256": null, + "sanitized_from_digest": null, + "compiler_config_sha256": "d3748d5c9304e9880719b005ec461ddfd6c1ad586139bb7ae870333099d7ae86", + "created_at": "2026-11-06T04:51:24.260972+00:00", + "policy_name": null, + "certified": false, + "certification_status": null, + "certified_at": null, + "expires_at": null, + "certification_invalidated_at": null, + "certification_invalidation_reason": null, + "governed_authorization_template": null + }, + "encrypted": false +} \ No newline at end of file diff --git a/packages/flow/docs/showcase-openemr/bundle/sections.json b/packages/flow/docs/showcase-openemr/bundle/sections.json new file mode 100644 index 0000000000..71d0fe9337 --- /dev/null +++ b/packages/flow/docs/showcase-openemr/bundle/sections.json @@ -0,0 +1,139 @@ +{ + "schema": "openadapt.flow.sections/v1", + "note": "Display metadata: named groups of steps for program maps. Execution and the outcome classifier ignore this file, and it is outside the content digest and the workflow contract hash.", + "workflow_contract_sha256": "93afd9d58a95ecbe1a09a99b822a110f7be535a182759887d1b6e9d3dc3beef8", + "sections": [ + { + "id": "fields-step_000", + "title": "1 field: text", + "title_source": "fields", + "kind": "fields", + "steps": [ + "step_000", + "step_001", + "step_002", + "step_003", + "step_004", + "step_005", + "step_006", + "step_007" + ], + "fields": [ + "text" + ], + "params": [] + }, + { + "id": "save-step_008", + "title": "+ Add", + "title_source": "ui", + "kind": "save", + "steps": [ + "step_008", + "step_009", + "step_010", + "step_011", + "step_012", + "step_013", + "step_014" + ], + "fields": [], + "params": [] + }, + { + "id": "fields-step_015", + "title": "1 field: note", + "title_source": "fields", + "kind": "fields", + "steps": [ + "step_015", + "step_016" + ], + "fields": [ + "note" + ], + "params": [ + "note" + ] + }, + { + "id": "save-step_017", + "title": "Save as new message", + "title_source": "ui", + "kind": "save", + "steps": [ + "step_017" + ], + "fields": [], + "params": [] + } + ], + "compiled": [ + { + "id": "fields-step_000", + "title": "1 field: text", + "title_source": "fields", + "kind": "fields", + "steps": [ + "step_000", + "step_001", + "step_002", + "step_003", + "step_004", + "step_005", + "step_006", + "step_007" + ], + "fields": [ + "text" + ], + "params": [] + }, + { + "id": "save-step_008", + "title": "+ Add", + "title_source": "ui", + "kind": "save", + "steps": [ + "step_008", + "step_009", + "step_010", + "step_011", + "step_012", + "step_013", + "step_014" + ], + "fields": [], + "params": [] + }, + { + "id": "fields-step_015", + "title": "1 field: note", + "title_source": "fields", + "kind": "fields", + "steps": [ + "step_015", + "step_016" + ], + "fields": [ + "note" + ], + "params": [ + "note" + ] + }, + { + "id": "save-step_017", + "title": "Save as new message", + "title_source": "ui", + "kind": "save", + "steps": [ + "step_017" + ], + "fields": [], + "params": [] + } + ], + "edits": [], + "context": {} +} diff --git a/packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_005.png b/packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_005.png new file mode 100644 index 0000000000000000000000000000000000000000..00f680ad61b437886d5bee3bed50cf78aad1ece5 GIT binary patch literal 27538 zcmb5W1yq&m+BN(vKu}Voq>+*kkVcTOXz7qfK#=aPrC5}7r<63(9fEX8cbC%L@!zoA z=iTQ$`@G}($8b0l9nZ7wn%A83n)mXSlM%y0CqaiG2unizsR9I{M1nsPUPl7IBRAx0 zLJ$=s@$`|B6KwVS4L*WTv%KdUjIZL1>!s!Oy~UYE_-*dpZL6_twn={{BWWkzn?xhf zJUv_x@|Y&5KAqO)-A}5pf})MFks^!_dIQX?hMuYA&a!Lj^Z;O;**XnlKS{1+KJG&u-MHjV`w;!Z>L?O^cU~GS6B3zG$Mu8^M_~h&rgRaDyW8m zp)V}-Qe>y3@Nm`qYO}H`9ZySDrNZ6vsA_M)%X2jFDhIxIQO6N@c@88u%c7xDZf|Xy z+*D?-3^HQF53}E-+be#xGML-s`NN{t>lxkB{hX{Eiz6Frh)w%*uKL&a#!7d`QVy+7 zIhU97#vV9Bzbx`}=xgu`O^sLXmsp|xw4MtNoewy4N#i5Cp{5LpdHwbf(mo8R931Xg zuvu6zXbeAxUtD+Buk6wgjC@)AHa5oh%z?N08V2FCX9NC6nmbwORCj5Xk0w$)57WhHu{U;2@f=1$*z~`su~m93$u`R6`5AC3`SqNOo_zS%<3W!C#fpL z;y=LrDs@Y_xXAvt1|RaTGHVIRZ83qn0Tst9e}*n>p=Tp0DTBp%{RVojT49@Iz}N4F z9Ow1_d)F1ZUYxrlG0#06Z0*UnhN`MCTKQ9kTaw!&yk}^TBHA0pET_)D^y>DCH zq_^GD(~LhEb1n!e7xOHgx|ane?K9$K4H=1HV;)a%_L--xjQVJ=8s4SwA>`Lp8s}9-{qLM=`Be6OL2vzMcW4mKSAH!nDjZjor<@Y7afx`Pm;7 zD~O_d(#ghuuJXKiq@YK9UcZ0H860&dvN49&l4Y%Aa`kX;%(AwJ9@q0wyC^?n;nBr3 z@8h>$F?z*Gd38wu&d(q=aUVG7mvySZOCmPA?s9Wc&U6 z?5tD~m-pZ2gwv{A6X1KEfu5d%q&7JmuW|H;9A#a5Pd=X7 zwS(hh*t`Mlizw2WtXZ3K>REJ10WX=1Jp0xQ;xD*cN!0c3f@Y^XsHl{g#w4@J=aZzq zM4r4?*gl)Qzo>4^c;l2;?_DDRe^^VmWHhhkq}Q7-9jhS?G~KZNq3|4yN|HibtF0={ zA#wYH=RP=SKL6xTwER5@n7IY!owCb@gX2G&Mow(D>~J$xK&$4Rd#$69 zFMVseW*etwgzLw6(+{N|q#ZeJ=#q`#P;u}u@(2<1#a^AvNHFQQuA_9U+h`?w`QC-+ z#;8WH;WezPkMjGm#KMYrha{yWrdOg6C*6v#C172u!87*TFcb7U`!<>Yh8sA>-~n#t z2FNkF>)Or_q8c8Z2)P|v70#)unDC_eV@=(;c|DRmqMh#M^}|v6uYAGG$}od8cCa#A zM1diy_*Q{$0-~3h3FcMx2><;M?2lRIsarZA{iMddb`52d?atMa%GIcJxe&61$htO` zx>PT0eltMQf($bQxS;*fCc?!9BT~3-QYx}iX1RY`U{4bLkRZ1Oohz~*X2b+CyD9eJj zGCFhOAi{go&IjwV`KiKW7~5L(H7Gh`(?9hSL`2PoA6lUU{?6rz>`Bj;Ko7?e|_**pvqi z3LLzgeeLYzpm&=Tl*2oZzURi+Pq~*~ud8(22=$KnC>Gz|-pZv@c|CF8#$85RUUoxE zULLns)Lt>UGn#Y&M*EH9G0p6=XYWp2hM~!7*NE6~+b_qxXD42y{bA{O>-A5ODrHEJigNE;XItj`x6^uR{C~g z4~$DYLr#+=K$bk&COjd}wuKLECEC$iX&-$NROZd0JLAZlBA> z{k9{^wbg~iJjI1P z4H19#(I<3)`QiqGK1Ah#$gsDQPkJFGrNQ}mBKyf@H9EFRZ|S6s)U9pSA4U6Jxd0FM zKkKBX(($hRj=a5`7aE4Td46`{w_2a6lRI8{pzA-&G>b~$O1eYP8~lgdndpTkdnqF^ z({NF1D?KgC`-r7$XZDckvwdo=wb^RvbAw}A|y?QR=A-|Lpue@Cl(?WZ!O~SVE7)sWklef-hw)fZ9 zuQjI*DUx_s z@O~9zCjmi<078v}Cr^%Z^7KW+XR zE<;rPx*a06Cp5Doo#r@{f@(QbMiwR_Qj*V zZb9q%^hBlO@2G2ZsRRKOg38*pqA@8mx<+@l<~Ta^Yzd zW}+zZryC^OJ@&>{X6x~wuF&{y76uFBm74^(Dr&X+y3^XJ_CAsO;8aL3ssT}={rPi{ zJs0oqU_Z=VLRL-a@(6R#XUrV?gDqUd#p~o;W^5++y=b!1y~nzBjl4EDSm*ce_1XCn z3=u<;zQMNsi@JQU*+fxeN)*T;&5GW@5FIW{iZAzh7Y=wWAHF_^PTd}|DQQ*6*=J?%2*k(dX6@QQo?BcOfLQ{QZ6ym#;p0x75%>hAvZ zr#pH&LXHT0u)<#@Wb05(p&Tn;!Ys5RE1pb0`P#gHgSz8 z(h>ET?afVH2Lu<3k|urp-yurh<7g(T+E8 zi8PSv5Oag?+75~k0AJ`nFFPo3aUVaT_5Jh8Kzy|q>yLKOrkCcNzbyB@&y>>GwQuVl z^84&BC3$a%9(PXv_QQt{mAO@4WQ>4%B_V!qEJdo|KBtnZR=|k!2`WJXBM|{E1v#lI z;JT9bjF41ij{kEOKpO$($l$MpzH0$X*-Ybsbd$2-rp9r@uVE)8vK2|Kx#3|;fVcoC zM|woz36z%~P}p|=bi(yOSvho<*Eoj6oDf$H^YWyDStQD2o2V5od!#ke)b=Av4>>V4 zr(w9BSj5#frzrFtR%3O|o3*F;G7_IqtS-Lmnz>0HtO;DI#8ioXdfYYh&KoH8Bip_U zOIfOIxpL~273uSu7t>E{Ls;-#nVlaHOIQ9EzyhV=7oSRV&N@({SV@^DDt84;IiWo5 z>d9i>>+pntyYgDsL=;R^cKrA4O$o5qr!X$p=2PU4l9*DkzZ1mUfU1ziP8IGvmCsyq8=3=yJPzFEc^G3Wd>@MR^)oR#q32q%3RRjB0O@P6Y%f z0A7EK83RMseoBS?ce z(f6IoE$Bm>oSZ0xDE2YXwODIH30yJ%e^I?@Dbe_9Ii*2)O%~7cWh3p$_mOw*JmBPt zb>@m(Uz)pypn0t!Gxp0a(0*Sv&ia&Z!1~@^z(ZGT5mnbYpjy!fFcK;>&kEa)i~lZ} zAhG2xAgAs>sjJZf$`otJ^2LZ{gOLeaczOoI#F}ejhUtfH1UrAy5Ot$9D)JE^4263I ziEwD7E-kB4Rb0->rvWB+MGWdALY0-AfBu44RWGvBsR9DPx`|NT>@c5*9?k(O_;+^X zhZWxJ)zTTxVj`)2rO@?#!Jqxy4Mqq~nBBh`IF}#!DCX%s#*XQ^Il3!;kA4GP#+nb3 z^exzJQ9lfZQ;EuW;KGBxu(~~^x_JqFb$_GfpC7t@sT?t}Gb7^+wCCz3G+H9WM?+zU z#*d1(Vl3Z<=gFZ0CIeiJnhV{)baTwaY}$|6$=+-TKLYJ0fkTVwN9MFRMnTz~8z_MZ zVFnf^b&{Rf4=8+zfxQr1k0~TH349m_8H&h9;&R{46-LmFTYp*^KDMDZ1W3JZR(-Fo zn~gdbqT*(6>-o;~HzUR4V10s6Q4;8O!*BUqt&#!F_YhhT3D|l}dD%D6H*f4tbzZ&A zRNq)ZMgAIsT)n-rEC@MYhkT-YPrS3#J*FoKSVJQwPdL5W?=4XC|NRF5FKkS?wlb`m zzH4b~Ox2t{(U>am)V-cCy0T`yDlQ_W@iG5Br{PT^4VxLwFTg7UaAdrqF{|jW%7m}l zu21Oqt?WnWJxIU75tu3P%xLERwaP!z_BZJmV4>eJn?AUF-BzG8Q2xqC=C-@~vNWgf zGrdEWSKKpx=EZ@mA<`m0P%GJ=Y_-~XbAvM%3@~(iyWsh%3PR(x6cSa$Xp~wVe>@@M-E~4 z=%yNeBnxQ79HrHhc!^3$X{oZFQ8zso`3P7`ri%WMuyBpR-w6y$bQBqRgrBUp&-E#@ z+-moe+zf645q{>T?_F=(7uxdZ{6G6EUP;t{8!AjhQ~gH54MCFE-=ea==)Vo%`jn}> z@O7DVmtoV9zMKN3?Vfq<6+5Sp7Hb<~%Ry(2W8k5EWi zK05C?wa4M+_kN$z-oZD-4<|BuWU;+x0I#H_3wJEhY$^i2)6c=u zRm$7-UlCpzVnEGb0lx+aIG22QyNO2?{ePU=fcD15)UeH{pUH1r9m@6pog47nKa_jk zcN0E@J(T|K35z*-6Jgiog0{Z5eXkHKj2O`-qpQXaUTZ$27s)7Zea|g3iAyKusT(LF z=+~$rAP^a`(Ga8a3Wyh61xO>JF?B1!q)OonC>n(bhcWj#^<=rLr=8hptH=@(MLgXt zQ$WoZ7GA{+=Uf$gaiC(O{F&k5O(u-33mVdlS`*cg-2XHX1xA8G9*W}0%mv^3iyBL;;3aUuE~{y-h>IH z6KgDUXz*!%%^UM4*3DnB(T;lDjcK~I8JJceh8Dx9C^OjfqvQx>B|t#|pQRA=$SiKv z^J(~wxy9~ho{Qh1hj7U1dUPui0*KPg`?;%!h7g7_h4cPn$HIYM!+eHRE3e~Oy6A6S zU#3kye{;vJZF2DRsm&@lpgwK<5PE@{A-x9V^z#=m@(jp33xZ>}+ejSQgxwZhCXNQs zZT=m%=92&Pil|vm`Wtm}4e6hKVmX8Ld6f4$ElnZ|Z>&KS!o(8%LBfZdTR2J7gNCRY zyG8eN(u)d$CU~|_8%>VF!qZ}S)0tp=R$S_ePxkiXYVOJ=q&9uT7Iwvr2tCrB{&Lt$ z*)mFkPuln8qpTYeF)MaBaB>0?u%#sK;p`urYb)L6jBQ~td2KS#qg1Oj#br79&^^vy zV4R_}!j*H|2id*aKvf*+_H7sZy}lb8J?M|-3cPl;q&#j4k_3Ho>M1>OsMJ^2Q=iNl!n^axmA%2g zq$`AOVQwx@X)uqN^uqt-4Jo+`1XZRcC#J6+wabqXCE%-o*1gsz zUD0hgw8vt%ecLST4_MDFf~IoKeb8;7_}bh4vRyTOL$y{jNLsz9-8rA#+^V2a{t8=*X9NzM$VSeG zniFJ|{W0kEO=V)S{h2-|Wev8SCW6LP9h7||tj_&%lulevqJXC@Ki@jo7M0LQkwlC(jNafEC4F3^>6$))<(a$_<_{p+>5VL8hSp^f9F#h+I6 z@Y8Hs#ktO><{{t|g975$Uxs$(_2zps4CUJ4;YrVbnNiRv`$w02^|QuAjQ<)8e=+UX zYtyIAbYD}JkeqfCwZ!Fa>Tn=1T1091OJc)}i$P9r(mmA!L82@+v5%BORt%VzN+vO} z4ir65Zua&!c5*Gvu(6DgfTylrB5hWfF9@$6$0s-U|5Y|0x~mBVP!t_ZP!5}!;UwPY zeI}+s!_WGFPLQZB8EtKeDbxK72RWUgQ>R9{!+9mw)&1=1joGL5oG1H~*QRR*yt9*? zsZR{>DhcYeEd%t$kXo)b3FuYL>fa6tQM}|%PHR^Fz}LG%%!~{M<=*iztw4e+x-fJ>ho zI1lmieEh%(n>V5}?-?S9P(h-iWq1+TKtkb6Xvg5@(*W9JK+g!fp4-HY(nDaq$yVNYXE%GR7sx({;=u7o z<5&92rk1omD;^QXb^bxuud>=QX-A{aa7e1NlhEN`1?;^lY?SV00hNL1A#1S##Lgj7nyjp>IVd7PBvO6kez<+_m)s%#$K>bKDxr&iu}%>BuvJ8cVy8T7 zC;K@bZj*`Z9rZo6C%EeF<^e|okk|QneESm%2tnR6t|&D`#y@@1fHNi5oc}>A9yvXa zMWf0oD7ZohN~&n{s5odTT=KiW2sRvWM*9ZZyuDk2IFRQ2Ii#$lSsb=3vU8)Vr`N*R z0^i}@YmoW4S_8@F&oYv@Jasp}>{=E4bh>rgML>EF775@pk&ju{>hB8)QfbpRouJLPlu$Ly@7I|iT4qCxvTE-C4 z{u&48WN>V_qRToGd{P4o*w5^jX^u%vW)`}aPptDSbp_m|?#~Q9#fC1aX)DuFnxaX-slnxwN&mZG!RY4Po0bMeJ zGk4>GVte53!y-v6g`z@M|9hdC{);3hy_p~kv3mT}DL=KngyVqm*K=nW?Hl$#F7Bj%xDNmKm#fMfj+c;(N+ z22?>GlzlY&#PlFeO!k90i=P>bf2`wS6zR&w_&N$zR$|I&ed_Eu?VU$3H~jC?)jbaF z<1cic9#+m45~<_{q6;qz^X(4b4TOp4sP2GX=iZRUV0O4KKVf97b;06xal%f>(b3Ul z)mg)&d1-mjq>Q7Hn_a0T)wC4~C1qYs?&3fbDe#p=tq0mbdzE*Tzd=utHr&^;x2J5> zYOW__CY0|($`1|(~h1(rprm#}p(X8#Lr`L~WAIZ2naco&MC z8;AR3)sbvGb|2V%v>XjUS8I$|B3o@~&WK`hL$?Ytfuqpb$3o5Z;<1XoPeVQzZ(FB0 z^wVuF?b5ygjfCEgZFm>jw4%NFV$y(V<}Be+iTnYFR}L7R{*CciJqUpOx?sKxyMY?xnRQxrf|Px@y+hUGA#`RxF6x-2U4Z5 z;$p6M?@&M*@$!iweh^4%X+)A*$SDsF_TId83;!0r6aVSzLVNxY9-im<(fNAyiP#f; z2-wC0+V{4$u^9v%FO8Etb9>;YQ3A;?uZ9K)GEN2Ma}ZpE`zGh4mg7Q(kJW)239KMM zwh;|V81QZF?YSgK2S8L0;0W-8^!|`zw#UE0XuP0Iy7zQ6^6fK#JgX7&9H z&FhkDUmLl{8Nz}vq=6ga!k_!n0U+{F`mb3|5GY(9i%p&HhsyXy)uTyay7Va^2nM2f zh6#t>|1h-~-tvHYfujML-FpsLD;IZvV`Iej&emmfiV!jEVu`1~C#CWG^-cc~64L2z zUErsm)4tphfAL=Na_(sG1ccmy0F8T>%>-qJtmVdyi5u8L`9WfiX11tx(&88aGAKXo zca~3&b_l}kA$)_v4(SqT^GfWt@JGL*dw{UDpwSpp%3U1!bZv<;Rm?5cVBms{?Co!a=@MD&0zP5*@Hc-Cq`4(lTqJ0p@H zKD1~`1iS8=bD7Ap{#JPVDhYUf`qv%LTETcImzzgJzzhxi8~bo}<>NQk>wx9X&5quO zPFA^lUH{t_m6eZdIPr$pA`Bk)wX|l z4LM!NGdevNoU+dZ4brfmBQ{ZC(QzRU0`LV`54TBcc8>RcTv~`? zXj%O~7@&w;F1i1otJ5JP)tz)8Gi7qEe8qkVL~B9P3<)Gxac%e@cx8kRNIHPCCZ{u2 z<-GGJdS&KlYamJ~fPX70JP6^>p-wN5%ekz-MdZk!64xVTr9p-~9oy83(0_#okDC`} zXL}_t3CrHW*fm{*OA~q}FmVfMu3&$b6R5ObHAUX7jG$>e;DOP=z9I7=s-MtZJaU zv#$8;A7uaIuIrxR%TV3?$2D*Uep9v_6_OPQh(7nL=RzauZnyVGWMZNJ13vyAkJ!H) z*JbA&;7I-x7-mzW|}6)nksDyQ=*xmTIw|6s8R56PX6 zY*zPv{7CSusde2r@A-Kf0)7bSi~XUo{{cwkYyKdw4QY~@vvm4H_wjmxe0_&OQm zR>$VttR|dZ{=b(;84loC75rfXh?E?BPZ3m74FL2ak9gWU3^1T8Y2lNdtfmsIsI4}) zzJQj9NUTDjfo8uqgb0@X*Oi<7i>$62SRZvSD1*=x^i3CrqbB7gnU9aF9UE<*nYnM` z;S&34(-GPy$BdahjgP{YSJyt<9kJCJ<$*Yb^>*UqsR?Pf&i$Y_r}eZYHSnR!BG6`fXbpPM?z z$WX#rb3CT7fFqOpMNqm?w@!SAE|S&WwoZ+iu|bPR$IU&(WBx{EdQ=1GYnKv!n2I)E z2=^5^eW`EpYuyBgij}djIC({yB5UJ2l_1~vRI`3aNmKC(OTI*ucaPYs6uM1$@u!bZ za^id6;sXyOgYk26W!`LNSkD)Una0*pDcf$nF!Dp_S{JtVDev57;+Yin6I__DdxbStvR(a{lxj6+YZcxjtUG zv0Ab+lYBSw`;_I-7v!41^`c9z@Ci%Eb~msVbg3`7ATTl5@O02c{_+AaMr&08dARHX z30987{v_srtpK`B;mS%!N0d>sR`;J6%ZF2_A&e^1D?Ui*sjBuY49y>V+Rh_$T$0$0 zmbD}_AYkZk!Eoa;kA_Raw7!4*YE}xC{#Wnn(aC{iYXGu1p^gAe=$mA}NWWPUr6Jm> zJye-0VF~E`Wbr}n!P&`-jQJ01H>EX|#6@oE3s0OxNbLdFS-9~7uzhv^5KaiB>U(>7 zoi5JL;#?0d9l^`)^UBMmd7^75?3=`b1%n*JFt|#h`=K`} z`*{T|uJtttFq6;NDt}u99>|fc7BpGhXlWUoPKV#*D4})e0aGA4?G8M1TQMkK0z^{E ziEoTmpMq}uZnZ$CndW6GSi5=<|K^W6Bk;r2XKL%(9t&aIhk%uC9_Ax!WMD(%)u(OD zrkg>a`fTZ=fxHo@K4XIJE6aKMsEAf0YFcOvc`b>>h>}v^2V;4d)aCP6SY{q5M4t4EwuJk5D;6itguj?#KfCtZE8+&PMI8pnq$4y}au8 z5`cDdpEXt?DTQ;5=rf`^j|$$#u3MBh*v>5uM{IMR6CMVG`xPfRA7(ibMsaLa65I>3 zV>*9=j(V302M5P+<|>`98T0gEnP;ANm~U!%5FGN9Z!Onh#s4V!1L3Lsw|%m{Ga~WD z1y-hHFRJWJ4wq6Txs*WQK0;T5u+BdtZ{NQ%%=^p5X>cl3p=7Z^CYH9gR%Ud*mO4y| zF)ixHIDm^zOa0*lh|npb4uQ0zfCek_Z3Kfy0K3%2gb)}i#Ht*31Ivhqr}%Jv|M~+m zkRSVN6TB~HykrN6TnB_;W5Eb=0RLB5+!x$6z-lC~dtVT8nKhQS<^u}>?o_!|ImoFp z+=Kx+su{O-e@#@X(mMVVe+^}k^fG0VDyjwckam!n~q z;gy{l&fQT7cvE9xJ##}E!J{nxPSM!z_PMQ5!$HzOt^8{! zBF70|#B)m9tpa&Z)l4Z6-TZe85^-+_oeB{-L{REKlLtIT#)BLg&=+-fIj?8C^qj_D zryuW}B>;Jdd@L)v1@}5Rs5#lu8kaTxBb#7wz{7y@f|w+C zhM02Izlfj&wqV-Ymo)r4M_Y^28r&bdjEoqVr4!*<+X4Hr192gtoX6YA>OvcjnMlf( z1_nC^S=m}m25kRNmf(WXmGSpK15<62taLpJQyrY90e;e4GqzbOKP|1%2VkH3VcK6N zqcQ(LPzR_{q~4jJjen_kQd4Qo7}oc&%n&({d3>YFua;V8_8p07NlLEyv{#Lhd%!j* zT;PBsZlhh&Czb5L{!0vcpb=}nQilAW`&CnG^}lE>aK{crO(=w1-Ufg`f``wI0jkWu zJOc=jw+I#ublt7l4g7i43QKzhO_qz37~XbO2ITA5yI8}e7=4I4WuSeT)h3H@e-XWS zKzGlcfMiJB_cPawE{FfKT@JBP8~`B=B)op(eMRV@XaB^2ZS#LXX#nDe!ltA9VO{ls z?zD=krMcEzdyh5lOydJa`4j99ID#%od0F1ZTBRE`WaPp+v9M`FBS#Ifp}rZT(v3;a zgZ1ujH0LWrOvB}CgOlm&m8PWd1O=O1p2>6u{zp_dH+!z`H#FsY)tI!I!eZ~SUX)iY zyIG?e@&#%1|Lk{RTDdFTF08e0>pU+#^!$3IUF(7HGm0iIwy7t+MDC={X0`8=hh{ejGZ^z4i;h=#=M7SlpWwXU za{+Fgt3@hw-S>I9qvx~%DqdYGXt(!mqjS#L%A9L@R;QlsdIPxtS=diKaEm2&_>s`s z7OZuL_*M~7Ux_OkE0K=N>HRp|Fs}Q%e8Pf+ednh;XA8$^mF{P$TSl~; zPrzx@ugy^zRI8EB&P}uHNwQ>bz!kNVbg|%@n`X_+L2xU2`mZU#ctGrj85%^R03p;6 zB@<*#r(_RQRF!#(ZHUQ95B}5mySkN!6c`c32eJunw51A*BRfQwY-@bo48%&2p{qRQ z&Cb5H;h8fjx*?>^(HRl~hwF7YILqw=R5aiOUxV(@g1^|=fsWPGhT-lSi zJyox0*Cpxb@p5o$`Y?O$IqHR%ejERT<@5!6`ib;oytOgRR0GCa&njrwMlE6ppO#X; z$HbC6iRWNRc`x2hE@2#`)E4?w(>^&j<6VK$sAz7S`jB?9l=x1^X#%MpMyWxNN529P$3z8LFT$%*LRVZ-TGFJ6OypR~U|S#I^+%^gnz z>hMdUFmNwqV@XbexASm?6cqD9vIU+-9;#J8uLBY}i1M@5V6x0cvA@}@Mi zsxnik+D9iB2G?vJsF`$5AVKc#XZX&_5SrEj|5~x4(j;abxJ9HPg%@%iWZhFOKZE?r5kvusOed6h{!H+US7?6#kmtZ@fzA+Etq_nxU=fq z4rUh8&R>zZIQ-by<(~RVxn$7LC>aTMJ9?xrlk!C*X`KE$#tf6{Ri~UH^{v3M?#RdH z)ot(lnZMxXEBV-wXWbSV8arqYY=RBXD!g;86~6(enC+2bLnfMZC-@<0*FjZ6+o?;qXWLnC|Z&sXp;H14YGA)88t z40Ap*kava%7e$wDzl55anhfR#v8?$7Fzxr(CXFvwOQBcoiMu&;!#hIe5Cz%k_?z4M z_=9#83lyt6cX~TsP{QN4x=xA%z_zg|P%v3pw)Z!K9%eE+pPJ5TFEv1KPk+toQAg5> z6TZl+K&{(g8{NjR2n$!8$a6=@&5D?{FDjjOMg7R9ATIgigpE=zs;D+}HnNWZ3?Vs7uVL`^Nn8?-U5RV1BiOb;Z@lNjH4iQW#e36M&C)_sKDsSf{ z1X}3U?6}gmXfd+2QyG{F(M2Tnit@i;LnITzjNDo>;p(=f5FFmZ0fAwI!ON0Ha}_^_ z#rmTc74snfJRGydV$p|izYC=`xm<1|#4D#sC@FPHZ1f#vH$0Xmtd|sEN}==FnqT0* zjZI>N%wPBR%eA*ke&>~xWX@iVg>mtIBfJp|(-beaS$zt@M>CUBr3@h2)U>`|=g@lC z!h6xCMWWD_Rt3{_Lg?4ZilvzgwutebkH0X<-}aAQ&bG<5)aOtv+1g_y8OUkdbdHeR z79zSu-RZZd+;2aevsSs5L;XY+`MR$khPN*C>$ltBl(0yU>b1b|j{85yP^p$gXZ|0T zLYL%ako6s0OgGsX|E!fEM$)ToEyvhvpRETaCntx#$^@`v;hNR*v+u6kTfAFrt%#4G z*mD-5&=;n{JjtksWaW?y4o>LRUsQ(ri6cj0={ZcnH;D+s)UHv{v5yYRp+WxZ38B%f zccpU5HU2e!6E$>adJoICF(k?MDK0mdjn}gJvxQE`ET5}OjPu9+;vD!L%GP#8xx+=Z$~mgSyx@~P#*V=PZblq$l$}9 zNggY;)p1pIl~<`o1*j?}C%8yJBU324GMC6)Uibmj!B$i~p{(XxW%8o{Bq+3XR71qgO zX2s3>&0?PDa;ZW8{#q;2+}zy2!3rb?xPV?)s%9Hxwe`3oTdnZEgahNAzD`1J5EfjF zn~tM5I4#_ji76|G<@s!q7Y>Dx^*_TMGZzOB7j?K~P_8}!@-LSYkMdyD%OMu@O8)zd zee(4_Et|ls8%Y<@0Cyz(YXygXa-iPCUDZ#lH6Lm7hgfN!MiZ|f|Kyx>uH6qx`=BnP zf(~cX5k=hd z7a0v^-SBXv-+09xY@?4~WK8smd@8Ujizga7TVp(Ki?na#tZ?r_Pv0LcgEcI%p@C!E z2r_IshIaiNC-dA>GQNa|gayawR5iCEi@3HwId^u0Vl@S$hAg*(#L3wxV84DJIn5bv z#cgLMX}p>q-KZLyTDnb7r_*X*#NEWjkvD;lhdY{*_EthZzx7oZw^0LV((0WYqYdV$ zy}ty{!#hj6tE9voZt}Qa7Ph5YF^CMbD~YX^Tb2p77xz#0iap28O|k2EX%|bE*b3M< z?myEwVdjmtbb?1z-6NBZ8E9LGis-(?{oTQ0u;U30Ly3sb`kKM4Y~r&auWspWy60H-nM! zAMP;bJ?Zg#TARr5sNhm>zYk?@mO+79S|xk>_}KTxPD1zsRNfm`D7#(ylyNX?fbm3e^hc z0>>CrG!1%)Q~uL< z9og%N_E)5j!dNR7Q^z4uU(!l`_kEyWa^D9f>y)v3u7)v!<4`>1Gf}`MmSVdgLD;}x zIK6excLXRLiKir0Wo@^I8=eQd)_Gyq9rVldsy2VW^SQs5dwTR`dPwx`oRrMHf;n!8 z=~=~RWxwB8n3$678PN3`d^7W}(Q?fU;|oc#>f)&BgZQKr$ME4xcZm2q-F2;6R+OeU zzrLJU(s zDvx1|W&=y2i6o3LuBvH{mUe;tM3Y#x|<7lWKt7NWWdy))J)D-8{ z3O{7>05pU41`}&pPQ4dv?2Z%onpR*%L79#n>sQ)CM!86Yi_CAGxEi1ZcbK2Nb2A?OC6URGPT}p zFtlqYM05F-1UC|<-~Etsqg8N~ngC$$5GkOPZvwNca{3`g{ihIF=mp-Ugw_4*h*v&C z;An?M_nWs#3ealW$z>xnTtiOI$#pPWja?p=e}nP=WRhTmfUR@Cx+~Ywr!5Bd4$W0y zz2G}O9vh9Sp{WxN7qD3u8WtmgyPt)@e@3Faa)!odI4tVs@ecf_EqhA&T^onq_Ptv z{>)i*A%dmzlW8f4(vXtTATUu`TFhbw% z^ZKCgtU2w8nis;L=UBzD*`cEqE?P9C|L4~DzdrQ_G1;s*ix%?qymdgsde@hr{QokO zuusCCTl2qfTK(ZRPNP3lGR)EIb^a77WGo++|E9RRCy#UF@?(X#K1SS(KQL z4^{p?uYZq`yFiGKKN-($sv@lf4_}x4XWRtTU}Jni-7ZU7COkSMtgTtBcd+!e#Y^P> z;F;6LPeRg47@_Vbtai~Sb<=@E8Fe$V4HaXh3JeN^mnT#~ZzxaEjF&~6@P7Xg>d=z#c}q(Lc3o=^EA2GOgut)y_goODHvp+49TtdW*>oTT z<4xGSAtPQzd&MCkxafhbFFIz z5Tkcb(+7U&wcQ2ahzPwXEqk?%L)Q0MeiO}oAJ|&%T(;MMp~MP-JI*46VQAk}oRuXW z4IUmky-?DW!q5O;;BoQ;7g2er)TS+r*Q=aU`e3+>M4Zt4Qc7pMpvF)OD%>}u!F!J4 z?92=MvjxtI=K@VoH0cY^(=D`T+5`ZX8bmBf%kDX>GqYrI@Un~h1>7blSALNLtLq%- z=^WVFSrV4M=P)!l(0;|TbxCs9n>h<@c{8#mb+vo@dE7^TqUh#qX9mB$_k`0Gq3Ftf zO)p3DUt(yn@-u?4?9KGUa@(bTCBm*bny^rn7b6dl#ihW-s32*U1Lza_&crJtH5Kdb zaThkY1}|sA>8R7CSc%QX2ZwGdI+`XK4{|DdfaQ6fZEAoFw7a)oQA)-_yic16kdNSH zSTTD*t^>gczMcjRq2uJP7J-b5?P}GL2{fON!riB2U{IF`=Ue#-vI^ z=>G+i#!qJjWhfvSGFxH0&86eJSZRc=ANa_?7={WJ@KsD9D_eUzTl>?~BOE;632bHv#pn2sdiJ|XalwDXud$s3_%3Ud6{(zCKH zhofp+qti>zsjh}X;QX|BO$UXdmXZ6WOYQ5y|TAs9Q(eGGl z+E@*5g-D&^q_pzN3#-<#pg{LL;4R4vQ9xHisqzljUlfT!q-eb~DMHSAP4RwBy4V-j z(j`wgX5L#C+vFy91MW{^Kt@dSlfJdJNrx zDk);&XV6*L^~s1<>9ld)m>zZh__eAl&vX8#vXq#I`T(>zFE5bx@rLZ@?j4G$p}g)> zpv?S{1yzd8-1u4;)qShIK@P!1x#Sij{qXOSH1Y{%inANh)liUZm1p-Xz@kw{ew5Mm8i{#|~IY7`qM^HI)Vuy5Cs{dR^Y)~$`%ic!J zh9DzPO3K-LmD|~}@=w&_4RzB5Mr0s((r4~B!LM-yQK>@uX2rq2(mK_mR5&~Xs zaeZp%5Q6EKd1Q1h>SN&5_1HpPd!M}s1qQ3^>Zc=(B7k9F=2ts914>U$Ol6Rio#nA@ z{W)8-LSC3_6fC4=;vqY)c}CNa0Rdri{Pm}B&UBJvU+Q}V>(`pPvn`63_W3+Y=XCD& zSbb@^zT$WEq7UJ6SLe;+3zJxtZaW7DB)lg^FNC1(F{G~_3)3so-3yC%9Pj*Hbn2^H zJ$V%+j^cbOW8dQ}pR&bln2NP#6++Ma8m}$#VkEFuw+4+PVIJ3h&7_|#Kkk`_-w6f0BBuO^kx786$YK32{m z!_&0AE$xdJF1d-LX0O}V7)j#w|LrRNF_uNh;FRQ)Q1v9{NVVV5AG-JrNhA;!L=Mf;c2L}t+adAkErAE}!lw zWnMF7UeotXV=_8os5Hz>xS$mR@+{k^E!k`$ZtD5T*M0NbX@=ggbx9aw7fz-X9B%90 zN}494lZj6D{TE$=2-Rq%ag_Gt3+=vuQ!+`vr50zR#gq?n?hq{{sx|C<`xxu$>MH;I z2SWWr;od1aMpR+>gp@SU|4=buySP=qtbF{aAbBs`%qJtuiK3~iOrH-)5niy^ll9yK zL3}&IlBB>-1u`Y>8y2tXbR6Jlxy#*RvJCqm+gp9LK8vN+69$AGURn7V5cdK2yd7$K5%SuYAH|h^*`_F5h<2jubmo>c4hfL|Rvh7Xhmsd9sP?aV&TdR6v z2ag6AxHOWykskJ{{7nAh$~NVMS)IUjK=N<>U|S<~x6?rNX|zH=Y| z?ic78QP~{FBO@@|s!hk`eAI*YoIqfC_BdX;6mb=y&G0T@uM47SbW{EP*iK50CdND{ zfSX0CA5AFV3+keBK{&rI961yYgc-WpNuW)7VW=JnI1Kf3^dN`E;fI8TN#u52zNBZnO*H852DQHMIV^ZQz$Pm5(v{{)F0Tz6#yEb*V6yEO)f^7JA68Y|5VgPaR;5+J=sfI9V^t*Q%@kT-= z`P&o`OG~1R>l@$=0rBQwZmf`$nqXyJkQA4-zYJc;Fc3$snt&j^UknA8DF4d2w0Egu zi6Jk%1&8KEIHXc?MZZO271LLiC97975W-P@`6kQKLPs1s!vYl9C*<9`9Qw6eSpxQQ zr4@3%37VhV;O+a>kC`b{j{CZ};3env#cl9u?jH#9?bMJB=3UazP?+>omV0xP^O*Hl zY+)+%ka_p5l7157`K&7l-K;y=>8!)%M@*mN-m|Z$-n;TrSGq&bxoDc|xxbWH)7=~l zLQBtxmE}HH*&gf$M=Sr0=g~b1l$IAcQyZ6Bk6hdzt@@@|R6!v+f&mv>*cLF%!PR$I zM1wrIya9WCc*Ok3!}{`-Td=BPs~*;79>VJ{Emq|C9~hCB{9vP-WNs{yo}vehipjm1 zqxkR5RISDy^)5ELL#$!Ltl{6fq9nezvFn}Ei{#LcJ7~hSJA#dIcOU3N^kY8| zaW2cA3)0i2s%DQ`>?Y{Ci)%d3RH;1vx+|uk#26hGVwRk3{_ZuEI3^>l!uS zC5J3QP_g$Xkt{4Mos#w#zh#?;k#uXG)l@!D{u`p){4UZqk3E{Ov6QkBq*xMMOMU*{ zD4aDowdt8W_#<*K+AfE8Z4cC(=(vfgHRb4}bJJZ_e9HHy;I?4sWKmRT^K&w;1ws~q z5>dXG`{OmZ;m4WM)>mQ-+Uume0P?~4M0nX)D>#2&yRnhpr>&ogW@9~_fYi;jzK51Y z+;Z3E7s_En5!rWD?V+YN0zMKL_SCvYlYi^%l8dh1A{xQl+jz&_4v|9X1=jgVxDMKqqzEC%(q~@vct+87lGUtBNzfNlBwO z2Vj*}*VKT?K>4H!<#+d{H$>ze)LvYC-#O;(Z40h^aB$}=m2>lMlfM1S=hd~;OAVP) z07sY`pf9;$SByy+=(!*$ULB!?s5)$ZA?+-PVF&&%^@&HSg^YN}((;|&CwVFp4p*n^ z8oltG8^0u?NT`{{Ia+VH1MF>V}clWj1#t}k_OA#EAN-?26IG}lkFm^?OTr{+b)DVAm{Bos= zcCGrDyDBxtv()}feAGD-I%G&}5ldunb5{jL_(46C;}M6oJZ`ao*1T|=A9-ZQiX2@w zWX!=I0jD*YkgFXVH+)u)Pocr_f%V8Et!btjyKLBn1v$ZF)vvv?z7{M!9oM?>)lZR+VX*y9y zsx5ys{c%qHxxr@6VBJ*<%Qo5N4WNwffdK)k;tXWUW}>y&$YFpDdn4gqrvRUaL^n*xPL?-QgR zEWCR1ZXq40259TKjjy$^Xa3l_ogXEV{Va}!V%)Ko;7A!cU9(VNyZOqKfzWG;{HL+>V z;}Q#`PwjhF`JbjWf9tJ+(1|>VrY}Y%?g1Y{_ju8z`=?C2-fw+MkH29;D#}ovB>srn zjOh3F8`D};e?bQ`JD^i35mQ++)7TL(qk z_MSYBN^&tD7>Mf||K}QN0ZQy!mqHr+nhg;2U#w3Aiaus?kel{;@%U@#n8?h`yrpJ( z{T9uUi0LLjD%{XWOk1zw!R*_dJ5z!_vrQ>zcPz*C_o<(TgJLA?zIA9o~#erNIy%&K-6>4 z(_{0&$MlWFNGT|AF*_*`;uA#7&pd+&L!_^Scq{jTfuGZNQcZu5LQ*C;4#BR3c8P2t zEfMAuzYU76y*+pL(PNPjl)_-;thZ~W9*;zHd24aAutQ6KfcmzqVueM0OyG6M?zh8s z58endzj*i=`}AxTS$=s@j=E89yzor#C;~x!lJJax9Mg1M$d2ZH_s$GvYKH;JaWZ&s z#=Q~B9kwIcvG=WV2f*JzSW4+jira7Fv6LN;>3in$Diu`~a~%@6GkDL9WHKT(+=2HC zdE>7Yy_%ELKf0Vc%whDLIZ@E*^1YHxeUBKS`vmq`!AcbxJnG5eaU>tZ+8X7}uv9XKQ-^g-bg2#R zeYBpu%gr|6fGE1(J{wzDqbMy98R4T2NO{EUvzwcnXut-SF}q&d$!-3NZnKvua3>{x z>+H{6vCpp-w$%AM9?2NqR3*DXeg(jh*4lQugQiz_vdSo1k7ndmAF+)1B|v&VLA~bK?*(aAxx}M;vRQF=EWZRIu?u`IiA`FM+Kz& zZqk)~sjKbiYU=3Pr=;ZCH6dj*5=VyUdY`Po7!w-pY@x6vXJPlP>SJ43E>i0tUgErK zX5Z-O&vqx)dI1}RuOC&Ae_$O_{f*qGUa4QVFWUFEi`qL>9Zg^jdr1b5?O0k|aKcBE zo%J;1y+6z99~elx*O5gCO_$b|F1vVs3Z8zUESnW0Dkk>8o`%*@-^(&6Fi`tuJapWM zRpIgY?2%GGzh@0G*dWD|Vq$Ypf=7N8R^LNtKgHKWyIgy!$a^M!uT3|6$L#D#uSrf_ zt#E&Tgx?k%QT@{&)gX=)rbY>6Xr0r3gXA0V{y92&tLCE=_a_g&!rrLrF3*ug5*+T{Y zftzipiU&dCH1d@hn&+6NL`;f3wn$L(y1@PSmO<(*Gk_=UwjyqBuE$PjE3TLM@vEt+b#$1(iOMg+v4rKr58DAQ zU@r}=a4Zr__{Cori1(J#R@qy?!ISOTbF3gPGCMnJNvRX~s-CIvB)Yb&TBp2F(igAnZ|Bhu!c>i2Aq-w zHtw04o*vV^6dLa1!^6AynXU+bnU|Lr%9Cd*13afsR@YE-mv;*cBdbV#XtYS8BQ$>zU<^IMj7L%42zn5+MSG%%pt`Q*P}TS*wvJMp&m#f|-l6h>rS zvEm8wg=u*zV|*`^oW*yyNOktO<1XFGJXkUZ_@=Fpj={7d z+oG|eci|ImimpfwmJeD&Qqsb%)Wkk=xWq^YjHB|{o}l~M45qtCZ{Tsgv9LCp@u4hXS8efKW6^+~n+e@1u%I@bxZ3uMwZ{TjDHvqAI&RE_e@GhVl3y9p_aZbg5D69hvW@66 zj1iT$*x<$Y+pYugXT0nm_ zivctBywWWD?-xbjOVMG(Pf#3Am`8S9k5+R zM_q7QmizUPU3~N9pMNgp-6@Wj)Aw6qqXSAsh+t+@apdlT^GuBwCCBBd9`iY4 zHDkar6ijvxAy=3Wf$kn zOuFA4O*grxuA%Wop#z0w=XUOlniX zC0z!Us;qpVJ>g1w0@FGtlvCr}isW#!AywER%R7y0{#z5Y2j&6-7RpPX60#Y*c>`L^ lta|%XlKzkXG`vOf(~PkyBjAz&JZlS~u6jWw?acLu{{=v0s`dZ? literal 0 HcmV?d00001 diff --git a/packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_008.png b/packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_008.png new file mode 100644 index 0000000000000000000000000000000000000000..bf04a13952aacf1671e80586bb09fa649d7a5b47 GIT binary patch literal 7647 zcmb_hby!qeyWc|#0!mA(bPEnCDJjy8fV9%m4I_dGh#(-1APv&p5-K7f(%mIPcf(zx z-#Ons=iKi;_j&F=^USOrv-Z2+^{W-6tSEzvO^yvg5U!l8q$&iV-T>c;uu#C~*&H<; z1W{$mNs6nx!B(5x@AL(o^KT6Do2)R<(fMl(`#@# zmoc6lW=&r~gMlwUe|L!D2GU~vI?!ZzX2Fjp4r%Bb^nUfa13zsFuM4?#;}oaJhl>$WXYc@qLVfia@H} zmzktV`pahNp}2AkpH0QCW2x$Cj(@hMA){1P(Y#~sBIaC*opJ?@nd7~!Nd^98yc{N0 zY967K8Xa`5hsl4R=94$kgJs+_N(#PQbn7=FU(C!{L2w?S1Q||?b+_lOI0u>MXK#8KnISXxY(s5`F^&-)s)-Y`uPtAQ6NNYn(! zFoT}6x#5+LJY=+jsIE?)&Q0v>r%mrtBXTF-ei%znHI-$sQ5$V;Z^y}QrBYfSgxC3u z%lGy2(ygnGG2!cl3B8>tv~oMGiOJ5gk)6n>QlaLG<&24r+xM{?IrL*^Q}I3*7fy4x z^w9ND0~$R>)jN5f>U=Z`q4~~l zoU~ufOi6RHr3tEf3F}XbGBdxgC~?Gqt}Qv|O<#pJcAYW=RiTvhypgZpu0f{hgT>Ef zpvJ-8H%M6+#Q7mSq0IuKz0Scaa_bUAe`rzrLJ*poCw1Tvz=h~Oe)`PF08610wINl( zq9XjOh7LZ`LFz=4{o}>nwzg+; zI-b%dAz0u!t?^_e^zzEz>46mixg7?K-B(2YS527Olt!icj2!t}*;Zo11SL1zAVWuf zP#JbyGw^ARw-_5hwXG8P=Y2BX-Ivf?8E*{VoG=092ZjNkM^f`A?e?~Cs^bzd{&;;6$rT~lsH{%T`#-M5 zq99)G2o7w#s7tg~a9`8CWDjlp-%baeK|^kj3V}-S2A0Z6?gqu>Y)ZV>d=sVdKrK7dGnD7C{eL*Lzf9h#q{dMz=%(j#bg-!=wz2D< z-d99xCzQ~;!RT>3VR_XR6_NRQpyr++TleU`W=ItU^t<E0{8IT)2?`Vd~rDqy(I>7hwujQuU7)mz|jz3%@iCk8cNW(<2Xu ztE)F&91QtC=nHXaFo;3%E0Af;_+DyvEyA_lSJceNqRXqVDVupHGt;tc(Ap58@xWcl zw&iG|p;GtbIKuDD`;q}euGWexP|ey{<_6hMe+|Yy44B_j6iuouy^ObU;%(|=YfD`gv+cH84?lXK87j|31CNAtWT^7%$nqd)WEI4ZFx-RpD7u z9-Prxb4&s||NY$$H-E&DQABV*-aI++%9w`_5as@Eipr|0s`ZUHKtcPgTR#^GkI${r zB4Jw>+D++$QP*ejf}cnG)Cp&s-w+dT;k2(N`3TxQx<)y5iLAr7_p>!8OFjz|3r%t? z-7+DLg9W1bI#$p3K97ScXGQ7Mpj+3JSsny&bF03L_j{f>(9)Xk$V~tc7NI(PFY5Jc z<36Ha@Hu%L!OT)a4jmm$UfsE$NNIiQJvJq(qpf`~J(UwTtOH#kBFcWBjyl-f+>DQ@ z)YrcX~*^<4YI@>0SY }T!PWlPGs{cZte4M4j_NZTDuw#{5AURm)jx}{ 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 Qjax`gN>Q>v!qD%(00K_1(f|Me literal 0 HcmV?d00001 diff --git a/packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_014.png b/packages/flow/docs/showcase-openemr/bundle/templates/identifiers/step_014.png new file mode 100644 index 0000000000000000000000000000000000000000..5364f293988719436f343f5d135a0719d13e88c4 GIT binary patch literal 8644 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 literal 0 HcmV?d00001 diff --git a/packages/flow/docs/showcase-openemr/bundle/templates/layout_fingerprint.json b/packages/flow/docs/showcase-openemr/bundle/templates/layout_fingerprint.json new file mode 100644 index 0000000000..7efc0cd79e --- /dev/null +++ b/packages/flow/docs/showcase-openemr/bundle/templates/layout_fingerprint.json @@ -0,0 +1,1373 @@ +{ + "scale_landmarks": { + "salt": "bb3807cda1af2c351335c9a37ae817c9", + "steps": { + "step_000": [ + [ + "53a1804f7dbd272e", + 659.5, + 197.0, + 117, + 36 + ], + [ + "7b7c2bec3951786f", + 640.0, + 294.0, + 282, + 28 + ], + [ + "7d60863fb47efc3f", + 641.0, + 323.5, + 340, + 23 + ], + [ + "054335a855f21b42", + 640.0, + 354.5, + 278, + 23 + ], + [ + "7adb465e50e1788d", + 493.5, + 513.5, + 77, + 25 + ], + [ + "10936cd3d9d7275a", + 708.0, + 509.0, + 210, + 24 + ], + [ + "1da31ede23d9a4f4", + 640.0, + 567.0, + 48, + 24 + ], + [ + "220ab5efc43935cc", + 641.0, + 611.5, + 264, + 17 + ] + ], + "step_002": [ + [ + "53a1804f7dbd272e", + 659.5, + 197.0, + 117, + 36 + ], + [ + "7b7c2bec3951786f", + 640.0, + 294.0, + 282, + 28 + ], + [ + "7d60863fb47efc3f", + 641.0, + 323.5, + 340, + 23 + ], + [ + "054335a855f21b42", + 639.5, + 354.5, + 277, + 23 + ], + [ + "b015c5882dd86535", + 494.5, + 404.5, + 81, + 25 + ], + [ + "70f57380a411a4d8", + 625.0, + 404.0, + 50, + 22 + ], + [ + "7adb465e50e1788d", + 493.5, + 513.5, + 77, + 25 + ], + [ + "10936cd3d9d7275a", + 708.0, + 509.0, + 210, + 24 + ], + [ + "1da31ede23d9a4f4", + 640.0, + 567.0, + 48, + 24 + ], + [ + "220ab5efc43935cc", + 641.0, + 611.5, + 264, + 17 + ] + ], + "step_004": [ + [ + "53a1804f7dbd272e", + 659.5, + 197.0, + 117, + 36 + ], + [ + "7b7c2bec3951786f", + 640.0, + 294.0, + 282, + 28 + ], + [ + "7d60863fb47efc3f", + 641.0, + 323.5, + 340, + 23 + ], + [ + "054335a855f21b42", + 639.5, + 354.5, + 277, + 23 + ], + [ + "b015c5882dd86535", + 494.5, + 404.5, + 81, + 25 + ], + [ + "70f57380a411a4d8", + 624.5, + 404.0, + 51, + 22 + ], + [ + "657e119c8311a639", + 493.0, + 458.0, + 76, + 20 + ], + [ + "7adb465e50e1788d", + 493.0, + 513.5, + 78, + 25 + ], + [ + "10936cd3d9d7275a", + 708.5, + 510.0, + 207, + 20 + ], + [ + "1da31ede23d9a4f4", + 640.0, + 567.0, + 48, + 24 + ], + [ + "220ab5efc43935cc", + 641.0, + 611.5, + 264, + 17 + ] + ], + "step_005": [ + [ + "1677f5e2c754311c", + 544.5, + 31.0, + 991, + 22 + ], + [ + "7cd7b8195acab3ae", + 1121.5, + 32.0, + 153, + 22 + ], + [ + "80869c7ff9366539", + 69.0, + 126.5, + 64, + 17 + ], + [ + "956f52f28fd1c1e2", + 259.0, + 126.5, + 156, + 17 + ], + [ + "69f2e8851af4f2ce", + 517.0, + 170.5, + 274, + 29 + ], + [ + "0185b8f808058a34", + 1223.0, + 170.5, + 48, + 23 + ], + [ + "64f0aed699ebdb79", + 819.5, + 213.0, + 81, + 20 + ], + [ + "f32e4f3eda3a5e29", + 292.5, + 393.5, + 35, + 15 + ], + [ + "306e615b3acd952f", + 402.0, + 398.5, + 174, + 25 + ], + [ + "e0c114688c9dadbd", + 292.5, + 413.0, + 37, + 18 + ], + [ + "40e4bca9bc444d9b", + 47.5, + 425.5, + 73, + 21 + ], + [ + "170de2352353716a", + 292.0, + 433.0, + 38, + 18 + ], + [ + "8495f9b08dcfdb54", + 60.5, + 453.5, + 69, + 21 + ], + [ + "8b574777a644e62b", + 292.5, + 453.0, + 37, + 18 + ], + [ + "682a3db0e6b6fc3a", + 70.5, + 473.5, + 89, + 21 + ], + [ + "9fd9094faab8b3d3", + 292.5, + 473.0, + 37, + 18 + ], + [ + "a6ac9c15d4eda0a9", + 402.0, + 479.5, + 172, + 25 + ], + [ + "7ef3675f1a74d196", + 68.5, + 492.5, + 85, + 21 + ], + [ + "468103917a121ed6", + 292.5, + 493.5, + 37, + 19 + ], + [ + "4fe15e048482f544", + 71.0, + 511.5, + 90, + 21 + ], + [ + "9f2fa0ba578fb3ef", + 292.5, + 514.5, + 37, + 19 + ], + [ + "ffee08e56fdcb485", + 292.5, + 534.0, + 37, + 18 + ], + [ + "ed83e9ca9462e5da", + 292.5, + 554.0, + 37, + 18 + ], + [ + "492950966cf06387", + 378.5, + 558.0, + 127, + 22 + ], + [ + "bee99d679f35404b", + 111.0, + 576.5, + 90, + 23 + ], + [ + "cfb07034e7ada233", + 292.5, + 574.0, + 37, + 18 + ], + [ + "d48473165b31ca81", + 356.0, + 575.0, + 78, + 20 + ], + [ + "e37c384c207c6fbe", + 292.5, + 594.0, + 37, + 18 + ], + [ + "17a53554383d5b25", + 292.5, + 615.5, + 35, + 15 + ], + [ + "56510da7830225f6", + 292.5, + 635.5, + 39, + 17 + ], + [ + "755b381e21e23ecc", + 292.5, + 655.5, + 39, + 17 + ], + [ + "26ecb99ae5c1ce04", + 292.5, + 675.5, + 39, + 17 + ] + ], + "step_008": [ + [ + "1677f5e2c754311c", + 497.0, + 31.0, + 992, + 22 + ], + [ + "cabf57ed43dd0dfd", + 1005.0, + 31.0, + 36, + 18 + ], + [ + "b497fc64468ee106", + 142.5, + 126.5, + 281, + 17 + ], + [ + "4f2b5a9008c22648", + 383.0, + 126.5, + 128, + 17 + ], + [ + "c7dd287d91934bfd", + 54.5, + 191.5, + 109, + 23 + ], + [ + "042fbd9f2e640116", + 210.0, + 191.0, + 148, + 20 + ], + [ + "8671ff7cf8316860", + 32.5, + 256.0, + 65, + 26 + ], + [ + "6192e224859b4ebb", + 151.0, + 257.0, + 118, + 20 + ], + [ + "fa8df429b3a4f4fe", + 88.0, + 293.0, + 54, + 22 + ], + [ + "28929be4d4e862e1", + 1088.0, + 293.0, + 62, + 24 + ], + [ + "d327d2e506cd7cb9", + 54.0, + 346.0, + 108, + 20 + ], + [ + "a584933894add8d6", + 337.5, + 347.0, + 173, + 20 + ], + [ + "e7ffbcef37172935", + 569.5, + 345.5, + 115, + 23 + ], + [ + "48c5a443e3139ea6", + 863.0, + 346.0, + 168, + 20 + ], + [ + "a93939858215ef46", + 1120.5, + 346.0, + 157, + 20 + ], + [ + "0cb8400a1aae2ae0", + 27.5, + 399.0, + 55, + 24 + ], + [ + "f83063f16fd132a2", + 288.0, + 399.5, + 106, + 23 + ], + [ + "d2126cc4b4c4b150", + 815.0, + 399.5, + 104, + 23 + ], + [ + "f4bf983b8713beef", + 1072.0, + 399.5, + 88, + 21 + ], + [ + "b763bbc04b6c5a5f", + 31.5, + 443.5, + 63, + 21 + ], + [ + "6924139615e397e4", + 288.0, + 443.5, + 102, + 17 + ], + [ + "a6776f7d8b03b4e6", + 542.5, + 444.0, + 85, + 20 + ], + [ + "2487c0eed7cb17dd", + 808.0, + 444.0, + 86, + 20 + ], + [ + "4d2129a88f7a3447", + 192.5, + 482.5, + 385, + 23 + ], + [ + "245c7b8ccc09a1c8", + 182.0, + 521.0, + 362, + 20 + ], + [ + "0823239da9ef6759", + 1145.0, + 520.0, + 70, + 24 + ], + [ + "0559598e813a7649", + 1251.5, + 520.0, + 41, + 22 + ] + ], + "step_013": [ + [ + "1677f5e2c754311c", + 497.5, + 31.0, + 993, + 22 + ], + [ + "cabf57ed43dd0dfd", + 1005.5, + 31.5, + 35, + 15 + ], + [ + "6ae712120248d437", + 149.0, + 88.5, + 208, + 33 + ], + [ + "ce346f06146e6042", + 721.0, + 87.0, + 150, + 20 + ], + [ + "b91f05b117d4027d", + 140.0, + 126.0, + 188, + 20 + ], + [ + "196076afbb94116b", + 691.5, + 123.0, + 167, + 20 + ], + [ + "b07e907f5614d8fd", + 62.0, + 184.5, + 120, + 17 + ], + [ + "ec4c770f0b0e9abe", + 291.0, + 183.5, + 338, + 21 + ], + [ + "d1a90e829751aa69", + 546.0, + 184.5, + 106, + 17 + ], + [ + "2c95cf0317f658cc", + 721.5, + 185.0, + 145, + 20 + ], + [ + "2a1f3799edbb93c3", + 47.0, + 215.5, + 94, + 23 + ], + [ + "a49d0f73d0a258c7", + 477.5, + 216.0, + 167, + 20 + ], + [ + "82d1da02d03cc78f", + 935.0, + 235.5, + 138, + 23 + ], + [ + "d7984f36e8ad711d", + 48.0, + 248.0, + 96, + 24 + ], + [ + "54905652996bb3b5", + 902.5, + 282.0, + 49, + 26 + ], + [ + "09f0df70eb032fd3", + 24.5, + 299.5, + 49, + 21 + ], + [ + "bc1441321b8dc29b", + 36.5, + 369.5, + 73, + 31 + ], + [ + "c051f72945e356ba", + 253.0, + 424.5, + 506, + 19 + ], + [ + "49e69c2e8fe609ca", + 68.0, + 608.5, + 136, + 25 + ], + [ + "7ec328752484b51c", + 83.0, + 635.5, + 164, + 17 + ], + [ + "dedde2680264e1ed", + 44.0, + 681.5, + 88, + 27 + ], + [ + "723a1506fa904dd8", + 186.0, + 734.0, + 62, + 20 + ], + [ + "07458fc788a34e28", + 502.0, + 735.5, + 68, + 21 + ], + [ + "5a452bd749085368", + 277.0, + 761.5, + 554, + 21 + ] + ], + "step_014": [ + [ + "1677f5e2c754311c", + 497.0, + 31.0, + 992, + 22 + ], + [ + "cabf57ed43dd0dfd", + 1005.5, + 31.5, + 37, + 19 + ], + [ + "6ae712120248d437", + 149.0, + 88.5, + 208, + 33 + ], + [ + "ce346f06146e6042", + 721.5, + 87.5, + 151, + 25 + ], + [ + "b91f05b117d4027d", + 139.5, + 126.0, + 187, + 20 + ], + [ + "196076afbb94116b", + 691.5, + 123.0, + 167, + 20 + ], + [ + "aa21e82e662b79b9", + 58.0, + 184.0, + 114, + 18 + ], + [ + "bcd2c5c62281b1b1", + 220.0, + 184.0, + 190, + 22 + ], + [ + "4f2b5a9008c22648", + 378.5, + 184.0, + 131, + 22 + ], + [ + "028b32f23c157e38", + 536.0, + 184.5, + 86, + 17 + ], + [ + "2c95cf0317f658cc", + 729.0, + 185.0, + 146, + 20 + ], + [ + "58e84578636ab263", + 278.5, + 236.5, + 425, + 29 + ], + [ + "4bf92fd089f77839", + 204.0, + 281.5, + 134, + 23 + ], + [ + "d7f882ec077611a6", + 333.0, + 281.5, + 84, + 21 + ], + [ + "fa23e64b79fcf4fa", + 446.0, + 281.0, + 106, + 26 + ], + [ + "d5a523154c8f7de7", + 584.0, + 289.5, + 132, + 17 + ], + [ + "b4746fa52617f48f", + 111.5, + 377.5, + 51, + 21 + ], + [ + "96c259549419c5e4", + 205.5, + 376.5, + 103, + 23 + ], + [ + "db906066370a68c6", + 306.0, + 376.0, + 76, + 20 + ], + [ + "d99b3c17eafd995b", + 115.5, + 431.5, + 59, + 21 + ], + [ + "ccb0ed7897c7a2a3", + 256.0, + 431.0, + 52, + 24 + ], + [ + "8ee0d075242b8329", + 433.0, + 433.5, + 42, + 23 + ], + [ + "ec3c8e7c62a98d10", + 551.0, + 431.5, + 62, + 21 + ], + [ + "d076cc2712b27ebc", + 696.0, + 431.5, + 52, + 21 + ], + [ + "c47d214198902180", + 843.0, + 432.0, + 90, + 26 + ], + [ + "53a0aa59157a51e3", + 1037.5, + 432.0, + 77, + 24 + ] + ], + "step_015": [ + [ + "252c77e2758550e5", + 147.5, + 30.5, + 293, + 21 + ], + [ + "cabf57ed43dd0dfd", + 1010.0, + 31.5, + 30, + 19 + ], + [ + "cd8745403afc1970", + 433.5, + 48.0, + 77, + 24 + ], + [ + "6ae712120248d437", + 149.0, + 88.5, + 208, + 33 + ], + [ + "7e329305c7f5bb7d", + 453.0, + 94.5, + 140, + 25 + ], + [ + "b91f05b117d4027d", + 140.0, + 126.0, + 188, + 20 + ], + [ + "8ee0d075242b8329", + 406.5, + 144.5, + 51, + 27 + ], + [ + "aa21e82e662b79b9", + 58.5, + 184.5, + 115, + 17 + ], + [ + "dfa69328a1178e6a", + 204.0, + 185.0, + 158, + 20 + ], + [ + "40efc8161d928af4", + 445.0, + 182.0, + 92, + 24 + ], + [ + "404e38e1bab62c12", + 178.0, + 236.5, + 224, + 29 + ], + [ + "7ef3675f1a74d196", + 442.0, + 268.0, + 86, + 24 + ], + [ + "4bf92fd089f77839", + 203.5, + 281.5, + 133, + 25 + ], + [ + "b4746fa52617f48f", + 111.5, + 377.0, + 49, + 18 + ], + [ + "96c259549419c5e4", + 204.5, + 376.5, + 101, + 23 + ], + [ + "d99b3c17eafd995b", + 116.0, + 432.0, + 58, + 18 + ], + [ + "ccb0ed7897c7a2a3", + 257.0, + 431.0, + 52, + 24 + ], + [ + "53a0aa59157a51e3", + 1036.0, + 431.5, + 76, + 23 + ], + [ + "2e01bf8d564a620d", + 500.0, + 454.0, + 168, + 24 + ], + [ + "087a9545562fe021", + 947.0, + 577.0, + 44, + 22 + ] + ], + "step_017": [ + [ + "252c77e2758550e5", + 147.5, + 30.5, + 293, + 21 + ], + [ + "cabf57ed43dd0dfd", + 1010.0, + 31.5, + 30, + 19 + ], + [ + "cd8745403afc1970", + 433.5, + 48.0, + 77, + 24 + ], + [ + "6ae712120248d437", + 149.0, + 88.5, + 208, + 33 + ], + [ + "7e329305c7f5bb7d", + 453.0, + 95.0, + 140, + 24 + ], + [ + "b91f05b117d4027d", + 140.0, + 126.0, + 188, + 20 + ], + [ + "8ee0d075242b8329", + 406.5, + 144.0, + 49, + 22 + ], + [ + "aa21e82e662b79b9", + 58.5, + 184.5, + 115, + 17 + ], + [ + "dfa69328a1178e6a", + 204.0, + 185.0, + 158, + 20 + ], + [ + "40efc8161d928af4", + 445.0, + 182.0, + 92, + 24 + ], + [ + "404e38e1bab62c12", + 178.0, + 236.5, + 224, + 29 + ], + [ + "7ef3675f1a74d196", + 441.5, + 267.0, + 87, + 26 + ], + [ + "4bf92fd089f77839", + 203.5, + 281.5, + 133, + 25 + ], + [ + "b4746fa52617f48f", + 112.0, + 377.0, + 50, + 18 + ], + [ + "96c259549419c5e4", + 204.0, + 376.5, + 102, + 23 + ], + [ + "d99b3c17eafd995b", + 116.0, + 432.0, + 58, + 18 + ], + [ + "ccb0ed7897c7a2a3", + 256.5, + 431.0, + 53, + 24 + ], + [ + "53a0aa59157a51e3", + 1036.0, + 431.5, + 76, + 23 + ], + [ + "2e01bf8d564a620d", + 500.0, + 454.0, + 168, + 24 + ], + [ + "087a9545562fe021", + 947.0, + 577.0, + 44, + 22 + ] + ] + } + }, + "schema": "openadapt.layout-fingerprints/v1", + "steps": { + "step_000": { + "char_width": 9.875, + "landmarks": {}, + "lines": 12, + "profile_sha256": null, + "text_height": 24.0, + "viewport": [ + 1280, + 800 + ] + }, + "step_002": { + "char_width": 9.8125, + "landmarks": {}, + "lines": 12, + "profile_sha256": null, + "text_height": 23.5, + "viewport": [ + 1280, + 800 + ] + }, + "step_004": { + "char_width": 10.125, + "landmarks": {}, + "lines": 11, + "profile_sha256": null, + "text_height": 23, + "viewport": [ + 1280, + 800 + ] + }, + "step_005": { + "char_width": 7.8, + "landmarks": {}, + "lines": 48, + "profile_sha256": null, + "text_height": 18.0, + "viewport": [ + 1280, + 800 + ] + }, + "step_008": { + "char_width": 9.26388888888889, + "landmarks": {}, + "lines": 28, + "profile_sha256": null, + "text_height": 21.0, + "viewport": [ + 1280, + 800 + ] + }, + "step_013": { + "char_width": 9.831683168316832, + "landmarks": {}, + "lines": 27, + "profile_sha256": null, + "text_height": 21, + "viewport": [ + 1280, + 800 + ] + }, + "step_014": { + "char_width": 9.35, + "landmarks": {}, + "lines": 27, + "profile_sha256": null, + "text_height": 22, + "viewport": [ + 1280, + 800 + ] + }, + "step_015": { + "char_width": 9.5, + "landmarks": {}, + "lines": 23, + "profile_sha256": null, + "text_height": 23, + "viewport": [ + 1280, + 800 + ] + }, + "step_017": { + "char_width": 9.5, + "landmarks": {}, + "lines": 24, + "profile_sha256": null, + "text_height": 23.0, + "viewport": [ + 1280, + 800 + ] + } + } +} diff --git a/packages/flow/docs/showcase-openemr/bundle/workflow.json b/packages/flow/docs/showcase-openemr/bundle/workflow.json index a313c3ac7d..5d3f2621ce 100644 --- a/packages/flow/docs/showcase-openemr/bundle/workflow.json +++ b/packages/flow/docs/showcase-openemr/bundle/workflow.json @@ -1,11 +1,11 @@ { - "schema_version": 1, + "schema_version": 2, "name": "openemr-showcase", "recording_id": "50758ec282e94c4a9f99f69ab7415678", "contains_phi": false, "phi_scrubbed": false, "encrypted": false, - "created_at": "2026-07-14T00:51:06.482608+00:00", + "created_at": "2026-11-06T04:51:24.259258+00:00", "viewport": [ 1280, 800 @@ -47,6 +47,7 @@ "identity_template": null, "identifier_crop": null, "identifier_region": null, + "form_identity": null, "landmarks": [ { "relation": "left_of", @@ -68,7 +69,12 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -90,11 +96,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "control label has no retained consequential-write signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": false, - "identity_unarmed_reason": "row text outside the target's label is too generic (< 12 squashed chars after volatile-line filtering)" + "identity_unarmed_reason": "row text outside the target's label is too generic (< 12 squashed chars after volatile-line filtering)", + "identifier_crop_missing_reason": "identity is not armed on this step (see identity_unarmed_reason) — there is no identity evidence to crop", + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_001", @@ -104,7 +123,12 @@ "text": "admin", "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -135,11 +159,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "action has no retained consequential commit signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_002", @@ -164,6 +201,7 @@ "identity_template": null, "identifier_crop": null, "identifier_region": null, + "form_identity": null, "landmarks": [ { "relation": "below", @@ -185,7 +223,12 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -207,11 +250,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "control label has no retained consequential-write signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": false, - "identity_unarmed_reason": "row text outside the target's label is too generic (< 12 squashed chars after volatile-line filtering)" + "identity_unarmed_reason": "row text outside the target's label is too generic (< 12 squashed chars after volatile-line filtering)", + "identifier_crop_missing_reason": "identity is not armed on this step (see identity_unarmed_reason) — there is no identity evidence to crop", + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_003", @@ -221,7 +277,12 @@ "text": "pass", "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -243,11 +304,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "action has no retained consequential commit signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_004", @@ -272,6 +346,7 @@ "identity_template": null, "identifier_crop": null, "identifier_region": null, + "form_identity": null, "landmarks": [ { "relation": "below", @@ -293,7 +368,12 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -310,11 +390,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "control label has no retained consequential-write signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": false, - "identity_unarmed_reason": "the only readable row text is the target's own label (mutable evidence, excluded from identity)" + "identity_unarmed_reason": "the only readable row text is the target's own label (mutable evidence, excluded from identity)", + "identifier_crop_missing_reason": "identity is not armed on this step (see identity_unarmed_reason) — there is no identity evidence to crop", + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_005", @@ -338,12 +431,12 @@ "structured_identity": null, "identity_template": { "schema_version": 1, - "salt": "076a9e81b90d6ddda88595bd2dd19e90", - "band_len": 102, + "salt": "d87570c6fc466f1ecb27166c1678853c", + "band_len": 120, "tokens": [ { - "c": "f31da5157c6a4025", - "r": "54173ac4de425e17", + "c": "604fe9a6f95fafdc", + "r": "7efb5b032480dcef", "n": 101, "alpha": true, "name": true, @@ -353,8 +446,41 @@ "gen": false }, { - "c": "c908362266024318", - "r": "c908362266024318", + "c": "d5c99e759afd1d93", + "r": "d5c99e759afd1d93", + "n": 8, + "alpha": true, + "name": true, + "digit": false, + "idsh": false, + "glyph": false, + "gen": false + }, + { + "c": "8ddc1a63df6fb2c4", + "r": "8ddc1a63df6fb2c4", + "n": 3, + "alpha": true, + "name": true, + "digit": false, + "idsh": false, + "glyph": false, + "gen": false + }, + { + "c": "85e2b1bbd8f7ad80", + "r": "85e2b1bbd8f7ad80", + "n": 7, + "alpha": true, + "name": true, + "digit": false, + "idsh": false, + "glyph": false, + "gen": false + }, + { + "c": "04b52a257d1c57ba", + "r": "04b52a257d1c57ba", "n": 1, "alpha": true, "name": true, @@ -368,19 +494,115 @@ { "i": 0, "size": 2, - "c": "c6686c7b4153485a", - "r": "e9642945cf74a524", + "c": "32437f3d54107399", + "r": "e0407dfe77e42213", "digit": false, "name": true, - "n": 102 + "n": 109 + }, + { + "i": 0, + "size": 3, + "c": "5443d007cbe9ec72", + "r": "ef18332e11c267df", + "digit": false, + "name": true, + "n": 112 + }, + { + "i": 0, + "size": 4, + "c": "9c8baec45ba1d6dd", + "r": "e69647c80eb6b0f9", + "digit": false, + "name": true, + "n": 119 + }, + { + "i": 1, + "size": 2, + "c": "067be73eab766c11", + "r": "067be73eab766c11", + "digit": false, + "name": true, + "n": 11 + }, + { + "i": 1, + "size": 3, + "c": "7bd66617954ee928", + "r": "7bd66617954ee928", + "digit": false, + "name": true, + "n": 18 + }, + { + "i": 1, + "size": 4, + "c": "cd5694fae4015b86", + "r": "cd5694fae4015b86", + "digit": false, + "name": true, + "n": 19 + }, + { + "i": 2, + "size": 2, + "c": "6f828a356d7e2f3e", + "r": "6f828a356d7e2f3e", + "digit": false, + "name": true, + "n": 10 + }, + { + "i": 2, + "size": 3, + "c": "be1455d31c15d2f9", + "r": "be1455d31c15d2f9", + "digit": false, + "name": true, + "n": 11 + }, + { + "i": 3, + "size": 2, + "c": "114752e88efa76b7", + "r": "114752e88efa76b7", + "digit": false, + "name": true, + "n": 8 } ], "structured": null, + "signal_hashes": { + "captured_context|exact|": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|unicode_nfkc": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|casefold": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa", + "captured_context|normalized|collapse_whitespace": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|strip_punctuation": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|unicode_nfkc,casefold": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa", + "captured_context|normalized|unicode_nfkc,collapse_whitespace": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|unicode_nfkc,strip_punctuation": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|casefold,collapse_whitespace": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa", + "captured_context|normalized|casefold,strip_punctuation": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa", + "captured_context|normalized|collapse_whitespace,strip_punctuation": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|unicode_nfkc,casefold,collapse_whitespace": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa", + "captured_context|normalized|unicode_nfkc,casefold,strip_punctuation": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa", + "captured_context|normalized|unicode_nfkc,collapse_whitespace,strip_punctuation": "39e71418e93e27657a515b9a6dd0a75664d4622c485e86f1b2a6366a6bc0943b", + "captured_context|normalized|casefold,collapse_whitespace,strip_punctuation": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa", + "captured_context|normalized|unicode_nfkc,casefold,collapse_whitespace,strip_punctuation": "3d304ebc4a0599802644f4db7003aef58689498b7ce6888a48c2ff4a327739fa" + }, "param_token_indices": {}, "rests_on_confusable_identifier": false }, - "identifier_crop": null, - "identifier_region": null, + "identifier_crop": "templates/identifiers/step_005.png", + "identifier_region": [ + 49, + 20, + 1176, + 22 + ], + "form_identity": null, "landmarks": [ { "relation": "below", @@ -402,7 +624,12 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -424,11 +651,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "control label has no retained consequential-write signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": true, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_006", @@ -438,7 +678,12 @@ "text": "Phil", "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -469,11 +714,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "action has no retained consequential commit signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_007", @@ -483,7 +741,12 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": "Enter", + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -514,11 +777,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "submission/deletion key may edit locally or commit application state", + "risk_review_required": true, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_008", @@ -542,13 +818,13 @@ "structured_identity": null, "identity_template": { "schema_version": 1, - "salt": "076a9e81b90d6ddda88595bd2dd19e90", + "salt": "d87570c6fc466f1ecb27166c1678853c", "band_len": 32, "tokens": [ { - "c": "a2929a25be2ec74e", - "r": "1a87697ed567a355", - "n": 12, + "c": "8238994f99152d25", + "r": "16c1a02262b5e5a3", + "n": 9, "alpha": false, "name": false, "digit": true, @@ -557,30 +833,30 @@ "gen": false }, { - "c": "8bd929d424f946de", - "r": "c14f3daa06b03b31", - "n": 9, + "c": "ec4c7c568e6c565a", + "r": "5235ee148f0a33f6", + "n": 10, "alpha": false, "name": false, "digit": true, - "idsh": true, + "idsh": false, "glyph": false, "gen": false }, { - "c": "3eb2a6e306ae66eb", - "r": "960f3c575917b914", - "n": 10, + "c": "850b3075b37d3799", + "r": "ce31d93bb38d3937", + "n": 12, "alpha": false, "name": false, "digit": true, - "idsh": false, + "idsh": true, "glyph": false, "gen": false }, { - "c": "75e7d6b0c8b31579", - "r": "bc89869273be9f2d", + "c": "b3240eb701d23d10", + "r": "79f11b95b96b715f", "n": 1, "alpha": false, "name": false, @@ -594,17 +870,17 @@ { "i": 0, "size": 2, - "c": "ec85fa0ac01cf590", - "r": "d7f444202dcda5ec", + "c": "21fddfe16cb62e5c", + "r": "6ea0d85015b84512", "digit": true, "name": false, - "n": 21 + "n": 19 }, { "i": 0, "size": 3, - "c": "15a4b9c9025d0962", - "r": "99ea03dca99dacd3", + "c": "217c8ea66bb48bc4", + "r": "7c27aa3ef4eb4d22", "digit": true, "name": false, "n": 31 @@ -612,8 +888,8 @@ { "i": 0, "size": 4, - "c": "d2f0b3f456a67420", - "r": "e8810bee45734b17", + "c": "5c5462b5052b2fd2", + "r": "63c7142316050a72", "digit": true, "name": false, "n": 32 @@ -621,37 +897,61 @@ { "i": 1, "size": 2, - "c": "c5acf03e788c6228", - "r": "32163c0ce6b28d4d", + "c": "4c9321292b55fe4d", + "r": "5f33343017a60ec5", "digit": true, "name": false, - "n": 19 + "n": 22 }, { "i": 1, "size": 3, - "c": "8f267bdbe912dc67", - "r": "1697925105c49d5e", + "c": "2426277988080a7d", + "r": "cb48f440843d9bff", "digit": true, "name": false, - "n": 20 + "n": 23 }, { "i": 2, "size": 2, - "c": "c9dd5262c3073063", - "r": "51b932b9fa3730ef", + "c": "fb2023f8f609db56", + "r": "4efe9b2c679592f8", "digit": true, "name": false, - "n": 11 + "n": 13 } ], "structured": null, + "signal_hashes": { + "captured_context|exact|": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|unicode_nfkc": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|casefold": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|collapse_whitespace": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc", + "captured_context|normalized|unicode_nfkc,casefold": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|unicode_nfkc,collapse_whitespace": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|unicode_nfkc,strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc", + "captured_context|normalized|casefold,collapse_whitespace": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|casefold,strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc", + "captured_context|normalized|collapse_whitespace,strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc", + "captured_context|normalized|unicode_nfkc,casefold,collapse_whitespace": "3fc70f1278c0dadc98235d310ccafd6bcc75a11adc8a0cf4fae5238c8dbb0a21", + "captured_context|normalized|unicode_nfkc,casefold,strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc", + "captured_context|normalized|unicode_nfkc,collapse_whitespace,strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc", + "captured_context|normalized|casefold,collapse_whitespace,strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc", + "captured_context|normalized|unicode_nfkc,casefold,collapse_whitespace,strip_punctuation": "b1316243c93e96a96f76597b7b4ee225549d6ef578eb8564e1900acebbff50cc" + }, "param_token_indices": {}, "rests_on_confusable_identifier": false }, - "identifier_crop": null, - "identifier_region": null, + "identifier_crop": "templates/identifiers/step_008.png", + "identifier_region": [ + 237, + 434, + 802, + 20 + ], + "form_identity": null, "landmarks": [ { "relation": "above", @@ -673,7 +973,12 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -704,11 +1009,24 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "control label has no retained consequential-write signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": true, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_009", @@ -718,18 +1036,36 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": 0, "scroll_dy": 400, "expect": [], "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "action has no retained consequential commit signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_010", @@ -739,18 +1075,36 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": 0, "scroll_dy": 400, "expect": [], "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "action has no retained consequential commit signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_011", @@ -760,18 +1114,36 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": 0, "scroll_dy": 400, "expect": [], "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "action has no retained consequential commit signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_012", @@ -781,22 +1153,40 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": 0, "scroll_dy": 400, "expect": [], "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "action has no retained consequential commit signal", + "risk_review_required": false, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": null, - "identity_unarmed_reason": null + "identity_unarmed_reason": null, + "identifier_crop_missing_reason": null, + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_013", - "intent": "click at (815, 369)", + "intent": "click recorded visual target", "action": "click", "anchor": { "template": "templates/step_013.png", @@ -817,6 +1207,7 @@ "identity_template": null, "identifier_crop": null, "identifier_region": null, + "form_identity": null, "landmarks": [ { "relation": "right_of", @@ -838,7 +1229,12 @@ "text": null, "param": null, "secret": false, + "field_label": null, + "selection_commit_key": null, + "selection_region": null, "key": null, + "modifiers": [], + "drag_end_anchor": null, "scroll_dx": null, "scroll_dy": null, "expect": [ @@ -869,15 +1265,28 @@ "effects": [], "api_binding": null, "risk": "reversible", + "risk_explanation": "unlabelled pointer control may be navigation, focus, or a state change", + "risk_review_required": true, "wait_until": null, "guard": null, + "read": null, + "read_gate": null, + "surface": null, + "opens": [], + "surface_origin": null, + "closes_surface": false, + "dialog": null, "timeout_s": 10.0, "identity_armed": false, - "identity_unarmed_reason": "row text outside the target's label is too generic (< 12 squashed chars after volatile-line filtering)" + "identity_unarmed_reason": "row text outside the target's label is too generic (< 12 squashed chars after volatile-line filtering)", + "identifier_crop_missing_reason": "identity is not armed on this step (see identity_unarmed_reason) — there is no identity evidence to crop", + "hover": null, + "submit_binding": null, + "submit_binding_unbound_reason": null }, { "id": "step_014", - "intent": "click '+Add [reversible] flow.type_text(params["note"]) - # step_017: click 'Save as new messag' [irreversible] + # step_017: click 'Save as new message' [irreversible] # landmark: 'Smith, Billy' (above, 191px) # landmark: 'Active' (left_of, 234px) flow.click( template="templates/step_017.png", click_point=(489, 452), region=(409, 420, 160, 64), - ocr_text="Save as new messag", + ocr_text="Save as new message", ) # expect region_stable region=(0, 0, 1280, 800) phash='c0d91b9db162c6a7' (tol 16) # expect text_present text='CalendarFinderFlowRecallsMessagesPatientFeesModulesInventoryProceduresAdminReportsMiscellaneousPopups' From eda4f6477e13abe5d17958251d8e66417779c574 Mon Sep 17 00:00:00 2001 From: abrichr Date: Sat, 7 Nov 2026 04:01:24 -0500 Subject: [PATCH 10/10] fix(flow): reject save-as asset collisions safely --- packages/flow/openadapt_flow/ir.py | 44 +++++++++ .../flow/tests/test_bundle_original_format.py | 97 ++++++++++++++++++- 2 files changed, 139 insertions(+), 2 deletions(-) diff --git a/packages/flow/openadapt_flow/ir.py b/packages/flow/openadapt_flow/ir.py index 811551461a..1494379336 100644 --- a/packages/flow/openadapt_flow/ir.py +++ b/packages/flow/openadapt_flow/ir.py @@ -3687,6 +3687,35 @@ def save( ) source_assets[rel] = source_asset.read_bytes() + # A save-as destination may already contain assets. Never let a + # destination-owned file silently replace the verified source asset in + # the new manifest. Check every collision before creating directories, + # writing bundle files, or sealing the derivative. + accepted_asset_bytes: dict[str, set[bytes]] = {} + for asset_map in ( + source_assets, + self._decrypted_templates, + template_replacements, + ): + for rel, data in asset_map.items(): + accepted_asset_bytes.setdefault(rel, set()).add(data) + + for rel, expected_bytes in accepted_asset_bytes.items(): + target_asset = _bv.bundle_asset_path(bundle, rel) + if not target_asset.exists(): + continue + try: + matches_source = ( + target_asset.is_file() + and target_asset.read_bytes() in expected_bytes + ) + except OSError: + matches_source = False + if not matches_source: + raise _bv.BundleIntegrityError( + f"save-as destination asset {rel!r} conflicts with verified source" + ) + if _sealed.active_writer(bundle) is not None or _sealed.is_sealed(bundle): # A sealed bundle stays sealed whatever ``encrypt`` says: many # commands re-save a bundle with a bare ``save(bundle)``, and that @@ -3786,6 +3815,7 @@ def _save_sealed_set( writer, so no plaintext of this workflow touches the disk. Assets come from the in-memory crop map and from crops already sealed in the set. """ + from openadapt_flow import bundle_validation as _bv from openadapt_flow import sealed_set as _sealed writer = _sealed.active_writer(bundle) @@ -3798,6 +3828,20 @@ def _save_sealed_set( ) writer = _sealed.SealedSetWriter(bundle, "bundle", passphrase, resume=True) assets = {rel: writer.read_bytes(rel) for rel in writer.files("templates/")} + accepted_asset_bytes: dict[str, set[bytes]] = {} + for asset_map in ( + source_assets or {}, + self._decrypted_templates, + template_replacements or {}, + ): + for rel, data in asset_map.items(): + accepted_asset_bytes.setdefault(rel, set()).add(data) + for rel, existing_bytes in assets.items(): + expected_bytes = accepted_asset_bytes.get(rel) + if expected_bytes is not None and existing_bytes not in expected_bytes: + raise _bv.BundleIntegrityError( + f"save-as destination asset {rel!r} conflicts with verified source" + ) for rel, data in (source_assets or {}).items(): if assets.get(rel) != data: writer.write(rel, data) diff --git a/packages/flow/tests/test_bundle_original_format.py b/packages/flow/tests/test_bundle_original_format.py index 6bf70d0854..f82cf3fcfa 100644 --- a/packages/flow/tests/test_bundle_original_format.py +++ b/packages/flow/tests/test_bundle_original_format.py @@ -14,10 +14,10 @@ from openadapt_flow.ir import ActionKind, Anchor, Step, Workflow -def saved_bundle(tmp_path): +def saved_bundle(tmp_path, *, template_color="white"): bundle = tmp_path / "bundle" (bundle / "templates").mkdir(parents=True) - Image.new("RGB", (20, 20), "white").save(bundle / "templates/button.png") + Image.new("RGB", (20, 20), template_color).save(bundle / "templates/button.png") workflow = Workflow( name="original", steps=[ @@ -238,6 +238,99 @@ def test_verified_template_replacement_saves_plaintext_derivative(tmp_path): assert Workflow.load(tmp_path / "derivative").manifest.content_digest +def test_save_as_refuses_foreign_existing_template_before_writing(tmp_path): + source = saved_bundle(tmp_path) + source_asset = (source / "templates/button.png").read_bytes() + loaded = Workflow.load(source) + destination = tmp_path / "preexisting-destination" + (destination / "templates").mkdir(parents=True) + foreign_asset = b"foreign destination template bytes" + target_asset = destination / "templates/button.png" + target_asset.write_bytes(foreign_asset) + + with pytest.raises( + bundles.BundleIntegrityError, + match=r"save-as destination asset.*conflicts with verified source", + ): + loaded.save(destination) + + assert target_asset.read_bytes() == foreign_asset + destination_entries = sorted( + path.relative_to(destination).as_posix() for path in destination.rglob("*") + ) + assert destination_entries == ["templates", "templates/button.png"] + assert not (destination / "workflow.json").exists() + assert not (destination / "manifest.json").exists() + assert (source / "templates/button.png").read_bytes() == source_asset + + +def test_save_as_accepts_matching_existing_template(tmp_path): + source = saved_bundle(tmp_path) + source_asset = (source / "templates/button.png").read_bytes() + loaded = Workflow.load(source) + destination = tmp_path / "matching-destination" + (destination / "templates").mkdir(parents=True) + target_asset = destination / "templates/button.png" + target_asset.write_bytes(source_asset) + + loaded.save(destination) + + reopened = Workflow.load(destination) + bundles.verify_integrity(reopened, destination, reopened.manifest) + assert target_asset.read_bytes() == source_asset + + +def test_encrypted_save_as_refuses_foreign_existing_template_before_writing( + tmp_path, monkeypatch +): + monkeypatch.setenv(crypto.ENV_KEY, "correct horse battery staple") + source = saved_bundle(tmp_path / "source") + encrypted_source = tmp_path / "encrypted-source" + seal_bundle(source, encrypted_source) + loaded = Workflow.load(encrypted_source, key="correct horse battery staple") + + destination = tmp_path / "foreign-destination" + (destination / "templates").mkdir(parents=True) + foreign_asset = b"foreign destination template bytes" + target_asset = destination / "templates/button.png" + target_asset.write_bytes(foreign_asset) + + with pytest.raises( + bundles.BundleIntegrityError, + match=r"save-as destination asset.*conflicts with verified source", + ): + loaded.save(destination) + + assert target_asset.read_bytes() == foreign_asset + assert not (destination / "workflow.json").exists() + assert not (destination / "manifest.json").exists() + + +def test_encrypted_save_as_refuses_foreign_asset_in_sealed_destination( + tmp_path, monkeypatch +): + monkeypatch.setenv(crypto.ENV_KEY, "correct horse battery staple") + key = "correct horse battery staple" + source = saved_bundle(tmp_path / "source") + encrypted_source = tmp_path / "encrypted-source" + seal_bundle(source, encrypted_source) + loaded = Workflow.load(encrypted_source, key=key) + + # Build a valid, differently hashed asset before its manifest is recorded. + foreign_plain = saved_bundle(tmp_path / "foreign-plain", template_color="black") + foreign_destination = tmp_path / "foreign-sealed" + seal_bundle(foreign_plain, foreign_destination) + before = _bundle_files(foreign_destination) + + with pytest.raises( + bundles.BundleIntegrityError, + match=r"save-as destination asset.*conflicts with verified source", + ): + loaded.save(foreign_destination, key=key) + + assert _bundle_files(foreign_destination) == before + + def test_verified_template_replacement_saves_encrypted_derivative( tmp_path, monkeypatch ):