From db64500a12a64206e1bb7fd0c9a0a21bca755c3d Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Wed, 15 Jul 2026 03:59:47 +0200 Subject: [PATCH] feat: rehearse approved proposal apply and rollback --- .../proposal-apply-clone-rollback-current.sql | 120 + ...oposal-apply-lifecycle-canary-current.json | 3324 +++++++++++++++++ ...posal-apply-lifecycle-receipt-current.json | 217 ++ ...ly-production-target-readback-current.json | 32 + pyproject.toml | 1 + scripts/apply_proposal.py | 369 +- scripts/apply_worker.py | 200 +- .../finalize_approve_claim_isolated_canary.py | 230 ++ scripts/kb_apply_replay_receipt.py | 35 + scripts/proposal_apply_lifecycle.py | 1513 ++++++++ scripts/run_approve_claim_clone_canary.py | 857 ++--- ...approve_claim_isolated_container_canary.sh | 268 +- tests/test_apply_proposal.py | 42 +- tests/test_apply_worker.py | 312 +- tests/test_proposal_apply_lifecycle.py | 846 +++++ ...n_approve_claim_clone_canary_macos_path.py | 82 +- 16 files changed, 7507 insertions(+), 941 deletions(-) create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json create mode 100644 scripts/finalize_approve_claim_isolated_canary.py create mode 100644 scripts/proposal_apply_lifecycle.py create mode 100644 tests/test_proposal_apply_lifecycle.py diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql b/docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql new file mode 100644 index 0000000..299b52b --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql @@ -0,0 +1,120 @@ +begin; +set local standard_conforming_strings = on; +set local lock_timeout = '5s'; +select pg_advisory_xact_lock(hashtextextended('proposal-rollback:' || E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946', 0)); +do $rollback$ +declare + affected integer; + approval_now jsonb; +begin + perform 1 from kb_stage.kb_proposals + where id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid + for update; + if not found then + raise exception 'proposal rollback: proposal row missing'; + end if; + if not exists ( + select 1 from kb_stage.kb_proposals + where id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid + and proposal_type = 'approve_claim' + and status = 'applied' + and payload = E'{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural"},{"confidence":0.85,"created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept"}],"contract_version":2,"edges":[{"created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}],"evidence":[{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_by":null,"role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9},{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_by":null,"role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}]}}'::jsonb + and reviewed_by_handle is not distinct from E'm3ta' + and reviewed_by_agent_id is not distinct from E'11111111-1111-4111-8111-111111111111'::uuid + and reviewed_at is not distinct from E'2026-07-15 01:59:03.105846+00'::timestamptz + and review_note is not distinct from E'Reviewed exact strict payload inside the disposable clone.' + and applied_by_handle is not distinct from E'kb-apply' + and applied_by_agent_id is not distinct from E'44444444-4444-4444-4444-444444444444'::uuid + and applied_at is not distinct from E'2026-07-15 01:59:04.449051+00'::timestamptz + ) then + raise exception 'proposal rollback: applied ledger does not match apply receipt'; + end if; + select jsonb_build_object( + 'proposal_id', proposal_id::text, + 'proposal_type', proposal_type, + 'payload', payload, + 'reviewed_by_handle', reviewed_by_handle, + 'reviewed_by_agent_id', reviewed_by_agent_id::text, + 'reviewed_by_db_role', reviewed_by_db_role::text, + 'reviewed_at', reviewed_at::text, + 'review_note', review_note + ) into approval_now + from kb_stage.kb_proposal_approvals + where proposal_id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid; + if approval_now is distinct from E'{"payload":{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural"},{"confidence":0.85,"created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept"}],"contract_version":2,"edges":[{"created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}],"evidence":[{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_by":null,"role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9},{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_by":null,"role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}]}},"proposal_id":"9b2b5fa4-0e0d-5db0-aa4d-6fd102889946","proposal_type":"approve_claim","review_note":"Reviewed exact strict payload inside the disposable clone.","reviewed_at":"2026-07-15 01:59:03.105846+00","reviewed_by_agent_id":"11111111-1111-4111-8111-111111111111","reviewed_by_db_role":"kb_review","reviewed_by_handle":"m3ta"}'::jsonb then + raise exception 'proposal rollback: immutable approval snapshot drifted'; + end if; + if (select count(*) from public.claim_evidence t + where t.id = E'dac3934f-b7ad-5205-8efe-a00163370a54'::uuid and to_jsonb(t) = E'{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"dac3934f-b7ad-5205-8efe-a00163370a54","role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claim_evidence[0] row does not match apply receipt'; + end if; + if (select count(*) from public.claim_evidence t + where t.id = E'13e580c5-458d-5146-aef3-8c74ffc43b7c'::uuid and to_jsonb(t) = E'{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"13e580c5-458d-5146-aef3-8c74ffc43b7c","role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claim_evidence[1] row does not match apply receipt'; + end if; + if (select count(*) from public.claim_edges t + where t.id = E'b28d8770-9ac9-5b5f-bfe2-7280d7422a21'::uuid and to_jsonb(t) = E'{"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","id":"b28d8770-9ac9-5b5f-bfe2-7280d7422a21","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claim_edges[0] row does not match apply receipt'; + end if; + if (select count(*) from public.reasoning_tools t + where t.id = E'2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0'::uuid and to_jsonb(t) = E'{"agent_id":null,"category":"verification","created_at":"2026-07-15T01:59:04.44165+00:00","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: reasoning_tools[0] row does not match apply receipt'; + end if; + if (select count(*) from public.sources t + where t.id = E'ed9da5f1-846e-5298-8689-15027a7ce56d'::uuid and to_jsonb(t) = E'{"captured_at":null,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: sources[0] row does not match apply receipt'; + end if; + if (select count(*) from public.sources t + where t.id = E'a86f55dc-5058-585d-b492-a4f1d932670f'::uuid and to_jsonb(t) = E'{"captured_at":null,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: sources[1] row does not match apply receipt'; + end if; + if (select count(*) from public.claims t + where t.id = E'71a3331b-fb75-5e18-aa61-b256bc38af36'::uuid and to_jsonb(t) = E'{"confidence":0.85,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept","updated_at":"2026-07-15T01:59:04.44165+00:00"}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claims[0] row does not match apply receipt'; + end if; + if (select count(*) from public.claims t + where t.id = E'38dead14-6b1d-5909-99c4-3c45ad2dd21f'::uuid and to_jsonb(t) = E'{"confidence":0.9,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural","updated_at":"2026-07-15T01:59:04.44165+00:00"}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claims[1] row does not match apply receipt'; + end if; + delete from public.claim_evidence where id in (E'dac3934f-b7ad-5205-8efe-a00163370a54'::uuid, E'13e580c5-458d-5146-aef3-8c74ffc43b7c'::uuid); + get diagnostics affected = row_count; + if affected <> 2 then + raise exception 'proposal rollback count mismatch for claim_evidence: expected 2, got %', affected; + end if; + delete from public.claim_edges where id in (E'b28d8770-9ac9-5b5f-bfe2-7280d7422a21'::uuid); + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback count mismatch for claim_edges: expected 1, got %', affected; + end if; + delete from public.reasoning_tools where id in (E'2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0'::uuid); + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback count mismatch for reasoning_tools: expected 1, got %', affected; + end if; + delete from public.sources where id in (E'ed9da5f1-846e-5298-8689-15027a7ce56d'::uuid, E'a86f55dc-5058-585d-b492-a4f1d932670f'::uuid); + get diagnostics affected = row_count; + if affected <> 2 then + raise exception 'proposal rollback count mismatch for sources: expected 2, got %', affected; + end if; + delete from public.claims where id in (E'71a3331b-fb75-5e18-aa61-b256bc38af36'::uuid, E'38dead14-6b1d-5909-99c4-3c45ad2dd21f'::uuid); + get diagnostics affected = row_count; + if affected <> 2 then + raise exception 'proposal rollback count mismatch for claims: expected 2, got %', affected; + end if; + update kb_stage.kb_proposals + set status = 'approved', + applied_by_handle = null, + applied_by_agent_id = null, + applied_at = null, + updated_at = now() + where id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid + and status = 'applied' + and payload = E'{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural"},{"confidence":0.85,"created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept"}],"contract_version":2,"edges":[{"created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}],"evidence":[{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_by":null,"role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9},{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_by":null,"role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}]}}'::jsonb + and applied_at is not distinct from E'2026-07-15 01:59:04.449051+00'::timestamptz; + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback: expected one applied ledger row, got %', affected; + end if; +end +$rollback$; +commit; diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json new file mode 100644 index 0000000..d494493 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json @@ -0,0 +1,3324 @@ +{ + "applied_rollback_receipt": { + "applied_row_ids": { + "claim_edges": [ + "b28d8770-9ac9-5b5f-bfe2-7280d7422a21" + ], + "claim_evidence": [ + "13e580c5-458d-5146-aef3-8c74ffc43b7c", + "dac3934f-b7ad-5205-8efe-a00163370a54" + ], + "claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + }, + "applied_row_sha256": { + "claim_edges": [ + "60d14cbc45d424fa1eb62db6478f185bf2c2d86559e57ffa77474e8d0b3c527c" + ], + "claim_evidence": [ + "e97de162c0730da69b357e1350f0da7e5843c4e67d498c12d731a0f04a8868d7", + "3054718195abe3f78e08cfcacd59902d41eb91c7153c6c63f9bcb3bc7c6902ac" + ], + "claims": [ + "e593b9cf7b0bc9267f3e48d8173129b84b09c028f290f1abc97ccb73940b31dc", + "24280b5159cbbf75e00eb24ae50ab69994397a583f347a241a5e262cf0c03179" + ], + "reasoning_tools": [ + "257924b236c3c27289b9ae2314687be0e96779415fc6ff87faada22893b757ba" + ], + "sources": [ + "d7a690c2b18701db5b48e8189b1621c8025c1ed75889a4d0a3a4c9761c03dc3b", + "bee048794799006bd00bf4bb02ae57998e670f5bcdfb140dff871dcb042d0198" + ] + }, + "apply_payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684", + "apply_receipt_sha256": "2aa65fe4509c6699af34e418de27c0225efe751223aaebf3f9b545fa159c754e", + "apply_sql_sha256": "1a7d65870e415ee8891b6dd84732a29bcee00412c10862d3d1db4b191a8ade38", + "approval_snapshot_sha256": "310b54030e26ed43d7226f2e803610586e80d21ff064c67e11ea21e93ef40bd9", + "artifact": "proposal_apply_lifecycle_receipt", + "checks": { + "apply_receipt_replay_ready": true, + "apply_rows_have_exact_ids": true, + "fresh_owned_targets": true, + "immutable_approval_unchanged": true, + "rollback_counts_restored": true, + "rollback_ledger_restored_to_approved": true, + "rollback_replay_refused_without_mutation": true, + "rollback_sql_exact_for_bound_rows": true, + "rollback_target_rows_absent": true, + "unrelated_rows_unchanged": true + }, + "current_tier": "T2_runtime", + "fresh_preflight_artifact_sha256": "1d5e7eed7355aa94ecfa213422e6178aecdeee70628994596e852bd7569d6a2d", + "fresh_preflight_sha256": "29a80dc9422af45fd067e98621442d31c60161f34deb5845aa410659a9c5de3a", + "generated_at_utc": "2026-07-15T01:59:05.526171+00:00", + "lifecycle_receipt_sha256": "8e74cf1f5c372633f9b6f15363ff759ba6ce9bf42df51288bb9be70ddabe49b3", + "pass": true, + "production_apply_authorization_present": false, + "production_apply_executed": false, + "proposal_before_apply": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "proposal_payload_sha256": "6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af", + "required_tier": "T2_runtime", + "rollback": { + "counts_after_rollback": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "counts_before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "delete_order": [ + "claim_evidence", + "claim_edges", + "reasoning_tools", + "sources", + "claims" + ], + "proposal_after": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3", + "schema": "livingip.proposalApplyRollback.v1", + "target_rows_after": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + } + }, + "schema": "livingip.proposalApplyLifecycleReceipt.v1", + "strongest_claim_allowed": "deterministic disposable-clone apply and compensating rollback" + }, + "apply_transition": { + "applied_readback": { + "applied_at": "2026-07-15 01:59:04.449051+00", + "applied_by_agent_id": "44444444-4444-4444-4444-444444444444", + "applied_by_handle": "kb-apply", + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "applied" + }, + "approved_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "exact_transition": true, + "immutable_approval_matches_applied_ledger": true, + "immutable_approval_readback": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + } + }, + "checks": { + "apply_transition_exact": true, + "claim_evidence_exact_after_permission_probe": true, + "clone_apply_table_deltas_exact": true, + "clone_cleanup_complete": true, + "clone_prerequisites_omit_cluster_role_ddl": true, + "compensating_rollback_receipt_passes": true, + "conflict_review_separate_and_exact": true, + "conflict_transaction_rolled_back_exactly": true, + "first_apply_succeeded": true, + "fresh_owned_preflight_exact": true, + "gateway_process_unchanged": null, + "idempotent_replay_refused": true, + "kb_apply_cannot_mutate_immutable_approval": true, + "kb_apply_cannot_mutate_proposal_approval": true, + "kb_apply_cannot_mutate_proposal_payload": true, + "kb_apply_cannot_update_existing_claim_evidence": true, + "kb_review_approval_succeeded": true, + "kb_review_used_security_definer": true, + "mutation_between_build_and_execute_refused": true, + "outer_container_absent_independent_readback": true, + "outer_container_has_no_docker_volumes": true, + "outer_container_lifecycle_labeled": true, + "outer_container_network_disabled": true, + "outer_isolation_complete": true, + "outer_labeled_container_absent_independent_readback": true, + "outer_live_counts_exactly_unchanged": true, + "outer_live_table_deltas_exactly_zero": true, + "outer_service_observed_if_required": true, + "outer_service_stable_if_observed": true, + "outer_source_tier_enforced": true, + "outer_workdir_absent_independent_readback": true, + "payload_agents_seeded_exactly": true, + "payload_projection_exact": true, + "proposal_exact_after_permission_probes": true, + "review_transition_exact": true, + "reviewer_agent_seeded_exactly": true, + "rollback_replay_refused_without_mutation": true, + "security_definer_dependencies_ready": true, + "source_binding_independently_verified": true, + "source_cluster_roles_preexisting": true, + "source_cluster_roles_unchanged": true, + "source_files_unchanged_during_run": true, + "source_hash_conflict_refused": true, + "source_table_deltas_exactly_zero": true, + "source_target_rows_unchanged": true, + "stale_ephemeral_paths_absent": true + }, + "cleanup_readback": { + "drop_attempted": true, + "leftover_clone_database_count": 0, + "returncode": 0, + "stderr": "", + "stdout": "pg_terminate_backend \n----------------------\n(0 rows)\n\nDROP DATABASE" + }, + "cleanup_returncode": 0, + "clone_apply_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + }, + "clone_counts_after_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 1, + "public.claim_evidence": 3, + "public.claims": 3, + "public.reasoning_tools": 1, + "public.sources": 3 + }, + "clone_counts_before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "clone_created": true, + "clone_database": "teleo_approve_claim_isolated_08c2358330", + "clone_dropped": true, + "clone_prerequisite_sanitization": { + "clone_sql_sha256": "a02a03a704954058745d8cee4d25597bb8eaeee2ae211d4f9be4fe055f3d98ec", + "cluster_role_ddl_removed": true, + "removed_alter_role_statements": 3, + "removed_create_role_statements": 2, + "source_sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa" + }, + "conflict_apply": { + "returncode": 1, + "source_hash_collision_refusal": true, + "stderr": "psql failed (3)\nSTDOUT:\n\n\nSTDERR:\nERROR: approve_claim: source hash collision for source 980ac481-1fa3-508f-b093-95de4ff4f8bc\nCONTEXT: PL/pgSQL function inline_code_block line 6 at RAISE" + }, + "conflict_readback": { + "canonical_rows": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "canonical_rows_absent": true, + "immutable_approval": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "7cb1468f-861c-5a85-ae32-470d891af133", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_unchanged": true, + "proposal": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "7cb1468f-861c-5a85-ae32-470d891af133", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-conflict-canary", + "status": "approved" + }, + "proposal_approval_unchanged": true + }, + "conflict_review_transition": { + "approved_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "7cb1468f-861c-5a85-ae32-470d891af133", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-conflict-canary", + "status": "approved" + }, + "exact_transition": true, + "immutable_approval_matches_ledger": true, + "immutable_approval_readback": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "7cb1468f-861c-5a85-ae32-470d891af133", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "pending_immutable_approval": null, + "pending_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "7cb1468f-861c-5a85-ae32-470d891af133", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": null, + "reviewed_at": null, + "reviewed_by_agent_id": null, + "reviewed_by_handle": null, + "source_ref": "working-leo:approve-claim-conflict-canary", + "status": "pending_review" + }, + "review_apply": { + "returncode": 0, + "stderr": "", + "stdout": "{\"id\": \"7cb1468f-861c-5a85-ae32-470d891af133\", \"proposal_type\": \"approve_claim\", \"review_note\": \"Reviewed exact strict payload inside the disposable clone.\", \"reviewed_at\": \"2026-07-15 01:59:06.046049+00\", \"reviewed_by_agent_id\": \"11111111-1111-4111-8111-111111111111\", \"reviewed_by_db_role\": \"kb_review\", \"reviewed_by_handle\": \"m3ta\", \"status\": \"approved\"}" + } + }, + "current_tier": "T2_runtime", + "dependencies": { + "apply_role": "kb_apply", + "apply_script": "scripts/apply_proposal.py", + "approve_script": "scripts/approve_proposal.py", + "credential_files": { + "apply": { + "present_at_start": true, + "runtime_location": "ephemeral_or_operator_managed" + }, + "review": { + "present_at_start": true, + "runtime_location": "ephemeral_or_operator_managed" + } + }, + "gate_owner_role": "kb_gate_owner", + "immutable_approval_table": "kb_stage.kb_proposal_approvals", + "prerequisites_sql": "scripts/kb_apply_prereqs.sql", + "review_role": "kb_review", + "reviewer_handle": "m3ta", + "security_definer_argument_types": { + "kb_stage.approve_strict_proposal": "uuid, text, jsonb, text, text", + "kb_stage.assert_approved_proposal": "uuid, text, jsonb, text, uuid, timestamp with time zone, text", + "kb_stage.finish_approved_proposal": "uuid, text, jsonb, text, uuid, timestamp with time zone, text, text" + }, + "security_definer_functions": [ + "kb_stage.approve_strict_proposal", + "kb_stage.assert_approved_proposal", + "kb_stage.finish_approved_proposal" + ] + }, + "external_claim_seed": { + "clone_rows": [], + "exact_clone_copy": true, + "required_ids": [], + "source_rows": [] + }, + "first_apply": { + "result": { + "applied": true, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "replay_material_sha256": "767fe35fc91b4cd9b82287e828d6a6a86521bd82f23b651e9a91db977c55af59", + "replay_ready": true + }, + "returncode": 0, + "stderr": "" + }, + "fixture_ids": { + "conflict_claim_id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "conflict_proposal_id": "7cb1468f-861c-5a85-ae32-470d891af133", + "conflict_source_id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "permission_claim_id": "199b9d5e-904a-52cf-9f2c-3ee97f3f54b4", + "permission_source_id": "c4893cfb-8725-510e-ae01-ef01e8c99723", + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946" + }, + "fresh_owned_preflight": { + "apply_payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684", + "artifact": "proposal_apply_fresh_preflight", + "captured_at_utc": "2026-07-15T01:59:04.091167+00:00", + "fresh_owned_targets": true, + "preflight_artifact_sha256": "1d5e7eed7355aa94ecfa213422e6178aecdeee70628994596e852bd7569d6a2d", + "preflight_sha256": "29a80dc9422af45fd067e98621442d31c60161f34deb5845aa410659a9c5de3a", + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_payload_sha256": "6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af", + "proposal_type": "approve_claim", + "schema": "livingip.proposalApplyFreshPreflight.v1", + "target_rows_before": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + } + }, + "gateway_process_observable": false, + "generated_at_utc": "2026-07-15T01:59:02.013207+00:00", + "idempotent_replay": { + "already_applied_refusal": true, + "returncode": 1, + "stderr": "proposal 9b2b5fa4-0e0d-5db0-aa4d-6fd102889946 is already applied (idempotent no-op)" + }, + "kb_apply_claim_evidence_update_probe": { + "after": { + "claim_id": "199b9d5e-904a-52cf-9f2c-3ee97f3f54b4", + "created_by": null, + "role": "grounds", + "source_id": "c4893cfb-8725-510e-ae01-ef01e8c99723", + "weight": 0.42 + }, + "attempt": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table claim_evidence", + "stdout": "" + }, + "before": { + "claim_id": "199b9d5e-904a-52cf-9f2c-3ee97f3f54b4", + "created_by": null, + "role": "grounds", + "source_id": "c4893cfb-8725-510e-ae01-ef01e8c99723", + "weight": 0.42 + }, + "exact_row_unchanged": true + }, + "kb_apply_proposal_mutation_probes": { + "after": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "approval_fields": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table kb_proposals", + "stdout": "" + }, + "before": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "exact_row_unchanged": true, + "immutable_approval_after": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_before": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_exactly_unchanged": true, + "immutable_approval_payload": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table kb_proposal_approvals", + "stdout": "" + }, + "payload": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table kb_proposals", + "stdout": "" + } + }, + "leftover_clone_count": 0, + "mutation_between_build_and_execute": { + "approval_restored_exactly": true, + "approved_before_mutation": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "build_returncode": 0, + "built_sql_sha256": "19f5dd6c27f293bfdc89adee59ffa99beb4503dba5aaa6291c9808ff16769ee7", + "built_sql_uses_assert_approved_proposal": true, + "built_sql_uses_finish_approved_proposal": true, + "canonical_rows_unchanged": true, + "immutable_approval_after_ledger_mutation": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_after_stale_execute": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_before_mutation": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_never_changed": true, + "mutated_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "mutation_after_build": true + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "proposal_after_stale_execute": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "mutation_after_build": true + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "restored_immutable_approval": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "restored_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "rows_after_stale_execute": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "stale_apply_refused": true, + "stale_execute": { + "returncode": 3, + "stderr": "ERROR: assert_approved_proposal: proposal 9b2b5fa4-0e0d-5db0-aa4d-6fd102889946 is not the exact immutable approved snapshot\nCONTEXT: PL/pgSQL function kb_stage.assert_approved_proposal(uuid,text,jsonb,text,uuid,timestamp with time zone,text) line 22 at RAISE", + "stdout": "" + } + }, + "outer_isolation": { + "canonical_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 0, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "cleanup_readback": { + "container_name_query_returncode": 0, + "container_name_query_stdout": "", + "container_remove_returncode": 0, + "label_scoped_orphan_count": 0, + "label_scoped_query_returncode": 0, + "label_scoped_query_stdout": "", + "label_scoped_temporary_container_absent": true, + "temporary_container_absent": true, + "temporary_workdir_absent": true, + "workdir_lexists_after_cleanup": false, + "workdir_remove_returncode": 0 + }, + "container_isolation": { + "canary_label": "proposal-apply-lifecycle", + "instance_label": "working-leo-approve-claim-6715-44544", + "lifecycle_labeled": true, + "network_disabled": true, + "network_mode": "none", + "no_docker_volumes": true, + "volume_mounts": [] + }, + "service_after": { + "Available": "false", + "Reason": "systemctl_unavailable" + }, + "service_before": { + "Available": "false", + "Reason": "systemctl_unavailable" + }, + "service_gate_passes": true, + "service_observable": false, + "service_required": false, + "service_stable": null, + "source_boundary": { + "canary_label": "working-leo-approved-source", + "network_mode": "none", + "source_tier": "isolated-local", + "tier_enforced": true + }, + "source_container": "leo-proposal-lifecycle-source-20260715", + "source_counts_after": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_before": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_unchanged": true, + "source_database": "teleo" + }, + "payload_agent_seed": { + "clone_rows": [], + "exact_clone_copy": true, + "required_ids": [], + "source_rows": [] + }, + "payload_projection": { + "expected_rows": { + "claim_edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "expected_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + }, + "payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684" + }, + "post_run_cleanup": { + "gateway_active_state": null, + "gateway_main_pid": null, + "gateway_process_observable": false, + "gateway_restart_count": null, + "gateway_sub_state": null, + "leftover_clone_database_count": 0 + }, + "required_tier": "T2_runtime", + "required_tiers": [ + "T2_runtime" + ], + "review_transition": { + "approved_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "exact_transition": true, + "immutable_approval_matches_ledger": true, + "immutable_approval_readback": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "pending_immutable_approval": null, + "pending_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": null, + "reviewed_at": null, + "reviewed_by_agent_id": null, + "reviewed_by_handle": null, + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "pending_review" + }, + "review_apply": { + "returncode": 0, + "stderr": "", + "stdout": "{\"id\": \"9b2b5fa4-0e0d-5db0-aa4d-6fd102889946\", \"proposal_type\": \"approve_claim\", \"review_note\": \"Reviewed exact strict payload inside the disposable clone.\", \"reviewed_at\": \"2026-07-15 01:59:03.105846+00\", \"reviewed_by_agent_id\": \"11111111-1111-4111-8111-111111111111\", \"reviewed_by_db_role\": \"kb_review\", \"reviewed_by_handle\": \"m3ta\", \"status\": \"approved\"}" + }, + "review_dry_run": { + "returncode": 0, + "stderr": "", + "uses_approve_strict_proposal": true + } + }, + "reviewer_agent_seed": { + "clone_row": { + "handle": "m3ta", + "id": "11111111-1111-4111-8111-111111111111", + "kind": "human" + }, + "exact_clone_copy": true, + "reviewer_handle": "m3ta", + "source_row": { + "handle": "m3ta", + "id": "11111111-1111-4111-8111-111111111111", + "kind": "human" + } + }, + "rollback_plan": { + "execution_authority": "disposable_clone_admin_only", + "rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3", + "schema": "livingip.proposalApplyRollback.v1" + }, + "rollback_replay": { + "refused": true, + "returncode": 3, + "state_unchanged": true, + "stderr": "ERROR: proposal rollback: applied ledger does not match apply receipt\nCONTEXT: PL/pgSQL function inline_code_block line 26 at RAISE" + }, + "row_readback": { + "claim_edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "runtime_scope": "isolated_postgres_container_from_readonly_local_fixture", + "schema": "working-leo.approve-claim-clone-canary.v3", + "security_definer_readback": { + "functions": [ + { + "argument_types": "uuid, text, jsonb, text, text", + "expected_role": "kb_review", + "expected_role_execute": true, + "function": "kb_stage.approve_strict_proposal", + "identity_arguments": "p_proposal_id uuid, p_expected_proposal_type text, p_expected_payload jsonb, p_expected_reviewed_by_handle text, p_review_note text", + "other_runtime_role_execute": false, + "owner": "kb_gate_owner", + "present": true, + "public_execute": false, + "security_definer": true + }, + { + "argument_types": "uuid, text, jsonb, text, uuid, timestamp with time zone, text", + "expected_role": "kb_apply", + "expected_role_execute": true, + "function": "kb_stage.assert_approved_proposal", + "identity_arguments": "p_proposal_id uuid, p_proposal_type text, p_payload jsonb, p_reviewed_by_handle text, p_reviewed_by_agent_id uuid, p_reviewed_at timestamp with time zone, p_review_note text", + "other_runtime_role_execute": false, + "owner": "kb_gate_owner", + "present": true, + "public_execute": false, + "security_definer": true + }, + { + "argument_types": "uuid, text, jsonb, text, uuid, timestamp with time zone, text, text", + "expected_role": "kb_apply", + "expected_role_execute": true, + "function": "kb_stage.finish_approved_proposal", + "identity_arguments": "p_proposal_id uuid, p_proposal_type text, p_payload jsonb, p_reviewed_by_handle text, p_reviewed_by_agent_id uuid, p_reviewed_at timestamp with time zone, p_review_note text, p_applied_by_handle text", + "other_runtime_role_execute": false, + "owner": "kb_gate_owner", + "present": true, + "public_execute": false, + "security_definer": true + } + ], + "login_role_owned_objects": [], + "protected_role_memberships": [], + "role_privileges": { + "kb_apply_approval_snapshot_select": false, + "kb_apply_approval_snapshot_update": false, + "kb_apply_claim_evidence_insert": true, + "kb_apply_claim_evidence_update": false, + "kb_apply_proposal_select": true, + "kb_apply_proposal_update": false, + "kb_review_approval_snapshot_select": false, + "kb_review_approval_snapshot_update": false, + "kb_review_proposal_select": true, + "kb_review_proposal_update": false + }, + "unexpected_overloads": [] + }, + "service_after": { + "available": false, + "reason": "systemctl_unavailable", + "returncode": 127 + }, + "service_before": { + "available": false, + "reason": "systemctl_unavailable", + "returncode": 127 + }, + "service_restarted": null, + "source_binding": { + "files": [ + { + "repo_relative_path": "scripts/apply_proposal.py", + "sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "size_bytes": 47270 + }, + { + "repo_relative_path": "scripts/approve_proposal.py", + "sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "size_bytes": 7323 + }, + { + "repo_relative_path": "scripts/finalize_approve_claim_isolated_canary.py", + "sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "size_bytes": 10684 + }, + { + "repo_relative_path": "scripts/kb_apply_prereqs.sql", + "sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "size_bytes": 42602 + }, + { + "repo_relative_path": "scripts/proposal_apply_lifecycle.py", + "sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "size_bytes": 71314 + }, + { + "repo_relative_path": "scripts/run_approve_claim_clone_canary.py", + "sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "size_bytes": 88340 + }, + { + "repo_relative_path": "scripts/run_approve_claim_isolated_container_canary.sh", + "sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "size_bytes": 9239 + } + ], + "independent_post_cleanup_verification": { + "all_match": true, + "files": [ + { + "actual_sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "expected_sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "matches": true, + "repo_relative_path": "scripts/apply_proposal.py", + "size_bytes": 47270 + }, + { + "actual_sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "expected_sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "matches": true, + "repo_relative_path": "scripts/approve_proposal.py", + "size_bytes": 7323 + }, + { + "actual_sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "expected_sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "matches": true, + "repo_relative_path": "scripts/finalize_approve_claim_isolated_canary.py", + "size_bytes": 10684 + }, + { + "actual_sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "expected_sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "matches": true, + "repo_relative_path": "scripts/kb_apply_prereqs.sql", + "size_bytes": 42602 + }, + { + "actual_sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "expected_sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "matches": true, + "repo_relative_path": "scripts/proposal_apply_lifecycle.py", + "size_bytes": 71314 + }, + { + "actual_sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "expected_sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "matches": true, + "repo_relative_path": "scripts/run_approve_claim_clone_canary.py", + "size_bytes": 88340 + }, + { + "actual_sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "expected_sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "matches": true, + "repo_relative_path": "scripts/run_approve_claim_isolated_container_canary.sh", + "size_bytes": 9239 + } + ] + }, + "post_run_files": [ + { + "repo_relative_path": "scripts/apply_proposal.py", + "sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "size_bytes": 47270 + }, + { + "repo_relative_path": "scripts/approve_proposal.py", + "sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "size_bytes": 7323 + }, + { + "repo_relative_path": "scripts/finalize_approve_claim_isolated_canary.py", + "sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "size_bytes": 10684 + }, + { + "repo_relative_path": "scripts/kb_apply_prereqs.sql", + "sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "size_bytes": 42602 + }, + { + "repo_relative_path": "scripts/proposal_apply_lifecycle.py", + "sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "size_bytes": 71314 + }, + { + "repo_relative_path": "scripts/run_approve_claim_clone_canary.py", + "sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "size_bytes": 88340 + }, + { + "repo_relative_path": "scripts/run_approve_claim_isolated_container_canary.sh", + "sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "size_bytes": 9239 + } + ], + "unchanged_during_run": true + }, + "source_cluster_roles_after": [ + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_apply", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": false, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_gate_owner", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_review", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + } + ], + "source_cluster_roles_before": [ + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_apply", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": false, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_gate_owner", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_review", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + } + ], + "source_counts_after": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_before": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_observed_unchanged": true, + "source_database": "teleo", + "source_database_mutated": false, + "source_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 0, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_target_rows_after": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "source_target_rows_before": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "status": "pass", + "strict_receipt": { + "canonical_row_keys": { + "actual": { + "public.claim_edges": [ + { + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36" + } + ], + "public.claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d" + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f" + } + ], + "public.claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "public.reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "public.sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + }, + "expected": { + "public.claim_edges": [ + { + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36" + } + ], + "public.claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d" + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f" + } + ], + "public.claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "public.reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "public.sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + } + }, + "checks": { + "before_after_counts_present": true, + "canonical_row_keys_exact": true, + "canonical_rows_exact": true, + "clone_database_removed": true, + "conflict_transaction_rolled_back": true, + "proposal_applied_at_present": true, + "proposal_id_exact": true, + "proposal_status_applied": true, + "source_database_unchanged": true, + "table_deltas_exact": true + }, + "pass": true, + "proposal": { + "applied_at": "2026-07-15 01:59:04.449051+00", + "applied_by_handle": "kb-apply", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "status": "applied" + }, + "rollback_cleanup": { + "clone_database_removed": true, + "conflict_transaction_rolled_back": true, + "leftover_clone_database_count": 0, + "source_database_unchanged": true + }, + "row_counts": { + "actual_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + }, + "after_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 1, + "public.claim_evidence": 3, + "public.claims": 3, + "public.reasoning_tools": 1, + "public.sources": 3 + }, + "before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "expected_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + } + }, + "schema": "working-leo.approved-change-strict-receipt.v1" + }, + "target_rows_before_apply": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "telegram_messages_sent": false, + "unrelated_fingerprint_before_apply": { + "kb_stage.kb_proposal_approvals": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "kb_stage.kb_proposals": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "public.agents": { + "content_md5": "56bbdd947ace3bd545744a2995795702", + "row_count": 2 + }, + "public.claim_edges": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "public.claim_evidence": { + "content_md5": "bc66b511bcc4926bb9065f98b41ce8ae", + "row_count": 1 + }, + "public.claims": { + "content_md5": "5cfbe5bd4ed3dc24adab29cb39f0b594", + "row_count": 1 + }, + "public.reasoning_tools": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "public.sources": { + "content_md5": "592f899d41dfeeecdb449402eb97ebd3", + "row_count": 1 + } + } +} diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json new file mode 100644 index 0000000..e5a2a3d --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json @@ -0,0 +1,217 @@ +{ + "applied_row_ids": { + "claim_edges": [ + "b28d8770-9ac9-5b5f-bfe2-7280d7422a21" + ], + "claim_evidence": [ + "13e580c5-458d-5146-aef3-8c74ffc43b7c", + "dac3934f-b7ad-5205-8efe-a00163370a54" + ], + "claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + }, + "applied_row_sha256": { + "claim_edges": [ + "60d14cbc45d424fa1eb62db6478f185bf2c2d86559e57ffa77474e8d0b3c527c" + ], + "claim_evidence": [ + "e97de162c0730da69b357e1350f0da7e5843c4e67d498c12d731a0f04a8868d7", + "3054718195abe3f78e08cfcacd59902d41eb91c7153c6c63f9bcb3bc7c6902ac" + ], + "claims": [ + "e593b9cf7b0bc9267f3e48d8173129b84b09c028f290f1abc97ccb73940b31dc", + "24280b5159cbbf75e00eb24ae50ab69994397a583f347a241a5e262cf0c03179" + ], + "reasoning_tools": [ + "257924b236c3c27289b9ae2314687be0e96779415fc6ff87faada22893b757ba" + ], + "sources": [ + "d7a690c2b18701db5b48e8189b1621c8025c1ed75889a4d0a3a4c9761c03dc3b", + "bee048794799006bd00bf4bb02ae57998e670f5bcdfb140dff871dcb042d0198" + ] + }, + "apply_payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684", + "apply_receipt_sha256": "2aa65fe4509c6699af34e418de27c0225efe751223aaebf3f9b545fa159c754e", + "apply_sql_sha256": "1a7d65870e415ee8891b6dd84732a29bcee00412c10862d3d1db4b191a8ade38", + "approval_snapshot_sha256": "310b54030e26ed43d7226f2e803610586e80d21ff064c67e11ea21e93ef40bd9", + "artifact": "proposal_apply_lifecycle_receipt", + "checks": { + "apply_receipt_replay_ready": true, + "apply_rows_have_exact_ids": true, + "fresh_owned_targets": true, + "immutable_approval_unchanged": true, + "rollback_counts_restored": true, + "rollback_ledger_restored_to_approved": true, + "rollback_replay_refused_without_mutation": true, + "rollback_sql_exact_for_bound_rows": true, + "rollback_target_rows_absent": true, + "unrelated_rows_unchanged": true + }, + "current_tier": "T2_runtime", + "fresh_preflight_artifact_sha256": "1d5e7eed7355aa94ecfa213422e6178aecdeee70628994596e852bd7569d6a2d", + "fresh_preflight_sha256": "29a80dc9422af45fd067e98621442d31c60161f34deb5845aa410659a9c5de3a", + "generated_at_utc": "2026-07-15T01:59:05.526171+00:00", + "lifecycle_receipt_sha256": "8e74cf1f5c372633f9b6f15363ff759ba6ce9bf42df51288bb9be70ddabe49b3", + "pass": true, + "production_apply_authorization_present": false, + "production_apply_executed": false, + "proposal_before_apply": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "proposal_payload_sha256": "6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af", + "required_tier": "T2_runtime", + "rollback": { + "counts_after_rollback": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "counts_before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "delete_order": [ + "claim_evidence", + "claim_edges", + "reasoning_tools", + "sources", + "claims" + ], + "proposal_after": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3", + "schema": "livingip.proposalApplyRollback.v1", + "target_rows_after": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + } + }, + "schema": "livingip.proposalApplyLifecycleReceipt.v1", + "strongest_claim_allowed": "deterministic disposable-clone apply and compensating rollback" +} diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json new file mode 100644 index 0000000..c56352f --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json @@ -0,0 +1,32 @@ +{ + "artifact": "proposal_apply_production_target_readback", + "schema": "livingip.proposalApplyProductionTargetReadback.v1", + "observed_at_utc": "2026-07-15T00:45:59.238006+00:00", + "transport": "ssh_readonly_transaction", + "read_only_transaction": true, + "read_only_setting": "on", + "container": "teleo-pg", + "database": "teleo", + "system_identifier": "7649789040005668902", + "server_version_num": "160014", + "approval_gate": { + "immutable_approval_table_present": false, + "approve_function_present": false, + "assert_function_present": false, + "finish_function_present": false, + "review_role_present": false, + "apply_role_present": true + }, + "approved_strict_candidate_count": 0, + "approved_strict_candidates": [], + "attempted_recovery": { + "strict_immutable_approval_join_attempted": true, + "strict_join_failed": true, + "strict_join_exact_gate": "relation kb_stage.kb_proposal_approvals does not exist", + "fallback_read_only_gate_and_candidate_count_succeeded": true + }, + "production_mutated": false, + "production_apply_authorization_present": false, + "production_apply_executed": false, + "claim_ceiling": "Fresh T3 live read-only identity and gate readback only. The immutable approval gate is absent and there are zero approved strict approve_claim candidates; production apply is neither authorized nor executed." +} diff --git a/pyproject.toml b/pyproject.toml index e264213..70fc9ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ [project.optional-dependencies] dev = [ + "hypothesis>=6,<7", "pytest>=8", "pytest-asyncio>=0.23", "ruff>=0.3", diff --git a/scripts/apply_proposal.py b/scripts/apply_proposal.py index 7fd4bd3..88c3c65 100644 --- a/scripts/apply_proposal.py +++ b/scripts/apply_proposal.py @@ -111,8 +111,17 @@ CLAIM_TYPES = {"empirical", "structural", "normative", "meta", "concept"} SOURCE_TYPES = {"paper", "article", "transcript", "observation", "dataset", "post", "dm", "other"} EVIDENCE_ROLES = {"grounds", "illustrates", "contradicts"} EDGE_TYPES = { - "supports", "challenges", "requires", "relates", "contradicts", - "supersedes", "derives_from", "cites", "causes", "constrains", "accelerates", + "supports", + "challenges", + "requires", + "relates", + "contradicts", + "supersedes", + "derives_from", + "cites", + "causes", + "constrains", + "accelerates", } MAX_BUNDLE_ROWS = { @@ -143,6 +152,21 @@ def _jsonb(value: Any) -> str: return sql_literal(json.dumps(value, sort_keys=True)) + "::jsonb" +def deterministic_row_uuid(proposal_id: str, row_kind: str, *semantic_key: Any) -> str: + """Derive a stable persisted row id from the proposal and semantic key. + + Evidence and edge ids are not part of the reviewed v2 payload. Deriving + them here makes clone/production receipts and a pre-authorized rollback + packet bind the same exact ids instead of relying on database randomness. + """ + material = json.dumps( + ["livingip", "kb-apply", str(proposal_id), row_kind, *semantic_key], + ensure_ascii=True, + separators=(",", ":"), + ) + return str(uuid.uuid5(uuid.NAMESPACE_URL, material)) + + def _text_array(values: list[str]) -> str: if not values: return "'{}'::text[]" @@ -196,9 +220,7 @@ def _reject_unknown(mapping: dict[str, Any], allowed: set[str], path: str) -> No raise ValueError(f"{path} contains unknown fields: {unknown}") -def _validate_approval_meta( - approval: dict[str, Any], proposal_type: str, apply_payload: dict[str, Any] -) -> None: +def _validate_approval_meta(approval: dict[str, Any], proposal_type: str, apply_payload: dict[str, Any]) -> None: if not isinstance(approval, dict): raise ValueError("approved proposal metadata is required") if approval.get("proposal_type") != proposal_type: @@ -222,12 +244,12 @@ def _validate_approval_meta( def _approval_guard_sql(proposal_id: str, approval: dict[str, Any]) -> str: return f"""select kb_stage.assert_approved_proposal( {sql_literal(proposal_id)}::uuid, - {sql_literal(approval['proposal_type'])}, - {_jsonb(approval['payload'])}, - {sql_literal(approval['reviewed_by_handle'])}, - {sql_literal(approval.get('reviewed_by_agent_id'))}::uuid, - {sql_literal(approval['reviewed_at'])}::timestamptz, - {sql_literal(approval['review_note'])} + {sql_literal(approval["proposal_type"])}, + {_jsonb(approval["payload"])}, + {sql_literal(approval["reviewed_by_handle"])}, + {sql_literal(approval.get("reviewed_by_agent_id"))}::uuid, + {sql_literal(approval["reviewed_at"])}::timestamptz, + {sql_literal(approval["review_note"])} );""" @@ -245,12 +267,12 @@ end $verify$;""" finish_sql = f"""select kb_stage.finish_approved_proposal( {sql_literal(proposal_id)}::uuid, - {sql_literal(approval['proposal_type'])}, - {_jsonb(approval['payload'])}, - {sql_literal(approval['reviewed_by_handle'])}, - {sql_literal(approval.get('reviewed_by_agent_id'))}::uuid, - {sql_literal(approval['reviewed_at'])}::timestamptz, - {sql_literal(approval['review_note'])}, + {sql_literal(approval["proposal_type"])}, + {_jsonb(approval["payload"])}, + {sql_literal(approval["reviewed_by_handle"])}, + {sql_literal(approval.get("reviewed_by_agent_id"))}::uuid, + {sql_literal(approval["reviewed_at"])}::timestamptz, + {sql_literal(approval["review_note"])}, {sql_literal(applied_by)} );""" return checks_sql + "\n" + finish_sql @@ -301,9 +323,9 @@ update public.strategies insert into public.strategies (agent_id, diagnosis, guiding_policy, proximate_objectives, version, active) select {sql_literal(agent_id)}::uuid, - {sql_literal(strategy['diagnosis'])}, - {sql_literal(strategy['guiding_policy'])}, - {_jsonb(strategy['proximate_objectives'])}, + {sql_literal(strategy["diagnosis"])}, + {sql_literal(strategy["guiding_policy"])}, + {_jsonb(strategy["proximate_objectives"])}, coalesce(max(version), 0) + 1, true from public.strategies @@ -325,9 +347,7 @@ values raise exception 'apply_proposal: expected exactly one active strategy for agent %', {sql_literal(agent_id)}; end if;""" - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -338,14 +358,16 @@ def build_add_edge_sql( approval: dict[str, Any], ) -> str: _validate_approval_meta(approval, "add_edge", apply_payload) - f = apply_payload.get("from_claim") - t = apply_payload.get("to_claim") + _reject_unknown(apply_payload, {"from_claim", "to_claim", "edge_type", "weight"}, "add_edge apply_payload") + f = _uuid(apply_payload.get("from_claim"), "add_edge.from_claim") + t = _uuid(apply_payload.get("to_claim"), "add_edge.to_claim") et = apply_payload.get("edge_type") - w = apply_payload.get("weight") - if not (f and t and et): - raise ValueError("add_edge apply_payload requires 'from_claim', 'to_claim', 'edge_type'") + if et not in EDGE_TYPES: + raise ValueError(f"add_edge.edge_type must be one of {sorted(EDGE_TYPES)}") + w = _weight(apply_payload.get("weight"), "add_edge.weight") if f == t: raise ValueError("add_edge from_claim and to_claim must differ") + row_id = deterministic_row_uuid(proposal_id, "claim_edge", f, t, et) edge_lock_key = f"claim_edge:{f}:{t}:{et}" canonical = f""" @@ -354,8 +376,8 @@ def build_add_edge_sql( select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0)); -- insert the edge unless an identical (from,to,type) edge already exists -insert into public.claim_edges (from_claim, to_claim, edge_type, weight) -select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, +insert into public.claim_edges (id, from_claim, to_claim, edge_type, weight) +select {sql_literal(row_id)}::uuid, {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, {sql_literal(et)}::edge_type, {sql_literal(w)} where not exists ( select 1 from public.claim_edges @@ -364,7 +386,15 @@ select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, and edge_type = {sql_literal(et)}::edge_type); """.rstrip() - checks = f""" if exists (select 1 from public.claim_edges + checks = f""" -- Historical replay receipts can carry a pre-deterministic row id. + -- Require one exact semantic row; fresh applies still insert row_id above. + if (select count(*) from public.claim_edges + where from_claim = {sql_literal(f)}::uuid + and to_claim = {sql_literal(t)}::uuid + and edge_type = {sql_literal(et)}::edge_type) <> 1 then + raise exception 'apply_proposal: claim_edge row does not match strict apply payload'; + end if; + if exists (select 1 from public.claim_edges where from_claim = {sql_literal(f)}::uuid and to_claim = {sql_literal(t)}::uuid and edge_type = {sql_literal(et)}::edge_type @@ -379,9 +409,7 @@ select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, raise exception 'apply_proposal: claim_edge row does not match strict apply payload'; end if;""" - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -392,6 +420,7 @@ def build_attach_evidence_sql( approval: dict[str, Any], ) -> str: _validate_approval_meta(approval, "attach_evidence", apply_payload) + _reject_unknown(apply_payload, {"evidence"}, "attach_evidence apply_payload") evidence: list[dict[str, Any]] = apply_payload.get("evidence") or [] if not evidence: raise ValueError("attach_evidence apply_payload requires non-empty 'evidence'") @@ -399,25 +428,33 @@ def build_attach_evidence_sql( statements = [] checks = [] for i, ev in enumerate(evidence): - claim_id = ev.get("claim_id") - source_id = ev.get("source_id") + _reject_unknown( + ev, + {"claim_id", "source_id", "role", "weight"}, + f"attach_evidence.evidence[{i}]", + ) + claim_id = _uuid(ev.get("claim_id"), f"attach_evidence.evidence[{i}].claim_id") + source_id = _uuid(ev.get("source_id"), f"attach_evidence.evidence[{i}].source_id") role = ev.get("role") or "grounds" - weight = ev.get("weight") - if not claim_id: - raise ValueError(f"evidence[{i}] requires 'claim_id'") - if not source_id: - raise ValueError( - f"evidence[{i}] requires an existing 'source_id'. " - "kb_apply cannot create public.sources; mint the source upstream first." - ) + if role not in EVIDENCE_ROLES: + raise ValueError(f"attach_evidence.evidence[{i}].role must be one of {sorted(EVIDENCE_ROLES)}") + weight = _weight(ev.get("weight"), f"attach_evidence.evidence[{i}].weight") + row_id = deterministic_row_uuid(proposal_id, "claim_evidence", claim_id, source_id, role) statements.append( - f"""insert into public.claim_evidence (claim_id, source_id, role, weight) -select {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid, + f"""insert into public.claim_evidence (id, claim_id, source_id, role, weight) +select {sql_literal(row_id)}::uuid, {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid, {sql_literal(role)}::evidence_role, {sql_literal(weight)} on conflict (claim_id, source_id, role) do nothing;""" ) checks.append( - f""" if exists (select 1 from public.claim_evidence + f""" -- Accept a legacy receipt id only when exactly one semantic row matches. + if (select count(*) from public.claim_evidence + where claim_id = {sql_literal(claim_id)}::uuid + and source_id = {sql_literal(source_id)}::uuid + and role = {sql_literal(role)}::evidence_role) <> 1 then + raise exception 'apply_proposal: evidence row % does not match strict apply payload', {i}; + end if; + if exists (select 1 from public.claim_evidence where claim_id = {sql_literal(claim_id)}::uuid and source_id = {sql_literal(source_id)}::uuid and role = {sql_literal(role)}::evidence_role @@ -434,9 +471,7 @@ on conflict (claim_id, source_id, role) do nothing;""" ) canonical = "\n".join(statements) - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -509,12 +544,8 @@ def build_approve_claim_sql( "status": status, "confidence": _weight(row.get("confidence"), f"{path}.confidence"), "tags": tags, - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), - "superseded_by": _uuid( - row.get("superseded_by"), f"{path}.superseded_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), + "superseded_by": _uuid(row.get("superseded_by"), f"{path}.superseded_by", nullable=True), } ) @@ -543,9 +574,7 @@ def build_approve_claim_sql( "storage_path": row.get("storage_path"), "excerpt": row.get("excerpt"), "hash": source_hash, - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), } ) @@ -562,13 +591,18 @@ def build_approve_claim_sql( raise ValueError(f"{path}.role must be one of {sorted(EVIDENCE_ROLES)}") evidence.append( { + "id": deterministic_row_uuid( + proposal_id, + "claim_evidence", + _uuid(row.get("claim_id"), f"{path}.claim_id"), + _uuid(row.get("source_id"), f"{path}.source_id"), + role, + ), "claim_id": _uuid(row.get("claim_id"), f"{path}.claim_id"), "source_id": _uuid(row.get("source_id"), f"{path}.source_id"), "role": role, "weight": _weight(row.get("weight"), f"{path}.weight"), - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), } ) @@ -589,13 +623,12 @@ def build_approve_claim_sql( raise ValueError(f"{path} cannot be a self-edge") edges.append( { + "id": deterministic_row_uuid(proposal_id, "claim_edge", from_claim, to_claim, edge_type), "from_claim": from_claim, "to_claim": to_claim, "edge_type": edge_type, "weight": _weight(row.get("weight"), f"{path}.weight"), - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), } ) @@ -647,150 +680,156 @@ def build_approve_claim_sql( for row in sources: collision_checks.append( f""" if exists (select 1 from public.sources - where hash = {sql_literal(row['hash'])} - and id <> {sql_literal(row['id'])}::uuid) then - raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])}; + where hash = {sql_literal(row["hash"])} + and id <> {sql_literal(row["id"])}::uuid) then + raise exception 'approve_claim: source hash collision for source %', {sql_literal(row["id"])}; end if;""" ) - statements.append( - "do $source_conflicts$\nbegin\n" - + "\n".join(collision_checks) - + "\nend\n$source_conflicts$;" - ) + statements.append("do $source_conflicts$\nbegin\n" + "\n".join(collision_checks) + "\nend\n$source_conflicts$;") for row in claims: tags = _text_array(row["tags"]) statements.append( f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by) -select {sql_literal(row['id'])}::uuid, {sql_literal(row['type'])}, {sql_literal(row['text'])}, - {sql_literal(row['status'])}, {sql_literal(row['confidence'])}, {tags}, - {sql_literal(row['created_by'])}::uuid, {sql_literal(row['superseded_by'])}::uuid +select {sql_literal(row["id"])}::uuid, {sql_literal(row["type"])}, {sql_literal(row["text"])}, + {sql_literal(row["status"])}, {sql_literal(row["confidence"])}, {tags}, + {sql_literal(row["created_by"])}::uuid, {sql_literal(row["superseded_by"])}::uuid on conflict (id) do nothing;""" ) checks.append( f""" if not exists (select 1 from public.claims - where id = {sql_literal(row['id'])}::uuid - and type = {sql_literal(row['type'])} - and text = {sql_literal(row['text'])} - and status = {sql_literal(row['status'])} - and confidence is not distinct from {sql_literal(row['confidence'])} + where id = {sql_literal(row["id"])}::uuid + and type = {sql_literal(row["type"])} + and text = {sql_literal(row["text"])} + and status = {sql_literal(row["status"])} + and confidence is not distinct from {sql_literal(row["confidence"])} and tags is not distinct from {tags} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid - and superseded_by is not distinct from {sql_literal(row['superseded_by'])}::uuid) then - raise exception 'approve_claim: claim row does not match strict apply payload: %', {sql_literal(row['id'])}; + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid + and superseded_by is not distinct from {sql_literal(row["superseded_by"])}::uuid) then + raise exception 'approve_claim: claim row does not match strict apply payload: %', {sql_literal(row["id"])}; end if;""" ) for row in sources: statements.append( f"""insert into public.sources (id, source_type, url, storage_path, excerpt, hash, created_by) -select {sql_literal(row['id'])}::uuid, {sql_literal(row['source_type'])}, {sql_literal(row['url'])}, - {sql_literal(row['storage_path'])}, {sql_literal(row['excerpt'])}, {sql_literal(row['hash'])}, - {sql_literal(row['created_by'])}::uuid +select {sql_literal(row["id"])}::uuid, {sql_literal(row["source_type"])}, {sql_literal(row["url"])}, + {sql_literal(row["storage_path"])}, {sql_literal(row["excerpt"])}, {sql_literal(row["hash"])}, + {sql_literal(row["created_by"])}::uuid on conflict do nothing;""" ) checks.append( f""" if exists (select 1 from public.sources - where hash = {sql_literal(row['hash'])} - and id <> {sql_literal(row['id'])}::uuid) then - raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])}; + where hash = {sql_literal(row["hash"])} + and id <> {sql_literal(row["id"])}::uuid) then + raise exception 'approve_claim: source hash collision for source %', {sql_literal(row["id"])}; end if; if not exists (select 1 from public.sources - where id = {sql_literal(row['id'])}::uuid - and source_type = {sql_literal(row['source_type'])} - and url is not distinct from {sql_literal(row['url'])} - and storage_path is not distinct from {sql_literal(row['storage_path'])} - and excerpt is not distinct from {sql_literal(row['excerpt'])} - and hash = {sql_literal(row['hash'])} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then - raise exception 'approve_claim: source row does not match strict apply payload: %', {sql_literal(row['id'])}; + where id = {sql_literal(row["id"])}::uuid + and source_type = {sql_literal(row["source_type"])} + and url is not distinct from {sql_literal(row["url"])} + and storage_path is not distinct from {sql_literal(row["storage_path"])} + and excerpt is not distinct from {sql_literal(row["excerpt"])} + and hash = {sql_literal(row["hash"])} + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid) then + raise exception 'approve_claim: source row does not match strict apply payload: %', {sql_literal(row["id"])}; end if;""" ) for row in reasoning_tools: statements.append( f"""insert into public.reasoning_tools (id, agent_id, name, description, category) -select {sql_literal(row['id'])}::uuid, {sql_literal(row['agent_id'])}::uuid, - {sql_literal(row['name'])}, {sql_literal(row['description'])}, {sql_literal(row['category'])} +select {sql_literal(row["id"])}::uuid, {sql_literal(row["agent_id"])}::uuid, + {sql_literal(row["name"])}, {sql_literal(row["description"])}, {sql_literal(row["category"])} on conflict (id) do nothing;""" ) checks.append( f""" if not exists (select 1 from public.reasoning_tools - where id = {sql_literal(row['id'])}::uuid - and agent_id is not distinct from {sql_literal(row['agent_id'])}::uuid - and name = {sql_literal(row['name'])} - and description = {sql_literal(row['description'])} - and category is not distinct from {sql_literal(row['category'])}) then - raise exception 'approve_claim: reasoning tool row does not match strict apply payload: %', {sql_literal(row['id'])}; + where id = {sql_literal(row["id"])}::uuid + and agent_id is not distinct from {sql_literal(row["agent_id"])}::uuid + and name = {sql_literal(row["name"])} + and description = {sql_literal(row["description"])} + and category is not distinct from {sql_literal(row["category"])}) then + raise exception 'approve_claim: reasoning tool row does not match strict apply payload: %', {sql_literal(row["id"])}; end if;""" ) for row in evidence: statements.append( - f"""insert into public.claim_evidence (claim_id, source_id, role, weight, created_by) -select {sql_literal(row['claim_id'])}::uuid, {sql_literal(row['source_id'])}::uuid, - {sql_literal(row['role'])}::evidence_role, {sql_literal(row['weight'])}, - {sql_literal(row['created_by'])}::uuid + f"""insert into public.claim_evidence (id, claim_id, source_id, role, weight, created_by) +select {sql_literal(row["id"])}::uuid, {sql_literal(row["claim_id"])}::uuid, {sql_literal(row["source_id"])}::uuid, + {sql_literal(row["role"])}::evidence_role, {sql_literal(row["weight"])}, + {sql_literal(row["created_by"])}::uuid on conflict (claim_id, source_id, role) do nothing;""" ) checks.append( - f""" if exists (select 1 from public.claim_evidence - where claim_id = {sql_literal(row['claim_id'])}::uuid - and source_id = {sql_literal(row['source_id'])}::uuid - and role = {sql_literal(row['role'])}::evidence_role - and (weight is distinct from {sql_literal(row['weight'])} - or created_by is distinct from {sql_literal(row['created_by'])}::uuid)) then + f""" -- Accept a legacy receipt id only when exactly one semantic row matches. + if (select count(*) from public.claim_evidence + where claim_id = {sql_literal(row["claim_id"])}::uuid + and source_id = {sql_literal(row["source_id"])}::uuid + and role = {sql_literal(row["role"])}::evidence_role) <> 1 then + raise exception 'approve_claim: evidence row does not match strict apply payload'; + end if; + if exists (select 1 from public.claim_evidence + where claim_id = {sql_literal(row["claim_id"])}::uuid + and source_id = {sql_literal(row["source_id"])}::uuid + and role = {sql_literal(row["role"])}::evidence_role + and (weight is distinct from {sql_literal(row["weight"])} + or created_by is distinct from {sql_literal(row["created_by"])}::uuid)) then raise exception 'approve_claim: evidence row does not match strict apply payload'; end if; if not exists (select 1 from public.claim_evidence - where claim_id = {sql_literal(row['claim_id'])}::uuid - and source_id = {sql_literal(row['source_id'])}::uuid - and role = {sql_literal(row['role'])}::evidence_role - and weight is not distinct from {sql_literal(row['weight'])} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then + where claim_id = {sql_literal(row["claim_id"])}::uuid + and source_id = {sql_literal(row["source_id"])}::uuid + and role = {sql_literal(row["role"])}::evidence_role + and weight is not distinct from {sql_literal(row["weight"])} + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid) then raise exception 'approve_claim: evidence row does not match strict apply payload'; end if;""" ) for row in edges: - edge_lock_key = ( - f"claim_edge:{row['from_claim']}:{row['to_claim']}:{row['edge_type']}" - ) + edge_lock_key = f"claim_edge:{row['from_claim']}:{row['to_claim']}:{row['edge_type']}" statements.append( f"""select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0)); -insert into public.claim_edges (from_claim, to_claim, edge_type, weight, created_by) -select {sql_literal(row['from_claim'])}::uuid, {sql_literal(row['to_claim'])}::uuid, - {sql_literal(row['edge_type'])}::edge_type, {sql_literal(row['weight'])}, - {sql_literal(row['created_by'])}::uuid +insert into public.claim_edges (id, from_claim, to_claim, edge_type, weight, created_by) +select {sql_literal(row["id"])}::uuid, {sql_literal(row["from_claim"])}::uuid, {sql_literal(row["to_claim"])}::uuid, + {sql_literal(row["edge_type"])}::edge_type, {sql_literal(row["weight"])}, + {sql_literal(row["created_by"])}::uuid where not exists ( select 1 from public.claim_edges - where from_claim = {sql_literal(row['from_claim'])}::uuid - and to_claim = {sql_literal(row['to_claim'])}::uuid - and edge_type = {sql_literal(row['edge_type'])}::edge_type);""" + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type);""" ) checks.append( - f""" if exists (select 1 from public.claim_edges - where from_claim = {sql_literal(row['from_claim'])}::uuid - and to_claim = {sql_literal(row['to_claim'])}::uuid - and edge_type = {sql_literal(row['edge_type'])}::edge_type - and (weight is distinct from {sql_literal(row['weight'])} - or created_by is distinct from {sql_literal(row['created_by'])}::uuid)) then + f""" -- Accept a legacy receipt id only when exactly one semantic row matches. + if (select count(*) from public.claim_edges + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type) <> 1 then + raise exception 'approve_claim: edge row does not match strict apply payload'; + end if; + if exists (select 1 from public.claim_edges + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type + and (weight is distinct from {sql_literal(row["weight"])} + or created_by is distinct from {sql_literal(row["created_by"])}::uuid)) then raise exception 'approve_claim: edge row does not match strict apply payload'; end if; if not exists (select 1 from public.claim_edges - where from_claim = {sql_literal(row['from_claim'])}::uuid - and to_claim = {sql_literal(row['to_claim'])}::uuid - and edge_type = {sql_literal(row['edge_type'])}::edge_type - and weight is not distinct from {sql_literal(row['weight'])} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type + and weight is not distinct from {sql_literal(row["weight"])} + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid) then raise exception 'approve_claim: edge row does not match strict apply payload'; end if;""" ) canonical = "\n\n".join(statements) - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -837,9 +876,7 @@ def assert_applyable(proposal: dict[str, Any]) -> None: if status == "applied": raise SystemExit(f"proposal {proposal['id']} is already applied (idempotent no-op)") if status != "approved": - raise SystemExit( - f"proposal {proposal['id']} has status {status!r}; only 'approved' proposals apply" - ) + raise SystemExit(f"proposal {proposal['id']} has status {status!r}; only 'approved' proposals apply") def assert_receiptable(proposal: dict[str, Any]) -> None: @@ -877,23 +914,36 @@ def run_psql( ) -> str: docker_binary = shutil.which("docker") or "docker" command = [ - docker_binary, "exec", "-e", "PGPASSWORD", "-i", args.container, - "psql", "-U", args.role, "-h", args.host, "-d", args.db, - "-v", "ON_ERROR_STOP=1", "-At", "-q", + docker_binary, + "exec", + "-e", + "PGPASSWORD", + "-i", + args.container, + "psql", + "-U", + args.role, + "-h", + args.host, + "-d", + args.db, + "-v", + "ON_ERROR_STOP=1", + "-At", + "-q", ] result = subprocess.run( - command, input=sql, text=True, capture_output=True, + command, + input=sql, + text=True, + capture_output=True, env={"PGPASSWORD": password, "PATH": "/usr/bin:/bin:/usr/local/bin"}, check=False, ) if result.returncode != 0: if redact_output_on_error: - raise SystemExit( - f"psql failed ({result.returncode}); private postflight output redacted" - ) - raise SystemExit( - f"psql failed ({result.returncode})\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}" - ) + raise SystemExit(f"psql failed ({result.returncode}); private postflight output redacted") + raise SystemExit(f"psql failed ({result.returncode})\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}") return result.stdout @@ -980,8 +1030,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace: p.add_argument( "--receipt-dir", default=None, - help="private receipt directory (default: KB_APPLY_RECEIPT_DIR or " - f"{replay_receipt.DEFAULT_RECEIPT_DIR})", + help=f"private receipt directory (default: KB_APPLY_RECEIPT_DIR or {replay_receipt.DEFAULT_RECEIPT_DIR})", ) p.add_argument( "--receipt-out", diff --git a/scripts/apply_worker.py b/scripts/apply_worker.py index 2abefc7..6d3dc13 100644 --- a/scripts/apply_worker.py +++ b/scripts/apply_worker.py @@ -35,6 +35,7 @@ from __future__ import annotations import argparse import json import os +import stat import subprocess import sys from pathlib import Path @@ -53,6 +54,13 @@ WORKER_TYPES = ap.APPLYABLE_TYPES # bundles and evidence/edge applies do not trigger a generic identity render. RENDER_TYPES = ("revise_strategy",) +POSTCOMMIT_RECEIPT_FAILURE_MARKER = "committed, but private replay receipt capture failed" +POSTCOMMIT_RECEIPT_RECOVERY_MARKER = ". Recover without reapplying via: " + + +class PostCommitReceiptError(RuntimeError): + """The proposal committed, but its private row-level receipt is unavailable.""" + # --------------------------------------------------------------------------- # # Pure helpers (unit-tested without a DB) # @@ -160,14 +168,69 @@ def partition_candidates( return {"poisoned": poisoned, "to_apply": eligible[: max(0, max_per_tick)]} +def has_exact_postcommit_receipt_failure(stderr: str | None, proposal_id: str) -> bool: + """Recognize only the proposal-bound post-COMMIT message from apply_proposal. + + The old marker can occur inside payload-validation tracebacks (for example, + as an attacker-controlled unknown field name). Requiring the complete + engine message shape prevents those pre-COMMIT failures from bypassing the + normal failure and poison-pill accounting. + """ + prefix = f"proposal {proposal_id} {POSTCOMMIT_RECEIPT_FAILURE_MARKER}: " + recovery_suffix = f" {proposal_id} --receipt-only" + for line in (stderr or "").splitlines(): + candidate = line.rstrip("\r") + if ( + candidate.startswith(prefix) + and POSTCOMMIT_RECEIPT_RECOVERY_MARKER in candidate[len(prefix) :] + and candidate.endswith(recovery_suffix) + ): + return True + return False + + +def receipt_path_for_proposal(args: argparse.Namespace, proposal_id: str) -> Path: + """Return the explicit, deterministic private receipt path for one proposal.""" + receipt_dir = ( + getattr(args, "receipt_dir", None) + or os.environ.get("KB_APPLY_WORKER_RECEIPT_DIR") + or os.environ.get("KB_APPLY_RECEIPT_DIR") + or ap.replay_receipt.DEFAULT_RECEIPT_DIR + ) + return Path(receipt_dir).expanduser().resolve() / f"{proposal_id}.json" + + +def assert_private_replay_receipt(path: Path, proposal_id: str) -> None: + """Verify the worker can rely on the exact private row-level receipt.""" + try: + path_stat = path.lstat() + except OSError as exc: + raise RuntimeError("private replay receipt was not written") from exc + if not stat.S_ISREG(path_stat.st_mode) or path.is_symlink(): + raise RuntimeError("private replay receipt is not a regular file") + if stat.S_IMODE(path_stat.st_mode) & 0o077: + raise RuntimeError("private replay receipt permissions are broader than 0600") + try: + receipt = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise RuntimeError("private replay receipt is unreadable or invalid") from exc + if not isinstance(receipt, dict): + raise RuntimeError("private replay receipt is not a JSON object") + try: + ap.replay_receipt.validate_replay_receipt( + receipt, + expected_proposal_id=str(proposal_id), + ) + except ValueError as exc: + raise RuntimeError("private replay receipt row-level contract is invalid") from exc + + # --------------------------------------------------------------------------- # # Side-effecting steps # # --------------------------------------------------------------------------- # def _psql_args(args: argparse.Namespace) -> argparse.Namespace: """Namespace shaped for ap.run_psql (reuses the kb_apply connection path).""" - return argparse.Namespace( - container=args.container, db=args.db, host=args.host, role=args.role - ) + return argparse.Namespace(container=args.container, db=args.db, host=args.host, role=args.role) def fetch_candidates( @@ -181,20 +244,73 @@ def fetch_candidates( def apply_one(args: argparse.Namespace, proposal_id: str) -> None: - """Apply via the audited apply_proposal.py CLI -- same txn + rowcount guard.""" + """Apply once and retain or safely recover the exact private replay receipt.""" + receipt_path = receipt_path_for_proposal(args, proposal_id) cmd = [ - sys.executable, str(args.apply_script), proposal_id, - "--applied-by", args.applied_by, - "--secrets-file", args.secrets_file, - "--container", args.container, "--db", args.db, - "--host", args.host, "--role", args.role, + sys.executable, + str(args.apply_script), + proposal_id, + "--applied-by", + args.applied_by, + "--secrets-file", + args.secrets_file, + "--container", + args.container, + "--db", + args.db, + "--host", + args.host, + "--role", + args.role, + "--receipt-dir", + str(receipt_path.parent), + "--receipt-out", + str(receipt_path), ] result = subprocess.run(cmd, text=True, capture_output=True, check=False) - if result.returncode != 0: + committed = result.returncode == 0 or has_exact_postcommit_receipt_failure( + result.stderr, + proposal_id, + ) + if not committed: raise RuntimeError( - f"apply failed for {proposal_id}: {result.stdout.strip()} {result.stderr.strip()}" + f"apply failed for {proposal_id}: {(result.stdout or '').strip()} {(result.stderr or '').strip()}" ) + if result.returncode == 0: + try: + assert_private_replay_receipt(receipt_path, proposal_id) + return + except RuntimeError: + # The engine returned only after COMMIT. A missing/invalid receipt is + # recoverable without replaying the apply transaction. + pass + + try: + recovery = subprocess.run( + [*cmd, "--receipt-only"], + text=True, + capture_output=True, + check=False, + ) + except OSError as exc: + raise PostCommitReceiptError( + f"proposal {proposal_id} committed, but the read-only receipt recovery " + "command could not start; do not retry the apply transaction" + ) from exc + if recovery.returncode != 0: + raise PostCommitReceiptError( + f"proposal {proposal_id} committed, but read-only receipt recovery " + f"failed (exit {recovery.returncode}); do not retry the apply transaction" + ) + try: + assert_private_replay_receipt(receipt_path, proposal_id) + except RuntimeError as exc: + raise PostCommitReceiptError( + f"proposal {proposal_id} committed, but receipt recovery did not produce " + "a valid private row-level receipt; do not retry the apply transaction" + ) from exc + def render_one(args: argparse.Namespace, agent_id: str | None) -> str: cmd = build_render_command(args.render_cmd, agent_id) @@ -202,9 +318,7 @@ def render_one(args: argparse.Namespace, agent_id: str | None) -> str: return "render skipped (no render-cmd configured; PR2 renderer not deployed)" result = subprocess.run(cmd, text=True, capture_output=True, check=False) if result.returncode != 0: - raise RuntimeError( - f"render failed for agent {agent_id}: {result.stdout.strip()} {result.stderr.strip()}" - ) + raise RuntimeError(f"render failed for agent {agent_id}: {result.stdout.strip()} {result.stderr.strip()}") return f"rendered agent {agent_id}" @@ -214,11 +328,7 @@ def render_one(args: argparse.Namespace, agent_id: str | None) -> str: def run(args: argparse.Namespace) -> int: password = ap.load_password(args.secrets_file) failure_counts = load_failure_state(args.failure_state_file) - exhausted_ids = tuple( - proposal_id - for proposal_id, count in failure_counts.items() - if count >= args.max_attempts - ) + exhausted_ids = tuple(proposal_id for proposal_id, count in failure_counts.items() if count >= args.max_attempts) candidates = fetch_candidates(args, password, exhausted_ids) for proposal_id in exhausted_ids: @@ -263,6 +373,15 @@ def run(args: argparse.Namespace) -> int: print(f"applied {pid} ({ptype})") if ptype in RENDER_TYPES: print(" " + render_one(args, agent_id)) + except PostCommitReceiptError as exc: + failures += 1 + # The canonical transaction already committed. Do not classify this + # as an apply failure or poison/retry the proposal; receipt-only is + # the sole safe recovery path. + print( + f"POST-COMMIT RECEIPT ERROR for {pid} ({ptype}): {exc}", + file=sys.stderr, + ) except Exception as exc: failures += 1 # Leave the proposal at 'approved' so a fixed one reapplies next tick @@ -270,48 +389,65 @@ def run(args: argparse.Namespace) -> int: # so a deterministically-failing proposal hits the ceiling instead of # retrying forever. Surface loudly for the operator. failure_counts[pid] = failure_counts.get(pid, 0) + 1 - print(f"ERROR applying {pid} ({ptype}) [attempt {failure_counts[pid]}/" - f"{args.max_attempts}]: {exc}", file=sys.stderr) + print( + f"ERROR applying {pid} ({ptype}) [attempt {failure_counts[pid]}/{args.max_attempts}]: {exc}", + file=sys.stderr, + ) save_failure_state(args.failure_state_file, failure_counts) return 1 if failures else 0 def parse_args(argv: list[str]) -> argparse.Namespace: - p = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter - ) + p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument( - "--enable", action="store_true", + "--enable", + action="store_true", help="actually apply (default: report-only). Also honored via KB_APPLY_WORKER_ENABLED=1.", ) p.add_argument("--limit", type=int, default=20, help="max candidates fetched per tick") p.add_argument( - "--max-per-tick", type=int, default=1, + "--max-per-tick", + type=int, + default=1, help="max proposals actually APPLIED per tick (default 1: applies land " "one-at-a-time and observably, not a whole-queue drain)", ) p.add_argument( - "--max-attempts", type=int, default=3, + "--max-attempts", + type=int, + default=3, help="consecutive apply failures before a proposal is treated as a " "poison pill and skipped (needs a human fix, not endless retries)", ) p.add_argument( "--failure-state-file", default=os.environ.get("KB_APPLY_WORKER_STATE", "/opt/teleo-eval/logs/kb-apply-worker-failures.json"), - help="persisted per-proposal failure counts (survives oneshot ticks so " - "the poison-pill ceiling actually bites)", + help="persisted per-proposal failure counts (survives oneshot ticks so the poison-pill ceiling actually bites)", ) p.add_argument( - "--applied-by", default=ap.SERVICE_AGENT_HANDLE, + "--applied-by", + default=ap.SERVICE_AGENT_HANDLE, help="handle recorded as applied_by (default: the kb-apply service agent)", ) p.add_argument( - "--apply-script", default=str(HERE / "apply_proposal.py"), + "--apply-script", + default=str(HERE / "apply_proposal.py"), help="path to the apply_proposal.py engine (the sole apply path)", ) p.add_argument( - "--render-cmd", default=os.environ.get("KB_APPLY_RENDER_CMD", ""), + "--receipt-dir", + default=( + os.environ.get("KB_APPLY_WORKER_RECEIPT_DIR") + or os.environ.get("KB_APPLY_RECEIPT_DIR") + or ap.replay_receipt.DEFAULT_RECEIPT_DIR + ), + help="private replay receipt directory; each apply writes the deterministic " + ".json path and verifies 0600 permissions", + ) + p.add_argument( + "--render-cmd", + default=os.environ.get("KB_APPLY_RENDER_CMD", ""), help="render hook template, e.g. 'python3 render_soul.py --agent-id {agent_id}'. " "Empty (default) skips render until the SOUL renderer is deployed.", ) diff --git a/scripts/finalize_approve_claim_isolated_canary.py b/scripts/finalize_approve_claim_isolated_canary.py new file mode 100644 index 0000000..059ca97 --- /dev/null +++ b/scripts/finalize_approve_claim_isolated_canary.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +"""Finalize outer isolation and cleanup evidence for the clone canary.""" + +from __future__ import annotations + +import hashlib +import json +import os +import sys +from collections.abc import Mapping +from pathlib import Path +from typing import Any + + +def _service(raw: str) -> dict[str, str]: + return dict(line.split("=", 1) for line in raw.splitlines() if "=" in line) + + +def _table_deltas(before: dict[str, int], after: dict[str, int]) -> dict[str, int]: + if set(before) != set(after): + raise ValueError( + f"live source count keys changed during the canary: before={sorted(before)} after={sorted(after)}" + ) + return {key: after[key] - before[key] for key in sorted(before)} + + +def _verify_source_binding(root: Path, records: list[dict[str, object]]) -> dict[str, object]: + readbacks = [] + for record in records: + relative_text = str(record.get("repo_relative_path", "")) + relative = Path(relative_text) + if not relative_text or relative.is_absolute() or ".." in relative.parts: + raise ValueError(f"unsafe repo-relative source path: {relative_text!r}") + candidate = (root / relative).resolve() + try: + candidate.relative_to(root) + except ValueError as exc: + raise ValueError(f"source path escaped repository root: {relative_text}") from exc + if not candidate.is_file(): + readbacks.append( + { + "repo_relative_path": relative.as_posix(), + "expected_sha256": record.get("sha256"), + "actual_sha256": None, + "matches": False, + } + ) + continue + content = candidate.read_bytes() + actual_sha256 = hashlib.sha256(content).hexdigest() + readbacks.append( + { + "repo_relative_path": relative.as_posix(), + "expected_sha256": record.get("sha256"), + "actual_sha256": actual_sha256, + "size_bytes": len(content), + "matches": (actual_sha256 == record.get("sha256") and len(content) == record.get("size_bytes")), + } + ) + return { + "files": readbacks, + "all_match": bool(readbacks) and all(bool(row["matches"]) for row in readbacks), + } + + +def _contains_ephemeral_path(value: object) -> bool: + if isinstance(value, str): + return "/tmp/" in value + if isinstance(value, dict): + return any(_contains_ephemeral_path(item) for item in value.values()) + if isinstance(value, list): + return any(_contains_ephemeral_path(item) for item in value) + return False + + +def finalize(path: Path, env: Mapping[str, str]) -> dict[str, Any]: + if not path.is_file(): + raise ValueError("inner canary did not produce a report") + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + raise ValueError("inner canary report must be a JSON object") + + before_counts = json.loads(env["SOURCE_COUNTS_BEFORE"]) + after_counts = json.loads(env["SOURCE_COUNTS_AFTER"]) + live_table_deltas = _table_deltas(before_counts, after_counts) + before_service = _service(env["SERVICE_BEFORE"]) + after_service = _service(env["SERVICE_AFTER"]) + source_tier = env["SOURCE_TIER"] + source_network_mode = env["SOURCE_NETWORK_MODE"] + source_canary_label = env["SOURCE_CANARY_LABEL"] + container_network_mode = env["CANARY_NETWORK_MODE"] + container_mounts = json.loads(env["CANARY_MOUNTS_JSON"]) + container_canary_label = env["CANARY_LABEL"] + container_instance_label = env["CANARY_INSTANCE_LABEL"] + container_volume_mounts = [mount for mount in container_mounts if mount.get("Type") == "volume"] + container_isolated = container_network_mode == "none" and not container_volume_mounts + container_labeled = container_canary_label == "proposal-apply-lifecycle" and container_instance_label.startswith( + "working-leo-approve-claim-" + ) + source_tier_enforced = source_tier != "isolated-local" or ( + source_network_mode == "none" and source_canary_label == "working-leo-approved-source" + ) + service_observable = before_service.get("Available") == "true" and after_service.get("Available") == "true" + service_stable = ( + all( + before_service.get(key) == after_service.get(key) + for key in ("ActiveState", "SubState", "MainPID", "NRestarts") + ) + if service_observable + else None + ) + service_gate_passes = service_stable is True if source_tier == "live-readonly" else service_stable is not False + container_absent = int(env["CONTAINER_NAME_READBACK_RC"]) == 0 and not env["CONTAINER_NAME_READBACK"].strip() + label_scoped_orphan_count = int(env["CONTAINER_LABEL_ORPHAN_COUNT"]) + label_scoped_container_absent = ( + int(env["CONTAINER_LABEL_READBACK_RC"]) == 0 + and label_scoped_orphan_count == 0 + and not env["CONTAINER_LABEL_READBACK"].strip() + ) + workdir_absent = env["OUTER_WORKDIR_LEXISTS"] == "false" + source_binding = _verify_source_binding( + Path(env["REPO_ROOT"]).resolve(), + data.get("source_binding", {}).get("files", []), + ) + data["source_binding"]["independent_post_cleanup_verification"] = source_binding + source_binding_ok = ( + data["source_binding"].get("unchanged_during_run") is True and source_binding["all_match"] is True + ) + stale_ephemeral_paths_absent = not _contains_ephemeral_path(data) + inner_runtime_pass = data.get("status") == "pass" and int(env["CANARY_RC"]) == 0 + outer_ok = ( + before_counts == after_counts + and all(delta == 0 for delta in live_table_deltas.values()) + and service_gate_passes + and container_absent + and label_scoped_container_absent + and workdir_absent + and source_binding_ok + and stale_ephemeral_paths_absent + and container_isolated + and container_labeled + and source_tier_enforced + ) + if source_tier == "isolated-local": + data["required_tier"] = "T2_runtime" + data["required_tiers"] = ["T2_runtime"] + data["runtime_scope"] = "isolated_postgres_container_from_readonly_local_fixture" + else: + data["required_tier"] = "T3_live_readonly" + data["required_tiers"] = ["T2_runtime", "T3_live_readonly"] + data["runtime_scope"] = "isolated_postgres_container_from_readonly_live_schema" + data["outer_isolation"] = { + "source_container": env.get("SOURCE_CONTAINER", "teleo-pg"), + "source_database": env.get("SOURCE_DB", "teleo"), + "source_counts_before": before_counts, + "source_counts_after": after_counts, + "canonical_table_deltas": live_table_deltas, + "source_counts_unchanged": before_counts == after_counts, + "service_before": before_service, + "service_after": after_service, + "service_required": source_tier == "live-readonly", + "service_observable": service_observable, + "service_stable": service_stable, + "service_gate_passes": service_gate_passes, + "source_boundary": { + "source_tier": source_tier, + "network_mode": source_network_mode, + "canary_label": source_canary_label, + "tier_enforced": source_tier_enforced, + }, + "container_isolation": { + "network_mode": container_network_mode, + "volume_mounts": container_volume_mounts, + "canary_label": container_canary_label, + "instance_label": container_instance_label, + "lifecycle_labeled": container_labeled, + "network_disabled": container_network_mode == "none", + "no_docker_volumes": not container_volume_mounts, + }, + "cleanup_readback": { + "container_remove_returncode": int(env["OUTER_CONTAINER_REMOVE_RC"]), + "container_name_query_returncode": int(env["CONTAINER_NAME_READBACK_RC"]), + "container_name_query_stdout": env["CONTAINER_NAME_READBACK"], + "temporary_container_absent": container_absent, + "label_scoped_query_returncode": int(env["CONTAINER_LABEL_READBACK_RC"]), + "label_scoped_query_stdout": env["CONTAINER_LABEL_READBACK"], + "label_scoped_orphan_count": label_scoped_orphan_count, + "label_scoped_temporary_container_absent": label_scoped_container_absent, + "workdir_remove_returncode": int(env["OUTER_WORKDIR_REMOVE_RC"]), + "workdir_lexists_after_cleanup": not workdir_absent, + "temporary_workdir_absent": workdir_absent, + }, + } + checks = data["checks"] + checks["outer_live_counts_exactly_unchanged"] = before_counts == after_counts + checks["outer_live_table_deltas_exactly_zero"] = all(delta == 0 for delta in live_table_deltas.values()) + checks["outer_service_observed_if_required"] = source_tier != "live-readonly" or service_observable + checks["outer_service_stable_if_observed"] = service_gate_passes + checks["outer_container_absent_independent_readback"] = container_absent + checks["outer_labeled_container_absent_independent_readback"] = label_scoped_container_absent + checks["outer_workdir_absent_independent_readback"] = workdir_absent + checks["source_binding_independently_verified"] = source_binding_ok + checks["stale_ephemeral_paths_absent"] = stale_ephemeral_paths_absent + checks["outer_source_tier_enforced"] = source_tier_enforced + checks["outer_container_network_disabled"] = container_network_mode == "none" + checks["outer_container_lifecycle_labeled"] = container_labeled + checks["outer_container_has_no_docker_volumes"] = not container_volume_mounts + checks["outer_isolation_complete"] = outer_ok + if outer_ok and inner_runtime_pass: + data["current_tier"] = "T2_runtime" if source_tier == "isolated-local" else "T3_live_readonly" + else: + data["status"] = "fail" + data["current_tier"] = "T2_runtime" if inner_runtime_pass else "T1_model" + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return data + + +def main(argv: list[str] | None = None) -> int: + arguments = sys.argv[1:] if argv is None else argv + if len(arguments) != 1: + raise SystemExit("usage: finalize_approve_claim_isolated_canary.py REPORT.json") + try: + data = finalize(Path(arguments[0]), os.environ) + except (KeyError, OSError, ValueError, json.JSONDecodeError) as exc: + raise SystemExit(str(exc)) from exc + return 0 if data["status"] == "pass" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/kb_apply_replay_receipt.py b/scripts/kb_apply_replay_receipt.py index 83678a9..2e89345 100644 --- a/scripts/kb_apply_replay_receipt.py +++ b/scripts/kb_apply_replay_receipt.py @@ -446,6 +446,41 @@ def build_replay_receipt( } +def validate_replay_receipt( + receipt: dict[str, Any], + *, + expected_proposal_id: str | None = None, +) -> dict[str, Any]: + """Rebuild and require the exact private replay-receipt contract. + + Canonical JSON comparison is deliberate: Python equality treats booleans + and integers as interchangeable, which is unsafe for authorization and + receipt fields. + """ + if not isinstance(receipt, dict): + raise ValueError("replay receipt must be an object") + proposal = receipt.get("proposal") + rows = receipt.get("canonical_rows") + apply_engine = receipt.get("apply_engine") + if not isinstance(proposal, dict) or not isinstance(rows, dict) or not isinstance(apply_engine, dict): + raise ValueError("replay receipt is missing proposal, canonical rows, or apply-engine metadata") + if expected_proposal_id is not None and proposal.get("id") != expected_proposal_id: + raise ValueError("replay receipt proposal id does not match") + try: + rebuilt = build_replay_receipt( + proposal, + rows, + apply_sql_sha256=apply_engine.get("apply_sql_sha256"), + apply_sql_source=apply_engine.get("source"), + exported_at_utc=receipt.get("exported_at_utc"), + ) + except (KeyError, TypeError, ValueError) as exc: + raise ValueError("replay receipt failed full contract validation") from exc + if _canonical_json(receipt) != _canonical_json(rebuilt): + raise ValueError("replay receipt hashes, counts, rows, or contract fields are internally inconsistent") + return rebuilt + + def resolve_receipt_path( proposal_id: str, *, diff --git a/scripts/proposal_apply_lifecycle.py b/scripts/proposal_apply_lifecycle.py new file mode 100644 index 0000000..9ddb600 --- /dev/null +++ b/scripts/proposal_apply_lifecycle.py @@ -0,0 +1,1513 @@ +#!/usr/bin/env python3 +"""Build and verify exact apply/rollback lifecycle evidence for one proposal. + +The module deliberately supports compensating rollback only for a fresh-owned +``approve_claim`` bundle: every payload-owned row must have been absent before +apply. It is used by the disposable-clone canary and by the production +authorization packet builder. It never connects to a database by itself. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) + +import apply_proposal as ap # noqa: E402 +import kb_apply_replay_receipt as replay_receipt # noqa: E402 + +ROLLBACK_SCHEMA = "livingip.proposalApplyRollback.v1" +LIFECYCLE_SCHEMA = "livingip.proposalApplyLifecycleReceipt.v1" +AUTHORIZATION_SCHEMA = "livingip.proposalApplyAuthorizationPacket.v1" +FRESH_PREFLIGHT_SCHEMA = "livingip.proposalApplyFreshPreflight.v1" +PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA = "livingip.proposalApplyProductionRollbackArtifact.v1" + +RUNTIME_DEPENDENCIES = ( + "scripts/proposal_apply_lifecycle.py", + "scripts/apply_proposal.py", + "scripts/kb_apply_replay_receipt.py", + "scripts/kb_apply_prereqs.sql", +) + +APPROVAL_GATE_CHECKS = ( + "immutable_approval_table_present", + "approve_function_present", + "assert_function_present", + "finish_function_present", + "review_role_present", + "apply_role_present", +) + +ROW_TABLES = { + "claims": "public.claims", + "sources": "public.sources", + "claim_evidence": "public.claim_evidence", + "claim_edges": "public.claim_edges", + "reasoning_tools": "public.reasoning_tools", +} +DELETE_ORDER = ( + "claim_evidence", + "claim_edges", + "reasoning_tools", + "sources", + "claims", +) + + +def canonical_json(value: Any) -> str: + return json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + + +def sha256_json(value: Any) -> str: + return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest() + + +def sha256_text(value: str) -> str: + return hashlib.sha256(value.encode("utf-8")).hexdigest() + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def is_sha256(value: Any) -> bool: + return isinstance(value, str) and len(value) == 64 and all(character in "0123456789abcdef" for character in value) + + +def repo_relative_path(path: Path) -> str: + try: + return path.resolve().relative_to(HERE.parent.resolve()).as_posix() + except ValueError as exc: + raise ValueError(f"authorization dependency must be inside the repository: {path}") from exc + + +def _require_approved_bundle(proposal: dict[str, Any]) -> dict[str, Any]: + if proposal.get("proposal_type") != "approve_claim": + raise ValueError("compensating rollback supports only approve_claim") + if proposal.get("status") != "approved": + raise ValueError("pre-apply proposal must be approved") + if any(proposal.get(field) is not None for field in ("applied_by_handle", "applied_by_agent_id", "applied_at")): + raise ValueError("approved pre-apply proposal must have blank applied fields") + payload = proposal.get("payload") + apply_payload = payload.get("apply_payload") if isinstance(payload, dict) else None + if not isinstance(apply_payload, dict): + raise ValueError("approved proposal requires payload.apply_payload") + # Reuse the audited builder as the strict contract validator. + ap.build_apply_sql(proposal, ap.SERVICE_AGENT_HANDLE) + return apply_payload + + +def build_fresh_preflight( + proposal: dict[str, Any], + target_rows_before: dict[str, list[dict[str, Any]]], + *, + captured_at_utc: str | None = None, +) -> dict[str, Any]: + """Bind an approved bundle to proof that all payload-owned rows are absent.""" + captured = captured_at_utc or datetime.now(timezone.utc).isoformat() + if _parse_utc(captured) is None: + raise ValueError("fresh preflight captured_at_utc must be a timezone-aware timestamp") + apply_payload = _require_approved_bundle(proposal) + if set(target_rows_before) != set(ROW_TABLES): + raise ValueError("fresh preflight must contain every approve_claim target table") + malformed = [name for name, rows in target_rows_before.items() if not isinstance(rows, list)] + if malformed: + raise ValueError(f"fresh preflight target rows must be lists: {malformed}") + nonempty = {name: len(rows) for name, rows in target_rows_before.items() if rows} + if nonempty: + raise ValueError(f"fresh-owned rollback requires absent target rows: {nonempty}") + envelope = { + "proposal_id": str(proposal.get("id")), + "proposal_type": proposal.get("proposal_type"), + "proposal_payload_sha256": sha256_json(proposal.get("payload")), + "apply_payload_sha256": sha256_json(apply_payload), + "target_rows_before": target_rows_before, + } + artifact_envelope = { + "captured_at_utc": captured, + **envelope, + "fresh_owned_targets": True, + } + return { + "artifact": "proposal_apply_fresh_preflight", + "schema": FRESH_PREFLIGHT_SCHEMA, + **artifact_envelope, + "preflight_sha256": sha256_json(envelope), + "preflight_artifact_sha256": sha256_json(artifact_envelope), + } + + +def _require_fresh_preflight(proposal_before: dict[str, Any], preflight: dict[str, Any]) -> None: + apply_payload = _require_approved_bundle(proposal_before) + if not isinstance(preflight, dict): + raise ValueError("fresh preflight must be an object") + expected_keys = { + "artifact", + "schema", + "captured_at_utc", + "proposal_id", + "proposal_type", + "proposal_payload_sha256", + "apply_payload_sha256", + "target_rows_before", + "fresh_owned_targets", + "preflight_sha256", + "preflight_artifact_sha256", + } + if set(preflight) != expected_keys: + raise ValueError("fresh preflight contract fields are incomplete or unexpected") + if ( + preflight.get("artifact") != "proposal_apply_fresh_preflight" + or preflight.get("schema") != FRESH_PREFLIGHT_SCHEMA + or preflight.get("fresh_owned_targets") is not True + ): + raise ValueError("rollback requires an exact fresh-owned preflight contract") + target_rows = preflight.get("target_rows_before") + if ( + not isinstance(target_rows, dict) + or set(target_rows) != set(ROW_TABLES) + or any(not isinstance(rows, list) or rows for rows in target_rows.values()) + ): + raise ValueError("fresh preflight must prove every payload-owned target row was absent") + envelope = { + "proposal_id": str(proposal_before.get("id")), + "proposal_type": proposal_before.get("proposal_type"), + "proposal_payload_sha256": sha256_json(proposal_before.get("payload")), + "apply_payload_sha256": sha256_json(apply_payload), + "target_rows_before": target_rows, + } + if any(preflight.get(key) != value for key, value in envelope.items()): + raise ValueError("fresh preflight does not match the approved proposal") + if preflight.get("preflight_sha256") != sha256_json(envelope): + raise ValueError("fresh preflight hash is internally inconsistent") + artifact_envelope = { + "captured_at_utc": preflight.get("captured_at_utc"), + **envelope, + "fresh_owned_targets": True, + } + if _parse_utc(preflight.get("captured_at_utc")) is None: + raise ValueError("fresh preflight captured_at_utc is invalid") + if preflight.get("preflight_artifact_sha256") != sha256_json(artifact_envelope): + raise ValueError("fresh preflight artifact hash is internally inconsistent") + rebuilt = build_fresh_preflight( + proposal_before, + target_rows, + captured_at_utc=preflight.get("captured_at_utc"), + ) + if canonical_json(preflight) != canonical_json(rebuilt): + raise ValueError("fresh preflight contract is internally inconsistent") + + +def _require_apply_receipt( + proposal_before: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], +) -> dict[str, list[dict[str, Any]]]: + _require_approved_bundle(proposal_before) + _require_fresh_preflight(proposal_before, preflight) + proposal_after = apply_receipt.get("proposal") + if ( + apply_receipt.get("artifact") != "kb_apply_replay_receipt" + or apply_receipt.get("receipt_contract_version") != replay_receipt.RECEIPT_CONTRACT_VERSION + or apply_receipt.get("replay_ready") is not True + ): + raise ValueError("rollback requires a replay-ready kb_apply receipt") + if not isinstance(proposal_after, dict) or proposal_after.get("status") != "applied": + raise ValueError("apply receipt proposal must be applied") + if proposal_after.get("id") != proposal_before.get("id"): + raise ValueError("apply receipt proposal id does not match preflight") + if proposal_after.get("payload") != proposal_before.get("payload"): + raise ValueError("apply receipt payload does not match preflight") + if not proposal_after.get("applied_at") or not proposal_after.get("applied_by_handle"): + raise ValueError("apply receipt requires applied_at and applied_by_handle") + rows = apply_receipt.get("canonical_rows") + if not isinstance(rows, dict) or set(rows) != set(ROW_TABLES): + raise ValueError("apply receipt must contain every approve_claim canonical row family") + for name, table_rows in rows.items(): + if not isinstance(table_rows, list) or any(not isinstance(row, dict) for row in table_rows): + raise ValueError(f"apply receipt {name} rows are malformed") + identifiers = [row.get("id") for row in table_rows] + if any(not identifier for identifier in identifiers) or len(set(identifiers)) != len(identifiers): + raise ValueError(f"apply receipt {name} rows require unique persisted ids") + try: + replay_receipt.validate_replay_receipt( + apply_receipt, + expected_proposal_id=str(proposal_before.get("id")), + ) + except ValueError as exc: + raise ValueError("apply receipt failed full replay-contract validation") from exc + proposal_id = str(proposal_before.get("id")) + for row in rows["claim_evidence"]: + expected_id = ap.deterministic_row_uuid( + proposal_id, + "claim_evidence", + row.get("claim_id"), + row.get("source_id"), + row.get("role"), + ) + if row.get("id") != expected_id: + raise ValueError("fresh apply receipt evidence id is not deterministic") + for row in rows["claim_edges"]: + expected_id = ap.deterministic_row_uuid( + proposal_id, + "claim_edge", + row.get("from_claim"), + row.get("to_claim"), + row.get("edge_type"), + ) + if row.get("id") != expected_id: + raise ValueError("fresh apply receipt edge id is not deterministic") + return rows + + +def _row_assertion(table: str, row: dict[str, Any], label: str) -> str: + row_json = ap.sql_literal(canonical_json(row)) + row_id = ap.sql_literal(row["id"]) + return f""" if (select count(*) from {table} t + where t.id = {row_id}::uuid and to_jsonb(t) = {row_json}::jsonb) <> 1 then + raise exception 'proposal rollback drift: {label} row does not match apply receipt'; + end if;""" + + +def _delete_statement(table: str, rows: list[dict[str, Any]], label: str) -> str: + if not rows: + return f" -- no {label} rows owned by this proposal" + identifiers = ", ".join(f"{ap.sql_literal(row['id'])}::uuid" for row in rows) + return f""" delete from {table} where id in ({identifiers}); + get diagnostics affected = row_count; + if affected <> {len(rows)} then + raise exception 'proposal rollback count mismatch for {label}: expected {len(rows)}, got %', affected; + end if;""" + + +def _require_approval_snapshot(proposal_before: dict[str, Any], approval_snapshot: dict[str, Any]) -> None: + expected_keys = { + "proposal_id", + "proposal_type", + "payload", + "reviewed_by_handle", + "reviewed_by_agent_id", + "reviewed_by_db_role", + "reviewed_at", + "review_note", + } + if not isinstance(approval_snapshot, dict) or set(approval_snapshot) != expected_keys: + raise ValueError("immutable approval snapshot contract is incomplete or unexpected") + reviewer_role = approval_snapshot.get("reviewed_by_db_role") + if not isinstance(reviewer_role, str) or not reviewer_role or reviewer_role == "kb_apply": + raise ValueError("immutable approval snapshot requires an independent review role") + expected = { + "proposal_id": proposal_before.get("id"), + "proposal_type": proposal_before.get("proposal_type"), + "payload": proposal_before.get("payload"), + "reviewed_by_handle": proposal_before.get("reviewed_by_handle"), + "reviewed_by_agent_id": proposal_before.get("reviewed_by_agent_id"), + "reviewed_by_db_role": reviewer_role, + "reviewed_at": proposal_before.get("reviewed_at"), + "review_note": proposal_before.get("review_note"), + } + if canonical_json(approval_snapshot) != canonical_json(expected): + raise ValueError("immutable approval snapshot does not match the approved proposal") + if approval_snapshot.get("reviewed_by_handle") == ap.SERVICE_AGENT_HANDLE: + raise ValueError("apply service cannot independently approve its own proposal") + + +def build_compensating_rollback_sql( + proposal_before: dict[str, Any], + approval_snapshot: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], +) -> str: + """Build one atomic, drift-sensitive apply inverse for a fresh-owned bundle.""" + rows = _require_apply_receipt(proposal_before, preflight, apply_receipt) + _require_approval_snapshot(proposal_before, approval_snapshot) + + applied = apply_receipt["proposal"] + proposal_id = ap.sql_literal(proposal_before["id"]) + payload = ap.sql_literal(canonical_json(proposal_before["payload"])) + reviewed_by_handle = ap.sql_literal(proposal_before.get("reviewed_by_handle")) + reviewed_by_agent_id = ap.sql_literal(proposal_before.get("reviewed_by_agent_id")) + reviewed_at = ap.sql_literal(proposal_before.get("reviewed_at")) + review_note = ap.sql_literal(proposal_before.get("review_note")) + applied_by_handle = ap.sql_literal(applied.get("applied_by_handle")) + applied_by_agent_id = ap.sql_literal(applied.get("applied_by_agent_id")) + applied_at = ap.sql_literal(applied.get("applied_at")) + approval_json = ap.sql_literal(canonical_json(approval_snapshot)) + + row_assertions = [] + for family in DELETE_ORDER: + table = ROW_TABLES[family] + row_assertions.extend( + _row_assertion(table, row, f"{family}[{index}]") for index, row in enumerate(rows[family]) + ) + deletions = [_delete_statement(ROW_TABLES[family], rows[family], family) for family in DELETE_ORDER] + + return f"""begin; +set local standard_conforming_strings = on; +set local lock_timeout = '5s'; +select pg_advisory_xact_lock(hashtextextended('proposal-rollback:' || {proposal_id}, 0)); +do $rollback$ +declare + affected integer; + approval_now jsonb; +begin + perform 1 from kb_stage.kb_proposals + where id = {proposal_id}::uuid + for update; + if not found then + raise exception 'proposal rollback: proposal row missing'; + end if; + if not exists ( + select 1 from kb_stage.kb_proposals + where id = {proposal_id}::uuid + and proposal_type = 'approve_claim' + and status = 'applied' + and payload = {payload}::jsonb + and reviewed_by_handle is not distinct from {reviewed_by_handle} + and reviewed_by_agent_id is not distinct from {reviewed_by_agent_id}::uuid + and reviewed_at is not distinct from {reviewed_at}::timestamptz + and review_note is not distinct from {review_note} + and applied_by_handle is not distinct from {applied_by_handle} + and applied_by_agent_id is not distinct from {applied_by_agent_id}::uuid + and applied_at is not distinct from {applied_at}::timestamptz + ) then + raise exception 'proposal rollback: applied ledger does not match apply receipt'; + end if; + select jsonb_build_object( + 'proposal_id', proposal_id::text, + 'proposal_type', proposal_type, + 'payload', payload, + 'reviewed_by_handle', reviewed_by_handle, + 'reviewed_by_agent_id', reviewed_by_agent_id::text, + 'reviewed_by_db_role', reviewed_by_db_role::text, + 'reviewed_at', reviewed_at::text, + 'review_note', review_note + ) into approval_now + from kb_stage.kb_proposal_approvals + where proposal_id = {proposal_id}::uuid; + if approval_now is distinct from {approval_json}::jsonb then + raise exception 'proposal rollback: immutable approval snapshot drifted'; + end if; +{chr(10).join(row_assertions)} +{chr(10).join(deletions)} + update kb_stage.kb_proposals + set status = 'approved', + applied_by_handle = null, + applied_by_agent_id = null, + applied_at = null, + updated_at = now() + where id = {proposal_id}::uuid + and status = 'applied' + and payload = {payload}::jsonb + and applied_at is not distinct from {applied_at}::timestamptz; + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback: expected one applied ledger row, got %', affected; + end if; +end +$rollback$; +commit; +""" + + +def build_production_rollback_artifact( + *, + proposal_before: dict[str, Any], + approval_snapshot: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], + destination: dict[str, Any], + generated_at_utc: str | None = None, +) -> dict[str, Any]: + """Build a private artifact whose rollback SQL is exactly reconstructable.""" + destination_keys = {"container", "database", "system_identifier", "server_version_num"} + if not isinstance(destination, dict) or set(destination) != destination_keys: + raise ValueError("production rollback artifact requires the exact destination identity") + if any(not destination.get(key) for key in destination_keys): + raise ValueError("production rollback artifact destination identity is incomplete") + generated = generated_at_utc or datetime.now(timezone.utc).isoformat() + if _parse_utc(generated) is None: + raise ValueError("production rollback artifact timestamp is invalid") + rollback_sql = build_compensating_rollback_sql( + proposal_before, + approval_snapshot, + preflight, + apply_receipt, + ) + private_material = { + "proposal_before": proposal_before, + "approval_snapshot": approval_snapshot, + "preflight": preflight, + "apply_receipt": apply_receipt, + } + artifact = { + "artifact": "proposal_apply_production_rollback_artifact", + "schema": PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA, + "generated_at_utc": generated, + "destination": destination, + "proposal_id": str(proposal_before.get("id")), + "proposal_payload_sha256": sha256_json(proposal_before.get("payload")), + "approval_snapshot_sha256": sha256_json(approval_snapshot), + "fresh_preflight_artifact_sha256": preflight.get("preflight_artifact_sha256"), + "apply_receipt_sha256": sha256_json(apply_receipt), + "rollback_sql": rollback_sql, + "rollback_sql_sha256": sha256_text(rollback_sql), + "private_material": private_material, + "privacy": "private production rollback material; never commit or print", + } + artifact["artifact_sha256"] = sha256_json(artifact) + return artifact + + +def validate_production_rollback_artifact( + artifact: dict[str, Any], + *, + expected_destination: dict[str, Any] | None = None, +) -> dict[str, Any]: + expected_keys = { + "artifact", + "schema", + "generated_at_utc", + "destination", + "proposal_id", + "proposal_payload_sha256", + "approval_snapshot_sha256", + "fresh_preflight_artifact_sha256", + "apply_receipt_sha256", + "rollback_sql", + "rollback_sql_sha256", + "private_material", + "privacy", + "artifact_sha256", + } + if not isinstance(artifact, dict) or set(artifact) != expected_keys: + raise ValueError("production rollback artifact contract is incomplete or unexpected") + if ( + artifact.get("artifact") != "proposal_apply_production_rollback_artifact" + or artifact.get("schema") != PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA + or artifact.get("privacy") != "private production rollback material; never commit or print" + or _parse_utc(artifact.get("generated_at_utc")) is None + ): + raise ValueError("production rollback artifact metadata is invalid") + unsigned = dict(artifact) + artifact_sha256 = unsigned.pop("artifact_sha256") + if not is_sha256(artifact_sha256) or sha256_json(unsigned) != artifact_sha256: + raise ValueError("production rollback artifact hash is internally inconsistent") + destination = artifact.get("destination") + destination_keys = {"container", "database", "system_identifier", "server_version_num"} + if not isinstance(destination, dict) or set(destination) != destination_keys: + raise ValueError("production rollback artifact destination is invalid") + if expected_destination is not None and canonical_json(destination) != canonical_json(expected_destination): + raise ValueError("production rollback artifact destination does not match authorization binding") + material = artifact.get("private_material") + material_keys = {"proposal_before", "approval_snapshot", "preflight", "apply_receipt"} + if not isinstance(material, dict) or set(material) != material_keys: + raise ValueError("production rollback artifact private material is incomplete") + expected_sql = build_compensating_rollback_sql( + material["proposal_before"], + material["approval_snapshot"], + material["preflight"], + material["apply_receipt"], + ) + expected_fields = { + "proposal_id": str(material["proposal_before"].get("id")), + "proposal_payload_sha256": sha256_json(material["proposal_before"].get("payload")), + "approval_snapshot_sha256": sha256_json(material["approval_snapshot"]), + "fresh_preflight_artifact_sha256": material["preflight"].get("preflight_artifact_sha256"), + "apply_receipt_sha256": sha256_json(material["apply_receipt"]), + "rollback_sql": expected_sql, + "rollback_sql_sha256": sha256_text(expected_sql), + } + if any(canonical_json(artifact.get(key)) != canonical_json(value) for key, value in expected_fields.items()): + raise ValueError("production rollback artifact does not match reconstructed exact SQL") + return artifact + + +def _row_ids(rows: dict[str, list[dict[str, Any]]]) -> dict[str, list[str]]: + return {name: sorted(str(row["id"]) for row in table_rows) for name, table_rows in rows.items()} + + +def build_lifecycle_receipt( + *, + proposal_before: dict[str, Any], + approval_before: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], + rollback_sql: str, + proposal_after: dict[str, Any], + approval_after: dict[str, Any], + target_rows_after: dict[str, list[dict[str, Any]]], + counts_before: dict[str, int], + counts_after: dict[str, int], + unrelated_before: dict[str, Any], + unrelated_after: dict[str, Any], + rollback_replay_refused: bool, + generated_at_utc: str | None = None, +) -> dict[str, Any]: + rows = _require_apply_receipt(proposal_before, preflight, apply_receipt) + expected_rollback_sql = build_compensating_rollback_sql( + proposal_before, + approval_before, + preflight, + apply_receipt, + ) + target_rows_absent = set(target_rows_after) == set(ROW_TABLES) and all( + isinstance(value, list) and not value for value in target_rows_after.values() + ) + expected_after = dict(proposal_before) + expected_after.update( + { + "status": "approved", + "applied_by_handle": None, + "applied_by_agent_id": None, + "applied_at": None, + } + ) + ledger_restored = all(proposal_after.get(key) == value for key, value in expected_after.items()) + checks = { + "fresh_owned_targets": preflight.get("fresh_owned_targets") is True, + "apply_receipt_replay_ready": apply_receipt.get("replay_ready") is True, + "apply_rows_have_exact_ids": all(rows[name] == apply_receipt["canonical_rows"][name] for name in ROW_TABLES), + "rollback_target_rows_absent": target_rows_absent, + "rollback_counts_restored": counts_after == counts_before, + "rollback_ledger_restored_to_approved": ledger_restored, + "immutable_approval_unchanged": approval_after == approval_before, + "unrelated_rows_unchanged": unrelated_after == unrelated_before, + "rollback_replay_refused_without_mutation": rollback_replay_refused is True, + "rollback_sql_exact_for_bound_rows": rollback_sql == expected_rollback_sql, + } + proposal_projection = { + key: proposal_before.get(key) + for key in ( + "id", + "proposal_type", + "status", + "payload", + "reviewed_by_handle", + "reviewed_by_agent_id", + "reviewed_at", + "review_note", + "applied_by_handle", + "applied_by_agent_id", + "applied_at", + ) + } + receipt = { + "artifact": "proposal_apply_lifecycle_receipt", + "schema": LIFECYCLE_SCHEMA, + "generated_at_utc": generated_at_utc or datetime.now(timezone.utc).isoformat(), + "required_tier": "T2_runtime", + "current_tier": "T2_runtime" if all(checks.values()) else "T1_model", + "proposal_before_apply": proposal_projection, + "proposal_payload_sha256": sha256_json(proposal_before.get("payload")), + "apply_payload_sha256": sha256_json(proposal_before["payload"]["apply_payload"]), + "approval_snapshot_sha256": sha256_json(approval_before), + "fresh_preflight_sha256": preflight["preflight_sha256"], + "fresh_preflight_artifact_sha256": preflight["preflight_artifact_sha256"], + "apply_receipt_sha256": sha256_json(apply_receipt), + "apply_sql_sha256": apply_receipt["apply_engine"]["apply_sql_sha256"], + "applied_row_ids": _row_ids(rows), + "applied_row_sha256": {name: [sha256_json(row) for row in table_rows] for name, table_rows in rows.items()}, + "rollback": { + "schema": ROLLBACK_SCHEMA, + "rollback_sql_sha256": sha256_text(rollback_sql), + "delete_order": list(DELETE_ORDER), + "target_rows_after": target_rows_after, + "counts_before_apply": counts_before, + "counts_after_rollback": counts_after, + "proposal_after": { + key: proposal_after.get(key) + for key in ( + "id", + "proposal_type", + "status", + "reviewed_by_handle", + "reviewed_by_agent_id", + "reviewed_at", + "review_note", + "applied_by_handle", + "applied_by_agent_id", + "applied_at", + ) + }, + }, + "checks": checks, + "pass": all(checks.values()), + "production_apply_executed": False, + "production_apply_authorization_present": False, + "strongest_claim_allowed": ( + "deterministic disposable-clone apply and compensating rollback" + if all(checks.values()) + else "local lifecycle evidence incomplete" + ), + } + receipt["lifecycle_receipt_sha256"] = sha256_json(receipt) + return receipt + + +def _require_lifecycle_receipt(lifecycle_receipt: dict[str, Any]) -> None: + expected_keys = { + "artifact", + "schema", + "generated_at_utc", + "required_tier", + "current_tier", + "proposal_before_apply", + "proposal_payload_sha256", + "apply_payload_sha256", + "approval_snapshot_sha256", + "fresh_preflight_sha256", + "fresh_preflight_artifact_sha256", + "apply_receipt_sha256", + "apply_sql_sha256", + "applied_row_ids", + "applied_row_sha256", + "rollback", + "checks", + "pass", + "production_apply_executed", + "production_apply_authorization_present", + "strongest_claim_allowed", + "lifecycle_receipt_sha256", + } + if not isinstance(lifecycle_receipt, dict) or set(lifecycle_receipt) != expected_keys: + raise ValueError("authorization packet requires the exact lifecycle receipt contract") + if ( + lifecycle_receipt.get("artifact") != "proposal_apply_lifecycle_receipt" + or lifecycle_receipt.get("schema") != LIFECYCLE_SCHEMA + or lifecycle_receipt.get("required_tier") != "T2_runtime" + or lifecycle_receipt.get("current_tier") != "T2_runtime" + or lifecycle_receipt.get("pass") is not True + or lifecycle_receipt.get("production_apply_executed") is not False + or lifecycle_receipt.get("production_apply_authorization_present") is not False + ): + raise ValueError("authorization packet requires a passing, non-production lifecycle receipt") + checks = lifecycle_receipt.get("checks") + if not isinstance(checks, dict) or not checks or any(value is not True for value in checks.values()): + raise ValueError("lifecycle receipt checks are incomplete or failed") + unsigned = dict(lifecycle_receipt) + receipt_sha256 = unsigned.pop("lifecycle_receipt_sha256") + if not is_sha256(receipt_sha256) or sha256_json(unsigned) != receipt_sha256: + raise ValueError("lifecycle receipt hash is internally inconsistent") + proposal = lifecycle_receipt.get("proposal_before_apply") + if not isinstance(proposal, dict) or proposal.get("status") != "approved": + raise ValueError("lifecycle receipt proposal is not approved before apply") + payload = proposal.get("payload") + apply_payload = payload.get("apply_payload") if isinstance(payload, dict) else None + if ( + not isinstance(apply_payload, dict) + or sha256_json(payload) != lifecycle_receipt.get("proposal_payload_sha256") + or sha256_json(apply_payload) != lifecycle_receipt.get("apply_payload_sha256") + ): + raise ValueError("lifecycle receipt proposal hashes are inconsistent") + hash_fields = ( + "approval_snapshot_sha256", + "fresh_preflight_sha256", + "fresh_preflight_artifact_sha256", + "apply_receipt_sha256", + "apply_sql_sha256", + ) + if any(not is_sha256(lifecycle_receipt.get(field)) for field in hash_fields): + raise ValueError("lifecycle receipt is missing an exact SHA-256 binding") + row_ids = lifecycle_receipt.get("applied_row_ids") + row_hashes = lifecycle_receipt.get("applied_row_sha256") + if ( + not isinstance(row_ids, dict) + or not isinstance(row_hashes, dict) + or set(row_ids) != set(ROW_TABLES) + or set(row_hashes) != set(ROW_TABLES) + or any(not isinstance(values, list) for values in row_ids.values()) + or any( + not isinstance(values, list) or any(not is_sha256(value) for value in values) + for values in row_hashes.values() + ) + or any(len(row_ids[name]) != len(row_hashes[name]) for name in ROW_TABLES) + ): + raise ValueError("lifecycle receipt row-level bindings are incomplete") + rollback = lifecycle_receipt.get("rollback") + if not isinstance(rollback, dict) or rollback.get("schema") != ROLLBACK_SCHEMA: + raise ValueError("lifecycle receipt rollback contract is missing") + if not is_sha256(rollback.get("rollback_sql_sha256")): + raise ValueError("lifecycle receipt rollback SQL hash is invalid") + target_rows_after = rollback.get("target_rows_after") + if ( + not isinstance(target_rows_after, dict) + or set(target_rows_after) != set(ROW_TABLES) + or any(not isinstance(rows, list) or rows for rows in target_rows_after.values()) + or rollback.get("counts_before_apply") != rollback.get("counts_after_rollback") + ): + raise ValueError("lifecycle receipt does not prove exact rollback cleanup") + + +def current_git_sha(repo_root: Path) -> str: + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + cwd=repo_root, + text=True, + capture_output=True, + check=False, + ) + return result.stdout.strip() if result.returncode == 0 else "" + + +def runtime_manifest_for_commit( + repo_git_sha: str, + *, + require_current_head: bool, +) -> dict[str, str]: + repo_root = HERE.parent.resolve() + if ( + not isinstance(repo_git_sha, str) + or len(repo_git_sha) != 40 + or any(character not in "0123456789abcdef" for character in repo_git_sha) + ): + raise ValueError("authorization runtime requires a full lowercase Git commit SHA") + head = current_git_sha(repo_root) + if require_current_head and head != repo_git_sha: + raise ValueError("authorization packet Git SHA must equal the current implementation HEAD") + ancestor = subprocess.run( + ["git", "merge-base", "--is-ancestor", repo_git_sha, head], + cwd=repo_root, + text=True, + capture_output=True, + check=False, + ) + if ancestor.returncode != 0: + raise ValueError("authorization runtime commit is not an ancestor of the current checkout") + status = subprocess.run( + ["git", "status", "--porcelain", "--", *RUNTIME_DEPENDENCIES], + cwd=repo_root, + text=True, + capture_output=True, + check=False, + ) + if status.returncode != 0 or status.stdout.strip(): + raise ValueError("authorization runtime dependencies are dirty or untracked") + manifest: dict[str, str] = {} + for relative in RUNTIME_DEPENDENCIES: + current_path = repo_root / relative + if not current_path.is_file(): + raise ValueError(f"authorization runtime dependency is missing: {relative}") + committed = subprocess.run( + ["git", "show", f"{repo_git_sha}:{relative}"], + cwd=repo_root, + capture_output=True, + check=False, + ) + if committed.returncode != 0: + raise ValueError(f"authorization runtime dependency is absent from the bound commit: {relative}") + committed_sha256 = hashlib.sha256(committed.stdout).hexdigest() + current_sha256 = sha256_file(current_path) + if committed_sha256 != current_sha256: + raise ValueError(f"authorization runtime dependency differs from the bound commit: {relative}") + manifest[relative] = current_sha256 + return manifest + + +def _authorization_text_template(binding: dict[str, Any], binding_sha256: str) -> str: + proposal = binding["proposal"] + destination = binding["destination"] + return ( + "I authorize one production DB apply for proposal " + f"{proposal['id']} with payload SHA-256 {proposal['proposal_payload_sha256']} " + f"to Docker container {destination['container']}, database {destination['database']}, " + f"system identifier {destination['system_identifier']}, binding SHA-256 {binding_sha256}. " + "I also authorize conditional production rollback SHA-256 " + " " + "only if the bound postflight fails and no downstream dependency exists. " + "Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. " + "Operator ; received ; expires ." + ) + + +def build_authorization_packet( + lifecycle_receipt: dict[str, Any], + destination_readback: dict[str, Any], + *, + repo_git_sha: str, + apply_engine_path: Path, + approval_gate_path: Path = HERE / "kb_apply_prereqs.sql", + production_rollback_artifact: dict[str, Any] | None = None, + generated_at_utc: str | None = None, +) -> dict[str, Any]: + _require_lifecycle_receipt(lifecycle_receipt) + if not apply_engine_path.is_file(): + raise ValueError(f"apply engine file is missing: {apply_engine_path}") + if not approval_gate_path.is_file(): + raise ValueError(f"approval gate prerequisite file is missing: {approval_gate_path}") + apply_engine_relative = repo_relative_path(apply_engine_path) + approval_gate_relative = repo_relative_path(approval_gate_path) + if apply_engine_relative != "scripts/apply_proposal.py": + raise ValueError("authorization packet requires the canonical apply engine path") + if approval_gate_relative != "scripts/kb_apply_prereqs.sql": + raise ValueError("authorization packet requires the canonical approval-gate path") + runtime_manifest = runtime_manifest_for_commit(repo_git_sha, require_current_head=True) + required_destination = { + key: destination_readback.get(key) + for key in ("container", "database", "system_identifier", "server_version_num") + } + if any(not value for value in required_destination.values()): + raise ValueError("destination readback is missing exact production identity fields") + production_rollback_sha256: str | None = None + production_rollback_artifact_sha256: str | None = None + if production_rollback_artifact is not None: + validated_rollback = validate_production_rollback_artifact( + production_rollback_artifact, + expected_destination=required_destination, + ) + if ( + validated_rollback.get("proposal_id") != lifecycle_receipt["proposal_before_apply"].get("id") + or validated_rollback.get("proposal_payload_sha256") != lifecycle_receipt.get("proposal_payload_sha256") + or validated_rollback.get("approval_snapshot_sha256") != lifecycle_receipt.get("approval_snapshot_sha256") + ): + raise ValueError("production rollback artifact does not match the lifecycle proposal binding") + production_rollback_sha256 = validated_rollback["rollback_sql_sha256"] + production_rollback_artifact_sha256 = validated_rollback["artifact_sha256"] + proposal = lifecycle_receipt["proposal_before_apply"] + approval_gate = destination_readback.get("approval_gate") or {} + bound_gate = {check: approval_gate.get(check) for check in APPROVAL_GATE_CHECKS} + approval_gate_ready = all(bound_gate.get(check) is True for check in APPROVAL_GATE_CHECKS) + candidates = destination_readback.get("approved_strict_candidates") or [] + if not isinstance(candidates, list) or any(not isinstance(row, dict) for row in candidates): + raise ValueError("destination readback candidates must be row objects") + matching = [ + row + for row in candidates + if row.get("id") == proposal["id"] + and row.get("proposal_type") == proposal["proposal_type"] + and row.get("status") == "approved" + and row.get("applied_at") is None + and row.get("proposal_payload_sha256") == lifecycle_receipt["proposal_payload_sha256"] + and row.get("apply_payload_sha256") == lifecycle_receipt["apply_payload_sha256"] + and row.get("approval_snapshot_sha256") == lifecycle_receipt["approval_snapshot_sha256"] + and row.get("reviewed_by_handle") == proposal.get("reviewed_by_handle") + and row.get("reviewed_by_agent_id") == proposal.get("reviewed_by_agent_id") + and row.get("reviewed_at") == proposal.get("reviewed_at") + and row.get("review_note") == proposal.get("review_note") + and row.get("immutable_approval_exact") is True + ] + candidate_projection = ( + { + "id": matching[0].get("id"), + "proposal_type": matching[0].get("proposal_type"), + "status": matching[0].get("status"), + "applied_at": matching[0].get("applied_at"), + "proposal_payload_sha256": matching[0].get("proposal_payload_sha256"), + "apply_payload_sha256": matching[0].get("apply_payload_sha256"), + "approval_snapshot_sha256": matching[0].get("approval_snapshot_sha256"), + "reviewed_by_handle": matching[0].get("reviewed_by_handle"), + "reviewed_by_agent_id": matching[0].get("reviewed_by_agent_id"), + "reviewed_at": matching[0].get("reviewed_at"), + "review_note": matching[0].get("review_note"), + "immutable_approval_exact": matching[0].get("immutable_approval_exact"), + } + if len(matching) == 1 + else None + ) + observed_at_utc = destination_readback.get("observed_at_utc") + destination_readback_was_read_only = ( + destination_readback.get("read_only_transaction") is True and _parse_utc(observed_at_utc) is not None + ) + production_preconditions = { + "destination_readback_observed_at_utc": observed_at_utc, + "destination_readback_was_read_only": destination_readback_was_read_only, + "approval_gate_ready": approval_gate_ready, + "strict_proposal_present_and_approved": len(matching) == 1, + "matching_candidate_count": len(matching), + "exact_production_rollback_packet_ready": production_rollback_sha256 is not None, + "production_apply_authorization_present": False, + "production_apply_executed": False, + } + binding = { + "proposal": { + "id": proposal["id"], + "proposal_type": proposal["proposal_type"], + "required_status": "approved", + "required_applied_at": None, + "payload": proposal["payload"], + "reviewed_by_handle": proposal.get("reviewed_by_handle"), + "reviewed_by_agent_id": proposal.get("reviewed_by_agent_id"), + "reviewed_at": proposal.get("reviewed_at"), + "review_note": proposal.get("review_note"), + "proposal_payload_sha256": lifecycle_receipt["proposal_payload_sha256"], + "apply_payload_sha256": lifecycle_receipt["apply_payload_sha256"], + "approval_snapshot_sha256": lifecycle_receipt["approval_snapshot_sha256"], + }, + "destination": required_destination, + "destination_readback": { + "observed_at_utc": observed_at_utc, + "read_only_transaction": destination_readback.get("read_only_transaction"), + }, + "approval_gate": bound_gate, + "candidate_readback": { + "matching_candidate_count": len(matching), + "matching_candidate": candidate_projection, + }, + "approval_gate_dependency": { + "path": approval_gate_relative, + "sha256": sha256_file(approval_gate_path), + "requires_separate_production_authorization": True, + }, + "engine": { + "repo_git_sha": repo_git_sha, + "apply_engine_path": apply_engine_relative, + "apply_engine_sha256": sha256_file(apply_engine_path), + "apply_sql_sha256": lifecycle_receipt["apply_sql_sha256"], + "runtime_manifest": runtime_manifest, + }, + "rollback": { + "rollback_sql_sha256": lifecycle_receipt["rollback"]["rollback_sql_sha256"], + "lifecycle_receipt_sha256": lifecycle_receipt["lifecycle_receipt_sha256"], + "scope": "disposable_clone_rehearsal", + "production_executable": False, + "conditional_rollback_requires_explicit_authorization": True, + }, + "production_rollback": { + "status": "ready" if production_rollback_sha256 is not None else "not_generated", + "rollback_sql_sha256": production_rollback_sha256, + "artifact_schema": ( + PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA if production_rollback_artifact_sha256 is not None else None + ), + "artifact_sha256": production_rollback_artifact_sha256, + "scope": "exact_production_destination_postapply_receipt", + "bound_for_action_time_authorization": production_rollback_artifact_sha256 is not None, + "conditional_rollback_requires_explicit_authorization": True, + }, + "expected_rows": { + "row_ids": lifecycle_receipt["applied_row_ids"], + "row_sha256": lifecycle_receipt["applied_row_sha256"], + }, + "production_preconditions": production_preconditions, + } + binding_sha256 = sha256_json(binding) + packet = { + "artifact": "proposal_apply_production_authorization_packet", + "schema": AUTHORIZATION_SCHEMA, + "generated_at_utc": generated_at_utc or datetime.now(timezone.utc).isoformat(), + "required_tier": "T6_authorized_production", + "current_tier": "T3_live_readonly", + "binding": binding, + "binding_sha256": binding_sha256, + "production_preconditions": dict(production_preconditions), + "authorization_record_required": { + "authorized": True, + "rollback_authorized": True, + "binding_sha256": binding_sha256, + "production_rollback_sql_sha256": (production_rollback_sha256 or ""), + "operator_identity": "", + "received_at_utc": "", + "expires_at_utc": "", + "authorization_text": "", + }, + "authorization_text_template": _authorization_text_template(binding, binding_sha256), + "safe_to_enter_apply_window": False, + "production_apply_authorization_present": False, + "production_apply_executed": False, + "scope_exclusions": { + "approval_gate_deployment_authorized": False, + "telegram_send_authorized": False, + "gcp_promotion_authorized": False, + "cloud_sql_exposure_authorized": False, + }, + "production_rollback_requirement": { + "status": "ready" if production_rollback_sha256 is not None else "not_generated", + "production_rollback_sql_sha256": production_rollback_sha256, + "production_rollback_artifact_schema": ( + PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA if production_rollback_artifact_sha256 is not None else None + ), + "production_rollback_artifact_sha256": production_rollback_artifact_sha256, + "clone_rehearsal_rollback_sql_sha256": lifecycle_receipt["rollback"]["rollback_sql_sha256"], + "clone_rehearsal_is_not_production_rollback": True, + "required_before_apply_window": True, + "required_binding": ( + "an exact production rollback SQL SHA-256 in both the action-time record and authorization text" + ), + }, + "next_exact_action": ( + "refresh the exact destination through a read-only transaction" + if not destination_readback_was_read_only + else "deploy and independently verify the immutable approval gate before selecting a production proposal" + if not approval_gate_ready + else "stage this exact strict proposal in production and obtain independent review" + if not matching + else "generate and retain the exact production rollback SQL" + if production_rollback_sha256 is None + else "obtain the exact action-time authorization record" + ), + "claim_ceiling": ( + "T2 clone lifecycle plus T3 destination readback; no production apply authorization or execution" + ), + } + return packet + + +def expected_authorization_text(packet: dict[str, Any], record: dict[str, Any]) -> str: + template = packet.get("authorization_text_template") + if not isinstance(template, str): + raise ValueError("authorization packet has no text template") + return ( + template.replace( + "", + str(record.get("production_rollback_sql_sha256") or ""), + ) + .replace("", str(record.get("operator_identity") or "")) + .replace("", str(record.get("received_at_utc") or ""), 1) + .replace("", str(record.get("expires_at_utc") or ""), 1) + ) + + +def _parse_utc(value: Any) -> datetime | None: + if not isinstance(value, str) or not value: + return None + try: + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + if parsed.tzinfo is None: + return None + return parsed.astimezone(timezone.utc) + + +def verify_authorization_record( + packet: dict[str, Any], + record: dict[str, Any], + *, + observed_destination: dict[str, Any], + apply_engine_path: Path, + approval_gate_path: Path = HERE / "kb_apply_prereqs.sql", + production_rollback_artifact: dict[str, Any] | None = None, + now: datetime | None = None, +) -> dict[str, Any]: + packet = packet if isinstance(packet, dict) else {} + record = record if isinstance(record, dict) else {} + observed_destination = observed_destination if isinstance(observed_destination, dict) else {} + now = (now or datetime.now(timezone.utc)).astimezone(timezone.utc) + received = _parse_utc(record.get("received_at_utc")) + expires = _parse_utc(record.get("expires_at_utc")) + binding = packet.get("binding") if isinstance(packet.get("binding"), dict) else {} + packet_preconditions = ( + packet.get("production_preconditions") if isinstance(packet.get("production_preconditions"), dict) else {} + ) + bound_preconditions = ( + binding.get("production_preconditions") if isinstance(binding.get("production_preconditions"), dict) else {} + ) + required_destination = binding.get("destination") if isinstance(binding.get("destination"), dict) else {} + destination_keys = {"container", "database", "system_identifier", "server_version_num"} + observed_destination_projection = {key: observed_destination.get(key) for key in destination_keys} + destination_matches = set(required_destination) == destination_keys and canonical_json( + observed_destination_projection + ) == canonical_json(required_destination) + bound_destination_readback = ( + binding.get("destination_readback") if isinstance(binding.get("destination_readback"), dict) else {} + ) + required_gate = binding.get("approval_gate") if isinstance(binding.get("approval_gate"), dict) else {} + observed_gate = ( + observed_destination.get("approval_gate") if isinstance(observed_destination.get("approval_gate"), dict) else {} + ) + required_gate_ready = set(required_gate) == set(APPROVAL_GATE_CHECKS) and all( + required_gate.get(key) is True for key in APPROVAL_GATE_CHECKS + ) + observed_gate_ready = all(observed_gate.get(key) is True for key in APPROVAL_GATE_CHECKS) + gate_matches = ( + required_gate_ready + and observed_gate_ready + and all(observed_gate.get(key) is required_gate.get(key) for key in APPROVAL_GATE_CHECKS) + ) + required_proposal = binding.get("proposal") if isinstance(binding.get("proposal"), dict) else {} + required_payload = required_proposal.get("payload") + required_apply_payload = required_payload.get("apply_payload") if isinstance(required_payload, dict) else None + proposal_binding_self_consistent = ( + required_proposal.get("required_status") == "approved" + and required_proposal.get("required_applied_at") is None + and isinstance(required_apply_payload, dict) + and sha256_json(required_payload) == required_proposal.get("proposal_payload_sha256") + and sha256_json(required_apply_payload) == required_proposal.get("apply_payload_sha256") + and is_sha256(required_proposal.get("approval_snapshot_sha256")) + ) + bound_candidate_readback = ( + binding.get("candidate_readback") if isinstance(binding.get("candidate_readback"), dict) else {} + ) + bound_candidate = bound_candidate_readback.get("matching_candidate") + expected_bound_candidate = { + "id": required_proposal.get("id"), + "proposal_type": required_proposal.get("proposal_type"), + "status": required_proposal.get("required_status"), + "applied_at": required_proposal.get("required_applied_at"), + "proposal_payload_sha256": required_proposal.get("proposal_payload_sha256"), + "apply_payload_sha256": required_proposal.get("apply_payload_sha256"), + "approval_snapshot_sha256": required_proposal.get("approval_snapshot_sha256"), + "reviewed_by_handle": required_proposal.get("reviewed_by_handle"), + "reviewed_by_agent_id": required_proposal.get("reviewed_by_agent_id"), + "reviewed_at": required_proposal.get("reviewed_at"), + "review_note": required_proposal.get("review_note"), + "immutable_approval_exact": True, + } + bound_candidate_exact = ( + bound_candidate_readback.get("matching_candidate_count") == 1 + and isinstance(bound_candidate, dict) + and canonical_json(bound_candidate) == canonical_json(expected_bound_candidate) + ) + observed_candidates = observed_destination.get("approved_strict_candidates") or [] + if not isinstance(observed_candidates, list) or any(not isinstance(row, dict) for row in observed_candidates): + observed_candidates = [] + matching_candidates = [ + candidate + for candidate in observed_candidates + if candidate.get("id") == required_proposal.get("id") + and candidate.get("proposal_type") == required_proposal.get("proposal_type") + and candidate.get("status") == required_proposal.get("required_status") + and candidate.get("applied_at") is required_proposal.get("required_applied_at") + and candidate.get("proposal_payload_sha256") == required_proposal.get("proposal_payload_sha256") + and candidate.get("apply_payload_sha256") == required_proposal.get("apply_payload_sha256") + and candidate.get("approval_snapshot_sha256") == required_proposal.get("approval_snapshot_sha256") + and candidate.get("reviewed_by_handle") == required_proposal.get("reviewed_by_handle") + and candidate.get("reviewed_by_agent_id") == required_proposal.get("reviewed_by_agent_id") + and candidate.get("reviewed_at") == required_proposal.get("reviewed_at") + and candidate.get("review_note") == required_proposal.get("review_note") + and candidate.get("immutable_approval_exact") is True + ] + production_rollback_sha256 = record.get("production_rollback_sql_sha256") + bound_production_rollback = ( + binding.get("production_rollback") if isinstance(binding.get("production_rollback"), dict) else {} + ) + bound_production_rollback_sha256 = bound_production_rollback.get("rollback_sql_sha256") + production_rollback_artifact_valid = False + if production_rollback_artifact is not None: + try: + validated_rollback = validate_production_rollback_artifact( + production_rollback_artifact, + expected_destination=required_destination, + ) + production_rollback_artifact_valid = ( + validated_rollback.get("artifact_sha256") == bound_production_rollback.get("artifact_sha256") + and validated_rollback.get("schema") == bound_production_rollback.get("artifact_schema") + and validated_rollback.get("rollback_sql_sha256") == bound_production_rollback_sha256 + and validated_rollback.get("proposal_id") == required_proposal.get("id") + and validated_rollback.get("proposal_payload_sha256") + == required_proposal.get("proposal_payload_sha256") + and validated_rollback.get("approval_snapshot_sha256") + == required_proposal.get("approval_snapshot_sha256") + ) + except ValueError: + production_rollback_artifact_valid = False + production_rollback_packet_ready = ( + bound_production_rollback.get("status") == "ready" + and bound_production_rollback.get("bound_for_action_time_authorization") is True + and bound_production_rollback.get("conditional_rollback_requires_explicit_authorization") is True + and bound_production_rollback.get("artifact_schema") == PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA + and is_sha256(bound_production_rollback.get("artifact_sha256")) + and is_sha256(bound_production_rollback_sha256) + and production_rollback_artifact_valid + ) + packet_binding_sha256 = packet.get("binding_sha256") + binding_self_consistent = is_sha256(packet_binding_sha256) and sha256_json(binding) == packet_binding_sha256 + try: + expected_template = _authorization_text_template(binding, packet_binding_sha256) + except (KeyError, TypeError): + expected_template = "" + expected_scope_exclusions = { + "approval_gate_deployment_authorized": False, + "telegram_send_authorized": False, + "gcp_promotion_authorized": False, + "cloud_sql_exposure_authorized": False, + } + required_record_keys = { + "authorized", + "rollback_authorized", + "binding_sha256", + "production_rollback_sql_sha256", + "operator_identity", + "received_at_utc", + "expires_at_utc", + "authorization_text", + } + expected_record_template = { + "authorized": True, + "rollback_authorized": True, + "binding_sha256": packet_binding_sha256, + "production_rollback_sql_sha256": ( + bound_production_rollback_sha256 + if is_sha256(bound_production_rollback_sha256) + else "" + ), + "operator_identity": "", + "received_at_utc": "", + "expires_at_utc": "", + "authorization_text": "", + } + try: + expected_record_text = expected_authorization_text(packet, record) + except ValueError: + expected_record_text = "" + bound_engine = binding.get("engine") if isinstance(binding.get("engine"), dict) else {} + bound_gate_dependency = ( + binding.get("approval_gate_dependency") if isinstance(binding.get("approval_gate_dependency"), dict) else {} + ) + runtime_provenance_valid = False + try: + current_runtime_manifest = runtime_manifest_for_commit( + bound_engine.get("repo_git_sha"), + require_current_head=False, + ) + runtime_provenance_valid = canonical_json(current_runtime_manifest) == canonical_json( + bound_engine.get("runtime_manifest") + ) + except ValueError: + runtime_provenance_valid = False + try: + apply_engine_relative = repo_relative_path(apply_engine_path) + except ValueError: + apply_engine_relative = "" + try: + approval_gate_relative = repo_relative_path(approval_gate_path) + except ValueError: + approval_gate_relative = "" + action_observed_at = _parse_utc(observed_destination.get("observed_at_utc")) + action_readback_fresh = ( + received is not None + and action_observed_at is not None + and received <= action_observed_at <= now + and observed_destination.get("read_only_transaction") is True + ) + checks = { + "packet_schema_exact": ( + packet.get("artifact") == "proposal_apply_production_authorization_packet" + and packet.get("schema") == AUTHORIZATION_SCHEMA + and packet.get("required_tier") == "T6_authorized_production" + and packet.get("current_tier") == "T3_live_readonly" + and _parse_utc(packet.get("generated_at_utc")) is not None + ), + "packet_binding_sha256_self_consistent": binding_self_consistent, + "packet_preconditions_bound_exactly": ( + canonical_json(packet_preconditions) == canonical_json(bound_preconditions) + ), + "proposal_binding_self_consistent": proposal_binding_self_consistent, + "packet_destination_readback_was_read_only": ( + packet_preconditions.get("destination_readback_was_read_only") is True + and bound_destination_readback.get("read_only_transaction") is True + and _parse_utc(bound_destination_readback.get("observed_at_utc")) is not None + and bound_destination_readback.get("observed_at_utc") + == packet_preconditions.get("destination_readback_observed_at_utc") + ), + "approval_gate_ready_at_packet": ( + packet_preconditions.get("approval_gate_ready") is True and required_gate_ready + ), + "exact_production_rollback_packet_ready": ( + packet_preconditions.get("exact_production_rollback_packet_ready") is True + and production_rollback_packet_ready + ), + "strict_proposal_present_and_approved": ( + packet_preconditions.get("strict_proposal_present_and_approved") is True + and packet_preconditions.get("matching_candidate_count") == 1 + and bound_candidate_exact + ), + "packet_never_self_authorizes_or_expands_scope": ( + packet.get("safe_to_enter_apply_window") is False + and packet.get("production_apply_authorization_present") is False + and packet.get("production_apply_executed") is False + and packet_preconditions.get("production_apply_authorization_present") is False + and packet_preconditions.get("production_apply_executed") is False + and canonical_json(packet.get("scope_exclusions")) == canonical_json(expected_scope_exclusions) + ), + "authorization_text_template_exact": packet.get("authorization_text_template") == expected_template, + "authorization_record_template_exact": ( + canonical_json(packet.get("authorization_record_required")) == canonical_json(expected_record_template) + ), + "authorization_record_contract_exact": isinstance(record, dict) and set(record) == required_record_keys, + "authorized_is_exact_true": record.get("authorized") is True, + "rollback_authorized_is_exact_true": record.get("rollback_authorized") is True, + "production_rollback_sql_sha256_exact": ( + is_sha256(production_rollback_sha256) and production_rollback_sha256 == bound_production_rollback_sha256 + ), + "binding_sha256_exact": binding_self_consistent and record.get("binding_sha256") == packet_binding_sha256, + "operator_identity_present": ( + isinstance(record.get("operator_identity"), str) + and bool(record.get("operator_identity", "").strip()) + and not record.get("operator_identity", "").startswith("<") + ), + "authorization_window_valid": ( + received is not None and expires is not None and received <= now <= expires and received < expires + ), + "authorization_text_exact": bool(expected_record_text) + and record.get("authorization_text") == expected_record_text, + "destination_identity_exact": destination_matches, + "destination_readback_fresh_and_read_only_at_action": action_readback_fresh, + "approval_gate_identity_exact_at_action": gate_matches, + "strict_proposal_exact_at_action": len(matching_candidates) == 1, + "apply_engine_sha256_exact": ( + apply_engine_path.is_file() + and apply_engine_relative == bound_engine.get("apply_engine_path") + and sha256_file(apply_engine_path) == bound_engine.get("apply_engine_sha256") + ), + "runtime_commit_and_manifest_exact": runtime_provenance_valid, + "approval_gate_prerequisite_sha256_exact": ( + approval_gate_path.is_file() + and approval_gate_relative == bound_gate_dependency.get("path") + and sha256_file(approval_gate_path) == bound_gate_dependency.get("sha256") + ), + } + return { + "schema": "livingip.proposalApplyAuthorizationVerification.v1", + "checks": checks, + "safe_to_enter_apply_window": all(checks.values()), + "production_apply_authorization_present": all(checks.values()), + "production_apply_executed": False, + } + + +def _load_json(path: Path) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + raise ValueError(f"JSON file must contain an object: {path}") + return data + + +def _write_json(path: Path, data: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def write_authorization_markdown(path: Path, packet: dict[str, Any]) -> None: + binding = packet["binding"] + proposal = binding["proposal"] + destination = binding["destination"] + engine = binding["engine"] + rollback = binding["rollback"] + production_rollback = binding["production_rollback"] + gate_dependency = binding["approval_gate_dependency"] + preconditions = packet["production_preconditions"] + lines = [ + "# Proposal Apply Production Authorization Packet", + "", + f"Generated UTC: `{packet['generated_at_utc']}`", + f"Required tier: `{packet['required_tier']}`", + f"Current tier: `{packet['current_tier']}`", + f"Safe to enter apply window: `{packet['safe_to_enter_apply_window']}`", + f"Production apply authorized: `{packet['production_apply_authorization_present']}`", + f"Production apply executed: `{packet['production_apply_executed']}`", + "", + "## Exact Binding", + "", + f"- Proposal: `{proposal['id']}` (`{proposal['proposal_type']}`)", + f"- Proposal payload SHA-256: `{proposal['proposal_payload_sha256']}`", + f"- Apply payload SHA-256: `{proposal['apply_payload_sha256']}`", + f"- Immutable approval SHA-256: `{proposal['approval_snapshot_sha256']}`", + f"- Destination: `{destination['container']}/{destination['database']}`", + f"- Destination system identifier: `{destination['system_identifier']}`", + f"- Apply engine SHA-256: `{engine['apply_engine_sha256']}`", + f"- Apply engine path: `{engine['apply_engine_path']}`", + f"- Apply SQL SHA-256: `{engine['apply_sql_sha256']}`", + f"- Clone rehearsal rollback SQL SHA-256: `{rollback['rollback_sql_sha256']}`", + f"- Production rollback status: `{production_rollback['status']}`", + f"- Production rollback SQL SHA-256: `{production_rollback['rollback_sql_sha256']}`", + f"- Approval gate prerequisite SHA-256: `{gate_dependency['sha256']}`", + f"- Approval gate prerequisite path: `{gate_dependency['path']}`", + f"- Binding SHA-256: `{packet['binding_sha256']}`", + "", + "## Production Preconditions", + "", + ] + lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(preconditions.items())) + lines.extend(["", "## Approval Gate Readback", ""]) + lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(binding["approval_gate"].items())) + lines.extend( + [ + "", + "## Exact Action-Time Authorization Text", + "", + "This text is not actionable until every production precondition above is true.", + "", + packet["authorization_text_template"], + "", + "## Current Gate", + "", + packet["next_exact_action"], + "", + "The production approval-gate deployment is explicitly outside this apply packet and requires separate authorization.", + "", + "## Claim Ceiling", + "", + packet["claim_ceiling"], + "", + ] + ) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("\n".join(lines), encoding="utf-8") + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + packet = sub.add_parser("build-authorization-packet") + packet.add_argument("--lifecycle-receipt", required=True, type=Path) + packet.add_argument("--destination-readback", required=True, type=Path) + packet.add_argument("--apply-engine", type=Path, default=HERE / "apply_proposal.py") + packet.add_argument("--approval-gate", type=Path, default=HERE / "kb_apply_prereqs.sql") + packet.add_argument("--production-rollback-artifact", type=Path) + packet.add_argument("--repo-git-sha") + packet.add_argument("--out", required=True, type=Path) + packet.add_argument("--markdown-out", type=Path) + verify = sub.add_parser("verify-authorization") + verify.add_argument("--packet", required=True, type=Path) + verify.add_argument("--record", required=True, type=Path) + verify.add_argument("--observed-destination", required=True, type=Path) + verify.add_argument("--apply-engine", type=Path, default=HERE / "apply_proposal.py") + verify.add_argument("--approval-gate", type=Path, default=HERE / "kb_apply_prereqs.sql") + verify.add_argument("--production-rollback-artifact", type=Path) + verify.add_argument("--out", required=True, type=Path) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + if args.command == "build-authorization-packet": + raw = _load_json(args.lifecycle_receipt) + lifecycle = raw.get("applied_rollback_receipt", raw) + destination = _load_json(args.destination_readback) + packet = build_authorization_packet( + lifecycle, + destination, + repo_git_sha=args.repo_git_sha or current_git_sha(HERE.parent), + apply_engine_path=args.apply_engine, + approval_gate_path=args.approval_gate, + production_rollback_artifact=( + _load_json(args.production_rollback_artifact) if args.production_rollback_artifact else None + ), + ) + _write_json(args.out, packet) + if args.markdown_out: + write_authorization_markdown(args.markdown_out, packet) + print(json.dumps({"out": str(args.out), "safe_to_enter_apply_window": False}, sort_keys=True)) + return 0 + packet = _load_json(args.packet) + record = _load_json(args.record) + destination = _load_json(args.observed_destination) + result = verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=args.apply_engine, + approval_gate_path=args.approval_gate, + production_rollback_artifact=( + _load_json(args.production_rollback_artifact) if args.production_rollback_artifact else None + ), + ) + _write_json(args.out, result) + print(json.dumps(result, sort_keys=True)) + return 0 if result["safe_to_enter_apply_window"] else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_approve_claim_clone_canary.py b/scripts/run_approve_claim_clone_canary.py index 6985401..87c4de2 100644 --- a/scripts/run_approve_claim_clone_canary.py +++ b/scripts/run_approve_claim_clone_canary.py @@ -18,6 +18,7 @@ import re import shutil import subprocess import sys +import tempfile import uuid from datetime import datetime, timezone from pathlib import Path @@ -28,13 +29,12 @@ REPO_ROOT = HERE.parent sys.path.insert(0, str(HERE)) import apply_proposal as ap # noqa: E402 +import proposal_apply_lifecycle as lifecycle # noqa: E402 SAFE_DB_RE = re.compile(r"^[a-z][a-z0-9_]{1,62}$") REVIEWER_HANDLE = "m3ta" REVIEW_NOTE = "Reviewed exact strict payload inside the disposable clone." -DEFAULT_REVIEW_SECRETS_FILE = ( - "/home/teleo/.hermes/profiles/leoclean/secrets/kb-review.env" -) +DEFAULT_REVIEW_SECRETS_FILE = "/home/teleo/.hermes/profiles/leoclean/secrets/kb-review.env" SECURITY_DEFINER_DEPENDENCIES = { "approve_strict_proposal": "kb_review", "assert_approved_proposal": "kb_apply", @@ -98,8 +98,10 @@ def _source_file_manifest(args: argparse.Namespace) -> list[dict[str, Any]]: paths = { Path(__file__).resolve(), (HERE / "run_approve_claim_isolated_container_canary.sh").resolve(), + (HERE / "finalize_approve_claim_isolated_canary.py").resolve(), Path(args.approve_script).resolve(), Path(args.apply_script).resolve(), + (HERE / "proposal_apply_lifecycle.py").resolve(), Path(args.prereqs).resolve(), } return sorted( @@ -190,9 +192,7 @@ def _schema_copy(container: str, source_db: str, clone_db: str) -> None: "ON_ERROR_STOP=1", "-q", ] - dump = subprocess.Popen( - dump_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True - ) + dump = subprocess.Popen(dump_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) assert dump.stdout is not None assert dump.stderr is not None restore = subprocess.run( @@ -319,37 +319,22 @@ def _cluster_roles_are_preprovisioned(rows: list[dict[str, Any]]) -> bool: def _clone_prerequisites_sql(prereqs: str) -> tuple[str, dict[str, Any]]: """Remove cluster-global role DDL while preserving clone-local objects/ACLs.""" start_marker = "do $roles$" - end_marker = ( - "alter role kb_apply login noinherit nosuperuser nocreatedb nocreaterole " - "noreplication nobypassrls;" - ) + end_marker = "alter role kb_apply login noinherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls;" start = prereqs.lower().find(start_marker) end_start = prereqs.lower().find(end_marker, start) if start < 0 or end_start < 0: - raise RuntimeError( - "prerequisites role bootstrap block changed; refusing unsafe clone execution" - ) + raise RuntimeError("prerequisites role bootstrap block changed; refusing unsafe clone execution") end = end_start + len(end_marker) removed = prereqs[start:end] - sanitized = ( - prereqs[:start] - + "-- Cluster-global role DDL omitted by disposable clone canary.\n" - + prereqs[end:] - ) + sanitized = prereqs[:start] + "-- Cluster-global role DDL omitted by disposable clone canary.\n" + prereqs[end:] if re.search(r"\b(?:create|alter|drop)\s+role\b", sanitized, re.IGNORECASE): - raise RuntimeError( - "prerequisites still contain cluster-global role DDL after sanitization" - ) + raise RuntimeError("prerequisites still contain cluster-global role DDL after sanitization") return sanitized, { "source_sha256": hashlib.sha256(prereqs.encode()).hexdigest(), "clone_sql_sha256": hashlib.sha256(sanitized.encode()).hexdigest(), "cluster_role_ddl_removed": True, - "removed_create_role_statements": len( - re.findall(r"\bcreate\s+role\b", removed, re.IGNORECASE) - ), - "removed_alter_role_statements": len( - re.findall(r"\balter\s+role\b", removed, re.IGNORECASE) - ), + "removed_create_role_statements": len(re.findall(r"\bcreate\s+role\b", removed, re.IGNORECASE)), + "removed_alter_role_statements": len(re.findall(r"\balter\s+role\b", removed, re.IGNORECASE)), } @@ -460,9 +445,7 @@ def load_normalized_fixture(path: Path, namespace: str) -> dict[str, Any]: data = json.loads(path.read_text(encoding="utf-8")) previews = data.get("normalization_previews") if isinstance(data, dict) else None if not isinstance(previews, list) or len(previews) != 1: - raise ValueError( - "normalization JSON must contain exactly one normalization_previews row" - ) + raise ValueError("normalization JSON must contain exactly one normalization_previews row") preview = previews[0] children = preview.get("strict_child_proposals") if ( @@ -472,9 +455,7 @@ def load_normalized_fixture(path: Path, namespace: str) -> dict[str, Any]: or len(children) != 1 or children[0].get("proposal_type") != "approve_claim" ): - raise ValueError( - "normalization JSON is not one unblocked strict approve_claim child" - ) + raise ValueError("normalization JSON is not one unblocked strict approve_claim child") apply_payload = (children[0].get("payload") or {}).get("apply_payload") if not isinstance(apply_payload, dict): raise ValueError("strict approve_claim child has no apply_payload") @@ -527,9 +508,7 @@ def _payload_agent_ids(payload: dict[str, Any]) -> list[str]: return sorted(values) -def _seed_payload_agents( - container: str, source_db: str, clone_db: str, payload: dict[str, Any] -) -> dict[str, Any]: +def _seed_payload_agents(container: str, source_db: str, clone_db: str, payload: dict[str, Any]) -> dict[str, Any]: required_ids = _payload_agent_ids(payload) if not required_ids: return { @@ -538,9 +517,7 @@ def _seed_payload_agents( "clone_rows": [], "exact_clone_copy": True, } - id_sql = ", ".join( - f"{ap.sql_literal(value)}::uuid" for value in required_ids - ) + id_sql = ", ".join(f"{ap.sql_literal(value)}::uuid" for value in required_ids) source_raw = _psql( container, source_db, @@ -560,8 +537,8 @@ rollback; statements = [ f"""insert into public.agents (id, handle, kind) -values ({ap.sql_literal(row['id'])}::uuid, {ap.sql_literal(row['handle'])}, - {ap.sql_literal(row['kind'])}) +values ({ap.sql_literal(row["id"])}::uuid, {ap.sql_literal(row["handle"])}, + {ap.sql_literal(row["kind"])}) on conflict (id) do nothing;""" for row in source_rows ] @@ -604,16 +581,14 @@ rollback; ) source_row = json.loads(raw) if not source_row: - raise RuntimeError( - f"reviewer principal dependency is missing from source agents: {reviewer_handle}" - ) + raise RuntimeError(f"reviewer principal dependency is missing from source agents: {reviewer_handle}") _psql( container, clone_db, f"""insert into public.agents (id, handle, kind) -values ({ap.sql_literal(source_row['id'])}::uuid, - {ap.sql_literal(source_row['handle'])}, - {ap.sql_literal(source_row['kind'])});""", +values ({ap.sql_literal(source_row["id"])}::uuid, + {ap.sql_literal(source_row["handle"])}, + {ap.sql_literal(source_row["kind"])});""", tuples=False, ) clone_raw = _psql( @@ -635,9 +610,7 @@ from public.agents where handle = {ap.sql_literal(reviewer_handle)};""", } -def _seed_external_claims( - container: str, source_db: str, clone_db: str, payload: dict[str, Any] -) -> dict[str, Any]: +def _seed_external_claims(container: str, source_db: str, clone_db: str, payload: dict[str, Any]) -> dict[str, Any]: external_ids = _external_claim_ids(payload) if not external_ids: return { @@ -646,9 +619,7 @@ def _seed_external_claims( "clone_rows": [], "exact_clone_copy": True, } - id_sql = ", ".join( - f"{ap.sql_literal(value)}::uuid" for value in external_ids - ) + id_sql = ", ".join(f"{ap.sql_literal(value)}::uuid" for value in external_ids) raw = _psql( container, source_db, @@ -666,28 +637,22 @@ rollback; found = {row["id"] for row in rows} missing = sorted(set(external_ids) - found) if missing: - raise RuntimeError( - f"normalized bundle references missing source claims: {missing}" - ) + raise RuntimeError(f"normalized bundle references missing source claims: {missing}") statements = [] for row in rows: tags = row.get("tags") or [] tags_sql = ( - "'{}'::text[]" - if not tags - else "array[" - + ", ".join(ap.sql_literal(tag) for tag in tags) - + "]::text[]" + "'{}'::text[]" if not tags else "array[" + ", ".join(ap.sql_literal(tag) for tag in tags) + "]::text[]" ) statements.append( f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by) values - ({ap.sql_literal(row['id'])}::uuid, {ap.sql_literal(row['type'])}, - {ap.sql_literal(row['text'])}, {ap.sql_literal(row['status'])}, - {ap.sql_literal(row.get('confidence'))}, {tags_sql}, - {ap.sql_literal(row.get('created_by'))}::uuid, - {ap.sql_literal(row.get('superseded_by'))}::uuid);""" + ({ap.sql_literal(row["id"])}::uuid, {ap.sql_literal(row["type"])}, + {ap.sql_literal(row["text"])}, {ap.sql_literal(row["status"])}, + {ap.sql_literal(row.get("confidence"))}, {tags_sql}, + {ap.sql_literal(row.get("created_by"))}::uuid, + {ap.sql_literal(row.get("superseded_by"))}::uuid);""" ) _psql(container, clone_db, "\n".join(statements), tuples=False) clone_raw = _psql( @@ -712,9 +677,7 @@ from public.claims where id in ({id_sql});""", def _id_predicate(column: str, values: list[str]) -> str: if not values: return "false" - return f"{column} in (" + ", ".join( - f"{ap.sql_literal(value)}::uuid" for value in values - ) + ")" + return f"{column} in (" + ", ".join(f"{ap.sql_literal(value)}::uuid" for value in values) + ")" def _key_predicate(rows: list[dict[str, Any]], fields: list[tuple[str, str]]) -> str: @@ -829,8 +792,7 @@ def _expected_table_deltas( def _table_deltas(before: dict[str, int], after: dict[str, int]) -> dict[str, int]: if set(before) != set(after): raise ValueError( - "cannot compare table counts with different keys: " - f"before={sorted(before)} after={sorted(after)}" + f"cannot compare table counts with different keys: before={sorted(before)} after={sorted(after)}" ) return {key: after[key] - before[key] for key in sorted(before)} @@ -862,9 +824,7 @@ def _canonical_row_keys(rows: dict[str, list[dict[str, Any]]]) -> dict[str, Any] ], key=lambda row: (row["from_claim"], row["to_claim"], row["edge_type"]), ), - "public.reasoning_tools": sorted( - row["id"] for row in rows.get("reasoning_tools", []) - ), + "public.reasoning_tools": sorted(row["id"] for row in rows.get("reasoning_tools", [])), } @@ -879,8 +839,7 @@ def _strict_receipt( expected_keys = _canonical_row_keys(expected_rows) actual_keys = _canonical_row_keys(actual_rows) checks = { - "proposal_id_exact": applied.get("id") - == result.get("fixture_ids", {}).get("proposal_id"), + "proposal_id_exact": applied.get("id") == result.get("fixture_ids", {}).get("proposal_id"), "proposal_status_applied": applied.get("status") == "applied", "proposal_applied_at_present": bool(applied.get("applied_at")), "canonical_rows_exact": actual_rows == expected_rows, @@ -889,18 +848,12 @@ def _strict_receipt( isinstance(result.get("clone_counts_before_apply"), dict) and isinstance(result.get("clone_counts_after_apply"), dict) ), - "table_deltas_exact": result.get("clone_apply_table_deltas") - == expected_table_deltas, - "conflict_transaction_rolled_back": result.get("checks", {}).get( - "conflict_transaction_rolled_back_exactly" - ) + "table_deltas_exact": result.get("clone_apply_table_deltas") == expected_table_deltas, + "conflict_transaction_rolled_back": result.get("checks", {}).get("conflict_transaction_rolled_back_exactly") is True, "clone_database_removed": ( result.get("clone_dropped") is True - and result.get("cleanup_readback", {}).get( - "leftover_clone_database_count" - ) - == 0 + and result.get("cleanup_readback", {}).get("leftover_clone_database_count") == 0 ), "source_database_unchanged": result.get("source_database_mutated") is False, } @@ -925,28 +878,18 @@ def _strict_receipt( "actual_deltas": result.get("clone_apply_table_deltas"), }, "rollback_cleanup": { - "conflict_transaction_rolled_back": checks[ - "conflict_transaction_rolled_back" - ], + "conflict_transaction_rolled_back": checks["conflict_transaction_rolled_back"], "clone_database_removed": checks["clone_database_removed"], - "leftover_clone_database_count": result.get("cleanup_readback", {}).get( - "leftover_clone_database_count" - ), + "leftover_clone_database_count": result.get("cleanup_readback", {}).get("leftover_clone_database_count"), "source_database_unchanged": checks["source_database_unchanged"], }, } -def _exact_bundle_readback( - container: str, database: str, payload: dict[str, Any] -) -> dict[str, list[dict[str, Any]]]: +def _exact_bundle_readback(container: str, database: str, payload: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: expected = _expected_bundle_rows(payload) - claim_predicate = _id_predicate( - "id", [row["id"] for row in expected["claims"]] - ) - source_predicate = _id_predicate( - "id", [row["id"] for row in expected["sources"]] - ) + claim_predicate = _id_predicate("id", [row["id"] for row in expected["claims"]]) + source_predicate = _id_predicate("id", [row["id"] for row in expected["sources"]]) evidence_predicate = _key_predicate( expected["claim_evidence"], [("claim_id", "uuid"), ("source_id", "uuid"), ("role", "text")], @@ -959,9 +902,7 @@ def _exact_bundle_readback( ("edge_type", "text"), ], ) - tool_predicate = _id_predicate( - "id", [row["id"] for row in expected["reasoning_tools"]] - ) + tool_predicate = _id_predicate("id", [row["id"] for row in expected["reasoning_tools"]]) raw = _psql( container, database, @@ -1001,9 +942,53 @@ rollback; return json.loads(raw) -def _proposal_readback( - container: str, database: str, proposal_id: str -) -> dict[str, Any] | None: +def _unrelated_state_fingerprint( + container: str, + database: str, + payload: dict[str, Any], + proposal_id: str, +) -> dict[str, Any]: + """Hash every potentially adjacent row while excluding proposal-owned rows.""" + expected = _expected_bundle_rows(payload) + claim_predicate = _id_predicate("id", [row["id"] for row in expected["claims"]]) + source_predicate = _id_predicate("id", [row["id"] for row in expected["sources"]]) + evidence_predicate = _key_predicate( + expected["claim_evidence"], + [("claim_id", "uuid"), ("source_id", "uuid"), ("role", "text")], + ) + edge_predicate = _key_predicate( + expected["claim_edges"], + [("from_claim", "uuid"), ("to_claim", "uuid"), ("edge_type", "text")], + ) + tool_predicate = _id_predicate("id", [row["id"] for row in expected["reasoning_tools"]]) + + def fingerprint(table: str, alias: str, where: str, order: str) -> str: + return f"""(select json_build_object( + 'row_count', count(*), + 'content_md5', md5(coalesce(string_agg(to_jsonb({alias})::text, '|' order by {order}), '')) + ) from {table} {alias} where {where})""" + + raw = _psql( + container, + database, + f"""begin transaction read only; +select json_build_object( + 'public.agents', {fingerprint("public.agents", "a", "true", "a.id::text")}, + 'public.claims', {fingerprint("public.claims", "c", f"not ({claim_predicate})", "c.id::text")}, + 'public.sources', {fingerprint("public.sources", "s", f"not ({source_predicate})", "s.id::text")}, + 'public.claim_evidence', {fingerprint("public.claim_evidence", "ce", f"not ({evidence_predicate})", "ce.id::text")}, + 'public.claim_edges', {fingerprint("public.claim_edges", "e", f"not ({edge_predicate})", "e.id::text")}, + 'public.reasoning_tools', {fingerprint("public.reasoning_tools", "rt", f"not ({tool_predicate})", "rt.id::text")}, + 'kb_stage.kb_proposals', {fingerprint("kb_stage.kb_proposals", "p", f"p.id <> {ap.sql_literal(proposal_id)}::uuid", "p.id::text")}, + 'kb_stage.kb_proposal_approvals', {fingerprint("kb_stage.kb_proposal_approvals", "pa", f"pa.proposal_id <> {ap.sql_literal(proposal_id)}::uuid", "pa.proposal_id::text")} +)::text; +rollback; +""", + ) + return json.loads(raw) + + +def _proposal_readback(container: str, database: str, proposal_id: str) -> dict[str, Any] | None: raw = _psql( container, database, @@ -1031,9 +1016,7 @@ rollback; return json.loads(raw) -def _approval_snapshot_readback( - container: str, database: str, proposal_id: str -) -> dict[str, Any] | None: +def _approval_snapshot_readback(container: str, database: str, proposal_id: str) -> dict[str, Any] | None: raw = _psql( container, database, @@ -1069,7 +1052,7 @@ values ({ap.sql_literal(proposal_id)}::uuid, 'approve_claim', 'pending_review', 'clone_canary', {ap.sql_literal(source_ref)}, 'Exercise strict canonical bundle review and apply lifecycle.', - {ap.sql_literal(json.dumps({'apply_payload': payload}, sort_keys=True))}::jsonb); + {ap.sql_literal(json.dumps({"apply_payload": payload}, sort_keys=True))}::jsonb); """ _psql(container, database, sql) @@ -1111,6 +1094,7 @@ def _apply( database: str, *, dry_run: bool = False, + receipt_out: Path | None = None, ) -> subprocess.CompletedProcess[str]: command = [ sys.executable, @@ -1129,15 +1113,37 @@ def _apply( "--role", args.apply_role, ] + if receipt_out is not None: + command.extend(["--receipt-out", str(receipt_out)]) if dry_run: command.append("--dry-run") return _run(command, check=False) +def _apply_command_readback( + result: subprocess.CompletedProcess[str], +) -> dict[str, Any]: + """Keep the public report useful without leaking a private receipt path.""" + readback: dict[str, Any] = { + "returncode": result.returncode, + "stderr": result.stderr.strip(), + } + try: + summary = json.loads(result.stdout) + except json.JSONDecodeError: + readback["stdout"] = result.stdout.strip() + else: + if isinstance(summary, dict): + summary.pop("receipt_path", None) + readback["result"] = summary + else: + readback["stdout"] = result.stdout.strip() + return readback + + def _security_definer_readback(container: str, database: str) -> dict[str, Any]: expected_values = ",\n".join( - f"({ap.sql_literal(name)}, {ap.sql_literal(role)}, " - f"{ap.sql_literal(SECURITY_DEFINER_ARGUMENT_TYPES[name])})" + f"({ap.sql_literal(name)}, {ap.sql_literal(role)}, {ap.sql_literal(SECURITY_DEFINER_ARGUMENT_TYPES[name])})" for name, role in SECURITY_DEFINER_DEPENDENCIES.items() ) raw = _psql( @@ -1270,8 +1276,7 @@ def _security_dependencies_ready(readback: dict[str, Any]) -> bool: functions = readback.get("functions") or [] functions_ready = len(functions) == len(SECURITY_DEFINER_DEPENDENCIES) and all( row.get("present") is True - and row.get("argument_types") - == SECURITY_DEFINER_ARGUMENT_TYPES[row["function"].removeprefix("kb_stage.")] + and row.get("argument_types") == SECURITY_DEFINER_ARGUMENT_TYPES[row["function"].removeprefix("kb_stage.")] and row.get("security_definer") is True and row.get("expected_role_execute") is True and row.get("other_runtime_role_execute") is False @@ -1285,23 +1290,26 @@ def _security_dependencies_ready(readback: dict[str, Any]) -> bool: and readback.get("protected_role_memberships") == [] and readback.get("login_role_owned_objects") == [] ) - return functions_ready and catalog_ready and privileges == { - "kb_review_proposal_select": True, - "kb_review_proposal_update": False, - "kb_apply_proposal_select": True, - "kb_apply_proposal_update": False, - "kb_apply_claim_evidence_insert": True, - "kb_apply_claim_evidence_update": False, - "kb_review_approval_snapshot_select": False, - "kb_review_approval_snapshot_update": False, - "kb_apply_approval_snapshot_select": False, - "kb_apply_approval_snapshot_update": False, - } + return ( + functions_ready + and catalog_ready + and privileges + == { + "kb_review_proposal_select": True, + "kb_review_proposal_update": False, + "kb_apply_proposal_select": True, + "kb_apply_proposal_update": False, + "kb_apply_claim_evidence_insert": True, + "kb_apply_claim_evidence_update": False, + "kb_review_approval_snapshot_select": False, + "kb_review_approval_snapshot_update": False, + "kb_apply_approval_snapshot_select": False, + "kb_apply_approval_snapshot_update": False, + } + ) -def _approval_transition_valid( - pending: dict[str, Any] | None, approved: dict[str, Any] | None -) -> bool: +def _approval_transition_valid(pending: dict[str, Any] | None, approved: dict[str, Any] | None) -> bool: if not pending or not approved: return False immutable = [ @@ -1337,9 +1345,7 @@ def _approval_transition_valid( ) -def _apply_transition_valid( - approved: dict[str, Any] | None, applied: dict[str, Any] | None -) -> bool: +def _apply_transition_valid(approved: dict[str, Any] | None, applied: dict[str, Any] | None) -> bool: if not approved or not applied: return False preserved = [ @@ -1382,9 +1388,7 @@ def _approval_snapshot_matches( } -def _seed_evidence_permission_probe( - container: str, database: str, fixture: dict[str, Any] -) -> None: +def _seed_evidence_permission_probe(container: str, database: str, fixture: dict[str, Any]) -> None: source_hash = f"approve-claim-permission-probe-{fixture['permission_source_id']}" _psql( container, @@ -1392,20 +1396,20 @@ def _seed_evidence_permission_probe( f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by) values - ({ap.sql_literal(fixture['permission_claim_id'])}::uuid, 'structural', + ({ap.sql_literal(fixture["permission_claim_id"])}::uuid, 'structural', 'Existing claim_evidence permission probe.', 'open', 0.42, array['clone-canary-permission-probe']::text[], null, null); insert into public.sources (id, source_type, url, storage_path, excerpt, hash, created_by) values - ({ap.sql_literal(fixture['permission_source_id'])}::uuid, 'observation', null, + ({ap.sql_literal(fixture["permission_source_id"])}::uuid, 'observation', null, null, 'Existing claim_evidence permission probe source.', {ap.sql_literal(source_hash)}, null); insert into public.claim_evidence (claim_id, source_id, role, weight, created_by) values - ({ap.sql_literal(fixture['permission_claim_id'])}::uuid, - {ap.sql_literal(fixture['permission_source_id'])}::uuid, + ({ap.sql_literal(fixture["permission_claim_id"])}::uuid, + {ap.sql_literal(fixture["permission_source_id"])}::uuid, 'grounds', 0.42, null); """, tuples=False, @@ -1426,8 +1430,8 @@ select coalesce((select json_build_object( 'weight', weight, 'created_by', created_by::text ) from public.claim_evidence -where claim_id = {ap.sql_literal(fixture['permission_claim_id'])}::uuid - and source_id = {ap.sql_literal(fixture['permission_source_id'])}::uuid +where claim_id = {ap.sql_literal(fixture["permission_claim_id"])}::uuid + and source_id = {ap.sql_literal(fixture["permission_source_id"])}::uuid and role = 'grounds'), 'null'::json)::text; rollback; """, @@ -1482,10 +1486,7 @@ def _conflict_payload(fixture: dict[str, Any]) -> dict[str, Any]: "url": None, "storage_path": None, "excerpt": "Conflicting source id.", - "hash": ( - f"approve-claim-permission-probe-" - f"{fixture['permission_source_id']}" - ), + "hash": (f"approve-claim-permission-probe-{fixture['permission_source_id']}"), "created_by": None, } ], @@ -1497,18 +1498,14 @@ def _conflict_payload(fixture: dict[str, Any]) -> dict[str, Any]: def run_canary(args: argparse.Namespace) -> dict[str, Any]: if args.review_role != "kb_review" or args.apply_role != "kb_apply": - raise ValueError( - "the canary is pinned to the intended kb_review/kb_apply authority split" - ) + raise ValueError("the canary is pinned to the intended kb_review/kb_apply authority split") suffix = uuid.uuid4().hex[:10] clone_db = f"{args.database_prefix}_{suffix}" if not SAFE_DB_RE.match(clone_db): raise ValueError(f"unsafe generated database name: {clone_db}") fixture = ( - load_normalized_fixture(args.normalization_json, suffix) - if args.normalization_json - else build_fixture(suffix) + load_normalized_fixture(args.normalization_json, suffix) if args.normalization_json else build_fixture(suffix) ) payload = fixture["apply_payload"] expected_rows = _expected_bundle_rows(payload) @@ -1526,33 +1523,19 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "service_restarted": False, "clone_created": False, "clone_dropped": False, - "fixture_ids": { - key: value for key, value in fixture.items() if key != "apply_payload" - }, + "fixture_ids": {key: value for key, value in fixture.items() if key != "apply_payload"}, "dependencies": { - "approve_script": Path(args.approve_script) - .resolve() - .relative_to(REPO_ROOT.resolve()) - .as_posix(), - "apply_script": Path(args.apply_script) - .resolve() - .relative_to(REPO_ROOT.resolve()) - .as_posix(), - "prerequisites_sql": Path(args.prereqs) - .resolve() - .relative_to(REPO_ROOT.resolve()) - .as_posix(), + "approve_script": Path(args.approve_script).resolve().relative_to(REPO_ROOT.resolve()).as_posix(), + "apply_script": Path(args.apply_script).resolve().relative_to(REPO_ROOT.resolve()).as_posix(), + "prerequisites_sql": Path(args.prereqs).resolve().relative_to(REPO_ROOT.resolve()).as_posix(), "review_role": args.review_role, "apply_role": args.apply_role, "gate_owner_role": "kb_gate_owner", "reviewer_handle": REVIEWER_HANDLE, "immutable_approval_table": "kb_stage.kb_proposal_approvals", - "security_definer_functions": [ - f"kb_stage.{name}" for name in SECURITY_DEFINER_DEPENDENCIES - ], + "security_definer_functions": [f"kb_stage.{name}" for name in SECURITY_DEFINER_DEPENDENCIES], "security_definer_argument_types": { - f"kb_stage.{name}": argument_types - for name, argument_types in SECURITY_DEFINER_ARGUMENT_TYPES.items() + f"kb_stage.{name}": argument_types for name, argument_types in SECURITY_DEFINER_ARGUMENT_TYPES.items() }, "credential_files": { "review": { @@ -1579,15 +1562,9 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: } try: result["service_before"] = _service_state(args.service) - result["source_counts_before"] = _base_counts( - args.container, args.source_db - ) - result["source_cluster_roles_before"] = _cluster_role_readback( - args.container, args.source_db - ) - if not _cluster_roles_are_preprovisioned( - result["source_cluster_roles_before"] - ): + result["source_counts_before"] = _base_counts(args.container, args.source_db) + result["source_cluster_roles_before"] = _cluster_role_readback(args.container, args.source_db) + if not _cluster_roles_are_preprovisioned(result["source_cluster_roles_before"]): raise RuntimeError( "refusing clone prerequisites until kb_apply, kb_review, and " "NOLOGIN kb_gate_owner are pre-provisioned without elevated attributes" @@ -1598,13 +1575,8 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: if not readback["present_at_start"] ] if missing_credentials: - raise RuntimeError( - "required separated credential file(s) are absent: " - + ", ".join(missing_credentials) - ) - result["source_target_rows_before"] = _exact_bundle_readback( - args.container, args.source_db, payload - ) + raise RuntimeError("required separated credential file(s) are absent: " + ", ".join(missing_credentials)) + result["source_target_rows_before"] = _exact_bundle_readback(args.container, args.source_db, payload) prereqs = Path(args.prereqs).read_text(encoding="utf-8") clone_prereqs, prereq_sanitization = _clone_prerequisites_sql(prereqs) result["clone_prerequisite_sanitization"] = prereq_sanitization @@ -1620,19 +1592,11 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: ) _psql(args.container, clone_db, clone_prereqs, tuples=False) - result["security_definer_readback"] = _security_definer_readback( - args.container, clone_db - ) - result["payload_agent_seed"] = _seed_payload_agents( - args.container, args.source_db, clone_db, payload - ) - result["external_claim_seed"] = _seed_external_claims( - args.container, args.source_db, clone_db, payload - ) + result["security_definer_readback"] = _security_definer_readback(args.container, clone_db) + result["payload_agent_seed"] = _seed_payload_agents(args.container, args.source_db, clone_db, payload) + result["external_claim_seed"] = _seed_external_claims(args.container, args.source_db, clone_db, payload) _seed_evidence_permission_probe(args.container, clone_db, fixture) - evidence_before = _evidence_permission_probe_readback( - args.container, clone_db, fixture - ) + evidence_before = _evidence_permission_probe_readback(args.container, clone_db, fixture) apply_password = ap.load_password(args.secrets_file) evidence_update = _permission_probe( @@ -1640,15 +1604,13 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update public.claim_evidence set weight = 0.99 - where claim_id = {ap.sql_literal(fixture['permission_claim_id'])}::uuid - and source_id = {ap.sql_literal(fixture['permission_source_id'])}::uuid + where claim_id = {ap.sql_literal(fixture["permission_claim_id"])}::uuid + and source_id = {ap.sql_literal(fixture["permission_source_id"])}::uuid and role = 'grounds';""", role=args.apply_role, password=apply_password, ) - evidence_after = _evidence_permission_probe_readback( - args.container, clone_db, fixture - ) + evidence_after = _evidence_permission_probe_readback(args.container, clone_db, fixture) result["kb_apply_claim_evidence_update_probe"] = { "before": evidence_before, "attempt": evidence_update, @@ -1663,41 +1625,25 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: payload, source_ref="working-leo:approve-claim-clone-canary", ) - pending_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - pending_approval_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) - review_dry_run = _approve( - args, fixture["proposal_id"], clone_db, dry_run=True - ) + pending_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + pending_approval_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) + review_dry_run = _approve(args, fixture["proposal_id"], clone_db, dry_run=True) review_apply = _approve(args, fixture["proposal_id"], clone_db) - approved_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - approved_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + approved_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + approved_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["review_transition"] = { "pending_readback": pending_proposal, "pending_immutable_approval": pending_approval_snapshot, "review_dry_run": { "returncode": review_dry_run.returncode, - "uses_approve_strict_proposal": ( - "kb_stage.approve_strict_proposal" in review_dry_run.stdout - ), + "uses_approve_strict_proposal": ("kb_stage.approve_strict_proposal" in review_dry_run.stdout), "stderr": review_dry_run.stderr.strip(), }, "review_apply": _command_readback(review_apply), "approved_readback": approved_proposal, "immutable_approval_readback": approved_snapshot, - "immutable_approval_matches_ledger": _approval_snapshot_matches( - approved_proposal, approved_snapshot - ), - "exact_transition": _approval_transition_valid( - pending_proposal, approved_proposal - ), + "immutable_approval_matches_ledger": _approval_snapshot_matches(approved_proposal, approved_snapshot), + "exact_transition": _approval_transition_valid(pending_proposal, approved_proposal), } approval_update = _permission_probe( @@ -1705,7 +1651,7 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update kb_stage.kb_proposals set status = 'pending_review', review_note = 'kb_apply direct mutation probe' - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", role=args.apply_role, password=apply_password, ) @@ -1714,7 +1660,7 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update kb_stage.kb_proposals set payload = payload || '{{"kb_apply_direct_mutation_probe": true}}'::jsonb - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", role=args.apply_role, password=apply_password, ) @@ -1723,16 +1669,12 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update kb_stage.kb_proposal_approvals set payload = payload || '{{"kb_apply_direct_mutation_probe": true}}'::jsonb - where proposal_id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where proposal_id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", role=args.apply_role, password=apply_password, ) - proposal_after_permission_probes = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - snapshot_after_permission_probes = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + proposal_after_permission_probes = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + snapshot_after_permission_probes = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["kb_apply_proposal_mutation_probes"] = { "approval_fields": approval_update, "payload": payload_update, @@ -1741,36 +1683,25 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "after": proposal_after_permission_probes, "immutable_approval_before": approved_snapshot, "immutable_approval_after": snapshot_after_permission_probes, - "exact_row_unchanged": ( - approved_proposal == proposal_after_permission_probes - ), - "immutable_approval_exactly_unchanged": ( - approved_snapshot == snapshot_after_permission_probes - ), + "exact_row_unchanged": (approved_proposal == proposal_after_permission_probes), + "immutable_approval_exactly_unchanged": (approved_snapshot == snapshot_after_permission_probes), } built_apply = _apply(args, fixture["proposal_id"], clone_db, dry_run=True) built_sql = built_apply.stdout if built_apply.returncode != 0 or not built_sql.strip(): - raise RuntimeError( - "failed to build apply SQL before mutation: " - f"{built_apply.stderr.strip()}" - ) + raise RuntimeError(f"failed to build apply SQL before mutation: {built_apply.stderr.strip()}") _psql( args.container, clone_db, f"""update kb_stage.kb_proposals set payload = payload || '{{"mutation_after_build": true}}'::jsonb, updated_at = now() - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", tuples=False, ) - proposal_after_mutation = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - snapshot_after_ledger_mutation = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + proposal_after_mutation = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + snapshot_after_ledger_mutation = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) stale_execute = _role_psql( args.container, clone_db, @@ -1778,58 +1709,38 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: role=args.apply_role, password=apply_password, ) - rows_after_stale_execute = _exact_bundle_readback( - args.container, clone_db, payload - ) - proposal_after_stale_execute = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - snapshot_after_stale_execute = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + rows_after_stale_execute = _exact_bundle_readback(args.container, clone_db, payload) + proposal_after_stale_execute = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + snapshot_after_stale_execute = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) assert approved_proposal is not None _psql( args.container, clone_db, f"""update kb_stage.kb_proposals - set payload = {ap.sql_literal(json.dumps(approved_proposal['payload'], sort_keys=True))}::jsonb, + set payload = {ap.sql_literal(json.dumps(approved_proposal["payload"], sort_keys=True))}::jsonb, updated_at = now() - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", tuples=False, ) - restored_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - restored_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + restored_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + restored_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["mutation_between_build_and_execute"] = { "build_returncode": built_apply.returncode, "built_sql_sha256": hashlib.sha256(built_sql.encode()).hexdigest(), - "built_sql_uses_assert_approved_proposal": ( - "kb_stage.assert_approved_proposal" in built_sql - ), - "built_sql_uses_finish_approved_proposal": ( - "kb_stage.finish_approved_proposal" in built_sql - ), + "built_sql_uses_assert_approved_proposal": ("kb_stage.assert_approved_proposal" in built_sql), + "built_sql_uses_finish_approved_proposal": ("kb_stage.finish_approved_proposal" in built_sql), "approved_before_mutation": approved_proposal, "immutable_approval_before_mutation": approved_snapshot, "mutated_readback": proposal_after_mutation, - "immutable_approval_after_ledger_mutation": ( - snapshot_after_ledger_mutation - ), + "immutable_approval_after_ledger_mutation": (snapshot_after_ledger_mutation), "stale_execute": _command_readback(stale_execute), "rows_after_stale_execute": rows_after_stale_execute, "proposal_after_stale_execute": proposal_after_stale_execute, - "immutable_approval_after_stale_execute": ( - snapshot_after_stale_execute - ), + "immutable_approval_after_stale_execute": (snapshot_after_stale_execute), "restored_readback": restored_proposal, "restored_immutable_approval": restored_snapshot, "stale_apply_refused": stale_execute.returncode != 0, - "canonical_rows_unchanged": ( - rows_after_stale_execute == _empty_bundle_readback(payload) - ), + "canonical_rows_unchanged": (rows_after_stale_execute == _empty_bundle_readback(payload)), "approval_restored_exactly": restored_proposal == approved_proposal, "immutable_approval_never_changed": all( snapshot == approved_snapshot @@ -1841,27 +1752,41 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: ), } - result["clone_counts_before_apply"] = _base_counts( - args.container, clone_db + if approved_proposal is None or approved_snapshot is None: + raise RuntimeError("approved ledger and immutable approval are required") + result["target_rows_before_apply"] = _exact_bundle_readback(args.container, clone_db, payload) + fresh_preflight = lifecycle.build_fresh_preflight( + approved_proposal, + result["target_rows_before_apply"], ) - first_apply = _apply(args, fixture["proposal_id"], clone_db) - result["first_apply"] = _command_readback(first_apply) - result["row_readback"] = _exact_bundle_readback( - args.container, clone_db, payload - ) - result["clone_counts_after_apply"] = _base_counts( - args.container, clone_db + result["fresh_owned_preflight"] = fresh_preflight + result["unrelated_fingerprint_before_apply"] = _unrelated_state_fingerprint( + args.container, clone_db, payload, fixture["proposal_id"] ) + result["clone_counts_before_apply"] = _base_counts(args.container, clone_db) + with tempfile.TemporaryDirectory(prefix="leo-proposal-apply-receipt-") as receipt_dir: + private_receipt_path = Path(receipt_dir) / "apply-receipt.json" + first_apply = _apply( + args, + fixture["proposal_id"], + clone_db, + receipt_out=private_receipt_path, + ) + result["first_apply"] = _apply_command_readback(first_apply) + if first_apply.returncode != 0 or not private_receipt_path.is_file(): + raise RuntimeError( + "first apply did not produce its explicit private receipt: " + f"returncode={first_apply.returncode} stderr={first_apply.stderr.strip()}" + ) + apply_receipt = json.loads(private_receipt_path.read_text(encoding="utf-8")) + result["row_readback"] = _exact_bundle_readback(args.container, clone_db, payload) + result["clone_counts_after_apply"] = _base_counts(args.container, clone_db) result["clone_apply_table_deltas"] = _table_deltas( result["clone_counts_before_apply"], result["clone_counts_after_apply"], ) - applied_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - applied_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + applied_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + applied_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["apply_transition"] = { "approved_readback": approved_proposal, "applied_readback": applied_proposal, @@ -1869,20 +1794,93 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "immutable_approval_matches_applied_ledger": ( _approval_snapshot_matches(applied_proposal, applied_snapshot) ), - "exact_transition": _apply_transition_valid( - approved_proposal, applied_proposal - ), + "exact_transition": _apply_transition_valid(approved_proposal, applied_proposal), } replay = _apply(args, fixture["proposal_id"], clone_db) result["idempotent_replay"] = { "returncode": replay.returncode, - "already_applied_refusal": ( - "already applied" in (replay.stdout + replay.stderr).lower() - ), + "already_applied_refusal": ("already applied" in (replay.stdout + replay.stderr).lower()), "stderr": replay.stderr.strip(), } + rollback_sql = lifecycle.build_compensating_rollback_sql( + approved_proposal, + approved_snapshot, + fresh_preflight, + apply_receipt, + ) + result["rollback_plan"] = { + "schema": lifecycle.ROLLBACK_SCHEMA, + "rollback_sql_sha256": lifecycle.sha256_text(rollback_sql), + "execution_authority": "disposable_clone_admin_only", + } + _psql(args.container, clone_db, rollback_sql, tuples=False) + rollback_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + rollback_approval = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) + rollback_target_rows = _exact_bundle_readback(args.container, clone_db, payload) + rollback_counts = _base_counts(args.container, clone_db) + rollback_unrelated = _unrelated_state_fingerprint(args.container, clone_db, payload, fixture["proposal_id"]) + + rollback_replay = _run( + [ + "docker", + "exec", + "-i", + args.container, + "psql", + "-U", + "postgres", + "-d", + clone_db, + "-v", + "ON_ERROR_STOP=1", + ], + input_text=rollback_sql, + check=False, + ) + rollback_replay_state = { + "proposal": _proposal_readback(args.container, clone_db, fixture["proposal_id"]), + "approval": _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]), + "target_rows": _exact_bundle_readback(args.container, clone_db, payload), + "counts": _base_counts(args.container, clone_db), + "unrelated": _unrelated_state_fingerprint(args.container, clone_db, payload, fixture["proposal_id"]), + } + rollback_replay_state_unchanged = rollback_replay_state == { + "proposal": rollback_proposal, + "approval": rollback_approval, + "target_rows": rollback_target_rows, + "counts": rollback_counts, + "unrelated": rollback_unrelated, + } + result["rollback_replay"] = { + "returncode": rollback_replay.returncode, + "refused": rollback_replay.returncode != 0, + "state_unchanged": rollback_replay_state_unchanged, + "stderr": rollback_replay.stderr.strip(), + } + lifecycle_receipt = lifecycle.build_lifecycle_receipt( + proposal_before=approved_proposal, + approval_before=approved_snapshot, + preflight=fresh_preflight, + apply_receipt=apply_receipt, + rollback_sql=rollback_sql, + proposal_after=rollback_proposal or {}, + approval_after=rollback_approval or {}, + target_rows_after=rollback_target_rows, + counts_before=result["clone_counts_before_apply"], + counts_after=rollback_counts, + unrelated_before=result["unrelated_fingerprint_before_apply"], + unrelated_after=rollback_unrelated, + rollback_replay_refused=(rollback_replay.returncode != 0 and rollback_replay_state_unchanged), + ) + result["applied_rollback_receipt"] = lifecycle_receipt + if args.lifecycle_receipt_output: + lifecycle._write_json(args.lifecycle_receipt_output, lifecycle_receipt) + if args.rollback_sql_output: + args.rollback_sql_output.parent.mkdir(parents=True, exist_ok=True) + args.rollback_sql_output.write_text(rollback_sql, encoding="utf-8") + _stage_proposal( args.container, clone_db, @@ -1890,29 +1888,19 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: conflict_payload, source_ref="working-leo:approve-claim-conflict-canary", ) - conflict_pending = _proposal_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) + conflict_pending = _proposal_readback(args.container, clone_db, fixture["conflict_proposal_id"]) conflict_pending_snapshot = _approval_snapshot_readback( args.container, clone_db, fixture["conflict_proposal_id"] ) conflict_review = _approve(args, fixture["conflict_proposal_id"], clone_db) - conflict_approved = _proposal_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) + conflict_approved = _proposal_readback(args.container, clone_db, fixture["conflict_proposal_id"]) conflict_approved_snapshot = _approval_snapshot_readback( args.container, clone_db, fixture["conflict_proposal_id"] ) conflict = _apply(args, fixture["conflict_proposal_id"], clone_db) - conflict_after = _proposal_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) - conflict_snapshot_after = _approval_snapshot_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) - conflict_rows = _exact_bundle_readback( - args.container, clone_db, conflict_payload - ) + conflict_after = _proposal_readback(args.container, clone_db, fixture["conflict_proposal_id"]) + conflict_snapshot_after = _approval_snapshot_readback(args.container, clone_db, fixture["conflict_proposal_id"]) + conflict_rows = _exact_bundle_readback(args.container, clone_db, conflict_payload) result["conflict_review_transition"] = { "pending_readback": conflict_pending, "pending_immutable_approval": conflict_pending_snapshot, @@ -1922,15 +1910,11 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "immutable_approval_matches_ledger": _approval_snapshot_matches( conflict_approved, conflict_approved_snapshot ), - "exact_transition": _approval_transition_valid( - conflict_pending, conflict_approved - ), + "exact_transition": _approval_transition_valid(conflict_pending, conflict_approved), } result["conflict_apply"] = { "returncode": conflict.returncode, - "source_hash_collision_refusal": ( - "source hash collision" in (conflict.stdout + conflict.stderr).lower() - ), + "source_hash_collision_refusal": ("source hash collision" in (conflict.stdout + conflict.stderr).lower()), "stderr": conflict.stderr.strip(), } result["conflict_readback"] = { @@ -1938,19 +1922,13 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "immutable_approval": conflict_snapshot_after, "canonical_rows": conflict_rows, "proposal_approval_unchanged": conflict_after == conflict_approved, - "immutable_approval_unchanged": ( - conflict_snapshot_after == conflict_approved_snapshot - ), - "canonical_rows_absent": ( - conflict_rows == _empty_bundle_readback(conflict_payload) - ), + "immutable_approval_unchanged": (conflict_snapshot_after == conflict_approved_snapshot), + "canonical_rows_absent": (conflict_rows == _empty_bundle_readback(conflict_payload)), } except Exception as exc: result["error"] = f"{type(exc).__name__}: {exc}" finally: - cleanup_readback: dict[str, Any] = { - "drop_attempted": bool(result["clone_created"]) - } + cleanup_readback: dict[str, Any] = {"drop_attempted": bool(result["clone_created"])} if result["clone_created"]: cleanup_sql = f"""select pg_terminate_backend(pid) from pg_stat_activity @@ -1985,46 +1963,32 @@ drop database if exists {clone_db}; ) or "0" ) - cleanup_readback["leftover_clone_database_count"] = result[ - "leftover_clone_count" - ] + cleanup_readback["leftover_clone_database_count"] = result["leftover_clone_count"] result["cleanup_readback"] = cleanup_readback - result["source_counts_after"] = _base_counts( - args.container, args.source_db - ) + result["source_counts_after"] = _base_counts(args.container, args.source_db) if "source_counts_before" in result: result["source_table_deltas"] = _table_deltas( result["source_counts_before"], result["source_counts_after"], ) - result["source_cluster_roles_after"] = _cluster_role_readback( - args.container, args.source_db - ) - result["source_target_rows_after"] = _exact_bundle_readback( - args.container, args.source_db, payload - ) + result["source_cluster_roles_after"] = _cluster_role_readback(args.container, args.source_db) + result["source_target_rows_after"] = _exact_bundle_readback(args.container, args.source_db, payload) result["service_after"] = _service_state(args.service) try: source_files_after = _source_file_manifest(args) result["source_binding"]["post_run_files"] = source_files_after - result["source_binding"]["unchanged_during_run"] = ( - source_files_after == source_files_before - ) + result["source_binding"]["unchanged_during_run"] = source_files_after == source_files_before except Exception as exc: - result["source_binding"]["post_run_error"] = ( - f"{type(exc).__name__}: {exc}" - ) + result["source_binding"]["post_run_error"] = f"{type(exc).__name__}: {exc}" gateway_process_observable = ( result.get("service_before", {}).get("available") is True and result.get("service_after", {}).get("available") is True ) gateway_process_unchanged = ( - result.get("service_before", {}).get("MainPID") - == result.get("service_after", {}).get("MainPID") - and result.get("service_before", {}).get("NRestarts") - == result.get("service_after", {}).get("NRestarts") + result.get("service_before", {}).get("MainPID") == result.get("service_after", {}).get("MainPID") + and result.get("service_before", {}).get("NRestarts") == result.get("service_after", {}).get("NRestarts") if gateway_process_observable else None ) @@ -2033,75 +1997,39 @@ drop database if exists {clone_db}; result.get("security_definer_readback") or {} ), "clone_prerequisites_omit_cluster_role_ddl": ( - result.get("clone_prerequisite_sanitization", {}).get( - "cluster_role_ddl_removed" - ) - is True - ), - "reviewer_agent_seeded_exactly": ( - result.get("reviewer_agent_seed", {}).get("exact_clone_copy") is True - ), - "payload_agents_seeded_exactly": ( - result.get("payload_agent_seed", {}).get("exact_clone_copy") is True + result.get("clone_prerequisite_sanitization", {}).get("cluster_role_ddl_removed") is True ), + "reviewer_agent_seeded_exactly": (result.get("reviewer_agent_seed", {}).get("exact_clone_copy") is True), + "payload_agents_seeded_exactly": (result.get("payload_agent_seed", {}).get("exact_clone_copy") is True), "kb_review_approval_succeeded": ( - result.get("review_transition", {}) - .get("review_apply", {}) - .get("returncode") - == 0 + result.get("review_transition", {}).get("review_apply", {}).get("returncode") == 0 ), "kb_review_used_security_definer": ( - result.get("review_transition", {}) - .get("review_dry_run", {}) - .get("uses_approve_strict_proposal") - is True + result.get("review_transition", {}).get("review_dry_run", {}).get("uses_approve_strict_proposal") is True ), "review_transition_exact": ( result.get("review_transition", {}).get("exact_transition") is True - and result.get("review_transition", {}).get( - "immutable_approval_matches_ledger" - ) - is True + and result.get("review_transition", {}).get("immutable_approval_matches_ledger") is True ), "kb_apply_cannot_mutate_proposal_approval": ( - result.get("kb_apply_proposal_mutation_probes", {}) - .get("approval_fields", {}) - .get("refused") - is True + result.get("kb_apply_proposal_mutation_probes", {}).get("approval_fields", {}).get("refused") is True ), "kb_apply_cannot_mutate_proposal_payload": ( - result.get("kb_apply_proposal_mutation_probes", {}) - .get("payload", {}) - .get("refused") - is True + result.get("kb_apply_proposal_mutation_probes", {}).get("payload", {}).get("refused") is True ), "kb_apply_cannot_mutate_immutable_approval": ( - result.get("kb_apply_proposal_mutation_probes", {}) - .get("immutable_approval_payload", {}) - .get("refused") + result.get("kb_apply_proposal_mutation_probes", {}).get("immutable_approval_payload", {}).get("refused") is True ), "proposal_exact_after_permission_probes": ( - result.get("kb_apply_proposal_mutation_probes", {}).get( - "exact_row_unchanged" - ) - is True - and result.get("kb_apply_proposal_mutation_probes", {}).get( - "immutable_approval_exactly_unchanged" - ) - is True + result.get("kb_apply_proposal_mutation_probes", {}).get("exact_row_unchanged") is True + and result.get("kb_apply_proposal_mutation_probes", {}).get("immutable_approval_exactly_unchanged") is True ), "kb_apply_cannot_update_existing_claim_evidence": ( - result.get("kb_apply_claim_evidence_update_probe", {}) - .get("attempt", {}) - .get("refused") - is True + result.get("kb_apply_claim_evidence_update_probe", {}).get("attempt", {}).get("refused") is True ), "claim_evidence_exact_after_permission_probe": ( - result.get("kb_apply_claim_evidence_update_probe", {}).get( - "exact_row_unchanged" - ) - is True + result.get("kb_apply_claim_evidence_update_probe", {}).get("exact_row_unchanged") is True ), "mutation_between_build_and_execute_refused": all( result.get("mutation_between_build_and_execute", {}).get(key) is True @@ -2114,87 +2042,64 @@ drop database if exists {clone_db}; "immutable_approval_never_changed", ) ), - "first_apply_succeeded": result.get("first_apply", {}).get("returncode") - == 0, - "payload_projection_exact": result.get("row_readback") == expected_rows, - "clone_apply_table_deltas_exact": ( - result.get("clone_apply_table_deltas") == expected_table_deltas + "first_apply_succeeded": result.get("first_apply", {}).get("returncode") == 0, + "fresh_owned_preflight_exact": ( + result.get("fresh_owned_preflight", {}).get("fresh_owned_targets") is True + and result.get("target_rows_before_apply") == _empty_bundle_readback(payload) ), + "compensating_rollback_receipt_passes": ( + result.get("applied_rollback_receipt", {}).get("pass") is True + and result.get("applied_rollback_receipt", {}).get("current_tier") == "T2_runtime" + ), + "rollback_replay_refused_without_mutation": ( + result.get("rollback_replay", {}).get("refused") is True + and result.get("rollback_replay", {}).get("state_unchanged") is True + ), + "payload_projection_exact": result.get("row_readback") == expected_rows, + "clone_apply_table_deltas_exact": (result.get("clone_apply_table_deltas") == expected_table_deltas), "apply_transition_exact": ( result.get("apply_transition", {}).get("exact_transition") is True - and result.get("apply_transition", {}).get( - "immutable_approval_matches_applied_ledger" - ) - is True + and result.get("apply_transition", {}).get("immutable_approval_matches_applied_ledger") is True ), "idempotent_replay_refused": ( result.get("idempotent_replay", {}).get("returncode", 0) != 0 - and result.get("idempotent_replay", {}).get( - "already_applied_refusal" - ) - is True + and result.get("idempotent_replay", {}).get("already_applied_refusal") is True ), "conflict_review_separate_and_exact": ( - result.get("conflict_review_transition", {}).get("exact_transition") - is True - and result.get("conflict_review_transition", {}).get( - "immutable_approval_matches_ledger" - ) - is True + result.get("conflict_review_transition", {}).get("exact_transition") is True + and result.get("conflict_review_transition", {}).get("immutable_approval_matches_ledger") is True ), "source_hash_conflict_refused": ( result.get("conflict_apply", {}).get("returncode", 0) != 0 - and result.get("conflict_apply", {}).get( - "source_hash_collision_refusal" - ) - is True + and result.get("conflict_apply", {}).get("source_hash_collision_refusal") is True ), "conflict_transaction_rolled_back_exactly": ( - result.get("conflict_readback", {}).get( - "proposal_approval_unchanged" - ) - is True - and result.get("conflict_readback", {}).get( - "immutable_approval_unchanged" - ) - is True - and result.get("conflict_readback", {}).get("canonical_rows_absent") - is True + result.get("conflict_readback", {}).get("proposal_approval_unchanged") is True + and result.get("conflict_readback", {}).get("immutable_approval_unchanged") is True + and result.get("conflict_readback", {}).get("canonical_rows_absent") is True ), "source_target_rows_unchanged": ( - result.get("source_target_rows_before") - == result.get("source_target_rows_after") + result.get("source_target_rows_before") == result.get("source_target_rows_after") ), "source_cluster_roles_preexisting": ( - _cluster_roles_are_preprovisioned( - result.get("source_cluster_roles_before") or [] - ) + _cluster_roles_are_preprovisioned(result.get("source_cluster_roles_before") or []) ), "source_cluster_roles_unchanged": ( - result.get("source_cluster_roles_before") - == result.get("source_cluster_roles_after") + result.get("source_cluster_roles_before") == result.get("source_cluster_roles_after") ), "source_table_deltas_exactly_zero": ( bool(result.get("source_table_deltas")) and all(delta == 0 for delta in result["source_table_deltas"].values()) ), - "source_files_unchanged_during_run": ( - result.get("source_binding", {}).get("unchanged_during_run") is True - ), + "source_files_unchanged_during_run": (result.get("source_binding", {}).get("unchanged_during_run") is True), "gateway_process_unchanged": gateway_process_unchanged, - "clone_cleanup_complete": ( - result.get("clone_dropped") is True - and result.get("leftover_clone_count") == 0 - ), + "clone_cleanup_complete": (result.get("clone_dropped") is True and result.get("leftover_clone_count") == 0), } - result["source_counts_observed_unchanged"] = ( - result.get("source_counts_before") == result.get("source_counts_after") - ) + result["source_counts_observed_unchanged"] = result.get("source_counts_before") == result.get("source_counts_after") result["gateway_process_observable"] = gateway_process_observable result["status"] = ( "pass" - if all(value is True for value in result["checks"].values() if value is not None) - and "error" not in result + if all(value is True for value in result["checks"].values() if value is not None) and "error" not in result else "fail" ) result["source_database_mutated"] = not all( @@ -2205,11 +2110,7 @@ drop database if exists {clone_db}; "source_table_deltas_exactly_zero", ) ) - result["service_restarted"] = ( - None - if gateway_process_unchanged is None - else not gateway_process_unchanged - ) + result["service_restarted"] = None if gateway_process_unchanged is None else not gateway_process_unchanged result["post_run_cleanup"] = { "leftover_clone_database_count": result.get("leftover_clone_count"), "gateway_process_observable": gateway_process_observable, @@ -2218,9 +2119,7 @@ drop database if exists {clone_db}; "gateway_main_pid": result.get("service_after", {}).get("MainPID"), "gateway_restart_count": result.get("service_after", {}).get("NRestarts"), } - result["strict_receipt"] = _strict_receipt( - result, expected_rows, expected_table_deltas - ) + result["strict_receipt"] = _strict_receipt(result, expected_rows, expected_table_deltas) if not result["strict_receipt"]["pass"]: result["status"] = "fail" return result @@ -2232,15 +2131,9 @@ def parse_args(argv: list[str]) -> argparse.Namespace: parser.add_argument("--source-db", default=ap.DEFAULT_DB) parser.add_argument("--database-prefix", default="teleo_approve_claim_clone") parser.add_argument("--service", default="leoclean-gateway.service") - parser.add_argument( - "--approve-script", type=Path, default=HERE / "approve_proposal.py" - ) - parser.add_argument( - "--apply-script", type=Path, default=HERE / "apply_proposal.py" - ) - parser.add_argument( - "--prereqs", type=Path, default=HERE / "kb_apply_prereqs.sql" - ) + parser.add_argument("--approve-script", type=Path, default=HERE / "approve_proposal.py") + parser.add_argument("--apply-script", type=Path, default=HERE / "apply_proposal.py") + parser.add_argument("--prereqs", type=Path, default=HERE / "kb_apply_prereqs.sql") parser.add_argument("--review-secrets-file", default=DEFAULT_REVIEW_SECRETS_FILE) parser.add_argument("--secrets-file", default=ap.DEFAULT_SECRETS_FILE) parser.add_argument("--review-role", default="kb_review") @@ -2251,6 +2144,16 @@ def parse_args(argv: list[str]) -> argparse.Namespace: help="optional one-row normalization output containing one strict approve_claim child", ) parser.add_argument("--output", type=Path) + parser.add_argument( + "--lifecycle-receipt-output", + type=Path, + help="optional public fixture receipt output (never use for private production data)", + ) + parser.add_argument( + "--rollback-sql-output", + type=Path, + help="optional exact clone rollback SQL output", + ) return parser.parse_args(argv) diff --git a/scripts/run_approve_claim_isolated_container_canary.sh b/scripts/run_approve_claim_isolated_container_canary.sh index a34f3bf..5690c31 100755 --- a/scripts/run_approve_claim_isolated_container_canary.sh +++ b/scripts/run_approve_claim_isolated_container_canary.sh @@ -9,6 +9,8 @@ image="${POSTGRES_CANARY_IMAGE:-postgres:16}" source_tier="${SOURCE_TIER:-live-readonly}" normalization_json="" output="${repo_root}/docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.json" +lifecycle_receipt_output="" +rollback_sql_output="" while [[ $# -gt 0 ]]; do case "$1" in @@ -20,6 +22,14 @@ while [[ $# -gt 0 ]]; do output="$2" shift 2 ;; + --lifecycle-receipt-output) + lifecycle_receipt_output="$2" + shift 2 + ;; + --rollback-sql-output) + rollback_sql_output="$2" + shift 2 + ;; --source-container) source_container="$2" shift 2 @@ -99,11 +109,19 @@ service_state() { } mkdir -p "$workdir" "$(dirname "$output")" +if [[ -n "$lifecycle_receipt_output" ]]; then + mkdir -p "$(dirname "$lifecycle_receipt_output")" +fi +if [[ -n "$rollback_sql_output" ]]; then + mkdir -p "$(dirname "$rollback_sql_output")" +fi source_counts_before="$(source_counts)" service_before="$(service_state)" docker run -d --name "$container" \ --network none \ + --label livingip.canary=proposal-apply-lifecycle \ + --label "livingip.canary.instance=${container}" \ --tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m \ -e POSTGRES_PASSWORD="isolated-postgres-${RANDOM}-${RANDOM}" \ -e POSTGRES_DB=teleo \ @@ -127,6 +145,12 @@ if [[ "$ready_streak" -lt 2 ]]; then fi container_network_mode="$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$container")" container_mounts_json="$(docker inspect -f '{{json .Mounts}}' "$container")" +container_canary_label="$( + docker inspect -f '{{index .Config.Labels "livingip.canary"}}' "$container" +)" +container_instance_label="$( + docker inspect -f '{{index .Config.Labels "livingip.canary.instance"}}' "$container" +)" docker exec "$source_container" pg_dump -U postgres -d "$source_db" \ --schema-only --no-owner --no-privileges >"$workdir/schema.sql" @@ -165,6 +189,13 @@ normalization_args=() if [[ -n "$normalization_json" ]]; then normalization_args=(--normalization-json "$normalization_json") fi +artifact_args=() +if [[ -n "$lifecycle_receipt_output" ]]; then + artifact_args+=(--lifecycle-receipt-output "$lifecycle_receipt_output") +fi +if [[ -n "$rollback_sql_output" ]]; then + artifact_args+=(--rollback-sql-output "$rollback_sql_output") +fi set +e python3 "$repo_root/scripts/run_approve_claim_clone_canary.py" \ @@ -178,6 +209,7 @@ python3 "$repo_root/scripts/run_approve_claim_clone_canary.py" \ --review-secrets-file "$workdir/kb-review.env" \ --secrets-file "$workdir/kb-apply.env" \ "${normalization_args[@]}" \ + "${artifact_args[@]}" \ --output "$output" \ >"$workdir/stdout.json" 2>"$workdir/stderr.log" canary_rc=$? @@ -192,6 +224,20 @@ container_name_readback="$( docker container ls -a --filter "name=^/${container}$" --format '{{.Names}}' 2>&1 )" container_name_readback_rc=$? +container_label_readback="$( + docker container ls -a \ + --filter "label=livingip.canary=proposal-apply-lifecycle" \ + --filter "label=livingip.canary.instance=${container}" \ + --format '{{.Names}}' 2>&1 +)" +container_label_readback_rc=$? +if [[ "$container_label_readback_rc" -eq 0 ]]; then + container_label_orphan_count="$( + awk 'NF { count += 1 } END { print count + 0 }' <<<"$container_label_readback" + )" +else + container_label_orphan_count=-1 +fi if [[ -e "$workdir" || -L "$workdir" ]]; then outer_workdir_lexists=true else @@ -214,6 +260,9 @@ OUTER_CONTAINER_REMOVE_RC="$outer_container_remove_rc" \ OUTER_WORKDIR_REMOVE_RC="$outer_workdir_remove_rc" \ CONTAINER_NAME_READBACK="$container_name_readback" \ CONTAINER_NAME_READBACK_RC="$container_name_readback_rc" \ +CONTAINER_LABEL_READBACK="$container_label_readback" \ +CONTAINER_LABEL_READBACK_RC="$container_label_readback_rc" \ +CONTAINER_LABEL_ORPHAN_COUNT="$container_label_orphan_count" \ OUTER_WORKDIR_LEXISTS="$outer_workdir_lexists" \ CANARY_RC="$canary_rc" \ SOURCE_TIER="$source_tier" \ @@ -221,219 +270,6 @@ SOURCE_NETWORK_MODE="$source_network_mode" \ SOURCE_CANARY_LABEL="$source_canary_label" \ CANARY_NETWORK_MODE="$container_network_mode" \ CANARY_MOUNTS_JSON="$container_mounts_json" \ -python3 - "$output" <<'PY' -import hashlib -import json -import os -import sys -from pathlib import Path - -path = Path(sys.argv[1]) -if not path.is_file(): - raise SystemExit("inner canary did not produce a report") - -data = json.loads(path.read_text(encoding="utf-8")) - -def service(raw: str) -> dict[str, str]: - return dict(line.split("=", 1) for line in raw.splitlines() if "=" in line) - -def table_deltas(before: dict[str, int], after: dict[str, int]) -> dict[str, int]: - if set(before) != set(after): - raise SystemExit( - "live source count keys changed during the canary: " - f"before={sorted(before)} after={sorted(after)}" - ) - return {key: after[key] - before[key] for key in sorted(before)} - -def verify_source_binding(root: Path, records: list[dict[str, object]]) -> dict[str, object]: - readbacks = [] - for record in records: - relative_text = str(record.get("repo_relative_path", "")) - relative = Path(relative_text) - if not relative_text or relative.is_absolute() or ".." in relative.parts: - raise SystemExit(f"unsafe repo-relative source path: {relative_text!r}") - candidate = (root / relative).resolve() - try: - candidate.relative_to(root) - except ValueError as exc: - raise SystemExit( - f"source path escaped repository root: {relative_text}" - ) from exc - if not candidate.is_file(): - readbacks.append( - { - "repo_relative_path": relative.as_posix(), - "expected_sha256": record.get("sha256"), - "actual_sha256": None, - "matches": False, - } - ) - continue - content = candidate.read_bytes() - actual_sha256 = hashlib.sha256(content).hexdigest() - readbacks.append( - { - "repo_relative_path": relative.as_posix(), - "expected_sha256": record.get("sha256"), - "actual_sha256": actual_sha256, - "size_bytes": len(content), - "matches": ( - actual_sha256 == record.get("sha256") - and len(content) == record.get("size_bytes") - ), - } - ) - return { - "files": readbacks, - "all_match": bool(readbacks) and all(row["matches"] for row in readbacks), - } - -def contains_ephemeral_path(value: object) -> bool: - if isinstance(value, str): - return "/tmp/" in value - if isinstance(value, dict): - return any(contains_ephemeral_path(item) for item in value.values()) - if isinstance(value, list): - return any(contains_ephemeral_path(item) for item in value) - return False - -before_counts = json.loads(os.environ["SOURCE_COUNTS_BEFORE"]) -after_counts = json.loads(os.environ["SOURCE_COUNTS_AFTER"]) -live_table_deltas = table_deltas(before_counts, after_counts) -before_service = service(os.environ["SERVICE_BEFORE"]) -after_service = service(os.environ["SERVICE_AFTER"]) -source_tier = os.environ["SOURCE_TIER"] -source_network_mode = os.environ["SOURCE_NETWORK_MODE"] -source_canary_label = os.environ["SOURCE_CANARY_LABEL"] -container_network_mode = os.environ["CANARY_NETWORK_MODE"] -container_mounts = json.loads(os.environ["CANARY_MOUNTS_JSON"]) -container_volume_mounts = [ - mount for mount in container_mounts if mount.get("Type") == "volume" -] -container_isolated = ( - container_network_mode == "none" and not container_volume_mounts -) -source_tier_enforced = ( - source_tier != "isolated-local" - or ( - source_network_mode == "none" - and source_canary_label == "working-leo-approved-source" - ) -) -service_observable = ( - before_service.get("Available") == "true" - and after_service.get("Available") == "true" -) -service_stable = ( - all( - before_service.get(key) == after_service.get(key) - for key in ("ActiveState", "SubState", "MainPID", "NRestarts") - ) - if service_observable - else None -) -service_gate_passes = ( - service_stable is True - if source_tier == "live-readonly" - else service_stable is not False -) -container_absent = ( - int(os.environ["CONTAINER_NAME_READBACK_RC"]) == 0 - and not os.environ["CONTAINER_NAME_READBACK"].strip() -) -workdir_absent = os.environ["OUTER_WORKDIR_LEXISTS"] == "false" -source_binding = verify_source_binding( - Path(os.environ["REPO_ROOT"]).resolve(), - data.get("source_binding", {}).get("files", []), -) -data["source_binding"]["independent_post_cleanup_verification"] = source_binding -source_binding_ok = ( - data["source_binding"].get("unchanged_during_run") is True - and source_binding["all_match"] is True -) -stale_ephemeral_paths_absent = not contains_ephemeral_path(data) -inner_runtime_pass = data.get("status") == "pass" and int(os.environ["CANARY_RC"]) == 0 -outer_ok = ( - before_counts == after_counts - and all(delta == 0 for delta in live_table_deltas.values()) - and service_gate_passes - and container_absent - and workdir_absent - and source_binding_ok - and stale_ephemeral_paths_absent - and container_isolated - and source_tier_enforced -) -if source_tier == "isolated-local": - data["required_tier"] = "T2_runtime" - data["required_tiers"] = ["T2_runtime"] - data["runtime_scope"] = "isolated_postgres_container_from_readonly_local_fixture" -else: - data["required_tier"] = "T3_live_readonly" - data["required_tiers"] = ["T2_runtime", "T3_live_readonly"] - data["runtime_scope"] = "isolated_postgres_container_from_readonly_live_schema" -data["outer_isolation"] = { - "source_container": os.environ.get("SOURCE_CONTAINER", "teleo-pg"), - "source_database": os.environ.get("SOURCE_DB", "teleo"), - "source_counts_before": before_counts, - "source_counts_after": after_counts, - "canonical_table_deltas": live_table_deltas, - "source_counts_unchanged": before_counts == after_counts, - "service_before": before_service, - "service_after": after_service, - "service_required": source_tier == "live-readonly", - "service_observable": service_observable, - "service_stable": service_stable, - "service_gate_passes": service_gate_passes, - "source_boundary": { - "source_tier": source_tier, - "network_mode": source_network_mode, - "canary_label": source_canary_label, - "tier_enforced": source_tier_enforced, - }, - "container_isolation": { - "network_mode": container_network_mode, - "volume_mounts": container_volume_mounts, - "network_disabled": container_network_mode == "none", - "no_docker_volumes": not container_volume_mounts, - }, - "cleanup_readback": { - "container_remove_returncode": int(os.environ["OUTER_CONTAINER_REMOVE_RC"]), - "container_name_query_returncode": int( - os.environ["CONTAINER_NAME_READBACK_RC"] - ), - "container_name_query_stdout": os.environ["CONTAINER_NAME_READBACK"], - "temporary_container_absent": container_absent, - "workdir_remove_returncode": int(os.environ["OUTER_WORKDIR_REMOVE_RC"]), - "workdir_lexists_after_cleanup": not workdir_absent, - "temporary_workdir_absent": workdir_absent, - }, -} -data["checks"]["outer_live_counts_exactly_unchanged"] = before_counts == after_counts -data["checks"]["outer_live_table_deltas_exactly_zero"] = all( - delta == 0 for delta in live_table_deltas.values() -) -data["checks"]["outer_service_observed_if_required"] = ( - source_tier != "live-readonly" or service_observable -) -data["checks"]["outer_service_stable_if_observed"] = service_gate_passes -data["checks"]["outer_container_absent_independent_readback"] = container_absent -data["checks"]["outer_workdir_absent_independent_readback"] = workdir_absent -data["checks"]["source_binding_independently_verified"] = source_binding_ok -data["checks"]["stale_ephemeral_paths_absent"] = stale_ephemeral_paths_absent -data["checks"]["outer_source_tier_enforced"] = source_tier_enforced -data["checks"]["outer_container_network_disabled"] = ( - container_network_mode == "none" -) -data["checks"]["outer_container_has_no_docker_volumes"] = not container_volume_mounts -data["checks"]["outer_isolation_complete"] = outer_ok -if outer_ok and inner_runtime_pass: - data["current_tier"] = ( - "T2_runtime" if source_tier == "isolated-local" else "T3_live_readonly" - ) -else: - data["status"] = "fail" - data["current_tier"] = "T2_runtime" if inner_runtime_pass else "T1_model" -path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") -raise SystemExit(0 if data["status"] == "pass" else 1) -PY +CANARY_LABEL="$container_canary_label" \ +CANARY_INSTANCE_LABEL="$container_instance_label" \ +python3 "$repo_root/scripts/finalize_approve_claim_isolated_canary.py" "$output" diff --git a/tests/test_apply_proposal.py b/tests/test_apply_proposal.py index b1fb67d..8f79873 100644 --- a/tests/test_apply_proposal.py +++ b/tests/test_apply_proposal.py @@ -30,6 +30,10 @@ sys.path.insert(0, str(REPO_ROOT / "scripts")) import apply_proposal as ap # noqa: E402 +CLAIM_A = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +CLAIM_B = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +SOURCE_A = "cccccccc-cccc-4ccc-8ccc-cccccccccccc" + def _approval_meta(proposal_type, apply_payload): return { @@ -112,7 +116,7 @@ def test_revise_strategy_rejects_bad_node_type(): # --- add_edge ------------------------------------------------------------- # def test_add_edge_sql_dedup_guard(): - payload = {"from_claim": "aaaa", "to_claim": "bbbb", "edge_type": "supersedes", "weight": 0.9} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supersedes", "weight": 0.9} sql = ap.build_add_edge_sql(payload, "pid-2", None, _approval_meta("add_edge", payload)) assert "insert into public.claim_edges" in sql assert "not exists" in sql @@ -122,7 +126,7 @@ def test_add_edge_sql_dedup_guard(): def test_add_edge_semantic_row_accepts_match_and_rejects_weight_mismatch(): - payload = {"from_claim": "aaaa", "to_claim": "bbbb", "edge_type": "supports", "weight": 0.9} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports", "weight": 0.9} sql = ap.build_add_edge_sql(payload, "pid-2", None, _approval_meta("add_edge", payload)) verify = sql[sql.index("do $verify$") : sql.index("kb_stage.finish_approved_proposal")] @@ -131,9 +135,7 @@ def test_add_edge_semantic_row_accepts_match_and_rejects_weight_mismatch(): assert "claim_edge row does not match strict apply payload" in verify assert sql.startswith("begin;") assert sql.rstrip().endswith("commit;") - assert sql.index("raise exception 'apply_proposal: claim_edge row") < sql.index( - "kb_stage.finish_approved_proposal" - ) + assert sql.index("raise exception 'apply_proposal: claim_edge row") < sql.index("kb_stage.finish_approved_proposal") def test_add_edge_rejects_self_loop(): @@ -144,7 +146,7 @@ def test_add_edge_rejects_self_loop(): # --- attach_evidence ------------------------------------------------------ # def test_attach_evidence_sql_with_source_id(): - payload = {"evidence": [{"claim_id": "cccc", "source_id": "dddd", "role": "grounds", "weight": 0.78}]} + payload = {"evidence": [{"claim_id": CLAIM_A, "source_id": SOURCE_A, "role": "grounds", "weight": 0.78}]} sql = ap.build_attach_evidence_sql(payload, "pid-3", None, _approval_meta("attach_evidence", payload)) assert "insert into public.claim_evidence" in sql assert "::evidence_role" in sql @@ -152,14 +154,8 @@ def test_attach_evidence_sql_with_source_id(): def test_attach_evidence_semantic_row_accepts_match_and_rejects_weight_mismatch(): - payload = { - "evidence": [ - {"claim_id": "cccc", "source_id": "dddd", "role": "grounds", "weight": 0.78} - ] - } - sql = ap.build_attach_evidence_sql( - payload, "pid-3", None, _approval_meta("attach_evidence", payload) - ) + payload = {"evidence": [{"claim_id": CLAIM_A, "source_id": SOURCE_A, "role": "grounds", "weight": 0.78}]} + sql = ap.build_attach_evidence_sql(payload, "pid-3", None, _approval_meta("attach_evidence", payload)) verify = sql[sql.index("do $verify$") : sql.index("kb_stage.finish_approved_proposal")] assert "weight is not distinct from 0.78" in verify @@ -167,9 +163,7 @@ def test_attach_evidence_semantic_row_accepts_match_and_rejects_weight_mismatch( assert "evidence row % does not match strict apply payload" in verify assert sql.startswith("begin;") assert sql.rstrip().endswith("commit;") - assert sql.index("raise exception 'apply_proposal: evidence row") < sql.index( - "kb_stage.finish_approved_proposal" - ) + assert sql.index("raise exception 'apply_proposal: evidence row") < sql.index("kb_stage.finish_approved_proposal") def test_attach_evidence_requires_source_id(): @@ -231,9 +225,7 @@ def _approve_claim_bundle(): {"claim_id": claim_a, "source_id": source_a, "role": "grounds", "weight": 0.9}, {"claim_id": claim_b, "source_id": source_b, "role": "illustrates", "weight": None}, ], - "edges": [ - {"from_claim": claim_a, "to_claim": claim_b, "edge_type": "supports", "weight": 0.7} - ], + "edges": [{"from_claim": claim_a, "to_claim": claim_b, "edge_type": "supports", "weight": 0.7}], "reasoning_tools": [ { "id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", @@ -291,9 +283,7 @@ def test_approve_claim_semantic_rows_accept_matches_and_reject_payload_mismatche assert "approve_claim: edge row does not match strict apply payload" in verify assert sql.startswith("begin;") assert sql.rstrip().endswith("commit;") - assert sql.index("raise exception 'approve_claim: evidence row") < sql.index( - "kb_stage.finish_approved_proposal" - ) + assert sql.index("raise exception 'approve_claim: evidence row") < sql.index("kb_stage.finish_approved_proposal") def test_approve_claim_bundle_rejects_duplicate_source_hashes(): @@ -445,7 +435,7 @@ def test_assert_applyable_allows_approved(): # --- ledger flip: concurrency guard + FK stamp --------------------------- # def test_ledger_transition_uses_payload_bound_security_functions(): - payload = {"from_claim": "a", "to_claim": "b", "edge_type": "supports"} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports"} sql = ap.build_add_edge_sql(payload, "pid", None, _approval_meta("add_edge", payload)) assert "kb_stage.assert_approved_proposal" in sql assert "kb_stage.finish_approved_proposal" in sql @@ -459,7 +449,7 @@ def test_ledger_transition_uses_payload_bound_security_functions(): def test_ledger_flip_stamps_agent_fk(): - payload = {"from_claim": "a", "to_claim": "b", "edge_type": "supports"} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports"} sql = ap.build_add_edge_sql(payload, "pid", "kb-apply", _approval_meta("add_edge", payload)) # Hard resolve into a variable + NOT-NULL assert, never a silent inline # subselect that would stamp NULL on an unresolved handle. @@ -468,7 +458,7 @@ def test_ledger_flip_stamps_agent_fk(): def test_build_apply_sql_defaults_applied_by_to_service_agent(): - payload = {"from_claim": "a", "to_claim": "b", "edge_type": "supports"} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports"} proposal = { "id": "pid", "proposal_type": "add_edge", diff --git a/tests/test_apply_worker.py b/tests/test_apply_worker.py index 21ce813..cd04d1d 100644 --- a/tests/test_apply_worker.py +++ b/tests/test_apply_worker.py @@ -6,7 +6,10 @@ standalone (`python3 tests/test_apply_worker.py`). """ import argparse +import json +import stat import sys +import tempfile from pathlib import Path try: @@ -40,19 +43,33 @@ sys.path.insert(0, str(REPO_ROOT / "scripts")) import apply_worker as w # noqa: E402 -@pytest.fixture(autouse=True) -def restore_apply_worker_dependencies(): +def _dependency_restore(): original_load_password = w.ap.load_password original_load_failure_state = w.load_failure_state original_fetch_candidates = w.fetch_candidates original_apply_one = w.apply_one original_render_one = w.render_one + original_subprocess_run = w.subprocess.run yield w.ap.load_password = original_load_password w.load_failure_state = original_load_failure_state w.fetch_candidates = original_fetch_candidates w.apply_one = original_apply_one w.render_one = original_render_one + w.subprocess.run = original_subprocess_run + + +@pytest.fixture(autouse=True) +def restore_apply_worker_dependencies(): + restore = _dependency_restore() + next(restore) + try: + yield + finally: + try: + next(restore) + except StopIteration: + pass # --- candidate query ------------------------------------------------------ # @@ -116,22 +133,283 @@ def test_render_command_expands_agent_id(): # --- enablement gate ------------------------------------------------------ # def _args(**over): base = dict( - enable=False, limit=20, max_per_tick=1, max_attempts=3, - failure_state_file=None, applied_by="kb-apply", - apply_script="apply_proposal.py", render_cmd="", - secrets_file="x", container="teleo-pg", db="teleo", - host="127.0.0.1", role="kb_apply", + enable=False, + limit=20, + max_per_tick=1, + max_attempts=3, + failure_state_file=None, + applied_by="kb-apply", + apply_script="apply_proposal.py", + render_cmd="", + receipt_dir="/private/tmp/kb-apply-worker-test-receipts", + secrets_file="x", + container="teleo-pg", + db="teleo", + host="127.0.0.1", + role="kb_apply", ) base.update(over) return argparse.Namespace(**base) +def _write_valid_private_receipt(path: Path, proposal_id: str) -> None: + from_claim = "11111111-1111-4111-8111-111111111111" + to_claim = "22222222-2222-4222-8222-222222222222" + proposal = { + "id": proposal_id, + "proposal_type": "add_edge", + "status": "applied", + "payload": { + "apply_payload": { + "from_claim": from_claim, + "to_claim": to_claim, + "edge_type": "supports", + "weight": 0.75, + } + }, + "reviewed_by_handle": "reviewer", + "reviewed_by_agent_id": None, + "reviewed_at": "2026-07-15T10:00:00+00:00", + "review_note": "Approved for worker receipt testing.", + "applied_by_handle": "kb-apply", + "applied_by_agent_id": None, + "applied_at": "2026-07-15T10:01:00+00:00", + } + receipt = w.ap.replay_receipt.build_replay_receipt( + proposal, + { + "claim_edges": [ + { + "id": "33333333-3333-4333-8333-333333333333", + "from_claim": from_claim, + "to_claim": to_claim, + "edge_type": "supports", + "weight": 0.75, + "created_by": None, + } + ] + }, + apply_sql_sha256="a" * 64, + apply_sql_source="exact_executed_sql", + exported_at_utc="2026-07-15T10:02:00+00:00", + ) + path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + path.write_text(json.dumps(receipt), encoding="utf-8") + path.chmod(0o600) + + +def _load_private_receipt(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def _write_private_receipt(path: Path, receipt: dict) -> None: + path.write_text(json.dumps(receipt), encoding="utf-8") + path.chmod(0o600) + + +def _completed(returncode: int, *, stdout: str = "", stderr: str = ""): + return w.subprocess.CompletedProcess([], returncode, stdout=stdout, stderr=stderr) + + +def _postcommit_receipt_failure(proposal_id: str, detail: str = "receipt write failed") -> str: + return ( + f"proposal {proposal_id} {w.POSTCOMMIT_RECEIPT_FAILURE_MARKER}: {detail}" + f"{w.POSTCOMMIT_RECEIPT_RECOVERY_MARKER}" + f"apply_proposal.py {proposal_id} --receipt-only" + ) + + +# --- private receipt and post-COMMIT recovery ---------------------------- # +def test_private_receipt_requires_complete_canonical_rows_and_hashes(): + proposal_id = "99999999-9999-4999-8999-999999999999" + receipt_path = Path(tempfile.mkdtemp()) / "receipt.json" + _write_valid_private_receipt(receipt_path, proposal_id) + + missing_rows = _load_private_receipt(receipt_path) + missing_rows.pop("canonical_rows") + _write_private_receipt(receipt_path, missing_rows) + with pytest.raises(RuntimeError, match="row-level contract is invalid"): + w.assert_private_replay_receipt(receipt_path, proposal_id) + + _write_valid_private_receipt(receipt_path, proposal_id) + missing_hashes = _load_private_receipt(receipt_path) + missing_hashes.pop("hashes") + _write_private_receipt(receipt_path, missing_hashes) + with pytest.raises(RuntimeError, match="row-level contract is invalid"): + w.assert_private_replay_receipt(receipt_path, proposal_id) + + +def test_private_receipt_rejects_canonical_row_tampering_without_recomputed_hashes(): + proposal_id = "88888888-8888-4888-8888-888888888888" + receipt_path = Path(tempfile.mkdtemp()) / "receipt.json" + _write_valid_private_receipt(receipt_path, proposal_id) + tampered = _load_private_receipt(receipt_path) + tampered["canonical_rows"]["claim_edges"][0]["weight"] = 0.5 + _write_private_receipt(receipt_path, tampered) + + with pytest.raises(RuntimeError, match="row-level contract is invalid|internally inconsistent"): + w.assert_private_replay_receipt(receipt_path, proposal_id) + + +def test_apply_one_supplies_deterministic_private_receipt_path(): + proposal_id = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + receipt_dir = Path(tempfile.mkdtemp()) / "receipts" + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + receipt_path = Path(cmd[cmd.index("--receipt-out") + 1]) + _write_valid_private_receipt(receipt_path, proposal_id) + return _completed(0, stdout='{"applied": true}') + + w.subprocess.run = _run + w.apply_one(_args(receipt_dir=str(receipt_dir)), proposal_id) + + expected = receipt_dir.resolve() / f"{proposal_id}.json" + assert len(calls) == 1 + assert calls[0][calls[0].index("--receipt-dir") + 1] == str(expected.parent) + assert calls[0][calls[0].index("--receipt-out") + 1] == str(expected) + assert stat.S_IMODE(expected.stat().st_mode) == 0o600 + + +def test_apply_one_recovers_postcommit_receipt_failure_without_reapplying(): + proposal_id = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" + receipt_dir = Path(tempfile.mkdtemp()) / "receipts" + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + if len(calls) == 1: + return _completed( + 1, + stderr=_postcommit_receipt_failure(proposal_id), + ) + assert "--receipt-only" in cmd + receipt_path = Path(cmd[cmd.index("--receipt-out") + 1]) + _write_valid_private_receipt(receipt_path, proposal_id) + return _completed(0, stdout='{"replay_ready": true}') + + w.subprocess.run = _run + w.apply_one(_args(receipt_dir=str(receipt_dir)), proposal_id) + + assert len(calls) == 2 + assert "--receipt-only" not in calls[0] + assert "--receipt-only" in calls[1] + assert calls[0][calls[0].index("--receipt-out") + 1] == calls[1][calls[1].index("--receipt-out") + 1] + + +def test_apply_one_does_not_recover_a_precommit_apply_failure(): + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + return _completed(1, stderr="psql transaction failed before commit") + + w.subprocess.run = _run + try: + w.apply_one(_args(receipt_dir=tempfile.mkdtemp()), "p1") + except w.PostCommitReceiptError as exc: + raise AssertionError("pre-COMMIT failure was misclassified as committed") from exc + except RuntimeError: + pass + else: + raise AssertionError("expected pre-COMMIT apply failure") + + assert len(calls) == 1 + + +def test_apply_one_classifies_unrecovered_receipt_as_postcommit(): + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + if len(calls) == 1: + return _completed(1, stderr=_postcommit_receipt_failure("p1")) + return _completed(1, stderr="receipt directory remains unavailable") + + w.subprocess.run = _run + with pytest.raises(w.PostCommitReceiptError): + w.apply_one(_args(receipt_dir=tempfile.mkdtemp()), "p1") + + assert len(calls) == 2 + assert "--receipt-only" in calls[1] + + +def test_forged_marker_in_unknown_field_traceback_is_a_normal_apply_failure(): + proposal_id = "cccccccc-cccc-4ccc-8ccc-cccccccccccc" + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + return _completed( + 1, + stderr=( + "Traceback (most recent call last):\n" + "ValueError: approve_claim apply_payload contains unknown fields: " + f"['{w.POSTCOMMIT_RECEIPT_FAILURE_MARKER}']" + ), + ) + + w.subprocess.run = _run + with pytest.raises(RuntimeError, match="apply failed"): + w.apply_one(_args(receipt_dir=tempfile.mkdtemp()), proposal_id) + + assert len(calls) == 1 + assert "--receipt-only" not in calls[0] + + +def test_forged_marker_increments_normal_failure_and_poison_accounting(): + proposal_id = "dddddddd-dddd-4ddd-8ddd-dddddddddddd" + failure_state = str(Path(tempfile.mkdtemp()) / "failures.json") + calls = [] + w.ap.load_password = lambda _path: "pw" + w.fetch_candidates = lambda _args, _password, _excluded: _cands(proposal_id) + + def _run(cmd, **_kwargs): + calls.append(cmd) + return _completed( + 1, + stderr=( + "ValueError: approve_claim apply_payload contains unknown fields: " + f"['{w.POSTCOMMIT_RECEIPT_FAILURE_MARKER}']" + ), + ) + + w.subprocess.run = _run + rc = w.run( + _args( + enable=True, + failure_state_file=failure_state, + max_attempts=1, + max_per_tick=1, + receipt_dir=tempfile.mkdtemp(), + ) + ) + + assert rc == 1 + assert w.load_failure_state(failure_state) == {proposal_id: 1} + assert len(calls) == 1 + + +def test_postcommit_receipt_error_does_not_increment_apply_failure_count(): + path = str(Path(tempfile.mkdtemp()) / "failures.json") + w.ap.load_password = lambda _f: "pw" + w.fetch_candidates = lambda a, p, excluded: _cands("committed") + + def _postcommit(_args, _proposal_id): + raise w.PostCommitReceiptError("committed; receipt recovery failed") + + w.apply_one = _postcommit + rc = w.run(_args(enable=True, failure_state_file=path, max_per_tick=1)) + + assert rc == 1 + assert w.load_failure_state(path) == {} + + def test_report_only_gate_does_not_apply(monkeypatch=None): calls = [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}] w.apply_one = lambda a, pid: calls.append(pid) rc = w.run(_args(enable=False)) @@ -142,9 +420,7 @@ def test_report_only_gate_does_not_apply(monkeypatch=None): def test_enabled_worker_applies_and_renders(): applied, rendered = [], [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}] w.apply_one = lambda a, pid: applied.append(pid) w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok" @@ -157,9 +433,7 @@ def test_enabled_worker_applies_and_renders(): def test_enabled_worker_skips_render_for_add_edge(): applied, rendered = [], [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "e1", "proposal_type": "add_edge", "agent_id": None} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "e1", "proposal_type": "add_edge", "agent_id": None}] w.apply_one = lambda a, pid: applied.append(pid) w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok" @@ -172,9 +446,7 @@ def test_enabled_worker_skips_render_for_add_edge(): def test_enabled_worker_skips_render_for_approve_claim(): applied, rendered = [], [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "c1", "proposal_type": "approve_claim", "agent_id": "ag"} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "c1", "proposal_type": "approve_claim", "agent_id": "ag"}] w.apply_one = lambda a, pid: applied.append(pid) w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok" @@ -263,7 +535,7 @@ if __name__ == "__main__": failures = 0 for name, fn in sorted(globals().items()): if name.startswith("test_") and callable(fn): - restore = restore_apply_worker_dependencies() + restore = _dependency_restore() next(restore) try: fn() diff --git a/tests/test_proposal_apply_lifecycle.py b/tests/test_proposal_apply_lifecycle.py new file mode 100644 index 0000000..370dde1 --- /dev/null +++ b/tests/test_proposal_apply_lifecycle.py @@ -0,0 +1,846 @@ +"""Deterministic and property-based tests for proposal apply rollback evidence.""" + +from __future__ import annotations + +import copy +import hashlib +import json +import string +import sys +from datetime import datetime, timezone +from pathlib import Path + +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts")) + +import apply_proposal as apply # noqa: E402 +import proposal_apply_lifecycle as lifecycle # noqa: E402 + +PROPOSAL_ID = "99999999-9999-4999-8999-999999999999" +CLAIM_A = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +CLAIM_B = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +SOURCE_ID = "cccccccc-cccc-4ccc-8ccc-cccccccccccc" +TOOL_ID = "dddddddd-dddd-4ddd-8ddd-dddddddddddd" +AGENT_ID = "11111111-1111-4111-8111-111111111111" +APPLIED_AT = "2026-07-15T10:15:00+00:00" +REVIEWED_AT = "2026-07-15T09:30:00+00:00" +ROW_FAMILIES = tuple(lifecycle.ROW_TABLES) + + +def _apply_payload() -> dict: + return { + "contract_version": 2, + "agent_id": AGENT_ID, + "claims": [ + { + "id": CLAIM_A, + "type": "structural", + "text": "Canonical claim A", + "status": "open", + "confidence": 0.8, + "tags": ["working-leo", "lifecycle"], + "created_by": AGENT_ID, + }, + { + "id": CLAIM_B, + "type": "concept", + "text": "Canonical claim B", + "status": "open", + "confidence": None, + "tags": [], + "created_by": AGENT_ID, + }, + ], + "sources": [ + { + "id": SOURCE_ID, + "source_type": "paper", + "url": "https://example.test/lifecycle", + "storage_path": None, + "excerpt": "Bound lifecycle evidence.", + "hash": "sha256:lifecycle-source", + "created_by": AGENT_ID, + } + ], + "evidence": [ + { + "claim_id": CLAIM_A, + "source_id": SOURCE_ID, + "role": "grounds", + "weight": 0.9, + "created_by": AGENT_ID, + } + ], + "edges": [ + { + "from_claim": CLAIM_A, + "to_claim": CLAIM_B, + "edge_type": "supports", + "weight": 0.7, + "created_by": AGENT_ID, + } + ], + "reasoning_tools": [ + { + "id": TOOL_ID, + "agent_id": AGENT_ID, + "name": "Lifecycle verifier", + "description": "Checks the apply and compensating rollback boundary.", + "category": "verification", + } + ], + } + + +def _proposal_before() -> dict: + return { + "id": PROPOSAL_ID, + "proposal_type": "approve_claim", + "status": "approved", + "payload": {"apply_payload": _apply_payload()}, + "reviewed_by_handle": "independent-reviewer", + "reviewed_by_agent_id": None, + "reviewed_at": REVIEWED_AT, + "review_note": "Approved this exact strict payload.", + "applied_by_handle": None, + "applied_by_agent_id": None, + "applied_at": None, + } + + +def _approval_snapshot(proposal: dict) -> dict: + return { + "proposal_id": proposal["id"], + "proposal_type": proposal["proposal_type"], + "payload": copy.deepcopy(proposal["payload"]), + "reviewed_by_handle": proposal["reviewed_by_handle"], + "reviewed_by_agent_id": proposal["reviewed_by_agent_id"], + "reviewed_by_db_role": "kb_reviewer", + "reviewed_at": proposal["reviewed_at"], + "review_note": proposal["review_note"], + } + + +def _canonical_rows() -> dict[str, list[dict]]: + evidence_id = apply.deterministic_row_uuid(PROPOSAL_ID, "claim_evidence", CLAIM_A, SOURCE_ID, "grounds") + edge_id = apply.deterministic_row_uuid(PROPOSAL_ID, "claim_edge", CLAIM_A, CLAIM_B, "supports") + return { + "claims": [ + { + "id": CLAIM_A, + "type": "structural", + "text": "Canonical claim A", + "status": "open", + "confidence": 0.8, + "tags": ["working-leo", "lifecycle"], + "created_by": AGENT_ID, + "superseded_by": None, + "created_at": APPLIED_AT, + "updated_at": APPLIED_AT, + }, + { + "id": CLAIM_B, + "type": "concept", + "text": "Canonical claim B", + "status": "open", + "confidence": None, + "tags": [], + "created_by": AGENT_ID, + "superseded_by": None, + "created_at": APPLIED_AT, + "updated_at": APPLIED_AT, + }, + ], + "sources": [ + { + "id": SOURCE_ID, + "source_type": "paper", + "url": "https://example.test/lifecycle", + "storage_path": None, + "excerpt": "Bound lifecycle evidence.", + "hash": "sha256:lifecycle-source", + "created_by": AGENT_ID, + "created_at": APPLIED_AT, + } + ], + "claim_evidence": [ + { + "id": evidence_id, + "claim_id": CLAIM_A, + "source_id": SOURCE_ID, + "role": "grounds", + "weight": 0.9, + "created_by": AGENT_ID, + "created_at": APPLIED_AT, + } + ], + "claim_edges": [ + { + "id": edge_id, + "from_claim": CLAIM_A, + "to_claim": CLAIM_B, + "edge_type": "supports", + "weight": 0.7, + "created_by": AGENT_ID, + "created_at": APPLIED_AT, + } + ], + "reasoning_tools": [ + { + "id": TOOL_ID, + "agent_id": AGENT_ID, + "name": "Lifecycle verifier", + "description": "Checks the apply and compensating rollback boundary.", + "category": "verification", + "created_at": APPLIED_AT, + "updated_at": APPLIED_AT, + } + ], + } + + +def _empty_targets() -> dict[str, list[dict]]: + return {family: [] for family in ROW_FAMILIES} + + +def _apply_receipt(proposal: dict, rows: dict[str, list[dict]]) -> dict: + applied = copy.deepcopy(proposal) + applied.update( + { + "status": "applied", + "applied_by_handle": apply.SERVICE_AGENT_HANDLE, + "applied_by_agent_id": AGENT_ID, + "applied_at": APPLIED_AT, + } + ) + apply_sql = apply.build_apply_sql(proposal, apply.SERVICE_AGENT_HANDLE) + return apply.replay_receipt.build_replay_receipt( + applied, + copy.deepcopy(rows), + apply_sql_sha256=lifecycle.sha256_text(apply_sql), + apply_sql_source="exact_executed_sql", + exported_at_utc="2026-07-15T10:16:00+00:00", + ) + + +def _materials() -> tuple[dict, dict, dict, dict, str]: + proposal = _proposal_before() + approval = _approval_snapshot(proposal) + preflight = lifecycle.build_fresh_preflight( + proposal, + _empty_targets(), + captured_at_utc="2026-07-15T10:00:00+00:00", + ) + receipt = _apply_receipt(proposal, _canonical_rows()) + rollback_sql = lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, receipt) + return proposal, approval, preflight, receipt, rollback_sql + + +def _passing_lifecycle_receipt() -> dict: + proposal, approval, preflight, receipt, rollback_sql = _materials() + proposal_after = copy.deepcopy(proposal) + return lifecycle.build_lifecycle_receipt( + proposal_before=proposal, + approval_before=approval, + preflight=preflight, + apply_receipt=receipt, + rollback_sql=rollback_sql, + proposal_after=proposal_after, + approval_after=copy.deepcopy(approval), + target_rows_after=_empty_targets(), + counts_before={family: 7 + index for index, family in enumerate(ROW_FAMILIES)}, + counts_after={family: 7 + index for index, family in enumerate(ROW_FAMILIES)}, + unrelated_before={"fingerprint": "same", "row_count": 17}, + unrelated_after={"fingerprint": "same", "row_count": 17}, + rollback_replay_refused=True, + generated_at_utc="2026-07-15T10:30:00+00:00", + ) + + +def _destination(*, candidate_present: bool = True) -> dict: + lifecycle_receipt = _passing_lifecycle_receipt() + candidates = [] + if candidate_present: + candidates.append( + { + "id": PROPOSAL_ID, + "proposal_type": "approve_claim", + "status": "approved", + "applied_at": None, + "proposal_payload_sha256": lifecycle_receipt["proposal_payload_sha256"], + "apply_payload_sha256": lifecycle_receipt["apply_payload_sha256"], + "approval_snapshot_sha256": lifecycle_receipt["approval_snapshot_sha256"], + "reviewed_by_handle": "independent-reviewer", + "reviewed_by_agent_id": None, + "reviewed_at": REVIEWED_AT, + "review_note": "Approved this exact strict payload.", + "immutable_approval_exact": True, + } + ) + return { + "observed_at_utc": "2026-07-15T10:45:00+00:00", + "read_only_transaction": True, + "container": "teleo-pg", + "database": "teleo", + "system_identifier": "7649789040005668902", + "server_version_num": "160014", + "approval_gate": { + "immutable_approval_table_present": True, + "approve_function_present": True, + "assert_function_present": True, + "finish_function_present": True, + "review_role_present": True, + "apply_role_present": True, + }, + "approved_strict_candidates": candidates, + } + + +def _authorization_materials() -> tuple[dict, dict, dict, dict]: + proposal, approval, preflight, apply_receipt, _production_rollback_sql = _materials() + receipt = _passing_lifecycle_receipt() + destination = _destination() + destination_identity = { + key: destination[key] for key in ("container", "database", "system_identifier", "server_version_num") + } + production_rollback_artifact = lifecycle.build_production_rollback_artifact( + proposal_before=proposal, + approval_snapshot=approval, + preflight=preflight, + apply_receipt=apply_receipt, + destination=destination_identity, + generated_at_utc="2026-07-15T10:49:00+00:00", + ) + packet = lifecycle.build_authorization_packet( + receipt, + destination, + repo_git_sha=lifecycle.current_git_sha(REPO_ROOT), + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + generated_at_utc="2026-07-15T10:50:00+00:00", + ) + record = { + "authorized": True, + "rollback_authorized": True, + "binding_sha256": packet["binding_sha256"], + "production_rollback_sql_sha256": production_rollback_artifact["rollback_sql_sha256"], + "operator_identity": "release-operator@example.test", + "received_at_utc": "2026-07-15T11:00:00+00:00", + "expires_at_utc": "2026-07-15T13:00:00+00:00", + } + record["authorization_text"] = lifecycle.expected_authorization_text(packet, record) + destination["observed_at_utc"] = "2026-07-15T11:30:00+00:00" + return packet, record, destination, production_rollback_artifact + + +def test_fresh_preflight_requires_every_owned_target_to_be_absent_and_is_deterministic(): + proposal = _proposal_before() + first = lifecycle.build_fresh_preflight( + proposal, + _empty_targets(), + captured_at_utc="2026-07-15T10:00:00+00:00", + ) + second = lifecycle.build_fresh_preflight( + proposal, + _empty_targets(), + captured_at_utc="2026-07-15T10:01:00+00:00", + ) + + assert first["fresh_owned_targets"] is True + assert first["preflight_sha256"] == second["preflight_sha256"] + assert first["preflight_artifact_sha256"] != second["preflight_artifact_sha256"] + assert first["captured_at_utc"] != second["captured_at_utc"] + + nonfresh = _empty_targets() + nonfresh["claims"] = [{"id": CLAIM_A}] + with pytest.raises(ValueError, match="absent target rows"): + lifecycle.build_fresh_preflight(proposal, nonfresh) + + incomplete = _empty_targets() + incomplete.pop("claim_edges") + with pytest.raises(ValueError, match="every approve_claim target table"): + lifecycle.build_fresh_preflight(proposal, incomplete) + + +@pytest.mark.parametrize( + "mutation", + ( + "artifact", + "schema", + "captured_at_utc", + "proposal_id", + "proposal_payload_sha256", + "apply_payload_sha256", + "target_family_missing", + "target_row_present", + "fresh_flag_type", + "state_hash", + "artifact_hash", + "extra_field", + ), +) +def test_rollback_refuses_every_preflight_contract_mutation(mutation: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed = copy.deepcopy(preflight) + if mutation == "artifact": + changed["artifact"] = "proposal_apply_fresh_preflight_tampered" + elif mutation == "schema": + changed["schema"] = "livingip.proposalApplyFreshPreflight.v0" + elif mutation == "captured_at_utc": + changed["captured_at_utc"] = "2026-07-15T10:00:01+00:00" + elif mutation == "proposal_id": + changed["proposal_id"] = CLAIM_A + elif mutation == "proposal_payload_sha256": + changed["proposal_payload_sha256"] = "0" * 64 + elif mutation == "apply_payload_sha256": + changed["apply_payload_sha256"] = "0" * 64 + elif mutation == "target_family_missing": + changed["target_rows_before"].pop("claim_edges") + elif mutation == "target_row_present": + changed["target_rows_before"]["claims"] = [{"id": CLAIM_A}] + elif mutation == "fresh_flag_type": + changed["fresh_owned_targets"] = 1 + elif mutation == "state_hash": + changed["preflight_sha256"] = "0" * 64 + elif mutation == "artifact_hash": + changed["preflight_artifact_sha256"] = "0" * 64 + else: + changed["unexpected"] = True + + with pytest.raises(ValueError): + lifecycle.build_compensating_rollback_sql(proposal, approval, changed, receipt) + + +def test_compensating_rollback_is_atomic_deterministic_and_refuses_state_drift(): + proposal, approval, preflight, receipt, rollback_sql = _materials() + + assert rollback_sql == lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, receipt) + assert rollback_sql.startswith("begin;") + assert rollback_sql.rstrip().endswith("commit;") + assert "proposal rollback drift:" in rollback_sql + assert "to_jsonb(t)" in rollback_sql + assert rollback_sql.index("proposal rollback drift:") < rollback_sql.index("delete from public.claim_evidence") + delete_positions = [ + rollback_sql.index(f"delete from {lifecycle.ROW_TABLES[family]}") for family in lifecycle.DELETE_ORDER + ] + assert delete_positions == sorted(delete_positions) + assert "set status = 'approved'" in rollback_sql + assert "immutable approval snapshot drifted" in rollback_sql + + drifted_receipt = copy.deepcopy(receipt) + drifted_receipt["proposal"]["payload"]["apply_payload"]["claims"][0]["text"] = "drift" + with pytest.raises(ValueError, match="payload does not match preflight"): + lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, drifted_receipt) + + +@given( + suffix=st.text( + alphabet=string.ascii_letters + string.digits + " -_", + min_size=1, + max_size=32, + ) +) +@settings(max_examples=30, deadline=None) +def test_rollback_refuses_any_canonical_row_tampering_without_rebuilt_receipt(suffix: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed = copy.deepcopy(receipt) + changed["canonical_rows"]["claims"][0]["text"] = f"mutated:{suffix}" + + with pytest.raises(ValueError, match="replay-contract validation"): + lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, changed) + + +@pytest.mark.parametrize( + "mutation", + ("contract_bool", "expected_count_bool", "actual_count_float", "check_integer"), +) +def test_replay_receipt_json_types_are_exact_not_python_equal(mutation: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed = copy.deepcopy(receipt) + if mutation == "contract_bool": + changed["receipt_contract_version"] = True + elif mutation == "expected_count_bool": + changed["expected_row_counts"]["sources"] = True + elif mutation == "actual_count_float": + changed["actual_row_counts"]["sources"] = 1.0 + else: + changed["checks"]["proposal_applied"] = 1 + + with pytest.raises(ValueError, match="replay-contract validation"): + lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, changed) + + +def test_rollback_sql_hash_binds_every_receipt_observed_field(): + proposal, approval, preflight, receipt, rollback_sql = _materials() + changed_rows = copy.deepcopy(receipt["canonical_rows"]) + changed_rows["claims"][0]["created_at"] = "2026-07-15T10:15:00.123456+00:00" + changed_receipt = _apply_receipt(proposal, changed_rows) + + changed_sql = lifecycle.build_compensating_rollback_sql( + proposal, + approval, + preflight, + changed_receipt, + ) + + assert lifecycle.sha256_text(changed_sql) != lifecycle.sha256_text(rollback_sql) + assert "to_jsonb(t)" in changed_sql + assert "proposal rollback drift: claims[0] row does not match apply receipt" in changed_sql + + +@pytest.mark.parametrize("family", ("claim_evidence", "claim_edges")) +def test_fresh_apply_receipt_requires_deterministic_generated_row_ids(family: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed_rows = copy.deepcopy(receipt["canonical_rows"]) + changed_rows[family][0]["id"] = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" + changed_receipt = _apply_receipt(proposal, changed_rows) + + with pytest.raises(ValueError, match="is not deterministic"): + lifecycle.build_compensating_rollback_sql( + proposal, + approval, + preflight, + changed_receipt, + ) + + +def test_production_rollback_artifact_reconstructs_exact_sql_and_rejects_noop_substitution(): + proposal, approval, preflight, apply_receipt, rollback_sql = _materials() + destination = { + "container": "teleo-pg", + "database": "teleo", + "system_identifier": "7649789040005668902", + "server_version_num": "160014", + } + artifact = lifecycle.build_production_rollback_artifact( + proposal_before=proposal, + approval_snapshot=approval, + preflight=preflight, + apply_receipt=apply_receipt, + destination=destination, + generated_at_utc="2026-07-15T10:49:00+00:00", + ) + + assert ( + lifecycle.validate_production_rollback_artifact( + artifact, + expected_destination=destination, + ) + == artifact + ) + assert artifact["rollback_sql"] == rollback_sql + + noop = copy.deepcopy(artifact) + noop["rollback_sql"] = ( + "begin; -- proposal rollback drift: -- immutable approval snapshot drifted " + "-- set status = 'approved'\ncommit;\n" + ) + noop["rollback_sql_sha256"] = lifecycle.sha256_text(noop["rollback_sql"]) + unsigned = dict(noop) + unsigned.pop("artifact_sha256") + noop["artifact_sha256"] = lifecycle.sha256_json(unsigned) + + with pytest.raises(ValueError, match="reconstructed exact SQL"): + lifecycle.validate_production_rollback_artifact(noop, expected_destination=destination) + + +def test_lifecycle_receipt_proves_inverse_and_has_self_consistent_hashes(): + receipt = _passing_lifecycle_receipt() + unsigned = dict(receipt) + receipt_hash = unsigned.pop("lifecycle_receipt_sha256") + + assert receipt["pass"] is True + assert receipt["current_tier"] == "T2_runtime" + assert all(receipt["checks"].values()) + assert all(not rows for rows in receipt["rollback"]["target_rows_after"].values()) + assert receipt["rollback"]["counts_after_rollback"] == receipt["rollback"]["counts_before_apply"] + assert receipt["production_apply_executed"] is False + assert receipt["production_apply_authorization_present"] is False + assert receipt_hash == lifecycle.sha256_json(unsigned) + + +def test_lifecycle_receipt_downgrades_when_unrelated_state_changes(): + proposal, approval, preflight, receipt, rollback_sql = _materials() + result = lifecycle.build_lifecycle_receipt( + proposal_before=proposal, + approval_before=approval, + preflight=preflight, + apply_receipt=receipt, + rollback_sql=rollback_sql, + proposal_after=copy.deepcopy(proposal), + approval_after=copy.deepcopy(approval), + target_rows_after=_empty_targets(), + counts_before={family: 1 for family in ROW_FAMILIES}, + counts_after={family: 1 for family in ROW_FAMILIES}, + unrelated_before={"fingerprint": "before"}, + unrelated_after={"fingerprint": "after"}, + rollback_replay_refused=True, + ) + + assert result["pass"] is False + assert result["current_tier"] == "T1_model" + assert result["checks"]["unrelated_rows_unchanged"] is False + + +def test_retained_current_lifecycle_evidence_is_self_consistent(): + report_dir = REPO_ROOT / "docs" / "reports" / "leo-working-state-20260709" + canary = json.loads((report_dir / "proposal-apply-lifecycle-canary-current.json").read_text(encoding="utf-8")) + receipt = json.loads((report_dir / "proposal-apply-lifecycle-receipt-current.json").read_text(encoding="utf-8")) + rollback = (report_dir / "proposal-apply-clone-rollback-current.sql").read_text(encoding="utf-8") + unsigned = dict(receipt) + receipt_sha256 = unsigned.pop("lifecycle_receipt_sha256") + + assert canary["status"] == "pass" + assert canary["current_tier"] == "T2_runtime" + assert canary["applied_rollback_receipt"] == receipt + assert canary["outer_isolation"]["cleanup_readback"]["label_scoped_orphan_count"] == 0 + assert all(delta == 0 for delta in canary["outer_isolation"]["canonical_table_deltas"].values()) + assert receipt["pass"] is True + assert receipt["production_apply_executed"] is False + assert receipt_sha256 == lifecycle.sha256_json(unsigned) + assert hashlib.sha256(rollback.encode("utf-8")).hexdigest() == receipt["rollback"]["rollback_sql_sha256"] + + +def test_authorization_packet_is_exact_but_never_self_authorizes_production(): + packet, record, destination, production_rollback_artifact = _authorization_materials() + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert packet["required_tier"] == "T6_authorized_production" + assert packet["current_tier"] == "T3_live_readonly" + assert packet["safe_to_enter_apply_window"] is False + assert packet["production_apply_authorization_present"] is False + assert packet["production_apply_executed"] is False + assert verification["safe_to_enter_apply_window"] is True + assert verification["production_apply_executed"] is False + assert all(verification["checks"].values()) + + +def test_authorization_packet_names_missing_live_gate_before_requesting_auth(tmp_path): + receipt = _passing_lifecycle_receipt() + destination = _destination(candidate_present=False) + destination["approval_gate"] = {key: False for key in destination["approval_gate"]} + packet = lifecycle.build_authorization_packet( + receipt, + destination, + repo_git_sha=lifecycle.current_git_sha(REPO_ROOT), + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + ) + markdown_path = tmp_path / "authorization.md" + lifecycle.write_authorization_markdown(markdown_path, packet) + + assert packet["production_preconditions"]["approval_gate_ready"] is False + assert packet["production_preconditions"]["matching_candidate_count"] == 0 + assert packet["production_preconditions"]["exact_production_rollback_packet_ready"] is False + assert packet["binding"]["production_rollback"]["rollback_sql_sha256"] is None + assert lifecycle.sha256_json(packet["binding"]) == packet["binding_sha256"] + assert packet["safe_to_enter_apply_window"] is False + assert packet["production_apply_authorization_present"] is False + assert packet["production_apply_executed"] is False + assert packet["next_exact_action"].startswith("deploy and independently verify") + assert "This text is not actionable" in markdown_path.read_text(encoding="utf-8") + + +@given(value=st.sampled_from([False, None, 0, 1, "true", "yes", [], {}])) +@settings(max_examples=8, deadline=None) +def test_authorized_field_requires_the_exact_boolean_true(value): + packet, record, destination, production_rollback_artifact = _authorization_materials() + record["authorized"] = value + record["authorization_text"] = lifecycle.expected_authorization_text(packet, record) + + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert verification["checks"]["authorized_is_exact_true"] is False + assert verification["safe_to_enter_apply_window"] is False + + +AUTHORIZATION_MUTATIONS = ( + "packet_schema", + "packet_readonly", + "approval_gate_packet", + "production_rollback_packet", + "strict_candidate", + "rollback_authorized", + "production_rollback_sha256", + "binding_sha256", + "operator_identity", + "authorization_window", + "authorization_text", + "destination_container", + "destination_database", + "destination_system_identifier", + "destination_server_version", + "approval_gate_action", + "strict_candidate_action", + "apply_engine_sha256", + "approval_gate_sha256", + "binding_proposal", + "binding_gate_false", + "binding_preconditions", + "packet_safe_flag", + "scope_exclusion", + "authorization_template", + "record_extra_field", + "action_readback_stale", + "production_rollback_binding", +) + + +@given(mutation=st.sampled_from(AUTHORIZATION_MUTATIONS)) +@settings(max_examples=len(AUTHORIZATION_MUTATIONS), deadline=None) +def test_every_authorization_binding_field_fails_closed_when_mutated(mutation: str): + packet, record, destination, production_rollback_artifact = _authorization_materials() + expected_failed_check = { + "packet_schema": "packet_schema_exact", + "packet_readonly": "packet_destination_readback_was_read_only", + "approval_gate_packet": "approval_gate_ready_at_packet", + "production_rollback_packet": "exact_production_rollback_packet_ready", + "strict_candidate": "strict_proposal_present_and_approved", + "rollback_authorized": "rollback_authorized_is_exact_true", + "production_rollback_sha256": "production_rollback_sql_sha256_exact", + "binding_sha256": "binding_sha256_exact", + "operator_identity": "operator_identity_present", + "authorization_window": "authorization_window_valid", + "authorization_text": "authorization_text_exact", + "destination_container": "destination_identity_exact", + "destination_database": "destination_identity_exact", + "destination_system_identifier": "destination_identity_exact", + "destination_server_version": "destination_identity_exact", + "approval_gate_action": "approval_gate_identity_exact_at_action", + "strict_candidate_action": "strict_proposal_exact_at_action", + "apply_engine_sha256": "apply_engine_sha256_exact", + "approval_gate_sha256": "approval_gate_prerequisite_sha256_exact", + "binding_proposal": "packet_binding_sha256_self_consistent", + "binding_gate_false": "approval_gate_ready_at_packet", + "binding_preconditions": "packet_preconditions_bound_exactly", + "packet_safe_flag": "packet_never_self_authorizes_or_expands_scope", + "scope_exclusion": "packet_never_self_authorizes_or_expands_scope", + "authorization_template": "authorization_text_template_exact", + "record_extra_field": "authorization_record_contract_exact", + "action_readback_stale": "destination_readback_fresh_and_read_only_at_action", + "production_rollback_binding": "production_rollback_sql_sha256_exact", + }[mutation] + + if mutation == "packet_schema": + packet["schema"] = "livingip.proposalApplyAuthorizationPacket.v0" + elif mutation == "packet_readonly": + packet["production_preconditions"]["destination_readback_was_read_only"] = False + elif mutation == "approval_gate_packet": + packet["production_preconditions"]["approval_gate_ready"] = False + elif mutation == "production_rollback_packet": + packet["production_preconditions"]["exact_production_rollback_packet_ready"] = False + elif mutation == "strict_candidate": + packet["production_preconditions"]["strict_proposal_present_and_approved"] = False + elif mutation == "rollback_authorized": + record["rollback_authorized"] = False + elif mutation == "production_rollback_sha256": + record["production_rollback_sql_sha256"] = "f" * 64 + elif mutation == "binding_sha256": + record["binding_sha256"] = "0" * 64 + elif mutation == "operator_identity": + record["operator_identity"] = " " + elif mutation == "authorization_window": + record["expires_at_utc"] = "2026-07-15T11:30:00+00:00" + elif mutation == "authorization_text": + record["authorization_text"] += " altered" + elif mutation.startswith("destination_"): + key = { + "destination_container": "container", + "destination_database": "database", + "destination_system_identifier": "system_identifier", + "destination_server_version": "server_version_num", + }[mutation] + destination[key] = f"wrong-{key}" + elif mutation == "approval_gate_action": + destination["approval_gate"]["review_role_present"] = False + elif mutation == "strict_candidate_action": + destination["approved_strict_candidates"] = [] + elif mutation == "apply_engine_sha256": + packet["binding"]["engine"]["apply_engine_sha256"] = "0" * 64 + elif mutation == "approval_gate_sha256": + packet["binding"]["approval_gate_dependency"]["sha256"] = "0" * 64 + elif mutation == "binding_proposal": + packet["binding"]["proposal"]["id"] = CLAIM_A + elif mutation == "binding_gate_false": + packet["binding"]["approval_gate"]["review_role_present"] = False + elif mutation == "binding_preconditions": + packet["binding"]["production_preconditions"]["approval_gate_ready"] = False + elif mutation == "packet_safe_flag": + packet["safe_to_enter_apply_window"] = True + elif mutation == "scope_exclusion": + packet["scope_exclusions"]["telegram_send_authorized"] = True + elif mutation == "authorization_template": + packet["authorization_text_template"] += " Also authorize all other mutations." + elif mutation == "record_extra_field": + record["unexpected"] = True + elif mutation == "action_readback_stale": + destination["observed_at_utc"] = "2026-07-15T10:59:59+00:00" + elif mutation == "production_rollback_binding": + packet["binding"]["production_rollback"]["rollback_sql_sha256"] = "f" * 64 + + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert verification["checks"][expected_failed_check] is False + assert verification["safe_to_enter_apply_window"] is False + assert verification["production_apply_authorization_present"] is False + + +def test_false_bound_gate_cannot_pass_even_after_packet_and_record_are_rehashed(): + packet, record, destination, production_rollback_artifact = _authorization_materials() + packet["binding"]["approval_gate"]["review_role_present"] = False + destination["approval_gate"]["review_role_present"] = False + packet["binding_sha256"] = lifecycle.sha256_json(packet["binding"]) + packet["authorization_record_required"]["binding_sha256"] = packet["binding_sha256"] + packet["authorization_text_template"] = lifecycle._authorization_text_template( + packet["binding"], + packet["binding_sha256"], + ) + record["binding_sha256"] = packet["binding_sha256"] + record["authorization_text"] = lifecycle.expected_authorization_text(packet, record) + + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert verification["checks"]["packet_binding_sha256_self_consistent"] is True + assert verification["checks"]["approval_gate_ready_at_packet"] is False + assert verification["safe_to_enter_apply_window"] is False + + +def test_authorization_packet_uses_repo_relative_dependency_paths_only(): + packet, _record, _destination, _production_rollback_artifact = _authorization_materials() + encoded = json.dumps(packet, sort_keys=True) + + assert packet["binding"]["engine"]["apply_engine_path"] == "scripts/apply_proposal.py" + assert packet["binding"]["approval_gate_dependency"]["path"] == "scripts/kb_apply_prereqs.sql" + assert "/private/tmp" not in encoded + assert "/tmp" not in encoded diff --git a/tests/test_run_approve_claim_clone_canary_macos_path.py b/tests/test_run_approve_claim_clone_canary_macos_path.py index ad472db..ad238a9 100644 --- a/tests/test_run_approve_claim_clone_canary_macos_path.py +++ b/tests/test_run_approve_claim_clone_canary_macos_path.py @@ -2,6 +2,8 @@ from __future__ import annotations +import hashlib +import json import subprocess import sys from pathlib import Path @@ -11,8 +13,10 @@ import pytest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) WRAPPER = ROOT / "scripts" / "run_approve_claim_isolated_container_canary.sh" +FINALIZER = ROOT / "scripts" / "finalize_approve_claim_isolated_canary.py" import apply_proposal as apply # noqa: E402 +import finalize_approve_claim_isolated_canary as finalizer # noqa: E402 import run_approve_claim_clone_canary as canary # noqa: E402 @@ -24,9 +28,7 @@ def test_role_psql_resolves_docker_before_scrubbing_path( monkeypatch.setattr( canary.shutil, "which", - lambda executable: "/opt/homebrew/bin/docker" - if executable == "docker" - else None, + lambda executable: "/opt/homebrew/bin/docker" if executable == "docker" else None, ) def fake_run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]: @@ -58,9 +60,7 @@ def test_apply_tool_resolves_docker_before_scrubbing_path( monkeypatch.setattr( apply.shutil, "which", - lambda executable: "/opt/homebrew/bin/docker" - if executable == "docker" - else None, + lambda executable: "/opt/homebrew/bin/docker" if executable == "docker" else None, ) def fake_run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]: @@ -89,14 +89,76 @@ def test_apply_tool_resolves_docker_before_scrubbing_path( def test_outer_wrapper_enforces_local_source_and_networkless_tmpfs() -> None: - text = WRAPPER.read_text(encoding="utf-8") + wrapper = WRAPPER.read_text(encoding="utf-8") + finalizer = FINALIZER.read_text(encoding="utf-8") + text = wrapper + "\n" + finalizer assert 'source_canary_label" != "working-leo-approved-source"' in text assert "--network none" in text assert "--tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m" in text assert 'CANARY_NETWORK_MODE="$container_network_mode"' in text assert 'CANARY_MOUNTS_JSON="$container_mounts_json"' in text - assert 'data["checks"]["outer_source_tier_enforced"]' in text - assert 'data["checks"]["outer_container_network_disabled"]' in text - assert 'data["checks"]["outer_container_has_no_docker_volumes"]' in text + assert 'checks["outer_source_tier_enforced"]' in text + assert 'checks["outer_container_network_disabled"]' in text + assert 'checks["outer_container_has_no_docker_volumes"]' in text assert 'source_tier != "live-readonly" or service_observable' in text + assert "<<'PY'" not in wrapper + assert "finalize_approve_claim_isolated_canary.py" in wrapper + + +def test_outer_finalizer_runs_from_a_normal_file_without_heredoc(tmp_path: Path) -> None: + content = FINALIZER.read_bytes() + report = tmp_path / "canary.json" + report.write_text( + json.dumps( + { + "status": "pass", + "checks": {}, + "source_binding": { + "unchanged_during_run": True, + "files": [ + { + "repo_relative_path": FINALIZER.relative_to(ROOT).as_posix(), + "sha256": hashlib.sha256(content).hexdigest(), + "size_bytes": len(content), + } + ], + }, + } + ), + encoding="utf-8", + ) + counts = json.dumps({"public.claims": 2, "kb_stage.kb_proposals": 0}) + data = finalizer.finalize( + report, + { + "SOURCE_COUNTS_BEFORE": counts, + "SOURCE_COUNTS_AFTER": counts, + "SOURCE_CONTAINER": "isolated-source", + "SOURCE_DB": "teleo", + "SERVICE_BEFORE": "Available=false\nReason=systemctl_unavailable\n", + "SERVICE_AFTER": "Available=false\nReason=systemctl_unavailable\n", + "REPO_ROOT": str(ROOT), + "OUTER_CONTAINER_REMOVE_RC": "0", + "OUTER_WORKDIR_REMOVE_RC": "0", + "CONTAINER_NAME_READBACK": "", + "CONTAINER_NAME_READBACK_RC": "0", + "CONTAINER_LABEL_READBACK": "", + "CONTAINER_LABEL_READBACK_RC": "0", + "CONTAINER_LABEL_ORPHAN_COUNT": "0", + "OUTER_WORKDIR_LEXISTS": "false", + "CANARY_RC": "0", + "SOURCE_TIER": "isolated-local", + "SOURCE_NETWORK_MODE": "none", + "SOURCE_CANARY_LABEL": "working-leo-approved-source", + "CANARY_NETWORK_MODE": "none", + "CANARY_MOUNTS_JSON": "[]", + "CANARY_LABEL": "proposal-apply-lifecycle", + "CANARY_INSTANCE_LABEL": "working-leo-approve-claim-test", + }, + ) + + assert data["status"] == "pass" + assert data["current_tier"] == "T2_runtime" + assert data["checks"]["outer_isolation_complete"] is True + assert data["outer_isolation"]["cleanup_readback"]["label_scoped_orphan_count"] == 0