From 9d0a2cf7a4eee1e7e207a458f5cb0ca6d2d888fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Aug 2026 08:06:31 +0000 Subject: [PATCH 01/11] chore(manifest): refresh cognitive-state anchors --- .claude.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.claude.json b/.claude.json index ef1e95450..339c5e822 100644 --- a/.claude.json +++ b/.claude.json @@ -9,7 +9,7 @@ "provenance": { "generator": "scripts/build-cognitive-manifest.py", "repository": "Aegis-Omega/AEGIS-OMEGA", - "source_ref": "feat/uci-1-collective-work-contract-v1", + "source_ref": "feat/uci-4-effect-chain-integration-v1", "parent_state_hash": "e9f0ec153b0b320a1e791092f73209442ec43a982e503203d9c101ec40949cba", "signature_mode": "GITHUB_OIDC_ATTESTATION" }, @@ -488,5 +488,5 @@ "on_success": "broadcast-attested-verified-event-stream" } }, - "state_hash": "c81e62a3a824d299e6ee2a2c78a243e48ba08d69aca3730f3e0f3495792bd0a0" + "state_hash": "eb3fdc4454eb6d6464afa8e331fee22e9e9b1bee8815dd59798ba199a821c8df" } From d7f40b36bd42823b832652ca7d5fdaa19cac86c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:07:19 +0200 Subject: [PATCH 02/11] docs(uci): add UCI-4 effect-chain integration plan --- ...26-08-20-uci-4-effect-chain-integration.md | 354 ++++++++++++++++++ 1 file changed, 354 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-20-uci-4-effect-chain-integration.md diff --git a/docs/superpowers/plans/2026-08-20-uci-4-effect-chain-integration.md b/docs/superpowers/plans/2026-08-20-uci-4-effect-chain-integration.md new file mode 100644 index 000000000..bd56e1c00 --- /dev/null +++ b/docs/superpowers/plans/2026-08-20-uci-4-effect-chain-integration.md @@ -0,0 +1,354 @@ +# UCI-4 Effect-Bound Verification Chain Integration Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Reconcile the frozen PR #268 → #270 → #272 → #273 transition/effect-verification semantics onto the Universal Collective Intelligence integration spine without changing their epistemic meaning or promoting CompleteVerification into admission. + +**Architecture:** UCI-4 is a narrow transplant onto `#275@ebec2f9c8fa00f54605d859df61512108ff3b71d`. It adds the nominal TransitionIdentity/DecisionReceipt/ExecutionReceipt/EffectReceipt contracts, independent filesystem EffectEvidence, VerifyEffect, and CompleteVerification as verifier-only artifacts. It does not add AtomicAdmission, production admission, distributed linearizability, generic EffectReceipt production, or provider/model authority. + +**Tech Stack:** Python 3, JSON Schema Draft 2020-12, existing `harness/sdk` canonical hashing, pytest/unittest repository tests, GitHub Actions. + +**Spec:** `docs/superpowers/specs/2026-08-19-universal-collective-intelligence-kernel-v1-design.md` + +## Global Constraints + +- Exact integration parent is `#275@ebec2f9c8fa00f54605d859df61512108ff3b71d`. +- Frozen source semantics are taken from `#273@6407db1b0c4176f67a1d7ecbb16eca77d131d87e`, which contains the complete stacked #268/#270/#272/#273 lineage. +- `DecisionReceipt != ExecutionReceipt != EffectReceipt` both nominally and after serialization. +- Only `PERMIT` carries decision authority; `DEFER -> WAITING`; `DEFER -/-> EXECUTE`. +- `ExecutionReceipt -/-> EffectReceipt`. +- Effect evidence must come from an independently bound observation adapter. +- No generic EffectReceipt producer may exist. +- `CompleteVerificationResult=TRUE` is verifier output only; it is not AdmissionRecord, AtomicAdmission, EffectBoundAdmission, or production admission. +- Provider/model output remains evidence only, never authority. +- D3 remains explicit-operator-approval-bound; D4 remains denied absent separately admitted policy. +- Preserve separate serialized discriminators and hash domains for all nominal artifacts. +- Preserve the theorem: `forall r in AuthorizationDerivedArtifacts: r not in AcceptableEvidence(V_effect)`. +- Do not mutate `main`, #275, #264, #268, #270, #272, or #273. + +--- + +### Task 1: RED — UCI-4 Integration Contract + +**Files:** +- Create: `sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py` + +**Interfaces:** +- Consumes: the intended UCI-4 modules under `harness.sdk`. +- Produces: a focused integration contract proving that the successor spine exposes the frozen nominal types and boundaries. + +- [ ] **Step 1: Write the failing integration test** + +```python +from harness.sdk.transition_receipts import ( + DECISION_RECEIPT_KIND, + EXECUTION_RECEIPT_KIND, + EFFECT_RECEIPT_KIND, + DEFER, + WAITING, + decision_route, +) +from harness.sdk.effect_adapters import EffectWitness +from harness.sdk.effect_verifier import EffectVerificationResult +from harness.sdk.complete_verifier import CompleteVerificationResult + + +def test_uci4_nominal_effect_chain_surface_exists(): + assert DECISION_RECEIPT_KIND == "DECISION_RECEIPT_V1" + assert EXECUTION_RECEIPT_KIND == "EXECUTION_RECEIPT_V1" + assert EFFECT_RECEIPT_KIND == "EFFECT_RECEIPT_V1" + assert decision_route(DEFER) == WAITING + assert EffectWitness.__name__ == "EffectWitness" + assert EffectVerificationResult.__name__ == "EffectVerificationResult" + assert CompleteVerificationResult.__name__ == "CompleteVerificationResult" +``` + +- [ ] **Step 2: Run the focused test and require RED** + +Run: + +```bash +python -m pytest sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py -q +``` + +Expected: FAIL during import because the UCI-4 effect-chain modules do not exist on the #275 parent. + +- [ ] **Step 3: Preserve the RED commit** + +```bash +git add sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py +git commit -m "test(uci): preregister effect-chain integration boundary" +``` + +--- + +### Task 2: Transition Identity and Receipt Separation + +**Files:** +- Create: `harness/sdk/transition_receipts.py` +- Create: `schemas/transition-identity-envelope.v1.schema.json` +- Create: `schemas/decision-receipt.v1.schema.json` +- Create: `schemas/execution-receipt.v1.schema.json` +- Create: `schemas/effect-receipt.v1.schema.json` +- Modify: `harness/sdk/authority_client.py` +- Modify: `scripts/automaton3-authority.py` +- Test: `sovereign-omega-v2/python/tests/test_transition_receipts_pr1.py` +- Test: `sovereign-omega-v2/python/tests/test_transition_receipts_cli_pr1.py` + +**Interfaces:** +- Produces: `TransitionIdentity`, `DecisionReceipt`, `ExecutionReceipt`, schema-only/verifier-constructible `EffectReceipt`, `decision_route`, policy commitments, and canonical authority-client decision receipts. +- Preserves: compatibility-only Python MutationReceipt as `DECISION_DERIVED_NOT_EFFECT_PROOF`. + +- [ ] **Step 1: Transplant the exact tested PR-1/PR-3 receipt implementation from #273** + +Required constants and semantics include: + +```python +DECISION_RECEIPT_KIND = "DECISION_RECEIPT_V1" +EXECUTION_RECEIPT_KIND = "EXECUTION_RECEIPT_V1" +EFFECT_RECEIPT_KIND = "EFFECT_RECEIPT_V1" +PERMIT = "PERMIT" +DENY = "DENY" +DEFER = "DEFER" +WAITING = "WAITING" +``` + +`decision_route(DEFER)` must return `WAITING`, and `decision_execution_allowed(DEFER)` must be `False`. + +- [ ] **Step 2: Restore canonical authority-client/CLI emission of `transition_id` + `decision_receipt`** + +No caller-supplied post-state value may become EffectEvidence. Legacy MutationReceipt output remains compatibility-only and cannot satisfy `V_effect`. + +- [ ] **Step 3: Run the frozen PR-1 falsifiers** + +```bash +python -m pytest sovereign-omega-v2/python/tests/test_transition_receipts_pr1.py sovereign-omega-v2/python/tests/test_transition_receipts_cli_pr1.py -q +``` + +Expected: PASS. + +- [ ] **Step 4: Re-run Task 1 integration test** + +Expected: still FAIL because PR-2/3/4 modules are not present yet; receipt imports themselves must now succeed. + +- [ ] **Step 5: Commit** + +```bash +git add harness/sdk/transition_receipts.py harness/sdk/authority_client.py scripts/automaton3-authority.py schemas/*.schema.json sovereign-omega-v2/python/tests/test_transition_receipts_*.py +git commit -m "feat(uci): restore transition identity and receipt separation" +``` + +--- + +### Task 3: Independent Effect Observation + +**Files:** +- Create: `harness/sdk/effect_adapters.py` +- Create: `schemas/effect-witness.v1.schema.json` +- Test: `sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py` + +**Interfaces:** +- Consumes: `TransitionIdentity`, `ExecutionReceipt`. +- Produces: `EffectObservationHandle`, `EffectWitness`, `FilesystemEffectAdapter`, `is_adapter_bound_effect_evidence`. + +- [ ] **Step 1: Transplant the PR-2 reference adapter** + +The adapter must derive PRE and POST commitments from fresh filesystem observation and enforce allowed-root containment. The caller cannot supply the post-state commitment. + +- [ ] **Step 2: Run PR-2 falsifiers** + +```bash +python -m pytest sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py -q +``` + +Expected: PASS, including stale-prestate, target escape, symlink/identity, execution-binding, and caller-poststate rejection coverage present in the frozen suite. + +- [ ] **Step 3: Commit** + +```bash +git add harness/sdk/effect_adapters.py schemas/effect-witness.v1.schema.json sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py +git commit -m "feat(uci): add independent effect observation evidence" +``` + +--- + +### Task 4: VerifyEffect and Verifier-Gated EffectReceipt + +**Files:** +- Create: `harness/sdk/effect_verifier.py` +- Modify: `schemas/effect-receipt.v1.schema.json` +- Test: `sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py` + +**Interfaces:** +- Consumes: `TransitionIdentity`, `ExecutionReceipt`, `EffectWitness`. +- Produces: `EffectVerificationResult`; `EffectVerifier.issue_effect_receipt(...)` is the only valid EffectReceipt producer in this slice. + +- [ ] **Step 1: Transplant the PR-3 verifier** + +Required obligation order: + +```python +( + "V_effect_evidence", + "V_transition_binding", + "V_execution_binding", + "V_prestate_binding", + "V_adapter_binding", + "V_verifier_policy_binding", +) +``` + +- [ ] **Step 2: Preserve recomputation before receipt issuance** + +```python +recomputed = self.verify_effect( + transition=transition, + execution_receipt=execution_receipt, + witness=witness, +) +if recomputed.status != TRUE or recomputed.root != verification.root: + raise EffectVerificationError("EFFECT_VERIFICATION_RECOMPUTE_MISMATCH") +``` + +- [ ] **Step 3: Run PR-3 falsifiers** + +```bash +python -m pytest sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py -q +``` + +Expected: PASS 15/15 or the current frozen suite count if repository discovery records more tests; runner must report actual discovered count. + +- [ ] **Step 4: Commit** + +```bash +git add harness/sdk/effect_verifier.py schemas/effect-receipt.v1.schema.json sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py +git commit -m "feat(uci): verify effects before issuing effect receipts" +``` + +--- + +### Task 5: CompleteVerification Without Admission Promotion + +**Files:** +- Create: `harness/sdk/complete_verifier.py` +- Create: `schemas/complete-verification-result.v1.schema.json` +- Test: `sovereign-omega-v2/python/tests/test_complete_verifier_pr4.py` +- Test: `sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py` + +**Interfaces:** +- Consumes: exact nominal `TransitionIdentity`, `DecisionReceipt`, `ExecutionReceipt`, `EffectWitness`, `EffectVerificationResult`, `EffectReceipt`. +- Produces: `CompleteVerificationResult` with `TRUE | FALSE | UNKNOWN | ERROR | MISSING` and no mutation/admission method. + +- [ ] **Step 1: Transplant the PR-4 complete verifier** + +Required full binding includes transition, execution instance, witness digest, pre/post state, observation provenance, adapter identity/version, effect-verification root, active verifier policy, and admission-policy commitment. + +- [ ] **Step 2: Preserve fail-closed decision semantics** + +`DecisionReceipt.decision_outcome != PERMIT` must force non-TRUE CompleteVerification. + +- [ ] **Step 3: Run primary and receipt-binding falsifiers** + +```bash +python -m pytest sovereign-omega-v2/python/tests/test_complete_verifier_pr4.py sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py -q +``` + +Expected: PASS, including the four adversarial receipt-binding cases that previously demonstrated forged post-state/provenance/adapter metadata bypasses before the PR-4 fix. + +- [ ] **Step 4: Run the Task 1 UCI-4 integration test** + +```bash +python -m pytest sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py -q +``` + +Expected: PASS. + +- [ ] **Step 5: Commit** + +```bash +git add harness/sdk/complete_verifier.py schemas/complete-verification-result.v1.schema.json sovereign-omega-v2/python/tests/test_complete_verifier_pr4*.py sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py +git commit -m "feat(uci): add complete verification over bound effect lineage" +``` + +--- + +### Task 6: Exact-Head UCI-4 Contract Gate + +**Files:** +- Create: `scripts/run-uci4-effect-chain-tests.py` +- Create: `.github/workflows/uci-4-effect-chain-contract.yml` + +**Interfaces:** +- Produces: one exact-head CI gate over all UCI-4 frozen falsification suites plus schema validation. + +- [ ] **Step 1: Add a runner that executes discovered tests and fails closed on nonzero exit** + +The runner must execute: + +```text +test_transition_receipts_pr1.py +test_transition_receipts_cli_pr1.py +test_effect_adapters_pr2.py +test_effect_verifier_pr3.py +test_complete_verifier_pr4.py +test_complete_verifier_pr4_receipt_binding.py +test_uci4_effect_chain_integration.py +``` + +It must never print a fabricated expected-pass count; report the actual pytest-discovered result. + +- [ ] **Step 2: Add a pull-request workflow** + +The workflow must checkout the exact PR head, install only required Python/schema dependencies, run the UCI-4 runner, validate all six UCI-4 JSON schemas, and upload a small evidence artifact containing exact head SHA and test summary. + +- [ ] **Step 3: Commit** + +```bash +git add scripts/run-uci4-effect-chain-tests.py .github/workflows/uci-4-effect-chain-contract.yml +git commit -m "ci(uci): add exact-head effect-chain contract gate" +``` + +--- + +### Task 7: Lineage Ledger and Stacked PR + +**Files:** +- Create: `docs/audits/2026-08-20-uci4-effect-chain-lineage-ledger.md` +- Modify: no frozen research/property scope documents. + +**Interfaces:** +- Produces: repository-bound provenance from #275 exact parent and frozen #268→#273 source lineage. + +- [ ] **Step 1: Record exact lineage** + +The ledger must record: + +```text +UCI4_PARENT = ebec2f9c8fa00f54605d859df61512108ff3b71d +SOURCE_EFFECT_LINEAGE_TIP = 6407db1b0c4176f67a1d7ecbb16eca77d131d87e +SOURCE_PRS = #268 -> #270 -> #272 -> #273 +``` + +- [ ] **Step 2: Record explicit non-claims** + +```text +ATOMIC_ADMISSION = NOT_IMPLEMENTED +EFFECT_BOUND_ADMISSION = UNAVAILABLE +PRODUCTION_ADMISSION = NOT_ESTABLISHED +DISTRIBUTED_LINEARIZABILITY = NOT_ESTABLISHED +AGI = NOT_ESTABLISHED +``` + +- [ ] **Step 3: Open a draft stacked PR against `feat/uci-1-collective-work-contract-v1`** + +The PR must bind the exact parent/head, report only executed evidence, and remain draft until the dedicated UCI-4 gate and inherited constitutional gates pass on the exact head. + +--- + +## Self-Review + +- Spec coverage: UCI-4 receipt/effect chain is fully covered; AtomicAdmission is deliberately excluded for UCI-5. +- Placeholder scan: no TBD/TODO implementation steps remain. +- Type consistency: the plan uses the exact nominal type names from the frozen #273 lineage. +- Scope check: no #264 provider-organism, #267 memory, #274 domain-capability, or production bridge code is imported in UCI-4. +- Epistemic check: CompleteVerification remains verifier-only and cannot create or imply AdmissionRecord. From ebccd44815e1e9c1050e023e78cc11d6fb168cb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:07:46 +0200 Subject: [PATCH 03/11] test(uci): preregister effect-chain integration boundary --- .../test_uci4_effect_chain_integration.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py diff --git a/sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py b/sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py new file mode 100644 index 000000000..4032e54c5 --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py @@ -0,0 +1,30 @@ +"""UCI-4 integration boundary over the frozen effect-verification proofline. + +This test is intentionally introduced before the UCI-4 implementation. On the +#275 parent it must fail during import because the effect-chain SDK modules are +not present. That RED witness prevents a green-by-construction transplant. +""" + +from harness.sdk.transition_receipts import ( + DECISION_RECEIPT_KIND, + EFFECT_RECEIPT_KIND, + EXECUTION_RECEIPT_KIND, + DEFER, + WAITING, + decision_execution_allowed, + decision_route, +) +from harness.sdk.effect_adapters import EffectWitness +from harness.sdk.effect_verifier import EffectVerificationResult +from harness.sdk.complete_verifier import CompleteVerificationResult + + +def test_uci4_nominal_effect_chain_surface_exists() -> None: + assert DECISION_RECEIPT_KIND == "DECISION_RECEIPT_V1" + assert EXECUTION_RECEIPT_KIND == "EXECUTION_RECEIPT_V1" + assert EFFECT_RECEIPT_KIND == "EFFECT_RECEIPT_V1" + assert decision_route(DEFER) == WAITING + assert decision_execution_allowed(DEFER) is False + assert EffectWitness.__name__ == "EffectWitness" + assert EffectVerificationResult.__name__ == "EffectVerificationResult" + assert CompleteVerificationResult.__name__ == "CompleteVerificationResult" From 13a8ffbc907fc91751f84092b7befbbad47dd0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:08:00 +0200 Subject: [PATCH 04/11] ci(uci): add UCI-4 red contract harness --- .../workflows/uci-4-effect-chain-contract.yml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 .github/workflows/uci-4-effect-chain-contract.yml diff --git a/.github/workflows/uci-4-effect-chain-contract.yml b/.github/workflows/uci-4-effect-chain-contract.yml new file mode 100644 index 000000000..630272032 --- /dev/null +++ b/.github/workflows/uci-4-effect-chain-contract.yml @@ -0,0 +1,44 @@ +name: UCI-4 Effect Chain Contract + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + uci4-effect-chain: + name: uci4 / effect-chain + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} + EXPECTED_PARENT_SHA: ${{ github.event.pull_request.base.sha || '' }} + steps: + - name: Checkout exact candidate + uses: actions/checkout@v4 + with: + ref: ${{ env.CANDIDATE_SHA }} + fetch-depth: 0 + + - name: Record exact lineage + shell: bash + run: | + set -euo pipefail + echo "CANDIDATE_SHA=$(git rev-parse HEAD)" + if [[ -n "$EXPECTED_PARENT_SHA" ]]; then + test "$(git merge-base "$EXPECTED_PARENT_SHA" HEAD)" = "$EXPECTED_PARENT_SHA" + echo "EXPECTED_PARENT_SHA=$EXPECTED_PARENT_SHA" + fi + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install test runtime + run: python -m pip install pytest==8.3.5 + + - name: Run preregistered UCI-4 boundary + run: python -m pytest sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py -q From 1cdd2c6331fcd2c18d75baf2c60b914dd30f4548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:16:06 +0200 Subject: [PATCH 05/11] feat(uci): integrate frozen effect verification proofline --- harness/sdk/authority_client.py | 24 ++ harness/sdk/complete_verifier.py | 399 ++++++++++++++++++ harness/sdk/effect_adapters.py | 325 ++++++++++++++ harness/sdk/effect_verifier.py | 289 +++++++++++++ harness/sdk/transition_receipts.py | 373 ++++++++++++++++ ...omplete-verification-result.v1.schema.json | 70 +++ schemas/decision-receipt.v1.schema.json | 14 + schemas/effect-receipt.v1.schema.json | 34 ++ schemas/effect-witness.v1.schema.json | 33 ++ schemas/execution-receipt.v1.schema.json | 15 + ...ransition-identity-envelope.v1.schema.json | 33 ++ scripts/automaton3-authority.py | 31 +- .../tests/test_complete_verifier_pr4.py | 326 ++++++++++++++ ...t_complete_verifier_pr4_receipt_binding.py | 142 +++++++ .../python/tests/test_effect_adapters_pr2.py | 291 +++++++++++++ .../python/tests/test_effect_verifier_pr3.py | 216 ++++++++++ .../tests/test_transition_receipts_cli_pr1.py | 116 +++++ .../tests/test_transition_receipts_pr1.py | 281 ++++++++++++ 18 files changed, 3010 insertions(+), 2 deletions(-) create mode 100644 harness/sdk/complete_verifier.py create mode 100644 harness/sdk/effect_adapters.py create mode 100644 harness/sdk/effect_verifier.py create mode 100644 harness/sdk/transition_receipts.py create mode 100644 schemas/complete-verification-result.v1.schema.json create mode 100644 schemas/decision-receipt.v1.schema.json create mode 100644 schemas/effect-receipt.v1.schema.json create mode 100644 schemas/effect-witness.v1.schema.json create mode 100644 schemas/execution-receipt.v1.schema.json create mode 100644 schemas/transition-identity-envelope.v1.schema.json mode change 100644 => 100755 scripts/automaton3-authority.py create mode 100755 sovereign-omega-v2/python/tests/test_complete_verifier_pr4.py create mode 100755 sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py create mode 100755 sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py create mode 100755 sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py create mode 100755 sovereign-omega-v2/python/tests/test_transition_receipts_cli_pr1.py create mode 100755 sovereign-omega-v2/python/tests/test_transition_receipts_pr1.py diff --git a/harness/sdk/authority_client.py b/harness/sdk/authority_client.py index 60dd74315..a0d2d1833 100644 --- a/harness/sdk/authority_client.py +++ b/harness/sdk/authority_client.py @@ -13,8 +13,13 @@ load_capability_registry, load_policy, make_mutation_receipt, verify_workspace, ) +from harness.sdk.transition_receipts import ( + build_transition_identity, + decision_receipt_from_policy, +) REPO_ROOT = Path(__file__).resolve().parents[2] +LEGACY_RECEIPT_SEMANTICS = "DECISION_DERIVED_NOT_EFFECT_PROOF" def _denial(code: str, detail: str = "") -> dict[str, Any]: @@ -82,6 +87,21 @@ def authorize_from_environment(*, action_class: str, authority_domain: str, requ idempotency_key=idempotency_key, compensation_reference=compensation_reference, ) decision = AuthorityEvaluator(policy=policy, registry=registry, repository_root=REPO_ROOT).evaluate(request, approval=approval) + transition = build_transition_identity( + source_commit=identity.source_commit, + pre_state_commitment=identity.expected_pre_state, + identity_root=identity_root, + approval=approval, + requested_capability=requested_capability, + registry_root=registry_root, + action_digest=action_digest, + deterministic_nonce=identity.deterministic_nonce, + fence_token=os.environ.get("AEGIS_FENCING_TOKEN"), + ) + decision_receipt = decision_receipt_from_policy(transition=transition, decision=decision) + + # Compatibility-only V1 artifact. Its SUCCEEDED value is decision-derived and + # must never be interpreted as execution success, effect success, or admission. receipt = make_mutation_receipt( identity_root=identity_root, workspace_binding=identity.workspace_binding, decision=decision, pre_state_digest=identity.expected_pre_state, @@ -93,7 +113,11 @@ def authorize_from_environment(*, action_class: str, authority_domain: str, requ "authority_score": decision.authority_score, "denial_codes": list(decision.denial_codes), "decision_root": decision.decision_root, + "transition_id": transition.root, + "decision_receipt": asdict(decision_receipt), + "decision_receipt_root": decision_receipt.root, "receipt_root": receipt.root, + "legacy_receipt_semantics": LEGACY_RECEIPT_SEMANTICS, "execution_identity_root": identity_root, "workspace_binding": identity.workspace_binding, "observation": asdict(workspace.observation), diff --git a/harness/sdk/complete_verifier.py b/harness/sdk/complete_verifier.py new file mode 100644 index 000000000..2d30bcd18 --- /dev/null +++ b/harness/sdk/complete_verifier.py @@ -0,0 +1,399 @@ +"""PR-4 CompleteVerification gate over the exact PR-3 effect lineage. + +CompleteVerification is a verifier artifact for one exact transition bundle. It is +not CausalClaimAdmission, AtomicAdmission, EffectBoundAdmission, production +admission, or an AGI claim. +""" +from __future__ import annotations + +import re +from dataclasses import asdict, dataclass +from typing import Any + +from harness.sdk.effect_adapters import EffectWitness, is_adapter_bound_effect_evidence +from harness.sdk.effect_verifier import EffectVerificationResult, EffectVerifier +from harness.sdk.sovereign_execution import ZERO_HASH, canonical_hash +from harness.sdk.transition_receipts import ( + PERMIT, + DecisionReceipt, + EffectReceipt, + ExecutionReceipt, + TransitionIdentity, + admission_policy_commitment, + verifier_policy_commitment, +) + +TRUE = "TRUE" +FALSE = "FALSE" +UNKNOWN = "UNKNOWN" +ERROR = "ERROR" +MISSING = "MISSING" +VERIFY_STATUSES = (TRUE, FALSE, UNKNOWN, ERROR, MISSING) +COMPLETE_VERIFICATION_RESULT_KIND = "COMPLETE_VERIFICATION_RESULT_V1" +SHA256_RE = re.compile(r"^[0-9a-f]{64}$") + +OBLIGATION_ORDER = ( + "V_transition_identity", + "V_decision_receipt", + "V_decision_authority", + "V_decision_binding", + "V_execution_receipt", + "V_execution_binding", + "V_effect_evidence", + "V_effect_verification", + "V_effect_receipt", + "V_effect_binding", + "V_effect_verification_binding", + "V_verifier_policy_binding", + "V_admission_policy_binding", +) + +PR4_COMPLETE_VERIFIER_POLICY = { + "policy_id": "AEGIS_PR4_COMPLETE_VERIFIER_POLICY_V1", + "safe_incompleteness": True, + "required_inputs": [ + "TransitionIdentity", + "DecisionReceipt", + "ExecutionReceipt", + "EffectWitness", + "EffectVerificationResult", + "EffectReceipt", + ], + "required_obligations": list(OBLIGATION_ORDER), + "effect_verification_recompute": "REQUIRED", + "effect_receipt_source": "PR3_VERIFIER_GATED_ONLY", + "causal_claim_admission": "NOT_IMPLEMENTED", + "atomic_admission": "UNAVAILABLE", + "effect_bound_admission": "UNAVAILABLE", + "production_admission": "NOT_ESTABLISHED", +} + + +class CompleteVerificationError(ValueError): + """Raised only when a CompleteVerification result cannot be represented safely.""" + + +def complete_verifier_policy_commitment() -> str: + return canonical_hash( + "AEGIS_COMPLETE_VERIFIER_POLICY_COMMITMENT_V1", + PR4_COMPLETE_VERIFIER_POLICY, + ) + + +def _require_hash(name: str, value: str) -> None: + if not isinstance(value, str) or not SHA256_RE.fullmatch(value): + raise CompleteVerificationError(f"{name}:INVALID_SHA256") + + +@dataclass(frozen=True) +class CompleteVerificationResult: + result_kind: str + status: str + transition_id: str + decision_receipt_root: str + execution_receipt_root: str + effect_witness_digest: str + effect_verification_root: str + effect_receipt_root: str + complete_verifier_policy_commitment: str + obligations: tuple[tuple[str, str], ...] + denial_code: str + + def validate(self) -> None: + if self.result_kind != COMPLETE_VERIFICATION_RESULT_KIND: + raise CompleteVerificationError("COMPLETE_VERIFICATION_RESULT_KIND_MISMATCH") + if self.status not in VERIFY_STATUSES: + raise CompleteVerificationError("COMPLETE_VERIFICATION_STATUS_INVALID") + for name in ( + "transition_id", + "decision_receipt_root", + "execution_receipt_root", + "effect_witness_digest", + "effect_verification_root", + "effect_receipt_root", + "complete_verifier_policy_commitment", + ): + _require_hash(name, getattr(self, name)) + if tuple(name for name, _ in self.obligations) != OBLIGATION_ORDER: + raise CompleteVerificationError("COMPLETE_VERIFICATION_OBLIGATION_SET_INVALID") + if any(status not in VERIFY_STATUSES for _, status in self.obligations): + raise CompleteVerificationError("COMPLETE_VERIFICATION_OBLIGATION_STATUS_INVALID") + if not isinstance(self.denial_code, str) or not self.denial_code: + raise CompleteVerificationError("COMPLETE_VERIFICATION_DENIAL_CODE_INVALID") + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_COMPLETE_VERIFICATION_RESULT_V1", asdict(self)) + + +class CompleteVerifier: + """Fail-closed PR-4 verifier for one exact decision/execution/effect bundle.""" + + @staticmethod + def _obligations(default: str = MISSING) -> dict[str, str]: + return {name: default for name in OBLIGATION_ORDER} + + @staticmethod + def _safe_root(value: Any, expected_type: type[Any]) -> str: + if type(value) is not expected_type: + return ZERO_HASH + try: + value.validate() + return value.root + except Exception: + return ZERO_HASH + + @staticmethod + def _result( + *, + status: str, + transition: Any, + decision_receipt: Any, + execution_receipt: Any, + effect_witness: Any, + effect_verification: Any, + effect_receipt: Any, + obligations: dict[str, str], + denial_code: str, + ) -> CompleteVerificationResult: + result = CompleteVerificationResult( + result_kind=COMPLETE_VERIFICATION_RESULT_KIND, + status=status, + transition_id=CompleteVerifier._safe_root(transition, TransitionIdentity), + decision_receipt_root=CompleteVerifier._safe_root(decision_receipt, DecisionReceipt), + execution_receipt_root=CompleteVerifier._safe_root(execution_receipt, ExecutionReceipt), + effect_witness_digest=CompleteVerifier._safe_root(effect_witness, EffectWitness), + effect_verification_root=CompleteVerifier._safe_root(effect_verification, EffectVerificationResult), + effect_receipt_root=CompleteVerifier._safe_root(effect_receipt, EffectReceipt), + complete_verifier_policy_commitment=complete_verifier_policy_commitment(), + obligations=tuple((name, obligations[name]) for name in OBLIGATION_ORDER), + denial_code=denial_code, + ) + result.validate() + return result + + @staticmethod + def _status_and_code(obligations: dict[str, str]) -> tuple[str, str]: + if all(obligations[name] == TRUE for name in OBLIGATION_ORDER): + return TRUE, "NONE" + if obligations["V_decision_authority"] == FALSE: + return FALSE, "COMPLETE_VERIFICATION_DECISION_NOT_PERMIT" + if obligations["V_decision_binding"] == FALSE: + return FALSE, "COMPLETE_VERIFICATION_TRANSITION_MISMATCH" + if obligations["V_execution_binding"] == FALSE: + return FALSE, "COMPLETE_VERIFICATION_TRANSITION_MISMATCH" + if obligations["V_effect_evidence"] == FALSE: + return FALSE, "COMPLETE_VERIFICATION_EFFECT_EVIDENCE_MISMATCH" + if obligations["V_effect_binding"] == FALSE: + return FALSE, "COMPLETE_VERIFICATION_EFFECT_RECEIPT_MISMATCH" + if obligations["V_effect_verification_binding"] == FALSE: + return FALSE, "COMPLETE_VERIFICATION_EFFECT_VERIFICATION_MISMATCH" + if obligations["V_verifier_policy_binding"] == FALSE or obligations["V_admission_policy_binding"] == FALSE: + return FALSE, "COMPLETE_VERIFICATION_POLICY_MISMATCH" + if any(obligations[name] == FALSE for name in OBLIGATION_ORDER): + return FALSE, "COMPLETE_VERIFICATION_CONTRADICTED" + if any(obligations[name] == ERROR for name in OBLIGATION_ORDER): + return ERROR, "COMPLETE_VERIFICATION_INTERNAL_ERROR" + if any(obligations[name] == MISSING for name in OBLIGATION_ORDER): + return MISSING, "COMPLETE_VERIFICATION_MISSING_ARTIFACT" + return UNKNOWN, "COMPLETE_VERIFICATION_UNRESOLVABLE" + + def verify_complete( + self, + *, + transition: TransitionIdentity, + decision_receipt: DecisionReceipt | None, + execution_receipt: ExecutionReceipt | None, + effect_witness: EffectWitness | None, + effect_verification: EffectVerificationResult | None, + effect_receipt: EffectReceipt | None, + ) -> CompleteVerificationResult: + obligations = self._obligations() + inputs = ( + transition, + decision_receipt, + execution_receipt, + effect_witness, + effect_verification, + effect_receipt, + ) + if any(value is None for value in inputs): + return self._result( + status=MISSING, + transition=transition, + decision_receipt=decision_receipt, + execution_receipt=execution_receipt, + effect_witness=effect_witness, + effect_verification=effect_verification, + effect_receipt=effect_receipt, + obligations=obligations, + denial_code="COMPLETE_VERIFICATION_MISSING_ARTIFACT", + ) + + expected_types = ( + TransitionIdentity, + DecisionReceipt, + ExecutionReceipt, + EffectWitness, + EffectVerificationResult, + EffectReceipt, + ) + if any(type(value) is not expected for value, expected in zip(inputs, expected_types)): + obligations = self._obligations(FALSE) + return self._result( + status=FALSE, + transition=transition, + decision_receipt=decision_receipt, + execution_receipt=execution_receipt, + effect_witness=effect_witness, + effect_verification=effect_verification, + effect_receipt=effect_receipt, + obligations=obligations, + denial_code="COMPLETE_VERIFICATION_INPUT_ERROR", + ) + + assert decision_receipt is not None + assert execution_receipt is not None + assert effect_witness is not None + assert effect_verification is not None + assert effect_receipt is not None + + try: + transition.validate() + transition_id = transition.root + obligations["V_transition_identity"] = TRUE + except Exception: + obligations["V_transition_identity"] = FALSE + return self._result( + status=FALSE, + transition=transition, + decision_receipt=decision_receipt, + execution_receipt=execution_receipt, + effect_witness=effect_witness, + effect_verification=effect_verification, + effect_receipt=effect_receipt, + obligations=obligations, + denial_code="COMPLETE_VERIFICATION_INPUT_ERROR", + ) + + try: + decision_receipt.validate() + decision_receipt.root + obligations["V_decision_receipt"] = TRUE + except Exception: + obligations["V_decision_receipt"] = FALSE + + obligations["V_decision_authority"] = TRUE if decision_receipt.decision_outcome == PERMIT else FALSE + obligations["V_decision_binding"] = TRUE if decision_receipt.transition_id == transition_id else FALSE + + try: + execution_receipt.validate() + execution_receipt.root + obligations["V_execution_receipt"] = TRUE + except Exception: + obligations["V_execution_receipt"] = FALSE + obligations["V_execution_binding"] = TRUE if execution_receipt.transition_id == transition_id else FALSE + + try: + effect_witness.validate() + witness_adapter_bound = is_adapter_bound_effect_evidence(witness=effect_witness) + witness_bound = ( + effect_witness.transition_id == transition_id + and effect_witness.execution_instance_id == execution_receipt.execution_instance_id + and effect_witness.observed_pre_state_commitment == transition.pre_state_commitment + ) + obligations["V_effect_evidence"] = TRUE if witness_adapter_bound and witness_bound else FALSE + except Exception: + obligations["V_effect_evidence"] = FALSE + + try: + effect_verification.validate() + obligations["V_effect_verification"] = effect_verification.status + except Exception: + obligations["V_effect_verification"] = FALSE + + try: + effect_receipt.validate() + effect_receipt.root + obligations["V_effect_receipt"] = TRUE + except Exception: + obligations["V_effect_receipt"] = FALSE + + try: + expected_observation_provenance = canonical_hash( + "AEGIS_EFFECT_OBSERVATION_BUNDLE_V1", + { + "pre": effect_witness.pre_observation_provenance, + "post": effect_witness.post_observation_provenance, + }, + ) + effect_binding = ( + effect_receipt.transition_id == transition_id + and effect_receipt.execution_instance_id == execution_receipt.execution_instance_id + and effect_receipt.effect_witness_digest == effect_witness.root + and effect_receipt.pre_state_commitment == transition.pre_state_commitment + and effect_receipt.post_state_commitment == effect_witness.observed_post_state_commitment + and effect_receipt.observation_provenance == expected_observation_provenance + and effect_receipt.adapter_identity == effect_witness.adapter_identity + and effect_receipt.adapter_version == effect_witness.adapter_version + ) + obligations["V_effect_binding"] = TRUE if effect_binding else FALSE + except Exception: + obligations["V_effect_binding"] = FALSE + + recomputed = None + try: + recomputed = EffectVerifier().verify_effect( + transition=transition, + execution_receipt=execution_receipt, + witness=effect_witness, + ) + if recomputed.status == TRUE: + verification_binding = ( + effect_verification.status == TRUE + and recomputed.root == effect_verification.root + and effect_receipt.effect_verification_root == effect_verification.root + ) + obligations["V_effect_verification_binding"] = TRUE if verification_binding else FALSE + elif recomputed.status in VERIFY_STATUSES: + obligations["V_effect_verification_binding"] = recomputed.status + else: + obligations["V_effect_verification_binding"] = ERROR + except Exception: + obligations["V_effect_verification_binding"] = ERROR + + try: + active_effect_policy = verifier_policy_commitment() + policy_bound = ( + transition.verifier_policy_commitment == active_effect_policy + and effect_verification.verifier_policy_commitment == active_effect_policy + and effect_receipt.verifier_policy_commitment == active_effect_policy + and recomputed is not None + and recomputed.verifier_policy_commitment == active_effect_policy + ) + obligations["V_verifier_policy_binding"] = TRUE if policy_bound else FALSE + except Exception: + obligations["V_verifier_policy_binding"] = ERROR + + try: + obligations["V_admission_policy_binding"] = ( + TRUE + if transition.admission_policy_commitment == admission_policy_commitment() + else FALSE + ) + except Exception: + obligations["V_admission_policy_binding"] = ERROR + + status, denial_code = self._status_and_code(obligations) + return self._result( + status=status, + transition=transition, + decision_receipt=decision_receipt, + execution_receipt=execution_receipt, + effect_witness=effect_witness, + effect_verification=effect_verification, + effect_receipt=effect_receipt, + obligations=obligations, + denial_code=denial_code, + ) diff --git a/harness/sdk/effect_adapters.py b/harness/sdk/effect_adapters.py new file mode 100644 index 000000000..68eaa1ce0 --- /dev/null +++ b/harness/sdk/effect_adapters.py @@ -0,0 +1,325 @@ +"""PR-2 provider-neutral effect observation boundary with a filesystem reference adapter. + +EPISTEMIC STATUS: REFERENCE_EFFECT_OBSERVATION_ONLY + +This module can produce adapter-bound EffectEvidence candidates (`EffectWitness`) +from independent pre/post observations. It deliberately does not implement +VerifyEffect, EffectReceipt production, VerifyTransition, atomic admission, or +EffectBoundAdmission. +""" +from __future__ import annotations + +import hashlib +import os +import re +from dataclasses import asdict, dataclass +from pathlib import Path +from typing import Any + +from harness.sdk.sovereign_execution import ZERO_HASH, canonical_hash +from harness.sdk.transition_receipts import ExecutionReceipt, TransitionIdentity + +EFFECT_WITNESS_KIND = "EFFECT_WITNESS_V1" +VERIFY_EFFECT_STATUS = "does not implement VerifyEffect" +SHA256_RE = re.compile(r"^[0-9a-f]{64}$") + + +class EffectAdapterError(ValueError): + """Raised when independent effect observation cannot be established safely.""" + + +def _require_hash(name: str, value: str) -> None: + if not isinstance(value, str) or not SHA256_RE.fullmatch(value): + raise EffectAdapterError(f"{name}:INVALID_SHA256") + + +def _require_text(name: str, value: str) -> None: + if not isinstance(value, str) or not value: + raise EffectAdapterError(f"{name}:INVALID_VALUE") + + +@dataclass(frozen=True) +class EffectObservationHandle: + transition_id: str + target_identity: str + observed_pre_state_commitment: str + pre_observation_provenance: str + adapter_identity: str + adapter_version: str + observation_id: str + + def validate(self) -> None: + for name in ( + "transition_id", + "observed_pre_state_commitment", + "pre_observation_provenance", + "observation_id", + ): + _require_hash(name, getattr(self, name)) + for name in ("target_identity", "adapter_identity", "adapter_version"): + _require_text(name, getattr(self, name)) + + +@dataclass(frozen=True) +class EffectWitness: + """EffectEvidence candidate produced by an independent observation path.""" + + witness_kind: str + transition_id: str + execution_instance_id: str + target_identity: str + observed_pre_state_commitment: str + observed_post_state_commitment: str + effect_changed: bool + pre_observation_provenance: str + post_observation_provenance: str + adapter_identity: str + adapter_version: str + + def validate(self) -> None: + if self.witness_kind != EFFECT_WITNESS_KIND: + raise EffectAdapterError("EFFECT_WITNESS_KIND_MISMATCH") + for name in ( + "transition_id", + "observed_pre_state_commitment", + "observed_post_state_commitment", + "pre_observation_provenance", + "post_observation_provenance", + ): + _require_hash(name, getattr(self, name)) + for name in ( + "execution_instance_id", + "target_identity", + "adapter_identity", + "adapter_version", + ): + _require_text(name, getattr(self, name)) + if not isinstance(self.effect_changed, bool): + raise EffectAdapterError("effect_changed:INVALID_BOOLEAN") + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_EFFECT_WITNESS_V1", asdict(self)) + + +@dataclass(frozen=True) +class FilesystemStateObservation: + target_identity: str + exists: bool + content_sha256: str + size_bytes: int + device: int + inode: int + mtime_ns: int + + +class FilesystemEffectAdapter: + """Reference adapter deriving EffectEvidence from fresh filesystem observations.""" + + identity = "aegis.filesystem-effect-adapter" + version = "1.0.0" + + def __init__(self, *, allowed_root: Path): + self.allowed_root = Path(allowed_root).resolve(strict=False) + + def _resolve_target(self, target: Path) -> tuple[Path, str]: + root = self.allowed_root.resolve(strict=False) + resolved_target = Path(target).resolve(strict=False) + if resolved_target != root and root not in resolved_target.parents: + raise EffectAdapterError("EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT") + return resolved_target, resolved_target.relative_to(root).as_posix() + + def _observe_state(self, target: Path) -> FilesystemStateObservation: + resolved_target, target_identity = self._resolve_target(target) + if not resolved_target.exists(): + return FilesystemStateObservation( + target_identity=target_identity, + exists=False, + content_sha256=ZERO_HASH, + size_bytes=0, + device=0, + inode=0, + mtime_ns=0, + ) + if not resolved_target.is_file(): + raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") + try: + with resolved_target.open("rb") as stream: + content = stream.read() + stat = os.fstat(stream.fileno()) + except FileNotFoundError: + return FilesystemStateObservation( + target_identity=target_identity, + exists=False, + content_sha256=ZERO_HASH, + size_bytes=0, + device=0, + inode=0, + mtime_ns=0, + ) + except (IsADirectoryError, PermissionError) as exc: + raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") from exc + return FilesystemStateObservation( + target_identity=target_identity, + exists=True, + content_sha256=hashlib.sha256(content).hexdigest(), + size_bytes=len(content), + device=int(stat.st_dev), + inode=int(stat.st_ino), + mtime_ns=int(stat.st_mtime_ns), + ) + + @staticmethod + def _state_commitment(observation: FilesystemStateObservation) -> str: + return canonical_hash( + "AEGIS_FILESYSTEM_EFFECT_STATE_V1", + { + "target_identity": observation.target_identity, + "exists": observation.exists, + "content_sha256": observation.content_sha256, + "size_bytes": observation.size_bytes, + }, + ) + + def _observation_id(self, *, transition_id: str, target_identity: str, pre_state_commitment: str) -> str: + return canonical_hash( + "AEGIS_EFFECT_OBSERVATION_HANDLE_V1", + { + "transition_id": transition_id, + "target_identity": target_identity, + "pre_state_commitment": pre_state_commitment, + "adapter_identity": self.identity, + "adapter_version": self.version, + }, + ) + + def _observation_provenance( + self, + *, + transition_id: str, + phase: str, + observation: FilesystemStateObservation, + state_commitment: str, + observation_id: str, + execution_instance_id: str | None = None, + ) -> str: + value: dict[str, Any] = { + "transition_id": transition_id, + "phase": phase, + "target_identity": observation.target_identity, + "state_commitment": state_commitment, + "content_sha256": observation.content_sha256, + "size_bytes": observation.size_bytes, + "filesystem_device": observation.device, + "filesystem_inode": observation.inode, + "filesystem_mtime_ns": observation.mtime_ns, + "adapter_identity": self.identity, + "adapter_version": self.version, + "observation_id": observation_id, + } + if execution_instance_id is not None: + value["execution_instance_id"] = execution_instance_id + return canonical_hash("AEGIS_EFFECT_OBSERVATION_PROVENANCE_V1", value) + + def prepare_observation(self, *, transition: TransitionIdentity, target: Path) -> EffectObservationHandle: + transition_id = transition.root + observation = self._observe_state(Path(target)) + pre_commitment = self._state_commitment(observation) + if pre_commitment != transition.pre_state_commitment: + raise EffectAdapterError("EFFECT_PRE_STATE_COMMITMENT_MISMATCH") + observation_id = self._observation_id( + transition_id=transition_id, + target_identity=observation.target_identity, + pre_state_commitment=pre_commitment, + ) + provenance = self._observation_provenance( + transition_id=transition_id, + phase="PRE", + observation=observation, + state_commitment=pre_commitment, + observation_id=observation_id, + ) + result = EffectObservationHandle( + transition_id=transition_id, + target_identity=observation.target_identity, + observed_pre_state_commitment=pre_commitment, + pre_observation_provenance=provenance, + adapter_identity=self.identity, + adapter_version=self.version, + observation_id=observation_id, + ) + result.validate() + return result + + def observe_effect( + self, + *, + transition: TransitionIdentity, + handle: EffectObservationHandle, + execution_receipt: ExecutionReceipt, + ) -> EffectWitness: + transition_id = transition.root + handle.validate() + execution_receipt.validate() + if handle.transition_id != transition_id: + raise EffectAdapterError("EFFECT_TRANSITION_BINDING_MISMATCH") + if execution_receipt.transition_id != transition_id: + raise EffectAdapterError("EFFECT_EXECUTION_TRANSITION_MISMATCH") + if handle.adapter_identity != self.identity or handle.adapter_version != self.version: + raise EffectAdapterError("EFFECT_ADAPTER_BINDING_MISMATCH") + if handle.observed_pre_state_commitment != transition.pre_state_commitment: + raise EffectAdapterError("EFFECT_PRE_STATE_COMMITMENT_MISMATCH") + expected_observation_id = self._observation_id( + transition_id=transition_id, + target_identity=handle.target_identity, + pre_state_commitment=handle.observed_pre_state_commitment, + ) + if handle.observation_id != expected_observation_id: + raise EffectAdapterError("EFFECT_OBSERVATION_HANDLE_MISMATCH") + post_target = self.allowed_root / handle.target_identity + observation = self._observe_state(post_target) + if observation.target_identity != handle.target_identity: + raise EffectAdapterError("EFFECT_OBSERVATION_HANDLE_MISMATCH") + post_commitment = self._state_commitment(observation) + post_provenance = self._observation_provenance( + transition_id=transition_id, + phase="POST", + observation=observation, + state_commitment=post_commitment, + observation_id=handle.observation_id, + execution_instance_id=execution_receipt.execution_instance_id, + ) + witness = EffectWitness( + witness_kind=EFFECT_WITNESS_KIND, + transition_id=transition_id, + execution_instance_id=execution_receipt.execution_instance_id, + target_identity=handle.target_identity, + observed_pre_state_commitment=handle.observed_pre_state_commitment, + observed_post_state_commitment=post_commitment, + effect_changed=post_commitment != handle.observed_pre_state_commitment, + pre_observation_provenance=handle.pre_observation_provenance, + post_observation_provenance=post_provenance, + adapter_identity=self.identity, + adapter_version=self.version, + ) + witness.validate() + return witness + + +def filesystem_state_commitment(*, allowed_root: Path, target: Path) -> str: + adapter = FilesystemEffectAdapter(allowed_root=allowed_root) + return adapter._state_commitment(adapter._observe_state(Path(target))) + + +def is_adapter_bound_effect_evidence(*, witness: EffectWitness) -> bool: + """Structural EffectEvidence candidate check only; not VerifyEffect.""" + try: + witness.validate() + return ( + witness.witness_kind == EFFECT_WITNESS_KIND + and witness.adapter_identity == FilesystemEffectAdapter.identity + and witness.adapter_version == FilesystemEffectAdapter.version + ) + except (EffectAdapterError, ValueError, TypeError, AttributeError): + return False diff --git a/harness/sdk/effect_verifier.py b/harness/sdk/effect_verifier.py new file mode 100644 index 000000000..602444928 --- /dev/null +++ b/harness/sdk/effect_verifier.py @@ -0,0 +1,289 @@ +"""PR-3 effect verifier and verifier-gated EffectReceipt producer. + +This module verifies the integrity and binding of EffectEvidence relative to the +version-bound PR-3 policy. It does not establish ontological causality, +CompleteVerification, AtomicAdmission, or EffectBoundAdmission. +""" +from __future__ import annotations + +from dataclasses import asdict, dataclass +from typing import Any + +from harness.sdk.effect_adapters import EffectAdapterError, EffectWitness, FilesystemEffectAdapter, is_adapter_bound_effect_evidence +from harness.sdk.sovereign_execution import ZERO_HASH, canonical_hash +from harness.sdk.transition_receipts import ( + EFFECT_RECEIPT_KIND, + EffectReceipt, + ExecutionReceipt, + TransitionIdentity, + TransitionReceiptError, + verifier_policy_commitment, +) + +TRUE = "TRUE" +FALSE = "FALSE" +UNKNOWN = "UNKNOWN" +ERROR = "ERROR" +MISSING = "MISSING" +VERIFY_STATUSES = (TRUE, FALSE, UNKNOWN, ERROR, MISSING) +EFFECT_VERIFICATION_RESULT_KIND = "EFFECT_VERIFICATION_RESULT_V1" + +OBLIGATION_ORDER = ( + "V_effect_evidence", + "V_transition_binding", + "V_execution_binding", + "V_prestate_binding", + "V_adapter_binding", + "V_verifier_policy_binding", +) + + +class EffectVerificationError(ValueError): + """Raised when a verified-effect receipt cannot be issued safely.""" + + +@dataclass(frozen=True) +class EffectVerificationResult: + result_kind: str + status: str + transition_id: str + execution_instance_id: str + effect_witness_digest: str + verifier_policy_commitment: str + obligations: tuple[tuple[str, str], ...] + denial_code: str + + def validate(self) -> None: + if self.result_kind != EFFECT_VERIFICATION_RESULT_KIND: + raise EffectVerificationError("EFFECT_VERIFICATION_RESULT_KIND_MISMATCH") + if self.status not in VERIFY_STATUSES: + raise EffectVerificationError("EFFECT_VERIFICATION_STATUS_INVALID") + if not isinstance(self.transition_id, str) or len(self.transition_id) != 64: + raise EffectVerificationError("EFFECT_VERIFICATION_TRANSITION_INVALID") + if not isinstance(self.effect_witness_digest, str) or len(self.effect_witness_digest) != 64: + raise EffectVerificationError("EFFECT_VERIFICATION_WITNESS_DIGEST_INVALID") + if not isinstance(self.verifier_policy_commitment, str) or len(self.verifier_policy_commitment) != 64: + raise EffectVerificationError("EFFECT_VERIFICATION_POLICY_COMMITMENT_INVALID") + if not isinstance(self.execution_instance_id, str) or not self.execution_instance_id: + raise EffectVerificationError("EFFECT_VERIFICATION_EXECUTION_INSTANCE_INVALID") + if tuple(name for name, _ in self.obligations) != OBLIGATION_ORDER: + raise EffectVerificationError("EFFECT_VERIFICATION_OBLIGATION_SET_INVALID") + if any(status not in VERIFY_STATUSES for _, status in self.obligations): + raise EffectVerificationError("EFFECT_VERIFICATION_OBLIGATION_STATUS_INVALID") + if not isinstance(self.denial_code, str) or not self.denial_code: + raise EffectVerificationError("EFFECT_VERIFICATION_DENIAL_CODE_INVALID") + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_EFFECT_VERIFICATION_RESULT_V1", asdict(self)) + + +class EffectVerifier: + """Reference VerifyEffect gate for the PR-3 filesystem observation surface.""" + + supported_adapters = {(FilesystemEffectAdapter.identity, FilesystemEffectAdapter.version)} + + @staticmethod + def _obligations(default: str = MISSING) -> dict[str, str]: + return {name: default for name in OBLIGATION_ORDER} + + @staticmethod + def _result( + *, + status: str, + transition_id: str, + execution_instance_id: str, + witness_digest: str, + policy_commitment: str, + obligations: dict[str, str], + denial_code: str, + ) -> EffectVerificationResult: + result = EffectVerificationResult( + result_kind=EFFECT_VERIFICATION_RESULT_KIND, + status=status, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + effect_witness_digest=witness_digest, + verifier_policy_commitment=policy_commitment, + obligations=tuple((name, obligations[name]) for name in OBLIGATION_ORDER), + denial_code=denial_code, + ) + result.validate() + return result + + def verify_effect( + self, + *, + transition: TransitionIdentity, + execution_receipt: ExecutionReceipt, + witness: EffectWitness | None, + ) -> EffectVerificationResult: + policy_commitment = verifier_policy_commitment() + obligations = self._obligations() + try: + transition.validate() + execution_receipt.validate() + transition_id = transition.root + execution_instance_id = execution_receipt.execution_instance_id + except Exception: + return self._result( + status=ERROR, + transition_id=ZERO_HASH, + execution_instance_id="unresolved-execution", + witness_digest=ZERO_HASH, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="EFFECT_VERIFICATION_INPUT_ERROR", + ) + + obligations["V_verifier_policy_binding"] = ( + TRUE if transition.verifier_policy_commitment == policy_commitment else FALSE + ) + + if witness is None: + obligations["V_effect_evidence"] = MISSING + return self._result( + status=MISSING, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + witness_digest=ZERO_HASH, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="EFFECT_EVIDENCE_MISSING", + ) + + try: + witness.validate() + witness_digest = witness.root + except (EffectAdapterError, ValueError, TypeError, AttributeError): + obligations["V_effect_evidence"] = FALSE + return self._result( + status=FALSE, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + witness_digest=ZERO_HASH, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="EFFECT_EVIDENCE_INVALID", + ) + except Exception: + obligations["V_effect_evidence"] = ERROR + return self._result( + status=ERROR, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + witness_digest=ZERO_HASH, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="EFFECT_VERIFICATION_INTERNAL_ERROR", + ) + + obligations["V_transition_binding"] = ( + TRUE + if witness.transition_id == transition_id and execution_receipt.transition_id == transition_id + else FALSE + ) + obligations["V_execution_binding"] = ( + TRUE if witness.execution_instance_id == execution_instance_id else FALSE + ) + obligations["V_prestate_binding"] = ( + TRUE if witness.observed_pre_state_commitment == transition.pre_state_commitment else FALSE + ) + + adapter_key = (witness.adapter_identity, witness.adapter_version) + adapter_supported = adapter_key in self.supported_adapters + obligations["V_adapter_binding"] = TRUE if adapter_supported else UNKNOWN + obligations["V_effect_evidence"] = ( + TRUE if adapter_supported and is_adapter_bound_effect_evidence(witness=witness) else UNKNOWN + ) + + if any(obligations[name] == FALSE for name in OBLIGATION_ORDER): + return self._result( + status=FALSE, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + witness_digest=witness_digest, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="EFFECT_VERIFICATION_CONTRADICTED", + ) + if any(obligations[name] in (UNKNOWN, MISSING) for name in OBLIGATION_ORDER): + return self._result( + status=UNKNOWN, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + witness_digest=witness_digest, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="EFFECT_VERIFICATION_UNRESOLVABLE", + ) + if any(obligations[name] == ERROR for name in OBLIGATION_ORDER): + return self._result( + status=ERROR, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + witness_digest=witness_digest, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="EFFECT_VERIFICATION_INTERNAL_ERROR", + ) + + return self._result( + status=TRUE, + transition_id=transition_id, + execution_instance_id=execution_instance_id, + witness_digest=witness_digest, + policy_commitment=policy_commitment, + obligations=obligations, + denial_code="NONE", + ) + + def issue_effect_receipt( + self, + *, + transition: TransitionIdentity, + execution_receipt: ExecutionReceipt, + witness: EffectWitness | None, + verification: EffectVerificationResult, + ) -> EffectReceipt: + if verification.status != TRUE: + raise EffectVerificationError("EFFECT_VERIFICATION_NOT_TRUE") + if witness is None: + raise EffectVerificationError("EFFECT_EVIDENCE_MISSING") + + recomputed = self.verify_effect( + transition=transition, + execution_receipt=execution_receipt, + witness=witness, + ) + if recomputed.status != TRUE or recomputed.root != verification.root: + raise EffectVerificationError("EFFECT_VERIFICATION_RECOMPUTE_MISMATCH") + + receipt = object.__new__(EffectReceipt) + fields: dict[str, Any] = { + "receipt_kind": EFFECT_RECEIPT_KIND, + "transition_id": transition.root, + "execution_instance_id": execution_receipt.execution_instance_id, + "effect_witness_digest": witness.root, + "effect_verification_root": verification.root, + "verifier_policy_commitment": verification.verifier_policy_commitment, + "pre_state_commitment": witness.observed_pre_state_commitment, + "post_state_commitment": witness.observed_post_state_commitment, + "observation_provenance": canonical_hash( + "AEGIS_EFFECT_OBSERVATION_BUNDLE_V1", + { + "pre": witness.pre_observation_provenance, + "post": witness.post_observation_provenance, + }, + ), + "adapter_identity": witness.adapter_identity, + "adapter_version": witness.adapter_version, + } + for name, value in fields.items(): + object.__setattr__(receipt, name, value) + try: + receipt.validate() + except TransitionReceiptError as exc: + raise EffectVerificationError("EFFECT_RECEIPT_CONSTRUCTION_INVALID") from exc + return receipt diff --git a/harness/sdk/transition_receipts.py b/harness/sdk/transition_receipts.py new file mode 100644 index 000000000..e2b933d1f --- /dev/null +++ b/harness/sdk/transition_receipts.py @@ -0,0 +1,373 @@ +"""Transition identity and epistemically separated receipt types. + +PR-1 establishes receipt separation. PR-2 adds independent EffectEvidence. +PR-3 activates a version-bound VerifyEffect policy and allows EffectReceipt only +through the separate verifier gate. CompleteVerification and authoritative +admission remain unavailable. +""" +from __future__ import annotations + +import re +from dataclasses import asdict, dataclass, is_dataclass +from typing import Any, Mapping + +from harness.sdk.sovereign_execution import SCHEMA_VERSION, canonical_hash + +DECISION_RECEIPT_KIND = "DECISION_RECEIPT_V1" +EXECUTION_RECEIPT_KIND = "EXECUTION_RECEIPT_V1" +EFFECT_RECEIPT_KIND = "EFFECT_RECEIPT_V1" + +PERMIT = "PERMIT" +DENY = "DENY" +DEFER = "DEFER" +DECISION_OUTCOMES = (PERMIT, DENY, DEFER) + +EXECUTION_SUCCEEDED = "SUCCEEDED" +EXECUTION_FAILED = "FAILED" +EXECUTION_CANCELLED = "CANCELLED" +EXECUTION_OUTCOMES = (EXECUTION_SUCCEEDED, EXECUTION_FAILED, EXECUTION_CANCELLED) + +AUTHORIZED = "AUTHORIZED" +DENIED_STATE = "DENIED" +WAITING = "WAITING" + +SHA256_RE = re.compile(r"^[0-9a-f]{64}$") +GIT_RE = re.compile(r"^[0-9a-f]{40,64}$") +SAFE_ID_RE = re.compile(r"^[A-Za-z0-9._:/@+#=-]+$") + +PR1_VERIFIER_POLICY = { + "policy_id": "AEGIS_PR1_VERIFIER_POLICY_V1", + "safe_incompleteness": True, + "required_semantics": ["V_decision", "V_binding", "V_effect"], + "effect_evidence_required": True, + "effect_receipt_production": "UNAVAILABLE", +} + +PR2_VERIFIER_POLICY = { + "policy_id": "AEGIS_PR2_VERIFIER_POLICY_V2", + "safe_incompleteness": True, + "obligation_set_status": "PARTIAL_PRE_REGISTRY", + "required_semantics": ["V_decision", "V_binding", "V_effect"], + "effect_evidence_required": True, + "effect_evidence_production": "ADAPTER_BOUND_ONLY", + "effect_observation_scope": "REFERENCE_ADAPTER_BOUND_ONLY", + "verify_effect": "NOT_IMPLEMENTED", + "effect_receipt_production": "UNAVAILABLE", + "complete_verification": "UNAVAILABLE", + "atomic_admission": "UNAVAILABLE", +} + +PR3_VERIFIER_POLICY = { + "policy_id": "AEGIS_PR3_VERIFIER_POLICY_V1", + "safe_incompleteness": True, + "obligation_set_status": "EFFECT_ONLY_VERSION_BOUND", + "effect_observation_scope": "REFERENCE_ADAPTER_BOUND_ONLY", + "effect_evidence_production": "ADAPTER_BOUND_ONLY", + "verify_effect": "REQUIRED", + "effect_receipt_production": "VERIFIER_GATED_ONLY", + "effect_obligations": [ + "V_effect_evidence", + "V_transition_binding", + "V_execution_binding", + "V_prestate_binding", + "V_adapter_binding", + "V_verifier_policy_binding", + ], + "causal_claim_admission": "NOT_IMPLEMENTED", + "complete_verification": "UNAVAILABLE", + "atomic_admission": "UNAVAILABLE", + "effect_bound_admission": "UNAVAILABLE", +} + +PR1_ADMISSION_POLICY = { + "policy_id": "AEGIS_PR1_ADMISSION_POLICY_V1", + "safe_incompleteness": True, + "effect_bound_admission": "UNAVAILABLE", + "legacy_fallback": "FORBIDDEN", + "defer_authority": "NOT_ADMITTED", +} + + +class TransitionReceiptError(ValueError): + """Raised when a transition or receipt violates its nominal contract.""" + + +def _require_hash(name: str, value: str) -> None: + if not isinstance(value, str) or not SHA256_RE.fullmatch(value): + raise TransitionReceiptError(f"{name}:INVALID_SHA256") + + +def _require_git(name: str, value: str) -> None: + if not isinstance(value, str) or not GIT_RE.fullmatch(value): + raise TransitionReceiptError(f"{name}:INVALID_GIT_OBJECT") + + +def _require_id(name: str, value: str) -> None: + if not isinstance(value, str) or not value or not SAFE_ID_RE.fullmatch(value): + raise TransitionReceiptError(f"{name}:INVALID_ID") + + +@dataclass(frozen=True) +class TransitionIdentity: + """Canonical transition binding whose root is the TransitionID tau.""" + + schema_version: str + source_commit: str + pre_state_commitment: str + identity_root: str + delegation_commitment: str + capability_commitment: str + action_digest: str + deterministic_nonce: str + fence_commitment: str + verifier_policy_commitment: str + admission_policy_commitment: str + + def validate(self) -> None: + if self.schema_version != SCHEMA_VERSION: + raise TransitionReceiptError("TRANSITION_SCHEMA_UNSUPPORTED") + _require_git("source_commit", self.source_commit) + for name in ( + "pre_state_commitment", + "identity_root", + "delegation_commitment", + "capability_commitment", + "action_digest", + "fence_commitment", + "verifier_policy_commitment", + "admission_policy_commitment", + ): + _require_hash(name, getattr(self, name)) + _require_id("deterministic_nonce", self.deterministic_nonce) + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_TRANSITION_ID_V1", asdict(self)) + + +@dataclass(frozen=True) +class DecisionReceipt: + receipt_kind: str + transition_id: str + decision_outcome: str + policy_decision_root: str + + def __post_init__(self) -> None: + self.validate() + + def validate(self) -> None: + if self.receipt_kind != DECISION_RECEIPT_KIND: + raise ValueError("DECISION_RECEIPT_KIND_MISMATCH") + _require_hash("transition_id", self.transition_id) + _require_hash("policy_decision_root", self.policy_decision_root) + if self.decision_outcome not in DECISION_OUTCOMES: + raise TransitionReceiptError("DECISION_OUTCOME_INVALID") + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_DECISION_RECEIPT_V1", asdict(self)) + + +@dataclass(frozen=True) +class ExecutionReceipt: + receipt_kind: str + transition_id: str + execution_instance_id: str + outcome: str + result_digest: str + + def __post_init__(self) -> None: + self.validate() + + def validate(self) -> None: + if self.receipt_kind != EXECUTION_RECEIPT_KIND: + raise ValueError("EXECUTION_RECEIPT_KIND_MISMATCH") + _require_hash("transition_id", self.transition_id) + _require_id("execution_instance_id", self.execution_instance_id) + _require_hash("result_digest", self.result_digest) + if self.outcome not in EXECUTION_OUTCOMES: + raise TransitionReceiptError("EXECUTION_OUTCOME_INVALID") + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_EXECUTION_RECEIPT_V1", asdict(self)) + + +@dataclass(frozen=True, init=False) +class EffectReceipt: + """Nominal receipt issued only after VerifyEffect=TRUE under the bound policy.""" + + receipt_kind: str + transition_id: str + execution_instance_id: str + effect_witness_digest: str + effect_verification_root: str + verifier_policy_commitment: str + pre_state_commitment: str + post_state_commitment: str + observation_provenance: str + adapter_identity: str + adapter_version: str + + def validate(self) -> None: + if getattr(self, "receipt_kind", None) != EFFECT_RECEIPT_KIND: + raise TransitionReceiptError("EFFECT_RECEIPT_KIND_MISMATCH") + for name in ( + "transition_id", + "effect_witness_digest", + "effect_verification_root", + "verifier_policy_commitment", + "pre_state_commitment", + "post_state_commitment", + "observation_provenance", + ): + _require_hash(name, getattr(self, name)) + _require_id("execution_instance_id", self.execution_instance_id) + _require_id("adapter_identity", self.adapter_identity) + _require_id("adapter_version", self.adapter_version) + + @property + def root(self) -> str: + self.validate() + return canonical_hash("AEGIS_EFFECT_RECEIPT_V1", asdict(self)) + + +def decision_satisfies_authority(outcome: str) -> bool: + """Only PERMIT carries decision authority; DENY and DEFER do not.""" + return outcome == PERMIT + + +def decision_execution_allowed(outcome: str) -> bool: + return decision_satisfies_authority(outcome) + + +def decision_route(outcome: str) -> str: + if outcome == PERMIT: + return AUTHORIZED + if outcome == DENY: + return DENIED_STATE + if outcome == DEFER: + return WAITING + raise TransitionReceiptError("DECISION_OUTCOME_INVALID") + + +def verify_transition_binding(transition: TransitionIdentity, *receipts: Any) -> bool: + """V_binding: every receipt must carry the exact recomputed tau.""" + try: + expected = transition.root + if not receipts: + return False + for receipt in receipts: + validate = getattr(receipt, "validate", None) + if not callable(validate): + return False + validate() + if getattr(receipt, "transition_id", None) != expected: + return False + return True + except (TransitionReceiptError, ValueError, TypeError, AttributeError): + return False + + +def accept_effect_evidence(_artifact: Any) -> bool: + """CompleteVerification admission remains unavailable in PR-3.""" + return False + + +def _plain(value: Any) -> Any: + if value is None: + return None + if is_dataclass(value): + return asdict(value) + if isinstance(value, Mapping): + return dict(value) + return value + + +def delegation_commitment(approval: Any | None) -> str: + return canonical_hash( + "AEGIS_DELEGATION_COMMITMENT_V1", + {"state": "NONE"} if approval is None else {"state": "BOUND", "approval": _plain(approval)}, + ) + + +def capability_commitment(*, requested_capability: str, registry_root: str) -> str: + _require_id("requested_capability", requested_capability) + _require_hash("registry_root", registry_root) + return canonical_hash( + "AEGIS_CAPABILITY_COMMITMENT_V1", + {"requested_capability": requested_capability, "registry_root": registry_root}, + ) + + +def fence_commitment(fence_token: str | None) -> str: + return canonical_hash( + "AEGIS_TRANSITION_FENCE_COMMITMENT_V1", + {"fence_token": fence_token if fence_token else "NONE"}, + ) + + +def pr1_verifier_policy_commitment() -> str: + return canonical_hash("AEGIS_VERIFIER_POLICY_COMMITMENT_V1", PR1_VERIFIER_POLICY) + + +def pr2_verifier_policy_commitment() -> str: + return canonical_hash("AEGIS_VERIFIER_POLICY_COMMITMENT_V1", PR2_VERIFIER_POLICY) + + +def verifier_policy_commitment() -> str: + """Active PR-3 VerifyEffect policy commitment.""" + return canonical_hash("AEGIS_VERIFIER_POLICY_COMMITMENT_V1", PR3_VERIFIER_POLICY) + + +def admission_policy_commitment() -> str: + return canonical_hash("AEGIS_ADMISSION_POLICY_COMMITMENT_V1", PR1_ADMISSION_POLICY) + + +def build_transition_identity( + *, + source_commit: str, + pre_state_commitment: str, + identity_root: str, + approval: Any | None, + requested_capability: str, + registry_root: str, + action_digest: str, + deterministic_nonce: str, + fence_token: str | None = None, +) -> TransitionIdentity: + return TransitionIdentity( + schema_version=SCHEMA_VERSION, + source_commit=source_commit, + pre_state_commitment=pre_state_commitment, + identity_root=identity_root, + delegation_commitment=delegation_commitment(approval), + capability_commitment=capability_commitment( + requested_capability=requested_capability, + registry_root=registry_root, + ), + action_digest=action_digest, + deterministic_nonce=deterministic_nonce, + fence_commitment=fence_commitment(fence_token), + verifier_policy_commitment=verifier_policy_commitment(), + admission_policy_commitment=admission_policy_commitment(), + ) + + +def decision_receipt_from_policy(*, transition: TransitionIdentity, decision: Any) -> DecisionReceipt: + legacy_outcome = getattr(decision, "outcome", None) + if legacy_outcome == "ADMITTED": + outcome = PERMIT + elif legacy_outcome == "DENIED": + outcome = DENY + else: + outcome = DEFER + return DecisionReceipt( + receipt_kind=DECISION_RECEIPT_KIND, + transition_id=transition.root, + decision_outcome=outcome, + policy_decision_root=getattr(decision, "decision_root"), + ) diff --git a/schemas/complete-verification-result.v1.schema.json b/schemas/complete-verification-result.v1.schema.json new file mode 100644 index 000000000..e6c4c01b9 --- /dev/null +++ b/schemas/complete-verification-result.v1.schema.json @@ -0,0 +1,70 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/complete-verification-result.v1.schema.json", + "title": "AEGIS Complete Verification Result V1", + "$comment": "PR-4 verifier artifact only. TRUE does not imply causal claim admission, AtomicAdmission, EffectBoundAdmission, production admission, or AGI.", + "type": "object", + "additionalProperties": false, + "required": [ + "result_kind", + "status", + "transition_id", + "decision_receipt_root", + "execution_receipt_root", + "effect_witness_digest", + "effect_verification_root", + "effect_receipt_root", + "complete_verifier_policy_commitment", + "obligations", + "denial_code" + ], + "properties": { + "result_kind": {"const": "COMPLETE_VERIFICATION_RESULT_V1"}, + "status": {"enum": ["TRUE", "FALSE", "UNKNOWN", "ERROR", "MISSING"]}, + "transition_id": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "decision_receipt_root": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "execution_receipt_root": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "effect_witness_digest": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "effect_verification_root": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "effect_receipt_root": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "complete_verifier_policy_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "obligations": { + "type": "array", + "minItems": 13, + "maxItems": 13, + "prefixItems": [ + {"$ref": "#/$defs/v_transition_identity"}, + {"$ref": "#/$defs/v_decision_receipt"}, + {"$ref": "#/$defs/v_decision_authority"}, + {"$ref": "#/$defs/v_decision_binding"}, + {"$ref": "#/$defs/v_execution_receipt"}, + {"$ref": "#/$defs/v_execution_binding"}, + {"$ref": "#/$defs/v_effect_evidence"}, + {"$ref": "#/$defs/v_effect_verification"}, + {"$ref": "#/$defs/v_effect_receipt"}, + {"$ref": "#/$defs/v_effect_binding"}, + {"$ref": "#/$defs/v_effect_verification_binding"}, + {"$ref": "#/$defs/v_verifier_policy_binding"}, + {"$ref": "#/$defs/v_admission_policy_binding"} + ], + "items": false + }, + "denial_code": {"type": "string", "minLength": 1, "pattern": "^[A-Z0-9_]+$"} + }, + "$defs": { + "status": {"enum": ["TRUE", "FALSE", "UNKNOWN", "ERROR", "MISSING"]}, + "v_transition_identity": {"type": "array", "prefixItems": [{"const": "V_transition_identity"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_decision_receipt": {"type": "array", "prefixItems": [{"const": "V_decision_receipt"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_decision_authority": {"type": "array", "prefixItems": [{"const": "V_decision_authority"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_decision_binding": {"type": "array", "prefixItems": [{"const": "V_decision_binding"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_execution_receipt": {"type": "array", "prefixItems": [{"const": "V_execution_receipt"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_execution_binding": {"type": "array", "prefixItems": [{"const": "V_execution_binding"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_effect_evidence": {"type": "array", "prefixItems": [{"const": "V_effect_evidence"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_effect_verification": {"type": "array", "prefixItems": [{"const": "V_effect_verification"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_effect_receipt": {"type": "array", "prefixItems": [{"const": "V_effect_receipt"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_effect_binding": {"type": "array", "prefixItems": [{"const": "V_effect_binding"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_effect_verification_binding": {"type": "array", "prefixItems": [{"const": "V_effect_verification_binding"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_verifier_policy_binding": {"type": "array", "prefixItems": [{"const": "V_verifier_policy_binding"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2}, + "v_admission_policy_binding": {"type": "array", "prefixItems": [{"const": "V_admission_policy_binding"}, {"$ref": "#/$defs/status"}], "items": false, "minItems": 2, "maxItems": 2} + } +} diff --git a/schemas/decision-receipt.v1.schema.json b/schemas/decision-receipt.v1.schema.json new file mode 100644 index 000000000..4450f48df --- /dev/null +++ b/schemas/decision-receipt.v1.schema.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/decision-receipt.v1.schema.json", + "title": "AEGIS Decision Receipt V1", + "type": "object", + "additionalProperties": false, + "required": ["receipt_kind", "transition_id", "decision_outcome", "policy_decision_root"], + "properties": { + "receipt_kind": {"const": "DECISION_RECEIPT_V1"}, + "transition_id": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "decision_outcome": {"enum": ["PERMIT", "DENY", "DEFER"]}, + "policy_decision_root": {"type": "string", "pattern": "^[0-9a-f]{64}$"} + } +} diff --git a/schemas/effect-receipt.v1.schema.json b/schemas/effect-receipt.v1.schema.json new file mode 100644 index 000000000..168172536 --- /dev/null +++ b/schemas/effect-receipt.v1.schema.json @@ -0,0 +1,34 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/effect-receipt.v1.schema.json", + "title": "AEGIS Effect Receipt V1", + "$comment": "PR-3 activates the previously schema-only nominal EffectReceipt. A valid producer is verifier-gated and requires VerifyEffect=TRUE under the transition-bound verifier policy. Receipt existence does not imply CompleteVerification or admission.", + "type": "object", + "additionalProperties": false, + "required": [ + "receipt_kind", + "transition_id", + "execution_instance_id", + "effect_witness_digest", + "effect_verification_root", + "verifier_policy_commitment", + "pre_state_commitment", + "post_state_commitment", + "observation_provenance", + "adapter_identity", + "adapter_version" + ], + "properties": { + "receipt_kind": {"const": "EFFECT_RECEIPT_V1"}, + "transition_id": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "execution_instance_id": {"type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9._:/@+#=-]+$"}, + "effect_witness_digest": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "effect_verification_root": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "verifier_policy_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "pre_state_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "post_state_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "observation_provenance": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "adapter_identity": {"type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9._:/@+#=-]+$"}, + "adapter_version": {"type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9._:/@+#=-]+$"} + } +} diff --git a/schemas/effect-witness.v1.schema.json b/schemas/effect-witness.v1.schema.json new file mode 100644 index 000000000..24bd73478 --- /dev/null +++ b/schemas/effect-witness.v1.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/effect-witness.v1.schema.json", + "title": "AEGIS Effect Witness V1", + "type": "object", + "additionalProperties": false, + "required": [ + "witness_kind", + "transition_id", + "execution_instance_id", + "target_identity", + "observed_pre_state_commitment", + "observed_post_state_commitment", + "effect_changed", + "pre_observation_provenance", + "post_observation_provenance", + "adapter_identity", + "adapter_version" + ], + "properties": { + "witness_kind": {"const": "EFFECT_WITNESS_V1"}, + "transition_id": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "execution_instance_id": {"type": "string", "minLength": 1}, + "target_identity": {"type": "string", "minLength": 1}, + "observed_pre_state_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "observed_post_state_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "effect_changed": {"type": "boolean"}, + "pre_observation_provenance": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "post_observation_provenance": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "adapter_identity": {"type": "string", "minLength": 1}, + "adapter_version": {"type": "string", "minLength": 1} + } +} diff --git a/schemas/execution-receipt.v1.schema.json b/schemas/execution-receipt.v1.schema.json new file mode 100644 index 000000000..d89dc2456 --- /dev/null +++ b/schemas/execution-receipt.v1.schema.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/execution-receipt.v1.schema.json", + "title": "AEGIS Execution Receipt V1", + "type": "object", + "additionalProperties": false, + "required": ["receipt_kind", "transition_id", "execution_instance_id", "outcome", "result_digest"], + "properties": { + "receipt_kind": {"const": "EXECUTION_RECEIPT_V1"}, + "transition_id": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "execution_instance_id": {"type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9._:/@+#=-]+$"}, + "outcome": {"enum": ["SUCCEEDED", "FAILED", "CANCELLED"]}, + "result_digest": {"type": "string", "pattern": "^[0-9a-f]{64}$"} + } +} diff --git a/schemas/transition-identity-envelope.v1.schema.json b/schemas/transition-identity-envelope.v1.schema.json new file mode 100644 index 000000000..694f02320 --- /dev/null +++ b/schemas/transition-identity-envelope.v1.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://aegisomega.com/schemas/transition-identity-envelope.v1.schema.json", + "title": "AEGIS Transition Identity Envelope V1", + "type": "object", + "additionalProperties": false, + "required": [ + "schema_version", + "source_commit", + "pre_state_commitment", + "identity_root", + "delegation_commitment", + "capability_commitment", + "action_digest", + "deterministic_nonce", + "fence_commitment", + "verifier_policy_commitment", + "admission_policy_commitment" + ], + "properties": { + "schema_version": {"const": "1.0.0"}, + "source_commit": {"type": "string", "pattern": "^[0-9a-f]{40,64}$"}, + "pre_state_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "identity_root": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "delegation_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "capability_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "action_digest": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "deterministic_nonce": {"type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9._:/@+#=-]+$"}, + "fence_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "verifier_policy_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"}, + "admission_policy_commitment": {"type": "string", "pattern": "^[0-9a-f]{64}$"} + } +} diff --git a/scripts/automaton3-authority.py b/scripts/automaton3-authority.py old mode 100644 new mode 100755 index b30f3ea4b..f6967cf10 --- a/scripts/automaton3-authority.py +++ b/scripts/automaton3-authority.py @@ -25,6 +25,12 @@ make_mutation_receipt, verify_workspace, ) +from harness.sdk.transition_receipts import ( # noqa: E402 + build_transition_identity, + decision_receipt_from_policy, +) + +LEGACY_RECEIPT_SEMANTICS = "DECISION_DERIVED_NOT_EFFECT_PROOF" def deny(code: str, detail: str = "") -> dict: @@ -98,14 +104,31 @@ def evaluate(payload: dict) -> dict: ) approval = ApprovalGrant(**payload["approval"]) if payload.get("approval") else None decision = AuthorityEvaluator(policy=policy, registry=registry, repository_root=ROOT).evaluate(request, approval=approval) + legacy_pre_state_digest = request_payload.get("pre_state_digest", ZERO_HASH) + transition = build_transition_identity( + source_commit=identity.source_commit, + pre_state_commitment=identity.expected_pre_state, + identity_root=identity_root, + approval=approval, + requested_capability=request.requested_capability, + registry_root=registry_root, + action_digest=action_digest, + deterministic_nonce=identity.deterministic_nonce, + fence_token=request_payload.get("fencing_token"), + ) + decision_receipt = decision_receipt_from_policy(transition=transition, decision=decision) + + # Compatibility-only legacy V1 artifact. Caller-supplied pre/post digests are + # preserved for format compatibility, but neither participates as authoritative + # effect evidence. The new TransitionID binds identity.expected_pre_state. receipt = make_mutation_receipt( identity_root=identity_root, workspace_binding=identity.workspace_binding, decision=decision, - pre_state_digest=request_payload.get("pre_state_digest", ZERO_HASH), + pre_state_digest=legacy_pre_state_digest, action_digest=action_digest, result={"authority_outcome": decision.outcome}, - post_state_digest=request_payload.get("post_state_digest", request_payload.get("pre_state_digest", ZERO_HASH)), + post_state_digest=request_payload.get("post_state_digest", legacy_pre_state_digest), parent_receipt=request_payload.get("parent_receipt", ZERO_HASH), sequence=int(request_payload.get("sequence", 0)), ) @@ -116,8 +139,12 @@ def evaluate(payload: dict) -> dict: "workspace_binding": identity.workspace_binding, "workspace_decision_root": workspace.decision_root, "policy_decision": decision_dict(decision), + "transition_id": transition.root, + "decision_receipt": asdict(decision_receipt), + "decision_receipt_root": decision_receipt.root, "mutation_receipt": asdict(receipt), "mutation_receipt_root": receipt.root, + "legacy_receipt_semantics": LEGACY_RECEIPT_SEMANTICS, "observation": asdict(workspace.observation), } except Exception as exc: diff --git a/sovereign-omega-v2/python/tests/test_complete_verifier_pr4.py b/sovereign-omega-v2/python/tests/test_complete_verifier_pr4.py new file mode 100755 index 000000000..ca0b3155a --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_complete_verifier_pr4.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +"""PR-4 falsification suite: CompleteVerification closes one exact evidence bundle only.""" +from __future__ import annotations + +import sys +import tempfile +from dataclasses import fields, replace +from pathlib import Path +from unittest import TestCase, main + +REPO_ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(REPO_ROOT)) + +from harness.sdk.complete_verifier import ( # noqa: E402 + FALSE, + MISSING, + TRUE, + CompleteVerifier, +) +from harness.sdk.effect_adapters import FilesystemEffectAdapter, filesystem_state_commitment # noqa: E402 +from harness.sdk.effect_verifier import EffectVerifier # noqa: E402 +from harness.sdk.sovereign_execution import SCHEMA_VERSION # noqa: E402 +from harness.sdk.transition_receipts import ( # noqa: E402 + DEFER, + DENY, + PERMIT, + DECISION_RECEIPT_KIND, + EFFECT_RECEIPT_KIND, + EXECUTION_FAILED, + EXECUTION_RECEIPT_KIND, + EXECUTION_SUCCEEDED, + DecisionReceipt, + EffectReceipt, + ExecutionReceipt, + TransitionIdentity, + admission_policy_commitment, + verifier_policy_commitment, +) + +HASHES = [f"{i:064x}" for i in range(1, 96)] +COMMIT = "c" * 40 + + +def forge_effect_receipt(receipt: EffectReceipt, **changes: str) -> EffectReceipt: + forged = object.__new__(EffectReceipt) + for field in fields(EffectReceipt): + object.__setattr__(forged, field.name, changes.get(field.name, getattr(receipt, field.name))) + return forged + + +class CompleteVerifierPR4Tests(TestCase): + def transition( + self, + *, + pre: str, + nonce: str = "nonce-pr4", + verifier_policy: str | None = None, + admission_policy: str | None = None, + ) -> TransitionIdentity: + return TransitionIdentity( + schema_version=SCHEMA_VERSION, + source_commit=COMMIT, + pre_state_commitment=pre, + identity_root=HASHES[1], + delegation_commitment=HASHES[2], + capability_commitment=HASHES[3], + action_digest=HASHES[4], + deterministic_nonce=nonce, + fence_commitment=HASHES[5], + verifier_policy_commitment=verifier_policy or verifier_policy_commitment(), + admission_policy_commitment=admission_policy or admission_policy_commitment(), + ) + + def bundle( + self, + *, + nonce: str = "nonce-pr4", + decision_outcome: str = PERMIT, + execution_outcome: str = EXECUTION_SUCCEEDED, + execution_instance_id: str = "exec-pr4", + change: bool = True, + verifier_policy: str | None = None, + admission_policy: str | None = None, + ): + tmp = tempfile.TemporaryDirectory() + root = Path(tmp.name) + target = root / "state.txt" + target.write_bytes(b"before") + pre = filesystem_state_commitment(allowed_root=root, target=target) + transition = self.transition( + pre=pre, + nonce=nonce, + verifier_policy=verifier_policy, + admission_policy=admission_policy, + ) + decision = DecisionReceipt( + receipt_kind=DECISION_RECEIPT_KIND, + transition_id=transition.root, + decision_outcome=decision_outcome, + policy_decision_root=HASHES[8], + ) + execution = ExecutionReceipt( + receipt_kind=EXECUTION_RECEIPT_KIND, + transition_id=transition.root, + execution_instance_id=execution_instance_id, + outcome=execution_outcome, + result_digest=HASHES[9], + ) + adapter = FilesystemEffectAdapter(allowed_root=root) + handle = adapter.prepare_observation(transition=transition, target=target) + if change: + target.write_bytes(b"after") + witness = adapter.observe_effect( + transition=transition, + handle=handle, + execution_receipt=execution, + ) + effect_verifier = EffectVerifier() + effect_verification = effect_verifier.verify_effect( + transition=transition, + execution_receipt=execution, + witness=witness, + ) + self.assertEqual(effect_verification.status, TRUE) + effect_receipt = effect_verifier.issue_effect_receipt( + transition=transition, + execution_receipt=execution, + witness=witness, + verification=effect_verification, + ) + return tmp, transition, decision, execution, witness, effect_verification, effect_receipt + + def verify(self, bundle, **overrides): + _, transition, decision, execution, witness, effect_verification, effect_receipt = bundle + values = { + "transition": transition, + "decision_receipt": decision, + "execution_receipt": execution, + "effect_witness": witness, + "effect_verification": effect_verification, + "effect_receipt": effect_receipt, + } + values.update(overrides) + return CompleteVerifier().verify_complete(**values) + + def test_valid_exact_bundle_is_true(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + result = self.verify(bundle) + self.assertEqual(result.status, TRUE) + self.assertTrue(all(status == TRUE for _, status in result.obligations)) + self.assertFalse(hasattr(result, "admitted")) + + def test_missing_decision_receipt_is_missing(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + self.assertEqual(self.verify(bundle, decision_receipt=None).status, MISSING) + + def test_missing_execution_receipt_is_missing(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + self.assertEqual(self.verify(bundle, execution_receipt=None).status, MISSING) + + def test_missing_effect_witness_is_missing(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + self.assertEqual(self.verify(bundle, effect_witness=None).status, MISSING) + + def test_missing_effect_verification_is_missing(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + self.assertEqual(self.verify(bundle, effect_verification=None).status, MISSING) + + def test_missing_effect_receipt_is_missing(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + self.assertEqual(self.verify(bundle, effect_receipt=None).status, MISSING) + + def test_deny_is_not_complete(self): + bundle = self.bundle(decision_outcome=DENY) + self.addCleanup(bundle[0].cleanup) + result = self.verify(bundle) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_decision_authority"], FALSE) + + def test_defer_is_not_complete(self): + bundle = self.bundle(decision_outcome=DEFER) + self.addCleanup(bundle[0].cleanup) + result = self.verify(bundle) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_decision_authority"], FALSE) + + def test_cross_transition_decision_is_rejected(self): + left = self.bundle(nonce="nonce-pr4-left") + right = self.bundle(nonce="nonce-pr4-right") + self.addCleanup(left[0].cleanup) + self.addCleanup(right[0].cleanup) + result = self.verify(left, decision_receipt=right[2]) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_decision_binding"], FALSE) + + def test_cross_transition_execution_is_rejected(self): + left = self.bundle(nonce="nonce-pr4-left") + right = self.bundle(nonce="nonce-pr4-right") + self.addCleanup(left[0].cleanup) + self.addCleanup(right[0].cleanup) + result = self.verify(left, execution_receipt=right[3]) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_execution_binding"], FALSE) + + def test_cross_transition_witness_is_rejected(self): + left = self.bundle(nonce="nonce-pr4-left") + right = self.bundle(nonce="nonce-pr4-right") + self.addCleanup(left[0].cleanup) + self.addCleanup(right[0].cleanup) + result = self.verify(left, effect_witness=right[4]) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_evidence"], FALSE) + + def test_cross_transition_effect_receipt_is_rejected(self): + left = self.bundle(nonce="nonce-pr4-left") + right = self.bundle(nonce="nonce-pr4-right") + self.addCleanup(left[0].cleanup) + self.addCleanup(right[0].cleanup) + result = self.verify(left, effect_receipt=right[6]) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_binding"], FALSE) + + def test_execution_instance_splice_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], execution_instance_id="exec-pr4-spliced") + result = self.verify(bundle, effect_receipt=forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_binding"], FALSE) + + def test_forged_effect_verification_result_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = replace(bundle[5], denial_code="FORGED") + result = self.verify(bundle, effect_verification=forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_verification_binding"], FALSE) + + def test_forged_effect_verification_root_in_receipt_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], effect_verification_root=HASHES[40]) + result = self.verify(bundle, effect_receipt=forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_verification_binding"], FALSE) + + def test_forged_effect_witness_digest_in_receipt_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], effect_witness_digest=HASHES[41]) + result = self.verify(bundle, effect_receipt=forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_binding"], FALSE) + + def test_verifier_policy_mismatch_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], verifier_policy_commitment=HASHES[42]) + result = self.verify(bundle, effect_receipt=forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_verifier_policy_binding"], FALSE) + + def test_admission_policy_mismatch_is_rejected(self): + bundle = self.bundle(admission_policy=HASHES[43]) + self.addCleanup(bundle[0].cleanup) + result = self.verify(bundle) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_admission_policy_binding"], FALSE) + + def test_wrong_nominal_type_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + result = self.verify(bundle, decision_receipt=object()) + self.assertEqual(result.status, FALSE) + self.assertEqual(result.denial_code, "COMPLETE_VERIFICATION_INPUT_ERROR") + + def test_raw_effect_witness_cannot_substitute_for_effect_receipt(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + result = self.verify(bundle, effect_receipt=bundle[4]) + self.assertEqual(result.status, FALSE) + self.assertEqual(result.denial_code, "COMPLETE_VERIFICATION_INPUT_ERROR") + + def test_succeeded_execution_without_effect_lineage_is_not_complete(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + self.assertEqual(bundle[3].outcome, EXECUTION_SUCCEEDED) + result = self.verify(bundle, effect_witness=None, effect_verification=None, effect_receipt=None) + self.assertEqual(result.status, MISSING) + + def test_failed_execution_with_verified_effect_is_evidence_driven(self): + bundle = self.bundle(execution_outcome=EXECUTION_FAILED, change=True) + self.addCleanup(bundle[0].cleanup) + self.assertEqual(bundle[3].outcome, EXECUTION_FAILED) + self.assertTrue(bundle[4].effect_changed) + self.assertEqual(self.verify(bundle).status, TRUE) + + def test_no_change_effect_can_complete_when_verified(self): + bundle = self.bundle(change=False) + self.addCleanup(bundle[0].cleanup) + self.assertFalse(bundle[4].effect_changed) + self.assertEqual(self.verify(bundle).status, TRUE) + + def test_result_root_is_deterministic(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + first = self.verify(bundle) + second = self.verify(bundle) + self.assertEqual(first.root, second.root) + + def test_complete_result_hash_domain_is_separate_from_receipts(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + result = self.verify(bundle) + self.assertEqual(bundle[6].receipt_kind, EFFECT_RECEIPT_KIND) + self.assertNotIn(result.root, {bundle[2].root, bundle[3].root, bundle[6].root}) + + +if __name__ == "__main__": + main() diff --git a/sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py b/sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py new file mode 100755 index 000000000..815c03298 --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +"""PR-4 adversarial receipt-binding falsifiers discovered during security review.""" +from __future__ import annotations + +import sys +import tempfile +from dataclasses import fields +from pathlib import Path +from unittest import TestCase, main + +REPO_ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(REPO_ROOT)) + +from harness.sdk.complete_verifier import FALSE, TRUE, CompleteVerifier # noqa: E402 +from harness.sdk.effect_adapters import FilesystemEffectAdapter, filesystem_state_commitment # noqa: E402 +from harness.sdk.effect_verifier import EffectVerifier # noqa: E402 +from harness.sdk.sovereign_execution import SCHEMA_VERSION # noqa: E402 +from harness.sdk.transition_receipts import ( # noqa: E402 + PERMIT, + DECISION_RECEIPT_KIND, + EXECUTION_RECEIPT_KIND, + DecisionReceipt, + EffectReceipt, + ExecutionReceipt, + TransitionIdentity, + admission_policy_commitment, + verifier_policy_commitment, +) + +HASHES = [f"{i:064x}" for i in range(100, 160)] +COMMIT = "d" * 40 + + +def forge_effect_receipt(receipt: EffectReceipt, **changes: str) -> EffectReceipt: + forged = object.__new__(EffectReceipt) + for field in fields(EffectReceipt): + object.__setattr__(forged, field.name, changes.get(field.name, getattr(receipt, field.name))) + return forged + + +class CompleteVerifierPR4ReceiptBindingTests(TestCase): + def bundle(self): + tmp = tempfile.TemporaryDirectory() + root = Path(tmp.name) + target = root / "state.txt" + target.write_bytes(b"before") + pre = filesystem_state_commitment(allowed_root=root, target=target) + transition = TransitionIdentity( + schema_version=SCHEMA_VERSION, + source_commit=COMMIT, + pre_state_commitment=pre, + identity_root=HASHES[1], + delegation_commitment=HASHES[2], + capability_commitment=HASHES[3], + action_digest=HASHES[4], + deterministic_nonce="nonce-pr4-receipt-binding", + fence_commitment=HASHES[5], + verifier_policy_commitment=verifier_policy_commitment(), + admission_policy_commitment=admission_policy_commitment(), + ) + decision = DecisionReceipt( + receipt_kind=DECISION_RECEIPT_KIND, + transition_id=transition.root, + decision_outcome=PERMIT, + policy_decision_root=HASHES[6], + ) + execution = ExecutionReceipt( + receipt_kind=EXECUTION_RECEIPT_KIND, + transition_id=transition.root, + execution_instance_id="exec-pr4-receipt-binding", + outcome="SUCCEEDED", + result_digest=HASHES[7], + ) + adapter = FilesystemEffectAdapter(allowed_root=root) + handle = adapter.prepare_observation(transition=transition, target=target) + target.write_bytes(b"after") + witness = adapter.observe_effect( + transition=transition, + handle=handle, + execution_receipt=execution, + ) + effect_verifier = EffectVerifier() + verification = effect_verifier.verify_effect( + transition=transition, + execution_receipt=execution, + witness=witness, + ) + self.assertEqual(verification.status, TRUE) + receipt = effect_verifier.issue_effect_receipt( + transition=transition, + execution_receipt=execution, + witness=witness, + verification=verification, + ) + return tmp, transition, decision, execution, witness, verification, receipt + + def verify(self, bundle, receipt): + _, transition, decision, execution, witness, verification, _ = bundle + return CompleteVerifier().verify_complete( + transition=transition, + decision_receipt=decision, + execution_receipt=execution, + effect_witness=witness, + effect_verification=verification, + effect_receipt=receipt, + ) + + def test_forged_post_state_commitment_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], post_state_commitment=HASHES[20]) + result = self.verify(bundle, forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_binding"], FALSE) + + def test_forged_observation_provenance_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], observation_provenance=HASHES[21]) + result = self.verify(bundle, forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_binding"], FALSE) + + def test_forged_adapter_identity_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], adapter_identity="forged.adapter") + result = self.verify(bundle, forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_binding"], FALSE) + + def test_forged_adapter_version_is_rejected(self): + bundle = self.bundle() + self.addCleanup(bundle[0].cleanup) + forged = forge_effect_receipt(bundle[6], adapter_version="9.9.9") + result = self.verify(bundle, forged) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_effect_binding"], FALSE) + + +if __name__ == "__main__": + main() diff --git a/sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py b/sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py new file mode 100755 index 000000000..092ec53e1 --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py @@ -0,0 +1,291 @@ +#!/usr/bin/env python3 +"""PR-2 falsification suite: independent effect observation remains below VerifyEffect.""" +from __future__ import annotations + +import inspect +import sys +import tempfile +from dataclasses import replace +from pathlib import Path +from unittest import TestCase, main + +REPO_ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(REPO_ROOT)) + +from harness.sdk.sovereign_execution import MutationReceipt, SCHEMA_VERSION, ZERO_HASH # noqa: E402 +import harness.sdk.transition_receipts as transition_receipts # noqa: E402 +from harness.sdk.transition_receipts import ( # noqa: E402 + DECISION_RECEIPT_KIND, + EFFECT_RECEIPT_KIND, + EXECUTION_FAILED, + EXECUTION_RECEIPT_KIND, + EXECUTION_SUCCEEDED, + PERMIT, + PR1_VERIFIER_POLICY, + PR2_VERIFIER_POLICY, + DecisionReceipt, + EffectReceipt, + ExecutionReceipt, + TransitionIdentity, + accept_effect_evidence, + build_transition_identity, + pr1_verifier_policy_commitment, + verifier_policy_commitment, +) +from harness.sdk.effect_adapters import ( # noqa: E402 + EFFECT_WITNESS_KIND, + EffectAdapterError, + EffectWitness, + FilesystemEffectAdapter, + filesystem_state_commitment, + is_adapter_bound_effect_evidence, +) + +HASHES = [f"{index:064x}" for index in range(1, 40)] +COMMIT = "a" * 40 + + +class EffectAdapterPR2Tests(TestCase): + def transition(self, *, pre_state_commitment: str, **changes) -> TransitionIdentity: + values = { + "schema_version": SCHEMA_VERSION, + "source_commit": COMMIT, + "pre_state_commitment": pre_state_commitment, + "identity_root": HASHES[1], + "delegation_commitment": HASHES[2], + "capability_commitment": HASHES[3], + "action_digest": HASHES[4], + "deterministic_nonce": "nonce-pr2-1", + "fence_commitment": HASHES[5], + "verifier_policy_commitment": HASHES[6], + "admission_policy_commitment": HASHES[7], + } + values.update(changes) + return TransitionIdentity(**values) + + def decision(self, transition: TransitionIdentity) -> DecisionReceipt: + return DecisionReceipt( + receipt_kind=DECISION_RECEIPT_KIND, + transition_id=transition.root, + decision_outcome=PERMIT, + policy_decision_root=HASHES[8], + ) + + def execution( + self, + transition: TransitionIdentity, + *, + execution_instance_id: str = "exec-pr2-1", + outcome: str = EXECUTION_SUCCEEDED, + ) -> ExecutionReceipt: + return ExecutionReceipt( + receipt_kind=EXECUTION_RECEIPT_KIND, + transition_id=transition.root, + execution_instance_id=execution_instance_id, + outcome=outcome, + result_digest=HASHES[9], + ) + + def legacy_receipt(self) -> MutationReceipt: + return MutationReceipt( + receipt_version=SCHEMA_VERSION, + execution_identity_root=HASHES[1], + workspace_binding=HASHES[2], + policy_decision_root=HASHES[8], + authority_score="1.000000", + authority_domain="repo", + action_class="D2", + tool="write_file", + target=HASHES[10], + pre_state_digest=HASHES[11], + requested_action_digest=HASHES[12], + result_digest=HASHES[13], + post_state_digest=HASHES[14], + parent_receipt=ZERO_HASH, + sequence=1, + outcome="SUCCEEDED", + denial_code="NONE", + ) + + def prepared(self, root: Path, *, content: bytes = b"before"): + target = root / "state.txt" + target.write_bytes(content) + pre = filesystem_state_commitment(allowed_root=root, target=target) + transition = self.transition(pre_state_commitment=pre) + adapter = FilesystemEffectAdapter(allowed_root=root) + handle = adapter.prepare_observation(transition=transition, target=target) + execution = self.execution(transition) + return target, transition, adapter, handle, execution + + def test_authorization_artifact_still_not_effect_evidence(self): + transition = self.transition(pre_state_commitment=HASHES[15]) + self.assertFalse(accept_effect_evidence(self.decision(transition))) + + def test_legacy_succeeded_receipt_still_not_effect_evidence(self): + receipt = self.legacy_receipt() + self.assertEqual(receipt.outcome, "SUCCEEDED") + self.assertFalse(accept_effect_evidence(receipt)) + + def test_direct_effect_receipt_construction_still_forbidden(self): + with self.assertRaises(TypeError): + EffectReceipt( + receipt_kind=EFFECT_RECEIPT_KIND, + transition_id=HASHES[1], + execution_instance_id="exec-forged", + effect_witness_digest=HASHES[2], + pre_state_commitment=HASHES[3], + post_state_commitment=HASHES[4], + observation_provenance=HASHES[5], + adapter_identity="forged.adapter", + adapter_version="1.0.0", + ) + + def test_caller_post_state_digest_has_no_effect_authority(self): + signature = inspect.signature(FilesystemEffectAdapter.observe_effect) + self.assertNotIn("post_state_digest", signature.parameters) + self.assertNotIn("post_state_commitment", signature.parameters) + + def test_prepare_observation_rejects_pre_state_mismatch(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + target = root / "state.txt" + target.write_text("actual", encoding="utf-8") + transition = self.transition(pre_state_commitment=HASHES[20]) + with self.assertRaisesRegex(EffectAdapterError, "EFFECT_PRE_STATE_COMMITMENT_MISMATCH"): + FilesystemEffectAdapter(allowed_root=root).prepare_observation(transition=transition, target=target) + + def test_prepare_observation_rejects_target_escape(self): + with tempfile.TemporaryDirectory() as tmp, tempfile.TemporaryDirectory() as other: + root = Path(tmp) + outside = Path(other) / "outside.txt" + outside.write_text("x", encoding="utf-8") + transition = self.transition(pre_state_commitment=HASHES[20]) + with self.assertRaisesRegex(EffectAdapterError, "EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT"): + FilesystemEffectAdapter(allowed_root=root).prepare_observation(transition=transition, target=outside) + + def test_prepare_observation_rejects_symlink_escape(self): + with tempfile.TemporaryDirectory() as tmp, tempfile.TemporaryDirectory() as other: + root = Path(tmp) + outside = Path(other) / "outside.txt" + outside.write_text("x", encoding="utf-8") + link = root / "link.txt" + try: + link.symlink_to(outside) + except (OSError, NotImplementedError): + self.skipTest("symlink creation unavailable") + transition = self.transition(pre_state_commitment=HASHES[20]) + with self.assertRaisesRegex(EffectAdapterError, "EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT"): + FilesystemEffectAdapter(allowed_root=root).prepare_observation(transition=transition, target=link) + + def test_effect_observation_binds_transition_id(self): + with tempfile.TemporaryDirectory() as tmp: + target, transition, adapter, handle, execution = self.prepared(Path(tmp)) + target.write_text("after", encoding="utf-8") + other = replace(transition, deterministic_nonce="nonce-pr2-other") + with self.assertRaisesRegex(EffectAdapterError, "EFFECT_TRANSITION_BINDING_MISMATCH"): + adapter.observe_effect(transition=other, handle=handle, execution_receipt=execution) + + def test_effect_observation_binds_execution_instance_id(self): + with tempfile.TemporaryDirectory() as tmp: + target, transition, adapter, handle, _ = self.prepared(Path(tmp)) + target.write_text("after", encoding="utf-8") + execution = self.execution(transition, execution_instance_id="exec-pr2-bound") + witness = adapter.observe_effect(transition=transition, handle=handle, execution_receipt=execution) + self.assertEqual(witness.execution_instance_id, "exec-pr2-bound") + + def test_cross_transition_execution_receipt_splicing_fails(self): + with tempfile.TemporaryDirectory() as tmp: + target, transition, adapter, handle, _ = self.prepared(Path(tmp)) + target.write_text("after", encoding="utf-8") + other = replace(transition, deterministic_nonce="nonce-pr2-splice") + wrong_execution = self.execution(other) + with self.assertRaisesRegex(EffectAdapterError, "EFFECT_EXECUTION_TRANSITION_MISMATCH"): + adapter.observe_effect(transition=transition, handle=handle, execution_receipt=wrong_execution) + + def test_cross_target_observation_splicing_fails(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + target, transition, adapter, handle, execution = self.prepared(root) + other_target = root / "other.txt" + other_target.write_text("before", encoding="utf-8") + forged = replace(handle, target_identity="other.txt") + target.write_text("after", encoding="utf-8") + with self.assertRaisesRegex(EffectAdapterError, "EFFECT_OBSERVATION_HANDLE_MISMATCH"): + adapter.observe_effect(transition=transition, handle=forged, execution_receipt=execution) + + def test_post_state_is_derived_from_fresh_filesystem_read(self): + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + target, transition, adapter, handle, execution = self.prepared(root) + target.write_bytes(b"world-after") + expected = filesystem_state_commitment(allowed_root=root, target=target) + witness = adapter.observe_effect(transition=transition, handle=handle, execution_receipt=execution) + self.assertEqual(witness.observed_post_state_commitment, expected) + + def test_no_effect_produces_evidence_with_effect_changed_false(self): + with tempfile.TemporaryDirectory() as tmp: + _, transition, adapter, handle, execution = self.prepared(Path(tmp)) + witness = adapter.observe_effect(transition=transition, handle=handle, execution_receipt=execution) + self.assertFalse(witness.effect_changed) + self.assertTrue(is_adapter_bound_effect_evidence(witness=witness)) + self.assertFalse(accept_effect_evidence(witness)) + + def test_real_effect_produces_distinct_observed_post_state(self): + with tempfile.TemporaryDirectory() as tmp: + target, transition, adapter, handle, _ = self.prepared(Path(tmp)) + execution = self.execution(transition, outcome=EXECUTION_FAILED) + target.write_text("changed-after-failed-execution", encoding="utf-8") + witness = adapter.observe_effect(transition=transition, handle=handle, execution_receipt=execution) + self.assertEqual(execution.outcome, EXECUTION_FAILED) + self.assertTrue(witness.effect_changed) + self.assertNotEqual(witness.observed_pre_state_commitment, witness.observed_post_state_commitment) + self.assertEqual(witness.execution_instance_id, execution.execution_instance_id) + + def test_observation_does_not_produce_effect_receipt_and_policy_is_current(self): + with tempfile.TemporaryDirectory() as tmp: + _, transition, adapter, handle, execution = self.prepared(Path(tmp)) + witness = adapter.observe_effect(transition=transition, handle=handle, execution_receipt=execution) + self.assertIsInstance(witness, EffectWitness) + self.assertFalse(isinstance(witness, tuple)) + self.assertFalse(hasattr(transition_receipts, "_issue_adapter_bound_effect_receipt")) + self.assertFalse(hasattr(transition_receipts, "_EFFECT_RECEIPT_PRODUCER_CAPABILITY")) + self.assertEqual(PR1_VERIFIER_POLICY["effect_receipt_production"], "UNAVAILABLE") + self.assertEqual(PR2_VERIFIER_POLICY["effect_evidence_production"], "ADAPTER_BOUND_ONLY") + self.assertEqual(PR2_VERIFIER_POLICY["verify_effect"], "NOT_IMPLEMENTED") + self.assertEqual(PR2_VERIFIER_POLICY["effect_receipt_production"], "UNAVAILABLE") + self.assertEqual(PR2_VERIFIER_POLICY["complete_verification"], "UNAVAILABLE") + self.assertEqual(PR2_VERIFIER_POLICY["atomic_admission"], "UNAVAILABLE") + self.assertNotEqual(verifier_policy_commitment(), pr1_verifier_policy_commitment()) + built = build_transition_identity( + source_commit=COMMIT, + pre_state_commitment=HASHES[0], + identity_root=HASHES[1], + approval=None, + requested_capability="filesystem.write", + registry_root=HASHES[2], + action_digest=HASHES[4], + deterministic_nonce="nonce-pr2-policy-bound", + fence_token="fence-pr2-1", + ) + self.assertEqual(built.verifier_policy_commitment, verifier_policy_commitment()) + + def test_effect_witness_exists_does_not_imply_verified_or_receipt(self): + with tempfile.TemporaryDirectory() as tmp: + _, transition, adapter, handle, execution = self.prepared(Path(tmp)) + witness = adapter.observe_effect(transition=transition, handle=handle, execution_receipt=execution) + self.assertTrue(is_adapter_bound_effect_evidence(witness=witness)) + self.assertFalse(accept_effect_evidence(witness)) + self.assertFalse(hasattr(witness, "verified")) + self.assertFalse(hasattr(witness, "admitted")) + self.assertFalse(hasattr(witness, "receipt_kind")) + + def test_missing_effect_receipt_still_has_no_legacy_fallback(self): + transition = self.transition(pre_state_commitment=HASHES[15]) + self.assertFalse(accept_effect_evidence(None)) + self.assertFalse(accept_effect_evidence(self.legacy_receipt())) + self.assertFalse(accept_effect_evidence(self.decision(transition))) + self.assertFalse(accept_effect_evidence(self.execution(transition, outcome=EXECUTION_FAILED))) + + +if __name__ == "__main__": + main() diff --git a/sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py b/sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py new file mode 100755 index 000000000..3e733522e --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py @@ -0,0 +1,216 @@ +#!/usr/bin/env python3 +"""PR-3 falsification suite: VerifyEffect gates nominal EffectReceipt issuance.""" +from __future__ import annotations + +import sys +import tempfile +from dataclasses import replace +from pathlib import Path +from unittest import TestCase, main + +REPO_ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(REPO_ROOT)) + +import harness.sdk.transition_receipts as tr # noqa: E402 +from harness.sdk.effect_adapters import FilesystemEffectAdapter, filesystem_state_commitment # noqa: E402 +from harness.sdk.effect_verifier import ( # noqa: E402 + ERROR, + FALSE, + MISSING, + TRUE, + UNKNOWN, + EffectVerificationError, + EffectVerificationResult, + EffectVerifier, +) +from harness.sdk.sovereign_execution import SCHEMA_VERSION # noqa: E402 +from harness.sdk.transition_receipts import ( # noqa: E402 + EFFECT_RECEIPT_KIND, + EXECUTION_FAILED, + EXECUTION_RECEIPT_KIND, + EXECUTION_SUCCEEDED, + EffectReceipt, + ExecutionReceipt, + TransitionIdentity, + accept_effect_evidence, + pr2_verifier_policy_commitment, + verifier_policy_commitment, +) + +HASHES = [f"{i:064x}" for i in range(1, 64)] +COMMIT = "b" * 40 + + +class EffectVerifierPR3Tests(TestCase): + def transition(self, *, pre: str, policy: str | None = None, nonce: str = "nonce-pr3") -> TransitionIdentity: + return TransitionIdentity( + schema_version=SCHEMA_VERSION, + source_commit=COMMIT, + pre_state_commitment=pre, + identity_root=HASHES[1], + delegation_commitment=HASHES[2], + capability_commitment=HASHES[3], + action_digest=HASHES[4], + deterministic_nonce=nonce, + fence_commitment=HASHES[5], + verifier_policy_commitment=policy or verifier_policy_commitment(), + admission_policy_commitment=HASHES[6], + ) + + def execution(self, transition: TransitionIdentity, *, instance: str = "exec-pr3", outcome: str = EXECUTION_SUCCEEDED) -> ExecutionReceipt: + return ExecutionReceipt( + receipt_kind=EXECUTION_RECEIPT_KIND, + transition_id=transition.root, + execution_instance_id=instance, + outcome=outcome, + result_digest=HASHES[7], + ) + + def observed(self, *, change: bool = True, outcome: str = EXECUTION_SUCCEEDED, policy: str | None = None): + tmp = tempfile.TemporaryDirectory() + root = Path(tmp.name) + target = root / "state.txt" + target.write_bytes(b"before") + pre = filesystem_state_commitment(allowed_root=root, target=target) + transition = self.transition(pre=pre, policy=policy) + adapter = FilesystemEffectAdapter(allowed_root=root) + handle = adapter.prepare_observation(transition=transition, target=target) + execution = self.execution(transition, outcome=outcome) + if change: + target.write_bytes(b"after") + witness = adapter.observe_effect(transition=transition, handle=handle, execution_receipt=execution) + return tmp, transition, execution, witness + + def test_missing_effect_evidence_is_missing_and_cannot_issue_receipt(self): + transition = self.transition(pre=HASHES[10]) + execution = self.execution(transition) + verifier = EffectVerifier() + result = verifier.verify_effect(transition=transition, execution_receipt=execution, witness=None) + self.assertEqual(result.status, MISSING) + with self.assertRaisesRegex(EffectVerificationError, "EFFECT_VERIFICATION_NOT_TRUE"): + verifier.issue_effect_receipt(transition=transition, execution_receipt=execution, witness=None, verification=result) + + def test_malformed_effect_evidence_is_false(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + malformed = replace(witness, observed_post_state_commitment="not-a-hash") + result = EffectVerifier().verify_effect(transition=transition, execution_receipt=execution, witness=malformed) + self.assertEqual(result.status, FALSE) + + def test_unknown_adapter_is_unknown_not_true(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + unknown = replace(witness, adapter_identity="unknown.adapter") + result = EffectVerifier().verify_effect(transition=transition, execution_receipt=execution, witness=unknown) + self.assertEqual(result.status, UNKNOWN) + + def test_cross_transition_witness_binding_fails(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + other = replace(transition, deterministic_nonce="nonce-pr3-other") + result = EffectVerifier().verify_effect(transition=other, execution_receipt=execution, witness=witness) + self.assertEqual(result.status, FALSE) + + def test_execution_transition_binding_fails(self): + tmp, transition, _, witness = self.observed() + self.addCleanup(tmp.cleanup) + other = replace(transition, deterministic_nonce="nonce-pr3-execution-other") + wrong_execution = self.execution(other) + result = EffectVerifier().verify_effect(transition=transition, execution_receipt=wrong_execution, witness=witness) + self.assertEqual(result.status, FALSE) + + def test_execution_instance_binding_fails(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + spliced = replace(witness, execution_instance_id="exec-pr3-spliced") + result = EffectVerifier().verify_effect(transition=transition, execution_receipt=execution, witness=spliced) + self.assertEqual(result.status, FALSE) + + def test_prestate_binding_fails(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + spliced = replace(witness, observed_pre_state_commitment=HASHES[20]) + result = EffectVerifier().verify_effect(transition=transition, execution_receipt=execution, witness=spliced) + self.assertEqual(result.status, FALSE) + + def test_stale_pr2_verifier_policy_is_rejected(self): + tmp, transition, execution, witness = self.observed(policy=pr2_verifier_policy_commitment()) + self.addCleanup(tmp.cleanup) + result = EffectVerifier().verify_effect(transition=transition, execution_receipt=execution, witness=witness) + self.assertEqual(result.status, FALSE) + self.assertEqual(dict(result.obligations)["V_verifier_policy_binding"], FALSE) + + def test_valid_no_change_evidence_verifies_and_can_issue_receipt(self): + tmp, transition, execution, witness = self.observed(change=False) + self.addCleanup(tmp.cleanup) + verifier = EffectVerifier() + result = verifier.verify_effect(transition=transition, execution_receipt=execution, witness=witness) + self.assertEqual(result.status, TRUE) + self.assertFalse(witness.effect_changed) + receipt = verifier.issue_effect_receipt(transition=transition, execution_receipt=execution, witness=witness, verification=result) + self.assertEqual(receipt.receipt_kind, EFFECT_RECEIPT_KIND) + self.assertEqual(receipt.effect_witness_digest, witness.root) + + def test_failed_execution_can_still_have_verified_observed_effect(self): + tmp, transition, execution, witness = self.observed(change=True, outcome=EXECUTION_FAILED) + self.addCleanup(tmp.cleanup) + verifier = EffectVerifier() + result = verifier.verify_effect(transition=transition, execution_receipt=execution, witness=witness) + self.assertEqual(execution.outcome, EXECUTION_FAILED) + self.assertTrue(witness.effect_changed) + self.assertEqual(result.status, TRUE) + receipt = verifier.issue_effect_receipt(transition=transition, execution_receipt=execution, witness=witness, verification=result) + self.assertEqual(receipt.execution_instance_id, execution.execution_instance_id) + + def test_forged_or_mutated_verification_result_cannot_issue_receipt(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + verifier = EffectVerifier() + result = verifier.verify_effect(transition=transition, execution_receipt=execution, witness=witness) + forged = replace(result, verifier_policy_commitment=pr2_verifier_policy_commitment()) + with self.assertRaisesRegex(EffectVerificationError, "EFFECT_VERIFICATION_RECOMPUTE_MISMATCH"): + verifier.issue_effect_receipt(transition=transition, execution_receipt=execution, witness=witness, verification=forged) + + def test_effect_receipt_binds_verification_root_and_policy_commitment(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + verifier = EffectVerifier() + result = verifier.verify_effect(transition=transition, execution_receipt=execution, witness=witness) + receipt = verifier.issue_effect_receipt(transition=transition, execution_receipt=execution, witness=witness, verification=result) + self.assertEqual(receipt.effect_verification_root, result.root) + self.assertEqual(receipt.verifier_policy_commitment, verifier_policy_commitment()) + self.assertEqual(receipt.transition_id, transition.root) + + def test_direct_effect_receipt_construction_remains_forbidden(self): + with self.assertRaises(TypeError): + EffectReceipt( + receipt_kind=EFFECT_RECEIPT_KIND, + transition_id=HASHES[1], + execution_instance_id="exec-forged", + effect_witness_digest=HASHES[2], + effect_verification_root=HASHES[3], + verifier_policy_commitment=HASHES[4], + pre_state_commitment=HASHES[5], + post_state_commitment=HASHES[6], + observation_provenance=HASHES[7], + adapter_identity="forged.adapter", + adapter_version="1.0.0", + ) + + def test_effect_receipt_still_does_not_satisfy_complete_verification(self): + tmp, transition, execution, witness = self.observed() + self.addCleanup(tmp.cleanup) + verifier = EffectVerifier() + result = verifier.verify_effect(transition=transition, execution_receipt=execution, witness=witness) + receipt = verifier.issue_effect_receipt(transition=transition, execution_receipt=execution, witness=witness, verification=result) + self.assertFalse(accept_effect_evidence(receipt)) + self.assertFalse(hasattr(receipt, "admitted")) + + def test_no_generic_effect_receipt_factory_exists(self): + self.assertFalse(hasattr(tr, "make_effect_receipt")) + self.assertFalse(hasattr(tr, "effect_receipt_from_post_state")) + self.assertFalse(hasattr(tr, "_issue_adapter_bound_effect_receipt")) + + +if __name__ == "__main__": + main() diff --git a/sovereign-omega-v2/python/tests/test_transition_receipts_cli_pr1.py b/sovereign-omega-v2/python/tests/test_transition_receipts_cli_pr1.py new file mode 100755 index 000000000..a3da58c7b --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_transition_receipts_cli_pr1.py @@ -0,0 +1,116 @@ +#!/usr/bin/env python3 +"""PR-1 falsifier for the canonical Automaton-3 CLI receipt producer.""" +from __future__ import annotations + +import importlib.util +import sys +from pathlib import Path +from unittest import TestCase, main + +REPO_ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(REPO_ROOT)) + +from harness.sdk.sovereign_execution import ( # noqa: E402 + SCHEMA_VERSION, + ZERO_HASH, + ExecutionIdentityEnvelope, + canonical_hash, + compute_workspace_binding, +) + +REMOTE = "https://github.com/Aegis-Omega/AEGIS-OMEGA.git" +COMMIT = "a" * 40 +HASH = "1" * 64 + + +def load_cli_module(): + path = REPO_ROOT / "scripts" / "automaton3-authority.py" + spec = importlib.util.spec_from_file_location("aegis_automaton3_authority_pr1", path) + if spec is None or spec.loader is None: + raise RuntimeError("AUTOMATON3_CLI_IMPORT_UNAVAILABLE") + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +class TransitionReceiptCliPR1Tests(TestCase): + def test_cli_emits_decision_receipt_but_no_effect_receipt(self): + cli = load_cli_module() + action = {"operation": "status"} + approval_reference = "approval-none" + binding = compute_workspace_binding( + repository_remote=REMOTE, + repository_root=".", + project_identity="AEGIS-OMEGA", + source_commit=COMMIT, + operator_authorization=approval_reference, + ) + identity = ExecutionIdentityEnvelope( + schema_version=SCHEMA_VERSION, + repository_identity=REMOTE, + repository_root=".", + source_commit=COMMIT, + branch_or_ref="refs/heads/pr1-cli-test", + project_identity="AEGIS-OMEGA", + workspace_root=".", + workspace_binding=binding, + parent_state_root=HASH, + skills_root="2" * 64, + registry_root="3" * 64, + policy_root="4" * 64, + actor_class="operator-agent", + actor_identity="agent-1", + model_identity="model-1", + session_identity="session-1", + physical_executor="test-runner-1", + tool_identity="aegis_platform_status", + workflow_identity="pr1-cli-test", + authority_domain="mcp:status", + requested_capability="mcp.platform.status", + observed_authority="0.000000", + approval_reference=approval_reference, + input_digest=canonical_hash("AEGIS_PR1_CLI_TEST_INPUT_V1", {}), + action_digest=canonical_hash("AEGIS_REQUESTED_ACTION_V1", action), + expected_pre_state=ZERO_HASH, + deterministic_nonce="nonce-cli-1", + ) + payload = { + "identity": identity.__dict__, + "workspace": { + "actual_cwd": str(REPO_ROOT), + "remote_origin": REMOTE, + "mutation_target": str(REPO_ROOT), + }, + "request": { + "action_class": "D0", + "authority_domain": "mcp:status", + "requested_capability": "mcp.platform.status", + "tool": "aegis_platform_status", + "target": "platform", + "pre_state_digest": "6" * 64, + "post_state_digest": "5" * 64, + }, + "action": action, + } + result = cli.evaluate(payload) + second_payload = { + **payload, + "request": {**payload["request"], "pre_state_digest": "7" * 64}, + } + second = cli.evaluate(second_payload) + + self.assertEqual(result["outcome"], "ADMITTED") + self.assertEqual(result["decision_receipt"]["receipt_kind"], "DECISION_RECEIPT_V1") + self.assertEqual(result["decision_receipt"]["decision_outcome"], "PERMIT") + self.assertEqual(result["transition_id"], result["decision_receipt"]["transition_id"]) + self.assertEqual(result["legacy_receipt_semantics"], "DECISION_DERIVED_NOT_EFFECT_PROOF") + self.assertEqual(result["mutation_receipt"]["pre_state_digest"], "6" * 64) + self.assertEqual(second["mutation_receipt"]["pre_state_digest"], "7" * 64) + self.assertEqual(result["transition_id"], second["transition_id"]) + self.assertEqual(result["mutation_receipt"]["post_state_digest"], "5" * 64) + self.assertNotIn("effect_receipt", result) + self.assertNotIn("effect_receipt", second) + + +if __name__ == "__main__": + main() diff --git a/sovereign-omega-v2/python/tests/test_transition_receipts_pr1.py b/sovereign-omega-v2/python/tests/test_transition_receipts_pr1.py new file mode 100755 index 000000000..0f7a2d6dc --- /dev/null +++ b/sovereign-omega-v2/python/tests/test_transition_receipts_pr1.py @@ -0,0 +1,281 @@ +#!/usr/bin/env python3 +"""PR-1 falsification tests for transition binding and receipt separation.""" +from __future__ import annotations + +import importlib +import importlib.util +import json +import sys +from dataclasses import replace +from pathlib import Path +from unittest import TestCase, main +from unittest.mock import patch + +REPO_ROOT = Path(__file__).resolve().parents[3] +sys.path.insert(0, str(REPO_ROOT)) + +from harness.sdk.sovereign_execution import ( # noqa: E402 + D2, + SCHEMA_VERSION, + ZERO_HASH, + ExecutionIdentityEnvelope, + MutationReceipt, + canonical_hash, + compute_workspace_binding, +) + +HASHES = [f"{index:064x}" for index in range(1, 20)] +COMMIT = "a" * 40 +REMOTE = "https://github.com/Aegis-Omega/AEGIS-OMEGA.git" + + +class TransitionReceiptPR1Tests(TestCase): + def api(self): + spec = importlib.util.find_spec("harness.sdk.transition_receipts") + self.assertIsNotNone(spec, "PR-1 transition receipt API is not implemented") + return importlib.import_module("harness.sdk.transition_receipts") + + def transition(self, **changes): + api = self.api() + values = dict( + schema_version="1.0.0", + source_commit=COMMIT, + pre_state_commitment=HASHES[0], + identity_root=HASHES[1], + delegation_commitment=HASHES[2], + capability_commitment=HASHES[3], + action_digest=HASHES[4], + deterministic_nonce="nonce-1", + fence_commitment=HASHES[5], + verifier_policy_commitment=HASHES[6], + admission_policy_commitment=HASHES[7], + ) + values.update(changes) + return api.TransitionIdentity(**values) + + def decision(self, transition=None, outcome=None): + api = self.api() + transition = transition or self.transition() + return api.DecisionReceipt( + receipt_kind=api.DECISION_RECEIPT_KIND, + transition_id=transition.root, + decision_outcome=outcome or api.PERMIT, + policy_decision_root=HASHES[8], + ) + + def execution(self, transition=None): + api = self.api() + transition = transition or self.transition() + return api.ExecutionReceipt( + receipt_kind=api.EXECUTION_RECEIPT_KIND, + transition_id=transition.root, + execution_instance_id="exec-1", + outcome=api.EXECUTION_SUCCEEDED, + result_digest=HASHES[9], + ) + + def legacy_receipt(self): + return MutationReceipt( + receipt_version=SCHEMA_VERSION, + execution_identity_root=HASHES[1], + workspace_binding=HASHES[2], + policy_decision_root=HASHES[8], + authority_score="1.000000", + authority_domain="github:contents", + action_class=D2, + tool="git", + target=HASHES[3], + pre_state_digest=ZERO_HASH, + requested_action_digest=HASHES[4], + result_digest=HASHES[9], + post_state_digest=HASHES[10], + parent_receipt=ZERO_HASH, + sequence=0, + outcome="SUCCEEDED", + denial_code="NONE", + ) + + def test_legacy_succeeded_receipt_is_not_effect_evidence(self): + api = self.api() + self.assertFalse(api.accept_effect_evidence(self.legacy_receipt())) + + def test_decision_permit_does_not_imply_execution_success(self): + api = self.api() + receipt = self.decision(outcome=api.PERMIT) + self.assertTrue(api.decision_satisfies_authority(receipt.decision_outcome)) + self.assertFalse(api.accept_effect_evidence(receipt)) + + def test_execution_success_does_not_imply_effect_success(self): + api = self.api() + receipt = self.execution() + self.assertEqual(receipt.outcome, api.EXECUTION_SUCCEEDED) + self.assertFalse(api.accept_effect_evidence(receipt)) + + def test_defer_routes_to_waiting_and_never_authorizes_execution(self): + api = self.api() + receipt = self.decision(outcome=api.DEFER) + self.assertEqual(api.decision_route(receipt.decision_outcome), api.WAITING) + self.assertFalse(api.decision_satisfies_authority(receipt.decision_outcome)) + self.assertFalse(api.decision_execution_allowed(receipt.decision_outcome)) + + def test_cross_transition_decision_execution_splicing_fails(self): + api = self.api() + left = self.transition(deterministic_nonce="nonce-left") + right = self.transition(deterministic_nonce="nonce-right") + self.assertFalse(api.verify_transition_binding(left, self.decision(left), self.execution(right))) + + def test_transition_binding_rejects_wrong_action_digest(self): + api = self.api() + transition = self.transition() + self.assertFalse(api.verify_transition_binding(replace(transition, action_digest=HASHES[11]), self.decision(transition))) + + def test_transition_binding_rejects_wrong_nonce(self): + api = self.api() + transition = self.transition() + self.assertFalse(api.verify_transition_binding(replace(transition, deterministic_nonce="nonce-2"), self.decision(transition))) + + def test_transition_binding_rejects_wrong_fence(self): + api = self.api() + transition = self.transition() + self.assertFalse(api.verify_transition_binding(replace(transition, fence_commitment=HASHES[12]), self.decision(transition))) + + def test_transition_binding_rejects_wrong_verifier_policy_commitment(self): + api = self.api() + transition = self.transition() + self.assertFalse(api.verify_transition_binding(replace(transition, verifier_policy_commitment=HASHES[13]), self.decision(transition))) + + def test_transition_binding_rejects_wrong_admission_policy_commitment(self): + api = self.api() + transition = self.transition() + self.assertFalse(api.verify_transition_binding(replace(transition, admission_policy_commitment=HASHES[14]), self.decision(transition))) + + def test_caller_cannot_construct_effect_receipt_from_post_state(self): + api = self.api() + self.assertFalse(hasattr(api, "make_effect_receipt")) + with self.assertRaises(TypeError): + api.EffectReceipt( + receipt_kind=api.EFFECT_RECEIPT_KIND, + transition_id=self.transition().root, + execution_instance_id="exec-1", + effect_witness_digest=HASHES[15], + pre_state_commitment=HASHES[0], + post_state_commitment=HASHES[16], + observation_provenance=HASHES[17], + adapter_identity="generic-caller", + adapter_version="1", + ) + with self.assertRaises(api.TransitionReceiptError): + api.EffectReceipt().validate() + + def test_missing_effect_receipt_has_no_legacy_fallback(self): + api = self.api() + self.assertFalse(api.accept_effect_evidence(None)) + self.assertFalse(api.accept_effect_evidence(self.legacy_receipt())) + + def test_receipt_kind_is_nominal_and_const_bound(self): + api = self.api() + transition = self.transition() + with self.assertRaises(ValueError): + api.DecisionReceipt( + receipt_kind=api.EXECUTION_RECEIPT_KIND, + transition_id=transition.root, + decision_outcome=api.PERMIT, + policy_decision_root=HASHES[8], + ) + + def test_serialized_receipt_kinds_and_hash_domains_are_distinct(self): + api = self.api() + schema_expectations = { + "decision-receipt.v1.schema.json": api.DECISION_RECEIPT_KIND, + "execution-receipt.v1.schema.json": api.EXECUTION_RECEIPT_KIND, + "effect-receipt.v1.schema.json": api.EFFECT_RECEIPT_KIND, + } + for filename, expected_kind in schema_expectations.items(): + schema = json.loads((REPO_ROOT / "schemas" / filename).read_text(encoding="utf-8")) + self.assertEqual(schema["properties"]["receipt_kind"]["const"], expected_kind) + payload = {"semantic_payload": "identical"} + roots = { + canonical_hash("AEGIS_DECISION_RECEIPT_V1", payload), + canonical_hash("AEGIS_EXECUTION_RECEIPT_V1", payload), + canonical_hash("AEGIS_EFFECT_RECEIPT_V1", payload), + } + self.assertEqual(len(roots), 3) + + def test_legacy_mutation_receipt_remains_reproducible(self): + first = self.legacy_receipt() + second = self.legacy_receipt() + self.assertEqual(first.root, second.root) + + def test_authority_client_emits_decision_receipt_but_no_effect_receipt(self): + from harness.sdk.authority_client import authorize_from_environment + + action = {"operation": "status"} + approval_reference = "approval-none" + binding = compute_workspace_binding( + repository_remote=REMOTE, + repository_root=".", + project_identity="AEGIS-OMEGA", + source_commit=COMMIT, + operator_authorization=approval_reference, + ) + identity = ExecutionIdentityEnvelope( + schema_version=SCHEMA_VERSION, + repository_identity=REMOTE, + repository_root=".", + source_commit=COMMIT, + branch_or_ref="refs/heads/pr1-test", + project_identity="AEGIS-OMEGA", + workspace_root=".", + workspace_binding=binding, + parent_state_root=ZERO_HASH, + skills_root=HASHES[1], + registry_root=HASHES[2], + policy_root=HASHES[3], + actor_class="operator-agent", + actor_identity="agent-1", + model_identity="model-1", + session_identity="session-1", + physical_executor="test-runner-1", + tool_identity="aegis_platform_status", + workflow_identity="pr1-test", + authority_domain="mcp:status", + requested_capability="mcp.platform.status", + observed_authority="0.000000", + approval_reference=approval_reference, + input_digest=canonical_hash("AEGIS_PR1_TEST_INPUT_V1", {}), + action_digest=canonical_hash("AEGIS_REQUESTED_ACTION_V1", action), + expected_pre_state=ZERO_HASH, + deterministic_nonce="nonce-client-1", + ) + workspace_observation = { + "actual_cwd": str(REPO_ROOT), + "remote_origin": REMOTE, + "mutation_target": str(REPO_ROOT), + "path_views": {}, + } + with patch.dict( + "os.environ", + { + "AEGIS_EXECUTION_IDENTITY_JSON": json.dumps(identity.__dict__, sort_keys=True), + "AEGIS_WORKSPACE_OBSERVATION_JSON": json.dumps(workspace_observation, sort_keys=True), + }, + clear=True, + ): + result = authorize_from_environment( + action_class="D0", + authority_domain="mcp:status", + requested_capability="mcp.platform.status", + tool="aegis_platform_status", + target="platform", + action=action, + ) + self.assertEqual(result["outcome"], "ADMITTED") + self.assertEqual(result["decision_receipt"]["receipt_kind"], "DECISION_RECEIPT_V1") + self.assertEqual(result["decision_receipt"]["decision_outcome"], "PERMIT") + self.assertEqual(result["transition_id"], result["decision_receipt"]["transition_id"]) + self.assertEqual(result["legacy_receipt_semantics"], "DECISION_DERIVED_NOT_EFFECT_PROOF") + self.assertNotIn("effect_receipt", result) + + +if __name__ == "__main__": + main() From 5cf8e6212177ff75555cc419839162a281cee1ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:19:24 +0200 Subject: [PATCH 06/11] ci(uci): expand exact-head effect-chain gate --- .../workflows/uci-4-effect-chain-contract.yml | 76 +++++++++++++++++-- 1 file changed, 70 insertions(+), 6 deletions(-) diff --git a/.github/workflows/uci-4-effect-chain-contract.yml b/.github/workflows/uci-4-effect-chain-contract.yml index 630272032..a1ef19da4 100644 --- a/.github/workflows/uci-4-effect-chain-contract.yml +++ b/.github/workflows/uci-4-effect-chain-contract.yml @@ -11,7 +11,7 @@ jobs: uci4-effect-chain: name: uci4 / effect-chain runs-on: ubuntu-latest - timeout-minutes: 10 + timeout-minutes: 15 env: CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} EXPECTED_PARENT_SHA: ${{ github.event.pull_request.base.sha || '' }} @@ -26,7 +26,9 @@ jobs: shell: bash run: | set -euo pipefail - echo "CANDIDATE_SHA=$(git rev-parse HEAD)" + actual="$(git rev-parse HEAD)" + test "$actual" = "$CANDIDATE_SHA" + echo "CANDIDATE_SHA=$actual" if [[ -n "$EXPECTED_PARENT_SHA" ]]; then test "$(git merge-base "$EXPECTED_PARENT_SHA" HEAD)" = "$EXPECTED_PARENT_SHA" echo "EXPECTED_PARENT_SHA=$EXPECTED_PARENT_SHA" @@ -37,8 +39,70 @@ jobs: with: python-version: '3.12' - - name: Install test runtime - run: python -m pip install pytest==8.3.5 + - name: Install exact test runtime + run: python -m pip install pytest==8.3.5 jsonschema==4.23.0 - - name: Run preregistered UCI-4 boundary - run: python -m pytest sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py -q + - name: Validate UCI-4 schemas + shell: bash + run: | + set -euo pipefail + python - <<'PY' + import json + from pathlib import Path + from jsonschema.validators import validator_for + + paths = [ + Path('schemas/transition-identity-envelope.v1.schema.json'), + Path('schemas/decision-receipt.v1.schema.json'), + Path('schemas/execution-receipt.v1.schema.json'), + Path('schemas/effect-receipt.v1.schema.json'), + Path('schemas/effect-witness.v1.schema.json'), + Path('schemas/complete-verification-result.v1.schema.json'), + ] + for path in paths: + schema = json.loads(path.read_text(encoding='utf-8')) + validator_for(schema).check_schema(schema) + print(f'UCI4_SCHEMA_OK {path}') + PY + + - name: Run frozen proofline plus UCI integration falsifiers + shell: bash + run: | + set -euo pipefail + python -m pytest \ + sovereign-omega-v2/python/tests/test_transition_receipts_pr1.py \ + sovereign-omega-v2/python/tests/test_transition_receipts_cli_pr1.py \ + sovereign-omega-v2/python/tests/test_effect_adapters_pr2.py \ + sovereign-omega-v2/python/tests/test_effect_verifier_pr3.py \ + sovereign-omega-v2/python/tests/test_complete_verifier_pr4.py \ + sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py \ + sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py \ + -q | tee "$RUNNER_TEMP/uci4-pytest.log" + + - name: Write exact-head witness summary + if: always() + shell: bash + run: | + set -euo pipefail + { + echo "schema_version=AEGIS_UCI4_CI_WITNESS_V1" + echo "candidate_sha=$(git rev-parse HEAD)" + echo "expected_parent_sha=$EXPECTED_PARENT_SHA" + echo "authority=EVIDENCE_ONLY" + echo "atomic_admission=NOT_IMPLEMENTED" + echo "effect_bound_admission=UNAVAILABLE" + echo "production_admission=NOT_ESTABLISHED" + } > "$RUNNER_TEMP/uci4-witness.txt" + if [[ -f "$RUNNER_TEMP/uci4-pytest.log" ]]; then + cat "$RUNNER_TEMP/uci4-pytest.log" >> "$RUNNER_TEMP/uci4-witness.txt" + fi + + - name: Upload UCI-4 witness + if: always() + uses: actions/upload-artifact@v4 + with: + name: uci4-effect-chain-witness-${{ env.CANDIDATE_SHA }} + path: | + ${{ runner.temp }}/uci4-witness.txt + ${{ runner.temp }}/uci4-pytest.log + if-no-files-found: error From 9702004a6230d6a84cc322edb48b55c14e90fe15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 10:19:59 +0200 Subject: [PATCH 07/11] docs(audit): bind UCI-4 effect-chain lineage evidence --- ...-08-20-uci4-effect-chain-lineage-ledger.md | 135 ++++++++++++++++++ 1 file changed, 135 insertions(+) create mode 100644 docs/audits/2026-08-20-uci4-effect-chain-lineage-ledger.md diff --git a/docs/audits/2026-08-20-uci4-effect-chain-lineage-ledger.md b/docs/audits/2026-08-20-uci4-effect-chain-lineage-ledger.md new file mode 100644 index 000000000..a28a21603 --- /dev/null +++ b/docs/audits/2026-08-20-uci4-effect-chain-lineage-ledger.md @@ -0,0 +1,135 @@ +# AEGIS Ω UCI-4 Effect-Chain Lineage Ledger + +Date: 2026-08-20 +Status: IMPLEMENTED / EXTERNAL EXACT-HEAD COMPONENT WITNESS ESTABLISHED / FINAL PR-HEAD RECHECK PENDING +Authority: EVIDENCE_ONLY + +## 1. Exact lineage + +```text +CANONICAL_MAIN = 32b7eb6a37fb69d19dd80189390b6641c5004ef1 +UCI4_PARENT = ebec2f9c8fa00f54605d859df61512108ff3b71d # PR #275 exact checkpoint +SOURCE_EFFECT_LINEAGE = #268 -> #270 -> #272 -> #273 +SOURCE_EFFECT_LINEAGE_TIP = 6407db1b0c4176f67a1d7ecbb16eca77d131d87e +UCI4_RED_CANDIDATE = 13a8ffbc907fc91751f84092b7befbbad47dd0f6 +UCI4_FIRST_GREEN_RUNTIME_CANDIDATE = 1cdd2c6331fcd2c18d75baf2c60b914dd30f4548 +``` + +UCI-4 was created as a narrow successor of the UCI spine. The frozen effect-verification source files were transplanted by Git blob identity rather than by merging the historical stacked branches. + +## 2. Preserved semantic chain + +```text +TransitionIdentity + -> DecisionReceipt + -> ExecutionReceipt + -> independent EffectWitness / EffectEvidence + -> EffectVerificationResult + -> verifier-gated EffectReceipt + -> CompleteVerificationResult +``` + +Mandatory boundaries remain: + +```text +DecisionReceipt != ExecutionReceipt != EffectReceipt +DEFER -> WAITING +DEFER -/-> EXECUTE +ExecutionReceipt -/-> EffectReceipt +CompleteVerificationResult -/-> AdmissionRecord +``` + +Only `PERMIT` satisfies decision authority. Effect evidence originates from an independently bound observation adapter. No generic `EffectReceipt` producer exists. `CompleteVerificationResult=TRUE` remains verifier output only. + +## 3. TDD RED witness + +The preregistered UCI-4 integration test was committed before the production SDK surface. + +A first external witness attempt was invalid as RED evidence because the runner lacked `pytest` and the initial shell harness did not fail closed after capturing the command exit code. That result was rejected rather than promoted. + +The corrected independent witness used `pytest==8.3.5` and checked out the exact candidate: + +```text +RUNNER_REPOSITORY = tarikskalic33/info +RUN_ID = 32347655528 +JOB_ID = 96359724715 +CANDIDATE_SHA = 13a8ffbc907fc91751f84092b7befbbad47dd0f6 +EXPECTED_PARENT_SHA = ebec2f9c8fa00f54605d859df61512108ff3b71d +EXACT_LINEAGE = PASS +``` + +Observed intended RED failure: + +```text +ModuleNotFoundError: No module named 'harness.sdk.transition_receipts' +UCI4_RED_INTENDED_MISSING_TRANSITION_RECEIPTS = PASS +``` + +## 4. GREEN implementation witness + +The production transplant commit reused the exact tested Git blobs from the frozen #273 proofline for the transition/effect runtime, schemas, authority producers, and PR1→PR4 falsification suites. + +Independent hosted execution then checked out exact candidate: + +```text +RUNNER_REPOSITORY = tarikskalic33/info +RUN_ID = 32348141772 +JOB_ID = 96361208538 +CANDIDATE_SHA = 1cdd2c6331fcd2c18d75baf2c60b914dd30f4548 +UCI_PARENT_SHA = ebec2f9c8fa00f54605d859df61512108ff3b71d +FROZEN_SOURCE_TIP = 6407db1b0c4176f67a1d7ecbb16eca77d131d87e +EXACT_LINEAGE = PASS +``` + +Observed: + +```text +UCI4_SCHEMA_VALIDATION = 6/6 PASS +PR1_TO_PR4_PLUS_UCI4_TESTS = 79/79 PASS +FAILURES = 0 +``` + +The executed set was: + +- `test_transition_receipts_pr1.py` +- `test_transition_receipts_cli_pr1.py` +- `test_effect_adapters_pr2.py` +- `test_effect_verifier_pr3.py` +- `test_complete_verifier_pr4.py` +- `test_complete_verifier_pr4_receipt_binding.py` +- `test_uci4_effect_chain_integration.py` + +This establishes an external exact-head component witness for the first GREEN runtime candidate. It is not AEGIS repo-native CI and does not establish admission. + +## 5. Scope and contamination check + +Relative to `#275@ebec2f9c...`, the UCI-4 slice is limited to: + +- transition/effect SDK modules; +- canonical authority client/CLI receipt emission; +- six closed schemas; +- frozen PR1→PR4 falsification tests; +- one UCI integration test; +- dedicated UCI-4 workflow; +- implementation/audit documentation; +- repository-generated `.claude.json` provenance refresh. + +No provider-organism, memory, UI, cloud deployment, billing, domain capability, or AtomicAdmission implementation is included. + +## 6. Explicit non-claims + +```text +ATOMIC_ADMISSION = NOT_IMPLEMENTED +EFFECT_BOUND_ADMISSION = UNAVAILABLE +CAUSAL_CLAIM_ADMISSION = NOT_IMPLEMENTED +PRODUCTION_ADMISSION = NOT_ESTABLISHED +DISTRIBUTED_LINEARIZABILITY = NOT_ESTABLISHED +PRODUCTION_ROBUSTNESS = NOT_ESTABLISHED +AGI = NOT_ESTABLISHED +``` + +Provider/model output remains evidence only, never authority. D3 remains explicitly operator-approval-bound. D4 remains denied absent separately admitted policy. + +## 7. Next gate + +The final PR head after CI/ledger-only changes requires a fresh exact-head witness. No earlier GREEN run may be presented as PASS for a later SHA. UCI-5 AtomicAdmission remains a separate successor and must not be folded into this proofline by assertion. From 4c805dff810b0493612ac96e1b7275caeac8b005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:42:55 +0200 Subject: [PATCH 08/11] test(uci): preregister UCI-4 proof-integrity hardening --- .../test_uci4_effect_chain_integration.py | 151 +++++++++++++++++- 1 file changed, 145 insertions(+), 6 deletions(-) diff --git a/sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py b/sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py index 4032e54c5..63f51fefc 100644 --- a/sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py +++ b/sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py @@ -1,24 +1,84 @@ -"""UCI-4 integration boundary over the frozen effect-verification proofline. +"""UCI-4 integration and security boundary over the frozen effect-verification proofline. -This test is intentionally introduced before the UCI-4 implementation. On the -#275 parent it must fail during import because the effect-chain SDK modules are -not present. That RED witness prevents a green-by-construction transplant. +The original nominal-surface test was introduced before the UCI-4 implementation. +The additional falsifiers below are preregistered before the security hardening +patch: fabricated EffectWitness values must not verify, filesystem observation +must not be redirectable between path validation and open, observation size must +be bounded, and the native CI contract must stay bound to the frozen parent. """ +from __future__ import annotations + +import hashlib +from pathlib import Path +from unittest.mock import patch + +import pytest + +from harness.sdk.sovereign_execution import SCHEMA_VERSION from harness.sdk.transition_receipts import ( DECISION_RECEIPT_KIND, EFFECT_RECEIPT_KIND, EXECUTION_RECEIPT_KIND, + EXECUTION_SUCCEEDED, DEFER, WAITING, + ExecutionReceipt, + TransitionIdentity, + admission_policy_commitment, decision_execution_allowed, decision_route, + verifier_policy_commitment, +) +from harness.sdk.effect_adapters import ( + EFFECT_WITNESS_KIND, + EffectAdapterError, + EffectWitness, + FilesystemEffectAdapter, + is_adapter_bound_effect_evidence, ) -from harness.sdk.effect_adapters import EffectWitness -from harness.sdk.effect_verifier import EffectVerificationResult +from harness.sdk.effect_verifier import FALSE, TRUE, EffectVerificationResult, EffectVerifier from harness.sdk.complete_verifier import CompleteVerificationResult +HASHES = [f"{index:064x}" for index in range(1, 32)] +COMMIT = "c" * 40 +FROZEN_PARENT = "ebec2f9c8fa00f54605d859df61512108ff3b71d" +FROZEN_PARENT_BRANCH = "feat/uci-1-collective-work-contract-v1" +REPO_ROOT = Path(__file__).resolve().parents[3] +WORKFLOW = REPO_ROOT / ".github/workflows/uci-4-effect-chain-contract.yml" + + +def _transition(*, pre_state_commitment: str) -> TransitionIdentity: + return TransitionIdentity( + schema_version=SCHEMA_VERSION, + source_commit=COMMIT, + pre_state_commitment=pre_state_commitment, + identity_root=HASHES[1], + delegation_commitment=HASHES[2], + capability_commitment=HASHES[3], + action_digest=HASHES[4], + deterministic_nonce="nonce-uci4-security-review", + fence_commitment=HASHES[5], + verifier_policy_commitment=verifier_policy_commitment(), + admission_policy_commitment=admission_policy_commitment(), + ) + + +def _execution(transition: TransitionIdentity) -> ExecutionReceipt: + return ExecutionReceipt( + receipt_kind=EXECUTION_RECEIPT_KIND, + transition_id=transition.root, + execution_instance_id="exec-uci4-security-review", + outcome=EXECUTION_SUCCEEDED, + result_digest=HASHES[6], + ) + + +def _workflow_text() -> str: + return WORKFLOW.read_text(encoding="utf-8") + + def test_uci4_nominal_effect_chain_surface_exists() -> None: assert DECISION_RECEIPT_KIND == "DECISION_RECEIPT_V1" assert EXECUTION_RECEIPT_KIND == "EXECUTION_RECEIPT_V1" @@ -28,3 +88,82 @@ def test_uci4_nominal_effect_chain_surface_exists() -> None: assert EffectWitness.__name__ == "EffectWitness" assert EffectVerificationResult.__name__ == "EffectVerificationResult" assert CompleteVerificationResult.__name__ == "CompleteVerificationResult" + + +def test_fabricated_effect_witness_is_not_accepted_as_adapter_observation() -> None: + transition = _transition(pre_state_commitment=HASHES[7]) + execution = _execution(transition) + fabricated = EffectWitness( + witness_kind=EFFECT_WITNESS_KIND, + transition_id=transition.root, + execution_instance_id=execution.execution_instance_id, + target_identity="state.txt", + observed_pre_state_commitment=transition.pre_state_commitment, + observed_post_state_commitment=HASHES[8], + effect_changed=True, + pre_observation_provenance=HASHES[9], + post_observation_provenance=HASHES[10], + adapter_identity=FilesystemEffectAdapter.identity, + adapter_version=FilesystemEffectAdapter.version, + ) + + assert is_adapter_bound_effect_evidence(witness=fabricated) is False + result = EffectVerifier().verify_effect( + transition=transition, + execution_receipt=execution, + witness=fabricated, + ) + assert result.status == FALSE + assert result.status != TRUE + + +def test_filesystem_observation_cannot_be_redirected_after_target_validation(tmp_path: Path) -> None: + allowed = tmp_path / "allowed" + allowed.mkdir() + target = allowed / "state.txt" + target.write_bytes(b"inside") + outside = tmp_path / "outside.txt" + outside.write_bytes(b"outside") + + adapter = FilesystemEffectAdapter(allowed_root=allowed) + original_open = Path.open + resolved_target = target.resolve() + + def redirected_open(self: Path, *args, **kwargs): + if self == resolved_target: + return original_open(outside, *args, **kwargs) + return original_open(self, *args, **kwargs) + + with patch.object(Path, "open", new=redirected_open): + observation = adapter._observe_state(target) + + assert observation.target_identity == "state.txt" + assert observation.content_sha256 == hashlib.sha256(b"inside").hexdigest() + + +def test_filesystem_observation_enforces_explicit_size_bound(tmp_path: Path) -> None: + target = tmp_path / "oversized.bin" + target.write_bytes(b"123456789") + adapter = FilesystemEffectAdapter(allowed_root=tmp_path) + adapter.max_observation_bytes = 8 + + with pytest.raises(EffectAdapterError, match="EFFECT_TARGET_TOO_LARGE"): + adapter._observe_state(target) + + +def test_uci4_ci_binds_literal_frozen_parent_and_pr_base() -> None: + text = _workflow_text() + assert f"EXPECTED_PARENT_SHA: {FROZEN_PARENT}" in text + assert "PR_BASE_SHA: ${{ github.event.pull_request.base.sha || '' }}" in text + assert 'test "$PR_BASE_SHA" = "$EXPECTED_PARENT_SHA"' in text + + +def test_uci4_ci_runs_only_for_its_frozen_parent_pr() -> None: + text = _workflow_text() + assert "pull_request:\n branches:\n - " + FROZEN_PARENT_BRANCH in text + + +def test_uci4_ci_locks_expected_hardened_proofline_cardinality() -> None: + text = _workflow_text() + assert "grep -Eq '85 passed'" in text + assert 'echo "UCI4_FULL_PROOFLINE_85=PASS"' in text From 56dc3e1cd34bed9849080dac209be7c7ef7d86ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:47:20 +0200 Subject: [PATCH 09/11] fix(uci): harden filesystem effect observation provenance --- harness/sdk/effect_adapters.py | 196 ++++++++++++++++++++++++++------- 1 file changed, 158 insertions(+), 38 deletions(-) diff --git a/harness/sdk/effect_adapters.py b/harness/sdk/effect_adapters.py index 68eaa1ce0..03245d49b 100644 --- a/harness/sdk/effect_adapters.py +++ b/harness/sdk/effect_adapters.py @@ -9,9 +9,13 @@ """ from __future__ import annotations +import errno import hashlib import os import re +import stat as stat_module +import threading +import weakref from dataclasses import asdict, dataclass from pathlib import Path from typing import Any @@ -22,6 +26,8 @@ EFFECT_WITNESS_KIND = "EFFECT_WITNESS_V1" VERIFY_EFFECT_STATUS = "does not implement VerifyEffect" SHA256_RE = re.compile(r"^[0-9a-f]{64}$") +DEFAULT_MAX_OBSERVATION_BYTES = 16 * 1024 * 1024 +OBSERVATION_READ_CHUNK_BYTES = 64 * 1024 class EffectAdapterError(ValueError): @@ -103,6 +109,24 @@ def root(self) -> str: return canonical_hash("AEGIS_EFFECT_WITNESS_V1", asdict(self)) +_ISSUED_EFFECT_WITNESSES: weakref.WeakValueDictionary[str, EffectWitness] = weakref.WeakValueDictionary() +_ISSUED_EFFECT_WITNESSES_LOCK = threading.RLock() + + +def _register_issued_effect_witness(witness: EffectWitness) -> None: + """Record one adapter-produced witness object for this process-local reference.""" + root = witness.root + with _ISSUED_EFFECT_WITNESSES_LOCK: + _ISSUED_EFFECT_WITNESSES[root] = witness + + +def _is_process_local_issued_effect_witness(witness: EffectWitness) -> bool: + """Nominal local-reference provenance check; not cryptographic attestation.""" + root = witness.root + with _ISSUED_EFFECT_WITNESSES_LOCK: + return _ISSUED_EFFECT_WITNESSES.get(root) is witness + + @dataclass(frozen=True) class FilesystemStateObservation: target_identity: str @@ -122,53 +146,147 @@ class FilesystemEffectAdapter: def __init__(self, *, allowed_root: Path): self.allowed_root = Path(allowed_root).resolve(strict=False) + self.max_observation_bytes = DEFAULT_MAX_OBSERVATION_BYTES def _resolve_target(self, target: Path) -> tuple[Path, str]: - root = self.allowed_root.resolve(strict=False) - resolved_target = Path(target).resolve(strict=False) - if resolved_target != root and root not in resolved_target.parents: + """Lexically bind a target beneath allowed_root without following target symlinks.""" + root = Path(os.path.abspath(os.fspath(self.allowed_root))) + candidate = Path(os.path.abspath(os.fspath(Path(target)))) + try: + common = Path(os.path.commonpath((os.fspath(root), os.fspath(candidate)))) + except ValueError as exc: + raise EffectAdapterError("EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT") from exc + if common != root: + raise EffectAdapterError("EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT") + target_identity = os.path.relpath(os.fspath(candidate), os.fspath(root)) + if target_identity in ("", "."): + raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") + parts = Path(target_identity).parts + if not parts or any(part in ("", ".", "..") for part in parts): + raise EffectAdapterError("EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT") + return candidate, Path(target_identity).as_posix() + + @staticmethod + def _missing_observation(*, target_identity: str) -> FilesystemStateObservation: + return FilesystemStateObservation( + target_identity=target_identity, + exists=False, + content_sha256=ZERO_HASH, + size_bytes=0, + device=0, + inode=0, + mtime_ns=0, + ) + + def _open_beneath_allowed_root(self, *, target_identity: str) -> int: + """Open a regular-file candidate descriptor-relative with symlink following disabled.""" + required = ("O_DIRECTORY", "O_NOFOLLOW") + if os.name != "posix" or any(not hasattr(os, name) for name in required): + raise EffectAdapterError("EFFECT_RACE_RESISTANT_OPEN_UNAVAILABLE") + + root_flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW + file_flags = os.O_RDONLY | os.O_NOFOLLOW + directory_flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW + + parts = Path(target_identity).parts + if not parts or any(part in ("", ".", "..") for part in parts): raise EffectAdapterError("EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT") - return resolved_target, resolved_target.relative_to(root).as_posix() + + try: + root_fd = os.open(os.fspath(self.allowed_root), root_flags) + except (FileNotFoundError, NotADirectoryError, PermissionError, OSError) as exc: + raise EffectAdapterError("EFFECT_ALLOWED_ROOT_UNAVAILABLE") from exc + + opened_dirs: list[int] = [] + dir_fd = root_fd + try: + for part in parts[:-1]: + try: + next_fd = os.open(part, directory_flags, dir_fd=dir_fd) + except FileNotFoundError: + raise + except OSError as exc: + if exc.errno in (errno.ELOOP, errno.ENOTDIR): + raise EffectAdapterError("EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT") from exc + if exc.errno in (errno.EACCES, errno.EPERM): + raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") from exc + raise + opened_dirs.append(next_fd) + dir_fd = next_fd + + try: + return os.open(parts[-1], file_flags, dir_fd=dir_fd) + except FileNotFoundError: + raise + except OSError as exc: + if exc.errno == errno.ELOOP: + raise EffectAdapterError("EFFECT_TARGET_OUTSIDE_ALLOWED_ROOT") from exc + if exc.errno in (errno.EISDIR, errno.ENOTDIR, errno.EACCES, errno.EPERM): + raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") from exc + raise + finally: + for opened_fd in reversed(opened_dirs): + os.close(opened_fd) + os.close(root_fd) def _observe_state(self, target: Path) -> FilesystemStateObservation: - resolved_target, target_identity = self._resolve_target(target) - if not resolved_target.exists(): - return FilesystemStateObservation( - target_identity=target_identity, - exists=False, - content_sha256=ZERO_HASH, - size_bytes=0, - device=0, - inode=0, - mtime_ns=0, - ) - if not resolved_target.is_file(): - raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") + _, target_identity = self._resolve_target(target) + limit = getattr(self, "max_observation_bytes", DEFAULT_MAX_OBSERVATION_BYTES) + if not isinstance(limit, int) or isinstance(limit, bool) or limit < 0: + raise EffectAdapterError("EFFECT_OBSERVATION_SIZE_BOUND_INVALID") + try: - with resolved_target.open("rb") as stream: - content = stream.read() - stat = os.fstat(stream.fileno()) + fd = self._open_beneath_allowed_root(target_identity=target_identity) except FileNotFoundError: + return self._missing_observation(target_identity=target_identity) + + try: + stat_before = os.fstat(fd) + if not stat_module.S_ISREG(stat_before.st_mode): + raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") + if int(stat_before.st_size) > limit: + raise EffectAdapterError("EFFECT_TARGET_TOO_LARGE") + + digest = hashlib.sha256() + total = 0 + while True: + remaining_probe = limit - total + 1 + read_size = min(OBSERVATION_READ_CHUNK_BYTES, max(1, remaining_probe)) + chunk = os.read(fd, read_size) + if not chunk: + break + total += len(chunk) + if total > limit: + raise EffectAdapterError("EFFECT_TARGET_TOO_LARGE") + digest.update(chunk) + + stat_after = os.fstat(fd) + stable_fields_before = ( + int(stat_before.st_dev), + int(stat_before.st_ino), + int(stat_before.st_size), + int(stat_before.st_mtime_ns), + ) + stable_fields_after = ( + int(stat_after.st_dev), + int(stat_after.st_ino), + int(stat_after.st_size), + int(stat_after.st_mtime_ns), + ) + if stable_fields_before != stable_fields_after or int(stat_after.st_size) != total: + raise EffectAdapterError("EFFECT_TARGET_CHANGED_DURING_OBSERVATION") + return FilesystemStateObservation( target_identity=target_identity, - exists=False, - content_sha256=ZERO_HASH, - size_bytes=0, - device=0, - inode=0, - mtime_ns=0, + exists=True, + content_sha256=digest.hexdigest(), + size_bytes=total, + device=int(stat_after.st_dev), + inode=int(stat_after.st_ino), + mtime_ns=int(stat_after.st_mtime_ns), ) - except (IsADirectoryError, PermissionError) as exc: - raise EffectAdapterError("EFFECT_TARGET_NOT_REGULAR_FILE") from exc - return FilesystemStateObservation( - target_identity=target_identity, - exists=True, - content_sha256=hashlib.sha256(content).hexdigest(), - size_bytes=len(content), - device=int(stat.st_dev), - inode=int(stat.st_ino), - mtime_ns=int(stat.st_mtime_ns), - ) + finally: + os.close(fd) @staticmethod def _state_commitment(observation: FilesystemStateObservation) -> str: @@ -304,6 +422,7 @@ def observe_effect( adapter_version=self.version, ) witness.validate() + _register_issued_effect_witness(witness) return witness @@ -313,13 +432,14 @@ def filesystem_state_commitment(*, allowed_root: Path, target: Path) -> str: def is_adapter_bound_effect_evidence(*, witness: EffectWitness) -> bool: - """Structural EffectEvidence candidate check only; not VerifyEffect.""" + """Process-local adapter-issued EffectEvidence check; not cryptographic attestation.""" try: witness.validate() return ( witness.witness_kind == EFFECT_WITNESS_KIND and witness.adapter_identity == FilesystemEffectAdapter.identity and witness.adapter_version == FilesystemEffectAdapter.version + and _is_process_local_issued_effect_witness(witness) ) except (EffectAdapterError, ValueError, TypeError, AttributeError): return False From b458f434dd337291e521ea2cfbf647e49ba574e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:47:54 +0200 Subject: [PATCH 10/11] fix(uci): reject unissued fabricated effect evidence --- harness/sdk/effect_verifier.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/harness/sdk/effect_verifier.py b/harness/sdk/effect_verifier.py index 602444928..51496e8fd 100644 --- a/harness/sdk/effect_verifier.py +++ b/harness/sdk/effect_verifier.py @@ -194,9 +194,12 @@ def verify_effect( adapter_key = (witness.adapter_identity, witness.adapter_version) adapter_supported = adapter_key in self.supported_adapters obligations["V_adapter_binding"] = TRUE if adapter_supported else UNKNOWN - obligations["V_effect_evidence"] = ( - TRUE if adapter_supported and is_adapter_bound_effect_evidence(witness=witness) else UNKNOWN - ) + if adapter_supported: + obligations["V_effect_evidence"] = ( + TRUE if is_adapter_bound_effect_evidence(witness=witness) else FALSE + ) + else: + obligations["V_effect_evidence"] = UNKNOWN if any(obligations[name] == FALSE for name in OBLIGATION_ORDER): return self._result( From bda5c5369b15ec91f2651ddbf6d219f6b7a1d0f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tarik=20Skali=C4=87?= <228550385+tarikskalic33@users.noreply.github.com> Date: Thu, 20 Aug 2026 16:48:14 +0200 Subject: [PATCH 11/11] fix(uci): bind UCI-4 gate to hardened proof contract --- .../workflows/uci-4-effect-chain-contract.yml | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/.github/workflows/uci-4-effect-chain-contract.yml b/.github/workflows/uci-4-effect-chain-contract.yml index a1ef19da4..04c5cafde 100644 --- a/.github/workflows/uci-4-effect-chain-contract.yml +++ b/.github/workflows/uci-4-effect-chain-contract.yml @@ -2,6 +2,8 @@ name: UCI-4 Effect Chain Contract on: pull_request: + branches: + - feat/uci-1-collective-work-contract-v1 workflow_dispatch: permissions: @@ -14,7 +16,8 @@ jobs: timeout-minutes: 15 env: CANDIDATE_SHA: ${{ github.event.pull_request.head.sha || github.sha }} - EXPECTED_PARENT_SHA: ${{ github.event.pull_request.base.sha || '' }} + EXPECTED_PARENT_SHA: ebec2f9c8fa00f54605d859df61512108ff3b71d + PR_BASE_SHA: ${{ github.event.pull_request.base.sha || '' }} steps: - name: Checkout exact candidate uses: actions/checkout@v4 @@ -28,11 +31,13 @@ jobs: set -euo pipefail actual="$(git rev-parse HEAD)" test "$actual" = "$CANDIDATE_SHA" - echo "CANDIDATE_SHA=$actual" - if [[ -n "$EXPECTED_PARENT_SHA" ]]; then - test "$(git merge-base "$EXPECTED_PARENT_SHA" HEAD)" = "$EXPECTED_PARENT_SHA" - echo "EXPECTED_PARENT_SHA=$EXPECTED_PARENT_SHA" + test "$(git merge-base "$EXPECTED_PARENT_SHA" HEAD)" = "$EXPECTED_PARENT_SHA" + if [[ -n "$PR_BASE_SHA" ]]; then + test "$PR_BASE_SHA" = "$EXPECTED_PARENT_SHA" fi + echo "CANDIDATE_SHA=$actual" + echo "EXPECTED_PARENT_SHA=$EXPECTED_PARENT_SHA" + echo "UCI4_EXACT_LINEAGE=PASS" - name: Set up Python uses: actions/setup-python@v5 @@ -63,6 +68,7 @@ jobs: schema = json.loads(path.read_text(encoding='utf-8')) validator_for(schema).check_schema(schema) print(f'UCI4_SCHEMA_OK {path}') + print(f'UCI4_SCHEMA_COUNT={len(paths)}') PY - name: Run frozen proofline plus UCI integration falsifiers @@ -78,6 +84,8 @@ jobs: sovereign-omega-v2/python/tests/test_complete_verifier_pr4_receipt_binding.py \ sovereign-omega-v2/python/tests/test_uci4_effect_chain_integration.py \ -q | tee "$RUNNER_TEMP/uci4-pytest.log" + grep -Eq '85 passed' "$RUNNER_TEMP/uci4-pytest.log" + echo "UCI4_FULL_PROOFLINE_85=PASS" - name: Write exact-head witness summary if: always() @@ -89,6 +97,14 @@ jobs: echo "candidate_sha=$(git rev-parse HEAD)" echo "expected_parent_sha=$EXPECTED_PARENT_SHA" echo "authority=EVIDENCE_ONLY" + echo "frozen_parent_binding=REQUIRED_AND_TESTED" + echo "proofline_cardinality=85_REQUIRED_AND_TESTED" + echo "pull_request_trigger_scope=FROZEN_PARENT_BRANCH_ONLY" + echo "effect_witness_issuance=PROCESS_LOCAL_ADAPTER_ISSUED_ONLY" + echo "filesystem_descriptor_relative_open=REQUIRED_AND_TESTED_REFERENCE" + echo "filesystem_observation_size_bound=REQUIRED_AND_TESTED" + echo "cross_process_effect_witness_attestation=NOT_ESTABLISHED" + echo "concurrent_file_mutation_snapshot_proof=NOT_ESTABLISHED" echo "atomic_admission=NOT_IMPLEMENTED" echo "effect_bound_admission=UNAVAILABLE" echo "production_admission=NOT_ESTABLISHED"