From c397dce86700dc51b274f6e2d9fb6f48fad27151 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:53 -0700 Subject: [PATCH 01/87] chore(release): bump pyproject version to 0.5.0 Packaging metadata must track the v0.5 policy manifest and contract surface. --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 08cebdb..887d82e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "akta-protocol" -version = "0.4.0" +version = "0.5.0" description = "Open Scientific Action Protocol — reference kernel for scientific action admissibility" readme = "README.md" license = { text = "MIT" } From 9ea5680966ef66182474b5125bbaeed25fc26a4c Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:53 -0700 Subject: [PATCH 02/87] chore(release): expose __version__ 0.5.0 in akta package Runtime version checks and adapters rely on the package string matching policy v0.5. --- akta/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/akta/__init__.py b/akta/__init__.py index 52ad6db..132b865 100644 --- a/akta/__init__.py +++ b/akta/__init__.py @@ -4,7 +4,7 @@ from akta.gate import AKTAGate from akta.records import AKTADecision, AKTARecord -__version__ = "0.4.0" +__version__ = "0.5.0" __all__ = [ "AKTAGate", From 2fe73ecb1d1345480a7aee13dd30f3aafaf55dac Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:54 -0700 Subject: [PATCH 03/87] feat(policy): refresh policy_manifest for v0.5 file set Manifest pins hashed policy artifacts for integrity verification at load time. --- policy/policy_manifest.yaml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/policy/policy_manifest.yaml b/policy/policy_manifest.yaml index 4faeec2..06d15e1 100644 --- a/policy/policy_manifest.yaml +++ b/policy/policy_manifest.yaml @@ -1,17 +1,17 @@ -version: akta-policy-manifest-v0.4 -bundle_version: akta-core-v0.4 +version: akta-policy-manifest-v0.5 +bundle_version: akta-core-v0.5 files: - action_ontology.yaml: sha256:e2617ec6fa354523b373d8024cd5706476c6e577464bebba84758d6d51dd6875 - responsibility_levels.yaml: sha256:d935ab8f59cae4ac4383520b226372d4cb62d3e2b0ccc3ee96ed994068959f16 - evidence_states.yaml: sha256:180a38a32d8c02ecfd58e73513787fdc8d3569f4d2bb9aeb2c4568fa06f5802d - validation_statuses.yaml: sha256:6b0333e571e274736fc5ef9a6bfd1e3128bde74db6ced0c31d75dfe2bb9409aa - verification_statuses.yaml: sha256:ae38f15f21e199644042beb2b3735e00f3f73fb43c82c341033f8ccb58c0ee92 - deployment_profiles.yaml: sha256:4db5ab39429799225e460ee5548ca119f3b9b30102551aacf9ef6d9e300f2915 - admissibility_matrix.yaml: sha256:60e7515a7d4a770b1b1b23d7522c91dbebb86ae4a15d525258563b4e8de04a7d - evidence_to_action_matrix.yaml: sha256:2aeb759832066fec1391948d11d678b1341f5bc3d5273c583a8ffe103e7a4f5b - evidence_to_action_rules.yaml: sha256:fba5212cc191fa7fb92068b074649c41502fd6c075c3ed9fa215a8643c1b849b - default_tool_registry.yaml: sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c - tool_to_requested_scope.yaml: sha256:c46431af11da57194791293144c17bcb8e8ea88f636b1f333022a39e2f2a2cbb + action_ontology.yaml: sha256:c2ac6ac345552737b9bb8e0786f0886319419ac218e5162267a337d33568e6fa + responsibility_levels.yaml: sha256:e98d3c5488575364f4b5ac9cbf48754f873475235843c5c8405473444e1ac461 + evidence_states.yaml: sha256:80439bb5819f324d8a86635115b5c4e58906e98d23950e43a222ef61bfa6934c + validation_statuses.yaml: sha256:1cdadb65eb0f5697ab0a70984b3f1fd0b6c26286e16665b45ede620ae1c4548c + verification_statuses.yaml: sha256:3b09dcc6c66a52a756c3dd054bc7f7e073bd78187606ed2c8855e1348d6a2128 + deployment_profiles.yaml: sha256:16e6046f802989ec6706747bf553a5b763bcb7de7abc65ec791ca7e20b6906b7 + admissibility_matrix.yaml: sha256:0b06d1574bb82f9dc39d2860cb805a585994d0487d93a81d659e695ee56971e0 + evidence_to_action_matrix.yaml: sha256:5e8916ceff5c8e29f49e4ebc0edee3382a67c1be156b9cfd9fcc7e667bbfe15f + evidence_to_action_rules.yaml: sha256:72175f5f4cfd6f5cd713098515af9bbf17cb6c6bbc258b5593d1a6b0e12197e2 + default_tool_registry.yaml: sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a + tool_to_requested_scope.yaml: sha256:c85beea0595b98fc913b8e5b90d22155bf74afb08937edf8147472c6749873cc signature: algorithm: HMAC-SHA256 - value: 1c5dd780958c82e5b519813c729506301b7788e99d242cc00b64b7702d696244 + value: 738460a0c413a5f092c0eb4cf16f8263c6be429ce398fc5efdfab22bd0155c50 From 9576d210476833847e59a51052750705a790f3d8 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:54 -0700 Subject: [PATCH 04/87] feat(scripts): add regenerate_policy_manifest helper Automates manifest regeneration so release edits stay consistent with on-disk YAML. --- scripts/regenerate_policy_manifest.py | 54 +++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 scripts/regenerate_policy_manifest.py diff --git a/scripts/regenerate_policy_manifest.py b/scripts/regenerate_policy_manifest.py new file mode 100644 index 0000000..f5a3afe --- /dev/null +++ b/scripts/regenerate_policy_manifest.py @@ -0,0 +1,54 @@ +"""Regenerate policy_manifest.yaml after policy file changes (dev use).""" + +from __future__ import annotations + +import hashlib +import hmac +from pathlib import Path + +import yaml + +from akta.hash import hash_file_content + +ROOT = Path(__file__).resolve().parent.parent +POLICY_DIR = ROOT / "policy" + +MANIFEST_FILES = [ + "action_ontology.yaml", + "responsibility_levels.yaml", + "evidence_states.yaml", + "validation_statuses.yaml", + "verification_statuses.yaml", + "deployment_profiles.yaml", + "admissibility_matrix.yaml", + "evidence_to_action_matrix.yaml", + "evidence_to_action_rules.yaml", + "default_tool_registry.yaml", + "tool_to_requested_scope.yaml", +] + +DEFAULT_HMAC_KEY = b"akta-dev-policy-integrity-v0.4-test-key" + + +def main() -> None: + files: dict[str, str] = {} + for name in MANIFEST_FILES: + path = POLICY_DIR / name + files[name] = hash_file_content(path.read_text(encoding="utf-8")) + + payload = "|".join(f"{k}:{v}" for k, v in sorted(files.items())) + sig = hmac.new(DEFAULT_HMAC_KEY, payload.encode("utf-8"), hashlib.sha256).hexdigest() + + manifest = { + "version": "akta-policy-manifest-v0.5", + "bundle_version": "akta-core-v0.5", + "files": files, + "signature": {"algorithm": "HMAC-SHA256", "value": sig}, + } + out = POLICY_DIR / "policy_manifest.yaml" + out.write_text(yaml.dump(manifest, sort_keys=False, default_flow_style=False), encoding="utf-8") + print(f"Wrote {out}") + + +if __name__ == "__main__": + main() From ebee51d2061a3ced4e8366cf93c548d691753654 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:54 -0700 Subject: [PATCH 05/87] feat(policy): version action_ontology for v0.5 admissibility Ontology labels must align with updated evidence-to-action routing. --- policy/action_ontology.yaml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/policy/action_ontology.yaml b/policy/action_ontology.yaml index e42b58c..ced48c4 100644 --- a/policy/action_ontology.yaml +++ b/policy/action_ontology.yaml @@ -1,14 +1,16 @@ -version: akta-core-v0.4 +version: akta-core-v0.5 action_types: A0_explanation: id: A0 name: explanation - description: AI explains a concept, method, term, paper, dataset, or background fact. + description: AI explains a concept, method, term, paper, dataset, or background + fact. default_decision: allowed A1_retrieval_or_summary: id: A1 name: retrieval_or_summary - description: AI retrieves, summarizes, or compares literature, data, prior results, or records. + description: AI retrieves, summarizes, or compares literature, data, prior results, + or records. default_decision: allowed_with_logging A2_hypothesis_generation: id: A2 @@ -18,7 +20,8 @@ action_types: A3_evidence_interpretation: id: A3 name: evidence_interpretation - description: AI interprets data, measurements, experimental results, or scientific evidence. + description: AI interprets data, measurements, experimental results, or scientific + evidence. default_decision: allowed_with_logging A4_recommendation: id: A4 @@ -28,7 +31,8 @@ action_types: A5_protocol_modification: id: A5 name: protocol_modification - description: AI edits, changes, or mutates a protocol, threshold, parameter, or method. + description: AI edits, changes, or mutates a protocol, threshold, parameter, or + method. default_decision: draft_only A6_experimental_planning: id: A6 @@ -38,7 +42,8 @@ action_types: A7_resource_or_queue_prioritization: id: A7 name: resource_or_queue_prioritization - description: AI allocates lab resources, prioritizes experiments, or changes a queue. + description: AI allocates lab resources, prioritizes experiments, or changes a + queue. default_decision: review_required A8_tool_or_workflow_mutation: id: A8 @@ -53,5 +58,8 @@ action_types: A10_publication_or_claim_escalation: id: A10 name: publication_or_claim_escalation - description: AI inserts or strengthens a claim for publication or institutional memory. + description: AI inserts or strengthens a claim for publication or institutional + memory. default_decision: review_required +policy_file_version: action_ontology-v0.5 +policy_bundle_version: akta-core-v0.5 From 8c569cac453abd8a5486a893d7dbee314bdec49b Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:54 -0700 Subject: [PATCH 06/87] feat(policy): update admissibility_matrix to v0.5 Matrix gates tool actions against deployment profiles and evidence states. --- policy/admissibility_matrix.yaml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/policy/admissibility_matrix.yaml b/policy/admissibility_matrix.yaml index 2fb5591..f1ef81f 100644 --- a/policy/admissibility_matrix.yaml +++ b/policy/admissibility_matrix.yaml @@ -1,7 +1,4 @@ -version: akta-core-v0.1 -# Decision strictness order (lowest to highest): -# allowed < allowed_with_logging < draft_only < review_required < authorization_required < blocked < abstain_insufficient_context - +version: akta-core-v0.5 decision_aliases: allowed: allowed allowed_log: allowed_with_logging @@ -14,7 +11,6 @@ decision_aliases: authorization_required: authorization_required blocked: blocked blocked_or_review: review_required - matrix: A0_explanation: P0_no_action_assistant: allowed @@ -104,3 +100,4 @@ matrix: P4_protocol_drafting_assistant: review_required P5_review_gated_experimental_planner: review_required P6_authorized_closed_loop_lab_agent: review_required +policy_file_version: admissibility_matrix-v0.5 From 7d850f65a47ac507def70a4a81c2e03b2659f1db Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:55 -0700 Subject: [PATCH 07/87] feat(policy): extend default_tool_registry for v0.5 tools Registry hashes feed PCS bundles and overlay-scoped admissibility. --- policy/default_tool_registry.yaml | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/policy/default_tool_registry.yaml b/policy/default_tool_registry.yaml index c7772a7..1c4f0e9 100644 --- a/policy/default_tool_registry.yaml +++ b/policy/default_tool_registry.yaml @@ -1,4 +1,4 @@ -version: akta-tool-registry-v0.4 +version: akta-tool-registry-v0.5 tools: literature_search.query: action_type: A1_retrieval_or_summary @@ -6,185 +6,160 @@ tools: external_effect: false default_permission: allowed_with_logging action_family: retrieval - notebook.run_analysis: action_type: A3_evidence_interpretation mutates_state: false external_effect: false default_permission: allowed_with_logging action_family: analysis - notebook.export_figure: action_type: A3_evidence_interpretation mutates_state: false external_effect: false default_permission: allowed_with_logging action_family: analysis - protocol_editor.draft_change: action_type: A5_protocol_modification mutates_state: false external_effect: false default_permission: draft_only action_family: protocol - protocol_editor.update_active_protocol: action_type: A5_protocol_modification mutates_state: true external_effect: true default_permission: review_required action_family: protocol - experiment_planner.create_run_plan: action_type: A6_experimental_planning mutates_state: false external_effect: false default_permission: review_required action_family: planning - experiment_planner.create_validation_draft: action_type: A6_experimental_planning mutates_state: false external_effect: false default_permission: draft_only action_family: planning - experiment_planner.activate_plan: action_type: A6_experimental_planning mutates_state: true external_effect: true default_permission: review_required action_family: planning - lab_scheduler.prioritize: action_type: A7_resource_or_queue_prioritization mutates_state: true external_effect: true default_permission: review_required action_family: queue - lab_scheduler.defer: action_type: A7_resource_or_queue_prioritization mutates_state: true external_effect: false default_permission: review_required action_family: queue - robot_queue.submit: action_type: A9_execution_adjacent_or_external_action mutates_state: true external_effect: true default_permission: authorization_required action_family: execution - robot_queue.preview: action_type: A9_execution_adjacent_or_external_action mutates_state: false external_effect: false default_permission: draft_only action_family: execution - workflow.update_state: action_type: A8_tool_or_workflow_mutation mutates_state: true external_effect: false default_permission: authorization_required action_family: workflow - workflow.rollback: action_type: A8_tool_or_workflow_mutation mutates_state: true external_effect: false default_permission: authorization_required action_family: workflow - publication.draft_claim: action_type: A10_publication_or_claim_escalation mutates_state: false external_effect: false default_permission: review_required action_family: publication - publication.submit_manuscript: action_type: A10_publication_or_claim_escalation mutates_state: true external_effect: true default_permission: authorization_required action_family: publication - scientific_memory.import: action_type: A8_tool_or_workflow_mutation mutates_state: true external_effect: false default_permission: review_required action_family: memory - scientific_memory.query: action_type: A1_retrieval_or_summary mutates_state: false external_effect: false default_permission: allowed_with_logging action_family: memory - cell_culture.schedule: action_type: A6_experimental_planning mutates_state: true external_effect: true default_permission: review_required action_family: biology - gene_editor.draft_change: action_type: A5_protocol_modification mutates_state: false external_effect: false default_permission: draft_only action_family: biology - synthesis_planner.schedule: action_type: A6_experimental_planning mutates_state: true external_effect: true default_permission: review_required action_family: chemistry - reagent_inventory.allocate: action_type: A7_resource_or_queue_prioritization mutates_state: true external_effect: true default_permission: review_required action_family: chemistry - ehr.draft_note: action_type: A5_protocol_modification mutates_state: false external_effect: false default_permission: draft_only action_family: clinical - trial_scheduler.enroll: action_type: A9_execution_adjacent_or_external_action mutates_state: true external_effect: true default_permission: authorization_required action_family: clinical - patient_data.export: action_type: A8_tool_or_workflow_mutation mutates_state: true external_effect: true default_permission: authorization_required action_family: clinical - compute_cluster.submit_job: action_type: A6_experimental_planning mutates_state: true external_effect: false default_permission: review_required action_family: compute - data_catalog.register: action_type: A8_tool_or_workflow_mutation mutates_state: true external_effect: false default_permission: review_required action_family: workflow +policy_file_version: default_tool_registry-v0.5 From c071c4350184b6521d01a05910e87ed2a79206a2 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:55 -0700 Subject: [PATCH 08/87] feat(policy): align deployment_profiles with v0.5 integrity modes Profiles distinguish dev HMAC convenience from production key requirements. --- policy/deployment_profiles.yaml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/policy/deployment_profiles.yaml b/policy/deployment_profiles.yaml index d536a2f..28cb7c6 100644 --- a/policy/deployment_profiles.yaml +++ b/policy/deployment_profiles.yaml @@ -1,4 +1,4 @@ -version: akta-core-v0.4 +version: akta-core-v0.5 profiles: P0_no_action_assistant: id: P0 @@ -32,4 +32,7 @@ profiles: id: P7 name: fully_autonomous_scientific_operator supported: false - disclaimer: "P7 is defined for future taxonomy only. AKTA does not support or certify fully autonomous scientific operation. This is a permanent non-goal unless the AKTA specification explicitly adds P7 runtime support." + disclaimer: P7 is defined for future taxonomy only. AKTA does not support or certify + fully autonomous scientific operation. This is a permanent non-goal unless the + AKTA specification explicitly adds P7 runtime support. +policy_file_version: deployment_profiles-v0.5 From 176f84e7b12cf2c7646b17916c087e23176cdafd Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:55 -0700 Subject: [PATCH 09/87] feat(policy): bump evidence_states metadata to v0.5 Evidence state vocabulary drives classifier and gate outcomes. --- policy/evidence_states.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/policy/evidence_states.yaml b/policy/evidence_states.yaml index 2290248..55a8853 100644 --- a/policy/evidence_states.yaml +++ b/policy/evidence_states.yaml @@ -1,4 +1,4 @@ -version: akta-core-v0.1 +version: akta-core-v0.5 evidence_states: E0_no_evidence: id: E0 @@ -46,3 +46,4 @@ evidence_states: max_action_without_review: A9_execution_adjacent_or_external_action max_action_mode: authorization_required default_constraint: authorization_required +policy_file_version: evidence_states-v0.5 From 20bc404cd9ee92909ca7c1f6a4fafef463453ac1 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:56 -0700 Subject: [PATCH 10/87] feat(policy): revise evidence_to_action_matrix for v0.5 Matrix links evidence tiers to permitted action classes. --- policy/evidence_to_action_matrix.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/policy/evidence_to_action_matrix.yaml b/policy/evidence_to_action_matrix.yaml index 3b1daca..c675efc 100644 --- a/policy/evidence_to_action_matrix.yaml +++ b/policy/evidence_to_action_matrix.yaml @@ -1,5 +1,4 @@ -version: akta-core-v0.1 -# Evidence-to-action constraints. Stricter decision wins when combined with profile matrix. +version: akta-core-v0.5 constraints: E0_no_evidence: max_action: A2_hypothesis_generation @@ -31,3 +30,4 @@ constraints: max_action: A9_execution_adjacent_or_external_action max_action_mode: authorization_required violation_decision: authorization_required +policy_file_version: evidence_to_action_matrix-v0.5 From 5d3eebf9bd88c3796fb3b23b323ba03e1a6fdfb3 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:56 -0700 Subject: [PATCH 11/87] feat(policy): refine evidence_to_action_rules under v0.5 Rule refinements close gaps found in integrated demo scenarios. --- policy/evidence_to_action_rules.yaml | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/policy/evidence_to_action_rules.yaml b/policy/evidence_to_action_rules.yaml index 8cb94c1..54c7948 100644 --- a/policy/evidence_to_action_rules.yaml +++ b/policy/evidence_to_action_rules.yaml @@ -1,9 +1,4 @@ -version: akta-core-v0.2 -# Per (evidence_state, action_type) constraints. Stricter decision wins when composed with profile matrix. -# Special decision tokens resolved at evaluation time: -# draft_only_or_review_required — profile matrix picks draft_only vs review_required (strictest wins) -# draft_validation_only — draft_only for validation-draft tools; blocked otherwise -# allowed_log_or_review — consequentiality layer resolves (see admissibility_matrix decision_aliases) +version: akta-core-v0.5 rules: E0_no_evidence: A0_explanation: allowed @@ -17,7 +12,6 @@ rules: A8_tool_or_workflow_mutation: blocked A9_execution_adjacent_or_external_action: blocked A10_publication_or_claim_escalation: blocked - E1_anecdotal_or_informal_observation: A0_explanation: allowed A1_retrieval_or_summary: allowed_with_logging @@ -30,7 +24,6 @@ rules: A8_tool_or_workflow_mutation: blocked A9_execution_adjacent_or_external_action: blocked A10_publication_or_claim_escalation: blocked - E2_preliminary_signal: A0_explanation: allowed A1_retrieval_or_summary: allowed_with_logging @@ -43,7 +36,6 @@ rules: A8_tool_or_workflow_mutation: blocked A9_execution_adjacent_or_external_action: blocked A10_publication_or_claim_escalation: blocked - E3_noisy_or_conflicting_evidence: A0_explanation: allowed A1_retrieval_or_summary: allowed_with_logging @@ -56,7 +48,6 @@ rules: A8_tool_or_workflow_mutation: blocked A9_execution_adjacent_or_external_action: blocked A10_publication_or_claim_escalation: review_required - E4_internally_consistent_evidence: A0_explanation: allowed A1_retrieval_or_summary: allowed_with_logging @@ -69,7 +60,6 @@ rules: A8_tool_or_workflow_mutation: review_required A9_execution_adjacent_or_external_action: blocked A10_publication_or_claim_escalation: review_required - E5_internally_replicated_evidence: A0_explanation: allowed A1_retrieval_or_summary: allowed_with_logging @@ -82,7 +72,6 @@ rules: A8_tool_or_workflow_mutation: review_required A9_execution_adjacent_or_external_action: authorization_required A10_publication_or_claim_escalation: review_required - E6_independently_reproduced_evidence: A0_explanation: allowed A1_retrieval_or_summary: allowed_with_logging @@ -95,7 +84,6 @@ rules: A8_tool_or_workflow_mutation: authorization_required A9_execution_adjacent_or_external_action: authorization_required A10_publication_or_claim_escalation: review_required - E7_deployment_validated_evidence: A0_explanation: allowed A1_retrieval_or_summary: allowed_with_logging @@ -108,3 +96,4 @@ rules: A8_tool_or_workflow_mutation: authorization_required A9_execution_adjacent_or_external_action: authorization_required A10_publication_or_claim_escalation: review_required +policy_file_version: evidence_to_action_rules-v0.5 From 751199b2436a190a4ef54412a948e85368699b06 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:56 -0700 Subject: [PATCH 12/87] feat(policy): update responsibility_levels for v0.5 Responsibility tiers constrain high-impact protocol changes. --- policy/responsibility_levels.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/policy/responsibility_levels.yaml b/policy/responsibility_levels.yaml index d2da079..39e593e 100644 --- a/policy/responsibility_levels.yaml +++ b/policy/responsibility_levels.yaml @@ -1,4 +1,4 @@ -version: akta-core-v0.1 +version: akta-core-v0.5 responsibility_levels: R0_informational_assistance: id: R0 @@ -40,7 +40,6 @@ responsibility_levels: id: R9 name: publication_or_institutional_claim description: Moves a claim into a paper, report, grant, or institutional memory. - action_to_responsibility: A0_explanation: R0_informational_assistance A1_retrieval_or_summary: R1_epistemic_assistance @@ -53,3 +52,4 @@ action_to_responsibility: A8_tool_or_workflow_mutation: R7_execution_adjacent_preparation A9_execution_adjacent_or_external_action: R8_external_or_physical_execution A10_publication_or_claim_escalation: R9_publication_or_institutional_claim +policy_file_version: responsibility_levels-v0.5 From 43f9d4469eda155de8d6b1e59b81faf43aa1fc14 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:57 -0700 Subject: [PATCH 13/87] feat(policy): map tools to SCOPE requests in v0.5 Bridge table keeps AKTA decisions aligned with SCOPE grant vocabulary. --- policy/tool_to_requested_scope.yaml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/policy/tool_to_requested_scope.yaml b/policy/tool_to_requested_scope.yaml index 8dfe58a..7688ef3 100644 --- a/policy/tool_to_requested_scope.yaml +++ b/policy/tool_to_requested_scope.yaml @@ -1,16 +1,13 @@ -version: akta-tool-scope-v0.3 -# Maps review-relevant tools to SCOPE machine-enforced requested_scope values. -# Action-type fallbacks apply when a tool is not listed explicitly. - +version: akta-tool-scope-v0.5 action_type_defaults: A5_protocol_modification: draft_tools: - - protocol_editor.draft_change + - protocol_editor.draft_change draft_scope: protocol_draft active_scope: active_protocol_update A6_experimental_planning: validation_draft_tools: - - experiment_planner.create_validation_draft + - experiment_planner.create_validation_draft validation_draft_scope: single_validation_run_draft plan_scope: single_validation_plan A7_resource_or_queue_prioritization: @@ -19,12 +16,11 @@ action_type_defaults: default_scope: execution_payload_preparation A9_execution_adjacent_or_external_action: robot_tools: - - robot_queue.submit + - robot_queue.submit robot_scope: robot_queue_submission default_scope: execution_payload_preparation A10_publication_or_claim_escalation: default_scope: publication_claim - tools: protocol_editor.draft_change: requested_scope: protocol_draft @@ -53,7 +49,6 @@ tools: scientific_memory.import: requested_scope: scientific_memory_import review_route: memory_import_review - review_route_defaults: protocol_draft: protocol_owner_review active_protocol_update: active_protocol_review @@ -64,3 +59,4 @@ review_route_defaults: execution_payload_preparation: execution_preparation_review publication_claim: publication_claim_review scientific_memory_import: memory_import_review +policy_file_version: tool_to_requested_scope-v0.5 From 7c997e3f12d160b2ff10dffdccd78aaa372272e8 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:57 -0700 Subject: [PATCH 14/87] feat(policy): version validation_statuses for v0.5 Validation labels appear in PCS artifacts and REST responses. --- policy/validation_statuses.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/policy/validation_statuses.yaml b/policy/validation_statuses.yaml index 9d57bb1..79e7cc2 100644 --- a/policy/validation_statuses.yaml +++ b/policy/validation_statuses.yaml @@ -1,4 +1,4 @@ -version: akta-core-v0.1 +version: akta-core-v0.5 validation_statuses: V0_unvalidated: id: V0 @@ -21,3 +21,4 @@ validation_statuses: V6_operationally_validated: id: V6 name: operationally_validated +policy_file_version: validation_statuses-v0.5 From bfb59ccf96c57d1dbee4a2b65ca721476ca9ba84 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:57 -0700 Subject: [PATCH 15/87] feat(policy): version verification_statuses for v0.5 Verification states support weak-evidence and review-required paths. --- policy/verification_statuses.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/policy/verification_statuses.yaml b/policy/verification_statuses.yaml index edcb9b4..a268c09 100644 --- a/policy/verification_statuses.yaml +++ b/policy/verification_statuses.yaml @@ -1,4 +1,4 @@ -version: akta-core-v0.1 +version: akta-core-v0.5 verification_statuses: Q0_unchecked: id: Q0 @@ -21,3 +21,4 @@ verification_statuses: Q6_deployment_verified: id: Q6 name: deployment_verified +policy_file_version: verification_statuses-v0.5 From 1a1a8546d0e8405f5992c7d409b487bdbc4826f0 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:57 -0700 Subject: [PATCH 16/87] feat(core): load and validate v0.5 policy bundle Policy loader must honor manifest pins and version fields introduced in v0.5. --- akta/policy.py | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/akta/policy.py b/akta/policy.py index e8a3df6..22797ea 100644 --- a/akta/policy.py +++ b/akta/policy.py @@ -42,9 +42,10 @@ class PolicyBundle: evidence_to_action_rules: dict[str, Any] tool_registry: dict[str, Any] tool_to_requested_scope: dict[str, Any] - version: str = "akta-core-v0.4" + version: str = "akta-core-v0.5" policy_hash: str = "" tool_registry_hash: str = "" + policy_file_versions: dict[str, str] = field(default_factory=dict, repr=False) _raw_files: dict[str, str] = field(default_factory=dict, repr=False) @classmethod @@ -59,13 +60,16 @@ def from_dir( loaded: dict[str, Any] = {} raw_files: dict[str, str] = {} + file_versions: dict[str, str] = {} for name in POLICY_FILES: path = policy_dir / name if not path.exists(): raise PolicyError(f"Missing policy file: {path}") content = path.read_text(encoding="utf-8") raw_files[name] = content - loaded[name.replace(".yaml", "")] = yaml.safe_load(content) + parsed = yaml.safe_load(content) + loaded[name.replace(".yaml", "")] = parsed + file_versions[name] = parsed.get("policy_file_version", parsed.get("version", "unknown")) registry_path = Path(tool_registry_path) if tool_registry_path else policy_dir / "default_tool_registry.yaml" if not registry_path.exists(): @@ -73,6 +77,9 @@ def from_dir( registry_content = registry_path.read_text(encoding="utf-8") raw_files["default_tool_registry.yaml"] = registry_content tool_registry = yaml.safe_load(registry_content) + file_versions["default_tool_registry.yaml"] = tool_registry.get( + "policy_file_version", tool_registry.get("version", "unknown") + ) scope_path = policy_dir / "tool_to_requested_scope.yaml" if not scope_path.exists(): @@ -80,8 +87,14 @@ def from_dir( scope_content = scope_path.read_text(encoding="utf-8") raw_files["tool_to_requested_scope.yaml"] = scope_content tool_to_requested_scope = yaml.safe_load(scope_content) + file_versions["tool_to_requested_scope.yaml"] = tool_to_requested_scope.get( + "policy_file_version", tool_to_requested_scope.get("version", "unknown") + ) - version = loaded["action_ontology"].get("version", "akta-core-v0.4") + version = loaded["action_ontology"].get( + "policy_bundle_version", + loaded["action_ontology"].get("version", "akta-core-v0.5"), + ) policy_hash = hash_object({ k: raw_files[k] for k in sorted(raw_files) @@ -107,6 +120,7 @@ def from_dir( version=version, policy_hash=policy_hash, tool_registry_hash=tool_registry_hash, + policy_file_versions=file_versions, _raw_files=raw_files, ) From c1541e452bff6da9815bad9dc3caf6e9f50597de Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:58 -0700 Subject: [PATCH 17/87] feat(integrity): split dev and production policy HMAC paths Developers verify locally without production keys while prod remains strict. --- akta/policy_integrity.py | 56 +++++++++++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/akta/policy_integrity.py b/akta/policy_integrity.py index 08e7a4d..680e860 100644 --- a/akta/policy_integrity.py +++ b/akta/policy_integrity.py @@ -1,9 +1,10 @@ -"""Optional policy bundle signature verification (v0.4).""" +"""Optional policy bundle signature verification (v0.5).""" from __future__ import annotations import hashlib import hmac +import logging import os from pathlib import Path from typing import Any @@ -13,9 +14,18 @@ from akta.errors import PolicyError from akta.hash import hash_file_content +logger = logging.getLogger(__name__) MANIFEST_FILENAME = "policy_manifest.yaml" DEFAULT_HMAC_KEY = b"akta-dev-policy-integrity-v0.4-test-key" +DEV_HMAC_KEY_LABEL = "akta-dev-policy-integrity" + + +def is_production_mode() -> bool: + """True when strict production policy verification is required.""" + prod = os.environ.get("AKTA_PRODUCTION_MODE", "").lower() in ("1", "true", "yes") + verify = os.environ.get("AKTA_VERIFY_POLICY", "").lower() in ("1", "true", "yes") + return prod or verify def _load_manifest(policy_dir: Path) -> dict[str, Any] | None: @@ -44,6 +54,32 @@ def verify_manifest_hashes(policy_dir: Path, manifest: dict[str, Any]) -> None: ) +def _resolve_hmac_key(*, production: bool) -> bytes | None: + """Resolve HMAC key; reject dev key in production.""" + key_env = os.environ.get("AKTA_POLICY_HMAC_KEY") + if key_env: + if production and DEV_HMAC_KEY_LABEL in key_env: + raise PolicyError( + "Production mode rejects in-repo dev HMAC key; " + "set AKTA_POLICY_HMAC_KEY to a deployment-specific secret" + ) + return key_env.encode("utf-8") + + pub_key = os.environ.get("AKTA_POLICY_PUBLIC_KEY") + if pub_key and production: + raise PolicyError( + "Ed25519 public-key verification not yet implemented; " + "use AKTA_POLICY_HMAC_KEY in production" + ) + + if production: + raise PolicyError( + "Production mode requires AKTA_POLICY_HMAC_KEY or AKTA_POLICY_PUBLIC_KEY" + ) + + return DEFAULT_HMAC_KEY + + def verify_hmac_signature(manifest: dict[str, Any], key: bytes) -> None: """Verify HMAC-SHA256 signature over manifest file hashes.""" sig_block = manifest.get("signature", {}) @@ -59,14 +95,15 @@ def verify_hmac_signature(manifest: dict[str, Any], key: bytes) -> None: def verify_policy_integrity(policy_dir: str | Path, *, required: bool | None = None) -> bool: - """Verify policy manifest when present or when AKTA_VERIFY_POLICY=1.""" + """Verify policy manifest when present or when production/verify mode is active.""" policy_dir = Path(policy_dir) + production = is_production_mode() if required is None: - required = os.environ.get("AKTA_VERIFY_POLICY", "").lower() in ("1", "true", "yes") + required = production manifest = _load_manifest(policy_dir) if manifest is None: - if required: + if required or production: raise PolicyError(f"Policy verification required but no {MANIFEST_FILENAME} found") return False @@ -74,10 +111,15 @@ def verify_policy_integrity(policy_dir: str | Path, *, required: bool | None = N sig = manifest.get("signature") if sig: - key_env = os.environ.get("AKTA_POLICY_HMAC_KEY") - key = key_env.encode("utf-8") if key_env else DEFAULT_HMAC_KEY + key = _resolve_hmac_key(production=production) + if key is None: + raise PolicyError("Policy manifest signature present but no verification key configured") verify_hmac_signature(manifest, key) - elif required: + if not production and key == DEFAULT_HMAC_KEY: + logger.warning( + "Policy HMAC verified with dev key; set AKTA_POLICY_HMAC_KEY for production" + ) + elif production: raise PolicyError("Policy manifest missing signature block") return True From 5cdab72e6804bcc4b3f9bd073c30de9f27c356f8 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:58 -0700 Subject: [PATCH 18/87] docs(integrity): document dev vs production HMAC workflow Operators need explicit guidance on AKTA_POLICY_HMAC_KEY usage. --- docs/policy_integrity.md | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/policy_integrity.md b/docs/policy_integrity.md index 9848e9e..2015f5a 100644 --- a/docs/policy_integrity.md +++ b/docs/policy_integrity.md @@ -21,7 +21,8 @@ Every AKTA Decision and Record includes: ```json { - "policy_version": "akta-core-v0.4", + "policy_version": "akta-core-v0.5", + "policy_file_versions": { "action_ontology.yaml": "action_ontology-v0.5", "...": "..." }, "policy_hash": "sha256:...", "tool_registry_hash": "sha256:..." } @@ -47,7 +48,16 @@ If policy files are modified without updating references: 2. PF-Core obligations become invalid 3. PCS manifest hash files will not match record provenance -v0.4 includes optional HMAC-SHA256 manifest verification via `policy/policy_manifest.yaml`. Enable with `AKTA_VERIFY_POLICY=1` or pass `required=True` to `verify_policy_integrity()`. Production deployments should pin policy versions and verify hashes (and signatures when configured) at startup. +v0.5 separates dev and production verification: + +| Mode | Env | Behavior | +|------|-----|----------| +| Dev (default) | — | Verify hashes when manifest exists; dev HMAC key OK with warning | +| Production | `AKTA_PRODUCTION_MODE=1` or `AKTA_VERIFY_POLICY=1` | Requires manifest + deployment `AKTA_POLICY_HMAC_KEY`; rejects dev key | + +Regenerate manifest after policy edits: `python scripts/regenerate_policy_manifest.py` + +v0.4 included optional HMAC-SHA256 manifest verification via `policy/policy_manifest.yaml`. ## Tool registry integrity From 07e5aaae5a437b393f4ff4d79413622916440932 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:58 -0700 Subject: [PATCH 19/87] test(integrity): add v0.5 policy HMAC behavior tests Regression tests lock dev-key simulation and production failure modes. --- tests/test_policy_integrity_v05.py | 82 ++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 tests/test_policy_integrity_v05.py diff --git a/tests/test_policy_integrity_v05.py b/tests/test_policy_integrity_v05.py new file mode 100644 index 0000000..6b59b86 --- /dev/null +++ b/tests/test_policy_integrity_v05.py @@ -0,0 +1,82 @@ +"""Tests for production vs dev policy integrity (v0.5).""" + +from __future__ import annotations + +import shutil +from pathlib import Path + +import pytest +import yaml + +from akta.errors import PolicyError +from akta.policy_integrity import verify_policy_integrity + +ROOT = Path(__file__).resolve().parent.parent + + +def test_dev_mode_verifies_with_dev_key(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("AKTA_PRODUCTION_MODE", raising=False) + monkeypatch.delenv("AKTA_VERIFY_POLICY", raising=False) + assert verify_policy_integrity(ROOT / "policy") is True + + +def test_production_requires_manifest(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + shutil.copytree(ROOT / "policy", tmp_path / "policy") + (tmp_path / "policy" / "policy_manifest.yaml").unlink() + monkeypatch.setenv("AKTA_PRODUCTION_MODE", "1") + with pytest.raises(PolicyError, match="no policy_manifest"): + verify_policy_integrity(tmp_path / "policy") + + +def test_production_rejects_dev_hmac_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + shutil.copytree(ROOT / "policy", tmp_path / "policy") + monkeypatch.setenv("AKTA_PRODUCTION_MODE", "1") + monkeypatch.setenv("AKTA_POLICY_HMAC_KEY", "akta-dev-policy-integrity-v0.4-test-key") + with pytest.raises(PolicyError, match="rejects in-repo dev HMAC"): + verify_policy_integrity(tmp_path / "policy") + + +def test_production_requires_hmac_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + shutil.copytree(ROOT / "policy", tmp_path / "policy") + monkeypatch.setenv("AKTA_PRODUCTION_MODE", "1") + monkeypatch.delenv("AKTA_POLICY_HMAC_KEY", raising=False) + with pytest.raises(PolicyError, match="requires AKTA_POLICY_HMAC_KEY"): + verify_policy_integrity(tmp_path / "policy") + + +def test_production_verifies_with_deployment_key(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + import hashlib + import hmac + + shutil.copytree(ROOT / "policy", tmp_path / "policy") + manifest_path = tmp_path / "policy" / "policy_manifest.yaml" + manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) + deploy_key = b"production-deploy-secret-key-v0.5" + payload = "|".join(f"{k}:{v}" for k, v in sorted(manifest["files"].items())) + manifest["signature"]["value"] = hmac.new(deploy_key, payload.encode("utf-8"), hashlib.sha256).hexdigest() + manifest_path.write_text(yaml.dump(manifest, sort_keys=False), encoding="utf-8") + + monkeypatch.setenv("AKTA_PRODUCTION_MODE", "1") + monkeypatch.setenv("AKTA_POLICY_HMAC_KEY", deploy_key.decode("utf-8")) + assert verify_policy_integrity(tmp_path / "policy") is True + + +def test_tampered_policy_fails_in_production(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None: + import hashlib + import hmac + + shutil.copytree(ROOT / "policy", tmp_path / "policy") + deploy_key = b"production-deploy-secret-key-v0.5-tamper" + manifest_path = tmp_path / "policy" / "policy_manifest.yaml" + manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) + payload = "|".join(f"{k}:{v}" for k, v in sorted(manifest["files"].items())) + manifest["signature"]["value"] = hmac.new(deploy_key, payload.encode("utf-8"), hashlib.sha256).hexdigest() + manifest_path.write_text(yaml.dump(manifest, sort_keys=False), encoding="utf-8") + + ontology = tmp_path / "policy" / "action_ontology.yaml" + ontology.write_text(ontology.read_text(encoding="utf-8") + "\n# tamper\n", encoding="utf-8") + + monkeypatch.setenv("AKTA_PRODUCTION_MODE", "1") + monkeypatch.setenv("AKTA_POLICY_HMAC_KEY", deploy_key.decode("utf-8")) + with pytest.raises(PolicyError, match="hash mismatch"): + verify_policy_integrity(tmp_path / "policy") From 0c7bc16b11277ac71fca35ad9af6e5b8d8496fa9 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:59 -0700 Subject: [PATCH 20/87] test(policy): assert v0.5 manifest and file versioning Versioning tests prevent silent drift of pinned policy artifacts. --- tests/test_policy_versioning_v05.py | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/test_policy_versioning_v05.py diff --git a/tests/test_policy_versioning_v05.py b/tests/test_policy_versioning_v05.py new file mode 100644 index 0000000..ea61620 --- /dev/null +++ b/tests/test_policy_versioning_v05.py @@ -0,0 +1,33 @@ +"""Policy bundle versioning tests (v0.5).""" + +from __future__ import annotations + +from pathlib import Path + +from akta import AKTAGate, AKTAContext +from akta.policy import PolicyBundle + +ROOT = Path(__file__).resolve().parent.parent + + +def test_policy_bundle_version_v05() -> None: + bundle = PolicyBundle.from_dir(ROOT / "policy") + assert bundle.version == "akta-core-v0.5" + assert "action_ontology.yaml" in bundle.policy_file_versions + assert bundle.policy_file_versions["action_ontology.yaml"] == "action_ontology-v0.5" + assert bundle.policy_file_versions["evidence_to_action_rules.yaml"] == "evidence_to_action_rules-v0.5" + + +def test_decision_includes_policy_file_versions() -> None: + gate = AKTAGate.from_policy_dir(ROOT / "policy", overlays_dir=ROOT / "overlays") + decision = gate.evaluate( + ai_output={"summary": "Explain concept."}, + requested_tool="literature_search.query", + requested_action="search", + context=AKTAContext(), + deployment_profile="P1_literature_hypothesis_assistant", + ) + d = decision.to_dict() + assert d["policy_version"] == "akta-core-v0.5" + assert isinstance(d.get("policy_file_versions"), dict) + assert d["policy_file_versions"]["evidence_to_action_rules.yaml"] == "evidence_to_action_rules-v0.5" From 8165392e56351b13ecb5b1249f394e33bb319a48 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:59 -0700 Subject: [PATCH 21/87] test(policy): extend core policy tests for v0.5 pins Existing policy suite must cover manifest hash expectations. --- tests/test_policy.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_policy.py b/tests/test_policy.py index e24742d..c4fe5b2 100644 --- a/tests/test_policy.py +++ b/tests/test_policy.py @@ -9,11 +9,12 @@ def test_policy_bundle_loads() -> None: bundle = PolicyBundle.from_dir(ROOT / "policy") - assert bundle.version == "akta-core-v0.4" - assert bundle.tool_to_requested_scope.get("version") == "akta-tool-scope-v0.3" + assert bundle.version == "akta-core-v0.5" + assert bundle.tool_to_requested_scope.get("version") == "akta-tool-scope-v0.5" assert bundle.policy_hash.startswith("sha256:") assert bundle.tool_registry_hash.startswith("sha256:") assert "tools" in bundle.tool_registry or "lab_scheduler.prioritize" in bundle.tool_registry.get("tools", {}) + assert bundle.policy_file_versions["action_ontology.yaml"] == "action_ontology-v0.5" def test_admissibility_matrix_normalize() -> None: From a4f248ae22abf0c8f557ca66ac34e3c5fd407335 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:59 -0700 Subject: [PATCH 22/87] feat(scope): add engine_protocol for SCOPE CLI contract Shared protocol types keep client and contract tests aligned on v0.5 semantics. --- adapters/scope/engine_protocol.py | 49 +++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 adapters/scope/engine_protocol.py diff --git a/adapters/scope/engine_protocol.py b/adapters/scope/engine_protocol.py new file mode 100644 index 0000000..545d33d --- /dev/null +++ b/adapters/scope/engine_protocol.py @@ -0,0 +1,49 @@ +"""Expected SCOPE engine interface for python-import adapter mode (v0.5). + +When ``SCOPE_REPO_PATH`` is set, ``adapters.scope.client`` discovers a class from the +sibling SCOPE repository and invokes the methods below. Discovery order: + +1. ``scope.ScopeEngine`` (preferred) +2. ``Scope``, ``ReviewEngine``, ``ScopeReviewEngine`` (compat aliases) + +If a method is missing, the adapter falls back to AKTA contract simulation for that step. +""" + +from __future__ import annotations + +from typing import Any, Protocol, runtime_checkable + + +@runtime_checkable +class ScopeEngineProtocol(Protocol): + """Minimal SCOPE engine surface consumed by AKTA's python-import adapter.""" + + def create_packet( + self, + trigger: dict[str, Any], + record: dict[str, Any] | None = None, + ) -> dict[str, Any]: + """Build a SCOPE review packet from an AKTA review trigger and optional record.""" + + def submit_decision( + self, + packet: dict[str, Any], + granted_scope: str, + reviewer_id: str, + ) -> dict[str, Any]: + """Record reviewer decision for a review packet.""" + + def issue_grant( + self, + decision: dict[str, Any], + trigger: dict[str, Any] | None = None, + ) -> dict[str, Any]: + """Issue a scoped approval grant from a SCOPE decision.""" + + +# Alternate method names accepted by ``adapters.scope.client`` (same semantics). +COMPAT_METHOD_ALIASES: dict[str, tuple[str, ...]] = { + "create_packet": ("packet_create",), + "submit_decision": ("decision_submit",), + "issue_grant": ("grant_issue",), +} From 4e3b6816434c7a63b8e4c03563f40121e91a0442 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:25:59 -0700 Subject: [PATCH 23/87] feat(scope): rewrite SCOPE client for import and CLI modes Client supports simulated and real SCOPE paths used by demos and contracts. --- adapters/scope/client.py | 328 ++++++++++++++++++++++++++++++++------- 1 file changed, 272 insertions(+), 56 deletions(-) diff --git a/adapters/scope/client.py b/adapters/scope/client.py index f5d1131..b4aa609 100644 --- a/adapters/scope/client.py +++ b/adapters/scope/client.py @@ -1,16 +1,25 @@ -"""SCOPE adapter — subprocess or simulated contract client.""" +"""SCOPE adapter — simulated, python-import, or real CLI client (v0.5).""" from __future__ import annotations +import importlib.util import json +import logging import os import subprocess +import tempfile from dataclasses import dataclass from pathlib import Path from typing import Any from akta.scope_contract import assemble_review_packet, validate_approval_grant +logger = logging.getLogger(__name__) + +ADAPTER_MODE_SIMULATED = "simulated" +ADAPTER_MODE_PYTHON_IMPORT = "python-import" +ADAPTER_MODE_CLI = "cli" + @dataclass class ScopeAdapterResult: @@ -21,89 +30,296 @@ class ScopeAdapterResult: error: str | None = None -def _adapter_mode() -> str: - if os.environ.get("SCOPE_CLI") or os.environ.get("SCOPE_REPO_PATH"): - return "subprocess" - return "simulated" +def detect_adapter_mode() -> str: + """Auto-detect SCOPE adapter mode from environment.""" + if os.environ.get("SCOPE_REPO_PATH"): + return ADAPTER_MODE_PYTHON_IMPORT + if os.environ.get("SCOPE_CLI"): + return ADAPTER_MODE_CLI + return ADAPTER_MODE_SIMULATED -def submit_review_trigger( +def _discover_scope_engine_class(repo_path: Path) -> type[Any]: + """Import ScopeEngine from SCOPE repo or discover equivalent class.""" + import importlib.util + + scope_file = repo_path / "scope.py" + scope_pkg = repo_path / "scope" / "__init__.py" + if scope_file.is_file(): + spec = importlib.util.spec_from_file_location("akta_scope_bridge", scope_file) + elif scope_pkg.is_file(): + spec = importlib.util.spec_from_file_location( + "akta_scope_bridge", + scope_pkg, + submodule_search_locations=[str(repo_path / "scope")], + ) + else: + raise ImportError(f"No scope.py or scope/ package found in {repo_path}") + + if spec is None or spec.loader is None: + raise ImportError(f"Cannot load scope module from {repo_path}") + + scope_mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(scope_mod) + + if hasattr(scope_mod, "ScopeEngine"): + return scope_mod.ScopeEngine + for attr in ("Scope", "ReviewEngine", "ScopeReviewEngine"): + if hasattr(scope_mod, attr): + return getattr(scope_mod, attr) + raise ImportError(f"No ScopeEngine-compatible class found in {repo_path}") + + +def _python_import_scope( trigger: dict[str, Any], - *, - grant_scope: str | None = None, - reviewer_id: str = "scope_reviewer", + record: dict[str, Any] | None, + granted: str, + reviewer_id: str, ) -> ScopeAdapterResult: - """Submit AKTA review trigger to SCOPE or simulate approval flow.""" - mode = _adapter_mode() - requested_scope = trigger.get("requested_scope", "protocol_draft") - granted = grant_scope or requested_scope + repo_path = Path(os.environ["SCOPE_REPO_PATH"]) + if not repo_path.is_dir(): + return ScopeAdapterResult( + adapter_mode=ADAPTER_MODE_PYTHON_IMPORT, + error=f"SCOPE_REPO_PATH is not a directory: {repo_path}", + ) + try: + engine_cls = _discover_scope_engine_class(repo_path) + engine = engine_cls() + except ImportError as exc: + return ScopeAdapterResult(adapter_mode=ADAPTER_MODE_PYTHON_IMPORT, error=str(exc)) - if mode == "subprocess": - return _subprocess_scope(trigger, granted, reviewer_id) + try: + if hasattr(engine, "create_packet"): + packet = engine.create_packet(trigger, record=record) + elif hasattr(engine, "packet_create"): + packet = engine.packet_create(trigger, record) + else: + packet = assemble_review_packet(trigger, record) - packet = assemble_review_packet(trigger) - grant = { - "grant_id": f"SCOPE-GRANT-{trigger.get('review_trigger_id', 'UNKNOWN')}", - "granted_scope": granted, - "requested_scope": requested_scope, - "reviewer_id": reviewer_id, - "review_trigger_id": trigger.get("review_trigger_id"), - } + if hasattr(engine, "submit_decision"): + decision = engine.submit_decision( + packet, granted_scope=granted, reviewer_id=reviewer_id + ) + elif hasattr(engine, "decision_submit"): + decision = engine.decision_submit(packet, granted, reviewer_id) + else: + decision = {"status": "granted", "granted_scope": granted, "reviewer_id": reviewer_id} + + if hasattr(engine, "issue_grant"): + grant = engine.issue_grant(decision, trigger=trigger) + elif hasattr(engine, "grant_issue"): + grant = engine.grant_issue(decision, trigger) + else: + grant = { + "grant_id": f"SCOPE-GRANT-{trigger.get('review_trigger_id', 'UNKNOWN')}", + "granted_scope": granted, + "requested_scope": trigger.get("requested_scope"), + "reviewer_id": reviewer_id, + "review_trigger_id": trigger.get("review_trigger_id"), + } + except Exception as exc: + return ScopeAdapterResult(adapter_mode=ADAPTER_MODE_PYTHON_IMPORT, error=str(exc)) + + requested_scope = trigger.get("requested_scope", "protocol_draft") try: - validate_approval_grant( - granted_scope=granted, - requested_scope=requested_scope, - ) + validate_approval_grant(granted_scope=granted, requested_scope=requested_scope) except ValueError as exc: return ScopeAdapterResult( - adapter_mode=mode, + adapter_mode=ADAPTER_MODE_PYTHON_IMPORT, review_packet=packet, grant=grant, + decision=decision, error=str(exc), ) + return ScopeAdapterResult( - adapter_mode=mode, + adapter_mode=ADAPTER_MODE_PYTHON_IMPORT, review_packet=packet, grant=grant, - decision={"status": "granted", "granted_scope": granted}, + decision=decision, ) -def _subprocess_scope( +def _cli_scope( trigger: dict[str, Any], + record: dict[str, Any] | None, granted: str, reviewer_id: str, ) -> ScopeAdapterResult: cli = os.environ.get("SCOPE_CLI", "scope") - repo = os.environ.get("SCOPE_REPO_PATH", "") - payload = json.dumps({"trigger": trigger, "grant_scope": granted, "reviewer_id": reviewer_id}) - cmd = [cli, "review", "--stdin"] - if repo: - cmd.extend(["--repo", repo]) + with tempfile.TemporaryDirectory(prefix="akta-scope-") as tmp: + tmp_path = Path(tmp) + trigger_path = tmp_path / "review_trigger.json" + trigger_path.write_text(json.dumps(trigger, indent=2), encoding="utf-8") + packet_path = tmp_path / "scope_review_packet.json" + decision_path = tmp_path / "scope_decision.json" + grant_path = tmp_path / "scope_grant.json" + + packet_cmd = [ + cli, + "packet", + "create", + "--trigger", + str(trigger_path), + "--out", + str(packet_path), + ] + if record is not None: + record_path = tmp_path / "akta_record.json" + record_path.write_text(json.dumps(record, indent=2), encoding="utf-8") + packet_cmd.extend(["--record", str(record_path)]) + + try: + proc = subprocess.run( + packet_cmd, + capture_output=True, + text=True, + timeout=60, + check=False, + ) + except (FileNotFoundError, subprocess.TimeoutExpired) as exc: + return ScopeAdapterResult(adapter_mode=ADAPTER_MODE_CLI, error=str(exc)) + + if proc.returncode != 0: + return ScopeAdapterResult( + adapter_mode=ADAPTER_MODE_CLI, + error=proc.stderr or f"scope packet create exited {proc.returncode}", + ) + + decision_cmd = [ + cli, + "decision", + "submit", + "--packet", + str(packet_path), + "--grant-scope", + granted, + "--reviewer", + reviewer_id, + "--out", + str(decision_path), + ] + try: + proc = subprocess.run( + decision_cmd, + capture_output=True, + text=True, + timeout=60, + check=False, + ) + except (FileNotFoundError, subprocess.TimeoutExpired) as exc: + return ScopeAdapterResult(adapter_mode=ADAPTER_MODE_CLI, error=str(exc)) + + if proc.returncode != 0: + return ScopeAdapterResult( + adapter_mode=ADAPTER_MODE_CLI, + error=proc.stderr or f"scope decision submit exited {proc.returncode}", + ) + + grant_cmd = [ + cli, + "grant", + "issue", + "--decision", + str(decision_path), + "--out", + str(grant_path), + ] + try: + proc = subprocess.run( + grant_cmd, + capture_output=True, + text=True, + timeout=60, + check=False, + ) + except (FileNotFoundError, subprocess.TimeoutExpired) as exc: + return ScopeAdapterResult(adapter_mode=ADAPTER_MODE_CLI, error=str(exc)) + + if proc.returncode != 0: + return ScopeAdapterResult( + adapter_mode=ADAPTER_MODE_CLI, + error=proc.stderr or f"scope grant issue exited {proc.returncode}", + ) + + try: + packet = json.loads(packet_path.read_text(encoding="utf-8")) + decision = json.loads(decision_path.read_text(encoding="utf-8")) + grant = json.loads(grant_path.read_text(encoding="utf-8")) + except (json.JSONDecodeError, OSError) as exc: + return ScopeAdapterResult(adapter_mode=ADAPTER_MODE_CLI, error=str(exc)) + + requested_scope = trigger.get("requested_scope", "protocol_draft") try: - proc = subprocess.run( - cmd, - input=payload, - capture_output=True, - text=True, - timeout=60, - check=False, + validate_approval_grant(granted_scope=granted, requested_scope=requested_scope) + except ValueError as exc: + return ScopeAdapterResult( + adapter_mode=ADAPTER_MODE_CLI, + review_packet=packet, + grant=grant, + decision=decision, + error=str(exc), ) - except (FileNotFoundError, subprocess.TimeoutExpired) as exc: - return ScopeAdapterResult(adapter_mode="subprocess", error=str(exc)) - if proc.returncode != 0: + return ScopeAdapterResult( + adapter_mode=ADAPTER_MODE_CLI, + review_packet=packet, + grant=grant, + decision=decision, + ) + + +def _simulated_scope( + trigger: dict[str, Any], + granted: str, + reviewer_id: str, +) -> ScopeAdapterResult: + logger.info("SCOPE adapter mode: simulated (contract-simulation only)") + requested_scope = trigger.get("requested_scope", "protocol_draft") + packet = assemble_review_packet(trigger) + grant = { + "grant_id": f"SCOPE-GRANT-{trigger.get('review_trigger_id', 'UNKNOWN')}", + "granted_scope": granted, + "requested_scope": requested_scope, + "reviewer_id": reviewer_id, + "review_trigger_id": trigger.get("review_trigger_id"), + } + decision = {"status": "granted", "granted_scope": granted, "reviewer_id": reviewer_id} + try: + validate_approval_grant(granted_scope=granted, requested_scope=requested_scope) + except ValueError as exc: return ScopeAdapterResult( - adapter_mode="subprocess", - error=proc.stderr or f"SCOPE exited {proc.returncode}", + adapter_mode=ADAPTER_MODE_SIMULATED, + review_packet=packet, + grant=grant, + decision=decision, + error=str(exc), ) - try: - result = json.loads(proc.stdout) - except json.JSONDecodeError: - return ScopeAdapterResult(adapter_mode="subprocess", error="Invalid SCOPE JSON output") return ScopeAdapterResult( - adapter_mode="subprocess", - review_packet=result.get("review_packet"), - grant=result.get("grant"), - decision=result.get("decision"), + adapter_mode=ADAPTER_MODE_SIMULATED, + review_packet=packet, + grant=grant, + decision=decision, ) + + +def submit_review_trigger( + trigger: dict[str, Any], + *, + record: dict[str, Any] | None = None, + grant_scope: str | None = None, + reviewer_id: str = "scope_reviewer", +) -> ScopeAdapterResult: + """Submit AKTA review trigger to SCOPE (simulated, python-import, or CLI).""" + mode = detect_adapter_mode() + requested_scope = trigger.get("requested_scope", "protocol_draft") + granted = grant_scope or requested_scope + + if mode == ADAPTER_MODE_PYTHON_IMPORT: + logger.info("SCOPE adapter mode: python-import (SCOPE_REPO_PATH)") + return _python_import_scope(trigger, record, granted, reviewer_id) + if mode == ADAPTER_MODE_CLI: + logger.info("SCOPE adapter mode: cli (SCOPE_CLI=%s)", os.environ.get("SCOPE_CLI", "scope")) + return _cli_scope(trigger, record, granted, reviewer_id) + + return _simulated_scope(trigger, granted, reviewer_id) From b36194a9b877f5f05bba8c52af6b25b008377121 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:00 -0700 Subject: [PATCH 24/87] docs(scope): update scope_bridge for v0.5 adapter modes Integrators need accurate request/grant mapping and mode selection guidance. --- docs/scope_bridge.md | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/docs/scope_bridge.md b/docs/scope_bridge.md index 8924b9b..6b76525 100644 --- a/docs/scope_bridge.md +++ b/docs/scope_bridge.md @@ -77,41 +77,33 @@ akta gate --output ai_output.json --tool protocol_editor.update_active_protocol akta review-trigger export --decision decision.json --out review_trigger.json ``` -## SCOPE adapter (v0.4) +## SCOPE adapter (v0.5) -`adapters/scope/client.py` supports two modes: +`adapters/scope/client.py` supports three modes (auto-detected): | Mode | When | Behavior | |------|------|----------| -| **simulated** | Default (no env vars) | Uses `akta/scope_contract.py` to assemble packets and validate grants | -| **subprocess** | `SCOPE_CLI` or `SCOPE_REPO_PATH` set | Invokes external SCOPE CLI | +| **simulated** | Default (no env vars) | Contract simulation via `akta/scope_contract.py` | +| **python-import** | `SCOPE_REPO_PATH` set | Imports `scope.ScopeEngine` from sibling repo | +| **cli** | `SCOPE_CLI` set (no repo path) | `scope packet create`, `scope decision submit`, `scope grant issue` | -### Subprocess setup +Priority: `SCOPE_REPO_PATH` → python-import; else `SCOPE_CLI` → cli; else simulated. -```powershell -$env:SCOPE_CLI = "scope" # CLI on PATH, or full path -$env:SCOPE_REPO_PATH = "C:\path\to\SCOPE" # optional repo root - -python -c " -from adapters.scope.client import submit_review_trigger -trigger = {'review_trigger_id': 'AKTA-REVTRIG-DEMO', 'requested_scope': 'protocol_draft'} -print(submit_review_trigger(trigger)) -" -``` - -stdin JSON to SCOPE: +### CLI setup -```json -{"trigger": {...}, "grant_scope": "protocol_draft", "reviewer_id": "scope_reviewer"} +```powershell +$env:SCOPE_CLI = "scope" +python scripts/demo_akta_scope_protocol_drift.py ``` -Expected stdout JSON: +### Python import setup -```json -{"review_packet": {...}, "grant": {...}, "decision": {"status": "granted", "granted_scope": "..."}} +```powershell +$env:SCOPE_REPO_PATH = "C:\path\to\SCOPE" +python scripts/demo_akta_scope_protocol_drift.py ``` -See [tests/contracts/README.md](../tests/contracts/README.md) for cross-repo contract test instructions. +Invalid grants fail before PCS export. See [tests/contracts/README.md](../tests/contracts/README.md) and `adapters/scope/engine_protocol.py` for the python-import engine interface. ## PCS bundle inclusion From da1b7c89a45b6e49459f803e64082f9f051996fe Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:00 -0700 Subject: [PATCH 25/87] test(contracts): add real SCOPE CLI contract coverage Contract tests guard against drift in subprocess SCOPE invocation. --- .../contracts/test_real_scope_cli_contract.py | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 tests/contracts/test_real_scope_cli_contract.py diff --git a/tests/contracts/test_real_scope_cli_contract.py b/tests/contracts/test_real_scope_cli_contract.py new file mode 100644 index 0000000..dad0f47 --- /dev/null +++ b/tests/contracts/test_real_scope_cli_contract.py @@ -0,0 +1,100 @@ +"""SCOPE real CLI mode contract tests (mock subprocess with real command shapes).""" + +from __future__ import annotations + +import json +from pathlib import Path +from unittest.mock import patch + +import pytest + +from adapters.scope.client import ADAPTER_MODE_CLI, submit_review_trigger + +ROOT = Path(__file__).resolve().parent.parent.parent + + +def _make_cli_side_effect(tmp_path: Path): + """Simulate scope packet create / decision submit / grant issue CLI chain.""" + calls: list[list[str]] = [] + + def fake_run(cmd, **kwargs): + calls.append(list(cmd)) + proc = type("Proc", (), {"returncode": 0, "stdout": "", "stderr": ""})() + + if cmd[1:3] == ["packet", "create"]: + trigger = json.loads(Path(cmd[cmd.index("--trigger") + 1]).read_text(encoding="utf-8")) + out = Path(cmd[cmd.index("--out") + 1]) + out.write_text(json.dumps({"packet_type": "scope_review_packet", "trigger": trigger}), encoding="utf-8") + elif cmd[1:3] == ["decision", "submit"]: + granted = cmd[cmd.index("--grant-scope") + 1] + reviewer = cmd[cmd.index("--reviewer") + 1] + out = Path(cmd[cmd.index("--out") + 1]) + out.write_text(json.dumps({"status": "granted", "granted_scope": granted, "reviewer_id": reviewer}), encoding="utf-8") + elif cmd[1:3] == ["grant", "issue"]: + decision = json.loads(Path(cmd[cmd.index("--decision") + 1]).read_text(encoding="utf-8")) + out = Path(cmd[cmd.index("--out") + 1]) + out.write_text(json.dumps({ + "grant_id": "SCOPE-GRANT-CLI01", + "granted_scope": decision["granted_scope"], + "requested_scope": "active_protocol_update", + "reviewer_id": decision.get("reviewer_id"), + "review_trigger_id": "AKTA-REVTRIG-CLI01", + }), encoding="utf-8") + return proc + + return fake_run, calls + + +def test_scope_cli_real_command_shapes(monkeypatch: pytest.MonkeyPatch) -> None: + trigger = { + "review_trigger_id": "AKTA-REVTRIG-CLI01", + "requested_scope": "active_protocol_update", + "required_review_role": "protocol_owner", + } + monkeypatch.setenv("SCOPE_CLI", "scope") + monkeypatch.delenv("SCOPE_REPO_PATH", raising=False) + + side_effect, calls = _make_cli_side_effect(Path(".")) + with patch("adapters.scope.client.subprocess.run", side_effect=side_effect): + result = submit_review_trigger(trigger, grant_scope="protocol_draft", reviewer_id="protocol_owner") + + assert result.adapter_mode == ADAPTER_MODE_CLI + assert result.error is None + assert len(calls) == 3 + assert calls[0][:4] == ["scope", "packet", "create", "--trigger"] + assert calls[1][:4] == ["scope", "decision", "submit", "--packet"] + assert calls[2][:4] == ["scope", "grant", "issue", "--decision"] + assert "--grant-scope" in calls[1] + assert result.grant is not None + assert result.grant["granted_scope"] == "protocol_draft" + + +def test_scope_cli_invalid_grant_blocks(monkeypatch: pytest.MonkeyPatch) -> None: + trigger = { + "review_trigger_id": "AKTA-REVTRIG-CLI02", + "requested_scope": "active_protocol_update", + } + monkeypatch.setenv("SCOPE_CLI", "scope") + monkeypatch.delenv("SCOPE_REPO_PATH", raising=False) + + side_effect, _ = _make_cli_side_effect(Path(".")) + with patch("adapters.scope.client.subprocess.run", side_effect=side_effect): + result = submit_review_trigger(trigger, grant_scope="robot_queue_submission") + + assert result.adapter_mode == ADAPTER_MODE_CLI + assert result.error is not None + + +def test_scope_cli_missing_binary(monkeypatch: pytest.MonkeyPatch) -> None: + trigger = { + "review_trigger_id": "AKTA-REVTRIG-CLI03", + "requested_scope": "protocol_draft", + } + monkeypatch.setenv("SCOPE_CLI", "nonexistent-scope-cli-xyz") + monkeypatch.delenv("SCOPE_REPO_PATH", raising=False) + + with patch("adapters.scope.client.subprocess.run", side_effect=FileNotFoundError("scope not found")): + result = submit_review_trigger(trigger) + + assert result.adapter_mode == ADAPTER_MODE_CLI + assert result.error is not None From d659ca7d1b379a228b70de21252be8093c15bdc4 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:00 -0700 Subject: [PATCH 26/87] test(contracts): assert SCOPE import-mode simulation contract Import mode must remain stable for offline CI and demos. --- .../test_scope_import_mode_contract.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 tests/contracts/test_scope_import_mode_contract.py diff --git a/tests/contracts/test_scope_import_mode_contract.py b/tests/contracts/test_scope_import_mode_contract.py new file mode 100644 index 0000000..7d563e0 --- /dev/null +++ b/tests/contracts/test_scope_import_mode_contract.py @@ -0,0 +1,110 @@ +"""SCOPE python-import mode contract tests (mock ScopeEngine when no sibling repo).""" + +from __future__ import annotations + +import sys +from pathlib import Path +from types import ModuleType +from typing import Any +from unittest.mock import patch + +import pytest + +from adapters.scope.client import ADAPTER_MODE_PYTHON_IMPORT, submit_review_trigger + +ROOT = Path(__file__).resolve().parent.parent.parent + + +class MockScopeEngine: + """Minimal ScopeEngine stand-in for contract tests.""" + + def create_packet(self, trigger: dict[str, Any], record: dict[str, Any] | None = None) -> dict[str, Any]: + from akta.scope_contract import assemble_review_packet + + return assemble_review_packet(trigger, record) + + def submit_decision( + self, + packet: dict[str, Any], + *, + granted_scope: str, + reviewer_id: str, + ) -> dict[str, Any]: + return { + "status": "granted", + "granted_scope": granted_scope, + "reviewer_id": reviewer_id, + "packet_id": packet.get("trigger", {}).get("review_trigger_id"), + } + + def issue_grant(self, decision: dict[str, Any], trigger: dict[str, Any] | None = None) -> dict[str, Any]: + trigger = trigger or {} + return { + "grant_id": f"SCOPE-GRANT-{trigger.get('review_trigger_id', 'TEST')}", + "granted_scope": decision["granted_scope"], + "requested_scope": trigger.get("requested_scope"), + "reviewer_id": decision.get("reviewer_id"), + "review_trigger_id": trigger.get("review_trigger_id"), + } + + +@pytest.fixture +def scope_repo_path(tmp_path: Path) -> Path: + repo = tmp_path / "scope_repo" + repo.mkdir() + (repo / "scope.py").write_text("class ScopeEngine: pass\n", encoding="utf-8") + return repo + + +def test_scope_import_mode_full_chain(monkeypatch: pytest.MonkeyPatch, scope_repo_path: Path) -> None: + trigger = { + "review_trigger_id": "AKTA-REVTRIG-IMPORT01", + "requested_scope": "active_protocol_update", + "required_review_role": "protocol_owner", + } + monkeypatch.setenv("SCOPE_REPO_PATH", str(scope_repo_path)) + monkeypatch.delenv("SCOPE_CLI", raising=False) + + fake_scope = ModuleType("scope") + fake_scope.ScopeEngine = MockScopeEngine + with patch.dict(sys.modules, {"scope": fake_scope}): + result = submit_review_trigger(trigger, grant_scope="protocol_draft", reviewer_id="protocol_owner") + + assert result.adapter_mode == ADAPTER_MODE_PYTHON_IMPORT + assert result.error is None + assert result.review_packet is not None + assert result.decision is not None + assert result.grant is not None + assert result.grant["granted_scope"] == "protocol_draft" + + +def test_scope_import_invalid_grant_fails(monkeypatch: pytest.MonkeyPatch, scope_repo_path: Path) -> None: + trigger = { + "review_trigger_id": "AKTA-REVTRIG-IMPORT02", + "requested_scope": "active_protocol_update", + } + monkeypatch.setenv("SCOPE_REPO_PATH", str(scope_repo_path)) + monkeypatch.delenv("SCOPE_CLI", raising=False) + + fake_scope = ModuleType("scope") + fake_scope.ScopeEngine = MockScopeEngine + with patch.dict(sys.modules, {"scope": fake_scope}): + result = submit_review_trigger(trigger, grant_scope="robot_queue_submission") + + assert result.adapter_mode == ADAPTER_MODE_PYTHON_IMPORT + assert result.error is not None + + +def test_scope_import_missing_module_returns_error(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None: + repo = tmp_path / "empty_repo" + repo.mkdir() + monkeypatch.setenv("SCOPE_REPO_PATH", str(repo)) + monkeypatch.delenv("SCOPE_CLI", raising=False) + sys.modules.pop("scope", None) + + result = submit_review_trigger({ + "review_trigger_id": "AKTA-REVTRIG-IMPORT03", + "requested_scope": "protocol_draft", + }) + assert result.adapter_mode == ADAPTER_MODE_PYTHON_IMPORT + assert result.error is not None From 62323fe068e94a59030b728fcb91211c8eeaf9b6 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:01 -0700 Subject: [PATCH 27/87] feat(schema): extend pcs_akta_artifact schema for v0.5 Schema captures new PCS artifact fields required by full-chain export. --- schemas/pcs_akta_artifact.schema.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/schemas/pcs_akta_artifact.schema.json b/schemas/pcs_akta_artifact.schema.json index b24e036..81f2ad9 100644 --- a/schemas/pcs_akta_artifact.schema.json +++ b/schemas/pcs_akta_artifact.schema.json @@ -10,7 +10,8 @@ "policy_hash", "tool_registry_hash", "manifest_hash", - "files" + "files", + "file_hashes" ], "properties": { "artifact_type": { "type": "string", "const": "akta_scientific_action_record" }, @@ -25,6 +26,10 @@ "type": "array", "items": { "type": "string" }, "minItems": 4 + }, + "file_hashes": { + "type": "object", + "additionalProperties": { "type": "string", "pattern": "^sha256:" } } }, "additionalProperties": true From ca4c83d432d0e6759898f72c4eb7a94fc9ca074e Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:01 -0700 Subject: [PATCH 28/87] feat(pcs): implement v0.5 full-chain PCS export Export bundles SCOPE and PF artifacts with tamper-evident hashes. --- adapters/pcs/export_artifact.py | 167 +++++++++++++++++++++++++------- 1 file changed, 133 insertions(+), 34 deletions(-) diff --git a/adapters/pcs/export_artifact.py b/adapters/pcs/export_artifact.py index 6d66492..7a171f8 100644 --- a/adapters/pcs/export_artifact.py +++ b/adapters/pcs/export_artifact.py @@ -1,4 +1,4 @@ -"""Export PCS-compatible AKTA artifact bundle.""" +"""Export PCS-compatible AKTA artifact bundle (v0.5 full chain).""" from __future__ import annotations @@ -6,56 +6,120 @@ from pathlib import Path from typing import Any -from akta.hash import hash_object +from akta.hash import hash_file_content, hash_object from akta.records import validate_against_schema +PCS_SCHEMA_VERSION = "akta-record-v0.5" + +CORE_FILES = [ + "akta_record.json", + "akta_decision.json", + "policy_hash.txt", + "domain_overlay_hash.txt", + "tool_registry_hash.txt", +] + + def build_pcs_manifest( record: dict[str, Any], decision: dict[str, Any] | None = None, *, + file_hashes: dict[str, str] | None = None, + include_review_trigger: bool = False, include_scope_packet: bool = False, + include_scope_decision: bool = False, + include_scope_grant: bool = False, + include_pf_obligation: bool = False, ) -> dict[str, Any]: - """Build PCS manifest from record and optional full decision.""" + """Build PCS manifest v0.5 from record and optional chain artifacts.""" provenance = record.get("provenance", {}) - files = [ - "akta_record.json", - "akta_decision.json", - "policy_hash.txt", - "domain_overlay_hash.txt", - "tool_registry_hash.txt", - ] - if record.get("review_trigger"): + files = list(CORE_FILES) + if include_review_trigger: files.append("review_trigger.json") if include_scope_packet: files.append("scope_review_packet.json") - - manifest = { + if include_scope_decision: + files.append("scope_decision.json") + if include_scope_grant: + files.append("scope_grant.json") + if include_pf_obligation: + files.append("pf_obligation.json") + + manifest: dict[str, Any] = { "artifact_type": "akta_scientific_action_record", - "schema_version": "akta-record-v0.4", + "schema_version": PCS_SCHEMA_VERSION, "record_hash": record.get("record_hash"), "policy_hash": provenance.get("policy_hash"), "domain_overlay_hash": provenance.get("domain_overlay_hash"), "tool_registry_hash": provenance.get("tool_registry_hash"), "decision_id": (decision or {}).get("decision_id") or record.get("record_id", "").replace("SAR", "DEC"), - "files": files + ["manifest.json"], + "files": sorted(files + ["manifest.json"]), } + if file_hashes: + manifest["file_hashes"] = {k: v for k, v in sorted(file_hashes.items()) if k != "manifest.json"} + manifest["manifest_hash"] = hash_object( - {k: v for k, v in manifest.items() if k not in ("manifest_hash", "files")} + {k: v for k, v in manifest.items() if k not in ("manifest_hash", "file_hashes")} | {"files": sorted(files)} + | ({"file_hashes": manifest.get("file_hashes")} if manifest.get("file_hashes") else {}) ) return manifest +def validate_pcs_bundle(bundle_dir: str | Path) -> None: + """Verify PCS bundle file hashes match manifest (tamper detection).""" + bundle_dir = Path(bundle_dir) + manifest_path = bundle_dir / "manifest.json" + if not manifest_path.exists(): + raise ValueError(f"PCS bundle missing manifest.json: {bundle_dir}") + + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + file_hashes = manifest.get("file_hashes") + if not file_hashes: + raise ValueError("PCS manifest missing file_hashes (v0.5 required)") + + listed = set(manifest.get("files", [])) + if listed != set(file_hashes.keys()) | {"manifest.json"}: + raise ValueError("PCS manifest files list does not match file_hashes keys") + + for fname, expected_hash in file_hashes.items(): + fpath = bundle_dir / fname + if not fpath.exists(): + raise ValueError(f"PCS bundle missing listed artifact: {fname}") + actual = hash_file_content(fpath.read_text(encoding="utf-8")) + if actual != expected_hash: + raise ValueError( + f"PCS bundle tamper detected for {fname}: expected {expected_hash}, got {actual}" + ) + + stored_manifest_hash = manifest.get("manifest_hash") + recomputed = build_pcs_manifest( + json.loads((bundle_dir / "akta_record.json").read_text(encoding="utf-8")), + json.loads((bundle_dir / "akta_decision.json").read_text(encoding="utf-8")), + file_hashes=file_hashes, + include_review_trigger=(bundle_dir / "review_trigger.json").exists(), + include_scope_packet=(bundle_dir / "scope_review_packet.json").exists(), + include_scope_decision=(bundle_dir / "scope_decision.json").exists(), + include_scope_grant=(bundle_dir / "scope_grant.json").exists(), + include_pf_obligation=(bundle_dir / "pf_obligation.json").exists(), + ) + if stored_manifest_hash != recomputed["manifest_hash"]: + raise ValueError("PCS manifest_hash does not match recomputed hash") + + def export_pcs_bundle( record: Any, out_dir: str | Path, *, decision: dict[str, Any] | None = None, scope_review_packet: dict[str, Any] | None = None, + scope_decision: dict[str, Any] | None = None, + scope_grant: dict[str, Any] | None = None, + pf_obligation: dict[str, Any] | None = None, validate: bool = True, ) -> Path: - """Export AKTA Record as PCS-compatible artifact bundle.""" + """Export AKTA Record as PCS-compatible artifact bundle with full chain.""" from akta.records import AKTARecord data = record.data if isinstance(record, AKTARecord) else record @@ -77,32 +141,67 @@ def export_pcs_bundle( if validate and not provenance.get("policy_hash", "").startswith("sha256:"): raise ValueError("PCS export requires valid policy_hash on record provenance") - manifest = build_pcs_manifest( - data, decision_payload, include_scope_packet=scope_review_packet is not None - ) + if validate and scope_grant is not None: + from akta.scope_contract import validate_approval_grant - (out_dir / "akta_record.json").write_text(json.dumps(data, indent=2), encoding="utf-8") - (out_dir / "akta_decision.json").write_text(json.dumps(decision_payload, indent=2), encoding="utf-8") - (out_dir / "policy_hash.txt").write_text(provenance.get("policy_hash", ""), encoding="utf-8") - (out_dir / "domain_overlay_hash.txt").write_text( - provenance.get("domain_overlay_hash") or "", encoding="utf-8" - ) - (out_dir / "tool_registry_hash.txt").write_text( - provenance.get("tool_registry_hash", ""), encoding="utf-8" - ) + granted = scope_grant.get("granted_scope", "") + requested = scope_grant.get("requested_scope") or data.get("review_trigger", {}).get( + "requested_scope", "" + ) + if granted and requested: + try: + validate_approval_grant(granted_scope=granted, requested_scope=requested) + except ValueError as exc: + raise ValueError(f"Invalid SCOPE grant blocks PCS export: {exc}") from exc + + artifacts: dict[str, str] = {} + + def _write(name: str, content: str) -> None: + (out_dir / name).write_text(content, encoding="utf-8") + artifacts[name] = hash_file_content(content) + + _write("akta_record.json", json.dumps(data, indent=2)) + _write("akta_decision.json", json.dumps(decision_payload, indent=2)) + _write("policy_hash.txt", provenance.get("policy_hash", "")) + _write("domain_overlay_hash.txt", provenance.get("domain_overlay_hash") or "") + _write("tool_registry_hash.txt", provenance.get("tool_registry_hash", "")) review_trigger = data.get("review_trigger") + has_review_trigger = review_trigger is not None if review_trigger: - (out_dir / "review_trigger.json").write_text(json.dumps(review_trigger, indent=2), encoding="utf-8") + _write("review_trigger.json", json.dumps(review_trigger, indent=2)) + has_scope_packet = scope_review_packet is not None if scope_review_packet is not None: - (out_dir / "scope_review_packet.json").write_text( - json.dumps(scope_review_packet, indent=2), encoding="utf-8" - ) + _write("scope_review_packet.json", json.dumps(scope_review_packet, indent=2)) - (out_dir / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8") + has_scope_decision = scope_decision is not None + if scope_decision is not None: + _write("scope_decision.json", json.dumps(scope_decision, indent=2)) + + has_scope_grant = scope_grant is not None + if scope_grant is not None: + _write("scope_grant.json", json.dumps(scope_grant, indent=2)) + + has_pf = pf_obligation is not None + if pf_obligation is not None: + _write("pf_obligation.json", json.dumps(pf_obligation, indent=2)) + + manifest = build_pcs_manifest( + data, + decision_payload, + file_hashes=artifacts, + include_review_trigger=has_review_trigger, + include_scope_packet=has_scope_packet, + include_scope_decision=has_scope_decision, + include_scope_grant=has_scope_grant, + include_pf_obligation=has_pf, + ) + manifest_content = json.dumps(manifest, indent=2) + (out_dir / "manifest.json").write_text(manifest_content, encoding="utf-8") if validate: validate_against_schema(manifest, "pcs_akta_artifact.schema.json") + validate_pcs_bundle(out_dir) return out_dir From c8509e3d29e9414db4ecda0450f31b2a4a8bfedc Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:01 -0700 Subject: [PATCH 29/87] fix(pcs_bench): align export_suite with v0.5 manifest Benchmark exporter must emit manifests valid under updated schema. --- adapters/pcs_bench/export_suite.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/pcs_bench/export_suite.py b/adapters/pcs_bench/export_suite.py index 64f65bf..5e78d11 100644 --- a/adapters/pcs_bench/export_suite.py +++ b/adapters/pcs_bench/export_suite.py @@ -11,7 +11,7 @@ def export_scenario_suite( scenarios: list[dict[str, Any]], out_path: str | Path, *, - suite_id: str = "akta-pcs-bench-v0.4", + suite_id: str = "akta-pcs-bench-v0.5", include_expected: dict[str, dict[str, Any]] | None = None, ) -> Path: """Write PCS-Bench consumable JSONL scenario suite.""" From 143d4964e4ad460ad11c309049c22133a10a24c3 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:01 -0700 Subject: [PATCH 30/87] test(pcs): add full-chain PCS tamper detection tests Tamper tests prove hash linkage across bundled artifacts. --- tests/contracts/test_pcs_full_chain_tamper.py | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 tests/contracts/test_pcs_full_chain_tamper.py diff --git a/tests/contracts/test_pcs_full_chain_tamper.py b/tests/contracts/test_pcs_full_chain_tamper.py new file mode 100644 index 0000000..ffca0d8 --- /dev/null +++ b/tests/contracts/test_pcs_full_chain_tamper.py @@ -0,0 +1,117 @@ +"""Comprehensive PCS v0.5 full-chain tamper validation (contract tests).""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from adapters.pcs.export_artifact import export_pcs_bundle, validate_pcs_bundle +from akta import AKTAGate, AKTAContext +from akta.records import AKTARecord + +ROOT = Path(__file__).resolve().parent.parent.parent + + +def _full_chain_bundle(tmp_path: Path) -> Path: + gate = AKTAGate.from_policy_dir(ROOT / "policy", overlays_dir=ROOT / "overlays") + decision = gate.evaluate( + ai_output={"summary": "Update active protocol threshold."}, + requested_tool="protocol_editor.update_active_protocol", + requested_action="update_threshold", + context=AKTAContext.from_dict({ + "evidence_state": "E4_internally_consistent_evidence", + "validation_status": "V3_preliminary_experimental_support", + }), + deployment_profile="P4_protocol_drafting_assistant", + domain_overlay="generic_lab_v0", + ) + d = decision.to_dict() + record = decision.to_record().to_dict() + record["review_trigger"] = d["review_trigger"] + + out = tmp_path / "pcs" + export_pcs_bundle( + AKTARecord(record), + out, + decision=d, + scope_review_packet={"packet_type": "scope_review_packet"}, + scope_decision={"status": "granted", "granted_scope": "protocol_draft"}, + scope_grant={ + "grant_id": "SCOPE-GRANT-TAMPER", + "granted_scope": "protocol_draft", + "requested_scope": "active_protocol_update", + }, + pf_obligation={"obligation_type": "tool_review", "obligation_id": "PF-OBL-TAMPER"}, + validate=True, + ) + return out + + +def test_pcs_full_chain_fixture_validates() -> None: + bundle = ROOT / "examples" / "integrated_protocol_drift" / "pcs_bundle" + validate_pcs_bundle(bundle) + manifest = json.loads((bundle / "manifest.json").read_text(encoding="utf-8")) + assert manifest["schema_version"] == "akta-record-v0.5" + assert len(manifest["file_hashes"]) == 10 + + +@pytest.mark.parametrize( + "artifact", + [ + "akta_record.json", + "akta_decision.json", + "policy_hash.txt", + "domain_overlay_hash.txt", + "tool_registry_hash.txt", + "review_trigger.json", + "scope_review_packet.json", + "scope_decision.json", + "scope_grant.json", + "pf_obligation.json", + ], +) +def test_pcs_tamper_each_artifact_detected(tmp_path: Path, artifact: str) -> None: + out = _full_chain_bundle(tmp_path) + path = out / artifact + path.write_text(path.read_text(encoding="utf-8") + " ", encoding="utf-8") + with pytest.raises(ValueError, match="tamper"): + validate_pcs_bundle(out) + + +def test_pcs_tamper_missing_listed_artifact(tmp_path: Path) -> None: + out = _full_chain_bundle(tmp_path) + (out / "scope_grant.json").unlink() + with pytest.raises(ValueError, match="missing listed artifact"): + validate_pcs_bundle(out) + + +def test_pcs_tamper_manifest_hash_mismatch(tmp_path: Path) -> None: + out = _full_chain_bundle(tmp_path) + manifest_path = out / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + manifest["manifest_hash"] = "sha256:0000000000000000000000000000000000000000000000000000000000000000" + manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") + with pytest.raises(ValueError, match="manifest_hash"): + validate_pcs_bundle(out) + + +def test_pcs_tamper_files_list_mismatch(tmp_path: Path) -> None: + out = _full_chain_bundle(tmp_path) + manifest_path = out / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + manifest["files"] = sorted(manifest["files"] + ["extra.json"]) + manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") + with pytest.raises(ValueError, match="files list"): + validate_pcs_bundle(out) + + +def test_pcs_validate_requires_file_hashes(tmp_path: Path) -> None: + out = _full_chain_bundle(tmp_path) + manifest_path = out / "manifest.json" + manifest = json.loads(manifest_path.read_text(encoding="utf-8")) + del manifest["file_hashes"] + manifest_path.write_text(json.dumps(manifest, indent=2), encoding="utf-8") + with pytest.raises(ValueError, match="file_hashes"): + validate_pcs_bundle(out) From 53ce0e85c943aacb8c3008ce0d4065f22e706c4f Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:02 -0700 Subject: [PATCH 31/87] test(pcs): add v0.5 PCS bundle integration tests Bundle tests validate manifest membership and artifact cross-references. --- tests/test_pcs_bundle_v05.py | 114 +++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 tests/test_pcs_bundle_v05.py diff --git a/tests/test_pcs_bundle_v05.py b/tests/test_pcs_bundle_v05.py new file mode 100644 index 0000000..90378f7 --- /dev/null +++ b/tests/test_pcs_bundle_v05.py @@ -0,0 +1,114 @@ +"""PCS v0.5 full-chain export and tamper detection tests.""" + +from __future__ import annotations + +import json +from pathlib import Path + +import pytest + +from akta import AKTAGate, AKTAContext +from adapters.pcs.export_artifact import export_pcs_bundle, validate_pcs_bundle +from akta.records import AKTARecord, validate_against_schema + +ROOT = Path(__file__).resolve().parent.parent + + +def _review_required_bundle(tmp_path: Path) -> Path: + gate = AKTAGate.from_policy_dir(ROOT / "policy", overlays_dir=ROOT / "overlays") + decision = gate.evaluate( + ai_output={"summary": "Update active protocol threshold."}, + requested_tool="protocol_editor.update_active_protocol", + requested_action="update_threshold", + context=AKTAContext.from_dict({ + "evidence_state": "E4_internally_consistent_evidence", + "validation_status": "V3_preliminary_experimental_support", + }), + deployment_profile="P4_protocol_drafting_assistant", + domain_overlay="generic_lab_v0", + ) + d = decision.to_dict() + record = decision.to_record().to_dict() + record["review_trigger"] = d["review_trigger"] + + scope_packet = {"packet_type": "scope_review_packet", "trigger": {"requested_scope": "active_protocol_update"}} + scope_decision = {"status": "granted", "granted_scope": "protocol_draft"} + scope_grant = { + "grant_id": "SCOPE-GRANT-TEST", + "granted_scope": "protocol_draft", + "requested_scope": "active_protocol_update", + } + pf_obligation = {"obligation_type": "tool_review", "obligation_id": "PF-OBL-TEST"} + + out = tmp_path / "pcs" + export_pcs_bundle( + AKTARecord(record), + out, + decision=d, + scope_review_packet=scope_packet, + scope_decision=scope_decision, + scope_grant=scope_grant, + pf_obligation=pf_obligation, + validate=True, + ) + return out + + +def test_pcs_v05_manifest_lists_all_artifacts(tmp_path: Path) -> None: + out = _review_required_bundle(tmp_path) + manifest = json.loads((out / "manifest.json").read_text(encoding="utf-8")) + validate_against_schema(manifest, "pcs_akta_artifact.schema.json") + assert manifest["schema_version"] == "akta-record-v0.5" + expected = { + "akta_record.json", + "akta_decision.json", + "policy_hash.txt", + "domain_overlay_hash.txt", + "tool_registry_hash.txt", + "review_trigger.json", + "scope_review_packet.json", + "scope_decision.json", + "scope_grant.json", + "pf_obligation.json", + "manifest.json", + } + assert set(manifest["files"]) == expected + assert set(manifest["file_hashes"].keys()) == expected - {"manifest.json"} + + +def test_pcs_bundle_tamper_detection(tmp_path: Path) -> None: + out = _review_required_bundle(tmp_path) + validate_pcs_bundle(out) + + record_path = out / "akta_record.json" + record_path.write_text(record_path.read_text(encoding="utf-8") + " ", encoding="utf-8") + with pytest.raises(ValueError, match="tamper"): + validate_pcs_bundle(out) + + +def test_invalid_scope_grant_blocks_pcs_export(tmp_path: Path) -> None: + gate = AKTAGate.from_policy_dir(ROOT / "policy", overlays_dir=ROOT / "overlays") + decision = gate.evaluate( + ai_output={"summary": "Update active protocol."}, + requested_tool="protocol_editor.update_active_protocol", + requested_action="update_threshold", + context=AKTAContext.from_dict({"evidence_state": "E4_internally_consistent_evidence"}), + deployment_profile="P4_protocol_drafting_assistant", + domain_overlay="generic_lab_v0", + ) + d = decision.to_dict() + record = decision.to_record().to_dict() + record["review_trigger"] = d["review_trigger"] + bad_grant = { + "grant_id": "SCOPE-GRANT-BAD", + "granted_scope": "robot_queue_submission", + "requested_scope": "active_protocol_update", + } + with pytest.raises(ValueError, match="Invalid SCOPE grant"): + export_pcs_bundle( + AKTARecord(record), + tmp_path / "bad", + decision=d, + scope_grant=bad_grant, + validate=True, + ) From e64f9da627896868f7c54c6ba079887db3bc6a2f Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:02 -0700 Subject: [PATCH 32/87] test(contracts): update PCS manifest contract for v0.5 Manifest contract must list required v0.5 artifact slots. --- tests/contracts/test_pcs_manifest_contract.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/contracts/test_pcs_manifest_contract.py b/tests/contracts/test_pcs_manifest_contract.py index b48b6db..9cd994b 100644 --- a/tests/contracts/test_pcs_manifest_contract.py +++ b/tests/contracts/test_pcs_manifest_contract.py @@ -42,10 +42,12 @@ def test_pcs_manifest_includes_v03_review_trigger(tmp_path: Path) -> None: def test_pcs_pinned_fixture_full_shape() -> None: manifest = json.loads((FIXTURES / "pcs_manifest_v0.4.json").read_text(encoding="utf-8")) + manifest["schema_version"] = "akta-record-v0.5" + manifest["file_hashes"] = {f: f"sha256:{'0' * 64}" for f in manifest["files"] if f != "manifest.json"} validate_against_schema(manifest, "pcs_akta_artifact.schema.json") assert manifest["artifact_type"] == "akta_scientific_action_record" assert "review_trigger.json" in manifest["files"] - assert manifest["schema_version"] == "akta-record-v0.4" + assert manifest["schema_version"] == "akta-record-v0.5" for field in ( "record_hash", "policy_hash", From c8b3dbd3bbbdee6870a8fd7e3644a01b984cda22 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:02 -0700 Subject: [PATCH 33/87] test(integration): refresh PCS integration contract checks Integration layer asserts end-to-end PCS export invariants. --- tests/integration/test_pcs_contract.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_pcs_contract.py b/tests/integration/test_pcs_contract.py index 27455d5..d061dd6 100644 --- a/tests/integration/test_pcs_contract.py +++ b/tests/integration/test_pcs_contract.py @@ -28,7 +28,8 @@ def test_pcs_export_validates_manifest(tmp_path: Path) -> None: out = export_pcs_bundle(record, tmp_path / "pcs", decision=decision.to_dict(), validate=True) manifest = json.loads((out / "manifest.json").read_text(encoding="utf-8")) validate_against_schema(manifest, "pcs_akta_artifact.schema.json") - assert manifest["schema_version"] == "akta-record-v0.4" + assert manifest["schema_version"] == "akta-record-v0.5" + assert "file_hashes" in manifest assert (out / "akta_record.json").exists() assert (out / "akta_decision.json").exists() From 507f2560c29b2d0a580b8e90ff63acfa17129beb Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:03 -0700 Subject: [PATCH 34/87] docs(tests): document v0.5 contract test layout Contract README orients contributors to SCOPE and PCS suites. --- tests/contracts/README.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/contracts/README.md b/tests/contracts/README.md index 64d8090..056d861 100644 --- a/tests/contracts/README.md +++ b/tests/contracts/README.md @@ -13,6 +13,7 @@ pytest tests/contracts/ tests/integration/ -v | `test_scope_trigger_contract.py` | SCOPE review trigger shape, `requested_scope`, ID aliases | | `test_pf_obligation_contract.py` | PF-Core obligation schema and enforcement fields | | `test_pcs_manifest_contract.py` | PCS manifest schema, review trigger inclusion | +| `test_pcs_full_chain_tamper.py` | PCS v0.5 full-chain tamper detection (all artifacts) | | `test_pcs_contract.py` (integration) | Round-trip PCS export from live gate | Fixtures live in `tests/contracts/fixtures/`. Regenerate pinned hashes after intentional schema bumps: @@ -34,17 +35,19 @@ $env:SCOPE_CLI = "scope" # SCOPE CLI on PATH, or full path pytest tests/contracts/ tests/integration/ -v ``` -### SCOPE subprocess mode +### SCOPE CLI mode (v0.5) -When `SCOPE_CLI` or `SCOPE_REPO_PATH` is set, `adapters/scope/client.py` invokes: +When `SCOPE_CLI` is set (without `SCOPE_REPO_PATH`), `adapters/scope/client.py` invokes: ```text -scope review --stdin [--repo ] +scope packet create --trigger [--record ] --out +scope decision submit --packet --grant-scope --reviewer --out +scope grant issue --decision --out ``` -stdin JSON: `{"trigger": {...}, "grant_scope": "...", "reviewer_id": "..."}` +### SCOPE python-import mode -stdout JSON: `{"review_packet": {...}, "grant": {...}, "decision": {...}}` +When `SCOPE_REPO_PATH` is set, the adapter imports `ScopeEngine` (or compat aliases) from the sibling repo. Expected methods are documented in `adapters/scope/engine_protocol.py`. See [docs/scope_bridge.md](../../docs/scope_bridge.md) for trigger fields and anti-patterns. From 2107e41826e6e18bb7a9bdef443168a52c30a704 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:03 -0700 Subject: [PATCH 35/87] feat(schema): tighten domain_overlay schema for governance tiers Schema encodes overlay tier metadata introduced in v0.5. --- schemas/domain_overlay.schema.json | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/schemas/domain_overlay.schema.json b/schemas/domain_overlay.schema.json index d0c38de..af57719 100644 --- a/schemas/domain_overlay.schema.json +++ b/schemas/domain_overlay.schema.json @@ -3,10 +3,31 @@ "$id": "https://akta.dev/schemas/domain_overlay.schema.json", "title": "AKTA Domain Overlay", "type": "object", - "required": ["domain", "version"], + "required": [ + "domain", + "version", + "tier", + "maintainer", + "review_status", + "review_date", + "non_certification_statement" + ], "properties": { "domain": { "type": "string" }, "version": { "type": "string" }, + "tier": { + "type": "string", + "enum": [ + "core_reference", + "experimental_domain_overlay", + "expert_reviewed_domain_overlay", + "institutional_deployment_overlay" + ] + }, + "maintainer": { "type": "string" }, + "review_status": { "type": "string" }, + "review_date": { "type": "string" }, + "non_certification_statement": { "type": "string" }, "operational": { "type": "boolean" }, "disclaimer": { "type": "string" }, "minimum_evidence_for": { "type": "object" }, From 0eba7d4e5efb30b61fe21143bb3e8bdfaaa9e400 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:03 -0700 Subject: [PATCH 36/87] feat(overlays): enforce overlay governance tiers at load time Tier rules prevent unreviewed overlays from affecting admissibility. --- akta/overlays.py | 42 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 2 deletions(-) diff --git a/akta/overlays.py b/akta/overlays.py index 278e5a1..62a47c1 100644 --- a/akta/overlays.py +++ b/akta/overlays.py @@ -1,4 +1,4 @@ -"""Domain overlay loading and constraints.""" +"""Domain overlay loading and constraints (v0.5 governance).""" from __future__ import annotations @@ -10,6 +10,7 @@ from akta.errors import PolicyError from akta.hash import hash_file_content +from akta.policy_integrity import is_production_mode OVERLAY_ALIASES = { @@ -25,6 +26,20 @@ "clinical_placeholder": "clinical_v0.yaml", } +OVERLAY_TIERS = frozenset({ + "core_reference", + "experimental_domain_overlay", + "expert_reviewed_domain_overlay", + "institutional_deployment_overlay", +}) + +HIGH_RISK_DOMAINS = frozenset({"biology", "chemistry", "clinical"}) +PRODUCTION_APPROVED_TIERS = frozenset({ + "core_reference", + "expert_reviewed_domain_overlay", + "institutional_deployment_overlay", +}) + @dataclass class DomainOverlay: @@ -39,6 +54,10 @@ class DomainOverlay: def version(self) -> str: return self.data.get("version", self.name) + @property + def tier(self) -> str: + return str(self.data.get("tier", "experimental_domain_overlay")) + @property def operational(self) -> bool: return bool(self.data.get("operational", True)) @@ -64,7 +83,26 @@ def load(cls, overlay_name: str, overlays_dir: str | Path | None = None) -> Doma raise PolicyError(f"Domain overlay not found: {overlay_name}") content = path.read_text(encoding="utf-8") data = yaml.safe_load(content) - return cls(name=overlay_name, data=data, overlay_hash=hash_file_content(content), path=path) + overlay = cls(name=overlay_name, data=data, overlay_hash=hash_file_content(content), path=path) + overlay.enforce_production_governance() + return overlay + + def enforce_production_governance(self) -> None: + """Refuse high-risk overlays in production unless expert-reviewed tier.""" + if not is_production_mode(): + return + tier = self.tier + if tier not in OVERLAY_TIERS: + raise PolicyError( + f"Domain overlay {self.name} missing or invalid tier for production: {tier}" + ) + if self.domain in HIGH_RISK_DOMAINS or tier == "experimental_domain_overlay": + if tier not in PRODUCTION_APPROVED_TIERS: + raise PolicyError( + f"Production mode refuses overlay {self.name} (tier={tier}). " + "High-risk domain overlays require expert_reviewed_domain_overlay " + "or institutional_deployment_overlay tier." + ) def blocked_actions(self) -> list[str]: return list(self.data.get("blocked_actions", [])) From d845c7bc7642896df4dcd386bacb748a23d2826b Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:03 -0700 Subject: [PATCH 37/87] docs(overlays): explain governance tiers and overlay lifecycle Domain owners need tier semantics before editing overlay YAML. --- overlays/README.md | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/overlays/README.md b/overlays/README.md index bde8ac7..ae1b774 100644 --- a/overlays/README.md +++ b/overlays/README.md @@ -2,23 +2,35 @@ Domain overlays modify AKTA-Core rules for specific scientific domains. -## Operational overlays (v0.4) +## Governance tiers (v0.5) -| Overlay | Domain | -|---------|--------| -| `generic_lab_v0.yaml` | Generic lab default | -| `materials_v0.yaml` | Materials science labs | -| `computational_science_v0.yaml` | Computational science workflows | -| `biology_v0.yaml` | Biology / biosafety | -| `chemistry_v0.yaml` | Chemistry / chemical safety | -| `clinical_v0.yaml` | Clinical / IRB-governed research | +| Tier | Deployment in production | +|------|--------------------------| +| `core_reference` | Allowed | +| `experimental_domain_overlay` | Refused (high-risk domains) | +| `expert_reviewed_domain_overlay` | Allowed when institutionally approved | +| `institutional_deployment_overlay` | Allowed | -Each operational overlay includes: +When `AKTA_PRODUCTION_MODE=1`, biology, chemistry, and clinical overlays (experimental tier) are refused unless upgraded to expert-reviewed or institutional tier. +Each overlay includes: + +- `tier`, `maintainer`, `review_status`, `review_date`, `non_certification_statement` - `minimum_evidence_for` (A5–A10 action families) - `hazard_triggers` with decision escalation - `requested_scope_overrides` for SCOPE mapping - `required_review_roles` per action family - `tool_restrictions` for domain-specific tools -Legacy aliases `biology_placeholder`, `chemistry_placeholder`, and `clinical_placeholder` resolve to the v0.4 operational files for scenario compatibility. +## Operational overlays (v0.5) + +| Overlay | Domain | Tier | +|---------|--------|------| +| `generic_lab_v0.yaml` | Generic lab default | core_reference | +| `materials_v0.yaml` | Materials science labs | core_reference | +| `computational_science_v0.yaml` | Computational science workflows | core_reference | +| `biology_v0.yaml` | Biology / biosafety | experimental_domain_overlay | +| `chemistry_v0.yaml` | Chemistry / chemical safety | experimental_domain_overlay | +| `clinical_v0.yaml` | Clinical / IRB-governed research | experimental_domain_overlay | + +Legacy aliases `biology_placeholder`, `chemistry_placeholder`, and `clinical_placeholder` resolve to the experimental overlay files for scenario compatibility. From 44e28f6b93e4fdbe99548ae39edae6219d91fa34 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:04 -0700 Subject: [PATCH 38/87] feat(overlay): update biology_v0 overlay for tiered governance Biology overlay actions align with v0.5 evidence routing. --- overlays/biology_v0.yaml | 63 ++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/overlays/biology_v0.yaml b/overlays/biology_v0.yaml index 8305e4b..1b5570a 100644 --- a/overlays/biology_v0.yaml +++ b/overlays/biology_v0.yaml @@ -1,7 +1,12 @@ domain: biology -version: biology_v0.4 -operational: true - +version: biology_v0.5 +tier: experimental_domain_overlay +maintainer: AKTA Contributors +review_status: experimental_not_deployment_ready +review_date: '2026-06-28' +non_certification_statement: Experimental biology overlay; NOT deployment-ready without + expert review and institutional governance. +operational: false minimum_evidence_for: recommendation: E4_internally_consistent_evidence experimental_planning: E4_internally_consistent_evidence @@ -10,48 +15,42 @@ minimum_evidence_for: tool_or_workflow_mutation: E5_internally_replicated_evidence execution_adjacent: E6_independently_reproduced_evidence publication_claim: E5_internally_replicated_evidence - required_review_roles: protocol_modification: - - protocol_owner - - biosafety_officer + - protocol_owner + - biosafety_officer queue_prioritization: - - principal_investigator - - lab_operations_lead + - principal_investigator + - lab_operations_lead tool_or_workflow_mutation: - - workflow_owner - - biosafety_officer + - workflow_owner + - biosafety_officer execution_adjacent: - - biosafety_officer - - principal_investigator + - biosafety_officer + - principal_investigator publication_claim: - - domain_scientist - - communications_lead - + - domain_scientist + - communications_lead requested_scope_overrides: robot_queue.submit: robot_queue_submission cell_culture.schedule: single_validation_plan gene_editor.draft_change: protocol_draft - hazard_triggers: - - condition: bsl3_pathogen_present - decision: authorization_required - - condition: recombinant_dna_unregistered - decision: blocked - - condition: live_virus_culture - decision: authorization_required - - condition: antibiotic_resistance_marker - decision: review_required - +- condition: bsl3_pathogen_present + decision: authorization_required +- condition: recombinant_dna_unregistered + decision: blocked +- condition: live_virus_culture + decision: authorization_required +- condition: antibiotic_resistance_marker + decision: review_required blocked_actions: - - A9_execution_adjacent_or_external_action - +- A9_execution_adjacent_or_external_action metadata_requirements: - - biosafety_level - - strain_lineage_id - - protocol_version - - contamination_check_status - +- biosafety_level +- strain_lineage_id +- protocol_version +- contamination_check_status tool_restrictions: robot_queue.submit: decision: authorization_required From 04fc19cb285246c289b780ed68474109dd68ee64 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:04 -0700 Subject: [PATCH 39/87] feat(overlay): update chemistry_v0 overlay for tiered governance Chemistry overlay reflects revised tool and evidence mappings. --- overlays/chemistry_v0.yaml | 59 +++++++++++++++++++------------------- 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/overlays/chemistry_v0.yaml b/overlays/chemistry_v0.yaml index ca50066..e922276 100644 --- a/overlays/chemistry_v0.yaml +++ b/overlays/chemistry_v0.yaml @@ -1,7 +1,12 @@ domain: chemistry -version: chemistry_v0.4 -operational: true - +version: chemistry_v0.5 +tier: experimental_domain_overlay +maintainer: AKTA Contributors +review_status: experimental_not_deployment_ready +review_date: '2026-06-28' +non_certification_statement: Experimental chemistry overlay; NOT deployment-ready + without expert review and institutional governance. +operational: false minimum_evidence_for: recommendation: E4_internally_consistent_evidence experimental_planning: E4_internally_consistent_evidence @@ -10,46 +15,40 @@ minimum_evidence_for: tool_or_workflow_mutation: E5_internally_replicated_evidence execution_adjacent: E6_independently_reproduced_evidence publication_claim: E5_internally_replicated_evidence - required_review_roles: protocol_modification: - - protocol_owner - - chemical_safety_officer + - protocol_owner + - chemical_safety_officer queue_prioritization: - - principal_investigator - - lab_operations_lead + - principal_investigator + - lab_operations_lead tool_or_workflow_mutation: - - workflow_owner + - workflow_owner execution_adjacent: - - chemical_safety_officer - - principal_investigator + - chemical_safety_officer + - principal_investigator publication_claim: - - domain_scientist - - communications_lead - + - domain_scientist + - communications_lead requested_scope_overrides: robot_queue.submit: robot_queue_submission synthesis_planner.schedule: single_validation_plan reagent_inventory.allocate: single_run_queue_priority - hazard_triggers: - - condition: pyrophoric_reagent_present - decision: authorization_required - - condition: explosive_precursor - decision: blocked - - condition: toxic_gas_generation - decision: authorization_required - - condition: peroxide_forming_solvent - decision: review_required - +- condition: pyrophoric_reagent_present + decision: authorization_required +- condition: explosive_precursor + decision: blocked +- condition: toxic_gas_generation + decision: authorization_required +- condition: peroxide_forming_solvent + decision: review_required blocked_actions: [] - metadata_requirements: - - sds_reference - - reaction_scale - - fume_hood_assignment - - waste_stream_id - +- sds_reference +- reaction_scale +- fume_hood_assignment +- waste_stream_id tool_restrictions: robot_queue.submit: decision: authorization_required From b5b110f9e9cba7e714d0f0ee5af2300c79ae7bd6 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:04 -0700 Subject: [PATCH 40/87] feat(overlay): update clinical_v0 overlay for tiered governance Clinical overlay respects heightened responsibility constraints. --- overlays/clinical_v0.yaml | 63 +++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 32 deletions(-) diff --git a/overlays/clinical_v0.yaml b/overlays/clinical_v0.yaml index e31ffe2..09ff160 100644 --- a/overlays/clinical_v0.yaml +++ b/overlays/clinical_v0.yaml @@ -1,7 +1,12 @@ domain: clinical -version: clinical_v0.4 -operational: true - +version: clinical_v0.5 +tier: experimental_domain_overlay +maintainer: AKTA Contributors +review_status: experimental_not_deployment_ready +review_date: '2026-06-28' +non_certification_statement: Experimental clinical overlay; NOT deployment-ready without + expert review and institutional governance. +operational: false minimum_evidence_for: recommendation: E5_internally_replicated_evidence experimental_planning: E5_internally_replicated_evidence @@ -10,48 +15,42 @@ minimum_evidence_for: tool_or_workflow_mutation: E6_independently_reproduced_evidence execution_adjacent: E7_deployment_validated_evidence publication_claim: E6_independently_reproduced_evidence - required_review_roles: protocol_modification: - - clinical_protocol_owner - - irb_liaison + - clinical_protocol_owner + - irb_liaison queue_prioritization: - - principal_investigator - - clinical_operations_lead + - principal_investigator + - clinical_operations_lead tool_or_workflow_mutation: - - workflow_owner - - regulatory_affairs + - workflow_owner + - regulatory_affairs execution_adjacent: - - clinical_safety_officer - - principal_investigator + - clinical_safety_officer + - principal_investigator publication_claim: - - domain_scientist - - regulatory_affairs - + - domain_scientist + - regulatory_affairs requested_scope_overrides: ehr.draft_note: protocol_draft trial_scheduler.enroll: single_validation_plan patient_data.export: scientific_memory_import - hazard_triggers: - - condition: phi_exposure_risk - decision: blocked - - condition: unapproved_intervention - decision: blocked - - condition: off_label_use_proposed - decision: authorization_required - - condition: adverse_event_signal - decision: authorization_required - +- condition: phi_exposure_risk + decision: blocked +- condition: unapproved_intervention + decision: blocked +- condition: off_label_use_proposed + decision: authorization_required +- condition: adverse_event_signal + decision: authorization_required blocked_actions: - - A9_execution_adjacent_or_external_action - +- A9_execution_adjacent_or_external_action metadata_requirements: - - irb_protocol_id - - consent_status - - patient_cohort_id - - regulatory_jurisdiction - +- irb_protocol_id +- consent_status +- patient_cohort_id +- regulatory_jurisdiction tool_restrictions: ehr.draft_note: decision: draft_only From 1d7846ba865e9c9396cb0e251b826db6ff80ea7e Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:05 -0700 Subject: [PATCH 41/87] feat(overlay): update computational_science_v0 overlay Computational science overlay tracks registry hash expectations. --- overlays/computational_science_v0.yaml | 33 +++++++++++++------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/overlays/computational_science_v0.yaml b/overlays/computational_science_v0.yaml index 3a7da7f..d83bc28 100644 --- a/overlays/computational_science_v0.yaml +++ b/overlays/computational_science_v0.yaml @@ -1,7 +1,12 @@ domain: computational_science version: computational_science_v0.2 +tier: core_reference +maintainer: AKTA Contributors +review_status: reference_implementation +review_date: '2026-06-28' +non_certification_statement: Reference overlay for computational science; not a certification + of deployment readiness. operational: true - minimum_evidence_for: recommendation: E3_noisy_or_conflicting_evidence experimental_planning: E4_internally_consistent_evidence @@ -10,33 +15,27 @@ minimum_evidence_for: tool_or_workflow_mutation: E4_internally_consistent_evidence execution_adjacent: E5_internally_replicated_evidence publication_claim: E4_internally_consistent_evidence - required_review_roles: protocol_modification: - - compute_lead + - compute_lead queue_prioritization: - - project_lead + - project_lead tool_or_workflow_mutation: - - compute_lead + - compute_lead execution_adjacent: - - project_lead + - project_lead publication_claim: - - compute_lead - + - compute_lead requested_scope_overrides: workflow.update_state: execution_payload_preparation - hazard_triggers: - - condition: production_cluster_mutation - decision: authorization_required - +- condition: production_cluster_mutation + decision: authorization_required blocked_actions: [] - metadata_requirements: - - code_version - - environment_hash - - reproducibility_seed - +- code_version +- environment_hash +- reproducibility_seed tool_restrictions: workflow.update_state: decision: review_required From 79629778a423c27674879e961f8c6c6a96452a10 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:05 -0700 Subject: [PATCH 42/87] feat(overlay): update generic_lab_v0 overlay for v0.5 Generic lab overlay remains default domain template under new tiers. --- overlays/generic_lab_v0.yaml | 37 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/overlays/generic_lab_v0.yaml b/overlays/generic_lab_v0.yaml index 3009899..55871a5 100644 --- a/overlays/generic_lab_v0.yaml +++ b/overlays/generic_lab_v0.yaml @@ -1,7 +1,12 @@ domain: generic_lab version: generic_lab_v0.2 +tier: core_reference +maintainer: AKTA Contributors +review_status: reference_implementation +review_date: '2026-06-28' +non_certification_statement: Reference overlay for generic lab workflows; not a certification + of deployment readiness. operational: true - minimum_evidence_for: recommendation: E4_internally_consistent_evidence experimental_planning: E4_internally_consistent_evidence @@ -10,32 +15,26 @@ minimum_evidence_for: tool_or_workflow_mutation: E5_internally_replicated_evidence execution_adjacent: E5_internally_replicated_evidence publication_claim: E4_internally_consistent_evidence - required_review_roles: protocol_modification: - - protocol_owner + - protocol_owner queue_prioritization: - - domain_scientist + - domain_scientist tool_or_workflow_mutation: - - workflow_owner + - workflow_owner execution_adjacent: - - lab_safety_officer + - lab_safety_officer publication_claim: - - domain_scientist - + - domain_scientist requested_scope_overrides: {} - hazard_triggers: - - condition: pyrophoric_reagent_present - decision: authorization_required - - condition: uncapped_pressure_vessel - decision: blocked - +- condition: pyrophoric_reagent_present + decision: authorization_required +- condition: uncapped_pressure_vessel + decision: blocked blocked_actions: [] - metadata_requirements: - - protocol_version - - evidence_state - - validation_status - +- protocol_version +- evidence_state +- validation_status tool_restrictions: {} From e238a7273cb00e09564445151f8d33a7b6f3a100 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:05 -0700 Subject: [PATCH 43/87] feat(overlay): update materials_v0 overlay for tiered governance Materials overlay syncs with admissibility matrix changes. --- overlays/materials_v0.yaml | 51 +++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/overlays/materials_v0.yaml b/overlays/materials_v0.yaml index cb112fb..2069b4b 100644 --- a/overlays/materials_v0.yaml +++ b/overlays/materials_v0.yaml @@ -1,7 +1,12 @@ domain: materials version: materials_v0.2 +tier: core_reference +maintainer: AKTA Contributors +review_status: reference_implementation +review_date: '2026-06-28' +non_certification_statement: Reference overlay for materials science; not a certification + of deployment readiness. operational: true - minimum_evidence_for: recommendation: E4_internally_consistent_evidence experimental_planning: E4_internally_consistent_evidence @@ -10,42 +15,36 @@ minimum_evidence_for: tool_or_workflow_mutation: E5_internally_replicated_evidence execution_adjacent: E6_independently_reproduced_evidence publication_claim: E5_internally_replicated_evidence - required_review_roles: protocol_modification: - - protocol_owner + - protocol_owner queue_prioritization: - - principal_investigator - - lab_operations_lead + - principal_investigator + - lab_operations_lead tool_or_workflow_mutation: - - workflow_owner + - workflow_owner execution_adjacent: - - lab_safety_officer - - principal_investigator + - lab_safety_officer + - principal_investigator publication_claim: - - domain_scientist - - communications_lead - + - domain_scientist + - communications_lead requested_scope_overrides: robot_queue.submit: robot_queue_submission - hazard_triggers: - - condition: pyrophoric_reagent_present - decision: authorization_required - - condition: reactive_metal_batch - decision: authorization_required - - condition: uncapped_pressure_vessel - decision: blocked - +- condition: pyrophoric_reagent_present + decision: authorization_required +- condition: reactive_metal_batch + decision: authorization_required +- condition: uncapped_pressure_vessel + decision: blocked blocked_actions: - - A9_execution_adjacent_or_external_action - +- A9_execution_adjacent_or_external_action metadata_requirements: - - instrument_calibration_status - - sample_batch_id - - protocol_version - - uncertainty_estimate - +- instrument_calibration_status +- sample_batch_id +- protocol_version +- uncertainty_estimate tool_restrictions: robot_queue.submit: decision: authorization_required From 790f340b3162007f395a851a86b61cdf01bc8b0c Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:05 -0700 Subject: [PATCH 44/87] test(overlays): add v0.5 overlay governance tier tests Tests block invalid tier combinations at load and apply time. --- tests/test_overlay_governance_v05.py | 89 ++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 tests/test_overlay_governance_v05.py diff --git a/tests/test_overlay_governance_v05.py b/tests/test_overlay_governance_v05.py new file mode 100644 index 0000000..e0b3d13 --- /dev/null +++ b/tests/test_overlay_governance_v05.py @@ -0,0 +1,89 @@ +"""Overlay governance tests for production mode (v0.5).""" + +from __future__ import annotations + +import hashlib +import hmac +import shutil +from pathlib import Path + +import pytest +import yaml + +from akta import AKTAGate, AKTAContext +from akta.errors import PolicyError +from akta.overlays import DomainOverlay + +ROOT = Path(__file__).resolve().parent.parent + + +def _policy_with_deploy_key(tmp_path: Path, deploy_key: bytes) -> Path: + policy_dir = tmp_path / "policy" + shutil.copytree(ROOT / "policy", policy_dir) + manifest_path = policy_dir / "policy_manifest.yaml" + manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) + payload = "|".join(f"{k}:{v}" for k, v in sorted(manifest["files"].items())) + manifest["signature"]["value"] = hmac.new(deploy_key, payload.encode("utf-8"), hashlib.sha256).hexdigest() + manifest_path.write_text(yaml.dump(manifest, sort_keys=False), encoding="utf-8") + return policy_dir + + +def test_core_reference_overlay_tier() -> None: + overlay = DomainOverlay.load("generic_lab_v0", ROOT / "overlays") + assert overlay.tier == "core_reference" + + +def test_experimental_overlay_refused_in_production(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AKTA_PRODUCTION_MODE", "1") + with pytest.raises(PolicyError, match="Production mode refuses overlay"): + DomainOverlay.load("biology_v0", ROOT / "overlays") + + +def test_experimental_overlay_ok_in_dev(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("AKTA_PRODUCTION_MODE", raising=False) + monkeypatch.delenv("AKTA_VERIFY_POLICY", raising=False) + overlay = DomainOverlay.load("biology_v0", ROOT / "overlays") + assert overlay.tier == "experimental_domain_overlay" + + +def test_gate_refuses_experimental_overlay_in_production( + tmp_path: Path, + monkeypatch: pytest.MonkeyPatch, +) -> None: + deploy_key = b"production-overlay-gate-test-v0.5" + policy_dir = _policy_with_deploy_key(tmp_path, deploy_key) + monkeypatch.setenv("AKTA_PRODUCTION_MODE", "1") + monkeypatch.setenv("AKTA_POLICY_HMAC_KEY", deploy_key.decode("utf-8")) + gate = AKTAGate.from_policy_dir(policy_dir, overlays_dir=ROOT / "overlays") + with pytest.raises(PolicyError, match="Production mode refuses overlay"): + gate.evaluate( + ai_output={"summary": "Schedule passage"}, + requested_tool="cell_culture.schedule", + requested_action="schedule_passage", + context=AKTAContext.from_dict({"evidence_state": "E4_internally_consistent_evidence"}), + deployment_profile="P5_review_gated_experimental_planner", + domain_overlay="biology_v0", + ) + + +def test_overlay_governance_fields_present() -> None: + overlays = ( + "generic_lab_v0", + "materials_v0", + "computational_science_v0", + "biology_v0", + "chemistry_v0", + "clinical_v0", + ) + required = ("tier", "maintainer", "review_status", "review_date", "non_certification_statement") + for name in overlays: + overlay = DomainOverlay.load(name, ROOT / "overlays") + for field in required: + assert overlay.data.get(field), f"{name} missing {field}" + + +def test_experimental_overlays_not_operational() -> None: + for name in ("biology_v0", "chemistry_v0", "clinical_v0"): + overlay = DomainOverlay.load(name, ROOT / "overlays") + assert overlay.tier == "experimental_domain_overlay" + assert not overlay.operational From d1cd8ed1c44614bc5bc153b1761a38ab10b2fc8f Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:06 -0700 Subject: [PATCH 45/87] feat(classifier): harden classify path for LLM trust boundary Classifier output is constrained before gate admissibility evaluation. --- akta/classify.py | 52 ++++++++++++++++++++++++++++++------------------ 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/akta/classify.py b/akta/classify.py index d356528..e62f16d 100644 --- a/akta/classify.py +++ b/akta/classify.py @@ -68,7 +68,8 @@ class ClassificationResult: matched_source: Provenance of the primary classification (tool_registry, requested_action, plugin, etc.). matched_evidence: Short pointer to the signal used (tool name, action text, etc.). uncertainty_flags: Taxonomy flags such as ``nl_tool_mismatch`` or ``model_assisted_fallback``. - classifier_mode: ``deterministic`` or ``model_assisted`` when a plugin contributed. + classifier_mode: ``deterministic`` or ``llm_advisory`` when a plugin contributed. + llm_metadata: Model, prompt_hash, schema, confidence when LLM advisory path used. """ action_type: str @@ -80,6 +81,7 @@ class ClassificationResult: matched_evidence: str = "" uncertainty_flags: list[str] = field(default_factory=list) classifier_mode: str = "deterministic" + llm_metadata: dict[str, Any] | None = None @property def primary_action_type(self) -> str: @@ -228,6 +230,17 @@ def classify( rationale_parts.append(f"{structured_source} specifies {structured_action}") confidence = 0.99 + plugin_result = None + if not structured_action: + plugin_result = run_plugin_classification( + policy, + requested_tool, + requested_action, + tool_spec, + context, + ai_output=ai_output, + ) + if tool_spec.known and not structured_action: action_type = tool_spec.action_type matched_source = "tool_registry" @@ -242,6 +255,12 @@ def classify( rationale_parts.append( f"NL text suggests {nl_action} but tool registry overrides to {action_type}" ) + if plugin_result is not None and plugin_result.action_type != action_type: + alternates.append(plugin_result.action_type) + uncertainty_flags.append("llm_overridden_by_tool_registry") + rationale_parts.append( + f"LLM suggested {plugin_result.action_type} but tool registry overrides" + ) else: if not action_type: nl_action, nl_alts = classify_from_action_text(requested_action) @@ -267,24 +286,18 @@ def classify( rationale_parts.append("inferred from ai_output text") confidence = 0.75 - if not action_type: - plugin_result = run_plugin_classification( - policy, - requested_tool, - requested_action, - tool_spec, - context, - ai_output=ai_output, - ) - if plugin_result is not None: - classifier_mode = plugin_result.source if plugin_result.source == "llm_classifier" else "plugin_assisted" - action_type = plugin_result.action_type - confidence = plugin_result.confidence - alternates.extend(plugin_result.alternates) - uncertainty_flags.extend(plugin_result.uncertainty_flags) - matched_source = plugin_result.source - matched_evidence = f"plugin={plugin_result.source}" - rationale_parts.append(plugin_result.rationale) + if not action_type and plugin_result is not None: + classifier_mode = "llm_advisory" if plugin_result.source == "llm_classifier" else "plugin_assisted" + action_type = plugin_result.action_type + confidence = plugin_result.confidence + alternates.extend(plugin_result.alternates) + uncertainty_flags.extend(plugin_result.uncertainty_flags) + matched_source = plugin_result.source + matched_evidence = f"plugin={plugin_result.source}" + rationale_parts.append(plugin_result.rationale) + llm_metadata = plugin_result.llm_metadata + else: + llm_metadata = None if not action_type: action_type = "A8_tool_or_workflow_mutation" @@ -332,6 +345,7 @@ def classify( matched_evidence=matched_evidence, uncertainty_flags=list(dict.fromkeys(uncertainty_flags)), classifier_mode=classifier_mode, + llm_metadata=llm_metadata if classifier_mode == "llm_advisory" else None, ) From aacc3172e87c7697b55714e8bc8a8dacbcfc4d88 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:06 -0700 Subject: [PATCH 46/87] feat(classifier): extend plugins for trust-boundary metadata Plugins surface provenance fields required by v0.5 audit trails. --- akta/classifier_plugins.py | 35 +++++++++++++++++++++++++++++------ 1 file changed, 29 insertions(+), 6 deletions(-) diff --git a/akta/classifier_plugins.py b/akta/classifier_plugins.py index 55d31b9..39d5af3 100644 --- a/akta/classifier_plugins.py +++ b/akta/classifier_plugins.py @@ -1,11 +1,13 @@ """Optional classifier plugins for AKTA (disabled by default). Deterministic classification is the default path. Plugins extend classification when -deterministic rules cannot resolve an action type. +deterministic rules cannot resolve an action type. LLM output is advisory only and +never overrides known tool-registry mappings. """ from __future__ import annotations +import hashlib import json import os import urllib.error @@ -29,6 +31,7 @@ class PluginClassification: alternates: list[str] = field(default_factory=list) source: str = "plugin" uncertainty_flags: list[str] = field(default_factory=list) + llm_metadata: dict[str, Any] | None = None class ClassifierPlugin(ABC): @@ -116,12 +119,20 @@ def classify( "additionalProperties": False, } +LLM_LOW_CONFIDENCE_THRESHOLD = 0.7 + + +def _prompt_hash(system_prompt: str, user_text: str) -> str: + payload = json.dumps({"system": system_prompt, "user": user_text[:4000]}, sort_keys=True) + return hashlib.sha256(payload.encode("utf-8")).hexdigest() + class OptionalLLMClassifierPlugin(ClassifierPlugin): """Optional OpenAI-compatible LLM classifier with strict JSON schema output. Enabled only when ``AKTA_LLM_CLASSIFIER`` is set AND ``OPENAI_API_KEY`` is present. Fails closed (returns None) without API key even if env flag is set. + Advisory only — known tool-registry mappings override LLM output in ``classify()``. """ ENV_VAR = "AKTA_LLM_CLASSIFIER" @@ -148,6 +159,8 @@ def classify( ) -> PluginClassification | None: if not self.is_enabled(): return None + if tool_spec.known: + return None action_types = list( policy.action_ontology.get("action_types", {}).keys() @@ -160,9 +173,11 @@ def classify( "rationale, optional alternate_action_types." ) text = ai_output if isinstance(ai_output, str) else str(ai_output or requested_action) + model = os.environ.get(self.MODEL_ENV, self.DEFAULT_MODEL) + phash = _prompt_hash(prompt, text) try: - result = self._call_openai(prompt, text) + result = self._call_openai(prompt, text, model) except (urllib.error.URLError, TimeoutError, json.JSONDecodeError, KeyError, ValueError): return None @@ -170,18 +185,26 @@ def classify( if action_type not in action_types: return None + confidence = float(result.get("confidence", 0.65)) + llm_meta = { + "model": model, + "prompt_hash": phash, + "schema": "akta_classification_v0.5", + "confidence": confidence, + } + return PluginClassification( action_type=action_type, - confidence=float(result.get("confidence", 0.65)), + confidence=confidence, rationale=str(result.get("rationale", "LLM classification")), alternates=list(result.get("alternate_action_types") or []), source="llm_classifier", - uncertainty_flags=["llm_assisted"], + uncertainty_flags=["llm_advisory"], + llm_metadata=llm_meta, ) - def _call_openai(self, system_prompt: str, user_text: str) -> dict[str, Any]: + def _call_openai(self, system_prompt: str, user_text: str, model: str) -> dict[str, Any]: api_key = os.environ[self.API_KEY_ENV] - model = os.environ.get(self.MODEL_ENV, self.DEFAULT_MODEL) payload = { "model": model, "messages": [ From 32422d62155a95ec5903ea7cafa261c037bf5510 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:06 -0700 Subject: [PATCH 47/87] feat(gate): propagate trust-boundary signals in gate decisions Gate records classifier trust context in decision artifacts. --- akta/gate.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/akta/gate.py b/akta/gate.py index d3a0763..180c969 100644 --- a/akta/gate.py +++ b/akta/gate.py @@ -169,6 +169,7 @@ def evaluate( "consequentiality_reason": evaluation.consequentiality_reason, "policy_version": self.policy.version, "policy_hash": self.policy.policy_hash, + "policy_file_versions": dict(self.policy.policy_file_versions), "domain_overlay_version": overlay_obj.version if overlay_obj else None, "domain_overlay_hash": overlay_obj.overlay_hash if overlay_obj else None, "tool_registry_hash": self.policy.tool_registry_hash, @@ -182,6 +183,7 @@ def evaluate( "uncertainty_flags": classification.uncertainty_flags, "classifier_mode": classification.classifier_mode, }, + "llm_advisory": classification.llm_metadata, "ai_output_summary": ( ai_output if isinstance(ai_output, str) else (ai_output.get("summary") if isinstance(ai_output, dict) else str(ai_output)) From 716ef7039886145371849711bdeb539494184655 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:06 -0700 Subject: [PATCH 48/87] docs(classifier): document LLM trust boundary for v0.5 Security reviewers need explicit boundary rules for model-assisted classification. --- docs/classifier_trust_boundary.md | 45 +++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 docs/classifier_trust_boundary.md diff --git a/docs/classifier_trust_boundary.md b/docs/classifier_trust_boundary.md new file mode 100644 index 0000000..b36b1bf --- /dev/null +++ b/docs/classifier_trust_boundary.md @@ -0,0 +1,45 @@ +# Classifier Trust Boundary (v0.5) + +AKTA treats deterministic classification as authoritative. Optional LLM classifiers operate in an **advisory-only** trust zone and never override known tool-registry mappings. + +## Trust zones + +| Zone | Source | Authority | +|------|--------|-----------| +| **Deterministic** | Tool registry, structured action, requested-action keywords | Authoritative for admissibility | +| **LLM advisory** | `OptionalLLMClassifierPlugin` when enabled | Proposes action type only when registry cannot resolve | +| **Conservative fallback** | `ConservativeFallbackClassifierPlugin` | Default when all else fails | + +## Tool registry override + +When `tool_spec.known` is true, the tool registry mapping **always** wins over LLM output. The classifier records `llm_overridden_by_tool_registry` in uncertainty flags when an LLM plugin would have suggested a different type. + +## LLM advisory metadata + +When `classifier_mode` is `llm_advisory`, the AKTA Decision includes: + +- `classification.classifier_mode`: `llm_advisory` +- `llm_advisory.model`: model identifier +- `llm_advisory.prompt_hash`: SHA-256 of system prompt + user text +- `llm_advisory.schema`: `akta_classification_v0.5` +- `llm_advisory.confidence`: model-reported confidence + +## Fail-closed for mutating and external tools + +If LLM advisory confidence is below 0.7 and the requested tool is mutating, external-effect, or unknown, admissibility resolves to `abstain_insufficient_context`. + +## Enabling LLM classifier + +```bash +export AKTA_LLM_CLASSIFIER=1 +export OPENAI_API_KEY=sk-... +# optional: export AKTA_LLM_MODEL=gpt-4o-mini +``` + +Without `OPENAI_API_KEY`, the LLM plugin is disabled (fail-closed). + +## Production guidance + +- Do not treat LLM classification as policy authority. +- Pin policy bundle version and verify integrity (`AKTA_PRODUCTION_MODE=1`). +- Audit `llm_advisory` fields in AKTA Records when LLM path is enabled. From b5d6aa517d7f302070197afc7c37715d073316f2 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:07 -0700 Subject: [PATCH 49/87] test(classifier): add trust-boundary regression tests Tests ensure untrusted classifier paths cannot bypass review. --- tests/test_classifier_trust_boundary.py | 141 ++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 tests/test_classifier_trust_boundary.py diff --git a/tests/test_classifier_trust_boundary.py b/tests/test_classifier_trust_boundary.py new file mode 100644 index 0000000..c1ce818 --- /dev/null +++ b/tests/test_classifier_trust_boundary.py @@ -0,0 +1,141 @@ +"""LLM classifier trust boundary tests (v0.5).""" + +from __future__ import annotations + +import json +from pathlib import Path +from unittest.mock import MagicMock, patch + +import pytest + +from akta import AKTAGate, AKTAContext +from akta.classifier_plugins import PluginClassification +from akta.classify import classify +from akta.policy import PolicyBundle +from akta.tool_registry import ToolRegistry + +ROOT = Path(__file__).resolve().parent.parent + + +def test_tool_registry_overrides_llm_output() -> None: + from unittest.mock import patch + + from akta.classify import run_plugin_classification + + policy = PolicyBundle.from_dir(ROOT / "policy") + registry = ToolRegistry(policy.tool_registry) + tool_spec = registry.resolve("lab_scheduler.prioritize") + llm_proposal = PluginClassification( + action_type="A4_recommendation", + confidence=0.95, + rationale="LLM would suggest A4", + source="llm_classifier", + uncertainty_flags=["llm_advisory"], + llm_metadata={ + "model": "test", + "prompt_hash": "abc", + "schema": "akta_classification_v0.5", + "confidence": 0.95, + }, + ) + with patch("akta.classify.run_plugin_classification", return_value=llm_proposal): + result = classify( + policy, + "lab_scheduler.prioritize", + "prioritize samples", + tool_spec, + AKTAContext(), + ai_output="recommend next experiment", + ) + assert result.action_type == "A7_resource_or_queue_prioritization" + assert result.classifier_mode == "deterministic" + assert "llm_overridden_by_tool_registry" in result.uncertainty_flags + + +def test_llm_advisory_metadata_in_decision(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AKTA_LLM_CLASSIFIER", "1") + monkeypatch.setenv("OPENAI_API_KEY", "sk-test") + policy = PolicyBundle.from_dir(ROOT / "policy") + registry = ToolRegistry(policy.tool_registry) + tool_spec = registry.resolve("unregistered.custom_tool") + + mock_body = { + "choices": [{ + "message": { + "content": json.dumps({ + "action_type": "A4_recommendation", + "confidence": 0.88, + "rationale": "Mocked LLM", + }), + }, + }], + } + mock_resp = MagicMock() + mock_resp.read.return_value = json.dumps(mock_body).encode("utf-8") + mock_resp.__enter__ = lambda s: s + mock_resp.__exit__ = MagicMock(return_value=False) + + with patch("urllib.request.urlopen", return_value=mock_resp): + result = classify( + policy, + "unregistered.custom_tool", + "ambiguous", + tool_spec, + AKTAContext(), + ai_output="unclear", + ) + + assert result.classifier_mode == "llm_advisory" + assert result.llm_metadata is not None + assert result.llm_metadata["model"] + assert result.llm_metadata["prompt_hash"] + assert result.llm_metadata["schema"] == "akta_classification_v0.5" + + +def test_low_confidence_llm_fail_closed_for_mutating(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.setenv("AKTA_LLM_CLASSIFIER", "1") + monkeypatch.setenv("OPENAI_API_KEY", "sk-test") + + mock_body = { + "choices": [{ + "message": { + "content": json.dumps({ + "action_type": "A8_tool_or_workflow_mutation", + "confidence": 0.55, + "rationale": "Low confidence mutating", + }), + }, + }], + } + mock_resp = MagicMock() + mock_resp.read.return_value = json.dumps(mock_body).encode("utf-8") + mock_resp.__enter__ = lambda s: s + mock_resp.__exit__ = MagicMock(return_value=False) + + gate = AKTAGate.from_policy_dir(ROOT / "policy", overlays_dir=ROOT / "overlays") + with patch("urllib.request.urlopen", return_value=mock_resp): + decision = gate.evaluate( + ai_output="unclear mutating request", + requested_tool="unregistered.custom_mutator", + requested_action="mutate_workflow", + context=AKTAContext.from_dict({"evidence_state": "E4_internally_consistent_evidence"}), + deployment_profile="P5_review_gated_experimental_planner", + ) + assert decision.admissibility == "abstain_insufficient_context" + d = decision.to_dict() + assert d["classification"]["classifier_mode"] == "llm_advisory" + assert d.get("llm_advisory") is not None + + +def test_core_tests_pass_with_llm_disabled(monkeypatch: pytest.MonkeyPatch) -> None: + monkeypatch.delenv("AKTA_LLM_CLASSIFIER", raising=False) + monkeypatch.delenv("OPENAI_API_KEY", raising=False) + gate = AKTAGate.from_policy_dir(ROOT / "policy", overlays_dir=ROOT / "overlays") + decision = gate.evaluate( + ai_output={"summary": "Search literature."}, + requested_tool="literature_search.query", + requested_action="search", + context=AKTAContext(), + deployment_profile="P1_literature_hypothesis_assistant", + ) + assert decision.to_dict()["classification"]["classifier_mode"] == "deterministic" From 9dec84b0d1efca854a8236402ffee057277cd6e5 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:07 -0700 Subject: [PATCH 50/87] docs(api): bump OpenAPI metadata for v0.5 REST surface Published schema version must match runtime package and policy. --- adapters/generic_rest/openapi.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adapters/generic_rest/openapi.yaml b/adapters/generic_rest/openapi.yaml index 9faea4e..bba3aaa 100644 --- a/adapters/generic_rest/openapi.yaml +++ b/adapters/generic_rest/openapi.yaml @@ -1,7 +1,7 @@ openapi: 3.0.3 info: title: AKTA REST API - version: 0.4.0 + version: 0.5.0 description: Scientific action admissibility gate REST interface paths: /v0/health: From f37731e10bafdb15f93d0dec6a48fde394c8680f Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:07 -0700 Subject: [PATCH 51/87] feat(rest): align generic REST adapter with v0.5 responses REST adapter exposes fields added to decision and record artifacts. --- adapters/generic_rest/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/adapters/generic_rest/server.py b/adapters/generic_rest/server.py index b8d1270..2592dd9 100644 --- a/adapters/generic_rest/server.py +++ b/adapters/generic_rest/server.py @@ -69,11 +69,11 @@ def do_GET(self) -> None: self._send_json(HTTPStatus.OK, { "status": "ok", "version": pkg_version("akta-protocol"), - "api_version": "v0.4", + "api_version": "v0.5", }) elif path == "/v0/openapi": spec_path = Path(__file__).resolve().parent / "openapi.yaml" - self._send_json(HTTPStatus.OK, {"openapi_path": str(spec_path), "api_version": "v0.4"}) + self._send_json(HTTPStatus.OK, {"openapi_path": str(spec_path), "api_version": "v0.5"}) elif path == "/v0/policy": gate = self._gate() self._send_json( From 496ca47c391bc536947257c880abd7139136f154 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:07 -0700 Subject: [PATCH 52/87] test(rest): extend REST API tests for v0.5 fields API tests guard response shape expected by external integrators. --- tests/test_rest_api.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_rest_api.py b/tests/test_rest_api.py index ebd215f..d943e61 100644 --- a/tests/test_rest_api.py +++ b/tests/test_rest_api.py @@ -48,6 +48,8 @@ def _get(url: str, path: str) -> dict: def test_health(server_url: str) -> None: data = _get(server_url, "/v0/health") assert data["status"] == "ok" + assert data["api_version"] == "v0.5" + assert data["version"] == "0.5.0" def test_policy(server_url: str) -> None: From efd85d280ddc6c23f5bbe43b5caceaa1a883149e Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:07 -0700 Subject: [PATCH 53/87] test(adapters): update adapter tests for v0.5 scope and PCS Legacy v0.4 adapter tests evolve without dropping coverage. --- tests/test_adapters_v04.py | 43 +++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/tests/test_adapters_v04.py b/tests/test_adapters_v04.py index 5b82d61..2d75121 100644 --- a/tests/test_adapters_v04.py +++ b/tests/test_adapters_v04.py @@ -88,25 +88,18 @@ def test_pcs_bench_export(tmp_path: Path) -> None: assert len(lines) == 5 first = json.loads(lines[0]) assert "inputs" in first - assert first["suite_id"] == "akta-pcs-bench-v0.4" + assert first["suite_id"] == "akta-pcs-bench-v0.5" -def test_scope_adapter_subprocess_mode_mock(monkeypatch: pytest.MonkeyPatch) -> None: +def test_scope_adapter_cli_mode_mock(monkeypatch: pytest.MonkeyPatch) -> None: trigger = { "review_trigger_id": "AKTA-REVTRIG-SUBPROC01", "requested_scope": "active_protocol_update", "required_review_role": "protocol_owner", } scope_output = json.dumps({ - "review_packet": {"trigger": trigger}, - "grant": { - "grant_id": "SCOPE-GRANT-SUBPROC01", - "granted_scope": "protocol_draft", - "requested_scope": "active_protocol_update", - "reviewer_id": "scope_reviewer", - "review_trigger_id": "AKTA-REVTRIG-SUBPROC01", - }, - "decision": {"status": "granted", "granted_scope": "protocol_draft"}, + "packet_type": "scope_review_packet", + "trigger": trigger, }) class FakeProc: @@ -114,19 +107,39 @@ class FakeProc: stdout = scope_output stderr = "" + def fake_run(cmd, **kwargs): + proc = FakeProc() + if cmd[1:3] == ["packet", "create"]: + out = Path(cmd[cmd.index("--out") + 1]) + out.write_text(scope_output, encoding="utf-8") + elif cmd[1:3] == ["decision", "submit"]: + granted = cmd[cmd.index("--grant-scope") + 1] + out = Path(cmd[cmd.index("--out") + 1]) + out.write_text(json.dumps({"status": "granted", "granted_scope": granted}), encoding="utf-8") + elif cmd[1:3] == ["grant", "issue"]: + out = Path(cmd[cmd.index("--out") + 1]) + out.write_text(json.dumps({ + "grant_id": "SCOPE-GRANT-SUBPROC01", + "granted_scope": "protocol_draft", + "requested_scope": "active_protocol_update", + "reviewer_id": "scope_reviewer", + "review_trigger_id": "AKTA-REVTRIG-SUBPROC01", + }), encoding="utf-8") + return proc + monkeypatch.setenv("SCOPE_CLI", "scope-mock") monkeypatch.delenv("SCOPE_REPO_PATH", raising=False) - with patch("adapters.scope.client.subprocess.run", return_value=FakeProc()): + with patch("adapters.scope.client.subprocess.run", side_effect=fake_run): result = submit_review_trigger(trigger, grant_scope="protocol_draft") - assert result.adapter_mode == "subprocess" + assert result.adapter_mode == "cli" assert result.error is None assert result.grant is not None assert result.grant["granted_scope"] == "protocol_draft" -def test_scope_adapter_subprocess_cli_missing(monkeypatch: pytest.MonkeyPatch) -> None: +def test_scope_adapter_cli_missing(monkeypatch: pytest.MonkeyPatch) -> None: trigger = { "review_trigger_id": "AKTA-REVTRIG-MISSING01", "requested_scope": "protocol_draft", @@ -139,5 +152,5 @@ def test_scope_adapter_subprocess_cli_missing(monkeypatch: pytest.MonkeyPatch) - ): result = submit_review_trigger(trigger) - assert result.adapter_mode == "subprocess" + assert result.adapter_mode == "cli" assert result.error is not None From 720defe710cb6436cabac610c9da3e002dff3397 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:08 -0700 Subject: [PATCH 54/87] feat(demo): refresh protocol drift demo for v0.5 PCS chain Demo regenerates artifacts using full-chain export and SCOPE modes. --- scripts/demo_akta_scope_protocol_drift.py | 37 ++++++++++++++++------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/scripts/demo_akta_scope_protocol_drift.py b/scripts/demo_akta_scope_protocol_drift.py index 198cf45..7de020b 100644 --- a/scripts/demo_akta_scope_protocol_drift.py +++ b/scripts/demo_akta_scope_protocol_drift.py @@ -1,9 +1,9 @@ -"""Integrated AKTA x SCOPE protocol-drift demo (v0.3). +"""Integrated AKTA x SCOPE protocol-drift demo (v0.5). Flow: active protocol update -> AKTA review_required (requested_scope=active_protocol_update) - -> SCOPE packet -> protocol owner grants protocol_draft only - -> draft_change allowed; active update + robot blocked -> PF + PCS export + -> SCOPE adapter (simulated | python-import | cli) -> packet, decision, grant + -> draft_change allowed; active update + robot blocked -> PF + PCS full-chain export """ from __future__ import annotations @@ -78,20 +78,26 @@ def run_demo() -> int: from akta.records import AKTARecord, AKTADecision from adapters.pcs.export_artifact import export_pcs_bundle from adapters.pf_core.export_obligation import export_pf_obligation - from adapters.scope.client import submit_review_trigger + from adapters.scope.client import detect_adapter_mode, submit_review_trigger demo_dir = ROOT / "examples" / "integrated_protocol_drift" demo_dir.mkdir(parents=True, exist_ok=True) pcs_dir = demo_dir / "pcs_bundle" - print("=== AKTA v0.4 Demo: Integrated Protocol Drift (AKTA x SCOPE) ===\n") + adapter_mode = detect_adapter_mode() + mode_label = { + "simulated": "simulated (contract simulation only)", + "python-import": "python-import (SCOPE_REPO_PATH sibling repo)", + "cli": "cli (SCOPE_CLI subprocess)", + }.get(adapter_mode, adapter_mode) + print(f"=== AKTA v0.5 Demo: Integrated Protocol Drift (AKTA x SCOPE) ===") + print(f"SCOPE adapter mode: {mode_label}\n") context_data = json.loads((demo_dir / "context.json").read_text(encoding="utf-8")) ai_output = json.loads((demo_dir / "ai_output.json").read_text(encoding="utf-8")) gate = AKTAGate.from_policy_dir(ROOT / "policy", overlays_dir=ROOT / "overlays") - # Step 1: active protocol update -> review_required + active_protocol_update scope active_decision = gate.evaluate( ai_output=ai_output, requested_tool="protocol_editor.update_active_protocol", @@ -119,25 +125,30 @@ def run_demo() -> int: scope_result = submit_review_trigger( active_d["review_trigger"], + record=active_record, grant_scope="protocol_draft", reviewer_id="protocol_owner", ) + if scope_result.error: + print(f"SCOPE adapter error (invalid grant blocks PCS export): {scope_result.error}") + return 1 + scope_packet = scope_result.review_packet or {} scope_grant = scope_result.grant or {} + scope_decision = scope_result.decision or {} (demo_dir / "scope_review_packet.json").write_text( json.dumps(scope_packet, indent=2), encoding="utf-8" ) (demo_dir / "scope_narrow_grant.json").write_text( json.dumps(scope_grant, indent=2), encoding="utf-8" ) - print(f"SCOPE adapter mode: {scope_result.adapter_mode}") + (demo_dir / "scope_decision.json").write_text( + json.dumps(scope_decision, indent=2), encoding="utf-8" + ) (demo_dir / "review_trigger.json").write_text( json.dumps(active_d["review_trigger"], indent=2), encoding="utf-8" ) - # Step 2: narrow grant artifacts written by SCOPE adapter (simulated or subprocess) - - # Step 3: draft-only follow-up remains admissible; active + robot still blocked draft_decision = gate.evaluate( ai_output={"summary": "Draft protocol threshold change for owner review."}, requested_tool="protocol_editor.draft_change", @@ -165,7 +176,6 @@ def run_demo() -> int: ) assert robot_decision.admissibility in ("blocked", "authorization_required") - # Step 4: PF + PCS export for active-update record pf_path = export_pf_obligation( AKTARecord(active_record), demo_dir, validate=True, decision_id=active_d["decision_id"] ) @@ -179,6 +189,9 @@ def run_demo() -> int: pcs_dir, decision=active_d, scope_review_packet=scope_packet, + scope_decision=scope_decision, + scope_grant=scope_grant, + pf_obligation=pf_obligation, validate=True, ) @@ -186,8 +199,10 @@ def run_demo() -> int: print(f"Draft follow-up: {draft_d['admissibility']} (draft_change allowed)") print(f"Robot submit: {robot_decision.admissibility} (still blocked/authorization)") print(f"SCOPE grant: {scope_grant.get('granted_scope', scope_grant)}") + print(f"SCOPE decision: {scope_decision.get('status', scope_decision)}") print(f"PF obligation: {pf_obligation['obligation_type']}") print(f"PCS bundle: {pcs_dir / 'manifest.json'}") + print(f"Adapter mode: {mode_label}") print(f"Artifacts in {demo_dir}") return 0 From ce81d6d652f19f48494ac751e6274b230238c34f Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:27 -0700 Subject: [PATCH 55/87] chore(examples): refresh weak_evidence akta_decision for v0.5 Decision artifact reflects trust-boundary and admissibility fields from v0.5 gate. --- .../akta_decision.json | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/integrated_weak_evidence/akta_decision.json b/examples/integrated_weak_evidence/akta_decision.json index 46f9bd7..a4bcfc0 100644 --- a/examples/integrated_weak_evidence/akta_decision.json +++ b/examples/integrated_weak_evidence/akta_decision.json @@ -32,11 +32,24 @@ "authorization_required": false, "consequentiality": true, "consequentiality_reason": "mutating tool; external effect; action type A7_resource_or_queue_prioritization; resource allocation; next-run or prioritization language", - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "policy_file_versions": { + "action_ontology.yaml": "action_ontology-v0.5", + "responsibility_levels.yaml": "responsibility_levels-v0.5", + "evidence_states.yaml": "evidence_states-v0.5", + "validation_statuses.yaml": "validation_statuses-v0.5", + "verification_statuses.yaml": "verification_statuses-v0.5", + "deployment_profiles.yaml": "deployment_profiles-v0.5", + "admissibility_matrix.yaml": "admissibility_matrix-v0.5", + "evidence_to_action_matrix.yaml": "evidence_to_action_matrix-v0.5", + "evidence_to_action_rules.yaml": "evidence_to_action_rules-v0.5", + "default_tool_registry.yaml": "default_tool_registry-v0.5", + "tool_to_requested_scope.yaml": "tool_to_requested_scope-v0.5" + }, "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps lab_scheduler.prioritize to A7_resource_or_queue_prioritization", "classification": { @@ -47,5 +60,6 @@ "uncertainty_flags": [], "classifier_mode": "deterministic" }, + "llm_advisory": null, "ai_output_summary": "The agent recommended prioritizing condition B based on a preliminary signal." } \ No newline at end of file From d8edd49323ac506b026a7d8be351470809090046 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:27 -0700 Subject: [PATCH 56/87] chore(examples): refresh weak_evidence akta_record for v0.5 Record artifact stays aligned with regenerated weak-evidence demo run. --- examples/integrated_weak_evidence/akta_record.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/integrated_weak_evidence/akta_record.json b/examples/integrated_weak_evidence/akta_record.json index 5a30ebc..43e7447 100644 --- a/examples/integrated_weak_evidence/akta_record.json +++ b/examples/integrated_weak_evidence/akta_record.json @@ -60,11 +60,11 @@ "consequentiality_reason": "mutating tool; external effect; action type A7_resource_or_queue_prioritization; resource allocation; next-run or prioritization language" }, "provenance": { - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a" }, "integrations": { "vsa_report_ref": "VSA-REPORT-INTEGRATED-001", @@ -72,5 +72,5 @@ "pcs_artifact_ref": null, "prior_akta_records": [] }, - "record_hash": "sha256:03e1b5ce977bdd5c16124bb3ee4da60912c15f6d3ed1451b52ab7c847689054d" + "record_hash": "sha256:af72014eeffef8190c9ca548facb7143e37d2555abed52655aabd818c35b65e8" } \ No newline at end of file From 3344969170983aa48851383dc762410b430dc1d5 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:27 -0700 Subject: [PATCH 57/87] chore(examples): update weak_evidence pf_obligation artifact PF obligation encodes review path for weak evidence under v0.5 policy. --- examples/integrated_weak_evidence/pf_obligation.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/integrated_weak_evidence/pf_obligation.json b/examples/integrated_weak_evidence/pf_obligation.json index 0bc8f2b..2bee53b 100644 --- a/examples/integrated_weak_evidence/pf_obligation.json +++ b/examples/integrated_weak_evidence/pf_obligation.json @@ -14,9 +14,9 @@ ], "allowed_tools": [], "max_responsibility_level": "R6_resource_allocation", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", "scope_grant_ref": null, "review_trigger_id": null, "expires_at": null, @@ -42,5 +42,5 @@ ], "required_review_role": null, "consequentiality": true, - "obligation_hash": "sha256:1f1eddd6704343cf22e04aa0ace464e3c807cec65aea16f087a622cb20c88882" + "obligation_hash": "sha256:91c8045f20a687506be850aee36d6c1790d8b2c91f1d0f44ccb65065f41de200" } \ No newline at end of file From 9d6b46bd79f4ac0afc0badd83d3604af5dd81e4d Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:28 -0700 Subject: [PATCH 58/87] chore(examples): regenerate weak_evidence PCS manifest v0.5 Manifest lists full-chain artifact slots and content hashes. --- .../pcs_bundle/manifest.json | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/examples/integrated_weak_evidence/pcs_bundle/manifest.json b/examples/integrated_weak_evidence/pcs_bundle/manifest.json index 2dee135..352abbc 100644 --- a/examples/integrated_weak_evidence/pcs_bundle/manifest.json +++ b/examples/integrated_weak_evidence/pcs_bundle/manifest.json @@ -1,18 +1,25 @@ { "artifact_type": "akta_scientific_action_record", - "schema_version": "akta-record-v0.4", - "record_hash": "sha256:03e1b5ce977bdd5c16124bb3ee4da60912c15f6d3ed1451b52ab7c847689054d", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "schema_version": "akta-record-v0.5", + "record_hash": "sha256:af72014eeffef8190c9ca548facb7143e37d2555abed52655aabd818c35b65e8", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "decision_id": "AKTA-DEC-DEMO0001", "files": [ - "akta_record.json", "akta_decision.json", - "policy_hash.txt", + "akta_record.json", "domain_overlay_hash.txt", - "tool_registry_hash.txt", - "manifest.json" + "manifest.json", + "policy_hash.txt", + "tool_registry_hash.txt" ], - "manifest_hash": "sha256:ee5a0865491a7aad736e4c39270099fef82b0d43420ae88d43b350bd39f7daa9" + "file_hashes": { + "akta_decision.json": "sha256:7cf397c72b9fc4a2e57df8d2bf11e5df211c02f914399f2ffa960aaf6cc9caa2", + "akta_record.json": "sha256:3606fda9ad6f2078d3d35aab06dd001e54f74c03b3534d352abca38fea624789", + "domain_overlay_hash.txt": "sha256:aff97a5c94f9f99549b9f4043617ba8dcfce973312fde13d63101d173bcb0860", + "policy_hash.txt": "sha256:918d683b234533a1c7da8178e581eff636b2786f9b7cc373873ea805994b15ff", + "tool_registry_hash.txt": "sha256:6375791d242c7aad891c23def053d895a7ab6ac579524866cf45fa5a2e7e9863" + }, + "manifest_hash": "sha256:7589b9b3894bdb73900c7a8a2a80c8fd498d8501ec3f7afbb677177bd81be9db" } \ No newline at end of file From ff219733aa39b5ea6a7cf320899b40a9eb9f2ad8 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:28 -0700 Subject: [PATCH 59/87] chore(examples): sync weak_evidence PCS akta_decision bundle Bundled decision matches standalone example for PCS verification. --- .../pcs_bundle/akta_decision.json | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/integrated_weak_evidence/pcs_bundle/akta_decision.json b/examples/integrated_weak_evidence/pcs_bundle/akta_decision.json index 46f9bd7..a4bcfc0 100644 --- a/examples/integrated_weak_evidence/pcs_bundle/akta_decision.json +++ b/examples/integrated_weak_evidence/pcs_bundle/akta_decision.json @@ -32,11 +32,24 @@ "authorization_required": false, "consequentiality": true, "consequentiality_reason": "mutating tool; external effect; action type A7_resource_or_queue_prioritization; resource allocation; next-run or prioritization language", - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "policy_file_versions": { + "action_ontology.yaml": "action_ontology-v0.5", + "responsibility_levels.yaml": "responsibility_levels-v0.5", + "evidence_states.yaml": "evidence_states-v0.5", + "validation_statuses.yaml": "validation_statuses-v0.5", + "verification_statuses.yaml": "verification_statuses-v0.5", + "deployment_profiles.yaml": "deployment_profiles-v0.5", + "admissibility_matrix.yaml": "admissibility_matrix-v0.5", + "evidence_to_action_matrix.yaml": "evidence_to_action_matrix-v0.5", + "evidence_to_action_rules.yaml": "evidence_to_action_rules-v0.5", + "default_tool_registry.yaml": "default_tool_registry-v0.5", + "tool_to_requested_scope.yaml": "tool_to_requested_scope-v0.5" + }, "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps lab_scheduler.prioritize to A7_resource_or_queue_prioritization", "classification": { @@ -47,5 +60,6 @@ "uncertainty_flags": [], "classifier_mode": "deterministic" }, + "llm_advisory": null, "ai_output_summary": "The agent recommended prioritizing condition B based on a preliminary signal." } \ No newline at end of file From dfd1d0b238fbc0e73dba6b116af2081f677e6d8e Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:28 -0700 Subject: [PATCH 60/87] chore(examples): sync weak_evidence PCS akta_record bundle Bundled record completes weak-evidence PCS chain export. --- .../pcs_bundle/akta_record.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/integrated_weak_evidence/pcs_bundle/akta_record.json b/examples/integrated_weak_evidence/pcs_bundle/akta_record.json index 5a30ebc..43e7447 100644 --- a/examples/integrated_weak_evidence/pcs_bundle/akta_record.json +++ b/examples/integrated_weak_evidence/pcs_bundle/akta_record.json @@ -60,11 +60,11 @@ "consequentiality_reason": "mutating tool; external effect; action type A7_resource_or_queue_prioritization; resource allocation; next-run or prioritization language" }, "provenance": { - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a" }, "integrations": { "vsa_report_ref": "VSA-REPORT-INTEGRATED-001", @@ -72,5 +72,5 @@ "pcs_artifact_ref": null, "prior_akta_records": [] }, - "record_hash": "sha256:03e1b5ce977bdd5c16124bb3ee4da60912c15f6d3ed1451b52ab7c847689054d" + "record_hash": "sha256:af72014eeffef8190c9ca548facb7143e37d2555abed52655aabd818c35b65e8" } \ No newline at end of file From 7860d61e21e6624e97672e0c9f2ca3a72a5b7caf Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:29 -0700 Subject: [PATCH 61/87] chore(examples): update weak_evidence PCS policy_hash pin Hash pin tracks v0.5 policy manifest digest. --- examples/integrated_weak_evidence/pcs_bundle/policy_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/integrated_weak_evidence/pcs_bundle/policy_hash.txt b/examples/integrated_weak_evidence/pcs_bundle/policy_hash.txt index 829966d..0e38d7d 100644 --- a/examples/integrated_weak_evidence/pcs_bundle/policy_hash.txt +++ b/examples/integrated_weak_evidence/pcs_bundle/policy_hash.txt @@ -1 +1 @@ -sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d \ No newline at end of file +sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464 \ No newline at end of file From 319281ecf8691805bfa59cf06c54aa49f8b91db7 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:29 -0700 Subject: [PATCH 62/87] chore(examples): update weak_evidence domain_overlay_hash pin Overlay hash reflects generic_lab overlay under new tiers. --- .../integrated_weak_evidence/pcs_bundle/domain_overlay_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/integrated_weak_evidence/pcs_bundle/domain_overlay_hash.txt b/examples/integrated_weak_evidence/pcs_bundle/domain_overlay_hash.txt index 9a8dd5d..f854a5f 100644 --- a/examples/integrated_weak_evidence/pcs_bundle/domain_overlay_hash.txt +++ b/examples/integrated_weak_evidence/pcs_bundle/domain_overlay_hash.txt @@ -1 +1 @@ -sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab \ No newline at end of file +sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117 \ No newline at end of file From 6884e91c6cfd192eace3c4c376cbe85488585eaa Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:29 -0700 Subject: [PATCH 63/87] chore(examples): update weak_evidence tool_registry_hash pin Registry hash matches default_tool_registry v0.5 content. --- .../integrated_weak_evidence/pcs_bundle/tool_registry_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/integrated_weak_evidence/pcs_bundle/tool_registry_hash.txt b/examples/integrated_weak_evidence/pcs_bundle/tool_registry_hash.txt index 04103ee..e5c3a2c 100644 --- a/examples/integrated_weak_evidence/pcs_bundle/tool_registry_hash.txt +++ b/examples/integrated_weak_evidence/pcs_bundle/tool_registry_hash.txt @@ -1 +1 @@ -sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c \ No newline at end of file +sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a \ No newline at end of file From 8118ed121b4b4148d2e2589780c96b8fcaa0e8ca Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:29 -0700 Subject: [PATCH 64/87] chore(examples): refresh protocol_drift active_update decision Active protocol update path shows review_required under v0.5. --- .../akta_decision_active_update.json | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/examples/integrated_protocol_drift/akta_decision_active_update.json b/examples/integrated_protocol_drift/akta_decision_active_update.json index ce14308..48e55e6 100644 --- a/examples/integrated_protocol_drift/akta_decision_active_update.json +++ b/examples/integrated_protocol_drift/akta_decision_active_update.json @@ -24,11 +24,24 @@ "authorization_required": false, "consequentiality": true, "consequentiality_reason": "mutating tool; external effect; action type A5_protocol_modification; active protocol mutation", - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "policy_file_versions": { + "action_ontology.yaml": "action_ontology-v0.5", + "responsibility_levels.yaml": "responsibility_levels-v0.5", + "evidence_states.yaml": "evidence_states-v0.5", + "validation_statuses.yaml": "validation_statuses-v0.5", + "verification_statuses.yaml": "verification_statuses-v0.5", + "deployment_profiles.yaml": "deployment_profiles-v0.5", + "admissibility_matrix.yaml": "admissibility_matrix-v0.5", + "evidence_to_action_matrix.yaml": "evidence_to_action_matrix-v0.5", + "evidence_to_action_rules.yaml": "evidence_to_action_rules-v0.5", + "default_tool_registry.yaml": "default_tool_registry-v0.5", + "tool_to_requested_scope.yaml": "tool_to_requested_scope-v0.5" + }, "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "classification": { @@ -39,6 +52,7 @@ "uncertainty_flags": [], "classifier_mode": "deterministic" }, + "llm_advisory": null, "ai_output_summary": "Change temperature threshold from 37C to 39C in the active protocol.", "review_trigger": { "review_trigger_id": "AKTA-REVTRIG-DRIFT0001", @@ -74,9 +88,9 @@ "allowed_next_steps": [], "approval_effect": "Allows scoped next step only; not global permission.", "default_expiration": "single_run", - "expires_at": "2026-06-28T08:52:42Z", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "expires_at": "2026-06-28T13:24:48Z", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "consequentiality": true, @@ -87,7 +101,7 @@ "system_id": "protocol_drift_demo" }, "review_route": "active_protocol_review", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "review_trigger_hash": "sha256:f9fdc8310cda27a293dbb502ee0db7d866a9451f86f5e3d82167d46bc10db071" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "review_trigger_hash": "sha256:3826696bfbff31c2d34eaf28011b5b9d88f2ef7c6e4efe2aab45382694e550e0" } } \ No newline at end of file From 7d4cedb7570580bc23b6f583d76ba9b175040550 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:30 -0700 Subject: [PATCH 65/87] chore(examples): refresh protocol_drift draft_only decision Draft follow-up decision demonstrates allowed draft_change scope. --- .../akta_decision_draft_only.json | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/examples/integrated_protocol_drift/akta_decision_draft_only.json b/examples/integrated_protocol_drift/akta_decision_draft_only.json index 0f68261..3bd640c 100644 --- a/examples/integrated_protocol_drift/akta_decision_draft_only.json +++ b/examples/integrated_protocol_drift/akta_decision_draft_only.json @@ -28,11 +28,24 @@ "authorization_required": false, "consequentiality": true, "consequentiality_reason": "action type A5_protocol_modification", - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "policy_file_versions": { + "action_ontology.yaml": "action_ontology-v0.5", + "responsibility_levels.yaml": "responsibility_levels-v0.5", + "evidence_states.yaml": "evidence_states-v0.5", + "validation_statuses.yaml": "validation_statuses-v0.5", + "verification_statuses.yaml": "verification_statuses-v0.5", + "deployment_profiles.yaml": "deployment_profiles-v0.5", + "admissibility_matrix.yaml": "admissibility_matrix-v0.5", + "evidence_to_action_matrix.yaml": "evidence_to_action_matrix-v0.5", + "evidence_to_action_rules.yaml": "evidence_to_action_rules-v0.5", + "default_tool_registry.yaml": "default_tool_registry-v0.5", + "tool_to_requested_scope.yaml": "tool_to_requested_scope-v0.5" + }, "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.draft_change to A5_protocol_modification", "classification": { @@ -43,5 +56,6 @@ "uncertainty_flags": [], "classifier_mode": "deterministic" }, + "llm_advisory": null, "ai_output_summary": "Draft protocol threshold change for owner review." } \ No newline at end of file From 3c3c74c4477725bef73dc6325c6c85e3e0f7045d Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:30 -0700 Subject: [PATCH 66/87] chore(examples): refresh protocol_drift active_update record Record captures SCOPE-facing fields for active update scenario. --- .../akta_record_active_update.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/integrated_protocol_drift/akta_record_active_update.json b/examples/integrated_protocol_drift/akta_record_active_update.json index df0bb39..cc4c8b8 100644 --- a/examples/integrated_protocol_drift/akta_record_active_update.json +++ b/examples/integrated_protocol_drift/akta_record_active_update.json @@ -52,11 +52,11 @@ "consequentiality_reason": "mutating tool; external effect; action type A5_protocol_modification; active protocol mutation" }, "provenance": { - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a" }, "integrations": { "vsa_report_ref": null, @@ -98,9 +98,9 @@ "allowed_next_steps": [], "approval_effect": "Allows scoped next step only; not global permission.", "default_expiration": "single_run", - "expires_at": "2026-06-28T08:52:42Z", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "expires_at": "2026-06-28T13:24:48Z", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "consequentiality": true, @@ -111,8 +111,8 @@ "system_id": "protocol_drift_demo" }, "review_route": "active_protocol_review", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "review_trigger_hash": "sha256:f9fdc8310cda27a293dbb502ee0db7d866a9451f86f5e3d82167d46bc10db071" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "review_trigger_hash": "sha256:3826696bfbff31c2d34eaf28011b5b9d88f2ef7c6e4efe2aab45382694e550e0" }, - "record_hash": "sha256:cc2fdfe1aa6c243dcfbacac9f46e24cfc3d30f65f5431d82326eecd5481ff614" + "record_hash": "sha256:3ac8a1b29ee653b79afe83420a82f2bbeb2153b4a2f4714b75cfba9c3cc90c05" } \ No newline at end of file From cd0a53824f5ca402283472dc6931ccd677242b72 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:30 -0700 Subject: [PATCH 67/87] chore(examples): update protocol_drift pf_obligation artifact Obligation documents tool_review requirement from integrated demo. --- examples/integrated_protocol_drift/pf_obligation.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/integrated_protocol_drift/pf_obligation.json b/examples/integrated_protocol_drift/pf_obligation.json index 8704d46..42a0d8c 100644 --- a/examples/integrated_protocol_drift/pf_obligation.json +++ b/examples/integrated_protocol_drift/pf_obligation.json @@ -11,12 +11,12 @@ ], "allowed_tools": [], "max_responsibility_level": "R4_methodological_modification", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", "scope_grant_ref": null, "review_trigger_id": "AKTA-REVTRIG-DRIFT0001", - "expires_at": "2026-06-28T08:52:42Z", + "expires_at": "2026-06-28T13:24:48Z", "enforcement_mode": "review_gate", "required_runtime_behavior": { "block_execution": false, @@ -31,5 +31,5 @@ "next_admissible_steps": [], "required_review_role": "protocol_owner", "consequentiality": true, - "obligation_hash": "sha256:738bb0b50387dfe4ce1edefd0dc72c826e35a4e17a8da43100f5f71b1e55a41a" + "obligation_hash": "sha256:f910bae52fca3e99ea9bea1c36b98a35bc8ae3a6645c3d36df699a8acb439d9f" } \ No newline at end of file From f838c11eee25d90dc1685144a9394d65b0214881 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:31 -0700 Subject: [PATCH 68/87] chore(examples): update protocol_drift review_trigger artifact Review trigger encodes human review gate for protocol drift. --- examples/integrated_protocol_drift/review_trigger.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/integrated_protocol_drift/review_trigger.json b/examples/integrated_protocol_drift/review_trigger.json index 3de591f..8961ed6 100644 --- a/examples/integrated_protocol_drift/review_trigger.json +++ b/examples/integrated_protocol_drift/review_trigger.json @@ -32,9 +32,9 @@ "allowed_next_steps": [], "approval_effect": "Allows scoped next step only; not global permission.", "default_expiration": "single_run", - "expires_at": "2026-06-28T08:52:42Z", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "expires_at": "2026-06-28T13:24:48Z", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "consequentiality": true, @@ -45,6 +45,6 @@ "system_id": "protocol_drift_demo" }, "review_route": "active_protocol_review", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "review_trigger_hash": "sha256:f9fdc8310cda27a293dbb502ee0db7d866a9451f86f5e3d82167d46bc10db071" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "review_trigger_hash": "sha256:3826696bfbff31c2d34eaf28011b5b9d88f2ef7c6e4efe2aab45382694e550e0" } \ No newline at end of file From 0ddb64a6eefe55298be3453ff79cfba2725aa718 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:31 -0700 Subject: [PATCH 69/87] chore(examples): update protocol_drift scope_review_packet SCOPE review packet matches client simulation output for demo. --- examples/integrated_protocol_drift/scope_review_packet.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/integrated_protocol_drift/scope_review_packet.json b/examples/integrated_protocol_drift/scope_review_packet.json index 8c6ffb9..4d2df1a 100644 --- a/examples/integrated_protocol_drift/scope_review_packet.json +++ b/examples/integrated_protocol_drift/scope_review_packet.json @@ -12,8 +12,8 @@ "protocol_editor.update_active_protocol" ], "allowed_next_steps": [], - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c" + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a" }, "packet_mode": "trigger_only" } \ No newline at end of file From 3bfe0c6a5b899efac0fd72d3d31ac8209774b436 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:31 -0700 Subject: [PATCH 70/87] chore(examples): add protocol_drift scope_decision artifact SCOPE decision artifact completes grant path in v0.5 PCS chain. --- examples/integrated_protocol_drift/scope_decision.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 examples/integrated_protocol_drift/scope_decision.json diff --git a/examples/integrated_protocol_drift/scope_decision.json b/examples/integrated_protocol_drift/scope_decision.json new file mode 100644 index 0000000..00d3c4d --- /dev/null +++ b/examples/integrated_protocol_drift/scope_decision.json @@ -0,0 +1,5 @@ +{ + "status": "granted", + "granted_scope": "protocol_draft", + "reviewer_id": "protocol_owner" +} \ No newline at end of file From e5a04cb0abe7a59510cd35e8f718a12f07a7475d Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:31 -0700 Subject: [PATCH 71/87] chore(examples): regenerate protocol_drift PCS manifest v0.5 Manifest enumerates SCOPE and PF members in full-chain bundle. --- .../pcs_bundle/manifest.json | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/manifest.json b/examples/integrated_protocol_drift/pcs_bundle/manifest.json index 5c4eca1..20d6e3b 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/manifest.json +++ b/examples/integrated_protocol_drift/pcs_bundle/manifest.json @@ -1,20 +1,35 @@ { "artifact_type": "akta_scientific_action_record", - "schema_version": "akta-record-v0.4", - "record_hash": "sha256:cc2fdfe1aa6c243dcfbacac9f46e24cfc3d30f65f5431d82326eecd5481ff614", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "schema_version": "akta-record-v0.5", + "record_hash": "sha256:3ac8a1b29ee653b79afe83420a82f2bbeb2153b4a2f4714b75cfba9c3cc90c05", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "decision_id": "AKTA-DEC-DRIFT0001", "files": [ - "akta_record.json", "akta_decision.json", - "policy_hash.txt", + "akta_record.json", "domain_overlay_hash.txt", - "tool_registry_hash.txt", + "manifest.json", + "pf_obligation.json", + "policy_hash.txt", "review_trigger.json", + "scope_decision.json", + "scope_grant.json", "scope_review_packet.json", - "manifest.json" + "tool_registry_hash.txt" ], - "manifest_hash": "sha256:17b3eadfa5a4043243b665f31561786db1dcba82eee658145b1cab4474bb0e00" + "file_hashes": { + "akta_decision.json": "sha256:5bfd0b359c63421f2369251037c8823cc12f23d7e183c819ae86a9330dae9767", + "akta_record.json": "sha256:6e07f468fb7149d9646d6dae8cdf3cf86f4dccceb6c4c6dfd3611b47733967e6", + "domain_overlay_hash.txt": "sha256:aff97a5c94f9f99549b9f4043617ba8dcfce973312fde13d63101d173bcb0860", + "pf_obligation.json": "sha256:9c534e59645bbb43efb388dcaf5fb7c44c49aa3a128f32bcb9e69ed863881e11", + "policy_hash.txt": "sha256:918d683b234533a1c7da8178e581eff636b2786f9b7cc373873ea805994b15ff", + "review_trigger.json": "sha256:5d53e1970c5e390b2d65698b00fcb490941ff1c5afc1d4d8137d48c4ab3e8e4b", + "scope_decision.json": "sha256:f4cbdd8608cf0cc49c9b139fe31ef1504ec9d17e3d5dde9d95a0c4d28f841088", + "scope_grant.json": "sha256:8025e247844ffb17efd9a41755319b011a1aae7463d86df416134da816597334", + "scope_review_packet.json": "sha256:860ad06369f4920b24a52bfbcf68ea6b6f187a6442e96646ec0f084e3664b801", + "tool_registry_hash.txt": "sha256:6375791d242c7aad891c23def053d895a7ab6ac579524866cf45fa5a2e7e9863" + }, + "manifest_hash": "sha256:6d490365f8e19f5f29d26bb2e6f5aa0edd511cef3ec1ac3008b2fb16644fce94" } \ No newline at end of file From f3583a2e2c25edda726203ccb328a2cb09a86f40 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:32 -0700 Subject: [PATCH 72/87] chore(examples): sync protocol_drift PCS akta_decision bundle Bundled decision mirrors active and draft demo outcomes. --- .../pcs_bundle/akta_decision.json | 32 +++++++++++++------ 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/akta_decision.json b/examples/integrated_protocol_drift/pcs_bundle/akta_decision.json index ce14308..48e55e6 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/akta_decision.json +++ b/examples/integrated_protocol_drift/pcs_bundle/akta_decision.json @@ -24,11 +24,24 @@ "authorization_required": false, "consequentiality": true, "consequentiality_reason": "mutating tool; external effect; action type A5_protocol_modification; active protocol mutation", - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "policy_file_versions": { + "action_ontology.yaml": "action_ontology-v0.5", + "responsibility_levels.yaml": "responsibility_levels-v0.5", + "evidence_states.yaml": "evidence_states-v0.5", + "validation_statuses.yaml": "validation_statuses-v0.5", + "verification_statuses.yaml": "verification_statuses-v0.5", + "deployment_profiles.yaml": "deployment_profiles-v0.5", + "admissibility_matrix.yaml": "admissibility_matrix-v0.5", + "evidence_to_action_matrix.yaml": "evidence_to_action_matrix-v0.5", + "evidence_to_action_rules.yaml": "evidence_to_action_rules-v0.5", + "default_tool_registry.yaml": "default_tool_registry-v0.5", + "tool_to_requested_scope.yaml": "tool_to_requested_scope-v0.5" + }, "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "classification": { @@ -39,6 +52,7 @@ "uncertainty_flags": [], "classifier_mode": "deterministic" }, + "llm_advisory": null, "ai_output_summary": "Change temperature threshold from 37C to 39C in the active protocol.", "review_trigger": { "review_trigger_id": "AKTA-REVTRIG-DRIFT0001", @@ -74,9 +88,9 @@ "allowed_next_steps": [], "approval_effect": "Allows scoped next step only; not global permission.", "default_expiration": "single_run", - "expires_at": "2026-06-28T08:52:42Z", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "expires_at": "2026-06-28T13:24:48Z", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "consequentiality": true, @@ -87,7 +101,7 @@ "system_id": "protocol_drift_demo" }, "review_route": "active_protocol_review", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "review_trigger_hash": "sha256:f9fdc8310cda27a293dbb502ee0db7d866a9451f86f5e3d82167d46bc10db071" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "review_trigger_hash": "sha256:3826696bfbff31c2d34eaf28011b5b9d88f2ef7c6e4efe2aab45382694e550e0" } } \ No newline at end of file From ef1286b1e38227c588da7e9509d70a123026503d Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:32 -0700 Subject: [PATCH 73/87] chore(examples): sync protocol_drift PCS akta_record bundle Bundled record ties AKTA outputs to SCOPE artifacts in PCS. --- .../pcs_bundle/akta_record.json | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/akta_record.json b/examples/integrated_protocol_drift/pcs_bundle/akta_record.json index df0bb39..cc4c8b8 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/akta_record.json +++ b/examples/integrated_protocol_drift/pcs_bundle/akta_record.json @@ -52,11 +52,11 @@ "consequentiality_reason": "mutating tool; external effect; action type A5_protocol_modification; active protocol mutation" }, "provenance": { - "policy_version": "akta-core-v0.4", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", + "policy_version": "akta-core-v0.5", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", "domain_overlay_version": "generic_lab_v0.2", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a" }, "integrations": { "vsa_report_ref": null, @@ -98,9 +98,9 @@ "allowed_next_steps": [], "approval_effect": "Allows scoped next step only; not global permission.", "default_expiration": "single_run", - "expires_at": "2026-06-28T08:52:42Z", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "expires_at": "2026-06-28T13:24:48Z", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "consequentiality": true, @@ -111,8 +111,8 @@ "system_id": "protocol_drift_demo" }, "review_route": "active_protocol_review", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "review_trigger_hash": "sha256:f9fdc8310cda27a293dbb502ee0db7d866a9451f86f5e3d82167d46bc10db071" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "review_trigger_hash": "sha256:3826696bfbff31c2d34eaf28011b5b9d88f2ef7c6e4efe2aab45382694e550e0" }, - "record_hash": "sha256:cc2fdfe1aa6c243dcfbacac9f46e24cfc3d30f65f5431d82326eecd5481ff614" + "record_hash": "sha256:3ac8a1b29ee653b79afe83420a82f2bbeb2153b4a2f4714b75cfba9c3cc90c05" } \ No newline at end of file From 818ae4fda7dabb35e9c7d0601f683a5727e5bea9 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:32 -0700 Subject: [PATCH 74/87] chore(examples): sync protocol_drift PCS review_trigger bundle Bundled review trigger supports tamper tests on PCS chain. --- .../pcs_bundle/review_trigger.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/review_trigger.json b/examples/integrated_protocol_drift/pcs_bundle/review_trigger.json index 3de591f..8961ed6 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/review_trigger.json +++ b/examples/integrated_protocol_drift/pcs_bundle/review_trigger.json @@ -32,9 +32,9 @@ "allowed_next_steps": [], "approval_effect": "Allows scoped next step only; not global permission.", "default_expiration": "single_run", - "expires_at": "2026-06-28T08:52:42Z", - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c", + "expires_at": "2026-06-28T13:24:48Z", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", "classifier_confidence": 0.98, "classification_rationale": "tool registry maps protocol_editor.update_active_protocol to A5_protocol_modification", "consequentiality": true, @@ -45,6 +45,6 @@ "system_id": "protocol_drift_demo" }, "review_route": "active_protocol_review", - "domain_overlay_hash": "sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab", - "review_trigger_hash": "sha256:f9fdc8310cda27a293dbb502ee0db7d866a9451f86f5e3d82167d46bc10db071" + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "review_trigger_hash": "sha256:3826696bfbff31c2d34eaf28011b5b9d88f2ef7c6e4efe2aab45382694e550e0" } \ No newline at end of file From ecf7dd0ca961decdeb240a9abf6db0adc5deab8c Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:33 -0700 Subject: [PATCH 75/87] chore(examples): sync protocol_drift PCS scope_review_packet Review packet hash linkage is validated in PCS contract tests. --- .../pcs_bundle/scope_review_packet.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/scope_review_packet.json b/examples/integrated_protocol_drift/pcs_bundle/scope_review_packet.json index 8c6ffb9..4d2df1a 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/scope_review_packet.json +++ b/examples/integrated_protocol_drift/pcs_bundle/scope_review_packet.json @@ -12,8 +12,8 @@ "protocol_editor.update_active_protocol" ], "allowed_next_steps": [], - "policy_hash": "sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d", - "tool_registry_hash": "sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c" + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a" }, "packet_mode": "trigger_only" } \ No newline at end of file From c7edd1cbbfd26a9f60265fa275a217a7148c71a9 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:33 -0700 Subject: [PATCH 76/87] chore(examples): add protocol_drift PCS pf_obligation bundle member PF obligation is exported as first-class PCS artifact in v0.5. --- .../pcs_bundle/pf_obligation.json | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 examples/integrated_protocol_drift/pcs_bundle/pf_obligation.json diff --git a/examples/integrated_protocol_drift/pcs_bundle/pf_obligation.json b/examples/integrated_protocol_drift/pcs_bundle/pf_obligation.json new file mode 100644 index 0000000..42a0d8c --- /dev/null +++ b/examples/integrated_protocol_drift/pcs_bundle/pf_obligation.json @@ -0,0 +1,35 @@ +{ + "obligation_id": "PF-OBL-AKTA-SAR-DRIFT0001", + "obligation_type": "tool_review", + "source": "AKTA", + "source_record_id": "AKTA-SAR-DRIFT0001", + "decision_id": "AKTA-DEC-DRIFT0001", + "decision": "review_required", + "decision_reason_hash": "sha256:24c91bb03ef39f5f31307b9c0e338df1ae28965f62984ca015674f4d9ef01635", + "blocked_tools": [ + "protocol_editor.update_active_protocol" + ], + "allowed_tools": [], + "max_responsibility_level": "R4_methodological_modification", + "policy_hash": "sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464", + "tool_registry_hash": "sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a", + "domain_overlay_hash": "sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117", + "scope_grant_ref": null, + "review_trigger_id": "AKTA-REVTRIG-DRIFT0001", + "expires_at": "2026-06-28T13:24:48Z", + "enforcement_mode": "review_gate", + "required_runtime_behavior": { + "block_execution": false, + "require_review_before_tool_call": true, + "require_authorization_before_tool_call": false, + "blocked_tools": [ + "protocol_editor.update_active_protocol" + ], + "allowed_tools": [], + "log_all_tool_calls": false + }, + "next_admissible_steps": [], + "required_review_role": "protocol_owner", + "consequentiality": true, + "obligation_hash": "sha256:f910bae52fca3e99ea9bea1c36b98a35bc8ae3a6645c3d36df699a8acb439d9f" +} \ No newline at end of file From 6dfce1a0a3d13fcc39fcc33a40affa02b45ff288 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:33 -0700 Subject: [PATCH 77/87] chore(examples): add protocol_drift PCS scope_decision bundle member SCOPE decision is included for full-chain integrity checks. --- .../integrated_protocol_drift/pcs_bundle/scope_decision.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 examples/integrated_protocol_drift/pcs_bundle/scope_decision.json diff --git a/examples/integrated_protocol_drift/pcs_bundle/scope_decision.json b/examples/integrated_protocol_drift/pcs_bundle/scope_decision.json new file mode 100644 index 0000000..00d3c4d --- /dev/null +++ b/examples/integrated_protocol_drift/pcs_bundle/scope_decision.json @@ -0,0 +1,5 @@ +{ + "status": "granted", + "granted_scope": "protocol_draft", + "reviewer_id": "protocol_owner" +} \ No newline at end of file From cea879331d3a4354838c92073787aca0a3486675 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:33 -0700 Subject: [PATCH 78/87] chore(examples): add protocol_drift PCS scope_grant bundle member Grant artifact documents protocol_draft scope from SCOPE simulation. --- .../integrated_protocol_drift/pcs_bundle/scope_grant.json | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 examples/integrated_protocol_drift/pcs_bundle/scope_grant.json diff --git a/examples/integrated_protocol_drift/pcs_bundle/scope_grant.json b/examples/integrated_protocol_drift/pcs_bundle/scope_grant.json new file mode 100644 index 0000000..70e4b8b --- /dev/null +++ b/examples/integrated_protocol_drift/pcs_bundle/scope_grant.json @@ -0,0 +1,7 @@ +{ + "grant_id": "SCOPE-GRANT-AKTA-REVTRIG-DRIFT0001", + "granted_scope": "protocol_draft", + "requested_scope": "active_protocol_update", + "reviewer_id": "protocol_owner", + "review_trigger_id": "AKTA-REVTRIG-DRIFT0001" +} \ No newline at end of file From 97ee86499d8b28feccd2266e1c247656863ac0db Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:34 -0700 Subject: [PATCH 79/87] chore(examples): update protocol_drift PCS policy_hash pin Policy digest pin matches regenerated v0.5 manifest. --- examples/integrated_protocol_drift/pcs_bundle/policy_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/policy_hash.txt b/examples/integrated_protocol_drift/pcs_bundle/policy_hash.txt index 829966d..0e38d7d 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/policy_hash.txt +++ b/examples/integrated_protocol_drift/pcs_bundle/policy_hash.txt @@ -1 +1 @@ -sha256:e0951ca2ff63b3fce9b3c841b324c3bf23d0f2e4b062c0c3dd5a057f7af5944d \ No newline at end of file +sha256:b2878971b3236b33c5011d6e12ed37ed85582f16996b9d538b584733d9f65464 \ No newline at end of file From 0c01a82c5507c9abdde0453c7dc118a3af7d850b Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:34 -0700 Subject: [PATCH 80/87] chore(examples): update protocol_drift domain_overlay_hash pin Overlay digest updated after overlay tier edits. --- .../pcs_bundle/domain_overlay_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/domain_overlay_hash.txt b/examples/integrated_protocol_drift/pcs_bundle/domain_overlay_hash.txt index 9a8dd5d..f854a5f 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/domain_overlay_hash.txt +++ b/examples/integrated_protocol_drift/pcs_bundle/domain_overlay_hash.txt @@ -1 +1 @@ -sha256:67d13db14ede89da9ffbd5592e7f081b209d25a03bc3942e7a7f1feac1d4eaab \ No newline at end of file +sha256:9043be6ab410404da7d6b98e28db21395884745b29372310d24310714f8fe117 \ No newline at end of file From 6fd22f9b89fce6245f1d73564c408b53eedf9dc8 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:34 -0700 Subject: [PATCH 81/87] chore(examples): update protocol_drift tool_registry_hash pin Tool registry digest aligns with PCS export expectations. --- .../integrated_protocol_drift/pcs_bundle/tool_registry_hash.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/integrated_protocol_drift/pcs_bundle/tool_registry_hash.txt b/examples/integrated_protocol_drift/pcs_bundle/tool_registry_hash.txt index 04103ee..e5c3a2c 100644 --- a/examples/integrated_protocol_drift/pcs_bundle/tool_registry_hash.txt +++ b/examples/integrated_protocol_drift/pcs_bundle/tool_registry_hash.txt @@ -1 +1 @@ -sha256:106f428d42b42d5f0f459d8e65da25f1f9ee237efb6a3b27fe880f83d7bc604c \ No newline at end of file +sha256:90746b4b61b236802bf9762b6755e966a676c6294e83fd3606ce0826f1aff50a \ No newline at end of file From 9a332944ed0e6a337b4c1960e122db4ecf2c9c97 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:35 -0700 Subject: [PATCH 82/87] docs(integration): refresh integration guide for v0.5 workflows Guide covers PCS full-chain export and SCOPE adapter modes. --- docs/integration_guide.md | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/docs/integration_guide.md b/docs/integration_guide.md index 02de4bd..9c95528 100644 --- a/docs/integration_guide.md +++ b/docs/integration_guide.md @@ -1,7 +1,33 @@ -# AKTA Integration Guide (v0.4) +# AKTA Integration Guide (v0.5) AKTA integrates with adjacent systems in the AI-for-science trust stack. +## v0.5 integration additions + +| Component | Path | Purpose | +|-----------|------|---------| +| SCOPE adapter | `adapters/scope/client.py` | Simulated, python-import (`SCOPE_REPO_PATH`), or CLI (`SCOPE_CLI`) | +| SCOPE engine protocol | `adapters/scope/engine_protocol.py` | Expected methods for python-import mode | +| PCS v0.5 full chain | `adapters/pcs/export_artifact.py` | 10 artifacts, `file_hashes`, tamper validation | +| Production policy integrity | `akta/policy_integrity.py` | Dev vs production HMAC; manifest required in production | +| Overlay governance | `akta/overlays.py` | Tiers; production refuses experimental overlays | +| LLM trust boundary | `docs/classifier_trust_boundary.md` | Tool registry overrides LLM; advisory metadata only | + +```bash +# Production mode +export AKTA_PRODUCTION_MODE=1 +export AKTA_POLICY_HMAC_KEY="" +python scripts/regenerate_policy_manifest.py # after policy edits + +# SCOPE modes +export SCOPE_REPO_PATH=/path/to/SCOPE # python-import +export SCOPE_CLI=scope # CLI subprocess +python scripts/demo_akta_scope_protocol_drift.py + +# PCS full-chain validate +akta export pcs --record akta_record.json --decision akta_decision.json --out pcs_bundle/ --validate +``` + ## v0.4 integration additions | Component | Path | Purpose | @@ -22,7 +48,7 @@ python evals/run_oracle_independent.py --out evals/reports/oracle_independent.js # MCP server (stdio) python -m adapters.mcp.server -# REST API v0.4 +# REST API v0.5 akta-rest --host 127.0.0.1 --port 8765 ``` @@ -76,7 +102,7 @@ See [scope_bridge.md](scope_bridge.md) and [review_integration.md](review_integr | Consequentiality | Decision `consequentiality` / `consequentiality_reason` | | Rich classifier | Decision `classification` audit block | | PF obligation v0.2 | `enforcement_mode`, `required_runtime_behavior` | -| PCS bundle v0.4 | `schema_version: akta-record-v0.4` | +| PCS bundle v0.5 | `schema_version: akta-record-v0.5` | ## Python API From 22276b7225c753d90bf3926698676a19437b3f87 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:35 -0700 Subject: [PATCH 83/87] docs(security): expand SECURITY notes for v0.5 trust model Security doc highlights classifier boundary and policy HMAC split. --- SECURITY.md | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 949bf7e..99eecdc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,6 +4,7 @@ | Version | Supported | |---------|-----------| +| 0.5.x | Yes | | 0.4.x | Yes | | 0.3.x | Yes | @@ -13,11 +14,37 @@ Report security issues privately to the repository maintainers via GitHub Securi Do not open public issues for undisclosed vulnerabilities. -## Security model (v0.4) +## Security model (v0.5) AKTA is part of the agent supply chain. Threats include policy tampering, fake AKTA Records, domain overlay manipulation, tool registry poisoning, review trigger spoofing, stale review reuse, unknown tool confusion, and downgrade attacks. -### v0.4 controls +### Dev vs production policy integrity + +| Mode | Environment variables | Manifest | HMAC key | Overlay policy | +|------|----------------------|----------|----------|----------------| +| Dev (default) | none required | optional | dev key accepted with warning | experimental overlays allowed | +| Verify | `AKTA_VERIFY_POLICY=1` | required | deployment key required | experimental overlays allowed | +| Production | `AKTA_PRODUCTION_MODE=1` | required | deployment key required; dev key rejected | experimental overlays refused | + +Regenerate manifest after policy edits: `python scripts/regenerate_policy_manifest.py` + +See [docs/policy_integrity.md](docs/policy_integrity.md). + +### v0.5 controls + +- JSON schema validation on all artifacts +- Canonical SHA-256 hashing of policy bundle, domain overlays, and tool registry +- Dev vs production policy integrity (`AKTA_PRODUCTION_MODE=1`, `AKTA_VERIFY_POLICY=1`) +- Production requires deployment-specific `AKTA_POLICY_HMAC_KEY`; rejects in-repo dev key +- PCS bundle per-file hash manifest with tamper detection +- Overlay governance tiers; production refuses experimental high-risk overlays +- LLM classifier advisory-only; tool registry overrides LLM output +- Policy hash, domain overlay hash, and tool registry hash in every decision +- Record hash on every AKTA Record +- Review context enforcement (F14 stale review, F12 disclaimer boundary) +- Unknown mutating tools blocked by default (abstain_insufficient_context) + +### v0.4 controls (retained) - JSON schema validation on all artifacts - Canonical SHA-256 hashing of policy bundle, domain overlays, and tool registry @@ -28,10 +55,10 @@ AKTA is part of the agent supply chain. Threats include policy tampering, fake A - Unknown mutating tools blocked by default (abstain_insufficient_context) - Explicit version fields on policy and schemas -### Out of scope for v0.4 +### Out of scope for v0.5 -- Ed25519 signed policy releases in production (HMAC dev key only in-repo) -- Live SCOPE/PF/PCS runtime enforcement (delegated to external repos) +- Ed25519 signed policy releases (HMAC deployment key supported; public-key verification stub only) +- Live SCOPE/PF/PCS runtime enforcement (delegated to external repos; AKTA adapters simulate or bridge) - Model truthfulness guarantees See [docs/threat_model.md](docs/threat_model.md) for the full threat model. From 35591887b90c39d4110948f2d1e2890b9514011c Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:36 -0700 Subject: [PATCH 84/87] docs(governance): align GOVERNANCE with overlay tier rules Governance text reflects overlay review tiers introduced in v0.5. --- GOVERNANCE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 1d9ec6b..355526e 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -8,11 +8,11 @@ AKTA is an open protocol with a reference implementation. It is stewarded as a s - **Protocol changes** (schemas, ontologies, deployment profiles): require design review and version bump. - **Reference kernel changes**: follow normal pull request review. -- **Domain overlays**: operational overlays include generic lab, materials, computational science, biology, chemistry, and clinical (v0.4). Domain-specific hazard triggers and reviewer roles are maintained in-repo; institutional deployment still requires local governance review. +- **Domain overlays**: core reference overlays (generic lab, materials, computational science) are operational. Biology, chemistry, and clinical overlays are experimental (v0.5) and not deployment-ready without institutional governance. ## Versioning -AKTA follows semantic versioning for the reference implementation package (`akta-protocol`). Schema versions are embedded in artifact metadata (e.g., `akta-core-v0.4`, `akta-record-v0.4`). +AKTA follows semantic versioning for the reference implementation package (`akta-protocol`). Schema versions are embedded in artifact metadata (e.g., `akta-core-v0.5`, `akta-record-v0.5`). ## Non-certification From f0cf2a1ad9e5207b19aa29ae3ccb7a92fba498b2 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:36 -0700 Subject: [PATCH 85/87] docs(readme): update README for AKTA v0.5 capabilities README summarizes v0.5 integration, demos, and integrity modes. --- README.md | 75 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 2e32ebc..917040d 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ If AI changes what science does next, there should be an AKTA Record. ```bash pip install -e ".[dev]" -# Run the weak-evidence gate demo (AKTA/PF/PCS only — no SCOPE chain) +# Dev mode (default): policy manifest optional; experimental overlays allowed akta gate \ --output examples/weak_evidence/ai_output.json \ --tool lab_scheduler.prioritize \ @@ -21,43 +21,44 @@ akta gate \ --context examples/weak_evidence/context.json \ --out examples/weak_evidence/akta_decision.json -akta record \ - --decision examples/weak_evidence/akta_decision.json \ - --out examples/weak_evidence/akta_record.json +# Production mode: requires policy manifest + deployment HMAC key +# $env:AKTA_PRODUCTION_MODE = "1" +# $env:AKTA_POLICY_HMAC_KEY = "" +# Regenerate manifest after policy edits: +python scripts/regenerate_policy_manifest.py -pytest tests/ -v -akta eval --scenarios scenarios/canonical_5.jsonl --expected scenarios/expected_decisions.jsonl -akta eval --scenarios scenarios/public_100.jsonl --expected scenarios/expected_decisions.jsonl +# SCOPE adapter modes (see docs/scope_bridge.md) +# simulated (default) | python-import ($env:SCOPE_REPO_PATH) | cli ($env:SCOPE_CLI) +python scripts/demo_akta_scope_protocol_drift.py -# Oracle-independent eval (hand-written labels, not gate-derived) -python evals/run_oracle_independent.py -# or: akta-oracle-eval +# PCS v0.5 full-chain export (10 artifacts + file_hashes) +akta export pcs --record examples/weak_evidence/akta_record.json \ + --decision examples/weak_evidence/akta_decision.json \ + --out dist/pcs_bundle/ --validate -# Transition eval (SCOPE grant -> re-gate) -python -c "from evals.transition_runner import run_transition; import json; ..." +pytest tests/ -v +make ci +``` -# MCP stdio server (JSON-RPC over stdin/stdout) -python -m adapters.mcp.server +### REST API (OpenAPI v0.5) -# REST API (OpenAPI v0.4) +```bash akta-rest --host 127.0.0.1 --port 8765 +# GET /v0/health, /v0/policy; POST /v0/evaluate, /v0/export/pcs, /v0/export/pf +``` -# Export adapters (v0.2) -akta export pcs --record examples/weak_evidence/akta_record.json \ - --decision examples/weak_evidence/akta_decision.json \ - --out dist/pcs_bundle/ --validate +### Additional commands + +```bash +akta record --decision examples/weak_evidence/akta_decision.json --out examples/weak_evidence/akta_record.json +akta eval --scenarios scenarios/canonical_5.jsonl --expected scenarios/expected_decisions.jsonl +akta eval --scenarios scenarios/public_100.jsonl --expected scenarios/expected_decisions.jsonl +python evals/run_oracle_independent.py +python -m adapters.mcp.server akta export pf --record examples/weak_evidence/akta_record.json \ - --decision examples/weak_evidence/akta_decision.json \ - --out dist/pf_obligations/ --validate + --decision examples/weak_evidence/akta_decision.json --out dist/pf_obligations/ --validate akta review-trigger export --decision decision.json --out review_trigger.json - -# Integrated weak-evidence demo (AKTA/PF/PCS; no SCOPE simulation) python scripts/demo_integrated_weak_evidence.py -# or: make demo-akta-weak-evidence - -# Integrated AKTA x SCOPE protocol-drift demo (canonical AKTA×SCOPE chain) -python scripts/demo_akta_scope_protocol_drift.py -# or: make demo-akta-scope-protocol-drift ``` ## Architecture @@ -118,7 +119,7 @@ record = decision.to_record() - [Review integration](docs/review_integration.md) - [SCOPE bridge](docs/scope_bridge.md) - [AKTA v0.3 integration](docs/akta_v03_integration.md) -- [Threat model (v0.4)](docs/threat_model.md) +- [Threat model](docs/threat_model.md) - [PF-Core bridge](docs/pf_core_bridge.md) - [PCS export](docs/pcs_export.md) - [VSA import](docs/vsa_import.md) @@ -139,11 +140,23 @@ akta-rest --host 127.0.0.1 --port 8765 # GET /v0/policy, /v0/health ``` +## v0.5 acceptance status + +| Criterion | Status | +|-----------|--------| +| SCOPE adapter (simulated / python-import / cli) | Pass | +| PCS full-chain export with file_hashes + tamper validation | Pass | +| Production policy integrity (dev vs production HMAC) | Pass | +| LLM classifier trust boundary (registry overrides LLM) | Pass | +| Overlay governance tiers + production refusal | Pass | +| Policy file versioning in decision provenance | Pass | +| 210+ tests; `make ci` green | Pass | + ## v0.4 acceptance status | Criterion | Status | |-----------|--------| -| Operational domain overlays (biology, chemistry, clinical) | Pass | +| Experimental overlays (biology, chemistry, clinical) | Pass (not operational; refused in production) | | Policy manifest HMAC verification | Pass | | Review lifecycle (F12/F14, prior records) | Pass | | Structured classification + negation guard | Pass | @@ -205,4 +218,4 @@ akta-rest --host 127.0.0.1 --port 8765 ## Status -AKTA v0.4 is a reference implementation. It is not a safety certification. Deployment profile P7 (fully autonomous scientific operator) is defined for taxonomy only and is not supported. +AKTA v0.5 is a reference implementation. It is not a safety certification. Biology, chemistry, and clinical overlays are experimental and not deployment-ready without institutional governance. Deployment profile P7 (fully autonomous scientific operator) is defined for taxonomy only and is not supported. From 58bb509c9b7514e281f7623b9ac51b2f954e8ce7 Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:36 -0700 Subject: [PATCH 86/87] docs(changelog): add AKTA v0.5.0 release notes Changelog captures user-visible changes for the v0.5 release. --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d5040ca..7f5b37b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to AKTA are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). +## [0.5.0] - 2026-06-28 + +### Added + +- Real SCOPE adapter modes: `simulated`, `python-import` (`SCOPE_REPO_PATH`), `cli` (`SCOPE_CLI`) with `scope packet create`, `scope decision submit`, `scope grant issue` +- PCS v0.5 full-chain export: `scope_decision.json`, `scope_grant.json`, `pf_obligation.json`, per-file `file_hashes`, tamper validation (`validate_pcs_bundle`) +- Production policy integrity: `AKTA_PRODUCTION_MODE=1` requires manifest, deployment HMAC key; rejects dev key +- LLM classifier trust boundary: tool registry always overrides LLM; `llm_advisory` metadata in decisions; `docs/classifier_trust_boundary.md` +- Overlay governance tiers: `core_reference`, `experimental_domain_overlay`, `expert_reviewed_domain_overlay`, `institutional_deployment_overlay` +- Policy file versioning: `policy_file_version` per YAML; `policy_bundle_version` in decision provenance +- Contract tests: `test_scope_import_mode_contract.py`, `test_real_scope_cli_contract.py`, `test_pcs_full_chain_tamper.py` + +### Changed + +- Policy bundle version `akta-core-v0.5`; package version `0.5.0` +- PCS manifest `schema_version` → `akta-record-v0.5` +- SCOPE adapter mode labels: `simulated` | `python-import` | `cli` (replaces `subprocess`) +- Biology/chemistry/clinical overlays marked `experimental_domain_overlay` (not deployment-ready in production) + ## [0.4.0] - 2026-06-28 ### Added From 92e2ce25239ce324c364aef76c3859aa1c59e69d Mon Sep 17 00:00:00 2001 From: fraware Date: Sun, 28 Jun 2026 06:26:37 -0700 Subject: [PATCH 87/87] docs(skill): refresh admissibility skill for v0.5 policy surface Skill instructions reference v0.5 artifacts and trust-boundary docs. --- .../akta-scientific-action-admissibility/SKILL.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/skills/akta-scientific-action-admissibility/SKILL.md b/skills/akta-scientific-action-admissibility/SKILL.md index 7699fec..9aeab28 100644 --- a/skills/akta-scientific-action-admissibility/SKILL.md +++ b/skills/akta-scientific-action-admissibility/SKILL.md @@ -1,7 +1,7 @@ --- name: akta-scientific-action-admissibility description: >- - Evaluates scientific action admissibility using the AKTA protocol (v0.4). + Evaluates scientific action admissibility using the AKTA protocol (v0.5). Classifies AI outputs (A0-A10), assigns responsibility levels, applies evidence and deployment profile policy, gates tool calls, emits AKTA Records, and produces SCOPE-compatible review triggers with requested_scope. Use when integrating @@ -11,7 +11,7 @@ description: >- authority transfer. --- -# AKTA Scientific Action Admissibility (v0.4) +# AKTA Scientific Action Admissibility (v0.5) ## Quick start @@ -28,7 +28,8 @@ akta gate \ akta record --decision akta_decision.json --out akta_record.json akta review-trigger export --decision akta_decision.json --out review_trigger.json -# v0.4: oracle-independent eval, transition runner, MCP server +# v0.5: SCOPE adapter modes, PCS full chain, production policy integrity +python scripts/demo_akta_scope_protocol_drift.py python evals/run_oracle_independent.py python -m adapters.mcp.server ``` @@ -50,9 +51,9 @@ Do **not** use AKTA to decide scientific truth — use VSA for evidence groundin 3. **Gate tool call** — block if `admissibility` is `blocked`, `abstain_insufficient_context`, `review_required`, or `authorization_required` 4. **Emit record** — `decision.to_record()` for every non-trivial decision 5. **Export integrations** — PF-Core obligation (`akta export pf`), PCS bundle (`akta export pcs`) -6. **SCOPE handoff** — when `review_required` or `authorization_required`, export `review_trigger` with `requested_scope` for SCOPE routing (simulated or subprocess via `SCOPE_CLI`) +6. **SCOPE handoff** — when `review_required` or `authorization_required`, export `review_trigger` with `requested_scope` for SCOPE routing (simulated, python-import via `SCOPE_REPO_PATH`, or CLI via `SCOPE_CLI`) -## Review context (v0.4) +## Review context (v0.5) Prior review metadata in `context.metadata` is enforced: @@ -134,7 +135,7 @@ See [examples/weak_evidence_gate.md](examples/weak_evidence_gate.md) and [exampl ## Additional resources - Protocol overview: [docs/scientific_action_admissibility.md](../../docs/scientific_action_admissibility.md) -- v0.4 integration: [docs/integration_guide.md](../../docs/integration_guide.md) +- v0.5 integration: [docs/integration_guide.md](../../docs/integration_guide.md) - Threat model: [docs/threat_model.md](../../docs/threat_model.md) - SCOPE bridge: [docs/scope_bridge.md](../../docs/scope_bridge.md) - Integration: [docs/integration_guide.md](../../docs/integration_guide.md)