Merge pull request #159 from living-ip/codex/leo-proposal-apply-lifecycle-20260715
Some checks are pending
CI / lint-and-test (push) Waiting to run
Some checks are pending
CI / lint-and-test (push) Waiting to run
Rehearse approved proposal apply and compensating rollback
This commit is contained in:
commit
2416f918ee
19 changed files with 8638 additions and 953 deletions
|
|
@ -0,0 +1,141 @@
|
||||||
|
begin;
|
||||||
|
set local standard_conforming_strings = on;
|
||||||
|
set local lock_timeout = '5s';
|
||||||
|
select pg_advisory_xact_lock(hashtextextended('proposal-rollback:' || E'edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b', 0));
|
||||||
|
do $rollback$
|
||||||
|
declare
|
||||||
|
affected integer;
|
||||||
|
approval_now jsonb;
|
||||||
|
downstream_dependency_count integer;
|
||||||
|
begin
|
||||||
|
perform 1 from kb_stage.kb_proposals
|
||||||
|
where id = E'edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b'::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'edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b'::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":"b4d0a944-8f11-5516-80ca-bc680f0059ad","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":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","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":"b4d0a944-8f11-5516-80ca-bc680f0059ad","to_claim":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","weight":0.75}],"evidence":[{"claim_id":"b4d0a944-8f11-5516-80ca-bc680f0059ad","created_by":null,"role":"grounds","source_id":"3953271f-7df5-5674-99b0-980b1b3534e6","weight":0.9},{"claim_id":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","created_by":null,"role":"illustrates","source_id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"a32a7252-d39f-5ee9-873a-a82b56a6bb08","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-e315c2518c-a","id":"3953271f-7df5-5674-99b0-980b1b3534e6","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-e315c2518c-b","id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","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'99999999-9999-9999-9999-999999999999'::uuid
|
||||||
|
and reviewed_at is not distinct from E'2026-07-15 03:59:09.091704+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 03:59:12.452878+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'edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b'::uuid;
|
||||||
|
if approval_now is distinct from E'{"payload":{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"b4d0a944-8f11-5516-80ca-bc680f0059ad","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":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","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":"b4d0a944-8f11-5516-80ca-bc680f0059ad","to_claim":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","weight":0.75}],"evidence":[{"claim_id":"b4d0a944-8f11-5516-80ca-bc680f0059ad","created_by":null,"role":"grounds","source_id":"3953271f-7df5-5674-99b0-980b1b3534e6","weight":0.9},{"claim_id":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","created_by":null,"role":"illustrates","source_id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"a32a7252-d39f-5ee9-873a-a82b56a6bb08","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-e315c2518c-a","id":"3953271f-7df5-5674-99b0-980b1b3534e6","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-e315c2518c-b","id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","source_type":"observation","storage_path":null,"url":null}]}},"proposal_id":"edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b","proposal_type":"approve_claim","review_note":"Reviewed exact strict payload inside the disposable clone.","reviewed_at":"2026-07-15 03:59:09.091704+00","reviewed_by_agent_id":"99999999-9999-9999-9999-999999999999","reviewed_by_db_role":"kb_review","reviewed_by_handle":"m3ta"}'::jsonb then
|
||||||
|
raise exception 'proposal rollback: immutable approval snapshot drifted';
|
||||||
|
end if;
|
||||||
|
select count(*) into downstream_dependency_count
|
||||||
|
from (
|
||||||
|
select ce.claim_id
|
||||||
|
from public.claim_evidence ce
|
||||||
|
where (ce.claim_id = any(array[E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid, E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid]::uuid[]) or ce.source_id = any(array[E'3953271f-7df5-5674-99b0-980b1b3534e6'::uuid, E'3d31345d-a4f7-52a7-af4a-0034f99a8123'::uuid]::uuid[]))
|
||||||
|
and not (((ce.claim_id = E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid and ce.source_id = E'3d31345d-a4f7-52a7-af4a-0034f99a8123'::uuid and ce.role = E'illustrates'::evidence_role) or (ce.claim_id = E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid and ce.source_id = E'3953271f-7df5-5674-99b0-980b1b3534e6'::uuid and ce.role = E'grounds'::evidence_role)))
|
||||||
|
union all
|
||||||
|
select edge.id
|
||||||
|
from public.claim_edges edge
|
||||||
|
where (edge.from_claim = any(array[E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid, E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid]::uuid[]) or edge.to_claim = any(array[E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid, E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid]::uuid[]))
|
||||||
|
and edge.id <> all(array[E'cd799806-18e8-5e8c-959c-5f9c267654a7'::uuid]::uuid[])
|
||||||
|
union all
|
||||||
|
select claim.id
|
||||||
|
from public.claims claim
|
||||||
|
where claim.superseded_by = any(array[E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid, E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid]::uuid[])
|
||||||
|
and claim.id <> all(array[E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid, E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid]::uuid[])
|
||||||
|
) external_dependencies;
|
||||||
|
if downstream_dependency_count <> 0 then
|
||||||
|
raise exception 'proposal rollback: % downstream dependenc(ies) exist; rollback quarantined', downstream_dependency_count;
|
||||||
|
end if;
|
||||||
|
if (select count(*) from public.claim_evidence t
|
||||||
|
where ((t.claim_id = E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid and t.source_id = E'3d31345d-a4f7-52a7-af4a-0034f99a8123'::uuid and t.role = E'illustrates'::evidence_role)) and to_jsonb(t) = E'{"claim_id":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","created_at":"2026-07-15T03:59:12.43629+00:00","created_by":null,"role":"illustrates","source_id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","weight":0.8}'::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.claim_id = E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid and t.source_id = E'3953271f-7df5-5674-99b0-980b1b3534e6'::uuid and t.role = E'grounds'::evidence_role)) and to_jsonb(t) = E'{"claim_id":"b4d0a944-8f11-5516-80ca-bc680f0059ad","created_at":"2026-07-15T03:59:12.43629+00:00","created_by":null,"role":"grounds","source_id":"3953271f-7df5-5674-99b0-980b1b3534e6","weight":0.9}'::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'cd799806-18e8-5e8c-959c-5f9c267654a7'::uuid and to_jsonb(t) = E'{"created_at":"2026-07-15T03:59:12.43629+00:00","created_by":null,"edge_type":"supports","from_claim":"b4d0a944-8f11-5516-80ca-bc680f0059ad","id":"cd799806-18e8-5e8c-959c-5f9c267654a7","to_claim":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","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'a32a7252-d39f-5ee9-873a-a82b56a6bb08'::uuid and to_jsonb(t) = E'{"agent_id":null,"category":"verification","created_at":"2026-07-15T03:59:12.43629+00:00","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"a32a7252-d39f-5ee9-873a-a82b56a6bb08","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'3953271f-7df5-5674-99b0-980b1b3534e6'::uuid and to_jsonb(t) = E'{"captured_at":null,"created_at":"2026-07-15T03:59:12.43629+00:00","created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-e315c2518c-a","id":"3953271f-7df5-5674-99b0-980b1b3534e6","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'3d31345d-a4f7-52a7-af4a-0034f99a8123'::uuid and to_jsonb(t) = E'{"captured_at":null,"created_at":"2026-07-15T03:59:12.43629+00:00","created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-e315c2518c-b","id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","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'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid and to_jsonb(t) = E'{"confidence":0.85,"created_at":"2026-07-15T03:59:12.43629+00:00","created_by":null,"id":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","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-15T03:59:12.43629+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'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid and to_jsonb(t) = E'{"confidence":0.9,"created_at":"2026-07-15T03:59:12.43629+00:00","created_by":null,"id":"b4d0a944-8f11-5516-80ca-bc680f0059ad","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-15T03:59:12.43629+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 target where ((target.claim_id = E'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid and target.source_id = E'3d31345d-a4f7-52a7-af4a-0034f99a8123'::uuid and target.role = E'illustrates'::evidence_role) or (target.claim_id = E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::uuid and target.source_id = E'3953271f-7df5-5674-99b0-980b1b3534e6'::uuid and target.role = E'grounds'::evidence_role));
|
||||||
|
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'cd799806-18e8-5e8c-959c-5f9c267654a7'::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'a32a7252-d39f-5ee9-873a-a82b56a6bb08'::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'3953271f-7df5-5674-99b0-980b1b3534e6'::uuid, E'3d31345d-a4f7-52a7-af4a-0034f99a8123'::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'a9dd972a-b867-56f7-b3c9-3913dd9a0abe'::uuid, E'b4d0a944-8f11-5516-80ca-bc680f0059ad'::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 = 'canceled',
|
||||||
|
applied_by_handle = null,
|
||||||
|
applied_by_agent_id = null,
|
||||||
|
applied_at = null,
|
||||||
|
updated_at = now()
|
||||||
|
where id = E'edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b'::uuid
|
||||||
|
and status = 'applied'
|
||||||
|
and payload = E'{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"b4d0a944-8f11-5516-80ca-bc680f0059ad","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":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","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":"b4d0a944-8f11-5516-80ca-bc680f0059ad","to_claim":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","weight":0.75}],"evidence":[{"claim_id":"b4d0a944-8f11-5516-80ca-bc680f0059ad","created_by":null,"role":"grounds","source_id":"3953271f-7df5-5674-99b0-980b1b3534e6","weight":0.9},{"claim_id":"a9dd972a-b867-56f7-b3c9-3913dd9a0abe","created_by":null,"role":"illustrates","source_id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"a32a7252-d39f-5ee9-873a-a82b56a6bb08","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-e315c2518c-a","id":"3953271f-7df5-5674-99b0-980b1b3534e6","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-e315c2518c-b","id":"3d31345d-a4f7-52a7-af4a-0034f99a8123","source_type":"observation","storage_path":null,"url":null}]}}'::jsonb
|
||||||
|
and applied_at is not distinct from E'2026-07-15 03:59:12.452878+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;
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,219 @@
|
||||||
|
{
|
||||||
|
"applied_row_keys": {
|
||||||
|
"claim_edges": [
|
||||||
|
"cd799806-18e8-5e8c-959c-5f9c267654a7"
|
||||||
|
],
|
||||||
|
"claim_evidence": [
|
||||||
|
"a9dd972a-b867-56f7-b3c9-3913dd9a0abe|3d31345d-a4f7-52a7-af4a-0034f99a8123|illustrates",
|
||||||
|
"b4d0a944-8f11-5516-80ca-bc680f0059ad|3953271f-7df5-5674-99b0-980b1b3534e6|grounds"
|
||||||
|
],
|
||||||
|
"claims": [
|
||||||
|
"a9dd972a-b867-56f7-b3c9-3913dd9a0abe",
|
||||||
|
"b4d0a944-8f11-5516-80ca-bc680f0059ad"
|
||||||
|
],
|
||||||
|
"reasoning_tools": [
|
||||||
|
"a32a7252-d39f-5ee9-873a-a82b56a6bb08"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"3953271f-7df5-5674-99b0-980b1b3534e6",
|
||||||
|
"3d31345d-a4f7-52a7-af4a-0034f99a8123"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"applied_row_sha256": {
|
||||||
|
"claim_edges": [
|
||||||
|
"aae7ed87c66cf58136f2b0027ae1cddc9092cd64bfc8e617cd7746b2306274c5"
|
||||||
|
],
|
||||||
|
"claim_evidence": [
|
||||||
|
"388f3363173efda00f0688fab77fe0ce2baf62035220b843be1adea1f1c70f7f",
|
||||||
|
"55838cb722f7158cbdbdc3b79212e764f9fa6014771985aec3c1c7856967db07"
|
||||||
|
],
|
||||||
|
"claims": [
|
||||||
|
"75fe4543c9c3194612b1cf98b074ff7e361da17c9bcd59f9b6f085c2c0861277",
|
||||||
|
"5914923936817eff6d7e5c824c94c18ae6392b7a6e80c87ace2e547865b173eb"
|
||||||
|
],
|
||||||
|
"reasoning_tools": [
|
||||||
|
"990dacb7b77cec117d9ee4a7630625a8782d06dfd549340b3269b8332c170576"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"585edbec2f98ec127041cdd662506a1494ea402f7e25ac0082dbda98197bf130",
|
||||||
|
"a8d2eead7cb3e563869978570173e2af753dfe131d4b7cfcf6dd419ffbb427e5"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"apply_payload_sha256": "e033bd4e742dfa0dad7d26e2f2b1f18eb71996541cdbc575bb2d4504c045e36f",
|
||||||
|
"apply_receipt_sha256": "ac99a2a315cc8e6e214e0f2f69d1a23413da9cfd8629c202b61a678ffda6d7ca",
|
||||||
|
"apply_sql_sha256": "b52cd47182dc0f542a394147201bb3a125518f8a4bc1a7fc44f2b0caa39c4a1b",
|
||||||
|
"approval_snapshot_sha256": "1f9d0a58ad236908da931c7489fe14ee855d0f13c4d72eb5b218370e116e2096",
|
||||||
|
"artifact": "proposal_apply_lifecycle_receipt",
|
||||||
|
"checks": {
|
||||||
|
"apply_receipt_replay_ready": true,
|
||||||
|
"apply_rows_have_exact_ids": true,
|
||||||
|
"fresh_owned_preflight_consumed_by_apply": true,
|
||||||
|
"fresh_owned_targets": true,
|
||||||
|
"immutable_approval_unchanged": true,
|
||||||
|
"rollback_counts_restored": true,
|
||||||
|
"rollback_ledger_quarantined_from_worker": 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_owned_apply_sql_sha256": "b52cd47182dc0f542a394147201bb3a125518f8a4bc1a7fc44f2b0caa39c4a1b",
|
||||||
|
"fresh_preflight_artifact_sha256": "2312c91d82ee378d4a53136b8e307900c97d0611d9f12098e799cb6de5d30077",
|
||||||
|
"fresh_preflight_sha256": "90facb2ca62feb1d3c7a242e84427ab402cf57148eaba40ec700f3cc4896d20f",
|
||||||
|
"generated_at_utc": "2026-07-15T03:59:15.412413+00:00",
|
||||||
|
"lifecycle_receipt_sha256": "4254f363b0c6115ecf07856c6e6a9879b43840e688aa73211cb9eff893b36c06",
|
||||||
|
"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": "edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b",
|
||||||
|
"payload": {
|
||||||
|
"apply_payload": {
|
||||||
|
"agent_id": null,
|
||||||
|
"claims": [
|
||||||
|
{
|
||||||
|
"confidence": 0.9,
|
||||||
|
"created_by": null,
|
||||||
|
"id": "b4d0a944-8f11-5516-80ca-bc680f0059ad",
|
||||||
|
"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": "a9dd972a-b867-56f7-b3c9-3913dd9a0abe",
|
||||||
|
"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": "b4d0a944-8f11-5516-80ca-bc680f0059ad",
|
||||||
|
"to_claim": "a9dd972a-b867-56f7-b3c9-3913dd9a0abe",
|
||||||
|
"weight": 0.75
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"evidence": [
|
||||||
|
{
|
||||||
|
"claim_id": "b4d0a944-8f11-5516-80ca-bc680f0059ad",
|
||||||
|
"created_by": null,
|
||||||
|
"role": "grounds",
|
||||||
|
"source_id": "3953271f-7df5-5674-99b0-980b1b3534e6",
|
||||||
|
"weight": 0.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"claim_id": "a9dd972a-b867-56f7-b3c9-3913dd9a0abe",
|
||||||
|
"created_by": null,
|
||||||
|
"role": "illustrates",
|
||||||
|
"source_id": "3d31345d-a4f7-52a7-af4a-0034f99a8123",
|
||||||
|
"weight": 0.8
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"reasoning_tools": [
|
||||||
|
{
|
||||||
|
"agent_id": null,
|
||||||
|
"category": "verification",
|
||||||
|
"description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.",
|
||||||
|
"id": "a32a7252-d39f-5ee9-873a-a82b56a6bb08",
|
||||||
|
"name": "Canonical row proof canary"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"created_by": null,
|
||||||
|
"excerpt": "Disposable clone lifecycle canary source A.",
|
||||||
|
"hash": "approve-claim-canary-e315c2518c-a",
|
||||||
|
"id": "3953271f-7df5-5674-99b0-980b1b3534e6",
|
||||||
|
"source_type": "observation",
|
||||||
|
"storage_path": null,
|
||||||
|
"url": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"created_by": null,
|
||||||
|
"excerpt": "Disposable clone lifecycle canary source B.",
|
||||||
|
"hash": "approve-claim-canary-e315c2518c-b",
|
||||||
|
"id": "3d31345d-a4f7-52a7-af4a-0034f99a8123",
|
||||||
|
"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 03:59:09.091704+00",
|
||||||
|
"reviewed_by_agent_id": "99999999-9999-9999-9999-999999999999",
|
||||||
|
"reviewed_by_handle": "m3ta",
|
||||||
|
"status": "approved"
|
||||||
|
},
|
||||||
|
"proposal_payload_sha256": "cf2676db5a64142d07a7b46f02f1ba38bfa8cca69d03484bbcaa5eeb7002af71",
|
||||||
|
"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": "edaf2c90-af15-5a2f-b6b9-fe08ab9ae81b",
|
||||||
|
"proposal_type": "approve_claim",
|
||||||
|
"review_note": "Reviewed exact strict payload inside the disposable clone.",
|
||||||
|
"reviewed_at": "2026-07-15 03:59:09.091704+00",
|
||||||
|
"reviewed_by_agent_id": "99999999-9999-9999-9999-999999999999",
|
||||||
|
"reviewed_by_handle": "m3ta",
|
||||||
|
"status": "canceled"
|
||||||
|
},
|
||||||
|
"rollback_sql_sha256": "50758dab3f998289a879cedb4a3f4535814e70c7f4dde417812092b9fe194901",
|
||||||
|
"schema": "livingip.proposalApplyRollback.v2",
|
||||||
|
"target_rows_after": {
|
||||||
|
"claim_edges": [],
|
||||||
|
"claim_evidence": [],
|
||||||
|
"claims": [],
|
||||||
|
"reasoning_tools": [],
|
||||||
|
"sources": []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": "livingip.proposalApplyLifecycleReceipt.v2",
|
||||||
|
"strongest_claim_allowed": "deterministic disposable-clone apply and compensating rollback"
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,260 @@
|
||||||
|
{
|
||||||
|
"artifact": "proposal_apply_production_authorization_packet",
|
||||||
|
"authorization_record_required": {
|
||||||
|
"authorization_text": "<EXACT_TEXT_FROM_VERIFIER>",
|
||||||
|
"authorized": true,
|
||||||
|
"binding_sha256": "6f1984f3a69ad049d4bbcc408ed2542dc5f081806ec9baa9a35c8959e5dab62a",
|
||||||
|
"expires_at_utc": "<UTC>",
|
||||||
|
"operator_identity": "<OPERATOR_IDENTITY>",
|
||||||
|
"production_rollback_sql_sha256": "<EXACT_PRODUCTION_ROLLBACK_SHA256>",
|
||||||
|
"received_at_utc": "<UTC>",
|
||||||
|
"rollback_authorized": true
|
||||||
|
},
|
||||||
|
"authorization_text_template": "I authorize one production DB apply for proposal 9b2b5fa4-0e0d-5db0-aa4d-6fd102889946 with payload SHA-256 6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af to Docker container teleo-pg, database teleo, system identifier 7649789040005668902, binding SHA-256 6f1984f3a69ad049d4bbcc408ed2542dc5f081806ec9baa9a35c8959e5dab62a. I also authorize conditional production rollback SHA-256 <PRODUCTION_ROLLBACK_SQL_SHA256> 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 <OPERATOR_IDENTITY>; received <UTC>; expires <UTC>.",
|
||||||
|
"binding": {
|
||||||
|
"approval_gate": {
|
||||||
|
"apply_role_present": true,
|
||||||
|
"approve_function_present": false,
|
||||||
|
"assert_function_present": false,
|
||||||
|
"finish_function_present": false,
|
||||||
|
"immutable_approval_table_present": false,
|
||||||
|
"review_role_present": false
|
||||||
|
},
|
||||||
|
"approval_gate_dependency": {
|
||||||
|
"path": "scripts/kb_apply_prereqs.sql",
|
||||||
|
"requires_separate_production_authorization": true,
|
||||||
|
"sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa"
|
||||||
|
},
|
||||||
|
"candidate_readback": {
|
||||||
|
"matching_candidate": null,
|
||||||
|
"matching_candidate_count": 0
|
||||||
|
},
|
||||||
|
"destination": {
|
||||||
|
"container": "teleo-pg",
|
||||||
|
"database": "teleo",
|
||||||
|
"server_version_num": "160014",
|
||||||
|
"system_identifier": "7649789040005668902"
|
||||||
|
},
|
||||||
|
"destination_readback": {
|
||||||
|
"observed_at_utc": "2026-07-15T02:07:27.133665+00:00",
|
||||||
|
"read_only_transaction": true
|
||||||
|
},
|
||||||
|
"engine": {
|
||||||
|
"apply_engine_path": "scripts/apply_proposal.py",
|
||||||
|
"apply_engine_sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26",
|
||||||
|
"apply_sql_sha256": "1a7d65870e415ee8891b6dd84732a29bcee00412c10862d3d1db4b191a8ade38",
|
||||||
|
"repo_git_sha": "bd3c17628c6cf7da734be01149bbf1cc77a6d71b",
|
||||||
|
"runtime_manifest": {
|
||||||
|
"scripts/apply_proposal.py": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26",
|
||||||
|
"scripts/kb_apply_prereqs.sql": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa",
|
||||||
|
"scripts/kb_apply_replay_receipt.py": "4ead132fc32a69baaecfe7b638cba8ba6d54d8508d0b7e6a922dc8065805c97f",
|
||||||
|
"scripts/proposal_apply_lifecycle.py": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expected_rows": {
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"row_sha256": {
|
||||||
|
"claim_edges": [
|
||||||
|
"60d14cbc45d424fa1eb62db6478f185bf2c2d86559e57ffa77474e8d0b3c527c"
|
||||||
|
],
|
||||||
|
"claim_evidence": [
|
||||||
|
"e97de162c0730da69b357e1350f0da7e5843c4e67d498c12d731a0f04a8868d7",
|
||||||
|
"3054718195abe3f78e08cfcacd59902d41eb91c7153c6c63f9bcb3bc7c6902ac"
|
||||||
|
],
|
||||||
|
"claims": [
|
||||||
|
"e593b9cf7b0bc9267f3e48d8173129b84b09c028f290f1abc97ccb73940b31dc",
|
||||||
|
"24280b5159cbbf75e00eb24ae50ab69994397a583f347a241a5e262cf0c03179"
|
||||||
|
],
|
||||||
|
"reasoning_tools": [
|
||||||
|
"257924b236c3c27289b9ae2314687be0e96779415fc6ff87faada22893b757ba"
|
||||||
|
],
|
||||||
|
"sources": [
|
||||||
|
"d7a690c2b18701db5b48e8189b1621c8025c1ed75889a4d0a3a4c9761c03dc3b",
|
||||||
|
"bee048794799006bd00bf4bb02ae57998e670f5bcdfb140dff871dcb042d0198"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"production_preconditions": {
|
||||||
|
"approval_gate_ready": false,
|
||||||
|
"destination_readback_observed_at_utc": "2026-07-15T02:07:27.133665+00:00",
|
||||||
|
"destination_readback_was_read_only": true,
|
||||||
|
"exact_production_rollback_packet_ready": false,
|
||||||
|
"matching_candidate_count": 0,
|
||||||
|
"production_apply_authorization_present": false,
|
||||||
|
"production_apply_executed": false,
|
||||||
|
"strict_proposal_present_and_approved": false
|
||||||
|
},
|
||||||
|
"production_rollback": {
|
||||||
|
"artifact_schema": null,
|
||||||
|
"artifact_sha256": null,
|
||||||
|
"bound_for_action_time_authorization": false,
|
||||||
|
"conditional_rollback_requires_explicit_authorization": true,
|
||||||
|
"rollback_sql_sha256": null,
|
||||||
|
"scope": "exact_production_destination_postapply_receipt",
|
||||||
|
"status": "not_generated"
|
||||||
|
},
|
||||||
|
"proposal": {
|
||||||
|
"apply_payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684",
|
||||||
|
"approval_snapshot_sha256": "310b54030e26ed43d7226f2e803610586e80d21ff064c67e11ea21e93ef40bd9",
|
||||||
|
"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_payload_sha256": "6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af",
|
||||||
|
"proposal_type": "approve_claim",
|
||||||
|
"required_applied_at": null,
|
||||||
|
"required_status": "approved",
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"rollback": {
|
||||||
|
"conditional_rollback_requires_explicit_authorization": true,
|
||||||
|
"lifecycle_receipt_sha256": "8e74cf1f5c372633f9b6f15363ff759ba6ce9bf42df51288bb9be70ddabe49b3",
|
||||||
|
"production_executable": false,
|
||||||
|
"rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3",
|
||||||
|
"scope": "disposable_clone_rehearsal"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"binding_sha256": "6f1984f3a69ad049d4bbcc408ed2542dc5f081806ec9baa9a35c8959e5dab62a",
|
||||||
|
"claim_ceiling": "T2 clone lifecycle plus T3 destination readback; no production apply authorization or execution",
|
||||||
|
"current_tier": "T3_live_readonly",
|
||||||
|
"generated_at_utc": "2026-07-15T02:08:10.775963+00:00",
|
||||||
|
"next_exact_action": "deploy and independently verify the immutable approval gate before selecting a production proposal",
|
||||||
|
"production_apply_authorization_present": false,
|
||||||
|
"production_apply_executed": false,
|
||||||
|
"production_preconditions": {
|
||||||
|
"approval_gate_ready": false,
|
||||||
|
"destination_readback_observed_at_utc": "2026-07-15T02:07:27.133665+00:00",
|
||||||
|
"destination_readback_was_read_only": true,
|
||||||
|
"exact_production_rollback_packet_ready": false,
|
||||||
|
"matching_candidate_count": 0,
|
||||||
|
"production_apply_authorization_present": false,
|
||||||
|
"production_apply_executed": false,
|
||||||
|
"strict_proposal_present_and_approved": false
|
||||||
|
},
|
||||||
|
"production_rollback_requirement": {
|
||||||
|
"clone_rehearsal_is_not_production_rollback": true,
|
||||||
|
"clone_rehearsal_rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3",
|
||||||
|
"production_rollback_artifact_schema": null,
|
||||||
|
"production_rollback_artifact_sha256": null,
|
||||||
|
"production_rollback_sql_sha256": null,
|
||||||
|
"required_before_apply_window": true,
|
||||||
|
"required_binding": "an exact production rollback SQL SHA-256 in both the action-time record and authorization text",
|
||||||
|
"status": "not_generated"
|
||||||
|
},
|
||||||
|
"required_tier": "T6_authorized_production",
|
||||||
|
"safe_to_enter_apply_window": false,
|
||||||
|
"schema": "livingip.proposalApplyAuthorizationPacket.v1",
|
||||||
|
"scope_exclusions": {
|
||||||
|
"approval_gate_deployment_authorized": false,
|
||||||
|
"cloud_sql_exposure_authorized": false,
|
||||||
|
"gcp_promotion_authorized": false,
|
||||||
|
"telegram_send_authorized": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Proposal Apply Production Authorization Packet
|
||||||
|
|
||||||
|
Generated UTC: `2026-07-15T02:08:10.775963+00:00`
|
||||||
|
Required tier: `T6_authorized_production`
|
||||||
|
Current tier: `T3_live_readonly`
|
||||||
|
Safe to enter apply window: `False`
|
||||||
|
Production apply authorized: `False`
|
||||||
|
Production apply executed: `False`
|
||||||
|
|
||||||
|
## Exact Binding
|
||||||
|
|
||||||
|
- Proposal: `9b2b5fa4-0e0d-5db0-aa4d-6fd102889946` (`approve_claim`)
|
||||||
|
- Proposal payload SHA-256: `6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af`
|
||||||
|
- Apply payload SHA-256: `c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684`
|
||||||
|
- Immutable approval SHA-256: `310b54030e26ed43d7226f2e803610586e80d21ff064c67e11ea21e93ef40bd9`
|
||||||
|
- Destination: `teleo-pg/teleo`
|
||||||
|
- Destination system identifier: `7649789040005668902`
|
||||||
|
- Apply engine SHA-256: `ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26`
|
||||||
|
- Apply engine path: `scripts/apply_proposal.py`
|
||||||
|
- Apply SQL SHA-256: `1a7d65870e415ee8891b6dd84732a29bcee00412c10862d3d1db4b191a8ade38`
|
||||||
|
- Clone rehearsal rollback SQL SHA-256: `ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3`
|
||||||
|
- Production rollback status: `not_generated`
|
||||||
|
- Production rollback SQL SHA-256: `None`
|
||||||
|
- Approval gate prerequisite SHA-256: `bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa`
|
||||||
|
- Approval gate prerequisite path: `scripts/kb_apply_prereqs.sql`
|
||||||
|
- Binding SHA-256: `6f1984f3a69ad049d4bbcc408ed2542dc5f081806ec9baa9a35c8959e5dab62a`
|
||||||
|
|
||||||
|
## Production Preconditions
|
||||||
|
|
||||||
|
- `approval_gate_ready`: `False`
|
||||||
|
- `destination_readback_observed_at_utc`: `2026-07-15T02:07:27.133665+00:00`
|
||||||
|
- `destination_readback_was_read_only`: `True`
|
||||||
|
- `exact_production_rollback_packet_ready`: `False`
|
||||||
|
- `matching_candidate_count`: `0`
|
||||||
|
- `production_apply_authorization_present`: `False`
|
||||||
|
- `production_apply_executed`: `False`
|
||||||
|
- `strict_proposal_present_and_approved`: `False`
|
||||||
|
|
||||||
|
## Approval Gate Readback
|
||||||
|
|
||||||
|
- `apply_role_present`: `True`
|
||||||
|
- `approve_function_present`: `False`
|
||||||
|
- `assert_function_present`: `False`
|
||||||
|
- `finish_function_present`: `False`
|
||||||
|
- `immutable_approval_table_present`: `False`
|
||||||
|
- `review_role_present`: `False`
|
||||||
|
|
||||||
|
## Exact Action-Time Authorization Text
|
||||||
|
|
||||||
|
This text is not actionable until every production precondition above is true.
|
||||||
|
|
||||||
|
I authorize one production DB apply for proposal 9b2b5fa4-0e0d-5db0-aa4d-6fd102889946 with payload SHA-256 6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af to Docker container teleo-pg, database teleo, system identifier 7649789040005668902, binding SHA-256 6f1984f3a69ad049d4bbcc408ed2542dc5f081806ec9baa9a35c8959e5dab62a. I also authorize conditional production rollback SHA-256 <PRODUCTION_ROLLBACK_SQL_SHA256> 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 <OPERATOR_IDENTITY>; received <UTC>; expires <UTC>.
|
||||||
|
|
||||||
|
## Current Gate
|
||||||
|
|
||||||
|
deploy and independently verify the immutable approval gate before selecting a production proposal
|
||||||
|
|
||||||
|
The production approval-gate deployment is explicitly outside this apply packet and requires separate authorization.
|
||||||
|
|
||||||
|
## Claim Ceiling
|
||||||
|
|
||||||
|
T2 clone lifecycle plus T3 destination readback; no production apply authorization or execution
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"artifact": "proposal_apply_production_target_readback",
|
||||||
|
"schema": "livingip.proposalApplyProductionTargetReadback.v1",
|
||||||
|
"observed_at_utc": "2026-07-15T02:07:27.133665+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."
|
||||||
|
}
|
||||||
|
|
@ -14,6 +14,7 @@ dependencies = [
|
||||||
|
|
||||||
[project.optional-dependencies]
|
[project.optional-dependencies]
|
||||||
dev = [
|
dev = [
|
||||||
|
"hypothesis>=6,<7",
|
||||||
"pytest>=8",
|
"pytest>=8",
|
||||||
"pytest-asyncio>=0.23",
|
"pytest-asyncio>=0.23",
|
||||||
"ruff>=0.3",
|
"ruff>=0.3",
|
||||||
|
|
|
||||||
|
|
@ -111,8 +111,17 @@ CLAIM_TYPES = {"empirical", "structural", "normative", "meta", "concept"}
|
||||||
SOURCE_TYPES = {"paper", "article", "transcript", "observation", "dataset", "post", "dm", "other"}
|
SOURCE_TYPES = {"paper", "article", "transcript", "observation", "dataset", "post", "dm", "other"}
|
||||||
EVIDENCE_ROLES = {"grounds", "illustrates", "contradicts"}
|
EVIDENCE_ROLES = {"grounds", "illustrates", "contradicts"}
|
||||||
EDGE_TYPES = {
|
EDGE_TYPES = {
|
||||||
"supports", "challenges", "requires", "relates", "contradicts",
|
"supports",
|
||||||
"supersedes", "derives_from", "cites", "causes", "constrains", "accelerates",
|
"challenges",
|
||||||
|
"requires",
|
||||||
|
"relates",
|
||||||
|
"contradicts",
|
||||||
|
"supersedes",
|
||||||
|
"derives_from",
|
||||||
|
"cites",
|
||||||
|
"causes",
|
||||||
|
"constrains",
|
||||||
|
"accelerates",
|
||||||
}
|
}
|
||||||
|
|
||||||
MAX_BUNDLE_ROWS = {
|
MAX_BUNDLE_ROWS = {
|
||||||
|
|
@ -143,6 +152,21 @@ def _jsonb(value: Any) -> str:
|
||||||
return sql_literal(json.dumps(value, sort_keys=True)) + "::jsonb"
|
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:
|
def _text_array(values: list[str]) -> str:
|
||||||
if not values:
|
if not values:
|
||||||
return "'{}'::text[]"
|
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}")
|
raise ValueError(f"{path} contains unknown fields: {unknown}")
|
||||||
|
|
||||||
|
|
||||||
def _validate_approval_meta(
|
def _validate_approval_meta(approval: dict[str, Any], proposal_type: str, apply_payload: dict[str, Any]) -> None:
|
||||||
approval: dict[str, Any], proposal_type: str, apply_payload: dict[str, Any]
|
|
||||||
) -> None:
|
|
||||||
if not isinstance(approval, dict):
|
if not isinstance(approval, dict):
|
||||||
raise ValueError("approved proposal metadata is required")
|
raise ValueError("approved proposal metadata is required")
|
||||||
if approval.get("proposal_type") != proposal_type:
|
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:
|
def _approval_guard_sql(proposal_id: str, approval: dict[str, Any]) -> str:
|
||||||
return f"""select kb_stage.assert_approved_proposal(
|
return f"""select kb_stage.assert_approved_proposal(
|
||||||
{sql_literal(proposal_id)}::uuid,
|
{sql_literal(proposal_id)}::uuid,
|
||||||
{sql_literal(approval['proposal_type'])},
|
{sql_literal(approval["proposal_type"])},
|
||||||
{_jsonb(approval['payload'])},
|
{_jsonb(approval["payload"])},
|
||||||
{sql_literal(approval['reviewed_by_handle'])},
|
{sql_literal(approval["reviewed_by_handle"])},
|
||||||
{sql_literal(approval.get('reviewed_by_agent_id'))}::uuid,
|
{sql_literal(approval.get("reviewed_by_agent_id"))}::uuid,
|
||||||
{sql_literal(approval['reviewed_at'])}::timestamptz,
|
{sql_literal(approval["reviewed_at"])}::timestamptz,
|
||||||
{sql_literal(approval['review_note'])}
|
{sql_literal(approval["review_note"])}
|
||||||
);"""
|
);"""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -245,12 +267,12 @@ end
|
||||||
$verify$;"""
|
$verify$;"""
|
||||||
finish_sql = f"""select kb_stage.finish_approved_proposal(
|
finish_sql = f"""select kb_stage.finish_approved_proposal(
|
||||||
{sql_literal(proposal_id)}::uuid,
|
{sql_literal(proposal_id)}::uuid,
|
||||||
{sql_literal(approval['proposal_type'])},
|
{sql_literal(approval["proposal_type"])},
|
||||||
{_jsonb(approval['payload'])},
|
{_jsonb(approval["payload"])},
|
||||||
{sql_literal(approval['reviewed_by_handle'])},
|
{sql_literal(approval["reviewed_by_handle"])},
|
||||||
{sql_literal(approval.get('reviewed_by_agent_id'))}::uuid,
|
{sql_literal(approval.get("reviewed_by_agent_id"))}::uuid,
|
||||||
{sql_literal(approval['reviewed_at'])}::timestamptz,
|
{sql_literal(approval["reviewed_at"])}::timestamptz,
|
||||||
{sql_literal(approval['review_note'])},
|
{sql_literal(approval["review_note"])},
|
||||||
{sql_literal(applied_by)}
|
{sql_literal(applied_by)}
|
||||||
);"""
|
);"""
|
||||||
return checks_sql + "\n" + finish_sql
|
return checks_sql + "\n" + finish_sql
|
||||||
|
|
@ -301,9 +323,9 @@ update public.strategies
|
||||||
insert into public.strategies
|
insert into public.strategies
|
||||||
(agent_id, diagnosis, guiding_policy, proximate_objectives, version, active)
|
(agent_id, diagnosis, guiding_policy, proximate_objectives, version, active)
|
||||||
select {sql_literal(agent_id)}::uuid,
|
select {sql_literal(agent_id)}::uuid,
|
||||||
{sql_literal(strategy['diagnosis'])},
|
{sql_literal(strategy["diagnosis"])},
|
||||||
{sql_literal(strategy['guiding_policy'])},
|
{sql_literal(strategy["guiding_policy"])},
|
||||||
{_jsonb(strategy['proximate_objectives'])},
|
{_jsonb(strategy["proximate_objectives"])},
|
||||||
coalesce(max(version), 0) + 1,
|
coalesce(max(version), 0) + 1,
|
||||||
true
|
true
|
||||||
from public.strategies
|
from public.strategies
|
||||||
|
|
@ -325,9 +347,7 @@ values
|
||||||
raise exception 'apply_proposal: expected exactly one active strategy for agent %', {sql_literal(agent_id)};
|
raise exception 'apply_proposal: expected exactly one active strategy for agent %', {sql_literal(agent_id)};
|
||||||
end if;"""
|
end if;"""
|
||||||
|
|
||||||
ledger = _ledger_and_verify(
|
ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval)
|
||||||
proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval
|
|
||||||
)
|
|
||||||
return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, 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],
|
approval: dict[str, Any],
|
||||||
) -> str:
|
) -> str:
|
||||||
_validate_approval_meta(approval, "add_edge", apply_payload)
|
_validate_approval_meta(approval, "add_edge", apply_payload)
|
||||||
f = apply_payload.get("from_claim")
|
_reject_unknown(apply_payload, {"from_claim", "to_claim", "edge_type", "weight"}, "add_edge apply_payload")
|
||||||
t = apply_payload.get("to_claim")
|
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")
|
et = apply_payload.get("edge_type")
|
||||||
w = apply_payload.get("weight")
|
if et not in EDGE_TYPES:
|
||||||
if not (f and t and et):
|
raise ValueError(f"add_edge.edge_type must be one of {sorted(EDGE_TYPES)}")
|
||||||
raise ValueError("add_edge apply_payload requires 'from_claim', 'to_claim', 'edge_type'")
|
w = _weight(apply_payload.get("weight"), "add_edge.weight")
|
||||||
if f == t:
|
if f == t:
|
||||||
raise ValueError("add_edge from_claim and to_claim must differ")
|
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}"
|
edge_lock_key = f"claim_edge:{f}:{t}:{et}"
|
||||||
canonical = f"""
|
canonical = f"""
|
||||||
|
|
@ -354,8 +376,8 @@ def build_add_edge_sql(
|
||||||
select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0));
|
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 the edge unless an identical (from,to,type) edge already exists
|
||||||
insert into public.claim_edges (from_claim, to_claim, edge_type, weight)
|
insert into public.claim_edges (id, from_claim, to_claim, edge_type, weight)
|
||||||
select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid,
|
select {sql_literal(row_id)}::uuid, {sql_literal(f)}::uuid, {sql_literal(t)}::uuid,
|
||||||
{sql_literal(et)}::edge_type, {sql_literal(w)}
|
{sql_literal(et)}::edge_type, {sql_literal(w)}
|
||||||
where not exists (
|
where not exists (
|
||||||
select 1 from public.claim_edges
|
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);
|
and edge_type = {sql_literal(et)}::edge_type);
|
||||||
""".rstrip()
|
""".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
|
where from_claim = {sql_literal(f)}::uuid
|
||||||
and to_claim = {sql_literal(t)}::uuid
|
and to_claim = {sql_literal(t)}::uuid
|
||||||
and edge_type = {sql_literal(et)}::edge_type
|
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';
|
raise exception 'apply_proposal: claim_edge row does not match strict apply payload';
|
||||||
end if;"""
|
end if;"""
|
||||||
|
|
||||||
ledger = _ledger_and_verify(
|
ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval)
|
||||||
proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval
|
|
||||||
)
|
|
||||||
return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, 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],
|
approval: dict[str, Any],
|
||||||
) -> str:
|
) -> str:
|
||||||
_validate_approval_meta(approval, "attach_evidence", apply_payload)
|
_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 []
|
evidence: list[dict[str, Any]] = apply_payload.get("evidence") or []
|
||||||
if not evidence:
|
if not evidence:
|
||||||
raise ValueError("attach_evidence apply_payload requires non-empty 'evidence'")
|
raise ValueError("attach_evidence apply_payload requires non-empty 'evidence'")
|
||||||
|
|
@ -399,17 +428,17 @@ def build_attach_evidence_sql(
|
||||||
statements = []
|
statements = []
|
||||||
checks = []
|
checks = []
|
||||||
for i, ev in enumerate(evidence):
|
for i, ev in enumerate(evidence):
|
||||||
claim_id = ev.get("claim_id")
|
_reject_unknown(
|
||||||
source_id = ev.get("source_id")
|
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"
|
role = ev.get("role") or "grounds"
|
||||||
weight = ev.get("weight")
|
if role not in EVIDENCE_ROLES:
|
||||||
if not claim_id:
|
raise ValueError(f"attach_evidence.evidence[{i}].role must be one of {sorted(EVIDENCE_ROLES)}")
|
||||||
raise ValueError(f"evidence[{i}] requires 'claim_id'")
|
weight = _weight(ev.get("weight"), f"attach_evidence.evidence[{i}].weight")
|
||||||
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."
|
|
||||||
)
|
|
||||||
statements.append(
|
statements.append(
|
||||||
f"""insert into public.claim_evidence (claim_id, source_id, role, weight)
|
f"""insert into public.claim_evidence (claim_id, source_id, role, weight)
|
||||||
select {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid,
|
select {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid,
|
||||||
|
|
@ -417,7 +446,14 @@ select {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid,
|
||||||
on conflict (claim_id, source_id, role) do nothing;"""
|
on conflict (claim_id, source_id, role) do nothing;"""
|
||||||
)
|
)
|
||||||
checks.append(
|
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
|
where claim_id = {sql_literal(claim_id)}::uuid
|
||||||
and source_id = {sql_literal(source_id)}::uuid
|
and source_id = {sql_literal(source_id)}::uuid
|
||||||
and role = {sql_literal(role)}::evidence_role
|
and role = {sql_literal(role)}::evidence_role
|
||||||
|
|
@ -434,9 +470,7 @@ on conflict (claim_id, source_id, role) do nothing;"""
|
||||||
)
|
)
|
||||||
|
|
||||||
canonical = "\n".join(statements)
|
canonical = "\n".join(statements)
|
||||||
ledger = _ledger_and_verify(
|
ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval)
|
||||||
proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval
|
|
||||||
)
|
|
||||||
return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval))
|
return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval))
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -445,6 +479,9 @@ def build_approve_claim_sql(
|
||||||
proposal_id: str,
|
proposal_id: str,
|
||||||
applied_by: str | None,
|
applied_by: str | None,
|
||||||
approval: dict[str, Any],
|
approval: dict[str, Any],
|
||||||
|
*,
|
||||||
|
fresh_owned: bool = False,
|
||||||
|
fresh_preflight_sha256: str | None = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Build one conflict-safe transaction for a reviewed canonical graph bundle."""
|
"""Build one conflict-safe transaction for a reviewed canonical graph bundle."""
|
||||||
_validate_approval_meta(approval, "approve_claim", apply_payload)
|
_validate_approval_meta(approval, "approve_claim", apply_payload)
|
||||||
|
|
@ -509,12 +546,8 @@ def build_approve_claim_sql(
|
||||||
"status": status,
|
"status": status,
|
||||||
"confidence": _weight(row.get("confidence"), f"{path}.confidence"),
|
"confidence": _weight(row.get("confidence"), f"{path}.confidence"),
|
||||||
"tags": tags,
|
"tags": tags,
|
||||||
"created_by": _uuid(
|
"created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True),
|
||||||
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),
|
||||||
),
|
|
||||||
"superseded_by": _uuid(
|
|
||||||
row.get("superseded_by"), f"{path}.superseded_by", nullable=True
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -543,9 +576,7 @@ def build_approve_claim_sql(
|
||||||
"storage_path": row.get("storage_path"),
|
"storage_path": row.get("storage_path"),
|
||||||
"excerpt": row.get("excerpt"),
|
"excerpt": row.get("excerpt"),
|
||||||
"hash": source_hash,
|
"hash": source_hash,
|
||||||
"created_by": _uuid(
|
"created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True),
|
||||||
row.get("created_by", agent_id), f"{path}.created_by", nullable=True
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -566,9 +597,7 @@ def build_approve_claim_sql(
|
||||||
"source_id": _uuid(row.get("source_id"), f"{path}.source_id"),
|
"source_id": _uuid(row.get("source_id"), f"{path}.source_id"),
|
||||||
"role": role,
|
"role": role,
|
||||||
"weight": _weight(row.get("weight"), f"{path}.weight"),
|
"weight": _weight(row.get("weight"), f"{path}.weight"),
|
||||||
"created_by": _uuid(
|
"created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True),
|
||||||
row.get("created_by", agent_id), f"{path}.created_by", nullable=True
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -589,13 +618,12 @@ def build_approve_claim_sql(
|
||||||
raise ValueError(f"{path} cannot be a self-edge")
|
raise ValueError(f"{path} cannot be a self-edge")
|
||||||
edges.append(
|
edges.append(
|
||||||
{
|
{
|
||||||
|
"id": deterministic_row_uuid(proposal_id, "claim_edge", from_claim, to_claim, edge_type),
|
||||||
"from_claim": from_claim,
|
"from_claim": from_claim,
|
||||||
"to_claim": to_claim,
|
"to_claim": to_claim,
|
||||||
"edge_type": edge_type,
|
"edge_type": edge_type,
|
||||||
"weight": _weight(row.get("weight"), f"{path}.weight"),
|
"weight": _weight(row.get("weight"), f"{path}.weight"),
|
||||||
"created_by": _uuid(
|
"created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True),
|
||||||
row.get("created_by", agent_id), f"{path}.created_by", nullable=True
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -639,169 +667,212 @@ def build_approve_claim_sql(
|
||||||
)
|
)
|
||||||
_dedupe([row["id"] for row in reasoning_tools], "approve_claim.reasoning_tools ids")
|
_dedupe([row["id"] for row in reasoning_tools], "approve_claim.reasoning_tools ids")
|
||||||
|
|
||||||
|
if fresh_owned:
|
||||||
|
if (
|
||||||
|
not isinstance(fresh_preflight_sha256, str)
|
||||||
|
or len(fresh_preflight_sha256) != 64
|
||||||
|
or any(character not in "0123456789abcdef" for character in fresh_preflight_sha256)
|
||||||
|
):
|
||||||
|
raise ValueError("fresh-owned approve_claim requires an exact preflight SHA-256")
|
||||||
|
elif fresh_preflight_sha256 is not None:
|
||||||
|
raise ValueError("fresh preflight SHA-256 is only valid for fresh-owned approve_claim")
|
||||||
|
|
||||||
statements: list[str] = []
|
statements: list[str] = []
|
||||||
checks: list[str] = []
|
checks: list[str] = []
|
||||||
|
|
||||||
|
if fresh_owned:
|
||||||
|
statements.extend(
|
||||||
|
[
|
||||||
|
f"-- fresh-owned preflight SHA-256: {fresh_preflight_sha256}",
|
||||||
|
(
|
||||||
|
"select pg_advisory_xact_lock(hashtextextended("
|
||||||
|
f"'proposal-fresh-apply:' || {sql_literal(proposal_id)}, 0));"
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
if sources:
|
if sources:
|
||||||
collision_checks = []
|
collision_checks = []
|
||||||
for row in sources:
|
for row in sources:
|
||||||
collision_checks.append(
|
collision_checks.append(
|
||||||
f""" if exists (select 1 from public.sources
|
f""" if exists (select 1 from public.sources
|
||||||
where hash = {sql_literal(row['hash'])}
|
where hash = {sql_literal(row["hash"])}
|
||||||
and id <> {sql_literal(row['id'])}::uuid) then
|
and id <> {sql_literal(row["id"])}::uuid) then
|
||||||
raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])};
|
raise exception 'approve_claim: source hash collision for source %', {sql_literal(row["id"])};
|
||||||
end if;"""
|
end if;"""
|
||||||
)
|
)
|
||||||
statements.append(
|
statements.append("do $source_conflicts$\nbegin\n" + "\n".join(collision_checks) + "\nend\n$source_conflicts$;")
|
||||||
"do $source_conflicts$\nbegin\n"
|
|
||||||
+ "\n".join(collision_checks)
|
|
||||||
+ "\nend\n$source_conflicts$;"
|
|
||||||
)
|
|
||||||
|
|
||||||
for row in claims:
|
for row in claims:
|
||||||
tags = _text_array(row["tags"])
|
tags = _text_array(row["tags"])
|
||||||
|
conflict_clause = ";" if fresh_owned else "\non conflict (id) do nothing;"
|
||||||
statements.append(
|
statements.append(
|
||||||
f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by)
|
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'])},
|
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["status"])}, {sql_literal(row["confidence"])}, {tags},
|
||||||
{sql_literal(row['created_by'])}::uuid, {sql_literal(row['superseded_by'])}::uuid
|
{sql_literal(row["created_by"])}::uuid, {sql_literal(row["superseded_by"])}::uuid{conflict_clause}"""
|
||||||
on conflict (id) do nothing;"""
|
|
||||||
)
|
)
|
||||||
checks.append(
|
checks.append(
|
||||||
f""" if not exists (select 1 from public.claims
|
f""" if not exists (select 1 from public.claims
|
||||||
where id = {sql_literal(row['id'])}::uuid
|
where id = {sql_literal(row["id"])}::uuid
|
||||||
and type = {sql_literal(row['type'])}
|
and type = {sql_literal(row["type"])}
|
||||||
and text = {sql_literal(row['text'])}
|
and text = {sql_literal(row["text"])}
|
||||||
and status = {sql_literal(row['status'])}
|
and status = {sql_literal(row["status"])}
|
||||||
and confidence is not distinct from {sql_literal(row['confidence'])}
|
and confidence is not distinct from {sql_literal(row["confidence"])}
|
||||||
and tags is not distinct from {tags}
|
and tags is not distinct from {tags}
|
||||||
and created_by is not distinct from {sql_literal(row['created_by'])}::uuid
|
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
|
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'])};
|
raise exception 'approve_claim: claim row does not match strict apply payload: %', {sql_literal(row["id"])};
|
||||||
end if;"""
|
end if;"""
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in sources:
|
for row in sources:
|
||||||
|
conflict_clause = ";" if fresh_owned else "\non conflict do nothing;"
|
||||||
statements.append(
|
statements.append(
|
||||||
f"""insert into public.sources (id, source_type, url, storage_path, excerpt, hash, created_by)
|
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'])},
|
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["storage_path"])}, {sql_literal(row["excerpt"])}, {sql_literal(row["hash"])},
|
||||||
{sql_literal(row['created_by'])}::uuid
|
{sql_literal(row["created_by"])}::uuid{conflict_clause}"""
|
||||||
on conflict do nothing;"""
|
|
||||||
)
|
)
|
||||||
checks.append(
|
checks.append(
|
||||||
f""" if exists (select 1 from public.sources
|
f""" if exists (select 1 from public.sources
|
||||||
where hash = {sql_literal(row['hash'])}
|
where hash = {sql_literal(row["hash"])}
|
||||||
and id <> {sql_literal(row['id'])}::uuid) then
|
and id <> {sql_literal(row["id"])}::uuid) then
|
||||||
raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])};
|
raise exception 'approve_claim: source hash collision for source %', {sql_literal(row["id"])};
|
||||||
end if;
|
end if;
|
||||||
if not exists (select 1 from public.sources
|
if not exists (select 1 from public.sources
|
||||||
where id = {sql_literal(row['id'])}::uuid
|
where id = {sql_literal(row["id"])}::uuid
|
||||||
and source_type = {sql_literal(row['source_type'])}
|
and source_type = {sql_literal(row["source_type"])}
|
||||||
and url is not distinct from {sql_literal(row['url'])}
|
and url is not distinct from {sql_literal(row["url"])}
|
||||||
and storage_path is not distinct from {sql_literal(row['storage_path'])}
|
and storage_path is not distinct from {sql_literal(row["storage_path"])}
|
||||||
and excerpt is not distinct from {sql_literal(row['excerpt'])}
|
and excerpt is not distinct from {sql_literal(row["excerpt"])}
|
||||||
and hash = {sql_literal(row['hash'])}
|
and hash = {sql_literal(row["hash"])}
|
||||||
and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then
|
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'])};
|
raise exception 'approve_claim: source row does not match strict apply payload: %', {sql_literal(row["id"])};
|
||||||
end if;"""
|
end if;"""
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in reasoning_tools:
|
for row in reasoning_tools:
|
||||||
|
conflict_clause = ";" if fresh_owned else "\non conflict (id) do nothing;"
|
||||||
statements.append(
|
statements.append(
|
||||||
f"""insert into public.reasoning_tools (id, agent_id, name, description, category)
|
f"""insert into public.reasoning_tools (id, agent_id, name, description, category)
|
||||||
select {sql_literal(row['id'])}::uuid, {sql_literal(row['agent_id'])}::uuid,
|
select {sql_literal(row["id"])}::uuid, {sql_literal(row["agent_id"])}::uuid,
|
||||||
{sql_literal(row['name'])}, {sql_literal(row['description'])}, {sql_literal(row['category'])}
|
{sql_literal(row["name"])}, {sql_literal(row["description"])}, {sql_literal(row["category"])}{conflict_clause}"""
|
||||||
on conflict (id) do nothing;"""
|
|
||||||
)
|
)
|
||||||
checks.append(
|
checks.append(
|
||||||
f""" if not exists (select 1 from public.reasoning_tools
|
f""" if not exists (select 1 from public.reasoning_tools
|
||||||
where id = {sql_literal(row['id'])}::uuid
|
where id = {sql_literal(row["id"])}::uuid
|
||||||
and agent_id is not distinct from {sql_literal(row['agent_id'])}::uuid
|
and agent_id is not distinct from {sql_literal(row["agent_id"])}::uuid
|
||||||
and name = {sql_literal(row['name'])}
|
and name = {sql_literal(row["name"])}
|
||||||
and description = {sql_literal(row['description'])}
|
and description = {sql_literal(row["description"])}
|
||||||
and category is not distinct from {sql_literal(row['category'])}) then
|
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'])};
|
raise exception 'approve_claim: reasoning tool row does not match strict apply payload: %', {sql_literal(row["id"])};
|
||||||
end if;"""
|
end if;"""
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in evidence:
|
for row in evidence:
|
||||||
|
conflict_clause = ";" if fresh_owned else "\non conflict (claim_id, source_id, role) do nothing;"
|
||||||
statements.append(
|
statements.append(
|
||||||
f"""insert into public.claim_evidence (claim_id, source_id, role, weight, created_by)
|
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,
|
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["role"])}::evidence_role, {sql_literal(row["weight"])},
|
||||||
{sql_literal(row['created_by'])}::uuid
|
{sql_literal(row["created_by"])}::uuid{conflict_clause}"""
|
||||||
on conflict (claim_id, source_id, role) do nothing;"""
|
|
||||||
)
|
)
|
||||||
checks.append(
|
checks.append(
|
||||||
f""" if exists (select 1 from public.claim_evidence
|
f""" -- Accept a legacy receipt id only when exactly one semantic row matches.
|
||||||
where claim_id = {sql_literal(row['claim_id'])}::uuid
|
if (select count(*) from public.claim_evidence
|
||||||
and source_id = {sql_literal(row['source_id'])}::uuid
|
where claim_id = {sql_literal(row["claim_id"])}::uuid
|
||||||
and role = {sql_literal(row['role'])}::evidence_role
|
and source_id = {sql_literal(row["source_id"])}::uuid
|
||||||
and (weight is distinct from {sql_literal(row['weight'])}
|
and role = {sql_literal(row["role"])}::evidence_role) <> 1 then
|
||||||
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 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';
|
raise exception 'approve_claim: evidence row does not match strict apply payload';
|
||||||
end if;
|
end if;
|
||||||
if not exists (select 1 from public.claim_evidence
|
if not exists (select 1 from public.claim_evidence
|
||||||
where claim_id = {sql_literal(row['claim_id'])}::uuid
|
where claim_id = {sql_literal(row["claim_id"])}::uuid
|
||||||
and source_id = {sql_literal(row['source_id'])}::uuid
|
and source_id = {sql_literal(row["source_id"])}::uuid
|
||||||
and role = {sql_literal(row['role'])}::evidence_role
|
and role = {sql_literal(row["role"])}::evidence_role
|
||||||
and weight is not distinct from {sql_literal(row['weight'])}
|
and weight is not distinct from {sql_literal(row["weight"])}
|
||||||
and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then
|
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';
|
raise exception 'approve_claim: evidence row does not match strict apply payload';
|
||||||
end if;"""
|
end if;"""
|
||||||
)
|
)
|
||||||
|
|
||||||
for row in edges:
|
for row in edges:
|
||||||
edge_lock_key = (
|
edge_lock_key = f"claim_edge:{row['from_claim']}:{row['to_claim']}:{row['edge_type']}"
|
||||||
f"claim_edge:{row['from_claim']}:{row['to_claim']}:{row['edge_type']}"
|
edge_insert_guard = (
|
||||||
|
";"
|
||||||
|
if fresh_owned
|
||||||
|
else f"""\n 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);"""
|
||||||
)
|
)
|
||||||
statements.append(
|
statements.append(
|
||||||
f"""select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0));
|
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)
|
insert into public.claim_edges (id, from_claim, to_claim, edge_type, weight, created_by)
|
||||||
select {sql_literal(row['from_claim'])}::uuid, {sql_literal(row['to_claim'])}::uuid,
|
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["edge_type"])}::edge_type, {sql_literal(row["weight"])},
|
||||||
{sql_literal(row['created_by'])}::uuid
|
{sql_literal(row["created_by"])}::uuid{edge_insert_guard}"""
|
||||||
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);"""
|
|
||||||
)
|
)
|
||||||
checks.append(
|
checks.append(
|
||||||
f""" if exists (select 1 from public.claim_edges
|
f""" -- Accept a legacy receipt id only when exactly one semantic row matches.
|
||||||
where from_claim = {sql_literal(row['from_claim'])}::uuid
|
if (select count(*) from public.claim_edges
|
||||||
and to_claim = {sql_literal(row['to_claim'])}::uuid
|
where from_claim = {sql_literal(row["from_claim"])}::uuid
|
||||||
and edge_type = {sql_literal(row['edge_type'])}::edge_type
|
and to_claim = {sql_literal(row["to_claim"])}::uuid
|
||||||
and (weight is distinct from {sql_literal(row['weight'])}
|
and edge_type = {sql_literal(row["edge_type"])}::edge_type) <> 1 then
|
||||||
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 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';
|
raise exception 'approve_claim: edge row does not match strict apply payload';
|
||||||
end if;
|
end if;
|
||||||
if not exists (select 1 from public.claim_edges
|
if not exists (select 1 from public.claim_edges
|
||||||
where from_claim = {sql_literal(row['from_claim'])}::uuid
|
where from_claim = {sql_literal(row["from_claim"])}::uuid
|
||||||
and to_claim = {sql_literal(row['to_claim'])}::uuid
|
and to_claim = {sql_literal(row["to_claim"])}::uuid
|
||||||
and edge_type = {sql_literal(row['edge_type'])}::edge_type
|
and edge_type = {sql_literal(row["edge_type"])}::edge_type
|
||||||
and weight is not distinct from {sql_literal(row['weight'])}
|
and weight is not distinct from {sql_literal(row["weight"])}
|
||||||
and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then
|
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';
|
raise exception 'approve_claim: edge row does not match strict apply payload';
|
||||||
end if;"""
|
end if;"""
|
||||||
)
|
)
|
||||||
|
|
||||||
canonical = "\n\n".join(statements)
|
canonical = "\n\n".join(statements)
|
||||||
ledger = _ledger_and_verify(
|
ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval)
|
||||||
proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval
|
return _wrap_txn(
|
||||||
|
canonical,
|
||||||
|
ledger,
|
||||||
|
_approval_guard_sql(proposal_id, approval),
|
||||||
|
serializable=fresh_owned,
|
||||||
)
|
)
|
||||||
return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval))
|
|
||||||
|
|
||||||
|
|
||||||
def _wrap_txn(canonical_sql: str, ledger_sql: str, approval_guard_sql: str) -> str:
|
def _wrap_txn(
|
||||||
|
canonical_sql: str,
|
||||||
|
ledger_sql: str,
|
||||||
|
approval_guard_sql: str,
|
||||||
|
*,
|
||||||
|
serializable: bool = False,
|
||||||
|
) -> str:
|
||||||
return (
|
return (
|
||||||
"begin;\n"
|
"begin;\n"
|
||||||
"set local standard_conforming_strings = on;\n"
|
+ ("set transaction isolation level serializable;\n" if serializable else "")
|
||||||
f"{approval_guard_sql}\n"
|
+ "set local standard_conforming_strings = on;\n"
|
||||||
f"{canonical_sql}\n"
|
+ f"{approval_guard_sql}\n"
|
||||||
f"{ledger_sql}\n"
|
+ f"{canonical_sql}\n"
|
||||||
"commit;\n"
|
+ f"{ledger_sql}\n"
|
||||||
|
+ "commit;\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -813,7 +884,13 @@ BUILDERS = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def build_apply_sql(proposal: dict[str, Any], applied_by: str | None) -> str:
|
def build_apply_sql(
|
||||||
|
proposal: dict[str, Any],
|
||||||
|
applied_by: str | None,
|
||||||
|
*,
|
||||||
|
fresh_owned: bool = False,
|
||||||
|
fresh_preflight_sha256: str | None = None,
|
||||||
|
) -> str:
|
||||||
ptype = proposal["proposal_type"]
|
ptype = proposal["proposal_type"]
|
||||||
if ptype not in BUILDERS:
|
if ptype not in BUILDERS:
|
||||||
raise ValueError(f"apply_proposal cannot apply proposal_type {ptype!r}")
|
raise ValueError(f"apply_proposal cannot apply proposal_type {ptype!r}")
|
||||||
|
|
@ -824,6 +901,17 @@ def build_apply_sql(proposal: dict[str, Any], applied_by: str | None) -> str:
|
||||||
"proposal payload has no 'apply_payload' strict contract. "
|
"proposal payload has no 'apply_payload' strict contract. "
|
||||||
"Run the normalizer to produce apply_payload before applying."
|
"Run the normalizer to produce apply_payload before applying."
|
||||||
)
|
)
|
||||||
|
if fresh_owned and ptype != "approve_claim":
|
||||||
|
raise ValueError("fresh-owned apply is supported only for approve_claim")
|
||||||
|
if ptype == "approve_claim":
|
||||||
|
return build_approve_claim_sql(
|
||||||
|
apply_payload,
|
||||||
|
proposal["id"],
|
||||||
|
applied_by or SERVICE_AGENT_HANDLE,
|
||||||
|
proposal,
|
||||||
|
fresh_owned=fresh_owned,
|
||||||
|
fresh_preflight_sha256=fresh_preflight_sha256,
|
||||||
|
)
|
||||||
return BUILDERS[ptype](
|
return BUILDERS[ptype](
|
||||||
apply_payload,
|
apply_payload,
|
||||||
proposal["id"],
|
proposal["id"],
|
||||||
|
|
@ -837,9 +925,7 @@ def assert_applyable(proposal: dict[str, Any]) -> None:
|
||||||
if status == "applied":
|
if status == "applied":
|
||||||
raise SystemExit(f"proposal {proposal['id']} is already applied (idempotent no-op)")
|
raise SystemExit(f"proposal {proposal['id']} is already applied (idempotent no-op)")
|
||||||
if status != "approved":
|
if status != "approved":
|
||||||
raise SystemExit(
|
raise SystemExit(f"proposal {proposal['id']} has status {status!r}; only 'approved' proposals apply")
|
||||||
f"proposal {proposal['id']} has status {status!r}; only 'approved' proposals apply"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def assert_receiptable(proposal: dict[str, Any]) -> None:
|
def assert_receiptable(proposal: dict[str, Any]) -> None:
|
||||||
|
|
@ -877,23 +963,36 @@ def run_psql(
|
||||||
) -> str:
|
) -> str:
|
||||||
docker_binary = shutil.which("docker") or "docker"
|
docker_binary = shutil.which("docker") or "docker"
|
||||||
command = [
|
command = [
|
||||||
docker_binary, "exec", "-e", "PGPASSWORD", "-i", args.container,
|
docker_binary,
|
||||||
"psql", "-U", args.role, "-h", args.host, "-d", args.db,
|
"exec",
|
||||||
"-v", "ON_ERROR_STOP=1", "-At", "-q",
|
"-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(
|
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"},
|
env={"PGPASSWORD": password, "PATH": "/usr/bin:/bin:/usr/local/bin"},
|
||||||
check=False,
|
check=False,
|
||||||
)
|
)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
if redact_output_on_error:
|
if redact_output_on_error:
|
||||||
raise SystemExit(
|
raise SystemExit(f"psql failed ({result.returncode}); private postflight output redacted")
|
||||||
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})\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
|
|
||||||
)
|
|
||||||
return result.stdout
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -940,7 +1039,7 @@ def capture_replay_receipt(
|
||||||
raise RuntimeError("postflight query did not return a JSON object")
|
raise RuntimeError("postflight query did not return a JSON object")
|
||||||
apply_sql_source = "exact_executed_sql"
|
apply_sql_source = "exact_executed_sql"
|
||||||
if apply_sql is None:
|
if apply_sql is None:
|
||||||
apply_sql = build_apply_sql(proposal, proposal.get("applied_by_handle"))
|
apply_sql = build_apply_sql_for_args(proposal, args, proposal.get("applied_by_handle"))
|
||||||
apply_sql_source = "reconstructed_current_engine"
|
apply_sql_source = "reconstructed_current_engine"
|
||||||
receipt = replay_receipt.build_replay_receipt(
|
receipt = replay_receipt.build_replay_receipt(
|
||||||
proposal,
|
proposal,
|
||||||
|
|
@ -972,6 +1071,14 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
p.add_argument("--db", default=DEFAULT_DB)
|
p.add_argument("--db", default=DEFAULT_DB)
|
||||||
p.add_argument("--host", default=DEFAULT_HOST)
|
p.add_argument("--host", default=DEFAULT_HOST)
|
||||||
p.add_argument("--role", default=DEFAULT_ROLE)
|
p.add_argument("--role", default=DEFAULT_ROLE)
|
||||||
|
p.add_argument(
|
||||||
|
"--fresh-preflight",
|
||||||
|
type=Path,
|
||||||
|
help=(
|
||||||
|
"exact proposal_apply_fresh_preflight JSON; makes approve_claim use strict fresh-owned inserts "
|
||||||
|
"bound to the timestamped preflight"
|
||||||
|
),
|
||||||
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--receipt-only",
|
"--receipt-only",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
|
|
@ -980,8 +1087,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--receipt-dir",
|
"--receipt-dir",
|
||||||
default=None,
|
default=None,
|
||||||
help="private receipt directory (default: KB_APPLY_RECEIPT_DIR or "
|
help=f"private receipt directory (default: KB_APPLY_RECEIPT_DIR or {replay_receipt.DEFAULT_RECEIPT_DIR})",
|
||||||
f"{replay_receipt.DEFAULT_RECEIPT_DIR})",
|
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--receipt-out",
|
"--receipt-out",
|
||||||
|
|
@ -994,6 +1100,28 @@ def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
return args
|
return args
|
||||||
|
|
||||||
|
|
||||||
|
def build_apply_sql_for_args(
|
||||||
|
proposal: dict[str, Any],
|
||||||
|
args: argparse.Namespace,
|
||||||
|
applied_by: str | None,
|
||||||
|
) -> str:
|
||||||
|
fresh_preflight_path = getattr(args, "fresh_preflight", None)
|
||||||
|
if fresh_preflight_path is None:
|
||||||
|
return build_apply_sql(proposal, applied_by)
|
||||||
|
if not fresh_preflight_path.is_file():
|
||||||
|
raise SystemExit(f"fresh preflight file not found: {fresh_preflight_path}")
|
||||||
|
try:
|
||||||
|
preflight = json.loads(fresh_preflight_path.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, json.JSONDecodeError) as exc:
|
||||||
|
raise SystemExit(f"fresh preflight is unreadable: {fresh_preflight_path}") from exc
|
||||||
|
import proposal_apply_lifecycle as lifecycle
|
||||||
|
|
||||||
|
try:
|
||||||
|
return lifecycle.build_fresh_owned_apply_sql(proposal, preflight, applied_by)
|
||||||
|
except ValueError as exc:
|
||||||
|
raise SystemExit(f"fresh preflight refused: {exc}") from exc
|
||||||
|
|
||||||
|
|
||||||
def main(argv: list[str] | None = None) -> int:
|
def main(argv: list[str] | None = None) -> int:
|
||||||
args = parse_args(sys.argv[1:] if argv is None else argv)
|
args = parse_args(sys.argv[1:] if argv is None else argv)
|
||||||
|
|
||||||
|
|
@ -1020,13 +1148,13 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
password = load_password(args.secrets_file)
|
password = load_password(args.secrets_file)
|
||||||
proposal = load_proposal(args, password)
|
proposal = load_proposal(args, password)
|
||||||
assert_applyable(proposal)
|
assert_applyable(proposal)
|
||||||
print(build_apply_sql(proposal, args.applied_by))
|
print(build_apply_sql_for_args(proposal, args, args.applied_by))
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
password = load_password(args.secrets_file)
|
password = load_password(args.secrets_file)
|
||||||
proposal = load_proposal(args, password)
|
proposal = load_proposal(args, password)
|
||||||
assert_applyable(proposal)
|
assert_applyable(proposal)
|
||||||
sql = build_apply_sql(proposal, args.applied_by)
|
sql = build_apply_sql_for_args(proposal, args, args.applied_by)
|
||||||
run_psql(args, sql, password)
|
run_psql(args, sql, password)
|
||||||
applied_proposal = load_proposal(args, password)
|
applied_proposal = load_proposal(args, password)
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ from __future__ import annotations
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
|
import stat
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
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.
|
# bundles and evidence/edge applies do not trigger a generic identity render.
|
||||||
RENDER_TYPES = ("revise_strategy",)
|
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) #
|
# 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)]}
|
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 #
|
# Side-effecting steps #
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
def _psql_args(args: argparse.Namespace) -> argparse.Namespace:
|
def _psql_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||||
"""Namespace shaped for ap.run_psql (reuses the kb_apply connection path)."""
|
"""Namespace shaped for ap.run_psql (reuses the kb_apply connection path)."""
|
||||||
return argparse.Namespace(
|
return argparse.Namespace(container=args.container, db=args.db, host=args.host, role=args.role)
|
||||||
container=args.container, db=args.db, host=args.host, role=args.role
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def fetch_candidates(
|
def fetch_candidates(
|
||||||
|
|
@ -181,20 +244,73 @@ def fetch_candidates(
|
||||||
|
|
||||||
|
|
||||||
def apply_one(args: argparse.Namespace, proposal_id: str) -> None:
|
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 = [
|
cmd = [
|
||||||
sys.executable, str(args.apply_script), proposal_id,
|
sys.executable,
|
||||||
"--applied-by", args.applied_by,
|
str(args.apply_script),
|
||||||
"--secrets-file", args.secrets_file,
|
proposal_id,
|
||||||
"--container", args.container, "--db", args.db,
|
"--applied-by",
|
||||||
"--host", args.host, "--role", args.role,
|
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)
|
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(
|
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:
|
def render_one(args: argparse.Namespace, agent_id: str | None) -> str:
|
||||||
cmd = build_render_command(args.render_cmd, agent_id)
|
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)"
|
return "render skipped (no render-cmd configured; PR2 renderer not deployed)"
|
||||||
result = subprocess.run(cmd, text=True, capture_output=True, check=False)
|
result = subprocess.run(cmd, text=True, capture_output=True, check=False)
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
raise RuntimeError(
|
raise RuntimeError(f"render failed for agent {agent_id}: {result.stdout.strip()} {result.stderr.strip()}")
|
||||||
f"render failed for agent {agent_id}: {result.stdout.strip()} {result.stderr.strip()}"
|
|
||||||
)
|
|
||||||
return f"rendered agent {agent_id}"
|
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:
|
def run(args: argparse.Namespace) -> int:
|
||||||
password = ap.load_password(args.secrets_file)
|
password = ap.load_password(args.secrets_file)
|
||||||
failure_counts = load_failure_state(args.failure_state_file)
|
failure_counts = load_failure_state(args.failure_state_file)
|
||||||
exhausted_ids = tuple(
|
exhausted_ids = tuple(proposal_id for proposal_id, count in failure_counts.items() if count >= args.max_attempts)
|
||||||
proposal_id
|
|
||||||
for proposal_id, count in failure_counts.items()
|
|
||||||
if count >= args.max_attempts
|
|
||||||
)
|
|
||||||
candidates = fetch_candidates(args, password, exhausted_ids)
|
candidates = fetch_candidates(args, password, exhausted_ids)
|
||||||
|
|
||||||
for proposal_id in exhausted_ids:
|
for proposal_id in exhausted_ids:
|
||||||
|
|
@ -263,6 +373,15 @@ def run(args: argparse.Namespace) -> int:
|
||||||
print(f"applied {pid} ({ptype})")
|
print(f"applied {pid} ({ptype})")
|
||||||
if ptype in RENDER_TYPES:
|
if ptype in RENDER_TYPES:
|
||||||
print(" " + render_one(args, agent_id))
|
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:
|
except Exception as exc:
|
||||||
failures += 1
|
failures += 1
|
||||||
# Leave the proposal at 'approved' so a fixed one reapplies next tick
|
# 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
|
# so a deterministically-failing proposal hits the ceiling instead of
|
||||||
# retrying forever. Surface loudly for the operator.
|
# retrying forever. Surface loudly for the operator.
|
||||||
failure_counts[pid] = failure_counts.get(pid, 0) + 1
|
failure_counts[pid] = failure_counts.get(pid, 0) + 1
|
||||||
print(f"ERROR applying {pid} ({ptype}) [attempt {failure_counts[pid]}/"
|
print(
|
||||||
f"{args.max_attempts}]: {exc}", file=sys.stderr)
|
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)
|
save_failure_state(args.failure_state_file, failure_counts)
|
||||||
return 1 if failures else 0
|
return 1 if failures else 0
|
||||||
|
|
||||||
|
|
||||||
def parse_args(argv: list[str]) -> argparse.Namespace:
|
def parse_args(argv: list[str]) -> argparse.Namespace:
|
||||||
p = argparse.ArgumentParser(
|
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter
|
|
||||||
)
|
|
||||||
p.add_argument(
|
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.",
|
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("--limit", type=int, default=20, help="max candidates fetched per tick")
|
||||||
p.add_argument(
|
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 "
|
help="max proposals actually APPLIED per tick (default 1: applies land "
|
||||||
"one-at-a-time and observably, not a whole-queue drain)",
|
"one-at-a-time and observably, not a whole-queue drain)",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
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 "
|
help="consecutive apply failures before a proposal is treated as a "
|
||||||
"poison pill and skipped (needs a human fix, not endless retries)",
|
"poison pill and skipped (needs a human fix, not endless retries)",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
p.add_argument(
|
||||||
"--failure-state-file",
|
"--failure-state-file",
|
||||||
default=os.environ.get("KB_APPLY_WORKER_STATE", "/opt/teleo-eval/logs/kb-apply-worker-failures.json"),
|
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 "
|
help="persisted per-proposal failure counts (survives oneshot ticks so the poison-pill ceiling actually bites)",
|
||||||
"the poison-pill ceiling actually bites)",
|
|
||||||
)
|
)
|
||||||
p.add_argument(
|
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)",
|
help="handle recorded as applied_by (default: the kb-apply service agent)",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
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)",
|
help="path to the apply_proposal.py engine (the sole apply path)",
|
||||||
)
|
)
|
||||||
p.add_argument(
|
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 "
|
||||||
|
"<proposal-id>.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}'. "
|
help="render hook template, e.g. 'python3 render_soul.py --agent-id {agent_id}'. "
|
||||||
"Empty (default) skips render until the SOUL renderer is deployed.",
|
"Empty (default) skips render until the SOUL renderer is deployed.",
|
||||||
)
|
)
|
||||||
|
|
|
||||||
230
scripts/finalize_approve_claim_isolated_canary.py
Normal file
230
scripts/finalize_approve_claim_isolated_canary.py
Normal file
|
|
@ -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())
|
||||||
|
|
@ -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(
|
def resolve_receipt_path(
|
||||||
proposal_id: str,
|
proposal_id: str,
|
||||||
*,
|
*,
|
||||||
|
|
|
||||||
1821
scripts/proposal_apply_lifecycle.py
Normal file
1821
scripts/proposal_apply_lifecycle.py
Normal file
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -9,6 +9,8 @@ image="${POSTGRES_CANARY_IMAGE:-postgres:16}"
|
||||||
source_tier="${SOURCE_TIER:-live-readonly}"
|
source_tier="${SOURCE_TIER:-live-readonly}"
|
||||||
normalization_json=""
|
normalization_json=""
|
||||||
output="${repo_root}/docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.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
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
|
|
@ -20,6 +22,14 @@ while [[ $# -gt 0 ]]; do
|
||||||
output="$2"
|
output="$2"
|
||||||
shift 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)
|
||||||
source_container="$2"
|
source_container="$2"
|
||||||
shift 2
|
shift 2
|
||||||
|
|
@ -99,11 +109,19 @@ service_state() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mkdir -p "$workdir" "$(dirname "$output")"
|
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)"
|
source_counts_before="$(source_counts)"
|
||||||
service_before="$(service_state)"
|
service_before="$(service_state)"
|
||||||
|
|
||||||
docker run -d --name "$container" \
|
docker run -d --name "$container" \
|
||||||
--network none \
|
--network none \
|
||||||
|
--label livingip.canary=proposal-apply-lifecycle \
|
||||||
|
--label "livingip.canary.instance=${container}" \
|
||||||
--tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m \
|
--tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m \
|
||||||
-e POSTGRES_PASSWORD="isolated-postgres-${RANDOM}-${RANDOM}" \
|
-e POSTGRES_PASSWORD="isolated-postgres-${RANDOM}-${RANDOM}" \
|
||||||
-e POSTGRES_DB=teleo \
|
-e POSTGRES_DB=teleo \
|
||||||
|
|
@ -127,6 +145,12 @@ if [[ "$ready_streak" -lt 2 ]]; then
|
||||||
fi
|
fi
|
||||||
container_network_mode="$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$container")"
|
container_network_mode="$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$container")"
|
||||||
container_mounts_json="$(docker inspect -f '{{json .Mounts}}' "$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" \
|
docker exec "$source_container" pg_dump -U postgres -d "$source_db" \
|
||||||
--schema-only --no-owner --no-privileges >"$workdir/schema.sql"
|
--schema-only --no-owner --no-privileges >"$workdir/schema.sql"
|
||||||
|
|
@ -165,6 +189,13 @@ normalization_args=()
|
||||||
if [[ -n "$normalization_json" ]]; then
|
if [[ -n "$normalization_json" ]]; then
|
||||||
normalization_args=(--normalization-json "$normalization_json")
|
normalization_args=(--normalization-json "$normalization_json")
|
||||||
fi
|
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
|
set +e
|
||||||
python3 "$repo_root/scripts/run_approve_claim_clone_canary.py" \
|
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" \
|
--review-secrets-file "$workdir/kb-review.env" \
|
||||||
--secrets-file "$workdir/kb-apply.env" \
|
--secrets-file "$workdir/kb-apply.env" \
|
||||||
"${normalization_args[@]}" \
|
"${normalization_args[@]}" \
|
||||||
|
"${artifact_args[@]}" \
|
||||||
--output "$output" \
|
--output "$output" \
|
||||||
>"$workdir/stdout.json" 2>"$workdir/stderr.log"
|
>"$workdir/stdout.json" 2>"$workdir/stderr.log"
|
||||||
canary_rc=$?
|
canary_rc=$?
|
||||||
|
|
@ -192,6 +224,20 @@ container_name_readback="$(
|
||||||
docker container ls -a --filter "name=^/${container}$" --format '{{.Names}}' 2>&1
|
docker container ls -a --filter "name=^/${container}$" --format '{{.Names}}' 2>&1
|
||||||
)"
|
)"
|
||||||
container_name_readback_rc=$?
|
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
|
if [[ -e "$workdir" || -L "$workdir" ]]; then
|
||||||
outer_workdir_lexists=true
|
outer_workdir_lexists=true
|
||||||
else
|
else
|
||||||
|
|
@ -214,6 +260,9 @@ OUTER_CONTAINER_REMOVE_RC="$outer_container_remove_rc" \
|
||||||
OUTER_WORKDIR_REMOVE_RC="$outer_workdir_remove_rc" \
|
OUTER_WORKDIR_REMOVE_RC="$outer_workdir_remove_rc" \
|
||||||
CONTAINER_NAME_READBACK="$container_name_readback" \
|
CONTAINER_NAME_READBACK="$container_name_readback" \
|
||||||
CONTAINER_NAME_READBACK_RC="$container_name_readback_rc" \
|
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" \
|
OUTER_WORKDIR_LEXISTS="$outer_workdir_lexists" \
|
||||||
CANARY_RC="$canary_rc" \
|
CANARY_RC="$canary_rc" \
|
||||||
SOURCE_TIER="$source_tier" \
|
SOURCE_TIER="$source_tier" \
|
||||||
|
|
@ -221,219 +270,6 @@ SOURCE_NETWORK_MODE="$source_network_mode" \
|
||||||
SOURCE_CANARY_LABEL="$source_canary_label" \
|
SOURCE_CANARY_LABEL="$source_canary_label" \
|
||||||
CANARY_NETWORK_MODE="$container_network_mode" \
|
CANARY_NETWORK_MODE="$container_network_mode" \
|
||||||
CANARY_MOUNTS_JSON="$container_mounts_json" \
|
CANARY_MOUNTS_JSON="$container_mounts_json" \
|
||||||
python3 - "$output" <<'PY'
|
CANARY_LABEL="$container_canary_label" \
|
||||||
import hashlib
|
CANARY_INSTANCE_LABEL="$container_instance_label" \
|
||||||
import json
|
python3 "$repo_root/scripts/finalize_approve_claim_isolated_canary.py" "$output"
|
||||||
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
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,10 @@ sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||||
|
|
||||||
import apply_proposal as ap # noqa: E402
|
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):
|
def _approval_meta(proposal_type, apply_payload):
|
||||||
return {
|
return {
|
||||||
|
|
@ -112,7 +116,7 @@ def test_revise_strategy_rejects_bad_node_type():
|
||||||
|
|
||||||
# --- add_edge ------------------------------------------------------------- #
|
# --- add_edge ------------------------------------------------------------- #
|
||||||
def test_add_edge_sql_dedup_guard():
|
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))
|
sql = ap.build_add_edge_sql(payload, "pid-2", None, _approval_meta("add_edge", payload))
|
||||||
assert "insert into public.claim_edges" in sql
|
assert "insert into public.claim_edges" in sql
|
||||||
assert "not exists" 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():
|
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))
|
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")]
|
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 "claim_edge row does not match strict apply payload" in verify
|
||||||
assert sql.startswith("begin;")
|
assert sql.startswith("begin;")
|
||||||
assert sql.rstrip().endswith("commit;")
|
assert sql.rstrip().endswith("commit;")
|
||||||
assert sql.index("raise exception 'apply_proposal: claim_edge row") < sql.index(
|
assert sql.index("raise exception 'apply_proposal: claim_edge row") < sql.index("kb_stage.finish_approved_proposal")
|
||||||
"kb_stage.finish_approved_proposal"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_add_edge_rejects_self_loop():
|
def test_add_edge_rejects_self_loop():
|
||||||
|
|
@ -144,22 +146,18 @@ def test_add_edge_rejects_self_loop():
|
||||||
|
|
||||||
# --- attach_evidence ------------------------------------------------------ #
|
# --- attach_evidence ------------------------------------------------------ #
|
||||||
def test_attach_evidence_sql_with_source_id():
|
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))
|
sql = ap.build_attach_evidence_sql(payload, "pid-3", None, _approval_meta("attach_evidence", payload))
|
||||||
assert "insert into public.claim_evidence" in sql
|
assert "insert into public.claim_evidence" in sql
|
||||||
|
assert "public.claim_evidence (id," not in sql
|
||||||
|
assert "public.claim_evidence (claim_id, source_id, role, weight)" in sql
|
||||||
assert "::evidence_role" in sql
|
assert "::evidence_role" in sql
|
||||||
assert "not exists" in sql
|
assert "not exists" in sql
|
||||||
|
|
||||||
|
|
||||||
def test_attach_evidence_semantic_row_accepts_match_and_rejects_weight_mismatch():
|
def test_attach_evidence_semantic_row_accepts_match_and_rejects_weight_mismatch():
|
||||||
payload = {
|
payload = {"evidence": [{"claim_id": CLAIM_A, "source_id": SOURCE_A, "role": "grounds", "weight": 0.78}]}
|
||||||
"evidence": [
|
sql = ap.build_attach_evidence_sql(payload, "pid-3", None, _approval_meta("attach_evidence", payload))
|
||||||
{"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)
|
|
||||||
)
|
|
||||||
verify = sql[sql.index("do $verify$") : sql.index("kb_stage.finish_approved_proposal")]
|
verify = sql[sql.index("do $verify$") : sql.index("kb_stage.finish_approved_proposal")]
|
||||||
|
|
||||||
assert "weight is not distinct from 0.78" in verify
|
assert "weight is not distinct from 0.78" in verify
|
||||||
|
|
@ -167,9 +165,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 "evidence row % does not match strict apply payload" in verify
|
||||||
assert sql.startswith("begin;")
|
assert sql.startswith("begin;")
|
||||||
assert sql.rstrip().endswith("commit;")
|
assert sql.rstrip().endswith("commit;")
|
||||||
assert sql.index("raise exception 'apply_proposal: evidence row") < sql.index(
|
assert sql.index("raise exception 'apply_proposal: evidence row") < sql.index("kb_stage.finish_approved_proposal")
|
||||||
"kb_stage.finish_approved_proposal"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_attach_evidence_requires_source_id():
|
def test_attach_evidence_requires_source_id():
|
||||||
|
|
@ -231,9 +227,7 @@ def _approve_claim_bundle():
|
||||||
{"claim_id": claim_a, "source_id": source_a, "role": "grounds", "weight": 0.9},
|
{"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},
|
{"claim_id": claim_b, "source_id": source_b, "role": "illustrates", "weight": None},
|
||||||
],
|
],
|
||||||
"edges": [
|
"edges": [{"from_claim": claim_a, "to_claim": claim_b, "edge_type": "supports", "weight": 0.7}],
|
||||||
{"from_claim": claim_a, "to_claim": claim_b, "edge_type": "supports", "weight": 0.7}
|
|
||||||
],
|
|
||||||
"reasoning_tools": [
|
"reasoning_tools": [
|
||||||
{
|
{
|
||||||
"id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
|
"id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee",
|
||||||
|
|
@ -259,6 +253,8 @@ def test_approve_claim_bundle_sql_is_atomic_and_row_verified():
|
||||||
assert "insert into public.claims" in sql
|
assert "insert into public.claims" in sql
|
||||||
assert "insert into public.sources" in sql
|
assert "insert into public.sources" in sql
|
||||||
assert "insert into public.claim_evidence" in sql
|
assert "insert into public.claim_evidence" in sql
|
||||||
|
assert "public.claim_evidence (id," not in sql
|
||||||
|
assert "public.claim_evidence (claim_id, source_id, role, weight, created_by)" in sql
|
||||||
assert "insert into public.claim_edges" in sql
|
assert "insert into public.claim_edges" in sql
|
||||||
assert "insert into public.reasoning_tools" in sql
|
assert "insert into public.reasoning_tools" in sql
|
||||||
assert "source hash collision" in sql
|
assert "source hash collision" in sql
|
||||||
|
|
@ -291,9 +287,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 "approve_claim: edge row does not match strict apply payload" in verify
|
||||||
assert sql.startswith("begin;")
|
assert sql.startswith("begin;")
|
||||||
assert sql.rstrip().endswith("commit;")
|
assert sql.rstrip().endswith("commit;")
|
||||||
assert sql.index("raise exception 'approve_claim: evidence row") < sql.index(
|
assert sql.index("raise exception 'approve_claim: evidence row") < sql.index("kb_stage.finish_approved_proposal")
|
||||||
"kb_stage.finish_approved_proposal"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_approve_claim_bundle_rejects_duplicate_source_hashes():
|
def test_approve_claim_bundle_rejects_duplicate_source_hashes():
|
||||||
|
|
@ -388,6 +382,34 @@ def test_apply_sql_locks_and_binds_approval_before_canonical_write():
|
||||||
assert "Reviewed exact strict payload." in sql
|
assert "Reviewed exact strict payload." in sql
|
||||||
|
|
||||||
|
|
||||||
|
def test_fresh_owned_apply_uses_strict_inserts_and_binds_preflight_hash():
|
||||||
|
payload = _approve_claim_bundle()
|
||||||
|
proposal = {
|
||||||
|
"id": "99999999-9999-4999-8999-999999999999",
|
||||||
|
"proposal_type": "approve_claim",
|
||||||
|
"status": "approved",
|
||||||
|
"payload": {"apply_payload": payload},
|
||||||
|
"reviewed_by_handle": "m3ta",
|
||||||
|
"reviewed_by_agent_id": None,
|
||||||
|
"reviewed_at": "2026-07-10T00:00:00+00:00",
|
||||||
|
"review_note": "Reviewed exact strict payload.",
|
||||||
|
}
|
||||||
|
preflight_sha256 = "a" * 64
|
||||||
|
|
||||||
|
sql = ap.build_apply_sql(
|
||||||
|
proposal,
|
||||||
|
"kb-apply",
|
||||||
|
fresh_owned=True,
|
||||||
|
fresh_preflight_sha256=preflight_sha256,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert preflight_sha256 in sql
|
||||||
|
assert "set transaction isolation level serializable" in sql.lower()
|
||||||
|
assert "on conflict" not in sql.lower()
|
||||||
|
assert "where not exists" not in sql.lower()
|
||||||
|
assert sql.index("set transaction isolation level serializable") < sql.index("kb_stage.assert_approved_proposal")
|
||||||
|
|
||||||
|
|
||||||
def test_build_apply_sql_requires_review_provenance():
|
def test_build_apply_sql_requires_review_provenance():
|
||||||
proposal = {
|
proposal = {
|
||||||
"id": "99999999-9999-4999-8999-999999999999",
|
"id": "99999999-9999-4999-8999-999999999999",
|
||||||
|
|
@ -445,7 +467,7 @@ def test_assert_applyable_allows_approved():
|
||||||
|
|
||||||
# --- ledger flip: concurrency guard + FK stamp --------------------------- #
|
# --- ledger flip: concurrency guard + FK stamp --------------------------- #
|
||||||
def test_ledger_transition_uses_payload_bound_security_functions():
|
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))
|
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.assert_approved_proposal" in sql
|
||||||
assert "kb_stage.finish_approved_proposal" in sql
|
assert "kb_stage.finish_approved_proposal" in sql
|
||||||
|
|
@ -459,7 +481,7 @@ def test_ledger_transition_uses_payload_bound_security_functions():
|
||||||
|
|
||||||
|
|
||||||
def test_ledger_flip_stamps_agent_fk():
|
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))
|
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
|
# Hard resolve into a variable + NOT-NULL assert, never a silent inline
|
||||||
# subselect that would stamp NULL on an unresolved handle.
|
# subselect that would stamp NULL on an unresolved handle.
|
||||||
|
|
@ -468,7 +490,7 @@ def test_ledger_flip_stamps_agent_fk():
|
||||||
|
|
||||||
|
|
||||||
def test_build_apply_sql_defaults_applied_by_to_service_agent():
|
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 = {
|
proposal = {
|
||||||
"id": "pid",
|
"id": "pid",
|
||||||
"proposal_type": "add_edge",
|
"proposal_type": "add_edge",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ standalone (`python3 tests/test_apply_worker.py`).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import json
|
||||||
|
import stat
|
||||||
import sys
|
import sys
|
||||||
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
@ -40,19 +43,33 @@ sys.path.insert(0, str(REPO_ROOT / "scripts"))
|
||||||
import apply_worker as w # noqa: E402
|
import apply_worker as w # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(autouse=True)
|
def _dependency_restore():
|
||||||
def restore_apply_worker_dependencies():
|
|
||||||
original_load_password = w.ap.load_password
|
original_load_password = w.ap.load_password
|
||||||
original_load_failure_state = w.load_failure_state
|
original_load_failure_state = w.load_failure_state
|
||||||
original_fetch_candidates = w.fetch_candidates
|
original_fetch_candidates = w.fetch_candidates
|
||||||
original_apply_one = w.apply_one
|
original_apply_one = w.apply_one
|
||||||
original_render_one = w.render_one
|
original_render_one = w.render_one
|
||||||
|
original_subprocess_run = w.subprocess.run
|
||||||
yield
|
yield
|
||||||
w.ap.load_password = original_load_password
|
w.ap.load_password = original_load_password
|
||||||
w.load_failure_state = original_load_failure_state
|
w.load_failure_state = original_load_failure_state
|
||||||
w.fetch_candidates = original_fetch_candidates
|
w.fetch_candidates = original_fetch_candidates
|
||||||
w.apply_one = original_apply_one
|
w.apply_one = original_apply_one
|
||||||
w.render_one = original_render_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 ------------------------------------------------------ #
|
# --- candidate query ------------------------------------------------------ #
|
||||||
|
|
@ -116,22 +133,283 @@ def test_render_command_expands_agent_id():
|
||||||
# --- enablement gate ------------------------------------------------------ #
|
# --- enablement gate ------------------------------------------------------ #
|
||||||
def _args(**over):
|
def _args(**over):
|
||||||
base = dict(
|
base = dict(
|
||||||
enable=False, limit=20, max_per_tick=1, max_attempts=3,
|
enable=False,
|
||||||
failure_state_file=None, applied_by="kb-apply",
|
limit=20,
|
||||||
apply_script="apply_proposal.py", render_cmd="",
|
max_per_tick=1,
|
||||||
secrets_file="x", container="teleo-pg", db="teleo",
|
max_attempts=3,
|
||||||
host="127.0.0.1", role="kb_apply",
|
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)
|
base.update(over)
|
||||||
return argparse.Namespace(**base)
|
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):
|
def test_report_only_gate_does_not_apply(monkeypatch=None):
|
||||||
calls = []
|
calls = []
|
||||||
w.ap.load_password = lambda _f: "pw"
|
w.ap.load_password = lambda _f: "pw"
|
||||||
w.fetch_candidates = lambda a, p, excluded: [
|
w.fetch_candidates = lambda a, p, excluded: [{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}]
|
||||||
{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}
|
|
||||||
]
|
|
||||||
w.apply_one = lambda a, pid: calls.append(pid)
|
w.apply_one = lambda a, pid: calls.append(pid)
|
||||||
|
|
||||||
rc = w.run(_args(enable=False))
|
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():
|
def test_enabled_worker_applies_and_renders():
|
||||||
applied, rendered = [], []
|
applied, rendered = [], []
|
||||||
w.ap.load_password = lambda _f: "pw"
|
w.ap.load_password = lambda _f: "pw"
|
||||||
w.fetch_candidates = lambda a, p, excluded: [
|
w.fetch_candidates = lambda a, p, excluded: [{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}]
|
||||||
{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}
|
|
||||||
]
|
|
||||||
w.apply_one = lambda a, pid: applied.append(pid)
|
w.apply_one = lambda a, pid: applied.append(pid)
|
||||||
w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok"
|
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():
|
def test_enabled_worker_skips_render_for_add_edge():
|
||||||
applied, rendered = [], []
|
applied, rendered = [], []
|
||||||
w.ap.load_password = lambda _f: "pw"
|
w.ap.load_password = lambda _f: "pw"
|
||||||
w.fetch_candidates = lambda a, p, excluded: [
|
w.fetch_candidates = lambda a, p, excluded: [{"id": "e1", "proposal_type": "add_edge", "agent_id": None}]
|
||||||
{"id": "e1", "proposal_type": "add_edge", "agent_id": None}
|
|
||||||
]
|
|
||||||
w.apply_one = lambda a, pid: applied.append(pid)
|
w.apply_one = lambda a, pid: applied.append(pid)
|
||||||
w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok"
|
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():
|
def test_enabled_worker_skips_render_for_approve_claim():
|
||||||
applied, rendered = [], []
|
applied, rendered = [], []
|
||||||
w.ap.load_password = lambda _f: "pw"
|
w.ap.load_password = lambda _f: "pw"
|
||||||
w.fetch_candidates = lambda a, p, excluded: [
|
w.fetch_candidates = lambda a, p, excluded: [{"id": "c1", "proposal_type": "approve_claim", "agent_id": "ag"}]
|
||||||
{"id": "c1", "proposal_type": "approve_claim", "agent_id": "ag"}
|
|
||||||
]
|
|
||||||
w.apply_one = lambda a, pid: applied.append(pid)
|
w.apply_one = lambda a, pid: applied.append(pid)
|
||||||
w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok"
|
w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok"
|
||||||
|
|
||||||
|
|
@ -263,7 +535,7 @@ if __name__ == "__main__":
|
||||||
failures = 0
|
failures = 0
|
||||||
for name, fn in sorted(globals().items()):
|
for name, fn in sorted(globals().items()):
|
||||||
if name.startswith("test_") and callable(fn):
|
if name.startswith("test_") and callable(fn):
|
||||||
restore = restore_apply_worker_dependencies()
|
restore = _dependency_restore()
|
||||||
next(restore)
|
next(restore)
|
||||||
try:
|
try:
|
||||||
fn()
|
fn()
|
||||||
|
|
|
||||||
1010
tests/test_proposal_apply_lifecycle.py
Normal file
1010
tests/test_proposal_apply_lifecycle.py
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -13,6 +13,11 @@ sys.path.insert(0, str(ROOT / "scripts"))
|
||||||
import run_approve_claim_clone_canary as canary # noqa: E402
|
import run_approve_claim_clone_canary as canary # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
|
def test_unrelated_fingerprint_orders_legacy_claim_evidence_by_composite_key() -> None:
|
||||||
|
assert canary.CLAIM_EVIDENCE_FINGERPRINT_ORDER == ("ce.claim_id::text, ce.source_id::text, ce.role::text")
|
||||||
|
assert "ce.id" not in canary.CLAIM_EVIDENCE_FINGERPRINT_ORDER
|
||||||
|
|
||||||
|
|
||||||
def _passing_result(expected_rows: dict, expected_deltas: dict) -> dict:
|
def _passing_result(expected_rows: dict, expected_deltas: dict) -> dict:
|
||||||
proposal_id = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
|
proposal_id = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa"
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
@ -11,8 +13,10 @@ import pytest
|
||||||
ROOT = Path(__file__).resolve().parents[1]
|
ROOT = Path(__file__).resolve().parents[1]
|
||||||
sys.path.insert(0, str(ROOT / "scripts"))
|
sys.path.insert(0, str(ROOT / "scripts"))
|
||||||
WRAPPER = ROOT / "scripts" / "run_approve_claim_isolated_container_canary.sh"
|
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 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
|
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(
|
monkeypatch.setattr(
|
||||||
canary.shutil,
|
canary.shutil,
|
||||||
"which",
|
"which",
|
||||||
lambda executable: "/opt/homebrew/bin/docker"
|
lambda executable: "/opt/homebrew/bin/docker" if executable == "docker" else None,
|
||||||
if executable == "docker"
|
|
||||||
else None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def fake_run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
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(
|
monkeypatch.setattr(
|
||||||
apply.shutil,
|
apply.shutil,
|
||||||
"which",
|
"which",
|
||||||
lambda executable: "/opt/homebrew/bin/docker"
|
lambda executable: "/opt/homebrew/bin/docker" if executable == "docker" else None,
|
||||||
if executable == "docker"
|
|
||||||
else None,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def fake_run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]:
|
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:
|
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 'source_canary_label" != "working-leo-approved-source"' in text
|
||||||
assert "--network none" in text
|
assert "--network none" in text
|
||||||
assert "--tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m" 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_NETWORK_MODE="$container_network_mode"' in text
|
||||||
assert 'CANARY_MOUNTS_JSON="$container_mounts_json"' in text
|
assert 'CANARY_MOUNTS_JSON="$container_mounts_json"' in text
|
||||||
assert 'data["checks"]["outer_source_tier_enforced"]' in text
|
assert 'checks["outer_source_tier_enforced"]' in text
|
||||||
assert 'data["checks"]["outer_container_network_disabled"]' in text
|
assert 'checks["outer_container_network_disabled"]' in text
|
||||||
assert 'data["checks"]["outer_container_has_no_docker_volumes"]' in text
|
assert 'checks["outer_container_has_no_docker_volumes"]' in text
|
||||||
assert 'source_tier != "live-readonly" or service_observable' 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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue