From dfa6be6ba098b63b5856d03f499a44883b193e4f Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Wed, 15 Jul 2026 03:37:39 +0200 Subject: [PATCH 01/18] feat: prepare zero-mutation Telegram Leo chain proof --- ...en-chain-authorization-packet-current.json | 135 ++++ ...seen-chain-authorization-packet-current.md | 64 ++ ...-unseen-chain-capture-receipt-current.json | 84 +++ ...le-unseen-chain-capture-receipt-current.md | 25 + ...le-unseen-chain-goal-readback-current.json | 21 + ...le-unseen-chain-nonsend-proof-current.json | 62 ++ ...isible-unseen-chain-preflight-current.json | 115 ++++ ...-visible-unseen-chain-preflight-current.md | 49 ++ ...ld_telegram_visible_unseen_chain_packet.py | 326 +++++++++ ...ect_telegram_visible_unseen_chain_state.py | 616 ++++++++++++++++++ .../verify_telegram_visible_unseen_chain.py | 524 +++++++++++++++ ...ld_telegram_visible_unseen_chain_packet.py | 64 ++ ...ect_telegram_visible_unseen_chain_state.py | 251 +++++++ ...st_verify_telegram_visible_unseen_chain.py | 240 +++++++ 14 files changed, 2576 insertions(+) create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.json create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.md create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.json create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.md create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-goal-readback-current.json create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-nonsend-proof-current.json create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json create mode 100644 docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.md create mode 100644 scripts/build_telegram_visible_unseen_chain_packet.py create mode 100644 scripts/collect_telegram_visible_unseen_chain_state.py create mode 100644 scripts/verify_telegram_visible_unseen_chain.py create mode 100644 tests/test_build_telegram_visible_unseen_chain_packet.py create mode 100644 tests/test_collect_telegram_visible_unseen_chain_state.py create mode 100644 tests/test_verify_telegram_visible_unseen_chain.py diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.json b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.json new file mode 100644 index 0000000..348e036 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.json @@ -0,0 +1,135 @@ +{ + "authorization_gate_status": "ready_to_request_exact_authorization", + "checks": { + "authenticated_chrome_is_only_send_transport": true, + "exact_prompts_imported_from_handler_verifier": true, + "exact_three_turn_sequence": true, + "first_prompt_supplies_no_row_id": true, + "handler_checks_all_pass": true, + "handler_outcomes_all_pass": true, + "handler_prompt_ids_exact": true, + "handler_proved_no_telegram_post": true, + "handler_proved_read_only_tool_traces": true, + "handler_proved_unchanged_fingerprint": true, + "handler_receipt_passed": true, + "handler_receipt_schema_supported": true, + "handler_safety_gate_passed": true, + "handler_tier_remains_below_telegram_visible": true, + "packet_does_not_mutate_database": true, + "packet_does_not_send": true + }, + "claim_ceiling": { + "current_tier": "T0_spec_plus_prior_T2_handler_evidence", + "not_proven": [ + "Telegram-visible delivery of these three messages", + "Telegram-visible replies from Leo", + "post-send canonical fingerprint equality" + ], + "proven": "The exact three-turn Chrome send packet is bound to a passing no-send handler receipt.", + "required_tier": "T3_live_readonly" + }, + "clear_CTA": "Reply with the exact authorization sentence in this packet. That sentence names the destination, transport, complete message bodies, ordering, evidence, and no-mutation boundary.", + "exact_messages": [ + { + "dimension": "independent_weak_claim_selection", + "message": "Looking at what Leo currently believes about collective intelligence or coordination, pick one claim that would matter to a founder making a product decision but seems more confident than its support. Do not ask me for an ID. Show what the database actually says and what supports it, then tell me the first narrower claim you would test. Do not change anything yet.", + "message_sha256": "c63838b98448f4f4599e4cf2b004c6b36f602ad0d25c92e442a6f866cd2740fc", + "prompt_id": "OOS-CHAIN-01", + "sequence": 1, + "wait_for_visible_reply_before_next": true + }, + { + "dimension": "challenge_body_evidence_and_candidates", + "message": "That still feels like you are polishing the same idea. Inspect the exact body and evidence you just chose: what is assumption versus observed support, what would falsify it, and which replacement claim or claims would you put into review? Keep them as proposals, not live knowledge.", + "message_sha256": "73346a98db4d7f74b49e1a5741b323d4216fd21f8346f6c4a4a69b1a6dd07946", + "prompt_id": "OOS-CHAIN-02", + "sequence": 2, + "wait_for_visible_reply_before_next": true + }, + { + "dimension": "user_objection_revision_and_apply_boundary", + "message": "I disagree with your first replacement because it jumps from a mechanism to a business outcome. Revise it with that objection, tell me what source would resolve the disagreement, and show the approval and apply boundary before it could become part of Leo.", + "message_sha256": "5f6b32b2c8c51cc594242ed9bdac849c744156ca2395efb7e8593c83c86286d4", + "prompt_id": "OOS-CHAIN-03", + "sequence": 3, + "wait_for_visible_reply_before_next": true + } + ], + "expected_proof_paths_after_authorized_run": { + "capture_json": "outputs/telegram-visible-unseen-chain-20260715/capture.json", + "capture_receipt_json": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.json", + "final_accessibility": "outputs/telegram-visible-unseen-chain-20260715/final-accessibility.txt", + "final_screenshot": "outputs/telegram-visible-unseen-chain-20260715/final.png", + "postflight_json": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-postflight-current.json", + "preflight_json": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json", + "turn_accessibility": [ + "outputs/telegram-visible-unseen-chain-20260715/turn-1-accessibility.txt", + "outputs/telegram-visible-unseen-chain-20260715/turn-2-accessibility.txt", + "outputs/telegram-visible-unseen-chain-20260715/turn-3-accessibility.txt" + ], + "turn_screenshots": [ + "outputs/telegram-visible-unseen-chain-20260715/turn-1.png", + "outputs/telegram-visible-unseen-chain-20260715/turn-2.png", + "outputs/telegram-visible-unseen-chain-20260715/turn-3.png" + ] + }, + "explicit_operator_authorization_text": "I authorize Codex to use the already-authenticated Chrome Telegram UI to send exactly these three messages, in order and with no additional messages, to the Telegram group Leo (chat ID -5146042086), waiting for Leo's visible reply after each turn: [OOS-CHAIN-01] \"Looking at what Leo currently believes about collective intelligence or coordination, pick one claim that would matter to a founder making a product decision but seems more confident than its support. Do not ask me for an ID. Show what the database actually says and what supports it, then tell me the first narrower claim you would test. Do not change anything yet.\" [OOS-CHAIN-02] \"That still feels like you are polishing the same idea. Inspect the exact body and evidence you just chose: what is assumption versus observed support, what would falsify it, and which replacement claim or claims would you put into review? Keep them as proposals, not live knowledge.\" [OOS-CHAIN-03] \"I disagree with your first replacement because it jumps from a mechanism to a business outcome. Revise it with that objection, tell me what source would resolve the disagreement, and show the approval and apply boundary before it could become part of Leo.\" Capture the visible replies, message IDs, timestamps, screenshot and accessibility evidence; perform only read-only DB/service readbacks; do not use the Bot API; do not stage, approve, apply, or otherwise mutate knowledge.", + "generated_at_utc": "2026-07-15T01:32:53.837725+00:00", + "handler_receipt_binding": { + "deployed_git_head": "b3aa184dd4c721f6c40dc813dcefd5930f8ed417", + "fingerprint_sha256": "32a1f2c18f4b4c623443cbd4d9520982e9f291ec74ca63855cef51c88ac56f24", + "path": "docs/reports/leo-working-state-20260709/leo-unseen-reasoning-chain-canary-current.json", + "proof_tier": "live_vps_gatewayrunner_temp_profile_no_telegram_post_no_apply", + "remote_run_id": "d5702cafbb0b", + "schema": "livingip.leoUnseenReasoningChainReceipt.v2", + "sha256": "f4fcf0207484e5322074c7838f3a345d421c1664e5dc8d73b9199c4475b4cee8" + }, + "mode": "telegram_visible_unseen_chain_exact_packet_not_sent", + "mutates_db": false, + "next_non_user_action_after_authorization": [ + "Re-run the zero-mutation preflight and confirm its packet file hash is current.", + "Use authenticated Chrome only and verify the Leo chat ID before typing anything.", + "Send one exact message, wait for Leo's visible reply, then continue to the next exact message.", + "Capture message IDs, timestamps, screenshot, and accessibility text without copying handler output.", + "Extract the selected DB object reference and run the read-only postflight with that reference.", + "Run the Telegram-visible verifier and accept T3 only if every visible and state check passes." + ], + "packet_generation_git_sha": "a47415b60ef059ecc55e067ae556f49d818dc68d", + "production_apply_executed": false, + "protocol_sha256": "c815dad151d640335eecbddd2ca7bb651c43f6c1d4e53642e12c8a35157ac2da", + "ready_to_request_action_time_authorization": true, + "schema": "livingip.telegramVisibleUnseenChainPacket.v1", + "target": { + "allowed_transport": "authenticated_chrome_telegram_ui", + "chat_id": "-5146042086", + "chat_label": "Leo", + "expected_prompt_ids": [ + "OOS-CHAIN-01", + "OOS-CHAIN-02", + "OOS-CHAIN-03" + ], + "forbidden_transports": [ + "telegram_bot_api", + "copied_transcript", + "handler_substitution" + ], + "message_count": 3, + "platform": "telegram" + }, + "telegram_visible_message_authorization_present": false, + "telegram_visible_messages_sent": false, + "tooling_binding": { + "capture_verifier": { + "path": "scripts/verify_telegram_visible_unseen_chain.py", + "sha256": "4eb3eb8799282d07689a39d21a608cfbb90568db56f3d0c9c8b3430e0c3024e9" + }, + "packet_builder": { + "path": "scripts/build_telegram_visible_unseen_chain_packet.py", + "sha256": "623452bbd577f3576b38412f3578cbe341d4c310b6e9657b1b9874588e38e4a0" + }, + "state_collector": { + "path": "scripts/collect_telegram_visible_unseen_chain_state.py", + "sha256": "4fa5174e79ec8f5224e42c4e3d01f5df019a17073275d51e32e1a57a06be481d" + } + } +} diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.md b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.md new file mode 100644 index 0000000..1e6d25e --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.md @@ -0,0 +1,64 @@ +# Telegram-Visible Unseen Chain Authorization Packet + +Generated UTC: `2026-07-15T01:32:53.837725+00:00` +Protocol SHA256: `c815dad151d640335eecbddd2ca7bb651c43f6c1d4e53642e12c8a35157ac2da` +Ready to request action-time authorization: `True` +Telegram-visible messages sent: `False` +Mutates DB: `False` + +## Exact Destination + +- Chat: `Leo` (`-5146042086`) +- Transport: `authenticated_chrome_telegram_ui` + +## Exact Messages + +### 1. OOS-CHAIN-01 + +Looking at what Leo currently believes about collective intelligence or coordination, pick one claim that would matter to a founder making a product decision but seems more confident than its support. Do not ask me for an ID. Show what the database actually says and what supports it, then tell me the first narrower claim you would test. Do not change anything yet. + +SHA256: `c63838b98448f4f4599e4cf2b004c6b36f602ad0d25c92e442a6f866cd2740fc` + +### 2. OOS-CHAIN-02 + +That still feels like you are polishing the same idea. Inspect the exact body and evidence you just chose: what is assumption versus observed support, what would falsify it, and which replacement claim or claims would you put into review? Keep them as proposals, not live knowledge. + +SHA256: `73346a98db4d7f74b49e1a5741b323d4216fd21f8346f6c4a4a69b1a6dd07946` + +### 3. OOS-CHAIN-03 + +I disagree with your first replacement because it jumps from a mechanism to a business outcome. Revise it with that objection, tell me what source would resolve the disagreement, and show the approval and apply boundary before it could become part of Leo. + +SHA256: `5f6b32b2c8c51cc594242ed9bdac849c744156ca2395efb7e8593c83c86286d4` + +## Checks + +- `authenticated_chrome_is_only_send_transport`: `True` +- `exact_prompts_imported_from_handler_verifier`: `True` +- `exact_three_turn_sequence`: `True` +- `first_prompt_supplies_no_row_id`: `True` +- `handler_checks_all_pass`: `True` +- `handler_outcomes_all_pass`: `True` +- `handler_prompt_ids_exact`: `True` +- `handler_proved_no_telegram_post`: `True` +- `handler_proved_read_only_tool_traces`: `True` +- `handler_proved_unchanged_fingerprint`: `True` +- `handler_receipt_passed`: `True` +- `handler_receipt_schema_supported`: `True` +- `handler_safety_gate_passed`: `True` +- `handler_tier_remains_below_telegram_visible`: `True` +- `packet_does_not_mutate_database`: `True` +- `packet_does_not_send`: `True` + +## Exact Action-Time Authorization + +I authorize Codex to use the already-authenticated Chrome Telegram UI to send exactly these three messages, in order and with no additional messages, to the Telegram group Leo (chat ID -5146042086), waiting for Leo's visible reply after each turn: [OOS-CHAIN-01] "Looking at what Leo currently believes about collective intelligence or coordination, pick one claim that would matter to a founder making a product decision but seems more confident than its support. Do not ask me for an ID. Show what the database actually says and what supports it, then tell me the first narrower claim you would test. Do not change anything yet." [OOS-CHAIN-02] "That still feels like you are polishing the same idea. Inspect the exact body and evidence you just chose: what is assumption versus observed support, what would falsify it, and which replacement claim or claims would you put into review? Keep them as proposals, not live knowledge." [OOS-CHAIN-03] "I disagree with your first replacement because it jumps from a mechanism to a business outcome. Revise it with that objection, tell me what source would resolve the disagreement, and show the approval and apply boundary before it could become part of Leo." Capture the visible replies, message IDs, timestamps, screenshot and accessibility evidence; perform only read-only DB/service readbacks; do not use the Bot API; do not stage, approve, apply, or otherwise mutate knowledge. + +## Clear CTA + +Reply with the exact authorization sentence in this packet. That sentence names the destination, transport, complete message bodies, ordering, evidence, and no-mutation boundary. + +## Claim Ceiling + +Current tier: `T0_spec_plus_prior_T2_handler_evidence` +Required tier: `T3_live_readonly` diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.json b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.json new file mode 100644 index 0000000..b4bd066 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.json @@ -0,0 +1,84 @@ +{ + "capture_template": { + "capture_source": { + "captured_at_utc": "", + "captured_by": "", + "chat_id": "-5146042086", + "chat_label": "Leo", + "final_accessibility": "", + "final_screenshot": "", + "platform": "telegram", + "transport": "authenticated_chrome_telegram_ui" + }, + "extra_messages_sent": 0, + "messages": [ + { + "prompt_id": "OOS-CHAIN-01", + "reply": "", + "reply_at_utc": "", + "reply_message_id": "", + "sent_at_utc": "", + "sent_text": "Looking at what Leo currently believes about collective intelligence or coordination, pick one claim that would matter to a founder making a product decision but seems more confident than its support. Do not ask me for an ID. Show what the database actually says and what supports it, then tell me the first narrower claim you would test. Do not change anything yet.", + "sequence": 1, + "telegram_message_id": "", + "turn_accessibility": "", + "turn_screenshot": "" + }, + { + "prompt_id": "OOS-CHAIN-02", + "reply": "", + "reply_at_utc": "", + "reply_message_id": "", + "sent_at_utc": "", + "sent_text": "That still feels like you are polishing the same idea. Inspect the exact body and evidence you just chose: what is assumption versus observed support, what would falsify it, and which replacement claim or claims would you put into review? Keep them as proposals, not live knowledge.", + "sequence": 2, + "telegram_message_id": "", + "turn_accessibility": "", + "turn_screenshot": "" + }, + { + "prompt_id": "OOS-CHAIN-03", + "reply": "", + "reply_at_utc": "", + "reply_message_id": "", + "sent_at_utc": "", + "sent_text": "I disagree with your first replacement because it jumps from a mechanism to a business outcome. Revise it with that objection, tell me what source would resolve the disagreement, and show the approval and apply boundary before it could become part of Leo.", + "sequence": 3, + "telegram_message_id": "", + "turn_accessibility": "", + "turn_screenshot": "" + } + ], + "operator_authorization_captured_at_utc": "", + "operator_authorization_text": "I authorize Codex to use the already-authenticated Chrome Telegram UI to send exactly these three messages, in order and with no additional messages, to the Telegram group Leo (chat ID -5146042086), waiting for Leo's visible reply after each turn: [OOS-CHAIN-01] \"Looking at what Leo currently believes about collective intelligence or coordination, pick one claim that would matter to a founder making a product decision but seems more confident than its support. Do not ask me for an ID. Show what the database actually says and what supports it, then tell me the first narrower claim you would test. Do not change anything yet.\" [OOS-CHAIN-02] \"That still feels like you are polishing the same idea. Inspect the exact body and evidence you just chose: what is assumption versus observed support, what would falsify it, and which replacement claim or claims would you put into review? Keep them as proposals, not live knowledge.\" [OOS-CHAIN-03] \"I disagree with your first replacement because it jumps from a mechanism to a business outcome. Revise it with that objection, tell me what source would resolve the disagreement, and show the approval and apply boundary before it could become part of Leo.\" Capture the visible replies, message IDs, timestamps, screenshot and accessibility evidence; perform only read-only DB/service readbacks; do not use the Bot API; do not stage, approve, apply, or otherwise mutate knowledge.", + "preflight_state_token_sha256": "a0af7235b62f88a009184f8f51b6de4b9aec008f6d93ebfa1458f9b77fc208ec", + "protocol_sha256": "c815dad151d640335eecbddd2ca7bb651c43f6c1d4e53642e12c8a35157ac2da" + }, + "checks": { + "action_time_authorization_present": false, + "capture_present": false, + "packet_ready": true, + "postflight_present": false, + "preflight_passed": true + }, + "claim_ceiling": { + "not_proven": [ + "Telegram-visible message delivery", + "Telegram-visible Leo replies", + "post-send fingerprint equality" + ], + "proven": "The exact packet and live zero-mutation preflight are ready." + }, + "current_tier": "T0_packet_plus_T3_preflight", + "generated_at_utc": "2026-07-15T01:33:10.948040+00:00", + "mode": "telegram_visible_unseen_chain_capture_verifier", + "mutates_db": false, + "packet_path": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.json", + "postflight_path": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-postflight-current.json", + "preflight_path": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json", + "receipt_ready": false, + "required_tier": "T3_live_readonly", + "schema": "livingip.telegramVisibleUnseenChainReceipt.v1", + "status": "awaiting_action_time_authorization", + "telegram_visible_messages_sent": false +} diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.md b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.md new file mode 100644 index 0000000..5d0b2fc --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.md @@ -0,0 +1,25 @@ +# Telegram-Visible Unseen Chain Capture Receipt + +Generated UTC: `2026-07-15T01:33:10.948040+00:00` +Status: `awaiting_action_time_authorization` +Receipt ready: `False` +Current tier: `T0_packet_plus_T3_preflight` +Required tier: `T3_live_readonly` +Telegram-visible messages sent: `False` +Mutates DB: `False` + +## Checks + +- `action_time_authorization_present`: `False` +- `capture_present`: `False` +- `packet_ready`: `True` +- `postflight_present`: `False` +- `preflight_passed`: `True` + +## Awaiting Authorized Capture + +The packet and preflight are ready. No Telegram message has been sent by this verifier. + +## Claim Ceiling + +The exact packet and live zero-mutation preflight are ready. diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-goal-readback-current.json b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-goal-readback-current.json new file mode 100644 index 0000000..6edb7a0 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-goal-readback-current.json @@ -0,0 +1,21 @@ +{ + "fallback_goal_path": "goal.md", + "generated_at_utc": "2026-07-15T01:26:37Z", + "platform_goal_after": { + "objective": "Complete and verify the Telegram-visible unseen challenge-to-revision canary defined in this goal file while proving zero database mutation.", + "status": "active", + "thread_id": "019f6350-3350-7040-b223-c5c22673fece" + }, + "platform_goal_before": null, + "platform_goal_current": { + "objective": "Complete and verify the Telegram-visible unseen challenge-to-revision canary defined in this goal file while proving zero database mutation.", + "status": "active", + "thread_id": "019f6350-3350-7040-b223-c5c22673fece", + "time_used_seconds_at_readback": 1116, + "tokens_used_at_readback": 211127 + }, + "platform_goal_repair_action": "create_goal", + "platform_goal_replaced": false, + "schema": "livingip.telegramVisibleUnseenChainGoalReadback.v1", + "why_not_replaced": "No active platform Goal existed, so the canonical Goal was created rather than replaced." +} diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-nonsend-proof-current.json b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-nonsend-proof-current.json new file mode 100644 index 0000000..e34e552 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-nonsend-proof-current.json @@ -0,0 +1,62 @@ +{ + "cleanup_readback": { + "collector_or_verifier_processes_remaining": [], + "status": "clean" + }, + "generated_at_utc": "2026-07-15T01:37:18Z", + "live_preflight": { + "canonical_fingerprint_sha256": "32a1f2c18f4b4c623443cbd4d9520982e9f291ec74ca63855cef51c88ac56f24", + "canonical_fingerprint_unchanged_across_two_snapshots": true, + "deployed_git_stamp": "15c30f1fbe30c8f2295ddc33e293a45788a95706", + "gateway_main_pid": "347406", + "gateway_restarts": "0", + "mutates_db": false, + "path": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json", + "packet_file_sha256": "fb0ab4af0396b2064efce1ead44aacc0465ee0e3eacf9b4a7c07abae044d217a", + "protocol_sha256": "c815dad151d640335eecbddd2ca7bb651c43f6c1d4e53642e12c8a35157ac2da", + "readback_attempts": { + "deploy": 1, + "fingerprint_after": 1, + "fingerprint_before": 1, + "service_after": 1, + "service_before": 1 + }, + "state_token_sha256": "a0af7235b62f88a009184f8f51b6de4b9aec008f6d93ebfa1458f9b77fc208ec", + "tooling_hashes_match_current_sources": true, + "status": "pass", + "table_count": 39, + "total_rows": 52167 + }, + "not_tested": [ + "Telegram message delivery", + "Telegram-visible Leo replies", + "post-send selected-object readback", + "post-send canonical fingerprint equality" + ], + "proof_paths": [ + "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.json", + "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json", + "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-capture-receipt-current.json", + "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-goal-readback-current.json" + ], + "schema": "livingip.telegramVisibleUnseenChainNonsendProof.v1", + "send_boundary": { + "action_time_authorization_present": false, + "authenticated_chrome_used": false, + "telegram_visible_messages_sent": false + }, + "tests": [ + { + "command": "/Users/user/Documents/Codex/2026-07-09/019f34eb-d297-72d0-b7e2-b222d5515ab9-load/work/teleo-infra-main/.venv/bin/pytest -q tests/test_verify_leo_unseen_reasoning_chain.py tests/test_build_telegram_visible_unseen_chain_packet.py tests/test_collect_telegram_visible_unseen_chain_state.py tests/test_verify_telegram_visible_unseen_chain.py", + "result": "24 passed in 0.16s" + }, + { + "command": "/Users/user/Documents/Codex/2026-07-09/019f34eb-d297-72d0-b7e2-b222d5515ab9-load/work/teleo-infra-main/.venv/bin/pytest -q", + "result": "1293 passed in 102.03s" + }, + { + "command": "/Users/user/Documents/Codex/2026-07-09/019f34eb-d297-72d0-b7e2-b222d5515ab9-load/work/teleo-infra-main/.venv/bin/ruff check ", + "result": "All checks passed" + } + ] +} diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json new file mode 100644 index 0000000..3af9b9e --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.json @@ -0,0 +1,115 @@ +{ + "baseline_checks": {}, + "baseline_path": "", + "blocker_readback": { + "attempted_routes": [ + "local exact packet validation", + "SSH deploy stamp and gateway service readback", + "two canonical repeatable-read read-only Postgres parity manifests" + ], + "clear_CTA": "Use the packet's exact authorization sentence only when ready to cross the send boundary.", + "current_canary": "Read-only pre-send readiness for the exact three-turn unseen Leo chain in Telegram", + "exact_gate": "", + "next_non_user_action": "Run the authenticated-Chrome three-turn flow only after exact authorization, then collect postflight." + }, + "claim_ceiling": { + "current_tier": "T3_live_readonly", + "not_proven": [ + "Telegram-visible delivery or reply behavior", + "authorization to send Telegram messages", + "canonical mutation" + ], + "proven": "Two canonical read-only snapshots and the gateway process were unchanged during this probe." + }, + "generated_at_utc": "2026-07-15T01:33:10.551000+00:00", + "mode": "telegram_visible_unseen_chain_zero_mutation_state_readback", + "mutates_db": false, + "packet_checks": { + "packet_does_not_mutate_db": true, + "packet_has_no_authorization_recorded": true, + "packet_messages_exact": true, + "packet_not_sent": true, + "packet_prompt_ids_exact": true, + "packet_protocol_sha256_present": true, + "packet_ready_for_authorization_request": true, + "packet_requires_authenticated_chrome": true, + "packet_schema_supported": true, + "packet_targets_exact_leo_chat": true, + "packet_tooling_hashes_match_current_sources": true + }, + "packet_file_sha256": "fb0ab4af0396b2064efce1ead44aacc0465ee0e3eacf9b4a7c07abae044d217a", + "packet_path": "docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-authorization-packet-current.json", + "phase": "preflight", + "production_apply_executed": false, + "protocol_sha256": "c815dad151d640335eecbddd2ca7bb651c43f6c1d4e53642e12c8a35157ac2da", + "ready_for_action_time_authorization": true, + "ready_for_visible_capture_verification": false, + "remote": { + "deploy_head": "15c30f1fbe30c8f2295ddc33e293a45788a95706", + "deploy_stamp_ancestor": true, + "fingerprint_after": { + "fingerprint_sha256": "32a1f2c18f4b4c623443cbd4d9520982e9f291ec74ca63855cef51c88ac56f24", + "schema": "livingip.teleoCanonicalDatabaseFingerprint.v1", + "status": "ok", + "structure_sha256": "61bf482330a652ebdb419de3fb78196f2c7d25d1a482b100f385e8102c8e8d52", + "table_count": 39, + "table_rows_sha256": "f50d86fc3ce699f602bdc0029485bd901fd98bd8114eecd5dcc4346a0f52112a", + "total_rows": 52167, + "transaction_read_only": "on" + }, + "fingerprint_before": { + "fingerprint_sha256": "32a1f2c18f4b4c623443cbd4d9520982e9f291ec74ca63855cef51c88ac56f24", + "schema": "livingip.teleoCanonicalDatabaseFingerprint.v1", + "status": "ok", + "structure_sha256": "61bf482330a652ebdb419de3fb78196f2c7d25d1a482b100f385e8102c8e8d52", + "table_count": 39, + "table_rows_sha256": "f50d86fc3ce699f602bdc0029485bd901fd98bd8114eecd5dcc4346a0f52112a", + "total_rows": 52167, + "transaction_read_only": "on" + }, + "last_deploy_sha": "15c30f1fbe30c8f2295ddc33e293a45788a95706", + "readback_attempts": { + "deploy": 1, + "fingerprint_after": 1, + "fingerprint_before": 1, + "service_after": 1, + "service_before": 1 + }, + "service_after": { + "ActiveState": "active", + "ExecMainStartTimestamp": "Tue 2026-07-14 22:59:43 UTC", + "MainPID": "347406", + "NRestarts": "0", + "SubState": "running" + }, + "service_before": { + "ActiveState": "active", + "ExecMainStartTimestamp": "Tue 2026-07-14 22:59:43 UTC", + "MainPID": "347406", + "NRestarts": "0", + "SubState": "running" + }, + "subject_readback": null, + "workspace_head_matches_deployed_stamp": true + }, + "remote_checks": { + "canonical_fingerprint_unchanged_during_probe": true, + "deployed_stamp_is_ancestor_of_workspace_head": true, + "deployed_stamp_is_sha": true, + "fingerprints_are_read_only": true, + "first_fingerprint_complete": true, + "second_fingerprint_complete": true, + "service_active_before_and_after": true, + "service_identity_complete": true, + "service_process_unchanged_during_probe": true, + "ssh_readbacks_succeeded": true, + "workspace_head_is_sha": true + }, + "remote_returncode": 0, + "remote_stderr": "", + "schema": "livingip.telegramVisibleUnseenChainState.v1", + "state_token_sha256": "a0af7235b62f88a009184f8f51b6de4b9aec008f6d93ebfa1458f9b77fc208ec", + "status": "pass", + "subject_ref": "", + "telegram_visible_messages_sent_by_collector": false +} diff --git a/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.md b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.md new file mode 100644 index 0000000..c4e17c6 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/telegram-visible-unseen-chain-preflight-current.md @@ -0,0 +1,49 @@ +# Telegram-Visible Unseen Chain Preflight + +Generated UTC: `2026-07-15T01:33:10.551000+00:00` +Status: `pass` +Protocol SHA256: `c815dad151d640335eecbddd2ca7bb651c43f6c1d4e53642e12c8a35157ac2da` +Packet file SHA256: `fb0ab4af0396b2064efce1ead44aacc0465ee0e3eacf9b4a7c07abae044d217a` +State token SHA256: `a0af7235b62f88a009184f8f51b6de4b9aec008f6d93ebfa1458f9b77fc208ec` +Messages sent by collector: `False` +Mutates DB: `False` + +## Checks + +- `packet_does_not_mutate_db`: `True` +- `packet_has_no_authorization_recorded`: `True` +- `packet_messages_exact`: `True` +- `packet_not_sent`: `True` +- `packet_prompt_ids_exact`: `True` +- `packet_protocol_sha256_present`: `True` +- `packet_ready_for_authorization_request`: `True` +- `packet_requires_authenticated_chrome`: `True` +- `packet_schema_supported`: `True` +- `packet_targets_exact_leo_chat`: `True` +- `packet_tooling_hashes_match_current_sources`: `True` +- `canonical_fingerprint_unchanged_during_probe`: `True` +- `deployed_stamp_is_ancestor_of_workspace_head`: `True` +- `deployed_stamp_is_sha`: `True` +- `fingerprints_are_read_only`: `True` +- `first_fingerprint_complete`: `True` +- `second_fingerprint_complete`: `True` +- `service_active_before_and_after`: `True` +- `service_identity_complete`: `True` +- `service_process_unchanged_during_probe`: `True` +- `ssh_readbacks_succeeded`: `True` +- `workspace_head_is_sha`: `True` + +## Live Readback + +- Deploy head: `15c30f1fbe30c8f2295ddc33e293a45788a95706` +- Deploy stamp: `15c30f1fbe30c8f2295ddc33e293a45788a95706` +- Gateway MainPID: `347406` +- Gateway NRestarts: `0` +- Canonical fingerprint: `32a1f2c18f4b4c623443cbd4d9520982e9f291ec74ca63855cef51c88ac56f24` +- Canonical tables: `39` +- Canonical rows: `52167` +- Selected object ref: `` + +## Claim Ceiling + +Two canonical read-only snapshots and the gateway process were unchanged during this probe. diff --git a/scripts/build_telegram_visible_unseen_chain_packet.py b/scripts/build_telegram_visible_unseen_chain_packet.py new file mode 100644 index 0000000..8a22800 --- /dev/null +++ b/scripts/build_telegram_visible_unseen_chain_packet.py @@ -0,0 +1,326 @@ +#!/usr/bin/env python3 +"""Build the exact no-send packet for Leo's unseen Telegram reasoning chain.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import subprocess +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +try: + import verify_leo_unseen_reasoning_chain as unseen +except ImportError: # pragma: no cover - imported as scripts.* in tests + from scripts import verify_leo_unseen_reasoning_chain as unseen + +SCHEMA = "livingip.telegramVisibleUnseenChainPacket.v1" +REPORT_DIR = Path("docs/reports/leo-working-state-20260709") +DEFAULT_HANDLER_RECEIPT = REPORT_DIR / "leo-unseen-reasoning-chain-canary-current.json" +DEFAULT_OUT = REPORT_DIR / "telegram-visible-unseen-chain-authorization-packet-current.json" +DEFAULT_MARKDOWN_OUT = REPORT_DIR / "telegram-visible-unseen-chain-authorization-packet-current.md" +DEFAULT_CAPTURE_RECEIPT = REPORT_DIR / "telegram-visible-unseen-chain-capture-receipt-current.json" +DEFAULT_PREFLIGHT = REPORT_DIR / "telegram-visible-unseen-chain-preflight-current.json" +DEFAULT_POSTFLIGHT = REPORT_DIR / "telegram-visible-unseen-chain-postflight-current.json" +DEFAULT_CHAT_ID = "-5146042086" +DEFAULT_CHAT_LABEL = "Leo" +DEFAULT_PROOF_DIR = "outputs/telegram-visible-unseen-chain-20260715" +TOOLING_PATHS = { + "packet_builder": Path("scripts/build_telegram_visible_unseen_chain_packet.py"), + "state_collector": Path("scripts/collect_telegram_visible_unseen_chain_state.py"), + "capture_verifier": Path("scripts/verify_telegram_visible_unseen_chain.py"), +} + + +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def _sha256_bytes(value: bytes) -> str: + return hashlib.sha256(value).hexdigest() + + +def _sha256_file(path: Path) -> str: + return _sha256_bytes(path.read_bytes()) + + +def _canonical_sha256(value: Any) -> str: + encoded = json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8") + return _sha256_bytes(encoded) + + +def current_git_sha(repo_root: Path = Path(".")) -> str: + proc = subprocess.run( + ["git", "rev-parse", "HEAD"], + cwd=repo_root, + text=True, + capture_output=True, + check=False, + ) + return proc.stdout.strip() if proc.returncode == 0 else "" + + +def exact_messages() -> list[dict[str, Any]]: + messages = [] + for sequence, prompt in enumerate(unseen.PROMPTS, start=1): + message = str(prompt["message"]) + messages.append( + { + "sequence": sequence, + "prompt_id": prompt["id"], + "dimension": prompt["dimension"], + "message": message, + "message_sha256": _sha256_bytes(message.encode("utf-8")), + "wait_for_visible_reply_before_next": True, + } + ) + return messages + + +def tooling_binding(repo_root: Path = Path(".")) -> dict[str, dict[str, Any]]: + return {name: {"path": str(path), "sha256": _sha256_file(repo_root / path)} for name, path in TOOLING_PATHS.items()} + + +def _handler_checks(receipt: dict[str, Any]) -> dict[str, bool]: + safety = receipt.get("safety_gate") if isinstance(receipt.get("safety_gate"), dict) else {} + safety_checks = safety.get("checks") if isinstance(safety.get("checks"), dict) else {} + database = receipt.get("database") if isinstance(receipt.get("database"), dict) else {} + prompt_ids = [str(item["id"]) for item in unseen.PROMPTS] + return { + "handler_receipt_passed": receipt.get("status") == "pass", + "handler_receipt_schema_supported": receipt.get("schema") == unseen.SCHEMA, + "handler_prompt_ids_exact": receipt.get("prompt_ids") == prompt_ids, + "handler_outcomes_all_pass": bool(receipt.get("outcomes")) + and all(value is True for value in receipt.get("outcomes", {}).values()), + "handler_checks_all_pass": bool(receipt.get("checks")) + and all(value is True for value in receipt.get("checks", {}).values()), + "handler_safety_gate_passed": safety.get("status") == "pass", + "handler_proved_no_telegram_post": safety_checks.get("no_telegram_post") is True, + "handler_proved_read_only_tool_traces": safety_checks.get("all_tool_traces_read_only_and_bound") is True, + "handler_proved_unchanged_fingerprint": database.get("fingerprint_unchanged") is True + and isinstance(database.get("fingerprint_sha256"), str) + and len(database["fingerprint_sha256"]) == 64, + "handler_tier_remains_below_telegram_visible": "Telegram-visible message delivery" + in (receipt.get("claim_ceiling") or {}).get("not_proven", []), + } + + +def _authorization_text(*, chat_label: str, chat_id: str, messages: list[dict[str, Any]]) -> str: + quoted = " ".join(f"[{item['prompt_id']}] {json.dumps(item['message'], ensure_ascii=True)}" for item in messages) + return ( + "I authorize Codex to use the already-authenticated Chrome Telegram UI to send exactly these three " + f"messages, in order and with no additional messages, to the Telegram group {chat_label} (chat ID " + f"{chat_id}), waiting for Leo's visible reply after each turn: {quoted} Capture the visible replies, " + "message IDs, timestamps, screenshot and accessibility evidence; perform only read-only DB/service " + "readbacks; do not use the Bot API; do not stage, approve, apply, or otherwise mutate knowledge." + ) + + +def build_packet( + *, + handler_receipt_path: Path = DEFAULT_HANDLER_RECEIPT, + chat_id: str = DEFAULT_CHAT_ID, + chat_label: str = DEFAULT_CHAT_LABEL, + proof_dir: str = DEFAULT_PROOF_DIR, + git_sha: str | None = None, +) -> dict[str, Any]: + handler_receipt = _load_json(handler_receipt_path) + messages = exact_messages() + handler_checks = _handler_checks(handler_receipt) + target = { + "platform": "telegram", + "chat_label": chat_label, + "chat_id": chat_id, + "allowed_transport": "authenticated_chrome_telegram_ui", + "forbidden_transports": ["telegram_bot_api", "copied_transcript", "handler_substitution"], + "message_count": len(messages), + "expected_prompt_ids": [item["prompt_id"] for item in messages], + } + handler_binding = { + "path": str(handler_receipt_path), + "sha256": _sha256_file(handler_receipt_path), + "schema": handler_receipt.get("schema"), + "proof_tier": handler_receipt.get("proof_tier"), + "remote_run_id": handler_receipt.get("remote_run_id"), + "deployed_git_head": handler_receipt.get("deployed_git_head"), + "fingerprint_sha256": (handler_receipt.get("database") or {}).get("fingerprint_sha256"), + } + tooling = tooling_binding() + protocol = { + "target": target, + "exact_messages": messages, + "handler_binding": handler_binding, + "tooling_binding": tooling, + "state_contract": { + "preflight": "two identical repeatable-read read-only canonical fingerprints", + "postflight": "same canonical fingerprint plus independent selected-object readback", + "service": "same active gateway process before and after", + }, + } + protocol_sha256 = _canonical_sha256(protocol) + checks = { + **handler_checks, + "exact_three_turn_sequence": len(messages) == 3 and [item["sequence"] for item in messages] == [1, 2, 3], + "exact_prompts_imported_from_handler_verifier": [item["message"] for item in messages] + == [item["message"] for item in unseen.PROMPTS], + "first_prompt_supplies_no_row_id": unseen.UUID_RE.search(messages[0]["message"]) is None, + "authenticated_chrome_is_only_send_transport": target["allowed_transport"] + == "authenticated_chrome_telegram_ui", + "packet_does_not_send": True, + "packet_does_not_mutate_database": True, + } + ready = all(checks.values()) + authorization_text = _authorization_text(chat_label=chat_label, chat_id=chat_id, messages=messages) + proof_paths = { + "preflight_json": str(DEFAULT_PREFLIGHT), + "postflight_json": str(DEFAULT_POSTFLIGHT), + "capture_receipt_json": str(DEFAULT_CAPTURE_RECEIPT), + "capture_json": f"{proof_dir}/capture.json", + "turn_screenshots": [f"{proof_dir}/turn-{index}.png" for index in range(1, 4)], + "turn_accessibility": [f"{proof_dir}/turn-{index}-accessibility.txt" for index in range(1, 4)], + "final_screenshot": f"{proof_dir}/final.png", + "final_accessibility": f"{proof_dir}/final-accessibility.txt", + } + return { + "schema": SCHEMA, + "generated_at_utc": datetime.now(timezone.utc).isoformat(), + "mode": "telegram_visible_unseen_chain_exact_packet_not_sent", + "packet_generation_git_sha": git_sha if git_sha is not None else current_git_sha(), + "protocol_sha256": protocol_sha256, + "ready_to_request_action_time_authorization": ready, + "authorization_gate_status": "ready_to_request_exact_authorization" if ready else "not_ready", + "telegram_visible_messages_sent": False, + "telegram_visible_message_authorization_present": False, + "production_apply_executed": False, + "mutates_db": False, + "target": target, + "exact_messages": messages, + "handler_receipt_binding": handler_binding, + "tooling_binding": tooling, + "checks": checks, + "expected_proof_paths_after_authorized_run": proof_paths, + "explicit_operator_authorization_text": authorization_text, + "clear_CTA": ( + "Reply with the exact authorization sentence in this packet. That sentence names the destination, " + "transport, complete message bodies, ordering, evidence, and no-mutation boundary." + ), + "next_non_user_action_after_authorization": [ + "Re-run the zero-mutation preflight and confirm its packet file hash is current.", + "Use authenticated Chrome only and verify the Leo chat ID before typing anything.", + "Send one exact message, wait for Leo's visible reply, then continue to the next exact message.", + "Capture message IDs, timestamps, screenshot, and accessibility text without copying handler output.", + "Extract the selected DB object reference and run the read-only postflight with that reference.", + "Run the Telegram-visible verifier and accept T3 only if every visible and state check passes.", + ], + "claim_ceiling": { + "current_tier": "T0_spec_plus_prior_T2_handler_evidence", + "required_tier": "T3_live_readonly", + "proven": "The exact three-turn Chrome send packet is bound to a passing no-send handler receipt.", + "not_proven": [ + "Telegram-visible delivery of these three messages", + "Telegram-visible replies from Leo", + "post-send canonical fingerprint equality", + ], + }, + } + + +def write_json(path: Path, data: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def write_markdown(path: Path, data: dict[str, Any]) -> None: + target = data["target"] + lines = [ + "# Telegram-Visible Unseen Chain Authorization Packet", + "", + f"Generated UTC: `{data['generated_at_utc']}`", + f"Protocol SHA256: `{data['protocol_sha256']}`", + f"Ready to request action-time authorization: `{data['ready_to_request_action_time_authorization']}`", + f"Telegram-visible messages sent: `{data['telegram_visible_messages_sent']}`", + f"Mutates DB: `{data['mutates_db']}`", + "", + "## Exact Destination", + "", + f"- Chat: `{target['chat_label']}` (`{target['chat_id']}`)", + f"- Transport: `{target['allowed_transport']}`", + "", + "## Exact Messages", + "", + ] + for item in data["exact_messages"]: + lines.extend( + [ + f"### {item['sequence']}. {item['prompt_id']}", + "", + item["message"], + "", + f"SHA256: `{item['message_sha256']}`", + "", + ] + ) + lines.extend(["## Checks", ""]) + lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(data["checks"].items())) + lines.extend( + [ + "", + "## Exact Action-Time Authorization", + "", + data["explicit_operator_authorization_text"], + "", + "## Clear CTA", + "", + data["clear_CTA"], + "", + "## Claim Ceiling", + "", + f"Current tier: `{data['claim_ceiling']['current_tier']}`", + f"Required tier: `{data['claim_ceiling']['required_tier']}`", + "", + ] + ) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("\n".join(lines), encoding="utf-8") + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--handler-receipt", type=Path, default=DEFAULT_HANDLER_RECEIPT) + parser.add_argument("--chat-id", default=DEFAULT_CHAT_ID) + parser.add_argument("--chat-label", default=DEFAULT_CHAT_LABEL) + parser.add_argument("--proof-dir", default=DEFAULT_PROOF_DIR) + parser.add_argument("--out", type=Path, default=DEFAULT_OUT) + parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + data = build_packet( + handler_receipt_path=args.handler_receipt, + chat_id=args.chat_id, + chat_label=args.chat_label, + proof_dir=args.proof_dir, + ) + write_json(args.out, data) + write_markdown(args.markdown_out, data) + print( + json.dumps( + { + "out": str(args.out), + "markdown_out": str(args.markdown_out), + "ready_to_request_action_time_authorization": data["ready_to_request_action_time_authorization"], + "telegram_visible_messages_sent": False, + }, + indent=2, + sort_keys=True, + ) + ) + return 0 if data["ready_to_request_action_time_authorization"] else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/collect_telegram_visible_unseen_chain_state.py b/scripts/collect_telegram_visible_unseen_chain_state.py new file mode 100644 index 0000000..2558539 --- /dev/null +++ b/scripts/collect_telegram_visible_unseen_chain_state.py @@ -0,0 +1,616 @@ +#!/usr/bin/env python3 +"""Collect fail-closed read-only state for the unseen Telegram chain.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import shlex +import subprocess +from collections.abc import Callable +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +try: + import build_telegram_visible_unseen_chain_packet as packet_builder + import verify_leo_unseen_reasoning_chain as unseen +except ImportError: # pragma: no cover - imported as scripts.* in tests + from scripts import build_telegram_visible_unseen_chain_packet as packet_builder + from scripts import verify_leo_unseen_reasoning_chain as unseen + +SCHEMA = "livingip.telegramVisibleUnseenChainState.v1" +REPORT_DIR = Path("docs/reports/leo-working-state-20260709") +DEFAULT_PACKET = packet_builder.DEFAULT_OUT +DEFAULT_PREFLIGHT_OUT = REPORT_DIR / "telegram-visible-unseen-chain-preflight-current.json" +DEFAULT_PREFLIGHT_MARKDOWN_OUT = REPORT_DIR / "telegram-visible-unseen-chain-preflight-current.md" +DEFAULT_POSTFLIGHT_OUT = REPORT_DIR / "telegram-visible-unseen-chain-postflight-current.json" +DEFAULT_POSTFLIGHT_MARKDOWN_OUT = REPORT_DIR / "telegram-visible-unseen-chain-postflight-current.md" +DEFAULT_SSH_TARGET = "root@77.42.65.182" +DEFAULT_SSH_KEY = Path.home() / ".ssh/livingip_hetzner_20260604_ed25519" +DEFAULT_MANIFEST_SQL = Path("ops/postgres_parity_manifest.sql") +REMOTE_REPO = "/opt/teleo-eval/workspaces/deploy-infra" +SUBJECT_REF_RE = re.compile( + r"^(?:[0-9a-f]{8}|[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})$", + re.IGNORECASE, +) +SHA40_RE = re.compile(r"^[0-9a-f]{40}$") + + +@dataclass(frozen=True) +class CommandResult: + returncode: int + stdout: str + stderr: str + + +Runner = Callable[[list[str], str, int], CommandResult] + + +def subprocess_runner(command: list[str], input_text: str, timeout: int) -> CommandResult: + try: + proc = subprocess.run( + command, + input=input_text, + text=True, + capture_output=True, + timeout=timeout, + check=False, + ) + except subprocess.TimeoutExpired as exc: + return CommandResult(124, str(exc.stdout or ""), f"command timed out after {timeout}s") + return CommandResult(proc.returncode, proc.stdout, proc.stderr) + + +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def _canonical_sha256(value: Any) -> str: + encoded = json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +def _sha256_file(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def parse_key_value_lines(text: str) -> dict[str, str]: + values: dict[str, str] = {} + for line in text.splitlines(): + if "=" not in line: + continue + key, value = line.split("=", 1) + values[key] = value.strip() + return values + + +def parse_manifest_output(raw: str) -> dict[str, Any]: + rows = [] + try: + for line in raw.splitlines(): + candidate = line.strip() + if not candidate or candidate in {"BEGIN", "SET", "ROLLBACK"}: + continue + value = json.loads(candidate) + if isinstance(value, dict): + rows.append(value) + except (TypeError, json.JSONDecodeError) as exc: + return {"status": "error", "error": f"manifest_parse_error:{type(exc).__name__}"} + + tables = { + f"{row['schema']}.{row['table']}": { + "row_count": int(row["row_count"]), + "rowset_md5": row["rowset_md5"], + } + for row in rows + if row.get("kind") == "table" + } + singleton_kinds = { + "schemas", + "extensions", + "application_roles", + "columns", + "constraints", + "indexes", + "sequences", + "views", + "functions", + "triggers", + "types", + "policies", + } + singleton = {str(row["kind"]): row.get("items", []) for row in rows if row.get("kind") in singleton_kinds} + identity_rows = [row for row in rows if row.get("kind") == "identity"] + if len(identity_rows) != 1 or not tables or singleton_kinds - set(singleton): + return {"status": "error", "error": "canonical_database_fingerprint_manifest_incomplete"} + identity = identity_rows[0] + stable = { + "identity": { + key: identity.get(key) + for key in ( + "database", + "server_version_num", + "server_encoding", + "database_collation", + "database_ctype", + "transaction_read_only", + ) + }, + "singleton_sha256": {key: _canonical_sha256(value) for key, value in sorted(singleton.items())}, + "tables": tables, + } + return { + "schema": "livingip.teleoCanonicalDatabaseFingerprint.v1", + "status": "ok", + "fingerprint_sha256": _canonical_sha256(stable), + "table_count": len(tables), + "total_rows": sum(item["row_count"] for item in tables.values()), + "table_rows_sha256": _canonical_sha256(tables), + "structure_sha256": _canonical_sha256(stable["singleton_sha256"]), + "transaction_read_only": identity.get("transaction_read_only"), + } + + +def packet_checks(packet: dict[str, Any]) -> dict[str, bool]: + target = packet.get("target") if isinstance(packet.get("target"), dict) else {} + exact = packet_builder.exact_messages() + expected_tooling = packet_builder.tooling_binding() + return { + "packet_schema_supported": packet.get("schema") == packet_builder.SCHEMA, + "packet_ready_for_authorization_request": packet.get("ready_to_request_action_time_authorization") is True, + "packet_not_sent": packet.get("telegram_visible_messages_sent") is False, + "packet_has_no_authorization_recorded": packet.get("telegram_visible_message_authorization_present") is False, + "packet_does_not_mutate_db": packet.get("mutates_db") is False, + "packet_targets_exact_leo_chat": target.get("chat_label") == packet_builder.DEFAULT_CHAT_LABEL + and target.get("chat_id") == packet_builder.DEFAULT_CHAT_ID, + "packet_requires_authenticated_chrome": target.get("allowed_transport") == "authenticated_chrome_telegram_ui", + "packet_prompt_ids_exact": target.get("expected_prompt_ids") == [item["id"] for item in unseen.PROMPTS], + "packet_messages_exact": [item.get("message") for item in packet.get("exact_messages", [])] + == [item["message"] for item in exact], + "packet_protocol_sha256_present": isinstance(packet.get("protocol_sha256"), str) + and len(packet["protocol_sha256"]) == 64, + "packet_tooling_hashes_match_current_sources": packet.get("tooling_binding") == expected_tooling, + } + + +def ssh_command(args: argparse.Namespace, remote_command: str) -> list[str]: + return [ + "ssh", + "-i", + str(args.ssh_key), + "-o", + "BatchMode=yes", + "-o", + "StrictHostKeyChecking=accept-new", + "-o", + f"ConnectTimeout={args.connect_timeout}", + args.ssh_target, + remote_command, + ] + + +def deploy_readback_command() -> str: + repo = shlex.quote(REMOTE_REPO) + return ( + f"cd {repo} && " + "deploy_head=$(git rev-parse HEAD) && " + "last_deploy_sha=$(cat /opt/teleo-eval/.last-deploy-sha) && " + 'printf \'deploy_head=%s\\nlast_deploy_sha=%s\\n\' "$deploy_head" "$last_deploy_sha" && ' + 'if git merge-base --is-ancestor "$last_deploy_sha" "$deploy_head"; then ' + "printf 'deploy_stamp_ancestor=true\\n'; else printf 'deploy_stamp_ancestor=false\\n'; fi" + ) + + +def service_readback_command() -> str: + return ( + "systemctl show leoclean-gateway.service " + "-p ActiveState -p SubState -p MainPID -p NRestarts -p ExecMainStartTimestamp --no-pager" + ) + + +def fingerprint_readback_command() -> str: + return "docker exec -i teleo-pg psql -U postgres -d teleo -At -q -v ON_ERROR_STOP=1" + + +def subject_readback_sql(subject_ref: str) -> str: + if not SUBJECT_REF_RE.fullmatch(subject_ref): + raise ValueError("subject_ref must be an eight-hex prefix or UUID") + prefix = subject_ref.casefold() + return f""" +\\set ON_ERROR_STOP on +begin transaction isolation level repeatable read read only; +select jsonb_build_object( + 'subject_ref', '{prefix}', + 'matches', coalesce(jsonb_agg(item order by item->>'table', item->'row'->>'id'), '[]'::jsonb) +)::text +from ( + select jsonb_build_object( + 'table', 'public.claims', + 'row', to_jsonb(claim_row), + 'evidence_count', (select count(*) from public.claim_evidence evidence where evidence.claim_id = claim_row.id) + ) as item + from public.claims claim_row + where lower(claim_row.id::text) like '{prefix}%' + union all + select jsonb_build_object( + 'table', 'public.beliefs', + 'row', to_jsonb(belief_row), + 'evidence_count', null + ) as item + from public.beliefs belief_row + where lower(belief_row.id::text) like '{prefix}%' +) matched; +rollback; +""".strip() + + +def _parse_single_json_line(raw: str) -> dict[str, Any] | None: + for line in raw.splitlines(): + candidate = line.strip() + if not candidate or candidate in {"BEGIN", "SET", "ROLLBACK"}: + continue + try: + value = json.loads(candidate) + except json.JSONDecodeError: + continue + if isinstance(value, dict): + return value + return None + + +def collect_remote(args: argparse.Namespace, runner: Runner) -> tuple[dict[str, Any], int, str]: + manifest_sql = args.manifest_sql.read_text(encoding="utf-8") + calls: list[tuple[str, list[str], str, int]] = [ + ("deploy", ssh_command(args, deploy_readback_command()), "", args.timeout), + ("service_before", ssh_command(args, service_readback_command()), "", args.timeout), + ( + "fingerprint_before", + ssh_command(args, fingerprint_readback_command()), + manifest_sql, + args.fingerprint_timeout, + ), + ( + "fingerprint_after", + ssh_command(args, fingerprint_readback_command()), + manifest_sql, + args.fingerprint_timeout, + ), + ("service_after", ssh_command(args, service_readback_command()), "", args.timeout), + ] + if args.subject_ref: + calls.append( + ( + "subject", + ssh_command(args, fingerprint_readback_command()), + subject_readback_sql(args.subject_ref), + args.timeout, + ) + ) + results: dict[str, CommandResult] = {} + attempts: dict[str, int] = {} + for name, command, input_text, timeout in calls: + result = CommandResult(1, "", "not attempted") + for attempt in range(1, max(1, args.ssh_attempts) + 1): + result = runner(command, input_text, timeout) + attempts[name] = attempt + if result.returncode == 0: + break + results[name] = result + deploy_values = parse_key_value_lines(results["deploy"].stdout) + service_before = parse_key_value_lines(results["service_before"].stdout) + service_after = parse_key_value_lines(results["service_after"].stdout) + fingerprint_before = parse_manifest_output(results["fingerprint_before"].stdout) + fingerprint_after = parse_manifest_output(results["fingerprint_after"].stdout) + subject = _parse_single_json_line(results["subject"].stdout) if "subject" in results else None + returncode = next((result.returncode for result in results.values() if result.returncode != 0), 0) + stderr = "\n".join(f"{name}: {result.stderr.strip()}" for name, result in results.items() if result.stderr.strip()) + return ( + { + "deploy_head": deploy_values.get("deploy_head", ""), + "last_deploy_sha": deploy_values.get("last_deploy_sha", ""), + "deploy_stamp_ancestor": deploy_values.get("deploy_stamp_ancestor") == "true", + "workspace_head_matches_deployed_stamp": deploy_values.get("deploy_head") + == deploy_values.get("last_deploy_sha"), + "service_before": service_before, + "service_after": service_after, + "fingerprint_before": fingerprint_before, + "fingerprint_after": fingerprint_after, + "subject_readback": subject, + "readback_attempts": attempts, + }, + returncode, + stderr, + ) + + +def _service_active(state: dict[str, Any]) -> bool: + return state.get("ActiveState") == "active" and state.get("SubState") == "running" + + +def _service_identity(state: dict[str, Any]) -> dict[str, Any]: + return {key: state.get(key) for key in ("MainPID", "NRestarts", "ExecMainStartTimestamp")} + + +def _remote_checks( + remote: dict[str, Any], + *, + returncode: int, + subject_required: bool, + expected_subject_ref: str | None, +) -> dict[str, bool]: + before = remote.get("fingerprint_before") or {} + after = remote.get("fingerprint_after") or {} + service_before = remote.get("service_before") or {} + service_after = remote.get("service_after") or {} + subject = remote.get("subject_readback") or {} + matches = subject.get("matches") if isinstance(subject, dict) else None + checks = { + "ssh_readbacks_succeeded": returncode == 0, + "workspace_head_is_sha": bool(SHA40_RE.fullmatch(str(remote.get("deploy_head") or ""))), + "deployed_stamp_is_sha": bool(SHA40_RE.fullmatch(str(remote.get("last_deploy_sha") or ""))), + "deployed_stamp_is_ancestor_of_workspace_head": remote.get("deploy_stamp_ancestor") is True, + "service_active_before_and_after": _service_active(service_before) and _service_active(service_after), + "service_identity_complete": all( + value + for value in ( + service_before.get("MainPID"), + service_before.get("NRestarts"), + service_before.get("ExecMainStartTimestamp"), + service_after.get("MainPID"), + service_after.get("NRestarts"), + service_after.get("ExecMainStartTimestamp"), + ) + ), + "service_process_unchanged_during_probe": _service_identity(service_before) == _service_identity(service_after), + "first_fingerprint_complete": before.get("status") == "ok" and int(before.get("table_count") or 0) > 0, + "second_fingerprint_complete": after.get("status") == "ok" and int(after.get("table_count") or 0) > 0, + "fingerprints_are_read_only": before.get("transaction_read_only") == "on" + and after.get("transaction_read_only") == "on", + "canonical_fingerprint_unchanged_during_probe": bool(before.get("fingerprint_sha256")) + and before.get("fingerprint_sha256") == after.get("fingerprint_sha256"), + } + if subject_required: + checks.update( + { + "subject_readback_present": isinstance(subject, dict), + "subject_reference_matches_request": subject.get("subject_ref") + == str(expected_subject_ref or "").casefold(), + "subject_resolved_to_exactly_one_db_object": isinstance(matches, list) and len(matches) == 1, + } + ) + return checks + + +def collect_state(args: argparse.Namespace, *, runner: Runner = subprocess_runner) -> dict[str, Any]: + packet = _load_json(args.packet) + local_packet_checks = packet_checks(packet) + remote, remote_returncode, remote_stderr = collect_remote(args, runner) + remote_checks = _remote_checks( + remote, + returncode=remote_returncode, + subject_required=args.phase == "postflight", + expected_subject_ref=args.subject_ref, + ) + baseline = _load_json(args.baseline) if args.baseline else None + baseline_checks: dict[str, bool] = {} + if args.phase == "postflight": + baseline_checks = { + "baseline_supplied": baseline is not None, + "baseline_is_passing_preflight": bool( + baseline and baseline.get("phase") == "preflight" and baseline.get("status") == "pass" + ), + "baseline_protocol_matches_packet": bool( + baseline and baseline.get("protocol_sha256") == packet.get("protocol_sha256") + ), + "baseline_packet_file_matches": bool( + baseline and baseline.get("packet_file_sha256") == _sha256_file(args.packet) + ), + "canonical_fingerprint_matches_preflight": bool( + baseline + and (baseline.get("remote") or {}).get("fingerprint_after", {}).get("fingerprint_sha256") + == remote.get("fingerprint_after", {}).get("fingerprint_sha256") + ), + "gateway_process_matches_preflight": bool( + baseline + and _service_identity((baseline.get("remote") or {}).get("service_after") or {}) + == _service_identity(remote.get("service_after") or {}) + ), + } + status = ( + "pass" + if all(local_packet_checks.values()) and all(remote_checks.values()) and all(baseline_checks.values()) + else "fail" + ) + packet_file_sha256 = _sha256_file(args.packet) + state_token = _canonical_sha256( + { + "phase": args.phase, + "packet_file_sha256": packet_file_sha256, + "protocol_sha256": packet.get("protocol_sha256"), + "deploy_head": remote.get("deploy_head"), + "service": _service_identity(remote.get("service_after") or {}), + "fingerprint": remote.get("fingerprint_after"), + "subject_readback": remote.get("subject_readback"), + } + ) + exact_gate = "" + if status != "pass": + failed = [ + key + for checks in (local_packet_checks, remote_checks, baseline_checks) + for key, value in checks.items() + if value is not True + ] + exact_gate = remote_stderr.strip() or ", ".join(failed) + current_canary = ( + "Read-only pre-send readiness for the exact three-turn unseen Leo chain in Telegram" + if args.phase == "preflight" + else "Read-only post-send DB grounding and unchanged-state proof for the exact Telegram chain" + ) + return { + "schema": SCHEMA, + "generated_at_utc": datetime.now(timezone.utc).isoformat(), + "mode": "telegram_visible_unseen_chain_zero_mutation_state_readback", + "phase": args.phase, + "status": status, + "ready_for_action_time_authorization": args.phase == "preflight" and status == "pass", + "ready_for_visible_capture_verification": args.phase == "postflight" and status == "pass", + "telegram_visible_messages_sent_by_collector": False, + "production_apply_executed": False, + "mutates_db": False, + "packet_path": str(args.packet), + "packet_file_sha256": packet_file_sha256, + "protocol_sha256": packet.get("protocol_sha256"), + "baseline_path": str(args.baseline) if args.baseline else "", + "subject_ref": args.subject_ref or "", + "state_token_sha256": state_token, + "packet_checks": local_packet_checks, + "remote_checks": remote_checks, + "baseline_checks": baseline_checks, + "remote": remote, + "remote_returncode": remote_returncode, + "remote_stderr": remote_stderr, + "blocker_readback": { + "current_canary": current_canary, + "attempted_routes": [ + "local exact packet validation", + "SSH deploy stamp and gateway service readback", + "two canonical repeatable-read read-only Postgres parity manifests", + *(["independent selected-object DB readback"] if args.subject_ref else []), + ], + "exact_gate": exact_gate, + "clear_CTA": ( + "No user action is needed; repair the named readback check and rerun this collector." + if exact_gate + else "Use the packet's exact authorization sentence only when ready to cross the send boundary." + ), + "next_non_user_action": ( + "Run the authenticated-Chrome three-turn flow only after exact authorization, then collect postflight." + if args.phase == "preflight" and status == "pass" + else "Repair the failed preflight check and rerun before requesting authorization." + if args.phase == "preflight" + else "Run the visible capture verifier against this postflight and the retained Chrome evidence." + ), + }, + "claim_ceiling": { + "current_tier": "T3_live_readonly" if status == "pass" else "T0_failed_preflight", + "proven": ( + "Two canonical read-only snapshots and the gateway process were unchanged during this probe." + if status == "pass" + else "No live readiness claim; one or more fail-closed checks did not pass." + ), + "not_proven": [ + "Telegram-visible delivery or reply behavior", + "authorization to send Telegram messages", + "canonical mutation", + ], + }, + } + + +def write_json(path: Path, data: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def write_markdown(path: Path, data: dict[str, Any]) -> None: + remote = data.get("remote") or {} + service = remote.get("service_after") or {} + fingerprint = remote.get("fingerprint_after") or {} + lines = [ + f"# Telegram-Visible Unseen Chain {data['phase'].title()}", + "", + f"Generated UTC: `{data['generated_at_utc']}`", + f"Status: `{data['status']}`", + f"Protocol SHA256: `{data['protocol_sha256']}`", + f"Packet file SHA256: `{data['packet_file_sha256']}`", + f"State token SHA256: `{data['state_token_sha256']}`", + f"Messages sent by collector: `{data['telegram_visible_messages_sent_by_collector']}`", + f"Mutates DB: `{data['mutates_db']}`", + "", + "## Checks", + "", + ] + for section in ("packet_checks", "remote_checks", "baseline_checks"): + for key, value in sorted(data.get(section, {}).items()): + lines.append(f"- `{key}`: `{value}`") + lines.extend( + [ + "", + "## Live Readback", + "", + f"- Deploy head: `{remote.get('deploy_head', '')}`", + f"- Deploy stamp: `{remote.get('last_deploy_sha', '')}`", + f"- Gateway MainPID: `{service.get('MainPID', '')}`", + f"- Gateway NRestarts: `{service.get('NRestarts', '')}`", + f"- Canonical fingerprint: `{fingerprint.get('fingerprint_sha256', '')}`", + f"- Canonical tables: `{fingerprint.get('table_count', '')}`", + f"- Canonical rows: `{fingerprint.get('total_rows', '')}`", + f"- Selected object ref: `{data.get('subject_ref', '')}`", + "", + "## Claim Ceiling", + "", + data["claim_ceiling"]["proven"], + "", + ] + ) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("\n".join(lines), encoding="utf-8") + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--phase", choices=("preflight", "postflight"), default="preflight") + parser.add_argument("--packet", type=Path, default=DEFAULT_PACKET) + parser.add_argument("--baseline", type=Path) + parser.add_argument("--subject-ref") + parser.add_argument("--manifest-sql", type=Path, default=DEFAULT_MANIFEST_SQL) + parser.add_argument("--ssh-target", default=DEFAULT_SSH_TARGET) + parser.add_argument("--ssh-key", type=Path, default=DEFAULT_SSH_KEY) + parser.add_argument("--connect-timeout", type=int, default=10) + parser.add_argument("--timeout", type=int, default=60) + parser.add_argument("--fingerprint-timeout", type=int, default=240) + parser.add_argument("--ssh-attempts", type=int, default=3) + parser.add_argument("--out", type=Path) + parser.add_argument("--markdown-out", type=Path) + args = parser.parse_args(argv) + if args.phase == "postflight" and (not args.baseline or not args.subject_ref): + parser.error("postflight requires --baseline and --subject-ref") + if args.subject_ref and not SUBJECT_REF_RE.fullmatch(args.subject_ref): + parser.error("--subject-ref must be an eight-hex prefix or UUID") + if args.out is None: + args.out = DEFAULT_PREFLIGHT_OUT if args.phase == "preflight" else DEFAULT_POSTFLIGHT_OUT + if args.markdown_out is None: + args.markdown_out = ( + DEFAULT_PREFLIGHT_MARKDOWN_OUT if args.phase == "preflight" else DEFAULT_POSTFLIGHT_MARKDOWN_OUT + ) + return args + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + data = collect_state(args) + write_json(args.out, data) + write_markdown(args.markdown_out, data) + print( + json.dumps( + { + "out": str(args.out), + "markdown_out": str(args.markdown_out), + "phase": args.phase, + "status": data["status"], + "mutates_db": False, + }, + indent=2, + sort_keys=True, + ) + ) + return 0 if data["status"] == "pass" else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/verify_telegram_visible_unseen_chain.py b/scripts/verify_telegram_visible_unseen_chain.py new file mode 100644 index 0000000..8a92450 --- /dev/null +++ b/scripts/verify_telegram_visible_unseen_chain.py @@ -0,0 +1,524 @@ +#!/usr/bin/env python3 +"""Verify retained authenticated-Chrome proof for Leo's unseen Telegram chain.""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +try: + import build_telegram_visible_unseen_chain_packet as packet_builder + import verify_leo_unseen_reasoning_chain as unseen +except ImportError: # pragma: no cover - imported as scripts.* in tests + from scripts import build_telegram_visible_unseen_chain_packet as packet_builder + from scripts import verify_leo_unseen_reasoning_chain as unseen + +SCHEMA = "livingip.telegramVisibleUnseenChainReceipt.v1" +REPORT_DIR = Path("docs/reports/leo-working-state-20260709") +DEFAULT_PACKET = packet_builder.DEFAULT_OUT +DEFAULT_PREFLIGHT = REPORT_DIR / "telegram-visible-unseen-chain-preflight-current.json" +DEFAULT_POSTFLIGHT = REPORT_DIR / "telegram-visible-unseen-chain-postflight-current.json" +DEFAULT_OUT = REPORT_DIR / "telegram-visible-unseen-chain-capture-receipt-current.json" +DEFAULT_MARKDOWN_OUT = REPORT_DIR / "telegram-visible-unseen-chain-capture-receipt-current.md" + + +def _load_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def _sha256_file(path: Path) -> str: + return hashlib.sha256(path.read_bytes()).hexdigest() + + +def _present(value: Any) -> bool: + if value is None: + return False + if isinstance(value, str): + return bool(value.strip()) + if isinstance(value, (dict, list)): + return bool(value) + return True + + +def blank_capture_template(packet: dict[str, Any], preflight: dict[str, Any]) -> dict[str, Any]: + target = packet.get("target") or {} + return { + "protocol_sha256": packet.get("protocol_sha256"), + "preflight_state_token_sha256": preflight.get("state_token_sha256"), + "operator_authorization_text": packet.get("explicit_operator_authorization_text"), + "operator_authorization_captured_at_utc": "", + "extra_messages_sent": 0, + "capture_source": { + "platform": "telegram", + "transport": "authenticated_chrome_telegram_ui", + "chat_label": target.get("chat_label"), + "chat_id": target.get("chat_id"), + "captured_at_utc": "", + "captured_by": "", + "final_screenshot": "", + "final_accessibility": "", + }, + "messages": [ + { + "sequence": item["sequence"], + "prompt_id": item["prompt_id"], + "sent_text": item["message"], + "reply": "", + "sent_at_utc": "", + "reply_at_utc": "", + "telegram_message_id": "", + "reply_message_id": "", + "turn_screenshot": "", + "turn_accessibility": "", + } + for item in packet.get("exact_messages", []) + ], + } + + +def _resolve_evidence(path_value: str, evidence_root: Path) -> dict[str, Any]: + if not path_value.strip(): + return {"path": path_value, "exists": False, "bytes": 0, "sha256": ""} + path = Path(path_value) + if not path.is_absolute(): + path = evidence_root / path + resolved = path.resolve() + exists = resolved.is_file() + return { + "path": str(resolved), + "exists": exists, + "bytes": resolved.stat().st_size if exists else 0, + "sha256": _sha256_file(resolved) if exists else "", + } + + +def _capture_messages(capture: dict[str, Any]) -> list[dict[str, Any]]: + messages = capture.get("messages") + return [item for item in messages if isinstance(item, dict)] if isinstance(messages, list) else [] + + +def _normalized_text(value: str) -> str: + return re.sub(r"\s+", " ", value).strip().casefold() + + +def _accessibility_binds_message(evidence: dict[str, Any], *, sent_text: str, reply: str) -> bool: + if not evidence.get("exists") or not evidence.get("path"): + return False + text = Path(str(evidence["path"])).read_text(encoding="utf-8", errors="replace") + normalized = _normalized_text(text) + return _normalized_text(sent_text) in normalized and _normalized_text(reply) in normalized + + +def _subject_match(postflight: dict[str, Any]) -> dict[str, Any]: + subject = (postflight.get("remote") or {}).get("subject_readback") or {} + matches = subject.get("matches") if isinstance(subject, dict) else [] + return matches[0] if isinstance(matches, list) and len(matches) == 1 else {} + + +def _semantic_checks(replies: list[str], *, subject_ref: str, subject_match: dict[str, Any]) -> dict[str, bool]: + if len(replies) != 3: + return { + "visible_db_object_selected_without_operator_id": False, + "visible_weak_support_explained": False, + "same_subject_survived_challenge": False, + "assumption_support_and_falsifier_separated": False, + "multiple_review_only_candidates_proposed": False, + "objection_revised_candidate": False, + "resolving_source_named": False, + "review_approval_apply_boundary_explicit": False, + "reply_claimed_no_mutation": False, + "independent_db_object_matches_visible_subject": False, + } + first, second, third = replies + first_refs = unseen._short_refs(first) + second_refs = unseen._short_refs(second) + shared_terms = unseen._shared_subject_terms(first, second) + selected = subject_ref[:8].casefold() if subject_ref else "" + visible_subject = bool(selected and (selected in first.casefold() or selected in second.casefold())) + same_subject = bool( + visible_subject + and ( + (selected in first.casefold() and selected in second.casefold()) + or first_refs & second_refs + or len(shared_terms) >= 2 + ) + ) + row = subject_match.get("row") if isinstance(subject_match.get("row"), dict) else {} + row_id = str(row.get("id") or "").casefold() + proposals = unseen._contains(second, "proposal a", "proposal b") or ( + unseen._contains_any(second, "two proposals", "two replacement claims") and "1" in second and "2" in second + ) + return { + "visible_db_object_selected_without_operator_id": unseen._contains_any(first, "claim", "belief", "axiom") + and unseen._contains_any(first, "database", "db", "public.") + and unseen._contains_any(first, "confidence", "support", "evidence"), + "visible_weak_support_explained": unseen._contains_any( + first, + "nothing supports", + "zero evidence", + "no evidence", + "no claim-evidence", + "no_source_pointer", + "no external primary source", + ), + "same_subject_survived_challenge": same_subject, + "assumption_support_and_falsifier_separated": unseen._contains_any(second, "assumption") + and unseen._contains_any(second, "observed support", "evidence") + and unseen._contains_any(second, "falsifier", "counterexample", "would falsify"), + "multiple_review_only_candidates_proposed": proposals + and unseen._contains_any(second, "review", "staged", "proposal") + and unseen._contains_any( + second, + "nothing applied", + "not live knowledge", + "staged only", + "not an apply", + ), + "objection_revised_candidate": unseen._contains_any(third, "revised proposal", "revision") + and unseen._contains(third, "mechanism", "outcome"), + "resolving_source_named": unseen._contains_any( + third, "what would resolve", "source would resolve", "evidence would resolve" + ) + and unseen._contains_any( + third, + "study", + "dataset", + "source", + "randomized", + "quasi-experimental", + "replication", + "ostrom", + ), + "review_approval_apply_boundary_explicit": unseen._contains_any( + third, "review", "pending_review", "human review" + ) + and unseen._contains_any(third, "approve", "approved", "approval") + and unseen._contains_any(third, "apply", "applied_at", "canonical"), + "reply_claimed_no_mutation": unseen._contains_any( + third, + "not an apply", + "nothing staged or changed", + "canonical unchanged", + "staged intent, not knowledge", + "proposal (not an apply)", + ), + "independent_db_object_matches_visible_subject": bool( + visible_subject and row_id and row_id.startswith(selected) + ), + } + + +def verify_capture( + packet: dict[str, Any], + preflight: dict[str, Any], + capture: dict[str, Any] | None, + postflight: dict[str, Any] | None, + *, + packet_path: Path, + evidence_root: Path, +) -> dict[str, Any]: + if capture is None: + return { + "schema": SCHEMA, + "generated_at_utc": datetime.now(timezone.utc).isoformat(), + "mode": "telegram_visible_unseen_chain_capture_verifier", + "status": "awaiting_action_time_authorization", + "receipt_ready": False, + "current_tier": "T0_packet_plus_T3_preflight", + "required_tier": "T3_live_readonly", + "telegram_visible_messages_sent": False, + "mutates_db": False, + "packet_path": str(packet_path), + "preflight_path": str(DEFAULT_PREFLIGHT), + "postflight_path": str(DEFAULT_POSTFLIGHT), + "capture_template": blank_capture_template(packet, preflight), + "checks": { + "packet_ready": packet.get("ready_to_request_action_time_authorization") is True, + "preflight_passed": preflight.get("status") == "pass", + "action_time_authorization_present": False, + "capture_present": False, + "postflight_present": False, + }, + "claim_ceiling": { + "proven": "The exact packet and live zero-mutation preflight are ready.", + "not_proven": [ + "Telegram-visible message delivery", + "Telegram-visible Leo replies", + "post-send fingerprint equality", + ], + }, + } + + messages = _capture_messages(capture) + expected = packet.get("exact_messages") or [] + capture_source = capture.get("capture_source") if isinstance(capture.get("capture_source"), dict) else {} + replies = [str(item.get("reply") or "") for item in messages] + ids = [str(item.get("telegram_message_id") or "") for item in messages] + reply_ids = [str(item.get("reply_message_id") or "") for item in messages] + screenshot = _resolve_evidence(str(capture_source.get("final_screenshot") or ""), evidence_root) + accessibility = _resolve_evidence(str(capture_source.get("final_accessibility") or ""), evidence_root) + turn_evidence = [ + { + "prompt_id": item.get("prompt_id"), + "screenshot": _resolve_evidence(str(item.get("turn_screenshot") or ""), evidence_root), + "accessibility": _resolve_evidence(str(item.get("turn_accessibility") or ""), evidence_root), + } + for item in messages + ] + postflight = postflight or {} + subject_ref = str(postflight.get("subject_ref") or "") + subject_match = _subject_match(postflight) + semantics = _semantic_checks(replies, subject_ref=subject_ref, subject_match=subject_match) + pre_fingerprint = (preflight.get("remote") or {}).get("fingerprint_after") or {} + post_fingerprint = (postflight.get("remote") or {}).get("fingerprint_after") or {} + pre_service = (preflight.get("remote") or {}).get("service_after") or {} + post_service = (postflight.get("remote") or {}).get("service_after") or {} + service_keys = ("MainPID", "NRestarts", "ExecMainStartTimestamp") + handler_binding = packet.get("handler_receipt_binding") or {} + handler_path = Path(str(handler_binding.get("path") or "")) + if not handler_path.is_absolute(): + handler_path = evidence_root / handler_path + expected_ids = [str(item.get("prompt_id") or "") for item in expected] + actual_ids = [str(item.get("prompt_id") or "") for item in messages] + checks = { + "packet_ready": packet.get("ready_to_request_action_time_authorization") is True, + "packet_file_hash_matches_preflight": _sha256_file(packet_path) == preflight.get("packet_file_sha256"), + "handler_receipt_binding_unchanged": handler_path.is_file() + and _sha256_file(handler_path) == handler_binding.get("sha256"), + "preflight_passed": preflight.get("status") == "pass" and preflight.get("phase") == "preflight", + "postflight_passed": postflight.get("status") == "pass" and postflight.get("phase") == "postflight", + "protocol_bound_across_all_artifacts": capture.get("protocol_sha256") + == preflight.get("protocol_sha256") + == postflight.get("protocol_sha256") + == packet.get("protocol_sha256"), + "preflight_state_token_bound_to_capture": capture.get("preflight_state_token_sha256") + == preflight.get("state_token_sha256"), + "exact_action_time_authorization_captured": capture.get("operator_authorization_text") + == packet.get("explicit_operator_authorization_text") + and _present(capture.get("operator_authorization_captured_at_utc")), + "authenticated_chrome_transport_proven": capture_source.get("platform") == "telegram" + and capture_source.get("transport") == "authenticated_chrome_telegram_ui", + "bot_api_not_substituted": "bot_api" not in json.dumps(capture_source).casefold(), + "exact_destination_matches": capture_source.get("chat_label") == (packet.get("target") or {}).get("chat_label") + and capture_source.get("chat_id") == (packet.get("target") or {}).get("chat_id"), + "exact_three_messages_and_no_extras": len(messages) == len(expected) == 3 + and capture.get("extra_messages_sent") == 0, + "prompt_ids_and_sequence_exact": actual_ids == expected_ids + and [item.get("sequence") for item in messages] == [1, 2, 3], + "sent_text_exact": [item.get("sent_text") for item in messages] == [item.get("message") for item in expected], + "visible_replies_nonempty": len(replies) == 3 and all(reply.strip() for reply in replies), + "message_and_reply_ids_present_unique": all(ids) + and all(reply_ids) + and len(set(ids)) == 3 + and len(set(reply_ids)) == 3, + "message_and_reply_timestamps_present": all( + _present(item.get("sent_at_utc")) and _present(item.get("reply_at_utc")) for item in messages + ), + "capture_metadata_present": all( + _present(capture_source.get(key)) for key in ("captured_at_utc", "captured_by") + ), + "visual_evidence_files_present": screenshot["exists"] + and screenshot["bytes"] > 0 + and accessibility["exists"] + and accessibility["bytes"] > 0 + and len(turn_evidence) == 3 + and all( + item["screenshot"]["exists"] + and item["screenshot"]["bytes"] > 0 + and item["accessibility"]["exists"] + and item["accessibility"]["bytes"] > 0 + for item in turn_evidence + ), + "turn_accessibility_binds_exact_messages_and_replies": len(turn_evidence) == len(messages) == 3 + and all( + _accessibility_binds_message( + evidence["accessibility"], + sent_text=str(message.get("sent_text") or ""), + reply=str(message.get("reply") or ""), + ) + for evidence, message in zip(turn_evidence, messages, strict=True) + ), + "canonical_fingerprint_unchanged_from_preflight": bool( + pre_fingerprint.get("fingerprint_sha256") + and pre_fingerprint.get("fingerprint_sha256") == post_fingerprint.get("fingerprint_sha256") + ), + "gateway_process_unchanged_from_preflight": all( + pre_service.get(key) == post_service.get(key) for key in service_keys + ), + **semantics, + } + outcomes = { + "telegram_visible_three_turn_chain": all( + checks[key] + for key in ( + "authenticated_chrome_transport_proven", + "exact_destination_matches", + "exact_three_messages_and_no_extras", + "sent_text_exact", + "visible_replies_nonempty", + "visual_evidence_files_present", + "turn_accessibility_binds_exact_messages_and_replies", + ) + ), + "visible_db_grounded_challenge": all( + checks[key] + for key in ( + "visible_db_object_selected_without_operator_id", + "visible_weak_support_explained", + "same_subject_survived_challenge", + "assumption_support_and_falsifier_separated", + "independent_db_object_matches_visible_subject", + ) + ), + "visible_review_only_revision": all( + checks[key] + for key in ( + "multiple_review_only_candidates_proposed", + "objection_revised_candidate", + "resolving_source_named", + "review_approval_apply_boundary_explicit", + "reply_claimed_no_mutation", + ) + ), + "canonical_state_unchanged": checks["canonical_fingerprint_unchanged_from_preflight"], + } + passed = all(checks.values()) and all(outcomes.values()) + return { + "schema": SCHEMA, + "generated_at_utc": datetime.now(timezone.utc).isoformat(), + "mode": "telegram_visible_unseen_chain_capture_verifier", + "status": "pass" if passed else "fail", + "receipt_ready": passed, + "current_tier": "T3_live_readonly" if passed else "T2_or_lower_incomplete_visible_proof", + "required_tier": "T3_live_readonly", + "telegram_visible_messages_sent": True, + "production_apply_executed": False, + "mutates_db": False, + "packet_path": str(packet_path), + "checks": checks, + "outcomes": outcomes, + "selected_subject": { + "ref": subject_ref, + "table": subject_match.get("table"), + "row_id": (subject_match.get("row") or {}).get("id"), + }, + "evidence": { + "final_screenshot": screenshot, + "final_accessibility": accessibility, + "turn_evidence": turn_evidence, + "telegram_message_ids": ids, + "telegram_reply_ids": reply_ids, + "preflight_state_token_sha256": preflight.get("state_token_sha256"), + "postflight_state_token_sha256": postflight.get("state_token_sha256"), + "canonical_fingerprint_sha256": post_fingerprint.get("fingerprint_sha256"), + }, + "claim_ceiling": { + "proven": ( + "One exact authenticated-Chrome Telegram chain visibly selected and challenged a DB object, " + "proposed and revised review-only knowledge, preserved the approval/apply boundary, and left the " + "canonical fingerprint unchanged." + if passed + else "No T3 claim; at least one visible, semantic, binding, or state check failed." + ), + "not_proven": [ + "canonical proposal staging or apply", + "agent-to-agent review", + "GCP runtime parity for the Telegram-visible chain", + ], + }, + } + + +def write_json(path: Path, data: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def write_markdown(path: Path, data: dict[str, Any]) -> None: + lines = [ + "# Telegram-Visible Unseen Chain Capture Receipt", + "", + f"Generated UTC: `{data['generated_at_utc']}`", + f"Status: `{data['status']}`", + f"Receipt ready: `{data['receipt_ready']}`", + f"Current tier: `{data['current_tier']}`", + f"Required tier: `{data['required_tier']}`", + f"Telegram-visible messages sent: `{data['telegram_visible_messages_sent']}`", + f"Mutates DB: `{data['mutates_db']}`", + "", + "## Checks", + "", + ] + lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(data.get("checks", {}).items())) + if data.get("capture_template"): + lines.extend( + [ + "", + "## Awaiting Authorized Capture", + "", + "The packet and preflight are ready. No Telegram message has been sent by this verifier.", + ] + ) + lines.extend( + [ + "", + "## Claim Ceiling", + "", + data["claim_ceiling"]["proven"], + "", + ] + ) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("\n".join(lines), encoding="utf-8") + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--packet", type=Path, default=DEFAULT_PACKET) + parser.add_argument("--preflight", type=Path, default=DEFAULT_PREFLIGHT) + parser.add_argument("--capture-json", type=Path) + parser.add_argument("--postflight", type=Path, default=DEFAULT_POSTFLIGHT) + parser.add_argument("--evidence-root", type=Path, default=Path(".")) + parser.add_argument("--out", type=Path, default=DEFAULT_OUT) + parser.add_argument("--markdown-out", type=Path, default=DEFAULT_MARKDOWN_OUT) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + packet = _load_json(args.packet) + preflight = _load_json(args.preflight) + capture = _load_json(args.capture_json) if args.capture_json else None + postflight = _load_json(args.postflight) if capture is not None and args.postflight.exists() else None + receipt = verify_capture( + packet, + preflight, + capture, + postflight, + packet_path=args.packet, + evidence_root=args.evidence_root, + ) + write_json(args.out, receipt) + write_markdown(args.markdown_out, receipt) + print( + json.dumps( + { + "out": str(args.out), + "markdown_out": str(args.markdown_out), + "status": receipt["status"], + "receipt_ready": receipt["receipt_ready"], + }, + indent=2, + sort_keys=True, + ) + ) + return 0 if receipt["status"] in {"pass", "awaiting_action_time_authorization"} else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_build_telegram_visible_unseen_chain_packet.py b/tests/test_build_telegram_visible_unseen_chain_packet.py new file mode 100644 index 0000000..5a67f47 --- /dev/null +++ b/tests/test_build_telegram_visible_unseen_chain_packet.py @@ -0,0 +1,64 @@ +from __future__ import annotations + +import json +from pathlib import Path + +from scripts import build_telegram_visible_unseen_chain_packet as packet + + +def test_packet_binds_handler_receipt_and_preserves_exact_messages() -> None: + data = packet.build_packet(git_sha="a" * 40) + + assert data["schema"] == packet.SCHEMA + assert data["ready_to_request_action_time_authorization"] is True + assert data["telegram_visible_messages_sent"] is False + assert data["telegram_visible_message_authorization_present"] is False + assert data["mutates_db"] is False + assert data["target"]["chat_label"] == "Leo" + assert data["target"]["chat_id"] == "-5146042086" + assert data["target"]["allowed_transport"] == "authenticated_chrome_telegram_ui" + assert [item["message"] for item in data["exact_messages"]] == [item["message"] for item in packet.unseen.PROMPTS] + assert [item["sequence"] for item in data["exact_messages"]] == [1, 2, 3] + assert all(len(item["message_sha256"]) == 64 for item in data["exact_messages"]) + assert data["handler_receipt_binding"]["sha256"] == packet._sha256_file(packet.DEFAULT_HANDLER_RECEIPT) + assert data["tooling_binding"] == packet.tooling_binding() + assert all(data["checks"].values()) + + +def test_action_time_authorization_contains_destination_transport_and_full_messages() -> None: + data = packet.build_packet(git_sha="a" * 40) + authorization = data["explicit_operator_authorization_text"] + + assert "Telegram group Leo (chat ID -5146042086)" in authorization + assert "already-authenticated Chrome Telegram UI" in authorization + assert "do not use the Bot API" in authorization + assert "no additional messages" in authorization + for item in data["exact_messages"]: + assert item["prompt_id"] in authorization + assert json.dumps(item["message"], ensure_ascii=True) in authorization + + +def test_packet_fails_closed_when_handler_receipt_no_longer_passes(tmp_path: Path) -> None: + receipt = json.loads(packet.DEFAULT_HANDLER_RECEIPT.read_text(encoding="utf-8")) + receipt["status"] = "fail" + receipt_path = tmp_path / "handler.json" + receipt_path.write_text(json.dumps(receipt), encoding="utf-8") + + data = packet.build_packet(handler_receipt_path=receipt_path, git_sha="a" * 40) + + assert data["ready_to_request_action_time_authorization"] is False + assert data["authorization_gate_status"] == "not_ready" + assert data["checks"]["handler_receipt_passed"] is False + + +def test_markdown_retains_exact_cta_and_no_send_ceiling(tmp_path: Path) -> None: + data = packet.build_packet(git_sha="a" * 40) + out = tmp_path / "packet.md" + + packet.write_markdown(out, data) + + text = out.read_text(encoding="utf-8") + assert "Telegram-Visible Unseen Chain Authorization Packet" in text + assert "Telegram-visible messages sent: `False`" in text + assert "Exact Action-Time Authorization" in text + assert data["exact_messages"][2]["message"] in text diff --git a/tests/test_collect_telegram_visible_unseen_chain_state.py b/tests/test_collect_telegram_visible_unseen_chain_state.py new file mode 100644 index 0000000..a53d59f --- /dev/null +++ b/tests/test_collect_telegram_visible_unseen_chain_state.py @@ -0,0 +1,251 @@ +from __future__ import annotations + +import argparse +import copy +import json +from pathlib import Path + +from scripts import build_telegram_visible_unseen_chain_packet as packet_builder +from scripts import collect_telegram_visible_unseen_chain_state as state + + +def manifest_output(*, row_count: int = 7) -> str: + rows = [ + { + "kind": "identity", + "database": "teleo", + "server_version_num": 160014, + "server_encoding": "UTF8", + "database_collation": "C.UTF-8", + "database_ctype": "C.UTF-8", + "transaction_read_only": "on", + } + ] + for kind in ( + "schemas", + "extensions", + "application_roles", + "columns", + "constraints", + "indexes", + "sequences", + "views", + "functions", + "triggers", + "types", + "policies", + ): + rows.append({"kind": kind, "items": []}) + rows.append( + { + "kind": "table", + "schema": "public", + "table": "claims", + "row_count": row_count, + "rowset_md5": "f" * 32, + } + ) + return "BEGIN\nSET\n" + "\n".join(json.dumps(item) for item in rows) + "\nROLLBACK\n" + + +def write_packet(tmp_path: Path) -> Path: + out = tmp_path / "packet.json" + packet_builder.write_json( + out, + packet_builder.build_packet( + handler_receipt_path=packet_builder.DEFAULT_HANDLER_RECEIPT.resolve(), + git_sha="a" * 40, + ), + ) + return out + + +def args_for(packet: Path, tmp_path: Path, *, phase: str = "preflight") -> argparse.Namespace: + return argparse.Namespace( + phase=phase, + packet=packet, + baseline=None, + subject_ref=None, + manifest_sql=Path("ops/postgres_parity_manifest.sql"), + ssh_target="root@example.invalid", + ssh_key=tmp_path / "key", + connect_timeout=1, + timeout=5, + fingerprint_timeout=5, + ssh_attempts=3, + out=tmp_path / f"{phase}.json", + markdown_out=tmp_path / f"{phase}.md", + ) + + +class GoodRunner: + def __init__(self) -> None: + self.service_calls = 0 + + def __call__(self, command: list[str], input_text: str, _timeout: int) -> state.CommandResult: + remote_command = command[-1] + if "git rev-parse HEAD" in remote_command: + sha = "b" * 40 + return state.CommandResult( + 0, + f"deploy_head={sha}\nlast_deploy_sha={sha}\ndeploy_stamp_ancestor=true\n", + "", + ) + if "systemctl show" in remote_command: + self.service_calls += 1 + return state.CommandResult( + 0, + "\n".join( + [ + "ActiveState=active", + "SubState=running", + "MainPID=123", + "NRestarts=0", + "ExecMainStartTimestamp=Wed 2026-07-15 00:00:00 UTC", + ] + ), + "", + ) + if "'subject_ref'" in input_text: + payload = { + "subject_ref": "c0000000", + "matches": [ + { + "table": "public.beliefs", + "row": {"id": "c0000000-0000-4000-8000-000000000001"}, + "evidence_count": None, + } + ], + } + return state.CommandResult(0, f"BEGIN\n{json.dumps(payload)}\nROLLBACK\n", "") + if "postgres_parity_manifest" not in remote_command and input_text.startswith("\\set ON_ERROR_STOP"): + return state.CommandResult(0, manifest_output(), "") + raise AssertionError(f"unexpected command: {command} / input={input_text[:80]}") + + +def test_preflight_passes_with_two_identical_read_only_fingerprints(tmp_path: Path) -> None: + args = args_for(write_packet(tmp_path), tmp_path) + + result = state.collect_state(args, runner=GoodRunner()) + + assert result["status"] == "pass" + assert result["ready_for_action_time_authorization"] is True + assert result["telegram_visible_messages_sent_by_collector"] is False + assert result["mutates_db"] is False + assert all(result["packet_checks"].values()) + assert all(result["remote_checks"].values()) + assert len(result["state_token_sha256"]) == 64 + + +def test_preflight_fails_when_second_fingerprint_drifts(tmp_path: Path) -> None: + args = args_for(write_packet(tmp_path), tmp_path) + fingerprint_calls = 0 + good = GoodRunner() + + def drift_runner(command: list[str], input_text: str, timeout: int) -> state.CommandResult: + nonlocal fingerprint_calls + if input_text.startswith("\\set ON_ERROR_STOP") and "'subject_ref'" not in input_text: + fingerprint_calls += 1 + return state.CommandResult(0, manifest_output(row_count=7 + fingerprint_calls - 1), "") + return good(command, input_text, timeout) + + result = state.collect_state(args, runner=drift_runner) + + assert result["status"] == "fail" + assert result["remote_checks"]["canonical_fingerprint_unchanged_during_probe"] is False + + +def test_preflight_accepts_newer_workspace_head_when_deployed_stamp_is_ancestor(tmp_path: Path) -> None: + args = args_for(write_packet(tmp_path), tmp_path) + + class AheadRunner(GoodRunner): + def __call__(self, command: list[str], input_text: str, timeout: int) -> state.CommandResult: + if "git rev-parse HEAD" in command[-1]: + return state.CommandResult( + 0, + f"deploy_head={'c' * 40}\nlast_deploy_sha={'b' * 40}\ndeploy_stamp_ancestor=true\n", + "", + ) + return super().__call__(command, input_text, timeout) + + result = state.collect_state(args, runner=AheadRunner()) + + assert result["status"] == "pass" + assert result["remote"]["workspace_head_matches_deployed_stamp"] is False + assert result["remote_checks"]["deployed_stamp_is_ancestor_of_workspace_head"] is True + + +def test_preflight_retries_transient_ssh_failure(tmp_path: Path) -> None: + args = args_for(write_packet(tmp_path), tmp_path) + good = GoodRunner() + deploy_attempts = 0 + + def transient_runner(command: list[str], input_text: str, timeout: int) -> state.CommandResult: + nonlocal deploy_attempts + if "git rev-parse HEAD" in command[-1]: + deploy_attempts += 1 + if deploy_attempts == 1: + return state.CommandResult(255, "", "Connection timed out during banner exchange") + return good(command, input_text, timeout) + + result = state.collect_state(args, runner=transient_runner) + + assert result["status"] == "pass" + assert result["remote"]["readback_attempts"]["deploy"] == 2 + + +def test_preflight_rejects_packet_bound_to_different_tooling_source(tmp_path: Path) -> None: + packet_path = write_packet(tmp_path) + packet = json.loads(packet_path.read_text(encoding="utf-8")) + packet["tooling_binding"]["capture_verifier"]["sha256"] = "0" * 64 + packet_path.write_text(json.dumps(packet), encoding="utf-8") + + result = state.collect_state(args_for(packet_path, tmp_path), runner=GoodRunner()) + + assert result["status"] == "fail" + assert result["packet_checks"]["packet_tooling_hashes_match_current_sources"] is False + + +def test_postflight_binds_baseline_fingerprint_service_and_subject(tmp_path: Path) -> None: + packet = write_packet(tmp_path) + pre_args = args_for(packet, tmp_path) + preflight = state.collect_state(pre_args, runner=GoodRunner()) + baseline = tmp_path / "preflight.json" + state.write_json(baseline, preflight) + post_args = args_for(packet, tmp_path, phase="postflight") + post_args.baseline = baseline + post_args.subject_ref = "c0000000" + + postflight = state.collect_state(post_args, runner=GoodRunner()) + + assert postflight["status"] == "pass" + assert postflight["ready_for_visible_capture_verification"] is True + assert all(postflight["baseline_checks"].values()) + assert postflight["remote_checks"]["subject_resolved_to_exactly_one_db_object"] is True + + +def test_postflight_rejects_baseline_fingerprint_mismatch(tmp_path: Path) -> None: + packet = write_packet(tmp_path) + pre_args = args_for(packet, tmp_path) + preflight = state.collect_state(pre_args, runner=GoodRunner()) + preflight = copy.deepcopy(preflight) + preflight["remote"]["fingerprint_after"]["fingerprint_sha256"] = "0" * 64 + baseline = tmp_path / "preflight.json" + state.write_json(baseline, preflight) + post_args = args_for(packet, tmp_path, phase="postflight") + post_args.baseline = baseline + post_args.subject_ref = "c0000000" + + postflight = state.collect_state(post_args, runner=GoodRunner()) + + assert postflight["status"] == "fail" + assert postflight["baseline_checks"]["canonical_fingerprint_matches_preflight"] is False + + +def test_subject_sql_rejects_untrusted_input() -> None: + try: + state.subject_readback_sql("c0000000'; delete from public.claims; --") + except ValueError as exc: + assert "subject_ref" in str(exc) + else: + raise AssertionError("unsafe subject ref was accepted") diff --git a/tests/test_verify_telegram_visible_unseen_chain.py b/tests/test_verify_telegram_visible_unseen_chain.py new file mode 100644 index 0000000..cbd6310 --- /dev/null +++ b/tests/test_verify_telegram_visible_unseen_chain.py @@ -0,0 +1,240 @@ +from __future__ import annotations + +import copy +from pathlib import Path + +from scripts import build_telegram_visible_unseen_chain_packet as packet_builder +from scripts import verify_telegram_visible_unseen_chain as verifier + +SUBJECT_ID = "c0000000-0000-4000-8000-000000000001" + + +def packet_and_path(tmp_path: Path) -> tuple[dict, Path]: + packet = packet_builder.build_packet( + handler_receipt_path=packet_builder.DEFAULT_HANDLER_RECEIPT.resolve(), + git_sha="a" * 40, + ) + path = tmp_path / "packet.json" + packet_builder.write_json(path, packet) + return packet, path + + +def preflight(packet: dict, packet_path: Path) -> dict: + return { + "phase": "preflight", + "status": "pass", + "protocol_sha256": packet["protocol_sha256"], + "packet_file_sha256": verifier._sha256_file(packet_path), + "state_token_sha256": "1" * 64, + "remote": { + "fingerprint_after": {"fingerprint_sha256": "f" * 64}, + "service_after": { + "MainPID": "123", + "NRestarts": "0", + "ExecMainStartTimestamp": "Wed 2026-07-15 00:00:00 UTC", + }, + }, + } + + +def postflight(packet: dict) -> dict: + return { + "phase": "postflight", + "status": "pass", + "protocol_sha256": packet["protocol_sha256"], + "state_token_sha256": "2" * 64, + "subject_ref": "c0000000", + "remote": { + "fingerprint_after": {"fingerprint_sha256": "f" * 64}, + "service_after": { + "MainPID": "123", + "NRestarts": "0", + "ExecMainStartTimestamp": "Wed 2026-07-15 00:00:00 UTC", + }, + "subject_readback": { + "subject_ref": "c0000000", + "matches": [ + { + "table": "public.beliefs", + "row": {"id": SUBJECT_ID, "statement": "Coordination is the bottleneck."}, + } + ], + }, + }, + } + + +def good_capture(packet: dict, tmp_path: Path) -> dict: + screenshot = tmp_path / "final.png" + accessibility = tmp_path / "final.txt" + screenshot.write_bytes(b"png proof") + accessibility.write_text("telegram accessibility proof", encoding="utf-8") + replies = [ + ( + "The database public.beliefs object c0000000 is an axiom with confidence 0.85. What supports it in " + "the DB: nothing supports it; there are zero evidence rows and no external primary source." + ), + ( + "Claim c0000000 is still the coordination bottleneck belief. Assumption versus observed support: the " + "priority leap is assumed and the evidence is absent. Falsifier: a contrary case. Proposal A and " + "Proposal B are staged only for review; nothing applied and neither is live knowledge." + ), + ( + "Revised proposal: separate the mechanism from the business outcome. What would resolve the " + "disagreement: a randomized study and its primary source dataset. Keep it pending_review for human " + "review; approved is not apply and applied_at remains null. This is not an apply." + ), + ] + messages = [] + for item, reply in zip(packet["exact_messages"], replies, strict=True): + sequence = item["sequence"] + turn_screenshot = tmp_path / f"turn-{sequence}.png" + turn_accessibility = tmp_path / f"turn-{sequence}.txt" + turn_screenshot.write_bytes(f"turn {sequence} screenshot".encode()) + turn_accessibility.write_text( + f"Telegram message: {item['message']}\nLeo reply: {reply}\n", + encoding="utf-8", + ) + messages.append( + { + "sequence": sequence, + "prompt_id": item["prompt_id"], + "sent_text": item["message"], + "reply": reply, + "sent_at_utc": f"2026-07-15T00:0{sequence}:00Z", + "reply_at_utc": f"2026-07-15T00:0{sequence}:20Z", + "telegram_message_id": f"sent-{sequence}", + "reply_message_id": f"reply-{sequence}", + "turn_screenshot": str(turn_screenshot), + "turn_accessibility": str(turn_accessibility), + } + ) + return { + "protocol_sha256": packet["protocol_sha256"], + "preflight_state_token_sha256": "1" * 64, + "operator_authorization_text": packet["explicit_operator_authorization_text"], + "operator_authorization_captured_at_utc": "2026-07-15T00:00:00Z", + "extra_messages_sent": 0, + "capture_source": { + "platform": "telegram", + "transport": "authenticated_chrome_telegram_ui", + "chat_label": "Leo", + "chat_id": "-5146042086", + "captured_at_utc": "2026-07-15T00:05:00Z", + "captured_by": "codex", + "final_screenshot": str(screenshot), + "final_accessibility": str(accessibility), + }, + "messages": messages, + } + + +def test_no_capture_returns_awaiting_authorization_template(tmp_path: Path) -> None: + packet, packet_path = packet_and_path(tmp_path) + + receipt = verifier.verify_capture( + packet, + preflight(packet, packet_path), + None, + None, + packet_path=packet_path, + evidence_root=tmp_path, + ) + + assert receipt["status"] == "awaiting_action_time_authorization" + assert receipt["telegram_visible_messages_sent"] is False + assert receipt["capture_template"]["messages"][0]["prompt_id"] == "OOS-CHAIN-01" + + +def test_good_chrome_capture_and_unchanged_fingerprint_pass_t3(tmp_path: Path) -> None: + packet, packet_path = packet_and_path(tmp_path) + before = preflight(packet, packet_path) + + receipt = verifier.verify_capture( + packet, + before, + good_capture(packet, tmp_path), + postflight(packet), + packet_path=packet_path, + evidence_root=tmp_path, + ) + + assert receipt["status"] == "pass" + assert receipt["receipt_ready"] is True + assert receipt["current_tier"] == "T3_live_readonly" + assert all(receipt["checks"].values()) + assert all(receipt["outcomes"].values()) + assert receipt["selected_subject"]["row_id"] == SUBJECT_ID + + +def test_bot_api_capture_is_rejected(tmp_path: Path) -> None: + packet, packet_path = packet_and_path(tmp_path) + capture = good_capture(packet, tmp_path) + capture["capture_source"]["transport"] = "telegram_bot_api" + + receipt = verifier.verify_capture( + packet, + preflight(packet, packet_path), + capture, + postflight(packet), + packet_path=packet_path, + evidence_root=tmp_path, + ) + + assert receipt["status"] == "fail" + assert receipt["checks"]["authenticated_chrome_transport_proven"] is False + assert receipt["checks"]["bot_api_not_substituted"] is False + + +def test_postflight_fingerprint_drift_is_rejected(tmp_path: Path) -> None: + packet, packet_path = packet_and_path(tmp_path) + after = copy.deepcopy(postflight(packet)) + after["remote"]["fingerprint_after"]["fingerprint_sha256"] = "0" * 64 + + receipt = verifier.verify_capture( + packet, + preflight(packet, packet_path), + good_capture(packet, tmp_path), + after, + packet_path=packet_path, + evidence_root=tmp_path, + ) + + assert receipt["status"] == "fail" + assert receipt["checks"]["canonical_fingerprint_unchanged_from_preflight"] is False + + +def test_message_text_drift_is_rejected(tmp_path: Path) -> None: + packet, packet_path = packet_and_path(tmp_path) + capture = good_capture(packet, tmp_path) + capture["messages"][1]["sent_text"] += " extra" + + receipt = verifier.verify_capture( + packet, + preflight(packet, packet_path), + capture, + postflight(packet), + packet_path=packet_path, + evidence_root=tmp_path, + ) + + assert receipt["status"] == "fail" + assert receipt["checks"]["sent_text_exact"] is False + + +def test_copied_reply_not_present_in_accessibility_is_rejected(tmp_path: Path) -> None: + packet, packet_path = packet_and_path(tmp_path) + capture = good_capture(packet, tmp_path) + capture["messages"][0]["reply"] += " copied-only suffix" + + receipt = verifier.verify_capture( + packet, + preflight(packet, packet_path), + capture, + postflight(packet), + packet_path=packet_path, + evidence_root=tmp_path, + ) + + assert receipt["status"] == "fail" + assert receipt["checks"]["turn_accessibility_binds_exact_messages_and_replies"] is False From db64500a12a64206e1bb7fd0c9a0a21bca755c3d Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Wed, 15 Jul 2026 03:59:47 +0200 Subject: [PATCH 02/18] feat: rehearse approved proposal apply and rollback --- .../proposal-apply-clone-rollback-current.sql | 120 + ...oposal-apply-lifecycle-canary-current.json | 3324 +++++++++++++++++ ...posal-apply-lifecycle-receipt-current.json | 217 ++ ...ly-production-target-readback-current.json | 32 + pyproject.toml | 1 + scripts/apply_proposal.py | 369 +- scripts/apply_worker.py | 200 +- .../finalize_approve_claim_isolated_canary.py | 230 ++ scripts/kb_apply_replay_receipt.py | 35 + scripts/proposal_apply_lifecycle.py | 1513 ++++++++ scripts/run_approve_claim_clone_canary.py | 857 ++--- ...approve_claim_isolated_container_canary.sh | 268 +- tests/test_apply_proposal.py | 42 +- tests/test_apply_worker.py | 312 +- tests/test_proposal_apply_lifecycle.py | 846 +++++ ...n_approve_claim_clone_canary_macos_path.py | 82 +- 16 files changed, 7507 insertions(+), 941 deletions(-) create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json create mode 100644 scripts/finalize_approve_claim_isolated_canary.py create mode 100644 scripts/proposal_apply_lifecycle.py create mode 100644 tests/test_proposal_apply_lifecycle.py diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql b/docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql new file mode 100644 index 0000000..299b52b --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-clone-rollback-current.sql @@ -0,0 +1,120 @@ +begin; +set local standard_conforming_strings = on; +set local lock_timeout = '5s'; +select pg_advisory_xact_lock(hashtextextended('proposal-rollback:' || E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946', 0)); +do $rollback$ +declare + affected integer; + approval_now jsonb; +begin + perform 1 from kb_stage.kb_proposals + where id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid + for update; + if not found then + raise exception 'proposal rollback: proposal row missing'; + end if; + if not exists ( + select 1 from kb_stage.kb_proposals + where id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid + and proposal_type = 'approve_claim' + and status = 'applied' + and payload = E'{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural"},{"confidence":0.85,"created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept"}],"contract_version":2,"edges":[{"created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}],"evidence":[{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_by":null,"role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9},{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_by":null,"role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}]}}'::jsonb + and reviewed_by_handle is not distinct from E'm3ta' + and reviewed_by_agent_id is not distinct from E'11111111-1111-4111-8111-111111111111'::uuid + and reviewed_at is not distinct from E'2026-07-15 01:59:03.105846+00'::timestamptz + and review_note is not distinct from E'Reviewed exact strict payload inside the disposable clone.' + and applied_by_handle is not distinct from E'kb-apply' + and applied_by_agent_id is not distinct from E'44444444-4444-4444-4444-444444444444'::uuid + and applied_at is not distinct from E'2026-07-15 01:59:04.449051+00'::timestamptz + ) then + raise exception 'proposal rollback: applied ledger does not match apply receipt'; + end if; + select jsonb_build_object( + 'proposal_id', proposal_id::text, + 'proposal_type', proposal_type, + 'payload', payload, + 'reviewed_by_handle', reviewed_by_handle, + 'reviewed_by_agent_id', reviewed_by_agent_id::text, + 'reviewed_by_db_role', reviewed_by_db_role::text, + 'reviewed_at', reviewed_at::text, + 'review_note', review_note + ) into approval_now + from kb_stage.kb_proposal_approvals + where proposal_id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid; + if approval_now is distinct from E'{"payload":{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural"},{"confidence":0.85,"created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept"}],"contract_version":2,"edges":[{"created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}],"evidence":[{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_by":null,"role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9},{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_by":null,"role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}]}},"proposal_id":"9b2b5fa4-0e0d-5db0-aa4d-6fd102889946","proposal_type":"approve_claim","review_note":"Reviewed exact strict payload inside the disposable clone.","reviewed_at":"2026-07-15 01:59:03.105846+00","reviewed_by_agent_id":"11111111-1111-4111-8111-111111111111","reviewed_by_db_role":"kb_review","reviewed_by_handle":"m3ta"}'::jsonb then + raise exception 'proposal rollback: immutable approval snapshot drifted'; + end if; + if (select count(*) from public.claim_evidence t + where t.id = E'dac3934f-b7ad-5205-8efe-a00163370a54'::uuid and to_jsonb(t) = E'{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"dac3934f-b7ad-5205-8efe-a00163370a54","role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claim_evidence[0] row does not match apply receipt'; + end if; + if (select count(*) from public.claim_evidence t + where t.id = E'13e580c5-458d-5146-aef3-8c74ffc43b7c'::uuid and to_jsonb(t) = E'{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"13e580c5-458d-5146-aef3-8c74ffc43b7c","role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claim_evidence[1] row does not match apply receipt'; + end if; + if (select count(*) from public.claim_edges t + where t.id = E'b28d8770-9ac9-5b5f-bfe2-7280d7422a21'::uuid and to_jsonb(t) = E'{"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","id":"b28d8770-9ac9-5b5f-bfe2-7280d7422a21","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claim_edges[0] row does not match apply receipt'; + end if; + if (select count(*) from public.reasoning_tools t + where t.id = E'2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0'::uuid and to_jsonb(t) = E'{"agent_id":null,"category":"verification","created_at":"2026-07-15T01:59:04.44165+00:00","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: reasoning_tools[0] row does not match apply receipt'; + end if; + if (select count(*) from public.sources t + where t.id = E'ed9da5f1-846e-5298-8689-15027a7ce56d'::uuid and to_jsonb(t) = E'{"captured_at":null,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: sources[0] row does not match apply receipt'; + end if; + if (select count(*) from public.sources t + where t.id = E'a86f55dc-5058-585d-b492-a4f1d932670f'::uuid and to_jsonb(t) = E'{"captured_at":null,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: sources[1] row does not match apply receipt'; + end if; + if (select count(*) from public.claims t + where t.id = E'71a3331b-fb75-5e18-aa61-b256bc38af36'::uuid and to_jsonb(t) = E'{"confidence":0.85,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept","updated_at":"2026-07-15T01:59:04.44165+00:00"}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claims[0] row does not match apply receipt'; + end if; + if (select count(*) from public.claims t + where t.id = E'38dead14-6b1d-5909-99c4-3c45ad2dd21f'::uuid and to_jsonb(t) = E'{"confidence":0.9,"created_at":"2026-07-15T01:59:04.44165+00:00","created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural","updated_at":"2026-07-15T01:59:04.44165+00:00"}'::jsonb) <> 1 then + raise exception 'proposal rollback drift: claims[1] row does not match apply receipt'; + end if; + delete from public.claim_evidence where id in (E'dac3934f-b7ad-5205-8efe-a00163370a54'::uuid, E'13e580c5-458d-5146-aef3-8c74ffc43b7c'::uuid); + get diagnostics affected = row_count; + if affected <> 2 then + raise exception 'proposal rollback count mismatch for claim_evidence: expected 2, got %', affected; + end if; + delete from public.claim_edges where id in (E'b28d8770-9ac9-5b5f-bfe2-7280d7422a21'::uuid); + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback count mismatch for claim_edges: expected 1, got %', affected; + end if; + delete from public.reasoning_tools where id in (E'2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0'::uuid); + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback count mismatch for reasoning_tools: expected 1, got %', affected; + end if; + delete from public.sources where id in (E'ed9da5f1-846e-5298-8689-15027a7ce56d'::uuid, E'a86f55dc-5058-585d-b492-a4f1d932670f'::uuid); + get diagnostics affected = row_count; + if affected <> 2 then + raise exception 'proposal rollback count mismatch for sources: expected 2, got %', affected; + end if; + delete from public.claims where id in (E'71a3331b-fb75-5e18-aa61-b256bc38af36'::uuid, E'38dead14-6b1d-5909-99c4-3c45ad2dd21f'::uuid); + get diagnostics affected = row_count; + if affected <> 2 then + raise exception 'proposal rollback count mismatch for claims: expected 2, got %', affected; + end if; + update kb_stage.kb_proposals + set status = 'approved', + applied_by_handle = null, + applied_by_agent_id = null, + applied_at = null, + updated_at = now() + where id = E'9b2b5fa4-0e0d-5db0-aa4d-6fd102889946'::uuid + and status = 'applied' + and payload = E'{"apply_payload":{"agent_id":null,"claims":[{"confidence":0.9,"created_by":null,"id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","status":"open","superseded_by":null,"tags":["working-leo","clone-canary"],"text":"An approved strict proposal can create exact canonical rows atomically.","type":"structural"},{"confidence":0.85,"created_by":null,"id":"71a3331b-fb75-5e18-aa61-b256bc38af36","status":"open","superseded_by":null,"tags":["working-leo","row-proof"],"text":"Row-level proof is the authority for canonical KB state.","type":"concept"}],"contract_version":2,"edges":[{"created_by":null,"edge_type":"supports","from_claim":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","to_claim":"71a3331b-fb75-5e18-aa61-b256bc38af36","weight":0.75}],"evidence":[{"claim_id":"38dead14-6b1d-5909-99c4-3c45ad2dd21f","created_by":null,"role":"grounds","source_id":"ed9da5f1-846e-5298-8689-15027a7ce56d","weight":0.9},{"claim_id":"71a3331b-fb75-5e18-aa61-b256bc38af36","created_by":null,"role":"illustrates","source_id":"a86f55dc-5058-585d-b492-a4f1d932670f","weight":0.8}],"reasoning_tools":[{"agent_id":null,"category":"verification","description":"Verify approved proposal to canonical graph lifecycle in a disposable clone.","id":"2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0","name":"Canonical row proof canary"}],"sources":[{"created_by":null,"excerpt":"Disposable clone lifecycle canary source A.","hash":"approve-claim-canary-08c2358330-a","id":"ed9da5f1-846e-5298-8689-15027a7ce56d","source_type":"observation","storage_path":null,"url":null},{"created_by":null,"excerpt":"Disposable clone lifecycle canary source B.","hash":"approve-claim-canary-08c2358330-b","id":"a86f55dc-5058-585d-b492-a4f1d932670f","source_type":"observation","storage_path":null,"url":null}]}}'::jsonb + and applied_at is not distinct from E'2026-07-15 01:59:04.449051+00'::timestamptz; + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback: expected one applied ledger row, got %', affected; + end if; +end +$rollback$; +commit; diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json new file mode 100644 index 0000000..d494493 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-canary-current.json @@ -0,0 +1,3324 @@ +{ + "applied_rollback_receipt": { + "applied_row_ids": { + "claim_edges": [ + "b28d8770-9ac9-5b5f-bfe2-7280d7422a21" + ], + "claim_evidence": [ + "13e580c5-458d-5146-aef3-8c74ffc43b7c", + "dac3934f-b7ad-5205-8efe-a00163370a54" + ], + "claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + }, + "applied_row_sha256": { + "claim_edges": [ + "60d14cbc45d424fa1eb62db6478f185bf2c2d86559e57ffa77474e8d0b3c527c" + ], + "claim_evidence": [ + "e97de162c0730da69b357e1350f0da7e5843c4e67d498c12d731a0f04a8868d7", + "3054718195abe3f78e08cfcacd59902d41eb91c7153c6c63f9bcb3bc7c6902ac" + ], + "claims": [ + "e593b9cf7b0bc9267f3e48d8173129b84b09c028f290f1abc97ccb73940b31dc", + "24280b5159cbbf75e00eb24ae50ab69994397a583f347a241a5e262cf0c03179" + ], + "reasoning_tools": [ + "257924b236c3c27289b9ae2314687be0e96779415fc6ff87faada22893b757ba" + ], + "sources": [ + "d7a690c2b18701db5b48e8189b1621c8025c1ed75889a4d0a3a4c9761c03dc3b", + "bee048794799006bd00bf4bb02ae57998e670f5bcdfb140dff871dcb042d0198" + ] + }, + "apply_payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684", + "apply_receipt_sha256": "2aa65fe4509c6699af34e418de27c0225efe751223aaebf3f9b545fa159c754e", + "apply_sql_sha256": "1a7d65870e415ee8891b6dd84732a29bcee00412c10862d3d1db4b191a8ade38", + "approval_snapshot_sha256": "310b54030e26ed43d7226f2e803610586e80d21ff064c67e11ea21e93ef40bd9", + "artifact": "proposal_apply_lifecycle_receipt", + "checks": { + "apply_receipt_replay_ready": true, + "apply_rows_have_exact_ids": true, + "fresh_owned_targets": true, + "immutable_approval_unchanged": true, + "rollback_counts_restored": true, + "rollback_ledger_restored_to_approved": true, + "rollback_replay_refused_without_mutation": true, + "rollback_sql_exact_for_bound_rows": true, + "rollback_target_rows_absent": true, + "unrelated_rows_unchanged": true + }, + "current_tier": "T2_runtime", + "fresh_preflight_artifact_sha256": "1d5e7eed7355aa94ecfa213422e6178aecdeee70628994596e852bd7569d6a2d", + "fresh_preflight_sha256": "29a80dc9422af45fd067e98621442d31c60161f34deb5845aa410659a9c5de3a", + "generated_at_utc": "2026-07-15T01:59:05.526171+00:00", + "lifecycle_receipt_sha256": "8e74cf1f5c372633f9b6f15363ff759ba6ce9bf42df51288bb9be70ddabe49b3", + "pass": true, + "production_apply_authorization_present": false, + "production_apply_executed": false, + "proposal_before_apply": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "proposal_payload_sha256": "6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af", + "required_tier": "T2_runtime", + "rollback": { + "counts_after_rollback": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "counts_before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "delete_order": [ + "claim_evidence", + "claim_edges", + "reasoning_tools", + "sources", + "claims" + ], + "proposal_after": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3", + "schema": "livingip.proposalApplyRollback.v1", + "target_rows_after": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + } + }, + "schema": "livingip.proposalApplyLifecycleReceipt.v1", + "strongest_claim_allowed": "deterministic disposable-clone apply and compensating rollback" + }, + "apply_transition": { + "applied_readback": { + "applied_at": "2026-07-15 01:59:04.449051+00", + "applied_by_agent_id": "44444444-4444-4444-4444-444444444444", + "applied_by_handle": "kb-apply", + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "applied" + }, + "approved_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "exact_transition": true, + "immutable_approval_matches_applied_ledger": true, + "immutable_approval_readback": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + } + }, + "checks": { + "apply_transition_exact": true, + "claim_evidence_exact_after_permission_probe": true, + "clone_apply_table_deltas_exact": true, + "clone_cleanup_complete": true, + "clone_prerequisites_omit_cluster_role_ddl": true, + "compensating_rollback_receipt_passes": true, + "conflict_review_separate_and_exact": true, + "conflict_transaction_rolled_back_exactly": true, + "first_apply_succeeded": true, + "fresh_owned_preflight_exact": true, + "gateway_process_unchanged": null, + "idempotent_replay_refused": true, + "kb_apply_cannot_mutate_immutable_approval": true, + "kb_apply_cannot_mutate_proposal_approval": true, + "kb_apply_cannot_mutate_proposal_payload": true, + "kb_apply_cannot_update_existing_claim_evidence": true, + "kb_review_approval_succeeded": true, + "kb_review_used_security_definer": true, + "mutation_between_build_and_execute_refused": true, + "outer_container_absent_independent_readback": true, + "outer_container_has_no_docker_volumes": true, + "outer_container_lifecycle_labeled": true, + "outer_container_network_disabled": true, + "outer_isolation_complete": true, + "outer_labeled_container_absent_independent_readback": true, + "outer_live_counts_exactly_unchanged": true, + "outer_live_table_deltas_exactly_zero": true, + "outer_service_observed_if_required": true, + "outer_service_stable_if_observed": true, + "outer_source_tier_enforced": true, + "outer_workdir_absent_independent_readback": true, + "payload_agents_seeded_exactly": true, + "payload_projection_exact": true, + "proposal_exact_after_permission_probes": true, + "review_transition_exact": true, + "reviewer_agent_seeded_exactly": true, + "rollback_replay_refused_without_mutation": true, + "security_definer_dependencies_ready": true, + "source_binding_independently_verified": true, + "source_cluster_roles_preexisting": true, + "source_cluster_roles_unchanged": true, + "source_files_unchanged_during_run": true, + "source_hash_conflict_refused": true, + "source_table_deltas_exactly_zero": true, + "source_target_rows_unchanged": true, + "stale_ephemeral_paths_absent": true + }, + "cleanup_readback": { + "drop_attempted": true, + "leftover_clone_database_count": 0, + "returncode": 0, + "stderr": "", + "stdout": "pg_terminate_backend \n----------------------\n(0 rows)\n\nDROP DATABASE" + }, + "cleanup_returncode": 0, + "clone_apply_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + }, + "clone_counts_after_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 1, + "public.claim_evidence": 3, + "public.claims": 3, + "public.reasoning_tools": 1, + "public.sources": 3 + }, + "clone_counts_before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "clone_created": true, + "clone_database": "teleo_approve_claim_isolated_08c2358330", + "clone_dropped": true, + "clone_prerequisite_sanitization": { + "clone_sql_sha256": "a02a03a704954058745d8cee4d25597bb8eaeee2ae211d4f9be4fe055f3d98ec", + "cluster_role_ddl_removed": true, + "removed_alter_role_statements": 3, + "removed_create_role_statements": 2, + "source_sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa" + }, + "conflict_apply": { + "returncode": 1, + "source_hash_collision_refusal": true, + "stderr": "psql failed (3)\nSTDOUT:\n\n\nSTDERR:\nERROR: approve_claim: source hash collision for source 980ac481-1fa3-508f-b093-95de4ff4f8bc\nCONTEXT: PL/pgSQL function inline_code_block line 6 at RAISE" + }, + "conflict_readback": { + "canonical_rows": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "canonical_rows_absent": true, + "immutable_approval": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "7cb1468f-861c-5a85-ae32-470d891af133", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_unchanged": true, + "proposal": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "7cb1468f-861c-5a85-ae32-470d891af133", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-conflict-canary", + "status": "approved" + }, + "proposal_approval_unchanged": true + }, + "conflict_review_transition": { + "approved_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "7cb1468f-861c-5a85-ae32-470d891af133", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-conflict-canary", + "status": "approved" + }, + "exact_transition": true, + "immutable_approval_matches_ledger": true, + "immutable_approval_readback": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "7cb1468f-861c-5a85-ae32-470d891af133", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:06.046049+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "pending_immutable_approval": null, + "pending_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "7cb1468f-861c-5a85-ae32-470d891af133", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.5, + "created_by": null, + "id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "status": "open", + "superseded_by": null, + "tags": [ + "conflict-canary" + ], + "text": "This row must roll back with the conflicting source.", + "type": "structural" + } + ], + "contract_version": 2, + "edges": [], + "evidence": [], + "reasoning_tools": [], + "sources": [ + { + "created_by": null, + "excerpt": "Conflicting source id.", + "hash": "approve-claim-permission-probe-c4893cfb-8725-510e-ae01-ef01e8c99723", + "id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": null, + "reviewed_at": null, + "reviewed_by_agent_id": null, + "reviewed_by_handle": null, + "source_ref": "working-leo:approve-claim-conflict-canary", + "status": "pending_review" + }, + "review_apply": { + "returncode": 0, + "stderr": "", + "stdout": "{\"id\": \"7cb1468f-861c-5a85-ae32-470d891af133\", \"proposal_type\": \"approve_claim\", \"review_note\": \"Reviewed exact strict payload inside the disposable clone.\", \"reviewed_at\": \"2026-07-15 01:59:06.046049+00\", \"reviewed_by_agent_id\": \"11111111-1111-4111-8111-111111111111\", \"reviewed_by_db_role\": \"kb_review\", \"reviewed_by_handle\": \"m3ta\", \"status\": \"approved\"}" + } + }, + "current_tier": "T2_runtime", + "dependencies": { + "apply_role": "kb_apply", + "apply_script": "scripts/apply_proposal.py", + "approve_script": "scripts/approve_proposal.py", + "credential_files": { + "apply": { + "present_at_start": true, + "runtime_location": "ephemeral_or_operator_managed" + }, + "review": { + "present_at_start": true, + "runtime_location": "ephemeral_or_operator_managed" + } + }, + "gate_owner_role": "kb_gate_owner", + "immutable_approval_table": "kb_stage.kb_proposal_approvals", + "prerequisites_sql": "scripts/kb_apply_prereqs.sql", + "review_role": "kb_review", + "reviewer_handle": "m3ta", + "security_definer_argument_types": { + "kb_stage.approve_strict_proposal": "uuid, text, jsonb, text, text", + "kb_stage.assert_approved_proposal": "uuid, text, jsonb, text, uuid, timestamp with time zone, text", + "kb_stage.finish_approved_proposal": "uuid, text, jsonb, text, uuid, timestamp with time zone, text, text" + }, + "security_definer_functions": [ + "kb_stage.approve_strict_proposal", + "kb_stage.assert_approved_proposal", + "kb_stage.finish_approved_proposal" + ] + }, + "external_claim_seed": { + "clone_rows": [], + "exact_clone_copy": true, + "required_ids": [], + "source_rows": [] + }, + "first_apply": { + "result": { + "applied": true, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "replay_material_sha256": "767fe35fc91b4cd9b82287e828d6a6a86521bd82f23b651e9a91db977c55af59", + "replay_ready": true + }, + "returncode": 0, + "stderr": "" + }, + "fixture_ids": { + "conflict_claim_id": "3543c9a6-9439-5f56-9d1d-60268ea53a10", + "conflict_proposal_id": "7cb1468f-861c-5a85-ae32-470d891af133", + "conflict_source_id": "980ac481-1fa3-508f-b093-95de4ff4f8bc", + "permission_claim_id": "199b9d5e-904a-52cf-9f2c-3ee97f3f54b4", + "permission_source_id": "c4893cfb-8725-510e-ae01-ef01e8c99723", + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946" + }, + "fresh_owned_preflight": { + "apply_payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684", + "artifact": "proposal_apply_fresh_preflight", + "captured_at_utc": "2026-07-15T01:59:04.091167+00:00", + "fresh_owned_targets": true, + "preflight_artifact_sha256": "1d5e7eed7355aa94ecfa213422e6178aecdeee70628994596e852bd7569d6a2d", + "preflight_sha256": "29a80dc9422af45fd067e98621442d31c60161f34deb5845aa410659a9c5de3a", + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_payload_sha256": "6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af", + "proposal_type": "approve_claim", + "schema": "livingip.proposalApplyFreshPreflight.v1", + "target_rows_before": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + } + }, + "gateway_process_observable": false, + "generated_at_utc": "2026-07-15T01:59:02.013207+00:00", + "idempotent_replay": { + "already_applied_refusal": true, + "returncode": 1, + "stderr": "proposal 9b2b5fa4-0e0d-5db0-aa4d-6fd102889946 is already applied (idempotent no-op)" + }, + "kb_apply_claim_evidence_update_probe": { + "after": { + "claim_id": "199b9d5e-904a-52cf-9f2c-3ee97f3f54b4", + "created_by": null, + "role": "grounds", + "source_id": "c4893cfb-8725-510e-ae01-ef01e8c99723", + "weight": 0.42 + }, + "attempt": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table claim_evidence", + "stdout": "" + }, + "before": { + "claim_id": "199b9d5e-904a-52cf-9f2c-3ee97f3f54b4", + "created_by": null, + "role": "grounds", + "source_id": "c4893cfb-8725-510e-ae01-ef01e8c99723", + "weight": 0.42 + }, + "exact_row_unchanged": true + }, + "kb_apply_proposal_mutation_probes": { + "after": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "approval_fields": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table kb_proposals", + "stdout": "" + }, + "before": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "exact_row_unchanged": true, + "immutable_approval_after": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_before": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_exactly_unchanged": true, + "immutable_approval_payload": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table kb_proposal_approvals", + "stdout": "" + }, + "payload": { + "refused": true, + "returncode": 3, + "stderr": "ERROR: permission denied for table kb_proposals", + "stdout": "" + } + }, + "leftover_clone_count": 0, + "mutation_between_build_and_execute": { + "approval_restored_exactly": true, + "approved_before_mutation": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "build_returncode": 0, + "built_sql_sha256": "19f5dd6c27f293bfdc89adee59ffa99beb4503dba5aaa6291c9808ff16769ee7", + "built_sql_uses_assert_approved_proposal": true, + "built_sql_uses_finish_approved_proposal": true, + "canonical_rows_unchanged": true, + "immutable_approval_after_ledger_mutation": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_after_stale_execute": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_before_mutation": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "immutable_approval_never_changed": true, + "mutated_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "mutation_after_build": true + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "proposal_after_stale_execute": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "mutation_after_build": true + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "restored_immutable_approval": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "restored_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "rows_after_stale_execute": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "stale_apply_refused": true, + "stale_execute": { + "returncode": 3, + "stderr": "ERROR: assert_approved_proposal: proposal 9b2b5fa4-0e0d-5db0-aa4d-6fd102889946 is not the exact immutable approved snapshot\nCONTEXT: PL/pgSQL function kb_stage.assert_approved_proposal(uuid,text,jsonb,text,uuid,timestamp with time zone,text) line 22 at RAISE", + "stdout": "" + } + }, + "outer_isolation": { + "canonical_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 0, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "cleanup_readback": { + "container_name_query_returncode": 0, + "container_name_query_stdout": "", + "container_remove_returncode": 0, + "label_scoped_orphan_count": 0, + "label_scoped_query_returncode": 0, + "label_scoped_query_stdout": "", + "label_scoped_temporary_container_absent": true, + "temporary_container_absent": true, + "temporary_workdir_absent": true, + "workdir_lexists_after_cleanup": false, + "workdir_remove_returncode": 0 + }, + "container_isolation": { + "canary_label": "proposal-apply-lifecycle", + "instance_label": "working-leo-approve-claim-6715-44544", + "lifecycle_labeled": true, + "network_disabled": true, + "network_mode": "none", + "no_docker_volumes": true, + "volume_mounts": [] + }, + "service_after": { + "Available": "false", + "Reason": "systemctl_unavailable" + }, + "service_before": { + "Available": "false", + "Reason": "systemctl_unavailable" + }, + "service_gate_passes": true, + "service_observable": false, + "service_required": false, + "service_stable": null, + "source_boundary": { + "canary_label": "working-leo-approved-source", + "network_mode": "none", + "source_tier": "isolated-local", + "tier_enforced": true + }, + "source_container": "leo-proposal-lifecycle-source-20260715", + "source_counts_after": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_before": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_unchanged": true, + "source_database": "teleo" + }, + "payload_agent_seed": { + "clone_rows": [], + "exact_clone_copy": true, + "required_ids": [], + "source_rows": [] + }, + "payload_projection": { + "expected_rows": { + "claim_edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "expected_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + }, + "payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684" + }, + "post_run_cleanup": { + "gateway_active_state": null, + "gateway_main_pid": null, + "gateway_process_observable": false, + "gateway_restart_count": null, + "gateway_sub_state": null, + "leftover_clone_database_count": 0 + }, + "required_tier": "T2_runtime", + "required_tiers": [ + "T2_runtime" + ], + "review_transition": { + "approved_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "approved" + }, + "exact_transition": true, + "immutable_approval_matches_ledger": true, + "immutable_approval_readback": { + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_db_role": "kb_review", + "reviewed_by_handle": "m3ta" + }, + "pending_immutable_approval": null, + "pending_readback": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "channel": "clone_canary", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "rationale": "Exercise strict canonical bundle review and apply lifecycle.", + "review_note": null, + "reviewed_at": null, + "reviewed_by_agent_id": null, + "reviewed_by_handle": null, + "source_ref": "working-leo:approve-claim-clone-canary", + "status": "pending_review" + }, + "review_apply": { + "returncode": 0, + "stderr": "", + "stdout": "{\"id\": \"9b2b5fa4-0e0d-5db0-aa4d-6fd102889946\", \"proposal_type\": \"approve_claim\", \"review_note\": \"Reviewed exact strict payload inside the disposable clone.\", \"reviewed_at\": \"2026-07-15 01:59:03.105846+00\", \"reviewed_by_agent_id\": \"11111111-1111-4111-8111-111111111111\", \"reviewed_by_db_role\": \"kb_review\", \"reviewed_by_handle\": \"m3ta\", \"status\": \"approved\"}" + }, + "review_dry_run": { + "returncode": 0, + "stderr": "", + "uses_approve_strict_proposal": true + } + }, + "reviewer_agent_seed": { + "clone_row": { + "handle": "m3ta", + "id": "11111111-1111-4111-8111-111111111111", + "kind": "human" + }, + "exact_clone_copy": true, + "reviewer_handle": "m3ta", + "source_row": { + "handle": "m3ta", + "id": "11111111-1111-4111-8111-111111111111", + "kind": "human" + } + }, + "rollback_plan": { + "execution_authority": "disposable_clone_admin_only", + "rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3", + "schema": "livingip.proposalApplyRollback.v1" + }, + "rollback_replay": { + "refused": true, + "returncode": 3, + "state_unchanged": true, + "stderr": "ERROR: proposal rollback: applied ledger does not match apply receipt\nCONTEXT: PL/pgSQL function inline_code_block line 26 at RAISE" + }, + "row_readback": { + "claim_edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + }, + "runtime_scope": "isolated_postgres_container_from_readonly_local_fixture", + "schema": "working-leo.approve-claim-clone-canary.v3", + "security_definer_readback": { + "functions": [ + { + "argument_types": "uuid, text, jsonb, text, text", + "expected_role": "kb_review", + "expected_role_execute": true, + "function": "kb_stage.approve_strict_proposal", + "identity_arguments": "p_proposal_id uuid, p_expected_proposal_type text, p_expected_payload jsonb, p_expected_reviewed_by_handle text, p_review_note text", + "other_runtime_role_execute": false, + "owner": "kb_gate_owner", + "present": true, + "public_execute": false, + "security_definer": true + }, + { + "argument_types": "uuid, text, jsonb, text, uuid, timestamp with time zone, text", + "expected_role": "kb_apply", + "expected_role_execute": true, + "function": "kb_stage.assert_approved_proposal", + "identity_arguments": "p_proposal_id uuid, p_proposal_type text, p_payload jsonb, p_reviewed_by_handle text, p_reviewed_by_agent_id uuid, p_reviewed_at timestamp with time zone, p_review_note text", + "other_runtime_role_execute": false, + "owner": "kb_gate_owner", + "present": true, + "public_execute": false, + "security_definer": true + }, + { + "argument_types": "uuid, text, jsonb, text, uuid, timestamp with time zone, text, text", + "expected_role": "kb_apply", + "expected_role_execute": true, + "function": "kb_stage.finish_approved_proposal", + "identity_arguments": "p_proposal_id uuid, p_proposal_type text, p_payload jsonb, p_reviewed_by_handle text, p_reviewed_by_agent_id uuid, p_reviewed_at timestamp with time zone, p_review_note text, p_applied_by_handle text", + "other_runtime_role_execute": false, + "owner": "kb_gate_owner", + "present": true, + "public_execute": false, + "security_definer": true + } + ], + "login_role_owned_objects": [], + "protected_role_memberships": [], + "role_privileges": { + "kb_apply_approval_snapshot_select": false, + "kb_apply_approval_snapshot_update": false, + "kb_apply_claim_evidence_insert": true, + "kb_apply_claim_evidence_update": false, + "kb_apply_proposal_select": true, + "kb_apply_proposal_update": false, + "kb_review_approval_snapshot_select": false, + "kb_review_approval_snapshot_update": false, + "kb_review_proposal_select": true, + "kb_review_proposal_update": false + }, + "unexpected_overloads": [] + }, + "service_after": { + "available": false, + "reason": "systemctl_unavailable", + "returncode": 127 + }, + "service_before": { + "available": false, + "reason": "systemctl_unavailable", + "returncode": 127 + }, + "service_restarted": null, + "source_binding": { + "files": [ + { + "repo_relative_path": "scripts/apply_proposal.py", + "sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "size_bytes": 47270 + }, + { + "repo_relative_path": "scripts/approve_proposal.py", + "sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "size_bytes": 7323 + }, + { + "repo_relative_path": "scripts/finalize_approve_claim_isolated_canary.py", + "sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "size_bytes": 10684 + }, + { + "repo_relative_path": "scripts/kb_apply_prereqs.sql", + "sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "size_bytes": 42602 + }, + { + "repo_relative_path": "scripts/proposal_apply_lifecycle.py", + "sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "size_bytes": 71314 + }, + { + "repo_relative_path": "scripts/run_approve_claim_clone_canary.py", + "sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "size_bytes": 88340 + }, + { + "repo_relative_path": "scripts/run_approve_claim_isolated_container_canary.sh", + "sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "size_bytes": 9239 + } + ], + "independent_post_cleanup_verification": { + "all_match": true, + "files": [ + { + "actual_sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "expected_sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "matches": true, + "repo_relative_path": "scripts/apply_proposal.py", + "size_bytes": 47270 + }, + { + "actual_sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "expected_sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "matches": true, + "repo_relative_path": "scripts/approve_proposal.py", + "size_bytes": 7323 + }, + { + "actual_sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "expected_sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "matches": true, + "repo_relative_path": "scripts/finalize_approve_claim_isolated_canary.py", + "size_bytes": 10684 + }, + { + "actual_sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "expected_sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "matches": true, + "repo_relative_path": "scripts/kb_apply_prereqs.sql", + "size_bytes": 42602 + }, + { + "actual_sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "expected_sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "matches": true, + "repo_relative_path": "scripts/proposal_apply_lifecycle.py", + "size_bytes": 71314 + }, + { + "actual_sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "expected_sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "matches": true, + "repo_relative_path": "scripts/run_approve_claim_clone_canary.py", + "size_bytes": 88340 + }, + { + "actual_sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "expected_sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "matches": true, + "repo_relative_path": "scripts/run_approve_claim_isolated_container_canary.sh", + "size_bytes": 9239 + } + ] + }, + "post_run_files": [ + { + "repo_relative_path": "scripts/apply_proposal.py", + "sha256": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", + "size_bytes": 47270 + }, + { + "repo_relative_path": "scripts/approve_proposal.py", + "sha256": "e991498264bd0f49755dfef809212fa7151a668ce479b100d830e447775a9fe0", + "size_bytes": 7323 + }, + { + "repo_relative_path": "scripts/finalize_approve_claim_isolated_canary.py", + "sha256": "d5dbd250a9fc28a22ab0880a57c330b3c95119b95d96c5df95cbaf49648bccb5", + "size_bytes": 10684 + }, + { + "repo_relative_path": "scripts/kb_apply_prereqs.sql", + "sha256": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", + "size_bytes": 42602 + }, + { + "repo_relative_path": "scripts/proposal_apply_lifecycle.py", + "sha256": "a643b83b55c9e76f545ebafb9582d6d07ff432600c1b66004cc7440d7ac1963b", + "size_bytes": 71314 + }, + { + "repo_relative_path": "scripts/run_approve_claim_clone_canary.py", + "sha256": "bd7ae232b20ff77f15560d7bd07b929f419fb615425c9970fb00afc8080d86ef", + "size_bytes": 88340 + }, + { + "repo_relative_path": "scripts/run_approve_claim_isolated_container_canary.sh", + "sha256": "ed81a8f8c05d516809bb54c80d82786ed941dc1cf87150f25addc935685c72bb", + "size_bytes": 9239 + } + ], + "unchanged_during_run": true + }, + "source_cluster_roles_after": [ + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_apply", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": false, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_gate_owner", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_review", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + } + ], + "source_cluster_roles_before": [ + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_apply", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": false, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_gate_owner", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + }, + { + "rolbypassrls": false, + "rolcanlogin": true, + "rolconnlimit": -1, + "rolcreatedb": false, + "rolcreaterole": false, + "rolinherit": false, + "rolname": "kb_review", + "rolreplication": false, + "rolsuper": false, + "rolvaliduntil": null + } + ], + "source_counts_after": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_before": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 2, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_counts_observed_unchanged": true, + "source_database": "teleo", + "source_database_mutated": false, + "source_table_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 0, + "public.claim_evidence": 0, + "public.claims": 0, + "public.reasoning_tools": 0, + "public.sources": 0 + }, + "source_target_rows_after": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "source_target_rows_before": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "status": "pass", + "strict_receipt": { + "canonical_row_keys": { + "actual": { + "public.claim_edges": [ + { + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36" + } + ], + "public.claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d" + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f" + } + ], + "public.claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "public.reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "public.sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + }, + "expected": { + "public.claim_edges": [ + { + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36" + } + ], + "public.claim_evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d" + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f" + } + ], + "public.claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "public.reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "public.sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + } + }, + "checks": { + "before_after_counts_present": true, + "canonical_row_keys_exact": true, + "canonical_rows_exact": true, + "clone_database_removed": true, + "conflict_transaction_rolled_back": true, + "proposal_applied_at_present": true, + "proposal_id_exact": true, + "proposal_status_applied": true, + "source_database_unchanged": true, + "table_deltas_exact": true + }, + "pass": true, + "proposal": { + "applied_at": "2026-07-15 01:59:04.449051+00", + "applied_by_handle": "kb-apply", + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "status": "applied" + }, + "rollback_cleanup": { + "clone_database_removed": true, + "conflict_transaction_rolled_back": true, + "leftover_clone_database_count": 0, + "source_database_unchanged": true + }, + "row_counts": { + "actual_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + }, + "after_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 1, + "public.claim_evidence": 3, + "public.claims": 3, + "public.reasoning_tools": 1, + "public.sources": 3 + }, + "before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "expected_deltas": { + "kb_stage.kb_proposals": 0, + "public.claim_edges": 1, + "public.claim_evidence": 2, + "public.claims": 2, + "public.reasoning_tools": 1, + "public.sources": 2 + } + }, + "schema": "working-leo.approved-change-strict-receipt.v1" + }, + "target_rows_before_apply": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + }, + "telegram_messages_sent": false, + "unrelated_fingerprint_before_apply": { + "kb_stage.kb_proposal_approvals": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "kb_stage.kb_proposals": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "public.agents": { + "content_md5": "56bbdd947ace3bd545744a2995795702", + "row_count": 2 + }, + "public.claim_edges": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "public.claim_evidence": { + "content_md5": "bc66b511bcc4926bb9065f98b41ce8ae", + "row_count": 1 + }, + "public.claims": { + "content_md5": "5cfbe5bd4ed3dc24adab29cb39f0b594", + "row_count": 1 + }, + "public.reasoning_tools": { + "content_md5": "d41d8cd98f00b204e9800998ecf8427e", + "row_count": 0 + }, + "public.sources": { + "content_md5": "592f899d41dfeeecdb449402eb97ebd3", + "row_count": 1 + } + } +} diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json new file mode 100644 index 0000000..e5a2a3d --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-lifecycle-receipt-current.json @@ -0,0 +1,217 @@ +{ + "applied_row_ids": { + "claim_edges": [ + "b28d8770-9ac9-5b5f-bfe2-7280d7422a21" + ], + "claim_evidence": [ + "13e580c5-458d-5146-aef3-8c74ffc43b7c", + "dac3934f-b7ad-5205-8efe-a00163370a54" + ], + "claims": [ + "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "71a3331b-fb75-5e18-aa61-b256bc38af36" + ], + "reasoning_tools": [ + "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0" + ], + "sources": [ + "a86f55dc-5058-585d-b492-a4f1d932670f", + "ed9da5f1-846e-5298-8689-15027a7ce56d" + ] + }, + "applied_row_sha256": { + "claim_edges": [ + "60d14cbc45d424fa1eb62db6478f185bf2c2d86559e57ffa77474e8d0b3c527c" + ], + "claim_evidence": [ + "e97de162c0730da69b357e1350f0da7e5843c4e67d498c12d731a0f04a8868d7", + "3054718195abe3f78e08cfcacd59902d41eb91c7153c6c63f9bcb3bc7c6902ac" + ], + "claims": [ + "e593b9cf7b0bc9267f3e48d8173129b84b09c028f290f1abc97ccb73940b31dc", + "24280b5159cbbf75e00eb24ae50ab69994397a583f347a241a5e262cf0c03179" + ], + "reasoning_tools": [ + "257924b236c3c27289b9ae2314687be0e96779415fc6ff87faada22893b757ba" + ], + "sources": [ + "d7a690c2b18701db5b48e8189b1621c8025c1ed75889a4d0a3a4c9761c03dc3b", + "bee048794799006bd00bf4bb02ae57998e670f5bcdfb140dff871dcb042d0198" + ] + }, + "apply_payload_sha256": "c89bc03ce7714f325293329a0bf95eaba0a56c596ef021d731b48dab0447c684", + "apply_receipt_sha256": "2aa65fe4509c6699af34e418de27c0225efe751223aaebf3f9b545fa159c754e", + "apply_sql_sha256": "1a7d65870e415ee8891b6dd84732a29bcee00412c10862d3d1db4b191a8ade38", + "approval_snapshot_sha256": "310b54030e26ed43d7226f2e803610586e80d21ff064c67e11ea21e93ef40bd9", + "artifact": "proposal_apply_lifecycle_receipt", + "checks": { + "apply_receipt_replay_ready": true, + "apply_rows_have_exact_ids": true, + "fresh_owned_targets": true, + "immutable_approval_unchanged": true, + "rollback_counts_restored": true, + "rollback_ledger_restored_to_approved": true, + "rollback_replay_refused_without_mutation": true, + "rollback_sql_exact_for_bound_rows": true, + "rollback_target_rows_absent": true, + "unrelated_rows_unchanged": true + }, + "current_tier": "T2_runtime", + "fresh_preflight_artifact_sha256": "1d5e7eed7355aa94ecfa213422e6178aecdeee70628994596e852bd7569d6a2d", + "fresh_preflight_sha256": "29a80dc9422af45fd067e98621442d31c60161f34deb5845aa410659a9c5de3a", + "generated_at_utc": "2026-07-15T01:59:05.526171+00:00", + "lifecycle_receipt_sha256": "8e74cf1f5c372633f9b6f15363ff759ba6ce9bf42df51288bb9be70ddabe49b3", + "pass": true, + "production_apply_authorization_present": false, + "production_apply_executed": false, + "proposal_before_apply": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "payload": { + "apply_payload": { + "agent_id": null, + "claims": [ + { + "confidence": 0.9, + "created_by": null, + "id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "clone-canary" + ], + "text": "An approved strict proposal can create exact canonical rows atomically.", + "type": "structural" + }, + { + "confidence": 0.85, + "created_by": null, + "id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "status": "open", + "superseded_by": null, + "tags": [ + "working-leo", + "row-proof" + ], + "text": "Row-level proof is the authority for canonical KB state.", + "type": "concept" + } + ], + "contract_version": 2, + "edges": [ + { + "created_by": null, + "edge_type": "supports", + "from_claim": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "to_claim": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "weight": 0.75 + } + ], + "evidence": [ + { + "claim_id": "38dead14-6b1d-5909-99c4-3c45ad2dd21f", + "created_by": null, + "role": "grounds", + "source_id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "weight": 0.9 + }, + { + "claim_id": "71a3331b-fb75-5e18-aa61-b256bc38af36", + "created_by": null, + "role": "illustrates", + "source_id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "weight": 0.8 + } + ], + "reasoning_tools": [ + { + "agent_id": null, + "category": "verification", + "description": "Verify approved proposal to canonical graph lifecycle in a disposable clone.", + "id": "2b8a01ea-125c-5d90-a9b9-39ccb0a1c9e0", + "name": "Canonical row proof canary" + } + ], + "sources": [ + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source A.", + "hash": "approve-claim-canary-08c2358330-a", + "id": "ed9da5f1-846e-5298-8689-15027a7ce56d", + "source_type": "observation", + "storage_path": null, + "url": null + }, + { + "created_by": null, + "excerpt": "Disposable clone lifecycle canary source B.", + "hash": "approve-claim-canary-08c2358330-b", + "id": "a86f55dc-5058-585d-b492-a4f1d932670f", + "source_type": "observation", + "storage_path": null, + "url": null + } + ] + } + }, + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "proposal_payload_sha256": "6fba5e8e105a28dfaf5733ff7c9f85a20d31d653add0bc4a0f81e4b08de803af", + "required_tier": "T2_runtime", + "rollback": { + "counts_after_rollback": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "counts_before_apply": { + "kb_stage.kb_proposals": 1, + "public.claim_edges": 0, + "public.claim_evidence": 1, + "public.claims": 1, + "public.reasoning_tools": 0, + "public.sources": 1 + }, + "delete_order": [ + "claim_evidence", + "claim_edges", + "reasoning_tools", + "sources", + "claims" + ], + "proposal_after": { + "applied_at": null, + "applied_by_agent_id": null, + "applied_by_handle": null, + "id": "9b2b5fa4-0e0d-5db0-aa4d-6fd102889946", + "proposal_type": "approve_claim", + "review_note": "Reviewed exact strict payload inside the disposable clone.", + "reviewed_at": "2026-07-15 01:59:03.105846+00", + "reviewed_by_agent_id": "11111111-1111-4111-8111-111111111111", + "reviewed_by_handle": "m3ta", + "status": "approved" + }, + "rollback_sql_sha256": "ef2464a6802426509333861cf0768ca64184901ab7bd2030529e7356a35d9cb3", + "schema": "livingip.proposalApplyRollback.v1", + "target_rows_after": { + "claim_edges": [], + "claim_evidence": [], + "claims": [], + "reasoning_tools": [], + "sources": [] + } + }, + "schema": "livingip.proposalApplyLifecycleReceipt.v1", + "strongest_claim_allowed": "deterministic disposable-clone apply and compensating rollback" +} diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json new file mode 100644 index 0000000..c56352f --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json @@ -0,0 +1,32 @@ +{ + "artifact": "proposal_apply_production_target_readback", + "schema": "livingip.proposalApplyProductionTargetReadback.v1", + "observed_at_utc": "2026-07-15T00:45:59.238006+00:00", + "transport": "ssh_readonly_transaction", + "read_only_transaction": true, + "read_only_setting": "on", + "container": "teleo-pg", + "database": "teleo", + "system_identifier": "7649789040005668902", + "server_version_num": "160014", + "approval_gate": { + "immutable_approval_table_present": false, + "approve_function_present": false, + "assert_function_present": false, + "finish_function_present": false, + "review_role_present": false, + "apply_role_present": true + }, + "approved_strict_candidate_count": 0, + "approved_strict_candidates": [], + "attempted_recovery": { + "strict_immutable_approval_join_attempted": true, + "strict_join_failed": true, + "strict_join_exact_gate": "relation kb_stage.kb_proposal_approvals does not exist", + "fallback_read_only_gate_and_candidate_count_succeeded": true + }, + "production_mutated": false, + "production_apply_authorization_present": false, + "production_apply_executed": false, + "claim_ceiling": "Fresh T3 live read-only identity and gate readback only. The immutable approval gate is absent and there are zero approved strict approve_claim candidates; production apply is neither authorized nor executed." +} diff --git a/pyproject.toml b/pyproject.toml index e264213..70fc9ab 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,6 +14,7 @@ dependencies = [ [project.optional-dependencies] dev = [ + "hypothesis>=6,<7", "pytest>=8", "pytest-asyncio>=0.23", "ruff>=0.3", diff --git a/scripts/apply_proposal.py b/scripts/apply_proposal.py index 7fd4bd3..88c3c65 100644 --- a/scripts/apply_proposal.py +++ b/scripts/apply_proposal.py @@ -111,8 +111,17 @@ CLAIM_TYPES = {"empirical", "structural", "normative", "meta", "concept"} SOURCE_TYPES = {"paper", "article", "transcript", "observation", "dataset", "post", "dm", "other"} EVIDENCE_ROLES = {"grounds", "illustrates", "contradicts"} EDGE_TYPES = { - "supports", "challenges", "requires", "relates", "contradicts", - "supersedes", "derives_from", "cites", "causes", "constrains", "accelerates", + "supports", + "challenges", + "requires", + "relates", + "contradicts", + "supersedes", + "derives_from", + "cites", + "causes", + "constrains", + "accelerates", } MAX_BUNDLE_ROWS = { @@ -143,6 +152,21 @@ def _jsonb(value: Any) -> str: return sql_literal(json.dumps(value, sort_keys=True)) + "::jsonb" +def deterministic_row_uuid(proposal_id: str, row_kind: str, *semantic_key: Any) -> str: + """Derive a stable persisted row id from the proposal and semantic key. + + Evidence and edge ids are not part of the reviewed v2 payload. Deriving + them here makes clone/production receipts and a pre-authorized rollback + packet bind the same exact ids instead of relying on database randomness. + """ + material = json.dumps( + ["livingip", "kb-apply", str(proposal_id), row_kind, *semantic_key], + ensure_ascii=True, + separators=(",", ":"), + ) + return str(uuid.uuid5(uuid.NAMESPACE_URL, material)) + + def _text_array(values: list[str]) -> str: if not values: return "'{}'::text[]" @@ -196,9 +220,7 @@ def _reject_unknown(mapping: dict[str, Any], allowed: set[str], path: str) -> No raise ValueError(f"{path} contains unknown fields: {unknown}") -def _validate_approval_meta( - approval: dict[str, Any], proposal_type: str, apply_payload: dict[str, Any] -) -> None: +def _validate_approval_meta(approval: dict[str, Any], proposal_type: str, apply_payload: dict[str, Any]) -> None: if not isinstance(approval, dict): raise ValueError("approved proposal metadata is required") if approval.get("proposal_type") != proposal_type: @@ -222,12 +244,12 @@ def _validate_approval_meta( def _approval_guard_sql(proposal_id: str, approval: dict[str, Any]) -> str: return f"""select kb_stage.assert_approved_proposal( {sql_literal(proposal_id)}::uuid, - {sql_literal(approval['proposal_type'])}, - {_jsonb(approval['payload'])}, - {sql_literal(approval['reviewed_by_handle'])}, - {sql_literal(approval.get('reviewed_by_agent_id'))}::uuid, - {sql_literal(approval['reviewed_at'])}::timestamptz, - {sql_literal(approval['review_note'])} + {sql_literal(approval["proposal_type"])}, + {_jsonb(approval["payload"])}, + {sql_literal(approval["reviewed_by_handle"])}, + {sql_literal(approval.get("reviewed_by_agent_id"))}::uuid, + {sql_literal(approval["reviewed_at"])}::timestamptz, + {sql_literal(approval["review_note"])} );""" @@ -245,12 +267,12 @@ end $verify$;""" finish_sql = f"""select kb_stage.finish_approved_proposal( {sql_literal(proposal_id)}::uuid, - {sql_literal(approval['proposal_type'])}, - {_jsonb(approval['payload'])}, - {sql_literal(approval['reviewed_by_handle'])}, - {sql_literal(approval.get('reviewed_by_agent_id'))}::uuid, - {sql_literal(approval['reviewed_at'])}::timestamptz, - {sql_literal(approval['review_note'])}, + {sql_literal(approval["proposal_type"])}, + {_jsonb(approval["payload"])}, + {sql_literal(approval["reviewed_by_handle"])}, + {sql_literal(approval.get("reviewed_by_agent_id"))}::uuid, + {sql_literal(approval["reviewed_at"])}::timestamptz, + {sql_literal(approval["review_note"])}, {sql_literal(applied_by)} );""" return checks_sql + "\n" + finish_sql @@ -301,9 +323,9 @@ update public.strategies insert into public.strategies (agent_id, diagnosis, guiding_policy, proximate_objectives, version, active) select {sql_literal(agent_id)}::uuid, - {sql_literal(strategy['diagnosis'])}, - {sql_literal(strategy['guiding_policy'])}, - {_jsonb(strategy['proximate_objectives'])}, + {sql_literal(strategy["diagnosis"])}, + {sql_literal(strategy["guiding_policy"])}, + {_jsonb(strategy["proximate_objectives"])}, coalesce(max(version), 0) + 1, true from public.strategies @@ -325,9 +347,7 @@ values raise exception 'apply_proposal: expected exactly one active strategy for agent %', {sql_literal(agent_id)}; end if;""" - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -338,14 +358,16 @@ def build_add_edge_sql( approval: dict[str, Any], ) -> str: _validate_approval_meta(approval, "add_edge", apply_payload) - f = apply_payload.get("from_claim") - t = apply_payload.get("to_claim") + _reject_unknown(apply_payload, {"from_claim", "to_claim", "edge_type", "weight"}, "add_edge apply_payload") + f = _uuid(apply_payload.get("from_claim"), "add_edge.from_claim") + t = _uuid(apply_payload.get("to_claim"), "add_edge.to_claim") et = apply_payload.get("edge_type") - w = apply_payload.get("weight") - if not (f and t and et): - raise ValueError("add_edge apply_payload requires 'from_claim', 'to_claim', 'edge_type'") + if et not in EDGE_TYPES: + raise ValueError(f"add_edge.edge_type must be one of {sorted(EDGE_TYPES)}") + w = _weight(apply_payload.get("weight"), "add_edge.weight") if f == t: raise ValueError("add_edge from_claim and to_claim must differ") + row_id = deterministic_row_uuid(proposal_id, "claim_edge", f, t, et) edge_lock_key = f"claim_edge:{f}:{t}:{et}" canonical = f""" @@ -354,8 +376,8 @@ def build_add_edge_sql( select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0)); -- insert the edge unless an identical (from,to,type) edge already exists -insert into public.claim_edges (from_claim, to_claim, edge_type, weight) -select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, +insert into public.claim_edges (id, from_claim, to_claim, edge_type, weight) +select {sql_literal(row_id)}::uuid, {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, {sql_literal(et)}::edge_type, {sql_literal(w)} where not exists ( select 1 from public.claim_edges @@ -364,7 +386,15 @@ select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, and edge_type = {sql_literal(et)}::edge_type); """.rstrip() - checks = f""" if exists (select 1 from public.claim_edges + checks = f""" -- Historical replay receipts can carry a pre-deterministic row id. + -- Require one exact semantic row; fresh applies still insert row_id above. + if (select count(*) from public.claim_edges + where from_claim = {sql_literal(f)}::uuid + and to_claim = {sql_literal(t)}::uuid + and edge_type = {sql_literal(et)}::edge_type) <> 1 then + raise exception 'apply_proposal: claim_edge row does not match strict apply payload'; + end if; + if exists (select 1 from public.claim_edges where from_claim = {sql_literal(f)}::uuid and to_claim = {sql_literal(t)}::uuid and edge_type = {sql_literal(et)}::edge_type @@ -379,9 +409,7 @@ select {sql_literal(f)}::uuid, {sql_literal(t)}::uuid, raise exception 'apply_proposal: claim_edge row does not match strict apply payload'; end if;""" - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, checks, approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -392,6 +420,7 @@ def build_attach_evidence_sql( approval: dict[str, Any], ) -> str: _validate_approval_meta(approval, "attach_evidence", apply_payload) + _reject_unknown(apply_payload, {"evidence"}, "attach_evidence apply_payload") evidence: list[dict[str, Any]] = apply_payload.get("evidence") or [] if not evidence: raise ValueError("attach_evidence apply_payload requires non-empty 'evidence'") @@ -399,25 +428,33 @@ def build_attach_evidence_sql( statements = [] checks = [] for i, ev in enumerate(evidence): - claim_id = ev.get("claim_id") - source_id = ev.get("source_id") + _reject_unknown( + ev, + {"claim_id", "source_id", "role", "weight"}, + f"attach_evidence.evidence[{i}]", + ) + claim_id = _uuid(ev.get("claim_id"), f"attach_evidence.evidence[{i}].claim_id") + source_id = _uuid(ev.get("source_id"), f"attach_evidence.evidence[{i}].source_id") role = ev.get("role") or "grounds" - weight = ev.get("weight") - if not claim_id: - raise ValueError(f"evidence[{i}] requires 'claim_id'") - if not source_id: - raise ValueError( - f"evidence[{i}] requires an existing 'source_id'. " - "kb_apply cannot create public.sources; mint the source upstream first." - ) + if role not in EVIDENCE_ROLES: + raise ValueError(f"attach_evidence.evidence[{i}].role must be one of {sorted(EVIDENCE_ROLES)}") + weight = _weight(ev.get("weight"), f"attach_evidence.evidence[{i}].weight") + row_id = deterministic_row_uuid(proposal_id, "claim_evidence", claim_id, source_id, role) statements.append( - f"""insert into public.claim_evidence (claim_id, source_id, role, weight) -select {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid, + f"""insert into public.claim_evidence (id, claim_id, source_id, role, weight) +select {sql_literal(row_id)}::uuid, {sql_literal(claim_id)}::uuid, {sql_literal(source_id)}::uuid, {sql_literal(role)}::evidence_role, {sql_literal(weight)} on conflict (claim_id, source_id, role) do nothing;""" ) checks.append( - f""" if exists (select 1 from public.claim_evidence + f""" -- Accept a legacy receipt id only when exactly one semantic row matches. + if (select count(*) from public.claim_evidence + where claim_id = {sql_literal(claim_id)}::uuid + and source_id = {sql_literal(source_id)}::uuid + and role = {sql_literal(role)}::evidence_role) <> 1 then + raise exception 'apply_proposal: evidence row % does not match strict apply payload', {i}; + end if; + if exists (select 1 from public.claim_evidence where claim_id = {sql_literal(claim_id)}::uuid and source_id = {sql_literal(source_id)}::uuid and role = {sql_literal(role)}::evidence_role @@ -434,9 +471,7 @@ on conflict (claim_id, source_id, role) do nothing;""" ) canonical = "\n".join(statements) - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -509,12 +544,8 @@ def build_approve_claim_sql( "status": status, "confidence": _weight(row.get("confidence"), f"{path}.confidence"), "tags": tags, - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), - "superseded_by": _uuid( - row.get("superseded_by"), f"{path}.superseded_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), + "superseded_by": _uuid(row.get("superseded_by"), f"{path}.superseded_by", nullable=True), } ) @@ -543,9 +574,7 @@ def build_approve_claim_sql( "storage_path": row.get("storage_path"), "excerpt": row.get("excerpt"), "hash": source_hash, - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), } ) @@ -562,13 +591,18 @@ def build_approve_claim_sql( raise ValueError(f"{path}.role must be one of {sorted(EVIDENCE_ROLES)}") evidence.append( { + "id": deterministic_row_uuid( + proposal_id, + "claim_evidence", + _uuid(row.get("claim_id"), f"{path}.claim_id"), + _uuid(row.get("source_id"), f"{path}.source_id"), + role, + ), "claim_id": _uuid(row.get("claim_id"), f"{path}.claim_id"), "source_id": _uuid(row.get("source_id"), f"{path}.source_id"), "role": role, "weight": _weight(row.get("weight"), f"{path}.weight"), - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), } ) @@ -589,13 +623,12 @@ def build_approve_claim_sql( raise ValueError(f"{path} cannot be a self-edge") edges.append( { + "id": deterministic_row_uuid(proposal_id, "claim_edge", from_claim, to_claim, edge_type), "from_claim": from_claim, "to_claim": to_claim, "edge_type": edge_type, "weight": _weight(row.get("weight"), f"{path}.weight"), - "created_by": _uuid( - row.get("created_by", agent_id), f"{path}.created_by", nullable=True - ), + "created_by": _uuid(row.get("created_by", agent_id), f"{path}.created_by", nullable=True), } ) @@ -647,150 +680,156 @@ def build_approve_claim_sql( for row in sources: collision_checks.append( f""" if exists (select 1 from public.sources - where hash = {sql_literal(row['hash'])} - and id <> {sql_literal(row['id'])}::uuid) then - raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])}; + where hash = {sql_literal(row["hash"])} + and id <> {sql_literal(row["id"])}::uuid) then + raise exception 'approve_claim: source hash collision for source %', {sql_literal(row["id"])}; end if;""" ) - statements.append( - "do $source_conflicts$\nbegin\n" - + "\n".join(collision_checks) - + "\nend\n$source_conflicts$;" - ) + statements.append("do $source_conflicts$\nbegin\n" + "\n".join(collision_checks) + "\nend\n$source_conflicts$;") for row in claims: tags = _text_array(row["tags"]) statements.append( f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by) -select {sql_literal(row['id'])}::uuid, {sql_literal(row['type'])}, {sql_literal(row['text'])}, - {sql_literal(row['status'])}, {sql_literal(row['confidence'])}, {tags}, - {sql_literal(row['created_by'])}::uuid, {sql_literal(row['superseded_by'])}::uuid +select {sql_literal(row["id"])}::uuid, {sql_literal(row["type"])}, {sql_literal(row["text"])}, + {sql_literal(row["status"])}, {sql_literal(row["confidence"])}, {tags}, + {sql_literal(row["created_by"])}::uuid, {sql_literal(row["superseded_by"])}::uuid on conflict (id) do nothing;""" ) checks.append( f""" if not exists (select 1 from public.claims - where id = {sql_literal(row['id'])}::uuid - and type = {sql_literal(row['type'])} - and text = {sql_literal(row['text'])} - and status = {sql_literal(row['status'])} - and confidence is not distinct from {sql_literal(row['confidence'])} + where id = {sql_literal(row["id"])}::uuid + and type = {sql_literal(row["type"])} + and text = {sql_literal(row["text"])} + and status = {sql_literal(row["status"])} + and confidence is not distinct from {sql_literal(row["confidence"])} and tags is not distinct from {tags} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid - and superseded_by is not distinct from {sql_literal(row['superseded_by'])}::uuid) then - raise exception 'approve_claim: claim row does not match strict apply payload: %', {sql_literal(row['id'])}; + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid + and superseded_by is not distinct from {sql_literal(row["superseded_by"])}::uuid) then + raise exception 'approve_claim: claim row does not match strict apply payload: %', {sql_literal(row["id"])}; end if;""" ) for row in sources: statements.append( f"""insert into public.sources (id, source_type, url, storage_path, excerpt, hash, created_by) -select {sql_literal(row['id'])}::uuid, {sql_literal(row['source_type'])}, {sql_literal(row['url'])}, - {sql_literal(row['storage_path'])}, {sql_literal(row['excerpt'])}, {sql_literal(row['hash'])}, - {sql_literal(row['created_by'])}::uuid +select {sql_literal(row["id"])}::uuid, {sql_literal(row["source_type"])}, {sql_literal(row["url"])}, + {sql_literal(row["storage_path"])}, {sql_literal(row["excerpt"])}, {sql_literal(row["hash"])}, + {sql_literal(row["created_by"])}::uuid on conflict do nothing;""" ) checks.append( f""" if exists (select 1 from public.sources - where hash = {sql_literal(row['hash'])} - and id <> {sql_literal(row['id'])}::uuid) then - raise exception 'approve_claim: source hash collision for source %', {sql_literal(row['id'])}; + where hash = {sql_literal(row["hash"])} + and id <> {sql_literal(row["id"])}::uuid) then + raise exception 'approve_claim: source hash collision for source %', {sql_literal(row["id"])}; end if; if not exists (select 1 from public.sources - where id = {sql_literal(row['id'])}::uuid - and source_type = {sql_literal(row['source_type'])} - and url is not distinct from {sql_literal(row['url'])} - and storage_path is not distinct from {sql_literal(row['storage_path'])} - and excerpt is not distinct from {sql_literal(row['excerpt'])} - and hash = {sql_literal(row['hash'])} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then - raise exception 'approve_claim: source row does not match strict apply payload: %', {sql_literal(row['id'])}; + where id = {sql_literal(row["id"])}::uuid + and source_type = {sql_literal(row["source_type"])} + and url is not distinct from {sql_literal(row["url"])} + and storage_path is not distinct from {sql_literal(row["storage_path"])} + and excerpt is not distinct from {sql_literal(row["excerpt"])} + and hash = {sql_literal(row["hash"])} + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid) then + raise exception 'approve_claim: source row does not match strict apply payload: %', {sql_literal(row["id"])}; end if;""" ) for row in reasoning_tools: statements.append( f"""insert into public.reasoning_tools (id, agent_id, name, description, category) -select {sql_literal(row['id'])}::uuid, {sql_literal(row['agent_id'])}::uuid, - {sql_literal(row['name'])}, {sql_literal(row['description'])}, {sql_literal(row['category'])} +select {sql_literal(row["id"])}::uuid, {sql_literal(row["agent_id"])}::uuid, + {sql_literal(row["name"])}, {sql_literal(row["description"])}, {sql_literal(row["category"])} on conflict (id) do nothing;""" ) checks.append( f""" if not exists (select 1 from public.reasoning_tools - where id = {sql_literal(row['id'])}::uuid - and agent_id is not distinct from {sql_literal(row['agent_id'])}::uuid - and name = {sql_literal(row['name'])} - and description = {sql_literal(row['description'])} - and category is not distinct from {sql_literal(row['category'])}) then - raise exception 'approve_claim: reasoning tool row does not match strict apply payload: %', {sql_literal(row['id'])}; + where id = {sql_literal(row["id"])}::uuid + and agent_id is not distinct from {sql_literal(row["agent_id"])}::uuid + and name = {sql_literal(row["name"])} + and description = {sql_literal(row["description"])} + and category is not distinct from {sql_literal(row["category"])}) then + raise exception 'approve_claim: reasoning tool row does not match strict apply payload: %', {sql_literal(row["id"])}; end if;""" ) for row in evidence: statements.append( - f"""insert into public.claim_evidence (claim_id, source_id, role, weight, created_by) -select {sql_literal(row['claim_id'])}::uuid, {sql_literal(row['source_id'])}::uuid, - {sql_literal(row['role'])}::evidence_role, {sql_literal(row['weight'])}, - {sql_literal(row['created_by'])}::uuid + f"""insert into public.claim_evidence (id, claim_id, source_id, role, weight, created_by) +select {sql_literal(row["id"])}::uuid, {sql_literal(row["claim_id"])}::uuid, {sql_literal(row["source_id"])}::uuid, + {sql_literal(row["role"])}::evidence_role, {sql_literal(row["weight"])}, + {sql_literal(row["created_by"])}::uuid on conflict (claim_id, source_id, role) do nothing;""" ) checks.append( - f""" if exists (select 1 from public.claim_evidence - where claim_id = {sql_literal(row['claim_id'])}::uuid - and source_id = {sql_literal(row['source_id'])}::uuid - and role = {sql_literal(row['role'])}::evidence_role - and (weight is distinct from {sql_literal(row['weight'])} - or created_by is distinct from {sql_literal(row['created_by'])}::uuid)) then + f""" -- Accept a legacy receipt id only when exactly one semantic row matches. + if (select count(*) from public.claim_evidence + where claim_id = {sql_literal(row["claim_id"])}::uuid + and source_id = {sql_literal(row["source_id"])}::uuid + and role = {sql_literal(row["role"])}::evidence_role) <> 1 then + raise exception 'approve_claim: evidence row does not match strict apply payload'; + end if; + if exists (select 1 from public.claim_evidence + where claim_id = {sql_literal(row["claim_id"])}::uuid + and source_id = {sql_literal(row["source_id"])}::uuid + and role = {sql_literal(row["role"])}::evidence_role + and (weight is distinct from {sql_literal(row["weight"])} + or created_by is distinct from {sql_literal(row["created_by"])}::uuid)) then raise exception 'approve_claim: evidence row does not match strict apply payload'; end if; if not exists (select 1 from public.claim_evidence - where claim_id = {sql_literal(row['claim_id'])}::uuid - and source_id = {sql_literal(row['source_id'])}::uuid - and role = {sql_literal(row['role'])}::evidence_role - and weight is not distinct from {sql_literal(row['weight'])} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then + where claim_id = {sql_literal(row["claim_id"])}::uuid + and source_id = {sql_literal(row["source_id"])}::uuid + and role = {sql_literal(row["role"])}::evidence_role + and weight is not distinct from {sql_literal(row["weight"])} + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid) then raise exception 'approve_claim: evidence row does not match strict apply payload'; end if;""" ) for row in edges: - edge_lock_key = ( - f"claim_edge:{row['from_claim']}:{row['to_claim']}:{row['edge_type']}" - ) + edge_lock_key = f"claim_edge:{row['from_claim']}:{row['to_claim']}:{row['edge_type']}" statements.append( f"""select pg_advisory_xact_lock(hashtextextended({sql_literal(edge_lock_key)}, 0)); -insert into public.claim_edges (from_claim, to_claim, edge_type, weight, created_by) -select {sql_literal(row['from_claim'])}::uuid, {sql_literal(row['to_claim'])}::uuid, - {sql_literal(row['edge_type'])}::edge_type, {sql_literal(row['weight'])}, - {sql_literal(row['created_by'])}::uuid +insert into public.claim_edges (id, from_claim, to_claim, edge_type, weight, created_by) +select {sql_literal(row["id"])}::uuid, {sql_literal(row["from_claim"])}::uuid, {sql_literal(row["to_claim"])}::uuid, + {sql_literal(row["edge_type"])}::edge_type, {sql_literal(row["weight"])}, + {sql_literal(row["created_by"])}::uuid where not exists ( select 1 from public.claim_edges - where from_claim = {sql_literal(row['from_claim'])}::uuid - and to_claim = {sql_literal(row['to_claim'])}::uuid - and edge_type = {sql_literal(row['edge_type'])}::edge_type);""" + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type);""" ) checks.append( - f""" if exists (select 1 from public.claim_edges - where from_claim = {sql_literal(row['from_claim'])}::uuid - and to_claim = {sql_literal(row['to_claim'])}::uuid - and edge_type = {sql_literal(row['edge_type'])}::edge_type - and (weight is distinct from {sql_literal(row['weight'])} - or created_by is distinct from {sql_literal(row['created_by'])}::uuid)) then + f""" -- Accept a legacy receipt id only when exactly one semantic row matches. + if (select count(*) from public.claim_edges + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type) <> 1 then + raise exception 'approve_claim: edge row does not match strict apply payload'; + end if; + if exists (select 1 from public.claim_edges + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type + and (weight is distinct from {sql_literal(row["weight"])} + or created_by is distinct from {sql_literal(row["created_by"])}::uuid)) then raise exception 'approve_claim: edge row does not match strict apply payload'; end if; if not exists (select 1 from public.claim_edges - where from_claim = {sql_literal(row['from_claim'])}::uuid - and to_claim = {sql_literal(row['to_claim'])}::uuid - and edge_type = {sql_literal(row['edge_type'])}::edge_type - and weight is not distinct from {sql_literal(row['weight'])} - and created_by is not distinct from {sql_literal(row['created_by'])}::uuid) then + where from_claim = {sql_literal(row["from_claim"])}::uuid + and to_claim = {sql_literal(row["to_claim"])}::uuid + and edge_type = {sql_literal(row["edge_type"])}::edge_type + and weight is not distinct from {sql_literal(row["weight"])} + and created_by is not distinct from {sql_literal(row["created_by"])}::uuid) then raise exception 'approve_claim: edge row does not match strict apply payload'; end if;""" ) canonical = "\n\n".join(statements) - ledger = _ledger_and_verify( - proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval - ) + ledger = _ledger_and_verify(proposal_id, applied_by or SERVICE_AGENT_HANDLE, "\n".join(checks), approval) return _wrap_txn(canonical, ledger, _approval_guard_sql(proposal_id, approval)) @@ -837,9 +876,7 @@ def assert_applyable(proposal: dict[str, Any]) -> None: if status == "applied": raise SystemExit(f"proposal {proposal['id']} is already applied (idempotent no-op)") if status != "approved": - raise SystemExit( - f"proposal {proposal['id']} has status {status!r}; only 'approved' proposals apply" - ) + raise SystemExit(f"proposal {proposal['id']} has status {status!r}; only 'approved' proposals apply") def assert_receiptable(proposal: dict[str, Any]) -> None: @@ -877,23 +914,36 @@ def run_psql( ) -> str: docker_binary = shutil.which("docker") or "docker" command = [ - docker_binary, "exec", "-e", "PGPASSWORD", "-i", args.container, - "psql", "-U", args.role, "-h", args.host, "-d", args.db, - "-v", "ON_ERROR_STOP=1", "-At", "-q", + docker_binary, + "exec", + "-e", + "PGPASSWORD", + "-i", + args.container, + "psql", + "-U", + args.role, + "-h", + args.host, + "-d", + args.db, + "-v", + "ON_ERROR_STOP=1", + "-At", + "-q", ] result = subprocess.run( - command, input=sql, text=True, capture_output=True, + command, + input=sql, + text=True, + capture_output=True, env={"PGPASSWORD": password, "PATH": "/usr/bin:/bin:/usr/local/bin"}, check=False, ) if result.returncode != 0: if redact_output_on_error: - raise SystemExit( - f"psql failed ({result.returncode}); private postflight output redacted" - ) - raise SystemExit( - f"psql failed ({result.returncode})\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}" - ) + raise SystemExit(f"psql failed ({result.returncode}); private postflight output redacted") + raise SystemExit(f"psql failed ({result.returncode})\nSTDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}") return result.stdout @@ -980,8 +1030,7 @@ def parse_args(argv: list[str]) -> argparse.Namespace: p.add_argument( "--receipt-dir", default=None, - help="private receipt directory (default: KB_APPLY_RECEIPT_DIR or " - f"{replay_receipt.DEFAULT_RECEIPT_DIR})", + help=f"private receipt directory (default: KB_APPLY_RECEIPT_DIR or {replay_receipt.DEFAULT_RECEIPT_DIR})", ) p.add_argument( "--receipt-out", diff --git a/scripts/apply_worker.py b/scripts/apply_worker.py index 2abefc7..6d3dc13 100644 --- a/scripts/apply_worker.py +++ b/scripts/apply_worker.py @@ -35,6 +35,7 @@ from __future__ import annotations import argparse import json import os +import stat import subprocess import sys from pathlib import Path @@ -53,6 +54,13 @@ WORKER_TYPES = ap.APPLYABLE_TYPES # bundles and evidence/edge applies do not trigger a generic identity render. RENDER_TYPES = ("revise_strategy",) +POSTCOMMIT_RECEIPT_FAILURE_MARKER = "committed, but private replay receipt capture failed" +POSTCOMMIT_RECEIPT_RECOVERY_MARKER = ". Recover without reapplying via: " + + +class PostCommitReceiptError(RuntimeError): + """The proposal committed, but its private row-level receipt is unavailable.""" + # --------------------------------------------------------------------------- # # Pure helpers (unit-tested without a DB) # @@ -160,14 +168,69 @@ def partition_candidates( return {"poisoned": poisoned, "to_apply": eligible[: max(0, max_per_tick)]} +def has_exact_postcommit_receipt_failure(stderr: str | None, proposal_id: str) -> bool: + """Recognize only the proposal-bound post-COMMIT message from apply_proposal. + + The old marker can occur inside payload-validation tracebacks (for example, + as an attacker-controlled unknown field name). Requiring the complete + engine message shape prevents those pre-COMMIT failures from bypassing the + normal failure and poison-pill accounting. + """ + prefix = f"proposal {proposal_id} {POSTCOMMIT_RECEIPT_FAILURE_MARKER}: " + recovery_suffix = f" {proposal_id} --receipt-only" + for line in (stderr or "").splitlines(): + candidate = line.rstrip("\r") + if ( + candidate.startswith(prefix) + and POSTCOMMIT_RECEIPT_RECOVERY_MARKER in candidate[len(prefix) :] + and candidate.endswith(recovery_suffix) + ): + return True + return False + + +def receipt_path_for_proposal(args: argparse.Namespace, proposal_id: str) -> Path: + """Return the explicit, deterministic private receipt path for one proposal.""" + receipt_dir = ( + getattr(args, "receipt_dir", None) + or os.environ.get("KB_APPLY_WORKER_RECEIPT_DIR") + or os.environ.get("KB_APPLY_RECEIPT_DIR") + or ap.replay_receipt.DEFAULT_RECEIPT_DIR + ) + return Path(receipt_dir).expanduser().resolve() / f"{proposal_id}.json" + + +def assert_private_replay_receipt(path: Path, proposal_id: str) -> None: + """Verify the worker can rely on the exact private row-level receipt.""" + try: + path_stat = path.lstat() + except OSError as exc: + raise RuntimeError("private replay receipt was not written") from exc + if not stat.S_ISREG(path_stat.st_mode) or path.is_symlink(): + raise RuntimeError("private replay receipt is not a regular file") + if stat.S_IMODE(path_stat.st_mode) & 0o077: + raise RuntimeError("private replay receipt permissions are broader than 0600") + try: + receipt = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise RuntimeError("private replay receipt is unreadable or invalid") from exc + if not isinstance(receipt, dict): + raise RuntimeError("private replay receipt is not a JSON object") + try: + ap.replay_receipt.validate_replay_receipt( + receipt, + expected_proposal_id=str(proposal_id), + ) + except ValueError as exc: + raise RuntimeError("private replay receipt row-level contract is invalid") from exc + + # --------------------------------------------------------------------------- # # Side-effecting steps # # --------------------------------------------------------------------------- # def _psql_args(args: argparse.Namespace) -> argparse.Namespace: """Namespace shaped for ap.run_psql (reuses the kb_apply connection path).""" - return argparse.Namespace( - container=args.container, db=args.db, host=args.host, role=args.role - ) + return argparse.Namespace(container=args.container, db=args.db, host=args.host, role=args.role) def fetch_candidates( @@ -181,20 +244,73 @@ def fetch_candidates( def apply_one(args: argparse.Namespace, proposal_id: str) -> None: - """Apply via the audited apply_proposal.py CLI -- same txn + rowcount guard.""" + """Apply once and retain or safely recover the exact private replay receipt.""" + receipt_path = receipt_path_for_proposal(args, proposal_id) cmd = [ - sys.executable, str(args.apply_script), proposal_id, - "--applied-by", args.applied_by, - "--secrets-file", args.secrets_file, - "--container", args.container, "--db", args.db, - "--host", args.host, "--role", args.role, + sys.executable, + str(args.apply_script), + proposal_id, + "--applied-by", + args.applied_by, + "--secrets-file", + args.secrets_file, + "--container", + args.container, + "--db", + args.db, + "--host", + args.host, + "--role", + args.role, + "--receipt-dir", + str(receipt_path.parent), + "--receipt-out", + str(receipt_path), ] result = subprocess.run(cmd, text=True, capture_output=True, check=False) - if result.returncode != 0: + committed = result.returncode == 0 or has_exact_postcommit_receipt_failure( + result.stderr, + proposal_id, + ) + if not committed: raise RuntimeError( - f"apply failed for {proposal_id}: {result.stdout.strip()} {result.stderr.strip()}" + f"apply failed for {proposal_id}: {(result.stdout or '').strip()} {(result.stderr or '').strip()}" ) + if result.returncode == 0: + try: + assert_private_replay_receipt(receipt_path, proposal_id) + return + except RuntimeError: + # The engine returned only after COMMIT. A missing/invalid receipt is + # recoverable without replaying the apply transaction. + pass + + try: + recovery = subprocess.run( + [*cmd, "--receipt-only"], + text=True, + capture_output=True, + check=False, + ) + except OSError as exc: + raise PostCommitReceiptError( + f"proposal {proposal_id} committed, but the read-only receipt recovery " + "command could not start; do not retry the apply transaction" + ) from exc + if recovery.returncode != 0: + raise PostCommitReceiptError( + f"proposal {proposal_id} committed, but read-only receipt recovery " + f"failed (exit {recovery.returncode}); do not retry the apply transaction" + ) + try: + assert_private_replay_receipt(receipt_path, proposal_id) + except RuntimeError as exc: + raise PostCommitReceiptError( + f"proposal {proposal_id} committed, but receipt recovery did not produce " + "a valid private row-level receipt; do not retry the apply transaction" + ) from exc + def render_one(args: argparse.Namespace, agent_id: str | None) -> str: cmd = build_render_command(args.render_cmd, agent_id) @@ -202,9 +318,7 @@ def render_one(args: argparse.Namespace, agent_id: str | None) -> str: return "render skipped (no render-cmd configured; PR2 renderer not deployed)" result = subprocess.run(cmd, text=True, capture_output=True, check=False) if result.returncode != 0: - raise RuntimeError( - f"render failed for agent {agent_id}: {result.stdout.strip()} {result.stderr.strip()}" - ) + raise RuntimeError(f"render failed for agent {agent_id}: {result.stdout.strip()} {result.stderr.strip()}") return f"rendered agent {agent_id}" @@ -214,11 +328,7 @@ def render_one(args: argparse.Namespace, agent_id: str | None) -> str: def run(args: argparse.Namespace) -> int: password = ap.load_password(args.secrets_file) failure_counts = load_failure_state(args.failure_state_file) - exhausted_ids = tuple( - proposal_id - for proposal_id, count in failure_counts.items() - if count >= args.max_attempts - ) + exhausted_ids = tuple(proposal_id for proposal_id, count in failure_counts.items() if count >= args.max_attempts) candidates = fetch_candidates(args, password, exhausted_ids) for proposal_id in exhausted_ids: @@ -263,6 +373,15 @@ def run(args: argparse.Namespace) -> int: print(f"applied {pid} ({ptype})") if ptype in RENDER_TYPES: print(" " + render_one(args, agent_id)) + except PostCommitReceiptError as exc: + failures += 1 + # The canonical transaction already committed. Do not classify this + # as an apply failure or poison/retry the proposal; receipt-only is + # the sole safe recovery path. + print( + f"POST-COMMIT RECEIPT ERROR for {pid} ({ptype}): {exc}", + file=sys.stderr, + ) except Exception as exc: failures += 1 # Leave the proposal at 'approved' so a fixed one reapplies next tick @@ -270,48 +389,65 @@ def run(args: argparse.Namespace) -> int: # so a deterministically-failing proposal hits the ceiling instead of # retrying forever. Surface loudly for the operator. failure_counts[pid] = failure_counts.get(pid, 0) + 1 - print(f"ERROR applying {pid} ({ptype}) [attempt {failure_counts[pid]}/" - f"{args.max_attempts}]: {exc}", file=sys.stderr) + print( + f"ERROR applying {pid} ({ptype}) [attempt {failure_counts[pid]}/{args.max_attempts}]: {exc}", + file=sys.stderr, + ) save_failure_state(args.failure_state_file, failure_counts) return 1 if failures else 0 def parse_args(argv: list[str]) -> argparse.Namespace: - p = argparse.ArgumentParser( - description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter - ) + p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) p.add_argument( - "--enable", action="store_true", + "--enable", + action="store_true", help="actually apply (default: report-only). Also honored via KB_APPLY_WORKER_ENABLED=1.", ) p.add_argument("--limit", type=int, default=20, help="max candidates fetched per tick") p.add_argument( - "--max-per-tick", type=int, default=1, + "--max-per-tick", + type=int, + default=1, help="max proposals actually APPLIED per tick (default 1: applies land " "one-at-a-time and observably, not a whole-queue drain)", ) p.add_argument( - "--max-attempts", type=int, default=3, + "--max-attempts", + type=int, + default=3, help="consecutive apply failures before a proposal is treated as a " "poison pill and skipped (needs a human fix, not endless retries)", ) p.add_argument( "--failure-state-file", default=os.environ.get("KB_APPLY_WORKER_STATE", "/opt/teleo-eval/logs/kb-apply-worker-failures.json"), - help="persisted per-proposal failure counts (survives oneshot ticks so " - "the poison-pill ceiling actually bites)", + help="persisted per-proposal failure counts (survives oneshot ticks so the poison-pill ceiling actually bites)", ) p.add_argument( - "--applied-by", default=ap.SERVICE_AGENT_HANDLE, + "--applied-by", + default=ap.SERVICE_AGENT_HANDLE, help="handle recorded as applied_by (default: the kb-apply service agent)", ) p.add_argument( - "--apply-script", default=str(HERE / "apply_proposal.py"), + "--apply-script", + default=str(HERE / "apply_proposal.py"), help="path to the apply_proposal.py engine (the sole apply path)", ) p.add_argument( - "--render-cmd", default=os.environ.get("KB_APPLY_RENDER_CMD", ""), + "--receipt-dir", + default=( + os.environ.get("KB_APPLY_WORKER_RECEIPT_DIR") + or os.environ.get("KB_APPLY_RECEIPT_DIR") + or ap.replay_receipt.DEFAULT_RECEIPT_DIR + ), + help="private replay receipt directory; each apply writes the deterministic " + ".json path and verifies 0600 permissions", + ) + p.add_argument( + "--render-cmd", + default=os.environ.get("KB_APPLY_RENDER_CMD", ""), help="render hook template, e.g. 'python3 render_soul.py --agent-id {agent_id}'. " "Empty (default) skips render until the SOUL renderer is deployed.", ) diff --git a/scripts/finalize_approve_claim_isolated_canary.py b/scripts/finalize_approve_claim_isolated_canary.py new file mode 100644 index 0000000..059ca97 --- /dev/null +++ b/scripts/finalize_approve_claim_isolated_canary.py @@ -0,0 +1,230 @@ +#!/usr/bin/env python3 +"""Finalize outer isolation and cleanup evidence for the clone canary.""" + +from __future__ import annotations + +import hashlib +import json +import os +import sys +from collections.abc import Mapping +from pathlib import Path +from typing import Any + + +def _service(raw: str) -> dict[str, str]: + return dict(line.split("=", 1) for line in raw.splitlines() if "=" in line) + + +def _table_deltas(before: dict[str, int], after: dict[str, int]) -> dict[str, int]: + if set(before) != set(after): + raise ValueError( + f"live source count keys changed during the canary: before={sorted(before)} after={sorted(after)}" + ) + return {key: after[key] - before[key] for key in sorted(before)} + + +def _verify_source_binding(root: Path, records: list[dict[str, object]]) -> dict[str, object]: + readbacks = [] + for record in records: + relative_text = str(record.get("repo_relative_path", "")) + relative = Path(relative_text) + if not relative_text or relative.is_absolute() or ".." in relative.parts: + raise ValueError(f"unsafe repo-relative source path: {relative_text!r}") + candidate = (root / relative).resolve() + try: + candidate.relative_to(root) + except ValueError as exc: + raise ValueError(f"source path escaped repository root: {relative_text}") from exc + if not candidate.is_file(): + readbacks.append( + { + "repo_relative_path": relative.as_posix(), + "expected_sha256": record.get("sha256"), + "actual_sha256": None, + "matches": False, + } + ) + continue + content = candidate.read_bytes() + actual_sha256 = hashlib.sha256(content).hexdigest() + readbacks.append( + { + "repo_relative_path": relative.as_posix(), + "expected_sha256": record.get("sha256"), + "actual_sha256": actual_sha256, + "size_bytes": len(content), + "matches": (actual_sha256 == record.get("sha256") and len(content) == record.get("size_bytes")), + } + ) + return { + "files": readbacks, + "all_match": bool(readbacks) and all(bool(row["matches"]) for row in readbacks), + } + + +def _contains_ephemeral_path(value: object) -> bool: + if isinstance(value, str): + return "/tmp/" in value + if isinstance(value, dict): + return any(_contains_ephemeral_path(item) for item in value.values()) + if isinstance(value, list): + return any(_contains_ephemeral_path(item) for item in value) + return False + + +def finalize(path: Path, env: Mapping[str, str]) -> dict[str, Any]: + if not path.is_file(): + raise ValueError("inner canary did not produce a report") + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + raise ValueError("inner canary report must be a JSON object") + + before_counts = json.loads(env["SOURCE_COUNTS_BEFORE"]) + after_counts = json.loads(env["SOURCE_COUNTS_AFTER"]) + live_table_deltas = _table_deltas(before_counts, after_counts) + before_service = _service(env["SERVICE_BEFORE"]) + after_service = _service(env["SERVICE_AFTER"]) + source_tier = env["SOURCE_TIER"] + source_network_mode = env["SOURCE_NETWORK_MODE"] + source_canary_label = env["SOURCE_CANARY_LABEL"] + container_network_mode = env["CANARY_NETWORK_MODE"] + container_mounts = json.loads(env["CANARY_MOUNTS_JSON"]) + container_canary_label = env["CANARY_LABEL"] + container_instance_label = env["CANARY_INSTANCE_LABEL"] + container_volume_mounts = [mount for mount in container_mounts if mount.get("Type") == "volume"] + container_isolated = container_network_mode == "none" and not container_volume_mounts + container_labeled = container_canary_label == "proposal-apply-lifecycle" and container_instance_label.startswith( + "working-leo-approve-claim-" + ) + source_tier_enforced = source_tier != "isolated-local" or ( + source_network_mode == "none" and source_canary_label == "working-leo-approved-source" + ) + service_observable = before_service.get("Available") == "true" and after_service.get("Available") == "true" + service_stable = ( + all( + before_service.get(key) == after_service.get(key) + for key in ("ActiveState", "SubState", "MainPID", "NRestarts") + ) + if service_observable + else None + ) + service_gate_passes = service_stable is True if source_tier == "live-readonly" else service_stable is not False + container_absent = int(env["CONTAINER_NAME_READBACK_RC"]) == 0 and not env["CONTAINER_NAME_READBACK"].strip() + label_scoped_orphan_count = int(env["CONTAINER_LABEL_ORPHAN_COUNT"]) + label_scoped_container_absent = ( + int(env["CONTAINER_LABEL_READBACK_RC"]) == 0 + and label_scoped_orphan_count == 0 + and not env["CONTAINER_LABEL_READBACK"].strip() + ) + workdir_absent = env["OUTER_WORKDIR_LEXISTS"] == "false" + source_binding = _verify_source_binding( + Path(env["REPO_ROOT"]).resolve(), + data.get("source_binding", {}).get("files", []), + ) + data["source_binding"]["independent_post_cleanup_verification"] = source_binding + source_binding_ok = ( + data["source_binding"].get("unchanged_during_run") is True and source_binding["all_match"] is True + ) + stale_ephemeral_paths_absent = not _contains_ephemeral_path(data) + inner_runtime_pass = data.get("status") == "pass" and int(env["CANARY_RC"]) == 0 + outer_ok = ( + before_counts == after_counts + and all(delta == 0 for delta in live_table_deltas.values()) + and service_gate_passes + and container_absent + and label_scoped_container_absent + and workdir_absent + and source_binding_ok + and stale_ephemeral_paths_absent + and container_isolated + and container_labeled + and source_tier_enforced + ) + if source_tier == "isolated-local": + data["required_tier"] = "T2_runtime" + data["required_tiers"] = ["T2_runtime"] + data["runtime_scope"] = "isolated_postgres_container_from_readonly_local_fixture" + else: + data["required_tier"] = "T3_live_readonly" + data["required_tiers"] = ["T2_runtime", "T3_live_readonly"] + data["runtime_scope"] = "isolated_postgres_container_from_readonly_live_schema" + data["outer_isolation"] = { + "source_container": env.get("SOURCE_CONTAINER", "teleo-pg"), + "source_database": env.get("SOURCE_DB", "teleo"), + "source_counts_before": before_counts, + "source_counts_after": after_counts, + "canonical_table_deltas": live_table_deltas, + "source_counts_unchanged": before_counts == after_counts, + "service_before": before_service, + "service_after": after_service, + "service_required": source_tier == "live-readonly", + "service_observable": service_observable, + "service_stable": service_stable, + "service_gate_passes": service_gate_passes, + "source_boundary": { + "source_tier": source_tier, + "network_mode": source_network_mode, + "canary_label": source_canary_label, + "tier_enforced": source_tier_enforced, + }, + "container_isolation": { + "network_mode": container_network_mode, + "volume_mounts": container_volume_mounts, + "canary_label": container_canary_label, + "instance_label": container_instance_label, + "lifecycle_labeled": container_labeled, + "network_disabled": container_network_mode == "none", + "no_docker_volumes": not container_volume_mounts, + }, + "cleanup_readback": { + "container_remove_returncode": int(env["OUTER_CONTAINER_REMOVE_RC"]), + "container_name_query_returncode": int(env["CONTAINER_NAME_READBACK_RC"]), + "container_name_query_stdout": env["CONTAINER_NAME_READBACK"], + "temporary_container_absent": container_absent, + "label_scoped_query_returncode": int(env["CONTAINER_LABEL_READBACK_RC"]), + "label_scoped_query_stdout": env["CONTAINER_LABEL_READBACK"], + "label_scoped_orphan_count": label_scoped_orphan_count, + "label_scoped_temporary_container_absent": label_scoped_container_absent, + "workdir_remove_returncode": int(env["OUTER_WORKDIR_REMOVE_RC"]), + "workdir_lexists_after_cleanup": not workdir_absent, + "temporary_workdir_absent": workdir_absent, + }, + } + checks = data["checks"] + checks["outer_live_counts_exactly_unchanged"] = before_counts == after_counts + checks["outer_live_table_deltas_exactly_zero"] = all(delta == 0 for delta in live_table_deltas.values()) + checks["outer_service_observed_if_required"] = source_tier != "live-readonly" or service_observable + checks["outer_service_stable_if_observed"] = service_gate_passes + checks["outer_container_absent_independent_readback"] = container_absent + checks["outer_labeled_container_absent_independent_readback"] = label_scoped_container_absent + checks["outer_workdir_absent_independent_readback"] = workdir_absent + checks["source_binding_independently_verified"] = source_binding_ok + checks["stale_ephemeral_paths_absent"] = stale_ephemeral_paths_absent + checks["outer_source_tier_enforced"] = source_tier_enforced + checks["outer_container_network_disabled"] = container_network_mode == "none" + checks["outer_container_lifecycle_labeled"] = container_labeled + checks["outer_container_has_no_docker_volumes"] = not container_volume_mounts + checks["outer_isolation_complete"] = outer_ok + if outer_ok and inner_runtime_pass: + data["current_tier"] = "T2_runtime" if source_tier == "isolated-local" else "T3_live_readonly" + else: + data["status"] = "fail" + data["current_tier"] = "T2_runtime" if inner_runtime_pass else "T1_model" + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + return data + + +def main(argv: list[str] | None = None) -> int: + arguments = sys.argv[1:] if argv is None else argv + if len(arguments) != 1: + raise SystemExit("usage: finalize_approve_claim_isolated_canary.py REPORT.json") + try: + data = finalize(Path(arguments[0]), os.environ) + except (KeyError, OSError, ValueError, json.JSONDecodeError) as exc: + raise SystemExit(str(exc)) from exc + return 0 if data["status"] == "pass" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/kb_apply_replay_receipt.py b/scripts/kb_apply_replay_receipt.py index 83678a9..2e89345 100644 --- a/scripts/kb_apply_replay_receipt.py +++ b/scripts/kb_apply_replay_receipt.py @@ -446,6 +446,41 @@ def build_replay_receipt( } +def validate_replay_receipt( + receipt: dict[str, Any], + *, + expected_proposal_id: str | None = None, +) -> dict[str, Any]: + """Rebuild and require the exact private replay-receipt contract. + + Canonical JSON comparison is deliberate: Python equality treats booleans + and integers as interchangeable, which is unsafe for authorization and + receipt fields. + """ + if not isinstance(receipt, dict): + raise ValueError("replay receipt must be an object") + proposal = receipt.get("proposal") + rows = receipt.get("canonical_rows") + apply_engine = receipt.get("apply_engine") + if not isinstance(proposal, dict) or not isinstance(rows, dict) or not isinstance(apply_engine, dict): + raise ValueError("replay receipt is missing proposal, canonical rows, or apply-engine metadata") + if expected_proposal_id is not None and proposal.get("id") != expected_proposal_id: + raise ValueError("replay receipt proposal id does not match") + try: + rebuilt = build_replay_receipt( + proposal, + rows, + apply_sql_sha256=apply_engine.get("apply_sql_sha256"), + apply_sql_source=apply_engine.get("source"), + exported_at_utc=receipt.get("exported_at_utc"), + ) + except (KeyError, TypeError, ValueError) as exc: + raise ValueError("replay receipt failed full contract validation") from exc + if _canonical_json(receipt) != _canonical_json(rebuilt): + raise ValueError("replay receipt hashes, counts, rows, or contract fields are internally inconsistent") + return rebuilt + + def resolve_receipt_path( proposal_id: str, *, diff --git a/scripts/proposal_apply_lifecycle.py b/scripts/proposal_apply_lifecycle.py new file mode 100644 index 0000000..9ddb600 --- /dev/null +++ b/scripts/proposal_apply_lifecycle.py @@ -0,0 +1,1513 @@ +#!/usr/bin/env python3 +"""Build and verify exact apply/rollback lifecycle evidence for one proposal. + +The module deliberately supports compensating rollback only for a fresh-owned +``approve_claim`` bundle: every payload-owned row must have been absent before +apply. It is used by the disposable-clone canary and by the production +authorization packet builder. It never connects to a database by itself. +""" + +from __future__ import annotations + +import argparse +import hashlib +import json +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +HERE = Path(__file__).resolve().parent +sys.path.insert(0, str(HERE)) + +import apply_proposal as ap # noqa: E402 +import kb_apply_replay_receipt as replay_receipt # noqa: E402 + +ROLLBACK_SCHEMA = "livingip.proposalApplyRollback.v1" +LIFECYCLE_SCHEMA = "livingip.proposalApplyLifecycleReceipt.v1" +AUTHORIZATION_SCHEMA = "livingip.proposalApplyAuthorizationPacket.v1" +FRESH_PREFLIGHT_SCHEMA = "livingip.proposalApplyFreshPreflight.v1" +PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA = "livingip.proposalApplyProductionRollbackArtifact.v1" + +RUNTIME_DEPENDENCIES = ( + "scripts/proposal_apply_lifecycle.py", + "scripts/apply_proposal.py", + "scripts/kb_apply_replay_receipt.py", + "scripts/kb_apply_prereqs.sql", +) + +APPROVAL_GATE_CHECKS = ( + "immutable_approval_table_present", + "approve_function_present", + "assert_function_present", + "finish_function_present", + "review_role_present", + "apply_role_present", +) + +ROW_TABLES = { + "claims": "public.claims", + "sources": "public.sources", + "claim_evidence": "public.claim_evidence", + "claim_edges": "public.claim_edges", + "reasoning_tools": "public.reasoning_tools", +} +DELETE_ORDER = ( + "claim_evidence", + "claim_edges", + "reasoning_tools", + "sources", + "claims", +) + + +def canonical_json(value: Any) -> str: + return json.dumps(value, ensure_ascii=False, separators=(",", ":"), sort_keys=True) + + +def sha256_json(value: Any) -> str: + return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest() + + +def sha256_text(value: str) -> str: + return hashlib.sha256(value.encode("utf-8")).hexdigest() + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def is_sha256(value: Any) -> bool: + return isinstance(value, str) and len(value) == 64 and all(character in "0123456789abcdef" for character in value) + + +def repo_relative_path(path: Path) -> str: + try: + return path.resolve().relative_to(HERE.parent.resolve()).as_posix() + except ValueError as exc: + raise ValueError(f"authorization dependency must be inside the repository: {path}") from exc + + +def _require_approved_bundle(proposal: dict[str, Any]) -> dict[str, Any]: + if proposal.get("proposal_type") != "approve_claim": + raise ValueError("compensating rollback supports only approve_claim") + if proposal.get("status") != "approved": + raise ValueError("pre-apply proposal must be approved") + if any(proposal.get(field) is not None for field in ("applied_by_handle", "applied_by_agent_id", "applied_at")): + raise ValueError("approved pre-apply proposal must have blank applied fields") + payload = proposal.get("payload") + apply_payload = payload.get("apply_payload") if isinstance(payload, dict) else None + if not isinstance(apply_payload, dict): + raise ValueError("approved proposal requires payload.apply_payload") + # Reuse the audited builder as the strict contract validator. + ap.build_apply_sql(proposal, ap.SERVICE_AGENT_HANDLE) + return apply_payload + + +def build_fresh_preflight( + proposal: dict[str, Any], + target_rows_before: dict[str, list[dict[str, Any]]], + *, + captured_at_utc: str | None = None, +) -> dict[str, Any]: + """Bind an approved bundle to proof that all payload-owned rows are absent.""" + captured = captured_at_utc or datetime.now(timezone.utc).isoformat() + if _parse_utc(captured) is None: + raise ValueError("fresh preflight captured_at_utc must be a timezone-aware timestamp") + apply_payload = _require_approved_bundle(proposal) + if set(target_rows_before) != set(ROW_TABLES): + raise ValueError("fresh preflight must contain every approve_claim target table") + malformed = [name for name, rows in target_rows_before.items() if not isinstance(rows, list)] + if malformed: + raise ValueError(f"fresh preflight target rows must be lists: {malformed}") + nonempty = {name: len(rows) for name, rows in target_rows_before.items() if rows} + if nonempty: + raise ValueError(f"fresh-owned rollback requires absent target rows: {nonempty}") + envelope = { + "proposal_id": str(proposal.get("id")), + "proposal_type": proposal.get("proposal_type"), + "proposal_payload_sha256": sha256_json(proposal.get("payload")), + "apply_payload_sha256": sha256_json(apply_payload), + "target_rows_before": target_rows_before, + } + artifact_envelope = { + "captured_at_utc": captured, + **envelope, + "fresh_owned_targets": True, + } + return { + "artifact": "proposal_apply_fresh_preflight", + "schema": FRESH_PREFLIGHT_SCHEMA, + **artifact_envelope, + "preflight_sha256": sha256_json(envelope), + "preflight_artifact_sha256": sha256_json(artifact_envelope), + } + + +def _require_fresh_preflight(proposal_before: dict[str, Any], preflight: dict[str, Any]) -> None: + apply_payload = _require_approved_bundle(proposal_before) + if not isinstance(preflight, dict): + raise ValueError("fresh preflight must be an object") + expected_keys = { + "artifact", + "schema", + "captured_at_utc", + "proposal_id", + "proposal_type", + "proposal_payload_sha256", + "apply_payload_sha256", + "target_rows_before", + "fresh_owned_targets", + "preflight_sha256", + "preflight_artifact_sha256", + } + if set(preflight) != expected_keys: + raise ValueError("fresh preflight contract fields are incomplete or unexpected") + if ( + preflight.get("artifact") != "proposal_apply_fresh_preflight" + or preflight.get("schema") != FRESH_PREFLIGHT_SCHEMA + or preflight.get("fresh_owned_targets") is not True + ): + raise ValueError("rollback requires an exact fresh-owned preflight contract") + target_rows = preflight.get("target_rows_before") + if ( + not isinstance(target_rows, dict) + or set(target_rows) != set(ROW_TABLES) + or any(not isinstance(rows, list) or rows for rows in target_rows.values()) + ): + raise ValueError("fresh preflight must prove every payload-owned target row was absent") + envelope = { + "proposal_id": str(proposal_before.get("id")), + "proposal_type": proposal_before.get("proposal_type"), + "proposal_payload_sha256": sha256_json(proposal_before.get("payload")), + "apply_payload_sha256": sha256_json(apply_payload), + "target_rows_before": target_rows, + } + if any(preflight.get(key) != value for key, value in envelope.items()): + raise ValueError("fresh preflight does not match the approved proposal") + if preflight.get("preflight_sha256") != sha256_json(envelope): + raise ValueError("fresh preflight hash is internally inconsistent") + artifact_envelope = { + "captured_at_utc": preflight.get("captured_at_utc"), + **envelope, + "fresh_owned_targets": True, + } + if _parse_utc(preflight.get("captured_at_utc")) is None: + raise ValueError("fresh preflight captured_at_utc is invalid") + if preflight.get("preflight_artifact_sha256") != sha256_json(artifact_envelope): + raise ValueError("fresh preflight artifact hash is internally inconsistent") + rebuilt = build_fresh_preflight( + proposal_before, + target_rows, + captured_at_utc=preflight.get("captured_at_utc"), + ) + if canonical_json(preflight) != canonical_json(rebuilt): + raise ValueError("fresh preflight contract is internally inconsistent") + + +def _require_apply_receipt( + proposal_before: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], +) -> dict[str, list[dict[str, Any]]]: + _require_approved_bundle(proposal_before) + _require_fresh_preflight(proposal_before, preflight) + proposal_after = apply_receipt.get("proposal") + if ( + apply_receipt.get("artifact") != "kb_apply_replay_receipt" + or apply_receipt.get("receipt_contract_version") != replay_receipt.RECEIPT_CONTRACT_VERSION + or apply_receipt.get("replay_ready") is not True + ): + raise ValueError("rollback requires a replay-ready kb_apply receipt") + if not isinstance(proposal_after, dict) or proposal_after.get("status") != "applied": + raise ValueError("apply receipt proposal must be applied") + if proposal_after.get("id") != proposal_before.get("id"): + raise ValueError("apply receipt proposal id does not match preflight") + if proposal_after.get("payload") != proposal_before.get("payload"): + raise ValueError("apply receipt payload does not match preflight") + if not proposal_after.get("applied_at") or not proposal_after.get("applied_by_handle"): + raise ValueError("apply receipt requires applied_at and applied_by_handle") + rows = apply_receipt.get("canonical_rows") + if not isinstance(rows, dict) or set(rows) != set(ROW_TABLES): + raise ValueError("apply receipt must contain every approve_claim canonical row family") + for name, table_rows in rows.items(): + if not isinstance(table_rows, list) or any(not isinstance(row, dict) for row in table_rows): + raise ValueError(f"apply receipt {name} rows are malformed") + identifiers = [row.get("id") for row in table_rows] + if any(not identifier for identifier in identifiers) or len(set(identifiers)) != len(identifiers): + raise ValueError(f"apply receipt {name} rows require unique persisted ids") + try: + replay_receipt.validate_replay_receipt( + apply_receipt, + expected_proposal_id=str(proposal_before.get("id")), + ) + except ValueError as exc: + raise ValueError("apply receipt failed full replay-contract validation") from exc + proposal_id = str(proposal_before.get("id")) + for row in rows["claim_evidence"]: + expected_id = ap.deterministic_row_uuid( + proposal_id, + "claim_evidence", + row.get("claim_id"), + row.get("source_id"), + row.get("role"), + ) + if row.get("id") != expected_id: + raise ValueError("fresh apply receipt evidence id is not deterministic") + for row in rows["claim_edges"]: + expected_id = ap.deterministic_row_uuid( + proposal_id, + "claim_edge", + row.get("from_claim"), + row.get("to_claim"), + row.get("edge_type"), + ) + if row.get("id") != expected_id: + raise ValueError("fresh apply receipt edge id is not deterministic") + return rows + + +def _row_assertion(table: str, row: dict[str, Any], label: str) -> str: + row_json = ap.sql_literal(canonical_json(row)) + row_id = ap.sql_literal(row["id"]) + return f""" if (select count(*) from {table} t + where t.id = {row_id}::uuid and to_jsonb(t) = {row_json}::jsonb) <> 1 then + raise exception 'proposal rollback drift: {label} row does not match apply receipt'; + end if;""" + + +def _delete_statement(table: str, rows: list[dict[str, Any]], label: str) -> str: + if not rows: + return f" -- no {label} rows owned by this proposal" + identifiers = ", ".join(f"{ap.sql_literal(row['id'])}::uuid" for row in rows) + return f""" delete from {table} where id in ({identifiers}); + get diagnostics affected = row_count; + if affected <> {len(rows)} then + raise exception 'proposal rollback count mismatch for {label}: expected {len(rows)}, got %', affected; + end if;""" + + +def _require_approval_snapshot(proposal_before: dict[str, Any], approval_snapshot: dict[str, Any]) -> None: + expected_keys = { + "proposal_id", + "proposal_type", + "payload", + "reviewed_by_handle", + "reviewed_by_agent_id", + "reviewed_by_db_role", + "reviewed_at", + "review_note", + } + if not isinstance(approval_snapshot, dict) or set(approval_snapshot) != expected_keys: + raise ValueError("immutable approval snapshot contract is incomplete or unexpected") + reviewer_role = approval_snapshot.get("reviewed_by_db_role") + if not isinstance(reviewer_role, str) or not reviewer_role or reviewer_role == "kb_apply": + raise ValueError("immutable approval snapshot requires an independent review role") + expected = { + "proposal_id": proposal_before.get("id"), + "proposal_type": proposal_before.get("proposal_type"), + "payload": proposal_before.get("payload"), + "reviewed_by_handle": proposal_before.get("reviewed_by_handle"), + "reviewed_by_agent_id": proposal_before.get("reviewed_by_agent_id"), + "reviewed_by_db_role": reviewer_role, + "reviewed_at": proposal_before.get("reviewed_at"), + "review_note": proposal_before.get("review_note"), + } + if canonical_json(approval_snapshot) != canonical_json(expected): + raise ValueError("immutable approval snapshot does not match the approved proposal") + if approval_snapshot.get("reviewed_by_handle") == ap.SERVICE_AGENT_HANDLE: + raise ValueError("apply service cannot independently approve its own proposal") + + +def build_compensating_rollback_sql( + proposal_before: dict[str, Any], + approval_snapshot: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], +) -> str: + """Build one atomic, drift-sensitive apply inverse for a fresh-owned bundle.""" + rows = _require_apply_receipt(proposal_before, preflight, apply_receipt) + _require_approval_snapshot(proposal_before, approval_snapshot) + + applied = apply_receipt["proposal"] + proposal_id = ap.sql_literal(proposal_before["id"]) + payload = ap.sql_literal(canonical_json(proposal_before["payload"])) + reviewed_by_handle = ap.sql_literal(proposal_before.get("reviewed_by_handle")) + reviewed_by_agent_id = ap.sql_literal(proposal_before.get("reviewed_by_agent_id")) + reviewed_at = ap.sql_literal(proposal_before.get("reviewed_at")) + review_note = ap.sql_literal(proposal_before.get("review_note")) + applied_by_handle = ap.sql_literal(applied.get("applied_by_handle")) + applied_by_agent_id = ap.sql_literal(applied.get("applied_by_agent_id")) + applied_at = ap.sql_literal(applied.get("applied_at")) + approval_json = ap.sql_literal(canonical_json(approval_snapshot)) + + row_assertions = [] + for family in DELETE_ORDER: + table = ROW_TABLES[family] + row_assertions.extend( + _row_assertion(table, row, f"{family}[{index}]") for index, row in enumerate(rows[family]) + ) + deletions = [_delete_statement(ROW_TABLES[family], rows[family], family) for family in DELETE_ORDER] + + return f"""begin; +set local standard_conforming_strings = on; +set local lock_timeout = '5s'; +select pg_advisory_xact_lock(hashtextextended('proposal-rollback:' || {proposal_id}, 0)); +do $rollback$ +declare + affected integer; + approval_now jsonb; +begin + perform 1 from kb_stage.kb_proposals + where id = {proposal_id}::uuid + for update; + if not found then + raise exception 'proposal rollback: proposal row missing'; + end if; + if not exists ( + select 1 from kb_stage.kb_proposals + where id = {proposal_id}::uuid + and proposal_type = 'approve_claim' + and status = 'applied' + and payload = {payload}::jsonb + and reviewed_by_handle is not distinct from {reviewed_by_handle} + and reviewed_by_agent_id is not distinct from {reviewed_by_agent_id}::uuid + and reviewed_at is not distinct from {reviewed_at}::timestamptz + and review_note is not distinct from {review_note} + and applied_by_handle is not distinct from {applied_by_handle} + and applied_by_agent_id is not distinct from {applied_by_agent_id}::uuid + and applied_at is not distinct from {applied_at}::timestamptz + ) then + raise exception 'proposal rollback: applied ledger does not match apply receipt'; + end if; + select jsonb_build_object( + 'proposal_id', proposal_id::text, + 'proposal_type', proposal_type, + 'payload', payload, + 'reviewed_by_handle', reviewed_by_handle, + 'reviewed_by_agent_id', reviewed_by_agent_id::text, + 'reviewed_by_db_role', reviewed_by_db_role::text, + 'reviewed_at', reviewed_at::text, + 'review_note', review_note + ) into approval_now + from kb_stage.kb_proposal_approvals + where proposal_id = {proposal_id}::uuid; + if approval_now is distinct from {approval_json}::jsonb then + raise exception 'proposal rollback: immutable approval snapshot drifted'; + end if; +{chr(10).join(row_assertions)} +{chr(10).join(deletions)} + update kb_stage.kb_proposals + set status = 'approved', + applied_by_handle = null, + applied_by_agent_id = null, + applied_at = null, + updated_at = now() + where id = {proposal_id}::uuid + and status = 'applied' + and payload = {payload}::jsonb + and applied_at is not distinct from {applied_at}::timestamptz; + get diagnostics affected = row_count; + if affected <> 1 then + raise exception 'proposal rollback: expected one applied ledger row, got %', affected; + end if; +end +$rollback$; +commit; +""" + + +def build_production_rollback_artifact( + *, + proposal_before: dict[str, Any], + approval_snapshot: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], + destination: dict[str, Any], + generated_at_utc: str | None = None, +) -> dict[str, Any]: + """Build a private artifact whose rollback SQL is exactly reconstructable.""" + destination_keys = {"container", "database", "system_identifier", "server_version_num"} + if not isinstance(destination, dict) or set(destination) != destination_keys: + raise ValueError("production rollback artifact requires the exact destination identity") + if any(not destination.get(key) for key in destination_keys): + raise ValueError("production rollback artifact destination identity is incomplete") + generated = generated_at_utc or datetime.now(timezone.utc).isoformat() + if _parse_utc(generated) is None: + raise ValueError("production rollback artifact timestamp is invalid") + rollback_sql = build_compensating_rollback_sql( + proposal_before, + approval_snapshot, + preflight, + apply_receipt, + ) + private_material = { + "proposal_before": proposal_before, + "approval_snapshot": approval_snapshot, + "preflight": preflight, + "apply_receipt": apply_receipt, + } + artifact = { + "artifact": "proposal_apply_production_rollback_artifact", + "schema": PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA, + "generated_at_utc": generated, + "destination": destination, + "proposal_id": str(proposal_before.get("id")), + "proposal_payload_sha256": sha256_json(proposal_before.get("payload")), + "approval_snapshot_sha256": sha256_json(approval_snapshot), + "fresh_preflight_artifact_sha256": preflight.get("preflight_artifact_sha256"), + "apply_receipt_sha256": sha256_json(apply_receipt), + "rollback_sql": rollback_sql, + "rollback_sql_sha256": sha256_text(rollback_sql), + "private_material": private_material, + "privacy": "private production rollback material; never commit or print", + } + artifact["artifact_sha256"] = sha256_json(artifact) + return artifact + + +def validate_production_rollback_artifact( + artifact: dict[str, Any], + *, + expected_destination: dict[str, Any] | None = None, +) -> dict[str, Any]: + expected_keys = { + "artifact", + "schema", + "generated_at_utc", + "destination", + "proposal_id", + "proposal_payload_sha256", + "approval_snapshot_sha256", + "fresh_preflight_artifact_sha256", + "apply_receipt_sha256", + "rollback_sql", + "rollback_sql_sha256", + "private_material", + "privacy", + "artifact_sha256", + } + if not isinstance(artifact, dict) or set(artifact) != expected_keys: + raise ValueError("production rollback artifact contract is incomplete or unexpected") + if ( + artifact.get("artifact") != "proposal_apply_production_rollback_artifact" + or artifact.get("schema") != PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA + or artifact.get("privacy") != "private production rollback material; never commit or print" + or _parse_utc(artifact.get("generated_at_utc")) is None + ): + raise ValueError("production rollback artifact metadata is invalid") + unsigned = dict(artifact) + artifact_sha256 = unsigned.pop("artifact_sha256") + if not is_sha256(artifact_sha256) or sha256_json(unsigned) != artifact_sha256: + raise ValueError("production rollback artifact hash is internally inconsistent") + destination = artifact.get("destination") + destination_keys = {"container", "database", "system_identifier", "server_version_num"} + if not isinstance(destination, dict) or set(destination) != destination_keys: + raise ValueError("production rollback artifact destination is invalid") + if expected_destination is not None and canonical_json(destination) != canonical_json(expected_destination): + raise ValueError("production rollback artifact destination does not match authorization binding") + material = artifact.get("private_material") + material_keys = {"proposal_before", "approval_snapshot", "preflight", "apply_receipt"} + if not isinstance(material, dict) or set(material) != material_keys: + raise ValueError("production rollback artifact private material is incomplete") + expected_sql = build_compensating_rollback_sql( + material["proposal_before"], + material["approval_snapshot"], + material["preflight"], + material["apply_receipt"], + ) + expected_fields = { + "proposal_id": str(material["proposal_before"].get("id")), + "proposal_payload_sha256": sha256_json(material["proposal_before"].get("payload")), + "approval_snapshot_sha256": sha256_json(material["approval_snapshot"]), + "fresh_preflight_artifact_sha256": material["preflight"].get("preflight_artifact_sha256"), + "apply_receipt_sha256": sha256_json(material["apply_receipt"]), + "rollback_sql": expected_sql, + "rollback_sql_sha256": sha256_text(expected_sql), + } + if any(canonical_json(artifact.get(key)) != canonical_json(value) for key, value in expected_fields.items()): + raise ValueError("production rollback artifact does not match reconstructed exact SQL") + return artifact + + +def _row_ids(rows: dict[str, list[dict[str, Any]]]) -> dict[str, list[str]]: + return {name: sorted(str(row["id"]) for row in table_rows) for name, table_rows in rows.items()} + + +def build_lifecycle_receipt( + *, + proposal_before: dict[str, Any], + approval_before: dict[str, Any], + preflight: dict[str, Any], + apply_receipt: dict[str, Any], + rollback_sql: str, + proposal_after: dict[str, Any], + approval_after: dict[str, Any], + target_rows_after: dict[str, list[dict[str, Any]]], + counts_before: dict[str, int], + counts_after: dict[str, int], + unrelated_before: dict[str, Any], + unrelated_after: dict[str, Any], + rollback_replay_refused: bool, + generated_at_utc: str | None = None, +) -> dict[str, Any]: + rows = _require_apply_receipt(proposal_before, preflight, apply_receipt) + expected_rollback_sql = build_compensating_rollback_sql( + proposal_before, + approval_before, + preflight, + apply_receipt, + ) + target_rows_absent = set(target_rows_after) == set(ROW_TABLES) and all( + isinstance(value, list) and not value for value in target_rows_after.values() + ) + expected_after = dict(proposal_before) + expected_after.update( + { + "status": "approved", + "applied_by_handle": None, + "applied_by_agent_id": None, + "applied_at": None, + } + ) + ledger_restored = all(proposal_after.get(key) == value for key, value in expected_after.items()) + checks = { + "fresh_owned_targets": preflight.get("fresh_owned_targets") is True, + "apply_receipt_replay_ready": apply_receipt.get("replay_ready") is True, + "apply_rows_have_exact_ids": all(rows[name] == apply_receipt["canonical_rows"][name] for name in ROW_TABLES), + "rollback_target_rows_absent": target_rows_absent, + "rollback_counts_restored": counts_after == counts_before, + "rollback_ledger_restored_to_approved": ledger_restored, + "immutable_approval_unchanged": approval_after == approval_before, + "unrelated_rows_unchanged": unrelated_after == unrelated_before, + "rollback_replay_refused_without_mutation": rollback_replay_refused is True, + "rollback_sql_exact_for_bound_rows": rollback_sql == expected_rollback_sql, + } + proposal_projection = { + key: proposal_before.get(key) + for key in ( + "id", + "proposal_type", + "status", + "payload", + "reviewed_by_handle", + "reviewed_by_agent_id", + "reviewed_at", + "review_note", + "applied_by_handle", + "applied_by_agent_id", + "applied_at", + ) + } + receipt = { + "artifact": "proposal_apply_lifecycle_receipt", + "schema": LIFECYCLE_SCHEMA, + "generated_at_utc": generated_at_utc or datetime.now(timezone.utc).isoformat(), + "required_tier": "T2_runtime", + "current_tier": "T2_runtime" if all(checks.values()) else "T1_model", + "proposal_before_apply": proposal_projection, + "proposal_payload_sha256": sha256_json(proposal_before.get("payload")), + "apply_payload_sha256": sha256_json(proposal_before["payload"]["apply_payload"]), + "approval_snapshot_sha256": sha256_json(approval_before), + "fresh_preflight_sha256": preflight["preflight_sha256"], + "fresh_preflight_artifact_sha256": preflight["preflight_artifact_sha256"], + "apply_receipt_sha256": sha256_json(apply_receipt), + "apply_sql_sha256": apply_receipt["apply_engine"]["apply_sql_sha256"], + "applied_row_ids": _row_ids(rows), + "applied_row_sha256": {name: [sha256_json(row) for row in table_rows] for name, table_rows in rows.items()}, + "rollback": { + "schema": ROLLBACK_SCHEMA, + "rollback_sql_sha256": sha256_text(rollback_sql), + "delete_order": list(DELETE_ORDER), + "target_rows_after": target_rows_after, + "counts_before_apply": counts_before, + "counts_after_rollback": counts_after, + "proposal_after": { + key: proposal_after.get(key) + for key in ( + "id", + "proposal_type", + "status", + "reviewed_by_handle", + "reviewed_by_agent_id", + "reviewed_at", + "review_note", + "applied_by_handle", + "applied_by_agent_id", + "applied_at", + ) + }, + }, + "checks": checks, + "pass": all(checks.values()), + "production_apply_executed": False, + "production_apply_authorization_present": False, + "strongest_claim_allowed": ( + "deterministic disposable-clone apply and compensating rollback" + if all(checks.values()) + else "local lifecycle evidence incomplete" + ), + } + receipt["lifecycle_receipt_sha256"] = sha256_json(receipt) + return receipt + + +def _require_lifecycle_receipt(lifecycle_receipt: dict[str, Any]) -> None: + expected_keys = { + "artifact", + "schema", + "generated_at_utc", + "required_tier", + "current_tier", + "proposal_before_apply", + "proposal_payload_sha256", + "apply_payload_sha256", + "approval_snapshot_sha256", + "fresh_preflight_sha256", + "fresh_preflight_artifact_sha256", + "apply_receipt_sha256", + "apply_sql_sha256", + "applied_row_ids", + "applied_row_sha256", + "rollback", + "checks", + "pass", + "production_apply_executed", + "production_apply_authorization_present", + "strongest_claim_allowed", + "lifecycle_receipt_sha256", + } + if not isinstance(lifecycle_receipt, dict) or set(lifecycle_receipt) != expected_keys: + raise ValueError("authorization packet requires the exact lifecycle receipt contract") + if ( + lifecycle_receipt.get("artifact") != "proposal_apply_lifecycle_receipt" + or lifecycle_receipt.get("schema") != LIFECYCLE_SCHEMA + or lifecycle_receipt.get("required_tier") != "T2_runtime" + or lifecycle_receipt.get("current_tier") != "T2_runtime" + or lifecycle_receipt.get("pass") is not True + or lifecycle_receipt.get("production_apply_executed") is not False + or lifecycle_receipt.get("production_apply_authorization_present") is not False + ): + raise ValueError("authorization packet requires a passing, non-production lifecycle receipt") + checks = lifecycle_receipt.get("checks") + if not isinstance(checks, dict) or not checks or any(value is not True for value in checks.values()): + raise ValueError("lifecycle receipt checks are incomplete or failed") + unsigned = dict(lifecycle_receipt) + receipt_sha256 = unsigned.pop("lifecycle_receipt_sha256") + if not is_sha256(receipt_sha256) or sha256_json(unsigned) != receipt_sha256: + raise ValueError("lifecycle receipt hash is internally inconsistent") + proposal = lifecycle_receipt.get("proposal_before_apply") + if not isinstance(proposal, dict) or proposal.get("status") != "approved": + raise ValueError("lifecycle receipt proposal is not approved before apply") + payload = proposal.get("payload") + apply_payload = payload.get("apply_payload") if isinstance(payload, dict) else None + if ( + not isinstance(apply_payload, dict) + or sha256_json(payload) != lifecycle_receipt.get("proposal_payload_sha256") + or sha256_json(apply_payload) != lifecycle_receipt.get("apply_payload_sha256") + ): + raise ValueError("lifecycle receipt proposal hashes are inconsistent") + hash_fields = ( + "approval_snapshot_sha256", + "fresh_preflight_sha256", + "fresh_preflight_artifact_sha256", + "apply_receipt_sha256", + "apply_sql_sha256", + ) + if any(not is_sha256(lifecycle_receipt.get(field)) for field in hash_fields): + raise ValueError("lifecycle receipt is missing an exact SHA-256 binding") + row_ids = lifecycle_receipt.get("applied_row_ids") + row_hashes = lifecycle_receipt.get("applied_row_sha256") + if ( + not isinstance(row_ids, dict) + or not isinstance(row_hashes, dict) + or set(row_ids) != set(ROW_TABLES) + or set(row_hashes) != set(ROW_TABLES) + or any(not isinstance(values, list) for values in row_ids.values()) + or any( + not isinstance(values, list) or any(not is_sha256(value) for value in values) + for values in row_hashes.values() + ) + or any(len(row_ids[name]) != len(row_hashes[name]) for name in ROW_TABLES) + ): + raise ValueError("lifecycle receipt row-level bindings are incomplete") + rollback = lifecycle_receipt.get("rollback") + if not isinstance(rollback, dict) or rollback.get("schema") != ROLLBACK_SCHEMA: + raise ValueError("lifecycle receipt rollback contract is missing") + if not is_sha256(rollback.get("rollback_sql_sha256")): + raise ValueError("lifecycle receipt rollback SQL hash is invalid") + target_rows_after = rollback.get("target_rows_after") + if ( + not isinstance(target_rows_after, dict) + or set(target_rows_after) != set(ROW_TABLES) + or any(not isinstance(rows, list) or rows for rows in target_rows_after.values()) + or rollback.get("counts_before_apply") != rollback.get("counts_after_rollback") + ): + raise ValueError("lifecycle receipt does not prove exact rollback cleanup") + + +def current_git_sha(repo_root: Path) -> str: + result = subprocess.run( + ["git", "rev-parse", "HEAD"], + cwd=repo_root, + text=True, + capture_output=True, + check=False, + ) + return result.stdout.strip() if result.returncode == 0 else "" + + +def runtime_manifest_for_commit( + repo_git_sha: str, + *, + require_current_head: bool, +) -> dict[str, str]: + repo_root = HERE.parent.resolve() + if ( + not isinstance(repo_git_sha, str) + or len(repo_git_sha) != 40 + or any(character not in "0123456789abcdef" for character in repo_git_sha) + ): + raise ValueError("authorization runtime requires a full lowercase Git commit SHA") + head = current_git_sha(repo_root) + if require_current_head and head != repo_git_sha: + raise ValueError("authorization packet Git SHA must equal the current implementation HEAD") + ancestor = subprocess.run( + ["git", "merge-base", "--is-ancestor", repo_git_sha, head], + cwd=repo_root, + text=True, + capture_output=True, + check=False, + ) + if ancestor.returncode != 0: + raise ValueError("authorization runtime commit is not an ancestor of the current checkout") + status = subprocess.run( + ["git", "status", "--porcelain", "--", *RUNTIME_DEPENDENCIES], + cwd=repo_root, + text=True, + capture_output=True, + check=False, + ) + if status.returncode != 0 or status.stdout.strip(): + raise ValueError("authorization runtime dependencies are dirty or untracked") + manifest: dict[str, str] = {} + for relative in RUNTIME_DEPENDENCIES: + current_path = repo_root / relative + if not current_path.is_file(): + raise ValueError(f"authorization runtime dependency is missing: {relative}") + committed = subprocess.run( + ["git", "show", f"{repo_git_sha}:{relative}"], + cwd=repo_root, + capture_output=True, + check=False, + ) + if committed.returncode != 0: + raise ValueError(f"authorization runtime dependency is absent from the bound commit: {relative}") + committed_sha256 = hashlib.sha256(committed.stdout).hexdigest() + current_sha256 = sha256_file(current_path) + if committed_sha256 != current_sha256: + raise ValueError(f"authorization runtime dependency differs from the bound commit: {relative}") + manifest[relative] = current_sha256 + return manifest + + +def _authorization_text_template(binding: dict[str, Any], binding_sha256: str) -> str: + proposal = binding["proposal"] + destination = binding["destination"] + return ( + "I authorize one production DB apply for proposal " + f"{proposal['id']} with payload SHA-256 {proposal['proposal_payload_sha256']} " + f"to Docker container {destination['container']}, database {destination['database']}, " + f"system identifier {destination['system_identifier']}, binding SHA-256 {binding_sha256}. " + "I also authorize conditional production rollback SHA-256 " + " " + "only if the bound postflight fails and no downstream dependency exists. " + "Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. " + "Operator ; received ; expires ." + ) + + +def build_authorization_packet( + lifecycle_receipt: dict[str, Any], + destination_readback: dict[str, Any], + *, + repo_git_sha: str, + apply_engine_path: Path, + approval_gate_path: Path = HERE / "kb_apply_prereqs.sql", + production_rollback_artifact: dict[str, Any] | None = None, + generated_at_utc: str | None = None, +) -> dict[str, Any]: + _require_lifecycle_receipt(lifecycle_receipt) + if not apply_engine_path.is_file(): + raise ValueError(f"apply engine file is missing: {apply_engine_path}") + if not approval_gate_path.is_file(): + raise ValueError(f"approval gate prerequisite file is missing: {approval_gate_path}") + apply_engine_relative = repo_relative_path(apply_engine_path) + approval_gate_relative = repo_relative_path(approval_gate_path) + if apply_engine_relative != "scripts/apply_proposal.py": + raise ValueError("authorization packet requires the canonical apply engine path") + if approval_gate_relative != "scripts/kb_apply_prereqs.sql": + raise ValueError("authorization packet requires the canonical approval-gate path") + runtime_manifest = runtime_manifest_for_commit(repo_git_sha, require_current_head=True) + required_destination = { + key: destination_readback.get(key) + for key in ("container", "database", "system_identifier", "server_version_num") + } + if any(not value for value in required_destination.values()): + raise ValueError("destination readback is missing exact production identity fields") + production_rollback_sha256: str | None = None + production_rollback_artifact_sha256: str | None = None + if production_rollback_artifact is not None: + validated_rollback = validate_production_rollback_artifact( + production_rollback_artifact, + expected_destination=required_destination, + ) + if ( + validated_rollback.get("proposal_id") != lifecycle_receipt["proposal_before_apply"].get("id") + or validated_rollback.get("proposal_payload_sha256") != lifecycle_receipt.get("proposal_payload_sha256") + or validated_rollback.get("approval_snapshot_sha256") != lifecycle_receipt.get("approval_snapshot_sha256") + ): + raise ValueError("production rollback artifact does not match the lifecycle proposal binding") + production_rollback_sha256 = validated_rollback["rollback_sql_sha256"] + production_rollback_artifact_sha256 = validated_rollback["artifact_sha256"] + proposal = lifecycle_receipt["proposal_before_apply"] + approval_gate = destination_readback.get("approval_gate") or {} + bound_gate = {check: approval_gate.get(check) for check in APPROVAL_GATE_CHECKS} + approval_gate_ready = all(bound_gate.get(check) is True for check in APPROVAL_GATE_CHECKS) + candidates = destination_readback.get("approved_strict_candidates") or [] + if not isinstance(candidates, list) or any(not isinstance(row, dict) for row in candidates): + raise ValueError("destination readback candidates must be row objects") + matching = [ + row + for row in candidates + if row.get("id") == proposal["id"] + and row.get("proposal_type") == proposal["proposal_type"] + and row.get("status") == "approved" + and row.get("applied_at") is None + and row.get("proposal_payload_sha256") == lifecycle_receipt["proposal_payload_sha256"] + and row.get("apply_payload_sha256") == lifecycle_receipt["apply_payload_sha256"] + and row.get("approval_snapshot_sha256") == lifecycle_receipt["approval_snapshot_sha256"] + and row.get("reviewed_by_handle") == proposal.get("reviewed_by_handle") + and row.get("reviewed_by_agent_id") == proposal.get("reviewed_by_agent_id") + and row.get("reviewed_at") == proposal.get("reviewed_at") + and row.get("review_note") == proposal.get("review_note") + and row.get("immutable_approval_exact") is True + ] + candidate_projection = ( + { + "id": matching[0].get("id"), + "proposal_type": matching[0].get("proposal_type"), + "status": matching[0].get("status"), + "applied_at": matching[0].get("applied_at"), + "proposal_payload_sha256": matching[0].get("proposal_payload_sha256"), + "apply_payload_sha256": matching[0].get("apply_payload_sha256"), + "approval_snapshot_sha256": matching[0].get("approval_snapshot_sha256"), + "reviewed_by_handle": matching[0].get("reviewed_by_handle"), + "reviewed_by_agent_id": matching[0].get("reviewed_by_agent_id"), + "reviewed_at": matching[0].get("reviewed_at"), + "review_note": matching[0].get("review_note"), + "immutable_approval_exact": matching[0].get("immutable_approval_exact"), + } + if len(matching) == 1 + else None + ) + observed_at_utc = destination_readback.get("observed_at_utc") + destination_readback_was_read_only = ( + destination_readback.get("read_only_transaction") is True and _parse_utc(observed_at_utc) is not None + ) + production_preconditions = { + "destination_readback_observed_at_utc": observed_at_utc, + "destination_readback_was_read_only": destination_readback_was_read_only, + "approval_gate_ready": approval_gate_ready, + "strict_proposal_present_and_approved": len(matching) == 1, + "matching_candidate_count": len(matching), + "exact_production_rollback_packet_ready": production_rollback_sha256 is not None, + "production_apply_authorization_present": False, + "production_apply_executed": False, + } + binding = { + "proposal": { + "id": proposal["id"], + "proposal_type": proposal["proposal_type"], + "required_status": "approved", + "required_applied_at": None, + "payload": proposal["payload"], + "reviewed_by_handle": proposal.get("reviewed_by_handle"), + "reviewed_by_agent_id": proposal.get("reviewed_by_agent_id"), + "reviewed_at": proposal.get("reviewed_at"), + "review_note": proposal.get("review_note"), + "proposal_payload_sha256": lifecycle_receipt["proposal_payload_sha256"], + "apply_payload_sha256": lifecycle_receipt["apply_payload_sha256"], + "approval_snapshot_sha256": lifecycle_receipt["approval_snapshot_sha256"], + }, + "destination": required_destination, + "destination_readback": { + "observed_at_utc": observed_at_utc, + "read_only_transaction": destination_readback.get("read_only_transaction"), + }, + "approval_gate": bound_gate, + "candidate_readback": { + "matching_candidate_count": len(matching), + "matching_candidate": candidate_projection, + }, + "approval_gate_dependency": { + "path": approval_gate_relative, + "sha256": sha256_file(approval_gate_path), + "requires_separate_production_authorization": True, + }, + "engine": { + "repo_git_sha": repo_git_sha, + "apply_engine_path": apply_engine_relative, + "apply_engine_sha256": sha256_file(apply_engine_path), + "apply_sql_sha256": lifecycle_receipt["apply_sql_sha256"], + "runtime_manifest": runtime_manifest, + }, + "rollback": { + "rollback_sql_sha256": lifecycle_receipt["rollback"]["rollback_sql_sha256"], + "lifecycle_receipt_sha256": lifecycle_receipt["lifecycle_receipt_sha256"], + "scope": "disposable_clone_rehearsal", + "production_executable": False, + "conditional_rollback_requires_explicit_authorization": True, + }, + "production_rollback": { + "status": "ready" if production_rollback_sha256 is not None else "not_generated", + "rollback_sql_sha256": production_rollback_sha256, + "artifact_schema": ( + PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA if production_rollback_artifact_sha256 is not None else None + ), + "artifact_sha256": production_rollback_artifact_sha256, + "scope": "exact_production_destination_postapply_receipt", + "bound_for_action_time_authorization": production_rollback_artifact_sha256 is not None, + "conditional_rollback_requires_explicit_authorization": True, + }, + "expected_rows": { + "row_ids": lifecycle_receipt["applied_row_ids"], + "row_sha256": lifecycle_receipt["applied_row_sha256"], + }, + "production_preconditions": production_preconditions, + } + binding_sha256 = sha256_json(binding) + packet = { + "artifact": "proposal_apply_production_authorization_packet", + "schema": AUTHORIZATION_SCHEMA, + "generated_at_utc": generated_at_utc or datetime.now(timezone.utc).isoformat(), + "required_tier": "T6_authorized_production", + "current_tier": "T3_live_readonly", + "binding": binding, + "binding_sha256": binding_sha256, + "production_preconditions": dict(production_preconditions), + "authorization_record_required": { + "authorized": True, + "rollback_authorized": True, + "binding_sha256": binding_sha256, + "production_rollback_sql_sha256": (production_rollback_sha256 or ""), + "operator_identity": "", + "received_at_utc": "", + "expires_at_utc": "", + "authorization_text": "", + }, + "authorization_text_template": _authorization_text_template(binding, binding_sha256), + "safe_to_enter_apply_window": False, + "production_apply_authorization_present": False, + "production_apply_executed": False, + "scope_exclusions": { + "approval_gate_deployment_authorized": False, + "telegram_send_authorized": False, + "gcp_promotion_authorized": False, + "cloud_sql_exposure_authorized": False, + }, + "production_rollback_requirement": { + "status": "ready" if production_rollback_sha256 is not None else "not_generated", + "production_rollback_sql_sha256": production_rollback_sha256, + "production_rollback_artifact_schema": ( + PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA if production_rollback_artifact_sha256 is not None else None + ), + "production_rollback_artifact_sha256": production_rollback_artifact_sha256, + "clone_rehearsal_rollback_sql_sha256": lifecycle_receipt["rollback"]["rollback_sql_sha256"], + "clone_rehearsal_is_not_production_rollback": True, + "required_before_apply_window": True, + "required_binding": ( + "an exact production rollback SQL SHA-256 in both the action-time record and authorization text" + ), + }, + "next_exact_action": ( + "refresh the exact destination through a read-only transaction" + if not destination_readback_was_read_only + else "deploy and independently verify the immutable approval gate before selecting a production proposal" + if not approval_gate_ready + else "stage this exact strict proposal in production and obtain independent review" + if not matching + else "generate and retain the exact production rollback SQL" + if production_rollback_sha256 is None + else "obtain the exact action-time authorization record" + ), + "claim_ceiling": ( + "T2 clone lifecycle plus T3 destination readback; no production apply authorization or execution" + ), + } + return packet + + +def expected_authorization_text(packet: dict[str, Any], record: dict[str, Any]) -> str: + template = packet.get("authorization_text_template") + if not isinstance(template, str): + raise ValueError("authorization packet has no text template") + return ( + template.replace( + "", + str(record.get("production_rollback_sql_sha256") or ""), + ) + .replace("", str(record.get("operator_identity") or "")) + .replace("", str(record.get("received_at_utc") or ""), 1) + .replace("", str(record.get("expires_at_utc") or ""), 1) + ) + + +def _parse_utc(value: Any) -> datetime | None: + if not isinstance(value, str) or not value: + return None + try: + parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) + except ValueError: + return None + if parsed.tzinfo is None: + return None + return parsed.astimezone(timezone.utc) + + +def verify_authorization_record( + packet: dict[str, Any], + record: dict[str, Any], + *, + observed_destination: dict[str, Any], + apply_engine_path: Path, + approval_gate_path: Path = HERE / "kb_apply_prereqs.sql", + production_rollback_artifact: dict[str, Any] | None = None, + now: datetime | None = None, +) -> dict[str, Any]: + packet = packet if isinstance(packet, dict) else {} + record = record if isinstance(record, dict) else {} + observed_destination = observed_destination if isinstance(observed_destination, dict) else {} + now = (now or datetime.now(timezone.utc)).astimezone(timezone.utc) + received = _parse_utc(record.get("received_at_utc")) + expires = _parse_utc(record.get("expires_at_utc")) + binding = packet.get("binding") if isinstance(packet.get("binding"), dict) else {} + packet_preconditions = ( + packet.get("production_preconditions") if isinstance(packet.get("production_preconditions"), dict) else {} + ) + bound_preconditions = ( + binding.get("production_preconditions") if isinstance(binding.get("production_preconditions"), dict) else {} + ) + required_destination = binding.get("destination") if isinstance(binding.get("destination"), dict) else {} + destination_keys = {"container", "database", "system_identifier", "server_version_num"} + observed_destination_projection = {key: observed_destination.get(key) for key in destination_keys} + destination_matches = set(required_destination) == destination_keys and canonical_json( + observed_destination_projection + ) == canonical_json(required_destination) + bound_destination_readback = ( + binding.get("destination_readback") if isinstance(binding.get("destination_readback"), dict) else {} + ) + required_gate = binding.get("approval_gate") if isinstance(binding.get("approval_gate"), dict) else {} + observed_gate = ( + observed_destination.get("approval_gate") if isinstance(observed_destination.get("approval_gate"), dict) else {} + ) + required_gate_ready = set(required_gate) == set(APPROVAL_GATE_CHECKS) and all( + required_gate.get(key) is True for key in APPROVAL_GATE_CHECKS + ) + observed_gate_ready = all(observed_gate.get(key) is True for key in APPROVAL_GATE_CHECKS) + gate_matches = ( + required_gate_ready + and observed_gate_ready + and all(observed_gate.get(key) is required_gate.get(key) for key in APPROVAL_GATE_CHECKS) + ) + required_proposal = binding.get("proposal") if isinstance(binding.get("proposal"), dict) else {} + required_payload = required_proposal.get("payload") + required_apply_payload = required_payload.get("apply_payload") if isinstance(required_payload, dict) else None + proposal_binding_self_consistent = ( + required_proposal.get("required_status") == "approved" + and required_proposal.get("required_applied_at") is None + and isinstance(required_apply_payload, dict) + and sha256_json(required_payload) == required_proposal.get("proposal_payload_sha256") + and sha256_json(required_apply_payload) == required_proposal.get("apply_payload_sha256") + and is_sha256(required_proposal.get("approval_snapshot_sha256")) + ) + bound_candidate_readback = ( + binding.get("candidate_readback") if isinstance(binding.get("candidate_readback"), dict) else {} + ) + bound_candidate = bound_candidate_readback.get("matching_candidate") + expected_bound_candidate = { + "id": required_proposal.get("id"), + "proposal_type": required_proposal.get("proposal_type"), + "status": required_proposal.get("required_status"), + "applied_at": required_proposal.get("required_applied_at"), + "proposal_payload_sha256": required_proposal.get("proposal_payload_sha256"), + "apply_payload_sha256": required_proposal.get("apply_payload_sha256"), + "approval_snapshot_sha256": required_proposal.get("approval_snapshot_sha256"), + "reviewed_by_handle": required_proposal.get("reviewed_by_handle"), + "reviewed_by_agent_id": required_proposal.get("reviewed_by_agent_id"), + "reviewed_at": required_proposal.get("reviewed_at"), + "review_note": required_proposal.get("review_note"), + "immutable_approval_exact": True, + } + bound_candidate_exact = ( + bound_candidate_readback.get("matching_candidate_count") == 1 + and isinstance(bound_candidate, dict) + and canonical_json(bound_candidate) == canonical_json(expected_bound_candidate) + ) + observed_candidates = observed_destination.get("approved_strict_candidates") or [] + if not isinstance(observed_candidates, list) or any(not isinstance(row, dict) for row in observed_candidates): + observed_candidates = [] + matching_candidates = [ + candidate + for candidate in observed_candidates + if candidate.get("id") == required_proposal.get("id") + and candidate.get("proposal_type") == required_proposal.get("proposal_type") + and candidate.get("status") == required_proposal.get("required_status") + and candidate.get("applied_at") is required_proposal.get("required_applied_at") + and candidate.get("proposal_payload_sha256") == required_proposal.get("proposal_payload_sha256") + and candidate.get("apply_payload_sha256") == required_proposal.get("apply_payload_sha256") + and candidate.get("approval_snapshot_sha256") == required_proposal.get("approval_snapshot_sha256") + and candidate.get("reviewed_by_handle") == required_proposal.get("reviewed_by_handle") + and candidate.get("reviewed_by_agent_id") == required_proposal.get("reviewed_by_agent_id") + and candidate.get("reviewed_at") == required_proposal.get("reviewed_at") + and candidate.get("review_note") == required_proposal.get("review_note") + and candidate.get("immutable_approval_exact") is True + ] + production_rollback_sha256 = record.get("production_rollback_sql_sha256") + bound_production_rollback = ( + binding.get("production_rollback") if isinstance(binding.get("production_rollback"), dict) else {} + ) + bound_production_rollback_sha256 = bound_production_rollback.get("rollback_sql_sha256") + production_rollback_artifact_valid = False + if production_rollback_artifact is not None: + try: + validated_rollback = validate_production_rollback_artifact( + production_rollback_artifact, + expected_destination=required_destination, + ) + production_rollback_artifact_valid = ( + validated_rollback.get("artifact_sha256") == bound_production_rollback.get("artifact_sha256") + and validated_rollback.get("schema") == bound_production_rollback.get("artifact_schema") + and validated_rollback.get("rollback_sql_sha256") == bound_production_rollback_sha256 + and validated_rollback.get("proposal_id") == required_proposal.get("id") + and validated_rollback.get("proposal_payload_sha256") + == required_proposal.get("proposal_payload_sha256") + and validated_rollback.get("approval_snapshot_sha256") + == required_proposal.get("approval_snapshot_sha256") + ) + except ValueError: + production_rollback_artifact_valid = False + production_rollback_packet_ready = ( + bound_production_rollback.get("status") == "ready" + and bound_production_rollback.get("bound_for_action_time_authorization") is True + and bound_production_rollback.get("conditional_rollback_requires_explicit_authorization") is True + and bound_production_rollback.get("artifact_schema") == PRODUCTION_ROLLBACK_ARTIFACT_SCHEMA + and is_sha256(bound_production_rollback.get("artifact_sha256")) + and is_sha256(bound_production_rollback_sha256) + and production_rollback_artifact_valid + ) + packet_binding_sha256 = packet.get("binding_sha256") + binding_self_consistent = is_sha256(packet_binding_sha256) and sha256_json(binding) == packet_binding_sha256 + try: + expected_template = _authorization_text_template(binding, packet_binding_sha256) + except (KeyError, TypeError): + expected_template = "" + expected_scope_exclusions = { + "approval_gate_deployment_authorized": False, + "telegram_send_authorized": False, + "gcp_promotion_authorized": False, + "cloud_sql_exposure_authorized": False, + } + required_record_keys = { + "authorized", + "rollback_authorized", + "binding_sha256", + "production_rollback_sql_sha256", + "operator_identity", + "received_at_utc", + "expires_at_utc", + "authorization_text", + } + expected_record_template = { + "authorized": True, + "rollback_authorized": True, + "binding_sha256": packet_binding_sha256, + "production_rollback_sql_sha256": ( + bound_production_rollback_sha256 + if is_sha256(bound_production_rollback_sha256) + else "" + ), + "operator_identity": "", + "received_at_utc": "", + "expires_at_utc": "", + "authorization_text": "", + } + try: + expected_record_text = expected_authorization_text(packet, record) + except ValueError: + expected_record_text = "" + bound_engine = binding.get("engine") if isinstance(binding.get("engine"), dict) else {} + bound_gate_dependency = ( + binding.get("approval_gate_dependency") if isinstance(binding.get("approval_gate_dependency"), dict) else {} + ) + runtime_provenance_valid = False + try: + current_runtime_manifest = runtime_manifest_for_commit( + bound_engine.get("repo_git_sha"), + require_current_head=False, + ) + runtime_provenance_valid = canonical_json(current_runtime_manifest) == canonical_json( + bound_engine.get("runtime_manifest") + ) + except ValueError: + runtime_provenance_valid = False + try: + apply_engine_relative = repo_relative_path(apply_engine_path) + except ValueError: + apply_engine_relative = "" + try: + approval_gate_relative = repo_relative_path(approval_gate_path) + except ValueError: + approval_gate_relative = "" + action_observed_at = _parse_utc(observed_destination.get("observed_at_utc")) + action_readback_fresh = ( + received is not None + and action_observed_at is not None + and received <= action_observed_at <= now + and observed_destination.get("read_only_transaction") is True + ) + checks = { + "packet_schema_exact": ( + packet.get("artifact") == "proposal_apply_production_authorization_packet" + and packet.get("schema") == AUTHORIZATION_SCHEMA + and packet.get("required_tier") == "T6_authorized_production" + and packet.get("current_tier") == "T3_live_readonly" + and _parse_utc(packet.get("generated_at_utc")) is not None + ), + "packet_binding_sha256_self_consistent": binding_self_consistent, + "packet_preconditions_bound_exactly": ( + canonical_json(packet_preconditions) == canonical_json(bound_preconditions) + ), + "proposal_binding_self_consistent": proposal_binding_self_consistent, + "packet_destination_readback_was_read_only": ( + packet_preconditions.get("destination_readback_was_read_only") is True + and bound_destination_readback.get("read_only_transaction") is True + and _parse_utc(bound_destination_readback.get("observed_at_utc")) is not None + and bound_destination_readback.get("observed_at_utc") + == packet_preconditions.get("destination_readback_observed_at_utc") + ), + "approval_gate_ready_at_packet": ( + packet_preconditions.get("approval_gate_ready") is True and required_gate_ready + ), + "exact_production_rollback_packet_ready": ( + packet_preconditions.get("exact_production_rollback_packet_ready") is True + and production_rollback_packet_ready + ), + "strict_proposal_present_and_approved": ( + packet_preconditions.get("strict_proposal_present_and_approved") is True + and packet_preconditions.get("matching_candidate_count") == 1 + and bound_candidate_exact + ), + "packet_never_self_authorizes_or_expands_scope": ( + packet.get("safe_to_enter_apply_window") is False + and packet.get("production_apply_authorization_present") is False + and packet.get("production_apply_executed") is False + and packet_preconditions.get("production_apply_authorization_present") is False + and packet_preconditions.get("production_apply_executed") is False + and canonical_json(packet.get("scope_exclusions")) == canonical_json(expected_scope_exclusions) + ), + "authorization_text_template_exact": packet.get("authorization_text_template") == expected_template, + "authorization_record_template_exact": ( + canonical_json(packet.get("authorization_record_required")) == canonical_json(expected_record_template) + ), + "authorization_record_contract_exact": isinstance(record, dict) and set(record) == required_record_keys, + "authorized_is_exact_true": record.get("authorized") is True, + "rollback_authorized_is_exact_true": record.get("rollback_authorized") is True, + "production_rollback_sql_sha256_exact": ( + is_sha256(production_rollback_sha256) and production_rollback_sha256 == bound_production_rollback_sha256 + ), + "binding_sha256_exact": binding_self_consistent and record.get("binding_sha256") == packet_binding_sha256, + "operator_identity_present": ( + isinstance(record.get("operator_identity"), str) + and bool(record.get("operator_identity", "").strip()) + and not record.get("operator_identity", "").startswith("<") + ), + "authorization_window_valid": ( + received is not None and expires is not None and received <= now <= expires and received < expires + ), + "authorization_text_exact": bool(expected_record_text) + and record.get("authorization_text") == expected_record_text, + "destination_identity_exact": destination_matches, + "destination_readback_fresh_and_read_only_at_action": action_readback_fresh, + "approval_gate_identity_exact_at_action": gate_matches, + "strict_proposal_exact_at_action": len(matching_candidates) == 1, + "apply_engine_sha256_exact": ( + apply_engine_path.is_file() + and apply_engine_relative == bound_engine.get("apply_engine_path") + and sha256_file(apply_engine_path) == bound_engine.get("apply_engine_sha256") + ), + "runtime_commit_and_manifest_exact": runtime_provenance_valid, + "approval_gate_prerequisite_sha256_exact": ( + approval_gate_path.is_file() + and approval_gate_relative == bound_gate_dependency.get("path") + and sha256_file(approval_gate_path) == bound_gate_dependency.get("sha256") + ), + } + return { + "schema": "livingip.proposalApplyAuthorizationVerification.v1", + "checks": checks, + "safe_to_enter_apply_window": all(checks.values()), + "production_apply_authorization_present": all(checks.values()), + "production_apply_executed": False, + } + + +def _load_json(path: Path) -> dict[str, Any]: + data = json.loads(path.read_text(encoding="utf-8")) + if not isinstance(data, dict): + raise ValueError(f"JSON file must contain an object: {path}") + return data + + +def _write_json(path: Path, data: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def write_authorization_markdown(path: Path, packet: dict[str, Any]) -> None: + binding = packet["binding"] + proposal = binding["proposal"] + destination = binding["destination"] + engine = binding["engine"] + rollback = binding["rollback"] + production_rollback = binding["production_rollback"] + gate_dependency = binding["approval_gate_dependency"] + preconditions = packet["production_preconditions"] + lines = [ + "# Proposal Apply Production Authorization Packet", + "", + f"Generated UTC: `{packet['generated_at_utc']}`", + f"Required tier: `{packet['required_tier']}`", + f"Current tier: `{packet['current_tier']}`", + f"Safe to enter apply window: `{packet['safe_to_enter_apply_window']}`", + f"Production apply authorized: `{packet['production_apply_authorization_present']}`", + f"Production apply executed: `{packet['production_apply_executed']}`", + "", + "## Exact Binding", + "", + f"- Proposal: `{proposal['id']}` (`{proposal['proposal_type']}`)", + f"- Proposal payload SHA-256: `{proposal['proposal_payload_sha256']}`", + f"- Apply payload SHA-256: `{proposal['apply_payload_sha256']}`", + f"- Immutable approval SHA-256: `{proposal['approval_snapshot_sha256']}`", + f"- Destination: `{destination['container']}/{destination['database']}`", + f"- Destination system identifier: `{destination['system_identifier']}`", + f"- Apply engine SHA-256: `{engine['apply_engine_sha256']}`", + f"- Apply engine path: `{engine['apply_engine_path']}`", + f"- Apply SQL SHA-256: `{engine['apply_sql_sha256']}`", + f"- Clone rehearsal rollback SQL SHA-256: `{rollback['rollback_sql_sha256']}`", + f"- Production rollback status: `{production_rollback['status']}`", + f"- Production rollback SQL SHA-256: `{production_rollback['rollback_sql_sha256']}`", + f"- Approval gate prerequisite SHA-256: `{gate_dependency['sha256']}`", + f"- Approval gate prerequisite path: `{gate_dependency['path']}`", + f"- Binding SHA-256: `{packet['binding_sha256']}`", + "", + "## Production Preconditions", + "", + ] + lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(preconditions.items())) + lines.extend(["", "## Approval Gate Readback", ""]) + lines.extend(f"- `{key}`: `{value}`" for key, value in sorted(binding["approval_gate"].items())) + lines.extend( + [ + "", + "## Exact Action-Time Authorization Text", + "", + "This text is not actionable until every production precondition above is true.", + "", + packet["authorization_text_template"], + "", + "## Current Gate", + "", + packet["next_exact_action"], + "", + "The production approval-gate deployment is explicitly outside this apply packet and requires separate authorization.", + "", + "## Claim Ceiling", + "", + packet["claim_ceiling"], + "", + ] + ) + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text("\n".join(lines), encoding="utf-8") + + +def parse_args(argv: list[str] | None = None) -> argparse.Namespace: + parser = argparse.ArgumentParser(description=__doc__) + sub = parser.add_subparsers(dest="command", required=True) + packet = sub.add_parser("build-authorization-packet") + packet.add_argument("--lifecycle-receipt", required=True, type=Path) + packet.add_argument("--destination-readback", required=True, type=Path) + packet.add_argument("--apply-engine", type=Path, default=HERE / "apply_proposal.py") + packet.add_argument("--approval-gate", type=Path, default=HERE / "kb_apply_prereqs.sql") + packet.add_argument("--production-rollback-artifact", type=Path) + packet.add_argument("--repo-git-sha") + packet.add_argument("--out", required=True, type=Path) + packet.add_argument("--markdown-out", type=Path) + verify = sub.add_parser("verify-authorization") + verify.add_argument("--packet", required=True, type=Path) + verify.add_argument("--record", required=True, type=Path) + verify.add_argument("--observed-destination", required=True, type=Path) + verify.add_argument("--apply-engine", type=Path, default=HERE / "apply_proposal.py") + verify.add_argument("--approval-gate", type=Path, default=HERE / "kb_apply_prereqs.sql") + verify.add_argument("--production-rollback-artifact", type=Path) + verify.add_argument("--out", required=True, type=Path) + return parser.parse_args(argv) + + +def main(argv: list[str] | None = None) -> int: + args = parse_args(argv) + if args.command == "build-authorization-packet": + raw = _load_json(args.lifecycle_receipt) + lifecycle = raw.get("applied_rollback_receipt", raw) + destination = _load_json(args.destination_readback) + packet = build_authorization_packet( + lifecycle, + destination, + repo_git_sha=args.repo_git_sha or current_git_sha(HERE.parent), + apply_engine_path=args.apply_engine, + approval_gate_path=args.approval_gate, + production_rollback_artifact=( + _load_json(args.production_rollback_artifact) if args.production_rollback_artifact else None + ), + ) + _write_json(args.out, packet) + if args.markdown_out: + write_authorization_markdown(args.markdown_out, packet) + print(json.dumps({"out": str(args.out), "safe_to_enter_apply_window": False}, sort_keys=True)) + return 0 + packet = _load_json(args.packet) + record = _load_json(args.record) + destination = _load_json(args.observed_destination) + result = verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=args.apply_engine, + approval_gate_path=args.approval_gate, + production_rollback_artifact=( + _load_json(args.production_rollback_artifact) if args.production_rollback_artifact else None + ), + ) + _write_json(args.out, result) + print(json.dumps(result, sort_keys=True)) + return 0 if result["safe_to_enter_apply_window"] else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_approve_claim_clone_canary.py b/scripts/run_approve_claim_clone_canary.py index 6985401..87c4de2 100644 --- a/scripts/run_approve_claim_clone_canary.py +++ b/scripts/run_approve_claim_clone_canary.py @@ -18,6 +18,7 @@ import re import shutil import subprocess import sys +import tempfile import uuid from datetime import datetime, timezone from pathlib import Path @@ -28,13 +29,12 @@ REPO_ROOT = HERE.parent sys.path.insert(0, str(HERE)) import apply_proposal as ap # noqa: E402 +import proposal_apply_lifecycle as lifecycle # noqa: E402 SAFE_DB_RE = re.compile(r"^[a-z][a-z0-9_]{1,62}$") REVIEWER_HANDLE = "m3ta" REVIEW_NOTE = "Reviewed exact strict payload inside the disposable clone." -DEFAULT_REVIEW_SECRETS_FILE = ( - "/home/teleo/.hermes/profiles/leoclean/secrets/kb-review.env" -) +DEFAULT_REVIEW_SECRETS_FILE = "/home/teleo/.hermes/profiles/leoclean/secrets/kb-review.env" SECURITY_DEFINER_DEPENDENCIES = { "approve_strict_proposal": "kb_review", "assert_approved_proposal": "kb_apply", @@ -98,8 +98,10 @@ def _source_file_manifest(args: argparse.Namespace) -> list[dict[str, Any]]: paths = { Path(__file__).resolve(), (HERE / "run_approve_claim_isolated_container_canary.sh").resolve(), + (HERE / "finalize_approve_claim_isolated_canary.py").resolve(), Path(args.approve_script).resolve(), Path(args.apply_script).resolve(), + (HERE / "proposal_apply_lifecycle.py").resolve(), Path(args.prereqs).resolve(), } return sorted( @@ -190,9 +192,7 @@ def _schema_copy(container: str, source_db: str, clone_db: str) -> None: "ON_ERROR_STOP=1", "-q", ] - dump = subprocess.Popen( - dump_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True - ) + dump = subprocess.Popen(dump_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) assert dump.stdout is not None assert dump.stderr is not None restore = subprocess.run( @@ -319,37 +319,22 @@ def _cluster_roles_are_preprovisioned(rows: list[dict[str, Any]]) -> bool: def _clone_prerequisites_sql(prereqs: str) -> tuple[str, dict[str, Any]]: """Remove cluster-global role DDL while preserving clone-local objects/ACLs.""" start_marker = "do $roles$" - end_marker = ( - "alter role kb_apply login noinherit nosuperuser nocreatedb nocreaterole " - "noreplication nobypassrls;" - ) + end_marker = "alter role kb_apply login noinherit nosuperuser nocreatedb nocreaterole noreplication nobypassrls;" start = prereqs.lower().find(start_marker) end_start = prereqs.lower().find(end_marker, start) if start < 0 or end_start < 0: - raise RuntimeError( - "prerequisites role bootstrap block changed; refusing unsafe clone execution" - ) + raise RuntimeError("prerequisites role bootstrap block changed; refusing unsafe clone execution") end = end_start + len(end_marker) removed = prereqs[start:end] - sanitized = ( - prereqs[:start] - + "-- Cluster-global role DDL omitted by disposable clone canary.\n" - + prereqs[end:] - ) + sanitized = prereqs[:start] + "-- Cluster-global role DDL omitted by disposable clone canary.\n" + prereqs[end:] if re.search(r"\b(?:create|alter|drop)\s+role\b", sanitized, re.IGNORECASE): - raise RuntimeError( - "prerequisites still contain cluster-global role DDL after sanitization" - ) + raise RuntimeError("prerequisites still contain cluster-global role DDL after sanitization") return sanitized, { "source_sha256": hashlib.sha256(prereqs.encode()).hexdigest(), "clone_sql_sha256": hashlib.sha256(sanitized.encode()).hexdigest(), "cluster_role_ddl_removed": True, - "removed_create_role_statements": len( - re.findall(r"\bcreate\s+role\b", removed, re.IGNORECASE) - ), - "removed_alter_role_statements": len( - re.findall(r"\balter\s+role\b", removed, re.IGNORECASE) - ), + "removed_create_role_statements": len(re.findall(r"\bcreate\s+role\b", removed, re.IGNORECASE)), + "removed_alter_role_statements": len(re.findall(r"\balter\s+role\b", removed, re.IGNORECASE)), } @@ -460,9 +445,7 @@ def load_normalized_fixture(path: Path, namespace: str) -> dict[str, Any]: data = json.loads(path.read_text(encoding="utf-8")) previews = data.get("normalization_previews") if isinstance(data, dict) else None if not isinstance(previews, list) or len(previews) != 1: - raise ValueError( - "normalization JSON must contain exactly one normalization_previews row" - ) + raise ValueError("normalization JSON must contain exactly one normalization_previews row") preview = previews[0] children = preview.get("strict_child_proposals") if ( @@ -472,9 +455,7 @@ def load_normalized_fixture(path: Path, namespace: str) -> dict[str, Any]: or len(children) != 1 or children[0].get("proposal_type") != "approve_claim" ): - raise ValueError( - "normalization JSON is not one unblocked strict approve_claim child" - ) + raise ValueError("normalization JSON is not one unblocked strict approve_claim child") apply_payload = (children[0].get("payload") or {}).get("apply_payload") if not isinstance(apply_payload, dict): raise ValueError("strict approve_claim child has no apply_payload") @@ -527,9 +508,7 @@ def _payload_agent_ids(payload: dict[str, Any]) -> list[str]: return sorted(values) -def _seed_payload_agents( - container: str, source_db: str, clone_db: str, payload: dict[str, Any] -) -> dict[str, Any]: +def _seed_payload_agents(container: str, source_db: str, clone_db: str, payload: dict[str, Any]) -> dict[str, Any]: required_ids = _payload_agent_ids(payload) if not required_ids: return { @@ -538,9 +517,7 @@ def _seed_payload_agents( "clone_rows": [], "exact_clone_copy": True, } - id_sql = ", ".join( - f"{ap.sql_literal(value)}::uuid" for value in required_ids - ) + id_sql = ", ".join(f"{ap.sql_literal(value)}::uuid" for value in required_ids) source_raw = _psql( container, source_db, @@ -560,8 +537,8 @@ rollback; statements = [ f"""insert into public.agents (id, handle, kind) -values ({ap.sql_literal(row['id'])}::uuid, {ap.sql_literal(row['handle'])}, - {ap.sql_literal(row['kind'])}) +values ({ap.sql_literal(row["id"])}::uuid, {ap.sql_literal(row["handle"])}, + {ap.sql_literal(row["kind"])}) on conflict (id) do nothing;""" for row in source_rows ] @@ -604,16 +581,14 @@ rollback; ) source_row = json.loads(raw) if not source_row: - raise RuntimeError( - f"reviewer principal dependency is missing from source agents: {reviewer_handle}" - ) + raise RuntimeError(f"reviewer principal dependency is missing from source agents: {reviewer_handle}") _psql( container, clone_db, f"""insert into public.agents (id, handle, kind) -values ({ap.sql_literal(source_row['id'])}::uuid, - {ap.sql_literal(source_row['handle'])}, - {ap.sql_literal(source_row['kind'])});""", +values ({ap.sql_literal(source_row["id"])}::uuid, + {ap.sql_literal(source_row["handle"])}, + {ap.sql_literal(source_row["kind"])});""", tuples=False, ) clone_raw = _psql( @@ -635,9 +610,7 @@ from public.agents where handle = {ap.sql_literal(reviewer_handle)};""", } -def _seed_external_claims( - container: str, source_db: str, clone_db: str, payload: dict[str, Any] -) -> dict[str, Any]: +def _seed_external_claims(container: str, source_db: str, clone_db: str, payload: dict[str, Any]) -> dict[str, Any]: external_ids = _external_claim_ids(payload) if not external_ids: return { @@ -646,9 +619,7 @@ def _seed_external_claims( "clone_rows": [], "exact_clone_copy": True, } - id_sql = ", ".join( - f"{ap.sql_literal(value)}::uuid" for value in external_ids - ) + id_sql = ", ".join(f"{ap.sql_literal(value)}::uuid" for value in external_ids) raw = _psql( container, source_db, @@ -666,28 +637,22 @@ rollback; found = {row["id"] for row in rows} missing = sorted(set(external_ids) - found) if missing: - raise RuntimeError( - f"normalized bundle references missing source claims: {missing}" - ) + raise RuntimeError(f"normalized bundle references missing source claims: {missing}") statements = [] for row in rows: tags = row.get("tags") or [] tags_sql = ( - "'{}'::text[]" - if not tags - else "array[" - + ", ".join(ap.sql_literal(tag) for tag in tags) - + "]::text[]" + "'{}'::text[]" if not tags else "array[" + ", ".join(ap.sql_literal(tag) for tag in tags) + "]::text[]" ) statements.append( f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by) values - ({ap.sql_literal(row['id'])}::uuid, {ap.sql_literal(row['type'])}, - {ap.sql_literal(row['text'])}, {ap.sql_literal(row['status'])}, - {ap.sql_literal(row.get('confidence'))}, {tags_sql}, - {ap.sql_literal(row.get('created_by'))}::uuid, - {ap.sql_literal(row.get('superseded_by'))}::uuid);""" + ({ap.sql_literal(row["id"])}::uuid, {ap.sql_literal(row["type"])}, + {ap.sql_literal(row["text"])}, {ap.sql_literal(row["status"])}, + {ap.sql_literal(row.get("confidence"))}, {tags_sql}, + {ap.sql_literal(row.get("created_by"))}::uuid, + {ap.sql_literal(row.get("superseded_by"))}::uuid);""" ) _psql(container, clone_db, "\n".join(statements), tuples=False) clone_raw = _psql( @@ -712,9 +677,7 @@ from public.claims where id in ({id_sql});""", def _id_predicate(column: str, values: list[str]) -> str: if not values: return "false" - return f"{column} in (" + ", ".join( - f"{ap.sql_literal(value)}::uuid" for value in values - ) + ")" + return f"{column} in (" + ", ".join(f"{ap.sql_literal(value)}::uuid" for value in values) + ")" def _key_predicate(rows: list[dict[str, Any]], fields: list[tuple[str, str]]) -> str: @@ -829,8 +792,7 @@ def _expected_table_deltas( def _table_deltas(before: dict[str, int], after: dict[str, int]) -> dict[str, int]: if set(before) != set(after): raise ValueError( - "cannot compare table counts with different keys: " - f"before={sorted(before)} after={sorted(after)}" + f"cannot compare table counts with different keys: before={sorted(before)} after={sorted(after)}" ) return {key: after[key] - before[key] for key in sorted(before)} @@ -862,9 +824,7 @@ def _canonical_row_keys(rows: dict[str, list[dict[str, Any]]]) -> dict[str, Any] ], key=lambda row: (row["from_claim"], row["to_claim"], row["edge_type"]), ), - "public.reasoning_tools": sorted( - row["id"] for row in rows.get("reasoning_tools", []) - ), + "public.reasoning_tools": sorted(row["id"] for row in rows.get("reasoning_tools", [])), } @@ -879,8 +839,7 @@ def _strict_receipt( expected_keys = _canonical_row_keys(expected_rows) actual_keys = _canonical_row_keys(actual_rows) checks = { - "proposal_id_exact": applied.get("id") - == result.get("fixture_ids", {}).get("proposal_id"), + "proposal_id_exact": applied.get("id") == result.get("fixture_ids", {}).get("proposal_id"), "proposal_status_applied": applied.get("status") == "applied", "proposal_applied_at_present": bool(applied.get("applied_at")), "canonical_rows_exact": actual_rows == expected_rows, @@ -889,18 +848,12 @@ def _strict_receipt( isinstance(result.get("clone_counts_before_apply"), dict) and isinstance(result.get("clone_counts_after_apply"), dict) ), - "table_deltas_exact": result.get("clone_apply_table_deltas") - == expected_table_deltas, - "conflict_transaction_rolled_back": result.get("checks", {}).get( - "conflict_transaction_rolled_back_exactly" - ) + "table_deltas_exact": result.get("clone_apply_table_deltas") == expected_table_deltas, + "conflict_transaction_rolled_back": result.get("checks", {}).get("conflict_transaction_rolled_back_exactly") is True, "clone_database_removed": ( result.get("clone_dropped") is True - and result.get("cleanup_readback", {}).get( - "leftover_clone_database_count" - ) - == 0 + and result.get("cleanup_readback", {}).get("leftover_clone_database_count") == 0 ), "source_database_unchanged": result.get("source_database_mutated") is False, } @@ -925,28 +878,18 @@ def _strict_receipt( "actual_deltas": result.get("clone_apply_table_deltas"), }, "rollback_cleanup": { - "conflict_transaction_rolled_back": checks[ - "conflict_transaction_rolled_back" - ], + "conflict_transaction_rolled_back": checks["conflict_transaction_rolled_back"], "clone_database_removed": checks["clone_database_removed"], - "leftover_clone_database_count": result.get("cleanup_readback", {}).get( - "leftover_clone_database_count" - ), + "leftover_clone_database_count": result.get("cleanup_readback", {}).get("leftover_clone_database_count"), "source_database_unchanged": checks["source_database_unchanged"], }, } -def _exact_bundle_readback( - container: str, database: str, payload: dict[str, Any] -) -> dict[str, list[dict[str, Any]]]: +def _exact_bundle_readback(container: str, database: str, payload: dict[str, Any]) -> dict[str, list[dict[str, Any]]]: expected = _expected_bundle_rows(payload) - claim_predicate = _id_predicate( - "id", [row["id"] for row in expected["claims"]] - ) - source_predicate = _id_predicate( - "id", [row["id"] for row in expected["sources"]] - ) + claim_predicate = _id_predicate("id", [row["id"] for row in expected["claims"]]) + source_predicate = _id_predicate("id", [row["id"] for row in expected["sources"]]) evidence_predicate = _key_predicate( expected["claim_evidence"], [("claim_id", "uuid"), ("source_id", "uuid"), ("role", "text")], @@ -959,9 +902,7 @@ def _exact_bundle_readback( ("edge_type", "text"), ], ) - tool_predicate = _id_predicate( - "id", [row["id"] for row in expected["reasoning_tools"]] - ) + tool_predicate = _id_predicate("id", [row["id"] for row in expected["reasoning_tools"]]) raw = _psql( container, database, @@ -1001,9 +942,53 @@ rollback; return json.loads(raw) -def _proposal_readback( - container: str, database: str, proposal_id: str -) -> dict[str, Any] | None: +def _unrelated_state_fingerprint( + container: str, + database: str, + payload: dict[str, Any], + proposal_id: str, +) -> dict[str, Any]: + """Hash every potentially adjacent row while excluding proposal-owned rows.""" + expected = _expected_bundle_rows(payload) + claim_predicate = _id_predicate("id", [row["id"] for row in expected["claims"]]) + source_predicate = _id_predicate("id", [row["id"] for row in expected["sources"]]) + evidence_predicate = _key_predicate( + expected["claim_evidence"], + [("claim_id", "uuid"), ("source_id", "uuid"), ("role", "text")], + ) + edge_predicate = _key_predicate( + expected["claim_edges"], + [("from_claim", "uuid"), ("to_claim", "uuid"), ("edge_type", "text")], + ) + tool_predicate = _id_predicate("id", [row["id"] for row in expected["reasoning_tools"]]) + + def fingerprint(table: str, alias: str, where: str, order: str) -> str: + return f"""(select json_build_object( + 'row_count', count(*), + 'content_md5', md5(coalesce(string_agg(to_jsonb({alias})::text, '|' order by {order}), '')) + ) from {table} {alias} where {where})""" + + raw = _psql( + container, + database, + f"""begin transaction read only; +select json_build_object( + 'public.agents', {fingerprint("public.agents", "a", "true", "a.id::text")}, + 'public.claims', {fingerprint("public.claims", "c", f"not ({claim_predicate})", "c.id::text")}, + 'public.sources', {fingerprint("public.sources", "s", f"not ({source_predicate})", "s.id::text")}, + 'public.claim_evidence', {fingerprint("public.claim_evidence", "ce", f"not ({evidence_predicate})", "ce.id::text")}, + 'public.claim_edges', {fingerprint("public.claim_edges", "e", f"not ({edge_predicate})", "e.id::text")}, + 'public.reasoning_tools', {fingerprint("public.reasoning_tools", "rt", f"not ({tool_predicate})", "rt.id::text")}, + 'kb_stage.kb_proposals', {fingerprint("kb_stage.kb_proposals", "p", f"p.id <> {ap.sql_literal(proposal_id)}::uuid", "p.id::text")}, + 'kb_stage.kb_proposal_approvals', {fingerprint("kb_stage.kb_proposal_approvals", "pa", f"pa.proposal_id <> {ap.sql_literal(proposal_id)}::uuid", "pa.proposal_id::text")} +)::text; +rollback; +""", + ) + return json.loads(raw) + + +def _proposal_readback(container: str, database: str, proposal_id: str) -> dict[str, Any] | None: raw = _psql( container, database, @@ -1031,9 +1016,7 @@ rollback; return json.loads(raw) -def _approval_snapshot_readback( - container: str, database: str, proposal_id: str -) -> dict[str, Any] | None: +def _approval_snapshot_readback(container: str, database: str, proposal_id: str) -> dict[str, Any] | None: raw = _psql( container, database, @@ -1069,7 +1052,7 @@ values ({ap.sql_literal(proposal_id)}::uuid, 'approve_claim', 'pending_review', 'clone_canary', {ap.sql_literal(source_ref)}, 'Exercise strict canonical bundle review and apply lifecycle.', - {ap.sql_literal(json.dumps({'apply_payload': payload}, sort_keys=True))}::jsonb); + {ap.sql_literal(json.dumps({"apply_payload": payload}, sort_keys=True))}::jsonb); """ _psql(container, database, sql) @@ -1111,6 +1094,7 @@ def _apply( database: str, *, dry_run: bool = False, + receipt_out: Path | None = None, ) -> subprocess.CompletedProcess[str]: command = [ sys.executable, @@ -1129,15 +1113,37 @@ def _apply( "--role", args.apply_role, ] + if receipt_out is not None: + command.extend(["--receipt-out", str(receipt_out)]) if dry_run: command.append("--dry-run") return _run(command, check=False) +def _apply_command_readback( + result: subprocess.CompletedProcess[str], +) -> dict[str, Any]: + """Keep the public report useful without leaking a private receipt path.""" + readback: dict[str, Any] = { + "returncode": result.returncode, + "stderr": result.stderr.strip(), + } + try: + summary = json.loads(result.stdout) + except json.JSONDecodeError: + readback["stdout"] = result.stdout.strip() + else: + if isinstance(summary, dict): + summary.pop("receipt_path", None) + readback["result"] = summary + else: + readback["stdout"] = result.stdout.strip() + return readback + + def _security_definer_readback(container: str, database: str) -> dict[str, Any]: expected_values = ",\n".join( - f"({ap.sql_literal(name)}, {ap.sql_literal(role)}, " - f"{ap.sql_literal(SECURITY_DEFINER_ARGUMENT_TYPES[name])})" + f"({ap.sql_literal(name)}, {ap.sql_literal(role)}, {ap.sql_literal(SECURITY_DEFINER_ARGUMENT_TYPES[name])})" for name, role in SECURITY_DEFINER_DEPENDENCIES.items() ) raw = _psql( @@ -1270,8 +1276,7 @@ def _security_dependencies_ready(readback: dict[str, Any]) -> bool: functions = readback.get("functions") or [] functions_ready = len(functions) == len(SECURITY_DEFINER_DEPENDENCIES) and all( row.get("present") is True - and row.get("argument_types") - == SECURITY_DEFINER_ARGUMENT_TYPES[row["function"].removeprefix("kb_stage.")] + and row.get("argument_types") == SECURITY_DEFINER_ARGUMENT_TYPES[row["function"].removeprefix("kb_stage.")] and row.get("security_definer") is True and row.get("expected_role_execute") is True and row.get("other_runtime_role_execute") is False @@ -1285,23 +1290,26 @@ def _security_dependencies_ready(readback: dict[str, Any]) -> bool: and readback.get("protected_role_memberships") == [] and readback.get("login_role_owned_objects") == [] ) - return functions_ready and catalog_ready and privileges == { - "kb_review_proposal_select": True, - "kb_review_proposal_update": False, - "kb_apply_proposal_select": True, - "kb_apply_proposal_update": False, - "kb_apply_claim_evidence_insert": True, - "kb_apply_claim_evidence_update": False, - "kb_review_approval_snapshot_select": False, - "kb_review_approval_snapshot_update": False, - "kb_apply_approval_snapshot_select": False, - "kb_apply_approval_snapshot_update": False, - } + return ( + functions_ready + and catalog_ready + and privileges + == { + "kb_review_proposal_select": True, + "kb_review_proposal_update": False, + "kb_apply_proposal_select": True, + "kb_apply_proposal_update": False, + "kb_apply_claim_evidence_insert": True, + "kb_apply_claim_evidence_update": False, + "kb_review_approval_snapshot_select": False, + "kb_review_approval_snapshot_update": False, + "kb_apply_approval_snapshot_select": False, + "kb_apply_approval_snapshot_update": False, + } + ) -def _approval_transition_valid( - pending: dict[str, Any] | None, approved: dict[str, Any] | None -) -> bool: +def _approval_transition_valid(pending: dict[str, Any] | None, approved: dict[str, Any] | None) -> bool: if not pending or not approved: return False immutable = [ @@ -1337,9 +1345,7 @@ def _approval_transition_valid( ) -def _apply_transition_valid( - approved: dict[str, Any] | None, applied: dict[str, Any] | None -) -> bool: +def _apply_transition_valid(approved: dict[str, Any] | None, applied: dict[str, Any] | None) -> bool: if not approved or not applied: return False preserved = [ @@ -1382,9 +1388,7 @@ def _approval_snapshot_matches( } -def _seed_evidence_permission_probe( - container: str, database: str, fixture: dict[str, Any] -) -> None: +def _seed_evidence_permission_probe(container: str, database: str, fixture: dict[str, Any]) -> None: source_hash = f"approve-claim-permission-probe-{fixture['permission_source_id']}" _psql( container, @@ -1392,20 +1396,20 @@ def _seed_evidence_permission_probe( f"""insert into public.claims (id, type, text, status, confidence, tags, created_by, superseded_by) values - ({ap.sql_literal(fixture['permission_claim_id'])}::uuid, 'structural', + ({ap.sql_literal(fixture["permission_claim_id"])}::uuid, 'structural', 'Existing claim_evidence permission probe.', 'open', 0.42, array['clone-canary-permission-probe']::text[], null, null); insert into public.sources (id, source_type, url, storage_path, excerpt, hash, created_by) values - ({ap.sql_literal(fixture['permission_source_id'])}::uuid, 'observation', null, + ({ap.sql_literal(fixture["permission_source_id"])}::uuid, 'observation', null, null, 'Existing claim_evidence permission probe source.', {ap.sql_literal(source_hash)}, null); insert into public.claim_evidence (claim_id, source_id, role, weight, created_by) values - ({ap.sql_literal(fixture['permission_claim_id'])}::uuid, - {ap.sql_literal(fixture['permission_source_id'])}::uuid, + ({ap.sql_literal(fixture["permission_claim_id"])}::uuid, + {ap.sql_literal(fixture["permission_source_id"])}::uuid, 'grounds', 0.42, null); """, tuples=False, @@ -1426,8 +1430,8 @@ select coalesce((select json_build_object( 'weight', weight, 'created_by', created_by::text ) from public.claim_evidence -where claim_id = {ap.sql_literal(fixture['permission_claim_id'])}::uuid - and source_id = {ap.sql_literal(fixture['permission_source_id'])}::uuid +where claim_id = {ap.sql_literal(fixture["permission_claim_id"])}::uuid + and source_id = {ap.sql_literal(fixture["permission_source_id"])}::uuid and role = 'grounds'), 'null'::json)::text; rollback; """, @@ -1482,10 +1486,7 @@ def _conflict_payload(fixture: dict[str, Any]) -> dict[str, Any]: "url": None, "storage_path": None, "excerpt": "Conflicting source id.", - "hash": ( - f"approve-claim-permission-probe-" - f"{fixture['permission_source_id']}" - ), + "hash": (f"approve-claim-permission-probe-{fixture['permission_source_id']}"), "created_by": None, } ], @@ -1497,18 +1498,14 @@ def _conflict_payload(fixture: dict[str, Any]) -> dict[str, Any]: def run_canary(args: argparse.Namespace) -> dict[str, Any]: if args.review_role != "kb_review" or args.apply_role != "kb_apply": - raise ValueError( - "the canary is pinned to the intended kb_review/kb_apply authority split" - ) + raise ValueError("the canary is pinned to the intended kb_review/kb_apply authority split") suffix = uuid.uuid4().hex[:10] clone_db = f"{args.database_prefix}_{suffix}" if not SAFE_DB_RE.match(clone_db): raise ValueError(f"unsafe generated database name: {clone_db}") fixture = ( - load_normalized_fixture(args.normalization_json, suffix) - if args.normalization_json - else build_fixture(suffix) + load_normalized_fixture(args.normalization_json, suffix) if args.normalization_json else build_fixture(suffix) ) payload = fixture["apply_payload"] expected_rows = _expected_bundle_rows(payload) @@ -1526,33 +1523,19 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "service_restarted": False, "clone_created": False, "clone_dropped": False, - "fixture_ids": { - key: value for key, value in fixture.items() if key != "apply_payload" - }, + "fixture_ids": {key: value for key, value in fixture.items() if key != "apply_payload"}, "dependencies": { - "approve_script": Path(args.approve_script) - .resolve() - .relative_to(REPO_ROOT.resolve()) - .as_posix(), - "apply_script": Path(args.apply_script) - .resolve() - .relative_to(REPO_ROOT.resolve()) - .as_posix(), - "prerequisites_sql": Path(args.prereqs) - .resolve() - .relative_to(REPO_ROOT.resolve()) - .as_posix(), + "approve_script": Path(args.approve_script).resolve().relative_to(REPO_ROOT.resolve()).as_posix(), + "apply_script": Path(args.apply_script).resolve().relative_to(REPO_ROOT.resolve()).as_posix(), + "prerequisites_sql": Path(args.prereqs).resolve().relative_to(REPO_ROOT.resolve()).as_posix(), "review_role": args.review_role, "apply_role": args.apply_role, "gate_owner_role": "kb_gate_owner", "reviewer_handle": REVIEWER_HANDLE, "immutable_approval_table": "kb_stage.kb_proposal_approvals", - "security_definer_functions": [ - f"kb_stage.{name}" for name in SECURITY_DEFINER_DEPENDENCIES - ], + "security_definer_functions": [f"kb_stage.{name}" for name in SECURITY_DEFINER_DEPENDENCIES], "security_definer_argument_types": { - f"kb_stage.{name}": argument_types - for name, argument_types in SECURITY_DEFINER_ARGUMENT_TYPES.items() + f"kb_stage.{name}": argument_types for name, argument_types in SECURITY_DEFINER_ARGUMENT_TYPES.items() }, "credential_files": { "review": { @@ -1579,15 +1562,9 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: } try: result["service_before"] = _service_state(args.service) - result["source_counts_before"] = _base_counts( - args.container, args.source_db - ) - result["source_cluster_roles_before"] = _cluster_role_readback( - args.container, args.source_db - ) - if not _cluster_roles_are_preprovisioned( - result["source_cluster_roles_before"] - ): + result["source_counts_before"] = _base_counts(args.container, args.source_db) + result["source_cluster_roles_before"] = _cluster_role_readback(args.container, args.source_db) + if not _cluster_roles_are_preprovisioned(result["source_cluster_roles_before"]): raise RuntimeError( "refusing clone prerequisites until kb_apply, kb_review, and " "NOLOGIN kb_gate_owner are pre-provisioned without elevated attributes" @@ -1598,13 +1575,8 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: if not readback["present_at_start"] ] if missing_credentials: - raise RuntimeError( - "required separated credential file(s) are absent: " - + ", ".join(missing_credentials) - ) - result["source_target_rows_before"] = _exact_bundle_readback( - args.container, args.source_db, payload - ) + raise RuntimeError("required separated credential file(s) are absent: " + ", ".join(missing_credentials)) + result["source_target_rows_before"] = _exact_bundle_readback(args.container, args.source_db, payload) prereqs = Path(args.prereqs).read_text(encoding="utf-8") clone_prereqs, prereq_sanitization = _clone_prerequisites_sql(prereqs) result["clone_prerequisite_sanitization"] = prereq_sanitization @@ -1620,19 +1592,11 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: ) _psql(args.container, clone_db, clone_prereqs, tuples=False) - result["security_definer_readback"] = _security_definer_readback( - args.container, clone_db - ) - result["payload_agent_seed"] = _seed_payload_agents( - args.container, args.source_db, clone_db, payload - ) - result["external_claim_seed"] = _seed_external_claims( - args.container, args.source_db, clone_db, payload - ) + result["security_definer_readback"] = _security_definer_readback(args.container, clone_db) + result["payload_agent_seed"] = _seed_payload_agents(args.container, args.source_db, clone_db, payload) + result["external_claim_seed"] = _seed_external_claims(args.container, args.source_db, clone_db, payload) _seed_evidence_permission_probe(args.container, clone_db, fixture) - evidence_before = _evidence_permission_probe_readback( - args.container, clone_db, fixture - ) + evidence_before = _evidence_permission_probe_readback(args.container, clone_db, fixture) apply_password = ap.load_password(args.secrets_file) evidence_update = _permission_probe( @@ -1640,15 +1604,13 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update public.claim_evidence set weight = 0.99 - where claim_id = {ap.sql_literal(fixture['permission_claim_id'])}::uuid - and source_id = {ap.sql_literal(fixture['permission_source_id'])}::uuid + where claim_id = {ap.sql_literal(fixture["permission_claim_id"])}::uuid + and source_id = {ap.sql_literal(fixture["permission_source_id"])}::uuid and role = 'grounds';""", role=args.apply_role, password=apply_password, ) - evidence_after = _evidence_permission_probe_readback( - args.container, clone_db, fixture - ) + evidence_after = _evidence_permission_probe_readback(args.container, clone_db, fixture) result["kb_apply_claim_evidence_update_probe"] = { "before": evidence_before, "attempt": evidence_update, @@ -1663,41 +1625,25 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: payload, source_ref="working-leo:approve-claim-clone-canary", ) - pending_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - pending_approval_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) - review_dry_run = _approve( - args, fixture["proposal_id"], clone_db, dry_run=True - ) + pending_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + pending_approval_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) + review_dry_run = _approve(args, fixture["proposal_id"], clone_db, dry_run=True) review_apply = _approve(args, fixture["proposal_id"], clone_db) - approved_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - approved_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + approved_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + approved_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["review_transition"] = { "pending_readback": pending_proposal, "pending_immutable_approval": pending_approval_snapshot, "review_dry_run": { "returncode": review_dry_run.returncode, - "uses_approve_strict_proposal": ( - "kb_stage.approve_strict_proposal" in review_dry_run.stdout - ), + "uses_approve_strict_proposal": ("kb_stage.approve_strict_proposal" in review_dry_run.stdout), "stderr": review_dry_run.stderr.strip(), }, "review_apply": _command_readback(review_apply), "approved_readback": approved_proposal, "immutable_approval_readback": approved_snapshot, - "immutable_approval_matches_ledger": _approval_snapshot_matches( - approved_proposal, approved_snapshot - ), - "exact_transition": _approval_transition_valid( - pending_proposal, approved_proposal - ), + "immutable_approval_matches_ledger": _approval_snapshot_matches(approved_proposal, approved_snapshot), + "exact_transition": _approval_transition_valid(pending_proposal, approved_proposal), } approval_update = _permission_probe( @@ -1705,7 +1651,7 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update kb_stage.kb_proposals set status = 'pending_review', review_note = 'kb_apply direct mutation probe' - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", role=args.apply_role, password=apply_password, ) @@ -1714,7 +1660,7 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update kb_stage.kb_proposals set payload = payload || '{{"kb_apply_direct_mutation_probe": true}}'::jsonb - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", role=args.apply_role, password=apply_password, ) @@ -1723,16 +1669,12 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: clone_db, f"""update kb_stage.kb_proposal_approvals set payload = payload || '{{"kb_apply_direct_mutation_probe": true}}'::jsonb - where proposal_id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where proposal_id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", role=args.apply_role, password=apply_password, ) - proposal_after_permission_probes = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - snapshot_after_permission_probes = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + proposal_after_permission_probes = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + snapshot_after_permission_probes = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["kb_apply_proposal_mutation_probes"] = { "approval_fields": approval_update, "payload": payload_update, @@ -1741,36 +1683,25 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "after": proposal_after_permission_probes, "immutable_approval_before": approved_snapshot, "immutable_approval_after": snapshot_after_permission_probes, - "exact_row_unchanged": ( - approved_proposal == proposal_after_permission_probes - ), - "immutable_approval_exactly_unchanged": ( - approved_snapshot == snapshot_after_permission_probes - ), + "exact_row_unchanged": (approved_proposal == proposal_after_permission_probes), + "immutable_approval_exactly_unchanged": (approved_snapshot == snapshot_after_permission_probes), } built_apply = _apply(args, fixture["proposal_id"], clone_db, dry_run=True) built_sql = built_apply.stdout if built_apply.returncode != 0 or not built_sql.strip(): - raise RuntimeError( - "failed to build apply SQL before mutation: " - f"{built_apply.stderr.strip()}" - ) + raise RuntimeError(f"failed to build apply SQL before mutation: {built_apply.stderr.strip()}") _psql( args.container, clone_db, f"""update kb_stage.kb_proposals set payload = payload || '{{"mutation_after_build": true}}'::jsonb, updated_at = now() - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", tuples=False, ) - proposal_after_mutation = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - snapshot_after_ledger_mutation = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + proposal_after_mutation = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + snapshot_after_ledger_mutation = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) stale_execute = _role_psql( args.container, clone_db, @@ -1778,58 +1709,38 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: role=args.apply_role, password=apply_password, ) - rows_after_stale_execute = _exact_bundle_readback( - args.container, clone_db, payload - ) - proposal_after_stale_execute = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - snapshot_after_stale_execute = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + rows_after_stale_execute = _exact_bundle_readback(args.container, clone_db, payload) + proposal_after_stale_execute = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + snapshot_after_stale_execute = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) assert approved_proposal is not None _psql( args.container, clone_db, f"""update kb_stage.kb_proposals - set payload = {ap.sql_literal(json.dumps(approved_proposal['payload'], sort_keys=True))}::jsonb, + set payload = {ap.sql_literal(json.dumps(approved_proposal["payload"], sort_keys=True))}::jsonb, updated_at = now() - where id = {ap.sql_literal(fixture['proposal_id'])}::uuid;""", + where id = {ap.sql_literal(fixture["proposal_id"])}::uuid;""", tuples=False, ) - restored_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - restored_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + restored_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + restored_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["mutation_between_build_and_execute"] = { "build_returncode": built_apply.returncode, "built_sql_sha256": hashlib.sha256(built_sql.encode()).hexdigest(), - "built_sql_uses_assert_approved_proposal": ( - "kb_stage.assert_approved_proposal" in built_sql - ), - "built_sql_uses_finish_approved_proposal": ( - "kb_stage.finish_approved_proposal" in built_sql - ), + "built_sql_uses_assert_approved_proposal": ("kb_stage.assert_approved_proposal" in built_sql), + "built_sql_uses_finish_approved_proposal": ("kb_stage.finish_approved_proposal" in built_sql), "approved_before_mutation": approved_proposal, "immutable_approval_before_mutation": approved_snapshot, "mutated_readback": proposal_after_mutation, - "immutable_approval_after_ledger_mutation": ( - snapshot_after_ledger_mutation - ), + "immutable_approval_after_ledger_mutation": (snapshot_after_ledger_mutation), "stale_execute": _command_readback(stale_execute), "rows_after_stale_execute": rows_after_stale_execute, "proposal_after_stale_execute": proposal_after_stale_execute, - "immutable_approval_after_stale_execute": ( - snapshot_after_stale_execute - ), + "immutable_approval_after_stale_execute": (snapshot_after_stale_execute), "restored_readback": restored_proposal, "restored_immutable_approval": restored_snapshot, "stale_apply_refused": stale_execute.returncode != 0, - "canonical_rows_unchanged": ( - rows_after_stale_execute == _empty_bundle_readback(payload) - ), + "canonical_rows_unchanged": (rows_after_stale_execute == _empty_bundle_readback(payload)), "approval_restored_exactly": restored_proposal == approved_proposal, "immutable_approval_never_changed": all( snapshot == approved_snapshot @@ -1841,27 +1752,41 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: ), } - result["clone_counts_before_apply"] = _base_counts( - args.container, clone_db + if approved_proposal is None or approved_snapshot is None: + raise RuntimeError("approved ledger and immutable approval are required") + result["target_rows_before_apply"] = _exact_bundle_readback(args.container, clone_db, payload) + fresh_preflight = lifecycle.build_fresh_preflight( + approved_proposal, + result["target_rows_before_apply"], ) - first_apply = _apply(args, fixture["proposal_id"], clone_db) - result["first_apply"] = _command_readback(first_apply) - result["row_readback"] = _exact_bundle_readback( - args.container, clone_db, payload - ) - result["clone_counts_after_apply"] = _base_counts( - args.container, clone_db + result["fresh_owned_preflight"] = fresh_preflight + result["unrelated_fingerprint_before_apply"] = _unrelated_state_fingerprint( + args.container, clone_db, payload, fixture["proposal_id"] ) + result["clone_counts_before_apply"] = _base_counts(args.container, clone_db) + with tempfile.TemporaryDirectory(prefix="leo-proposal-apply-receipt-") as receipt_dir: + private_receipt_path = Path(receipt_dir) / "apply-receipt.json" + first_apply = _apply( + args, + fixture["proposal_id"], + clone_db, + receipt_out=private_receipt_path, + ) + result["first_apply"] = _apply_command_readback(first_apply) + if first_apply.returncode != 0 or not private_receipt_path.is_file(): + raise RuntimeError( + "first apply did not produce its explicit private receipt: " + f"returncode={first_apply.returncode} stderr={first_apply.stderr.strip()}" + ) + apply_receipt = json.loads(private_receipt_path.read_text(encoding="utf-8")) + result["row_readback"] = _exact_bundle_readback(args.container, clone_db, payload) + result["clone_counts_after_apply"] = _base_counts(args.container, clone_db) result["clone_apply_table_deltas"] = _table_deltas( result["clone_counts_before_apply"], result["clone_counts_after_apply"], ) - applied_proposal = _proposal_readback( - args.container, clone_db, fixture["proposal_id"] - ) - applied_snapshot = _approval_snapshot_readback( - args.container, clone_db, fixture["proposal_id"] - ) + applied_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + applied_snapshot = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) result["apply_transition"] = { "approved_readback": approved_proposal, "applied_readback": applied_proposal, @@ -1869,20 +1794,93 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "immutable_approval_matches_applied_ledger": ( _approval_snapshot_matches(applied_proposal, applied_snapshot) ), - "exact_transition": _apply_transition_valid( - approved_proposal, applied_proposal - ), + "exact_transition": _apply_transition_valid(approved_proposal, applied_proposal), } replay = _apply(args, fixture["proposal_id"], clone_db) result["idempotent_replay"] = { "returncode": replay.returncode, - "already_applied_refusal": ( - "already applied" in (replay.stdout + replay.stderr).lower() - ), + "already_applied_refusal": ("already applied" in (replay.stdout + replay.stderr).lower()), "stderr": replay.stderr.strip(), } + rollback_sql = lifecycle.build_compensating_rollback_sql( + approved_proposal, + approved_snapshot, + fresh_preflight, + apply_receipt, + ) + result["rollback_plan"] = { + "schema": lifecycle.ROLLBACK_SCHEMA, + "rollback_sql_sha256": lifecycle.sha256_text(rollback_sql), + "execution_authority": "disposable_clone_admin_only", + } + _psql(args.container, clone_db, rollback_sql, tuples=False) + rollback_proposal = _proposal_readback(args.container, clone_db, fixture["proposal_id"]) + rollback_approval = _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]) + rollback_target_rows = _exact_bundle_readback(args.container, clone_db, payload) + rollback_counts = _base_counts(args.container, clone_db) + rollback_unrelated = _unrelated_state_fingerprint(args.container, clone_db, payload, fixture["proposal_id"]) + + rollback_replay = _run( + [ + "docker", + "exec", + "-i", + args.container, + "psql", + "-U", + "postgres", + "-d", + clone_db, + "-v", + "ON_ERROR_STOP=1", + ], + input_text=rollback_sql, + check=False, + ) + rollback_replay_state = { + "proposal": _proposal_readback(args.container, clone_db, fixture["proposal_id"]), + "approval": _approval_snapshot_readback(args.container, clone_db, fixture["proposal_id"]), + "target_rows": _exact_bundle_readback(args.container, clone_db, payload), + "counts": _base_counts(args.container, clone_db), + "unrelated": _unrelated_state_fingerprint(args.container, clone_db, payload, fixture["proposal_id"]), + } + rollback_replay_state_unchanged = rollback_replay_state == { + "proposal": rollback_proposal, + "approval": rollback_approval, + "target_rows": rollback_target_rows, + "counts": rollback_counts, + "unrelated": rollback_unrelated, + } + result["rollback_replay"] = { + "returncode": rollback_replay.returncode, + "refused": rollback_replay.returncode != 0, + "state_unchanged": rollback_replay_state_unchanged, + "stderr": rollback_replay.stderr.strip(), + } + lifecycle_receipt = lifecycle.build_lifecycle_receipt( + proposal_before=approved_proposal, + approval_before=approved_snapshot, + preflight=fresh_preflight, + apply_receipt=apply_receipt, + rollback_sql=rollback_sql, + proposal_after=rollback_proposal or {}, + approval_after=rollback_approval or {}, + target_rows_after=rollback_target_rows, + counts_before=result["clone_counts_before_apply"], + counts_after=rollback_counts, + unrelated_before=result["unrelated_fingerprint_before_apply"], + unrelated_after=rollback_unrelated, + rollback_replay_refused=(rollback_replay.returncode != 0 and rollback_replay_state_unchanged), + ) + result["applied_rollback_receipt"] = lifecycle_receipt + if args.lifecycle_receipt_output: + lifecycle._write_json(args.lifecycle_receipt_output, lifecycle_receipt) + if args.rollback_sql_output: + args.rollback_sql_output.parent.mkdir(parents=True, exist_ok=True) + args.rollback_sql_output.write_text(rollback_sql, encoding="utf-8") + _stage_proposal( args.container, clone_db, @@ -1890,29 +1888,19 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: conflict_payload, source_ref="working-leo:approve-claim-conflict-canary", ) - conflict_pending = _proposal_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) + conflict_pending = _proposal_readback(args.container, clone_db, fixture["conflict_proposal_id"]) conflict_pending_snapshot = _approval_snapshot_readback( args.container, clone_db, fixture["conflict_proposal_id"] ) conflict_review = _approve(args, fixture["conflict_proposal_id"], clone_db) - conflict_approved = _proposal_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) + conflict_approved = _proposal_readback(args.container, clone_db, fixture["conflict_proposal_id"]) conflict_approved_snapshot = _approval_snapshot_readback( args.container, clone_db, fixture["conflict_proposal_id"] ) conflict = _apply(args, fixture["conflict_proposal_id"], clone_db) - conflict_after = _proposal_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) - conflict_snapshot_after = _approval_snapshot_readback( - args.container, clone_db, fixture["conflict_proposal_id"] - ) - conflict_rows = _exact_bundle_readback( - args.container, clone_db, conflict_payload - ) + conflict_after = _proposal_readback(args.container, clone_db, fixture["conflict_proposal_id"]) + conflict_snapshot_after = _approval_snapshot_readback(args.container, clone_db, fixture["conflict_proposal_id"]) + conflict_rows = _exact_bundle_readback(args.container, clone_db, conflict_payload) result["conflict_review_transition"] = { "pending_readback": conflict_pending, "pending_immutable_approval": conflict_pending_snapshot, @@ -1922,15 +1910,11 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "immutable_approval_matches_ledger": _approval_snapshot_matches( conflict_approved, conflict_approved_snapshot ), - "exact_transition": _approval_transition_valid( - conflict_pending, conflict_approved - ), + "exact_transition": _approval_transition_valid(conflict_pending, conflict_approved), } result["conflict_apply"] = { "returncode": conflict.returncode, - "source_hash_collision_refusal": ( - "source hash collision" in (conflict.stdout + conflict.stderr).lower() - ), + "source_hash_collision_refusal": ("source hash collision" in (conflict.stdout + conflict.stderr).lower()), "stderr": conflict.stderr.strip(), } result["conflict_readback"] = { @@ -1938,19 +1922,13 @@ def run_canary(args: argparse.Namespace) -> dict[str, Any]: "immutable_approval": conflict_snapshot_after, "canonical_rows": conflict_rows, "proposal_approval_unchanged": conflict_after == conflict_approved, - "immutable_approval_unchanged": ( - conflict_snapshot_after == conflict_approved_snapshot - ), - "canonical_rows_absent": ( - conflict_rows == _empty_bundle_readback(conflict_payload) - ), + "immutable_approval_unchanged": (conflict_snapshot_after == conflict_approved_snapshot), + "canonical_rows_absent": (conflict_rows == _empty_bundle_readback(conflict_payload)), } except Exception as exc: result["error"] = f"{type(exc).__name__}: {exc}" finally: - cleanup_readback: dict[str, Any] = { - "drop_attempted": bool(result["clone_created"]) - } + cleanup_readback: dict[str, Any] = {"drop_attempted": bool(result["clone_created"])} if result["clone_created"]: cleanup_sql = f"""select pg_terminate_backend(pid) from pg_stat_activity @@ -1985,46 +1963,32 @@ drop database if exists {clone_db}; ) or "0" ) - cleanup_readback["leftover_clone_database_count"] = result[ - "leftover_clone_count" - ] + cleanup_readback["leftover_clone_database_count"] = result["leftover_clone_count"] result["cleanup_readback"] = cleanup_readback - result["source_counts_after"] = _base_counts( - args.container, args.source_db - ) + result["source_counts_after"] = _base_counts(args.container, args.source_db) if "source_counts_before" in result: result["source_table_deltas"] = _table_deltas( result["source_counts_before"], result["source_counts_after"], ) - result["source_cluster_roles_after"] = _cluster_role_readback( - args.container, args.source_db - ) - result["source_target_rows_after"] = _exact_bundle_readback( - args.container, args.source_db, payload - ) + result["source_cluster_roles_after"] = _cluster_role_readback(args.container, args.source_db) + result["source_target_rows_after"] = _exact_bundle_readback(args.container, args.source_db, payload) result["service_after"] = _service_state(args.service) try: source_files_after = _source_file_manifest(args) result["source_binding"]["post_run_files"] = source_files_after - result["source_binding"]["unchanged_during_run"] = ( - source_files_after == source_files_before - ) + result["source_binding"]["unchanged_during_run"] = source_files_after == source_files_before except Exception as exc: - result["source_binding"]["post_run_error"] = ( - f"{type(exc).__name__}: {exc}" - ) + result["source_binding"]["post_run_error"] = f"{type(exc).__name__}: {exc}" gateway_process_observable = ( result.get("service_before", {}).get("available") is True and result.get("service_after", {}).get("available") is True ) gateway_process_unchanged = ( - result.get("service_before", {}).get("MainPID") - == result.get("service_after", {}).get("MainPID") - and result.get("service_before", {}).get("NRestarts") - == result.get("service_after", {}).get("NRestarts") + result.get("service_before", {}).get("MainPID") == result.get("service_after", {}).get("MainPID") + and result.get("service_before", {}).get("NRestarts") == result.get("service_after", {}).get("NRestarts") if gateway_process_observable else None ) @@ -2033,75 +1997,39 @@ drop database if exists {clone_db}; result.get("security_definer_readback") or {} ), "clone_prerequisites_omit_cluster_role_ddl": ( - result.get("clone_prerequisite_sanitization", {}).get( - "cluster_role_ddl_removed" - ) - is True - ), - "reviewer_agent_seeded_exactly": ( - result.get("reviewer_agent_seed", {}).get("exact_clone_copy") is True - ), - "payload_agents_seeded_exactly": ( - result.get("payload_agent_seed", {}).get("exact_clone_copy") is True + result.get("clone_prerequisite_sanitization", {}).get("cluster_role_ddl_removed") is True ), + "reviewer_agent_seeded_exactly": (result.get("reviewer_agent_seed", {}).get("exact_clone_copy") is True), + "payload_agents_seeded_exactly": (result.get("payload_agent_seed", {}).get("exact_clone_copy") is True), "kb_review_approval_succeeded": ( - result.get("review_transition", {}) - .get("review_apply", {}) - .get("returncode") - == 0 + result.get("review_transition", {}).get("review_apply", {}).get("returncode") == 0 ), "kb_review_used_security_definer": ( - result.get("review_transition", {}) - .get("review_dry_run", {}) - .get("uses_approve_strict_proposal") - is True + result.get("review_transition", {}).get("review_dry_run", {}).get("uses_approve_strict_proposal") is True ), "review_transition_exact": ( result.get("review_transition", {}).get("exact_transition") is True - and result.get("review_transition", {}).get( - "immutable_approval_matches_ledger" - ) - is True + and result.get("review_transition", {}).get("immutable_approval_matches_ledger") is True ), "kb_apply_cannot_mutate_proposal_approval": ( - result.get("kb_apply_proposal_mutation_probes", {}) - .get("approval_fields", {}) - .get("refused") - is True + result.get("kb_apply_proposal_mutation_probes", {}).get("approval_fields", {}).get("refused") is True ), "kb_apply_cannot_mutate_proposal_payload": ( - result.get("kb_apply_proposal_mutation_probes", {}) - .get("payload", {}) - .get("refused") - is True + result.get("kb_apply_proposal_mutation_probes", {}).get("payload", {}).get("refused") is True ), "kb_apply_cannot_mutate_immutable_approval": ( - result.get("kb_apply_proposal_mutation_probes", {}) - .get("immutable_approval_payload", {}) - .get("refused") + result.get("kb_apply_proposal_mutation_probes", {}).get("immutable_approval_payload", {}).get("refused") is True ), "proposal_exact_after_permission_probes": ( - result.get("kb_apply_proposal_mutation_probes", {}).get( - "exact_row_unchanged" - ) - is True - and result.get("kb_apply_proposal_mutation_probes", {}).get( - "immutable_approval_exactly_unchanged" - ) - is True + result.get("kb_apply_proposal_mutation_probes", {}).get("exact_row_unchanged") is True + and result.get("kb_apply_proposal_mutation_probes", {}).get("immutable_approval_exactly_unchanged") is True ), "kb_apply_cannot_update_existing_claim_evidence": ( - result.get("kb_apply_claim_evidence_update_probe", {}) - .get("attempt", {}) - .get("refused") - is True + result.get("kb_apply_claim_evidence_update_probe", {}).get("attempt", {}).get("refused") is True ), "claim_evidence_exact_after_permission_probe": ( - result.get("kb_apply_claim_evidence_update_probe", {}).get( - "exact_row_unchanged" - ) - is True + result.get("kb_apply_claim_evidence_update_probe", {}).get("exact_row_unchanged") is True ), "mutation_between_build_and_execute_refused": all( result.get("mutation_between_build_and_execute", {}).get(key) is True @@ -2114,87 +2042,64 @@ drop database if exists {clone_db}; "immutable_approval_never_changed", ) ), - "first_apply_succeeded": result.get("first_apply", {}).get("returncode") - == 0, - "payload_projection_exact": result.get("row_readback") == expected_rows, - "clone_apply_table_deltas_exact": ( - result.get("clone_apply_table_deltas") == expected_table_deltas + "first_apply_succeeded": result.get("first_apply", {}).get("returncode") == 0, + "fresh_owned_preflight_exact": ( + result.get("fresh_owned_preflight", {}).get("fresh_owned_targets") is True + and result.get("target_rows_before_apply") == _empty_bundle_readback(payload) ), + "compensating_rollback_receipt_passes": ( + result.get("applied_rollback_receipt", {}).get("pass") is True + and result.get("applied_rollback_receipt", {}).get("current_tier") == "T2_runtime" + ), + "rollback_replay_refused_without_mutation": ( + result.get("rollback_replay", {}).get("refused") is True + and result.get("rollback_replay", {}).get("state_unchanged") is True + ), + "payload_projection_exact": result.get("row_readback") == expected_rows, + "clone_apply_table_deltas_exact": (result.get("clone_apply_table_deltas") == expected_table_deltas), "apply_transition_exact": ( result.get("apply_transition", {}).get("exact_transition") is True - and result.get("apply_transition", {}).get( - "immutable_approval_matches_applied_ledger" - ) - is True + and result.get("apply_transition", {}).get("immutable_approval_matches_applied_ledger") is True ), "idempotent_replay_refused": ( result.get("idempotent_replay", {}).get("returncode", 0) != 0 - and result.get("idempotent_replay", {}).get( - "already_applied_refusal" - ) - is True + and result.get("idempotent_replay", {}).get("already_applied_refusal") is True ), "conflict_review_separate_and_exact": ( - result.get("conflict_review_transition", {}).get("exact_transition") - is True - and result.get("conflict_review_transition", {}).get( - "immutable_approval_matches_ledger" - ) - is True + result.get("conflict_review_transition", {}).get("exact_transition") is True + and result.get("conflict_review_transition", {}).get("immutable_approval_matches_ledger") is True ), "source_hash_conflict_refused": ( result.get("conflict_apply", {}).get("returncode", 0) != 0 - and result.get("conflict_apply", {}).get( - "source_hash_collision_refusal" - ) - is True + and result.get("conflict_apply", {}).get("source_hash_collision_refusal") is True ), "conflict_transaction_rolled_back_exactly": ( - result.get("conflict_readback", {}).get( - "proposal_approval_unchanged" - ) - is True - and result.get("conflict_readback", {}).get( - "immutable_approval_unchanged" - ) - is True - and result.get("conflict_readback", {}).get("canonical_rows_absent") - is True + result.get("conflict_readback", {}).get("proposal_approval_unchanged") is True + and result.get("conflict_readback", {}).get("immutable_approval_unchanged") is True + and result.get("conflict_readback", {}).get("canonical_rows_absent") is True ), "source_target_rows_unchanged": ( - result.get("source_target_rows_before") - == result.get("source_target_rows_after") + result.get("source_target_rows_before") == result.get("source_target_rows_after") ), "source_cluster_roles_preexisting": ( - _cluster_roles_are_preprovisioned( - result.get("source_cluster_roles_before") or [] - ) + _cluster_roles_are_preprovisioned(result.get("source_cluster_roles_before") or []) ), "source_cluster_roles_unchanged": ( - result.get("source_cluster_roles_before") - == result.get("source_cluster_roles_after") + result.get("source_cluster_roles_before") == result.get("source_cluster_roles_after") ), "source_table_deltas_exactly_zero": ( bool(result.get("source_table_deltas")) and all(delta == 0 for delta in result["source_table_deltas"].values()) ), - "source_files_unchanged_during_run": ( - result.get("source_binding", {}).get("unchanged_during_run") is True - ), + "source_files_unchanged_during_run": (result.get("source_binding", {}).get("unchanged_during_run") is True), "gateway_process_unchanged": gateway_process_unchanged, - "clone_cleanup_complete": ( - result.get("clone_dropped") is True - and result.get("leftover_clone_count") == 0 - ), + "clone_cleanup_complete": (result.get("clone_dropped") is True and result.get("leftover_clone_count") == 0), } - result["source_counts_observed_unchanged"] = ( - result.get("source_counts_before") == result.get("source_counts_after") - ) + result["source_counts_observed_unchanged"] = result.get("source_counts_before") == result.get("source_counts_after") result["gateway_process_observable"] = gateway_process_observable result["status"] = ( "pass" - if all(value is True for value in result["checks"].values() if value is not None) - and "error" not in result + if all(value is True for value in result["checks"].values() if value is not None) and "error" not in result else "fail" ) result["source_database_mutated"] = not all( @@ -2205,11 +2110,7 @@ drop database if exists {clone_db}; "source_table_deltas_exactly_zero", ) ) - result["service_restarted"] = ( - None - if gateway_process_unchanged is None - else not gateway_process_unchanged - ) + result["service_restarted"] = None if gateway_process_unchanged is None else not gateway_process_unchanged result["post_run_cleanup"] = { "leftover_clone_database_count": result.get("leftover_clone_count"), "gateway_process_observable": gateway_process_observable, @@ -2218,9 +2119,7 @@ drop database if exists {clone_db}; "gateway_main_pid": result.get("service_after", {}).get("MainPID"), "gateway_restart_count": result.get("service_after", {}).get("NRestarts"), } - result["strict_receipt"] = _strict_receipt( - result, expected_rows, expected_table_deltas - ) + result["strict_receipt"] = _strict_receipt(result, expected_rows, expected_table_deltas) if not result["strict_receipt"]["pass"]: result["status"] = "fail" return result @@ -2232,15 +2131,9 @@ def parse_args(argv: list[str]) -> argparse.Namespace: parser.add_argument("--source-db", default=ap.DEFAULT_DB) parser.add_argument("--database-prefix", default="teleo_approve_claim_clone") parser.add_argument("--service", default="leoclean-gateway.service") - parser.add_argument( - "--approve-script", type=Path, default=HERE / "approve_proposal.py" - ) - parser.add_argument( - "--apply-script", type=Path, default=HERE / "apply_proposal.py" - ) - parser.add_argument( - "--prereqs", type=Path, default=HERE / "kb_apply_prereqs.sql" - ) + parser.add_argument("--approve-script", type=Path, default=HERE / "approve_proposal.py") + parser.add_argument("--apply-script", type=Path, default=HERE / "apply_proposal.py") + parser.add_argument("--prereqs", type=Path, default=HERE / "kb_apply_prereqs.sql") parser.add_argument("--review-secrets-file", default=DEFAULT_REVIEW_SECRETS_FILE) parser.add_argument("--secrets-file", default=ap.DEFAULT_SECRETS_FILE) parser.add_argument("--review-role", default="kb_review") @@ -2251,6 +2144,16 @@ def parse_args(argv: list[str]) -> argparse.Namespace: help="optional one-row normalization output containing one strict approve_claim child", ) parser.add_argument("--output", type=Path) + parser.add_argument( + "--lifecycle-receipt-output", + type=Path, + help="optional public fixture receipt output (never use for private production data)", + ) + parser.add_argument( + "--rollback-sql-output", + type=Path, + help="optional exact clone rollback SQL output", + ) return parser.parse_args(argv) diff --git a/scripts/run_approve_claim_isolated_container_canary.sh b/scripts/run_approve_claim_isolated_container_canary.sh index a34f3bf..5690c31 100755 --- a/scripts/run_approve_claim_isolated_container_canary.sh +++ b/scripts/run_approve_claim_isolated_container_canary.sh @@ -9,6 +9,8 @@ image="${POSTGRES_CANARY_IMAGE:-postgres:16}" source_tier="${SOURCE_TIER:-live-readonly}" normalization_json="" output="${repo_root}/docs/reports/leo-working-state-20260709/approve-claim-clone-canary-current.json" +lifecycle_receipt_output="" +rollback_sql_output="" while [[ $# -gt 0 ]]; do case "$1" in @@ -20,6 +22,14 @@ while [[ $# -gt 0 ]]; do output="$2" shift 2 ;; + --lifecycle-receipt-output) + lifecycle_receipt_output="$2" + shift 2 + ;; + --rollback-sql-output) + rollback_sql_output="$2" + shift 2 + ;; --source-container) source_container="$2" shift 2 @@ -99,11 +109,19 @@ service_state() { } mkdir -p "$workdir" "$(dirname "$output")" +if [[ -n "$lifecycle_receipt_output" ]]; then + mkdir -p "$(dirname "$lifecycle_receipt_output")" +fi +if [[ -n "$rollback_sql_output" ]]; then + mkdir -p "$(dirname "$rollback_sql_output")" +fi source_counts_before="$(source_counts)" service_before="$(service_state)" docker run -d --name "$container" \ --network none \ + --label livingip.canary=proposal-apply-lifecycle \ + --label "livingip.canary.instance=${container}" \ --tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m \ -e POSTGRES_PASSWORD="isolated-postgres-${RANDOM}-${RANDOM}" \ -e POSTGRES_DB=teleo \ @@ -127,6 +145,12 @@ if [[ "$ready_streak" -lt 2 ]]; then fi container_network_mode="$(docker inspect -f '{{.HostConfig.NetworkMode}}' "$container")" container_mounts_json="$(docker inspect -f '{{json .Mounts}}' "$container")" +container_canary_label="$( + docker inspect -f '{{index .Config.Labels "livingip.canary"}}' "$container" +)" +container_instance_label="$( + docker inspect -f '{{index .Config.Labels "livingip.canary.instance"}}' "$container" +)" docker exec "$source_container" pg_dump -U postgres -d "$source_db" \ --schema-only --no-owner --no-privileges >"$workdir/schema.sql" @@ -165,6 +189,13 @@ normalization_args=() if [[ -n "$normalization_json" ]]; then normalization_args=(--normalization-json "$normalization_json") fi +artifact_args=() +if [[ -n "$lifecycle_receipt_output" ]]; then + artifact_args+=(--lifecycle-receipt-output "$lifecycle_receipt_output") +fi +if [[ -n "$rollback_sql_output" ]]; then + artifact_args+=(--rollback-sql-output "$rollback_sql_output") +fi set +e python3 "$repo_root/scripts/run_approve_claim_clone_canary.py" \ @@ -178,6 +209,7 @@ python3 "$repo_root/scripts/run_approve_claim_clone_canary.py" \ --review-secrets-file "$workdir/kb-review.env" \ --secrets-file "$workdir/kb-apply.env" \ "${normalization_args[@]}" \ + "${artifact_args[@]}" \ --output "$output" \ >"$workdir/stdout.json" 2>"$workdir/stderr.log" canary_rc=$? @@ -192,6 +224,20 @@ container_name_readback="$( docker container ls -a --filter "name=^/${container}$" --format '{{.Names}}' 2>&1 )" container_name_readback_rc=$? +container_label_readback="$( + docker container ls -a \ + --filter "label=livingip.canary=proposal-apply-lifecycle" \ + --filter "label=livingip.canary.instance=${container}" \ + --format '{{.Names}}' 2>&1 +)" +container_label_readback_rc=$? +if [[ "$container_label_readback_rc" -eq 0 ]]; then + container_label_orphan_count="$( + awk 'NF { count += 1 } END { print count + 0 }' <<<"$container_label_readback" + )" +else + container_label_orphan_count=-1 +fi if [[ -e "$workdir" || -L "$workdir" ]]; then outer_workdir_lexists=true else @@ -214,6 +260,9 @@ OUTER_CONTAINER_REMOVE_RC="$outer_container_remove_rc" \ OUTER_WORKDIR_REMOVE_RC="$outer_workdir_remove_rc" \ CONTAINER_NAME_READBACK="$container_name_readback" \ CONTAINER_NAME_READBACK_RC="$container_name_readback_rc" \ +CONTAINER_LABEL_READBACK="$container_label_readback" \ +CONTAINER_LABEL_READBACK_RC="$container_label_readback_rc" \ +CONTAINER_LABEL_ORPHAN_COUNT="$container_label_orphan_count" \ OUTER_WORKDIR_LEXISTS="$outer_workdir_lexists" \ CANARY_RC="$canary_rc" \ SOURCE_TIER="$source_tier" \ @@ -221,219 +270,6 @@ SOURCE_NETWORK_MODE="$source_network_mode" \ SOURCE_CANARY_LABEL="$source_canary_label" \ CANARY_NETWORK_MODE="$container_network_mode" \ CANARY_MOUNTS_JSON="$container_mounts_json" \ -python3 - "$output" <<'PY' -import hashlib -import json -import os -import sys -from pathlib import Path - -path = Path(sys.argv[1]) -if not path.is_file(): - raise SystemExit("inner canary did not produce a report") - -data = json.loads(path.read_text(encoding="utf-8")) - -def service(raw: str) -> dict[str, str]: - return dict(line.split("=", 1) for line in raw.splitlines() if "=" in line) - -def table_deltas(before: dict[str, int], after: dict[str, int]) -> dict[str, int]: - if set(before) != set(after): - raise SystemExit( - "live source count keys changed during the canary: " - f"before={sorted(before)} after={sorted(after)}" - ) - return {key: after[key] - before[key] for key in sorted(before)} - -def verify_source_binding(root: Path, records: list[dict[str, object]]) -> dict[str, object]: - readbacks = [] - for record in records: - relative_text = str(record.get("repo_relative_path", "")) - relative = Path(relative_text) - if not relative_text or relative.is_absolute() or ".." in relative.parts: - raise SystemExit(f"unsafe repo-relative source path: {relative_text!r}") - candidate = (root / relative).resolve() - try: - candidate.relative_to(root) - except ValueError as exc: - raise SystemExit( - f"source path escaped repository root: {relative_text}" - ) from exc - if not candidate.is_file(): - readbacks.append( - { - "repo_relative_path": relative.as_posix(), - "expected_sha256": record.get("sha256"), - "actual_sha256": None, - "matches": False, - } - ) - continue - content = candidate.read_bytes() - actual_sha256 = hashlib.sha256(content).hexdigest() - readbacks.append( - { - "repo_relative_path": relative.as_posix(), - "expected_sha256": record.get("sha256"), - "actual_sha256": actual_sha256, - "size_bytes": len(content), - "matches": ( - actual_sha256 == record.get("sha256") - and len(content) == record.get("size_bytes") - ), - } - ) - return { - "files": readbacks, - "all_match": bool(readbacks) and all(row["matches"] for row in readbacks), - } - -def contains_ephemeral_path(value: object) -> bool: - if isinstance(value, str): - return "/tmp/" in value - if isinstance(value, dict): - return any(contains_ephemeral_path(item) for item in value.values()) - if isinstance(value, list): - return any(contains_ephemeral_path(item) for item in value) - return False - -before_counts = json.loads(os.environ["SOURCE_COUNTS_BEFORE"]) -after_counts = json.loads(os.environ["SOURCE_COUNTS_AFTER"]) -live_table_deltas = table_deltas(before_counts, after_counts) -before_service = service(os.environ["SERVICE_BEFORE"]) -after_service = service(os.environ["SERVICE_AFTER"]) -source_tier = os.environ["SOURCE_TIER"] -source_network_mode = os.environ["SOURCE_NETWORK_MODE"] -source_canary_label = os.environ["SOURCE_CANARY_LABEL"] -container_network_mode = os.environ["CANARY_NETWORK_MODE"] -container_mounts = json.loads(os.environ["CANARY_MOUNTS_JSON"]) -container_volume_mounts = [ - mount for mount in container_mounts if mount.get("Type") == "volume" -] -container_isolated = ( - container_network_mode == "none" and not container_volume_mounts -) -source_tier_enforced = ( - source_tier != "isolated-local" - or ( - source_network_mode == "none" - and source_canary_label == "working-leo-approved-source" - ) -) -service_observable = ( - before_service.get("Available") == "true" - and after_service.get("Available") == "true" -) -service_stable = ( - all( - before_service.get(key) == after_service.get(key) - for key in ("ActiveState", "SubState", "MainPID", "NRestarts") - ) - if service_observable - else None -) -service_gate_passes = ( - service_stable is True - if source_tier == "live-readonly" - else service_stable is not False -) -container_absent = ( - int(os.environ["CONTAINER_NAME_READBACK_RC"]) == 0 - and not os.environ["CONTAINER_NAME_READBACK"].strip() -) -workdir_absent = os.environ["OUTER_WORKDIR_LEXISTS"] == "false" -source_binding = verify_source_binding( - Path(os.environ["REPO_ROOT"]).resolve(), - data.get("source_binding", {}).get("files", []), -) -data["source_binding"]["independent_post_cleanup_verification"] = source_binding -source_binding_ok = ( - data["source_binding"].get("unchanged_during_run") is True - and source_binding["all_match"] is True -) -stale_ephemeral_paths_absent = not contains_ephemeral_path(data) -inner_runtime_pass = data.get("status") == "pass" and int(os.environ["CANARY_RC"]) == 0 -outer_ok = ( - before_counts == after_counts - and all(delta == 0 for delta in live_table_deltas.values()) - and service_gate_passes - and container_absent - and workdir_absent - and source_binding_ok - and stale_ephemeral_paths_absent - and container_isolated - and source_tier_enforced -) -if source_tier == "isolated-local": - data["required_tier"] = "T2_runtime" - data["required_tiers"] = ["T2_runtime"] - data["runtime_scope"] = "isolated_postgres_container_from_readonly_local_fixture" -else: - data["required_tier"] = "T3_live_readonly" - data["required_tiers"] = ["T2_runtime", "T3_live_readonly"] - data["runtime_scope"] = "isolated_postgres_container_from_readonly_live_schema" -data["outer_isolation"] = { - "source_container": os.environ.get("SOURCE_CONTAINER", "teleo-pg"), - "source_database": os.environ.get("SOURCE_DB", "teleo"), - "source_counts_before": before_counts, - "source_counts_after": after_counts, - "canonical_table_deltas": live_table_deltas, - "source_counts_unchanged": before_counts == after_counts, - "service_before": before_service, - "service_after": after_service, - "service_required": source_tier == "live-readonly", - "service_observable": service_observable, - "service_stable": service_stable, - "service_gate_passes": service_gate_passes, - "source_boundary": { - "source_tier": source_tier, - "network_mode": source_network_mode, - "canary_label": source_canary_label, - "tier_enforced": source_tier_enforced, - }, - "container_isolation": { - "network_mode": container_network_mode, - "volume_mounts": container_volume_mounts, - "network_disabled": container_network_mode == "none", - "no_docker_volumes": not container_volume_mounts, - }, - "cleanup_readback": { - "container_remove_returncode": int(os.environ["OUTER_CONTAINER_REMOVE_RC"]), - "container_name_query_returncode": int( - os.environ["CONTAINER_NAME_READBACK_RC"] - ), - "container_name_query_stdout": os.environ["CONTAINER_NAME_READBACK"], - "temporary_container_absent": container_absent, - "workdir_remove_returncode": int(os.environ["OUTER_WORKDIR_REMOVE_RC"]), - "workdir_lexists_after_cleanup": not workdir_absent, - "temporary_workdir_absent": workdir_absent, - }, -} -data["checks"]["outer_live_counts_exactly_unchanged"] = before_counts == after_counts -data["checks"]["outer_live_table_deltas_exactly_zero"] = all( - delta == 0 for delta in live_table_deltas.values() -) -data["checks"]["outer_service_observed_if_required"] = ( - source_tier != "live-readonly" or service_observable -) -data["checks"]["outer_service_stable_if_observed"] = service_gate_passes -data["checks"]["outer_container_absent_independent_readback"] = container_absent -data["checks"]["outer_workdir_absent_independent_readback"] = workdir_absent -data["checks"]["source_binding_independently_verified"] = source_binding_ok -data["checks"]["stale_ephemeral_paths_absent"] = stale_ephemeral_paths_absent -data["checks"]["outer_source_tier_enforced"] = source_tier_enforced -data["checks"]["outer_container_network_disabled"] = ( - container_network_mode == "none" -) -data["checks"]["outer_container_has_no_docker_volumes"] = not container_volume_mounts -data["checks"]["outer_isolation_complete"] = outer_ok -if outer_ok and inner_runtime_pass: - data["current_tier"] = ( - "T2_runtime" if source_tier == "isolated-local" else "T3_live_readonly" - ) -else: - data["status"] = "fail" - data["current_tier"] = "T2_runtime" if inner_runtime_pass else "T1_model" -path.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n", encoding="utf-8") -raise SystemExit(0 if data["status"] == "pass" else 1) -PY +CANARY_LABEL="$container_canary_label" \ +CANARY_INSTANCE_LABEL="$container_instance_label" \ +python3 "$repo_root/scripts/finalize_approve_claim_isolated_canary.py" "$output" diff --git a/tests/test_apply_proposal.py b/tests/test_apply_proposal.py index b1fb67d..8f79873 100644 --- a/tests/test_apply_proposal.py +++ b/tests/test_apply_proposal.py @@ -30,6 +30,10 @@ sys.path.insert(0, str(REPO_ROOT / "scripts")) import apply_proposal as ap # noqa: E402 +CLAIM_A = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +CLAIM_B = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +SOURCE_A = "cccccccc-cccc-4ccc-8ccc-cccccccccccc" + def _approval_meta(proposal_type, apply_payload): return { @@ -112,7 +116,7 @@ def test_revise_strategy_rejects_bad_node_type(): # --- add_edge ------------------------------------------------------------- # def test_add_edge_sql_dedup_guard(): - payload = {"from_claim": "aaaa", "to_claim": "bbbb", "edge_type": "supersedes", "weight": 0.9} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supersedes", "weight": 0.9} sql = ap.build_add_edge_sql(payload, "pid-2", None, _approval_meta("add_edge", payload)) assert "insert into public.claim_edges" in sql assert "not exists" in sql @@ -122,7 +126,7 @@ def test_add_edge_sql_dedup_guard(): def test_add_edge_semantic_row_accepts_match_and_rejects_weight_mismatch(): - payload = {"from_claim": "aaaa", "to_claim": "bbbb", "edge_type": "supports", "weight": 0.9} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports", "weight": 0.9} sql = ap.build_add_edge_sql(payload, "pid-2", None, _approval_meta("add_edge", payload)) verify = sql[sql.index("do $verify$") : sql.index("kb_stage.finish_approved_proposal")] @@ -131,9 +135,7 @@ def test_add_edge_semantic_row_accepts_match_and_rejects_weight_mismatch(): assert "claim_edge row does not match strict apply payload" in verify assert sql.startswith("begin;") assert sql.rstrip().endswith("commit;") - assert sql.index("raise exception 'apply_proposal: claim_edge row") < sql.index( - "kb_stage.finish_approved_proposal" - ) + assert sql.index("raise exception 'apply_proposal: claim_edge row") < sql.index("kb_stage.finish_approved_proposal") def test_add_edge_rejects_self_loop(): @@ -144,7 +146,7 @@ def test_add_edge_rejects_self_loop(): # --- attach_evidence ------------------------------------------------------ # def test_attach_evidence_sql_with_source_id(): - payload = {"evidence": [{"claim_id": "cccc", "source_id": "dddd", "role": "grounds", "weight": 0.78}]} + payload = {"evidence": [{"claim_id": CLAIM_A, "source_id": SOURCE_A, "role": "grounds", "weight": 0.78}]} sql = ap.build_attach_evidence_sql(payload, "pid-3", None, _approval_meta("attach_evidence", payload)) assert "insert into public.claim_evidence" in sql assert "::evidence_role" in sql @@ -152,14 +154,8 @@ def test_attach_evidence_sql_with_source_id(): def test_attach_evidence_semantic_row_accepts_match_and_rejects_weight_mismatch(): - payload = { - "evidence": [ - {"claim_id": "cccc", "source_id": "dddd", "role": "grounds", "weight": 0.78} - ] - } - sql = ap.build_attach_evidence_sql( - payload, "pid-3", None, _approval_meta("attach_evidence", payload) - ) + payload = {"evidence": [{"claim_id": CLAIM_A, "source_id": SOURCE_A, "role": "grounds", "weight": 0.78}]} + sql = ap.build_attach_evidence_sql(payload, "pid-3", None, _approval_meta("attach_evidence", payload)) verify = sql[sql.index("do $verify$") : sql.index("kb_stage.finish_approved_proposal")] assert "weight is not distinct from 0.78" in verify @@ -167,9 +163,7 @@ def test_attach_evidence_semantic_row_accepts_match_and_rejects_weight_mismatch( assert "evidence row % does not match strict apply payload" in verify assert sql.startswith("begin;") assert sql.rstrip().endswith("commit;") - assert sql.index("raise exception 'apply_proposal: evidence row") < sql.index( - "kb_stage.finish_approved_proposal" - ) + assert sql.index("raise exception 'apply_proposal: evidence row") < sql.index("kb_stage.finish_approved_proposal") def test_attach_evidence_requires_source_id(): @@ -231,9 +225,7 @@ def _approve_claim_bundle(): {"claim_id": claim_a, "source_id": source_a, "role": "grounds", "weight": 0.9}, {"claim_id": claim_b, "source_id": source_b, "role": "illustrates", "weight": None}, ], - "edges": [ - {"from_claim": claim_a, "to_claim": claim_b, "edge_type": "supports", "weight": 0.7} - ], + "edges": [{"from_claim": claim_a, "to_claim": claim_b, "edge_type": "supports", "weight": 0.7}], "reasoning_tools": [ { "id": "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee", @@ -291,9 +283,7 @@ def test_approve_claim_semantic_rows_accept_matches_and_reject_payload_mismatche assert "approve_claim: edge row does not match strict apply payload" in verify assert sql.startswith("begin;") assert sql.rstrip().endswith("commit;") - assert sql.index("raise exception 'approve_claim: evidence row") < sql.index( - "kb_stage.finish_approved_proposal" - ) + assert sql.index("raise exception 'approve_claim: evidence row") < sql.index("kb_stage.finish_approved_proposal") def test_approve_claim_bundle_rejects_duplicate_source_hashes(): @@ -445,7 +435,7 @@ def test_assert_applyable_allows_approved(): # --- ledger flip: concurrency guard + FK stamp --------------------------- # def test_ledger_transition_uses_payload_bound_security_functions(): - payload = {"from_claim": "a", "to_claim": "b", "edge_type": "supports"} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports"} sql = ap.build_add_edge_sql(payload, "pid", None, _approval_meta("add_edge", payload)) assert "kb_stage.assert_approved_proposal" in sql assert "kb_stage.finish_approved_proposal" in sql @@ -459,7 +449,7 @@ def test_ledger_transition_uses_payload_bound_security_functions(): def test_ledger_flip_stamps_agent_fk(): - payload = {"from_claim": "a", "to_claim": "b", "edge_type": "supports"} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports"} sql = ap.build_add_edge_sql(payload, "pid", "kb-apply", _approval_meta("add_edge", payload)) # Hard resolve into a variable + NOT-NULL assert, never a silent inline # subselect that would stamp NULL on an unresolved handle. @@ -468,7 +458,7 @@ def test_ledger_flip_stamps_agent_fk(): def test_build_apply_sql_defaults_applied_by_to_service_agent(): - payload = {"from_claim": "a", "to_claim": "b", "edge_type": "supports"} + payload = {"from_claim": CLAIM_A, "to_claim": CLAIM_B, "edge_type": "supports"} proposal = { "id": "pid", "proposal_type": "add_edge", diff --git a/tests/test_apply_worker.py b/tests/test_apply_worker.py index 21ce813..cd04d1d 100644 --- a/tests/test_apply_worker.py +++ b/tests/test_apply_worker.py @@ -6,7 +6,10 @@ standalone (`python3 tests/test_apply_worker.py`). """ import argparse +import json +import stat import sys +import tempfile from pathlib import Path try: @@ -40,19 +43,33 @@ sys.path.insert(0, str(REPO_ROOT / "scripts")) import apply_worker as w # noqa: E402 -@pytest.fixture(autouse=True) -def restore_apply_worker_dependencies(): +def _dependency_restore(): original_load_password = w.ap.load_password original_load_failure_state = w.load_failure_state original_fetch_candidates = w.fetch_candidates original_apply_one = w.apply_one original_render_one = w.render_one + original_subprocess_run = w.subprocess.run yield w.ap.load_password = original_load_password w.load_failure_state = original_load_failure_state w.fetch_candidates = original_fetch_candidates w.apply_one = original_apply_one w.render_one = original_render_one + w.subprocess.run = original_subprocess_run + + +@pytest.fixture(autouse=True) +def restore_apply_worker_dependencies(): + restore = _dependency_restore() + next(restore) + try: + yield + finally: + try: + next(restore) + except StopIteration: + pass # --- candidate query ------------------------------------------------------ # @@ -116,22 +133,283 @@ def test_render_command_expands_agent_id(): # --- enablement gate ------------------------------------------------------ # def _args(**over): base = dict( - enable=False, limit=20, max_per_tick=1, max_attempts=3, - failure_state_file=None, applied_by="kb-apply", - apply_script="apply_proposal.py", render_cmd="", - secrets_file="x", container="teleo-pg", db="teleo", - host="127.0.0.1", role="kb_apply", + enable=False, + limit=20, + max_per_tick=1, + max_attempts=3, + failure_state_file=None, + applied_by="kb-apply", + apply_script="apply_proposal.py", + render_cmd="", + receipt_dir="/private/tmp/kb-apply-worker-test-receipts", + secrets_file="x", + container="teleo-pg", + db="teleo", + host="127.0.0.1", + role="kb_apply", ) base.update(over) return argparse.Namespace(**base) +def _write_valid_private_receipt(path: Path, proposal_id: str) -> None: + from_claim = "11111111-1111-4111-8111-111111111111" + to_claim = "22222222-2222-4222-8222-222222222222" + proposal = { + "id": proposal_id, + "proposal_type": "add_edge", + "status": "applied", + "payload": { + "apply_payload": { + "from_claim": from_claim, + "to_claim": to_claim, + "edge_type": "supports", + "weight": 0.75, + } + }, + "reviewed_by_handle": "reviewer", + "reviewed_by_agent_id": None, + "reviewed_at": "2026-07-15T10:00:00+00:00", + "review_note": "Approved for worker receipt testing.", + "applied_by_handle": "kb-apply", + "applied_by_agent_id": None, + "applied_at": "2026-07-15T10:01:00+00:00", + } + receipt = w.ap.replay_receipt.build_replay_receipt( + proposal, + { + "claim_edges": [ + { + "id": "33333333-3333-4333-8333-333333333333", + "from_claim": from_claim, + "to_claim": to_claim, + "edge_type": "supports", + "weight": 0.75, + "created_by": None, + } + ] + }, + apply_sql_sha256="a" * 64, + apply_sql_source="exact_executed_sql", + exported_at_utc="2026-07-15T10:02:00+00:00", + ) + path.parent.mkdir(parents=True, exist_ok=True, mode=0o700) + path.write_text(json.dumps(receipt), encoding="utf-8") + path.chmod(0o600) + + +def _load_private_receipt(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def _write_private_receipt(path: Path, receipt: dict) -> None: + path.write_text(json.dumps(receipt), encoding="utf-8") + path.chmod(0o600) + + +def _completed(returncode: int, *, stdout: str = "", stderr: str = ""): + return w.subprocess.CompletedProcess([], returncode, stdout=stdout, stderr=stderr) + + +def _postcommit_receipt_failure(proposal_id: str, detail: str = "receipt write failed") -> str: + return ( + f"proposal {proposal_id} {w.POSTCOMMIT_RECEIPT_FAILURE_MARKER}: {detail}" + f"{w.POSTCOMMIT_RECEIPT_RECOVERY_MARKER}" + f"apply_proposal.py {proposal_id} --receipt-only" + ) + + +# --- private receipt and post-COMMIT recovery ---------------------------- # +def test_private_receipt_requires_complete_canonical_rows_and_hashes(): + proposal_id = "99999999-9999-4999-8999-999999999999" + receipt_path = Path(tempfile.mkdtemp()) / "receipt.json" + _write_valid_private_receipt(receipt_path, proposal_id) + + missing_rows = _load_private_receipt(receipt_path) + missing_rows.pop("canonical_rows") + _write_private_receipt(receipt_path, missing_rows) + with pytest.raises(RuntimeError, match="row-level contract is invalid"): + w.assert_private_replay_receipt(receipt_path, proposal_id) + + _write_valid_private_receipt(receipt_path, proposal_id) + missing_hashes = _load_private_receipt(receipt_path) + missing_hashes.pop("hashes") + _write_private_receipt(receipt_path, missing_hashes) + with pytest.raises(RuntimeError, match="row-level contract is invalid"): + w.assert_private_replay_receipt(receipt_path, proposal_id) + + +def test_private_receipt_rejects_canonical_row_tampering_without_recomputed_hashes(): + proposal_id = "88888888-8888-4888-8888-888888888888" + receipt_path = Path(tempfile.mkdtemp()) / "receipt.json" + _write_valid_private_receipt(receipt_path, proposal_id) + tampered = _load_private_receipt(receipt_path) + tampered["canonical_rows"]["claim_edges"][0]["weight"] = 0.5 + _write_private_receipt(receipt_path, tampered) + + with pytest.raises(RuntimeError, match="row-level contract is invalid|internally inconsistent"): + w.assert_private_replay_receipt(receipt_path, proposal_id) + + +def test_apply_one_supplies_deterministic_private_receipt_path(): + proposal_id = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" + receipt_dir = Path(tempfile.mkdtemp()) / "receipts" + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + receipt_path = Path(cmd[cmd.index("--receipt-out") + 1]) + _write_valid_private_receipt(receipt_path, proposal_id) + return _completed(0, stdout='{"applied": true}') + + w.subprocess.run = _run + w.apply_one(_args(receipt_dir=str(receipt_dir)), proposal_id) + + expected = receipt_dir.resolve() / f"{proposal_id}.json" + assert len(calls) == 1 + assert calls[0][calls[0].index("--receipt-dir") + 1] == str(expected.parent) + assert calls[0][calls[0].index("--receipt-out") + 1] == str(expected) + assert stat.S_IMODE(expected.stat().st_mode) == 0o600 + + +def test_apply_one_recovers_postcommit_receipt_failure_without_reapplying(): + proposal_id = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" + receipt_dir = Path(tempfile.mkdtemp()) / "receipts" + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + if len(calls) == 1: + return _completed( + 1, + stderr=_postcommit_receipt_failure(proposal_id), + ) + assert "--receipt-only" in cmd + receipt_path = Path(cmd[cmd.index("--receipt-out") + 1]) + _write_valid_private_receipt(receipt_path, proposal_id) + return _completed(0, stdout='{"replay_ready": true}') + + w.subprocess.run = _run + w.apply_one(_args(receipt_dir=str(receipt_dir)), proposal_id) + + assert len(calls) == 2 + assert "--receipt-only" not in calls[0] + assert "--receipt-only" in calls[1] + assert calls[0][calls[0].index("--receipt-out") + 1] == calls[1][calls[1].index("--receipt-out") + 1] + + +def test_apply_one_does_not_recover_a_precommit_apply_failure(): + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + return _completed(1, stderr="psql transaction failed before commit") + + w.subprocess.run = _run + try: + w.apply_one(_args(receipt_dir=tempfile.mkdtemp()), "p1") + except w.PostCommitReceiptError as exc: + raise AssertionError("pre-COMMIT failure was misclassified as committed") from exc + except RuntimeError: + pass + else: + raise AssertionError("expected pre-COMMIT apply failure") + + assert len(calls) == 1 + + +def test_apply_one_classifies_unrecovered_receipt_as_postcommit(): + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + if len(calls) == 1: + return _completed(1, stderr=_postcommit_receipt_failure("p1")) + return _completed(1, stderr="receipt directory remains unavailable") + + w.subprocess.run = _run + with pytest.raises(w.PostCommitReceiptError): + w.apply_one(_args(receipt_dir=tempfile.mkdtemp()), "p1") + + assert len(calls) == 2 + assert "--receipt-only" in calls[1] + + +def test_forged_marker_in_unknown_field_traceback_is_a_normal_apply_failure(): + proposal_id = "cccccccc-cccc-4ccc-8ccc-cccccccccccc" + calls = [] + + def _run(cmd, **_kwargs): + calls.append(cmd) + return _completed( + 1, + stderr=( + "Traceback (most recent call last):\n" + "ValueError: approve_claim apply_payload contains unknown fields: " + f"['{w.POSTCOMMIT_RECEIPT_FAILURE_MARKER}']" + ), + ) + + w.subprocess.run = _run + with pytest.raises(RuntimeError, match="apply failed"): + w.apply_one(_args(receipt_dir=tempfile.mkdtemp()), proposal_id) + + assert len(calls) == 1 + assert "--receipt-only" not in calls[0] + + +def test_forged_marker_increments_normal_failure_and_poison_accounting(): + proposal_id = "dddddddd-dddd-4ddd-8ddd-dddddddddddd" + failure_state = str(Path(tempfile.mkdtemp()) / "failures.json") + calls = [] + w.ap.load_password = lambda _path: "pw" + w.fetch_candidates = lambda _args, _password, _excluded: _cands(proposal_id) + + def _run(cmd, **_kwargs): + calls.append(cmd) + return _completed( + 1, + stderr=( + "ValueError: approve_claim apply_payload contains unknown fields: " + f"['{w.POSTCOMMIT_RECEIPT_FAILURE_MARKER}']" + ), + ) + + w.subprocess.run = _run + rc = w.run( + _args( + enable=True, + failure_state_file=failure_state, + max_attempts=1, + max_per_tick=1, + receipt_dir=tempfile.mkdtemp(), + ) + ) + + assert rc == 1 + assert w.load_failure_state(failure_state) == {proposal_id: 1} + assert len(calls) == 1 + + +def test_postcommit_receipt_error_does_not_increment_apply_failure_count(): + path = str(Path(tempfile.mkdtemp()) / "failures.json") + w.ap.load_password = lambda _f: "pw" + w.fetch_candidates = lambda a, p, excluded: _cands("committed") + + def _postcommit(_args, _proposal_id): + raise w.PostCommitReceiptError("committed; receipt recovery failed") + + w.apply_one = _postcommit + rc = w.run(_args(enable=True, failure_state_file=path, max_per_tick=1)) + + assert rc == 1 + assert w.load_failure_state(path) == {} + + def test_report_only_gate_does_not_apply(monkeypatch=None): calls = [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}] w.apply_one = lambda a, pid: calls.append(pid) rc = w.run(_args(enable=False)) @@ -142,9 +420,7 @@ def test_report_only_gate_does_not_apply(monkeypatch=None): def test_enabled_worker_applies_and_renders(): applied, rendered = [], [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "p1", "proposal_type": "revise_strategy", "agent_id": "ag"}] w.apply_one = lambda a, pid: applied.append(pid) w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok" @@ -157,9 +433,7 @@ def test_enabled_worker_applies_and_renders(): def test_enabled_worker_skips_render_for_add_edge(): applied, rendered = [], [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "e1", "proposal_type": "add_edge", "agent_id": None} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "e1", "proposal_type": "add_edge", "agent_id": None}] w.apply_one = lambda a, pid: applied.append(pid) w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok" @@ -172,9 +446,7 @@ def test_enabled_worker_skips_render_for_add_edge(): def test_enabled_worker_skips_render_for_approve_claim(): applied, rendered = [], [] w.ap.load_password = lambda _f: "pw" - w.fetch_candidates = lambda a, p, excluded: [ - {"id": "c1", "proposal_type": "approve_claim", "agent_id": "ag"} - ] + w.fetch_candidates = lambda a, p, excluded: [{"id": "c1", "proposal_type": "approve_claim", "agent_id": "ag"}] w.apply_one = lambda a, pid: applied.append(pid) w.render_one = lambda a, agent_id: rendered.append(agent_id) or "ok" @@ -263,7 +535,7 @@ if __name__ == "__main__": failures = 0 for name, fn in sorted(globals().items()): if name.startswith("test_") and callable(fn): - restore = restore_apply_worker_dependencies() + restore = _dependency_restore() next(restore) try: fn() diff --git a/tests/test_proposal_apply_lifecycle.py b/tests/test_proposal_apply_lifecycle.py new file mode 100644 index 0000000..370dde1 --- /dev/null +++ b/tests/test_proposal_apply_lifecycle.py @@ -0,0 +1,846 @@ +"""Deterministic and property-based tests for proposal apply rollback evidence.""" + +from __future__ import annotations + +import copy +import hashlib +import json +import string +import sys +from datetime import datetime, timezone +from pathlib import Path + +import pytest +from hypothesis import given, settings +from hypothesis import strategies as st + +REPO_ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(REPO_ROOT / "scripts")) + +import apply_proposal as apply # noqa: E402 +import proposal_apply_lifecycle as lifecycle # noqa: E402 + +PROPOSAL_ID = "99999999-9999-4999-8999-999999999999" +CLAIM_A = "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa" +CLAIM_B = "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" +SOURCE_ID = "cccccccc-cccc-4ccc-8ccc-cccccccccccc" +TOOL_ID = "dddddddd-dddd-4ddd-8ddd-dddddddddddd" +AGENT_ID = "11111111-1111-4111-8111-111111111111" +APPLIED_AT = "2026-07-15T10:15:00+00:00" +REVIEWED_AT = "2026-07-15T09:30:00+00:00" +ROW_FAMILIES = tuple(lifecycle.ROW_TABLES) + + +def _apply_payload() -> dict: + return { + "contract_version": 2, + "agent_id": AGENT_ID, + "claims": [ + { + "id": CLAIM_A, + "type": "structural", + "text": "Canonical claim A", + "status": "open", + "confidence": 0.8, + "tags": ["working-leo", "lifecycle"], + "created_by": AGENT_ID, + }, + { + "id": CLAIM_B, + "type": "concept", + "text": "Canonical claim B", + "status": "open", + "confidence": None, + "tags": [], + "created_by": AGENT_ID, + }, + ], + "sources": [ + { + "id": SOURCE_ID, + "source_type": "paper", + "url": "https://example.test/lifecycle", + "storage_path": None, + "excerpt": "Bound lifecycle evidence.", + "hash": "sha256:lifecycle-source", + "created_by": AGENT_ID, + } + ], + "evidence": [ + { + "claim_id": CLAIM_A, + "source_id": SOURCE_ID, + "role": "grounds", + "weight": 0.9, + "created_by": AGENT_ID, + } + ], + "edges": [ + { + "from_claim": CLAIM_A, + "to_claim": CLAIM_B, + "edge_type": "supports", + "weight": 0.7, + "created_by": AGENT_ID, + } + ], + "reasoning_tools": [ + { + "id": TOOL_ID, + "agent_id": AGENT_ID, + "name": "Lifecycle verifier", + "description": "Checks the apply and compensating rollback boundary.", + "category": "verification", + } + ], + } + + +def _proposal_before() -> dict: + return { + "id": PROPOSAL_ID, + "proposal_type": "approve_claim", + "status": "approved", + "payload": {"apply_payload": _apply_payload()}, + "reviewed_by_handle": "independent-reviewer", + "reviewed_by_agent_id": None, + "reviewed_at": REVIEWED_AT, + "review_note": "Approved this exact strict payload.", + "applied_by_handle": None, + "applied_by_agent_id": None, + "applied_at": None, + } + + +def _approval_snapshot(proposal: dict) -> dict: + return { + "proposal_id": proposal["id"], + "proposal_type": proposal["proposal_type"], + "payload": copy.deepcopy(proposal["payload"]), + "reviewed_by_handle": proposal["reviewed_by_handle"], + "reviewed_by_agent_id": proposal["reviewed_by_agent_id"], + "reviewed_by_db_role": "kb_reviewer", + "reviewed_at": proposal["reviewed_at"], + "review_note": proposal["review_note"], + } + + +def _canonical_rows() -> dict[str, list[dict]]: + evidence_id = apply.deterministic_row_uuid(PROPOSAL_ID, "claim_evidence", CLAIM_A, SOURCE_ID, "grounds") + edge_id = apply.deterministic_row_uuid(PROPOSAL_ID, "claim_edge", CLAIM_A, CLAIM_B, "supports") + return { + "claims": [ + { + "id": CLAIM_A, + "type": "structural", + "text": "Canonical claim A", + "status": "open", + "confidence": 0.8, + "tags": ["working-leo", "lifecycle"], + "created_by": AGENT_ID, + "superseded_by": None, + "created_at": APPLIED_AT, + "updated_at": APPLIED_AT, + }, + { + "id": CLAIM_B, + "type": "concept", + "text": "Canonical claim B", + "status": "open", + "confidence": None, + "tags": [], + "created_by": AGENT_ID, + "superseded_by": None, + "created_at": APPLIED_AT, + "updated_at": APPLIED_AT, + }, + ], + "sources": [ + { + "id": SOURCE_ID, + "source_type": "paper", + "url": "https://example.test/lifecycle", + "storage_path": None, + "excerpt": "Bound lifecycle evidence.", + "hash": "sha256:lifecycle-source", + "created_by": AGENT_ID, + "created_at": APPLIED_AT, + } + ], + "claim_evidence": [ + { + "id": evidence_id, + "claim_id": CLAIM_A, + "source_id": SOURCE_ID, + "role": "grounds", + "weight": 0.9, + "created_by": AGENT_ID, + "created_at": APPLIED_AT, + } + ], + "claim_edges": [ + { + "id": edge_id, + "from_claim": CLAIM_A, + "to_claim": CLAIM_B, + "edge_type": "supports", + "weight": 0.7, + "created_by": AGENT_ID, + "created_at": APPLIED_AT, + } + ], + "reasoning_tools": [ + { + "id": TOOL_ID, + "agent_id": AGENT_ID, + "name": "Lifecycle verifier", + "description": "Checks the apply and compensating rollback boundary.", + "category": "verification", + "created_at": APPLIED_AT, + "updated_at": APPLIED_AT, + } + ], + } + + +def _empty_targets() -> dict[str, list[dict]]: + return {family: [] for family in ROW_FAMILIES} + + +def _apply_receipt(proposal: dict, rows: dict[str, list[dict]]) -> dict: + applied = copy.deepcopy(proposal) + applied.update( + { + "status": "applied", + "applied_by_handle": apply.SERVICE_AGENT_HANDLE, + "applied_by_agent_id": AGENT_ID, + "applied_at": APPLIED_AT, + } + ) + apply_sql = apply.build_apply_sql(proposal, apply.SERVICE_AGENT_HANDLE) + return apply.replay_receipt.build_replay_receipt( + applied, + copy.deepcopy(rows), + apply_sql_sha256=lifecycle.sha256_text(apply_sql), + apply_sql_source="exact_executed_sql", + exported_at_utc="2026-07-15T10:16:00+00:00", + ) + + +def _materials() -> tuple[dict, dict, dict, dict, str]: + proposal = _proposal_before() + approval = _approval_snapshot(proposal) + preflight = lifecycle.build_fresh_preflight( + proposal, + _empty_targets(), + captured_at_utc="2026-07-15T10:00:00+00:00", + ) + receipt = _apply_receipt(proposal, _canonical_rows()) + rollback_sql = lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, receipt) + return proposal, approval, preflight, receipt, rollback_sql + + +def _passing_lifecycle_receipt() -> dict: + proposal, approval, preflight, receipt, rollback_sql = _materials() + proposal_after = copy.deepcopy(proposal) + return lifecycle.build_lifecycle_receipt( + proposal_before=proposal, + approval_before=approval, + preflight=preflight, + apply_receipt=receipt, + rollback_sql=rollback_sql, + proposal_after=proposal_after, + approval_after=copy.deepcopy(approval), + target_rows_after=_empty_targets(), + counts_before={family: 7 + index for index, family in enumerate(ROW_FAMILIES)}, + counts_after={family: 7 + index for index, family in enumerate(ROW_FAMILIES)}, + unrelated_before={"fingerprint": "same", "row_count": 17}, + unrelated_after={"fingerprint": "same", "row_count": 17}, + rollback_replay_refused=True, + generated_at_utc="2026-07-15T10:30:00+00:00", + ) + + +def _destination(*, candidate_present: bool = True) -> dict: + lifecycle_receipt = _passing_lifecycle_receipt() + candidates = [] + if candidate_present: + candidates.append( + { + "id": PROPOSAL_ID, + "proposal_type": "approve_claim", + "status": "approved", + "applied_at": None, + "proposal_payload_sha256": lifecycle_receipt["proposal_payload_sha256"], + "apply_payload_sha256": lifecycle_receipt["apply_payload_sha256"], + "approval_snapshot_sha256": lifecycle_receipt["approval_snapshot_sha256"], + "reviewed_by_handle": "independent-reviewer", + "reviewed_by_agent_id": None, + "reviewed_at": REVIEWED_AT, + "review_note": "Approved this exact strict payload.", + "immutable_approval_exact": True, + } + ) + return { + "observed_at_utc": "2026-07-15T10:45:00+00:00", + "read_only_transaction": True, + "container": "teleo-pg", + "database": "teleo", + "system_identifier": "7649789040005668902", + "server_version_num": "160014", + "approval_gate": { + "immutable_approval_table_present": True, + "approve_function_present": True, + "assert_function_present": True, + "finish_function_present": True, + "review_role_present": True, + "apply_role_present": True, + }, + "approved_strict_candidates": candidates, + } + + +def _authorization_materials() -> tuple[dict, dict, dict, dict]: + proposal, approval, preflight, apply_receipt, _production_rollback_sql = _materials() + receipt = _passing_lifecycle_receipt() + destination = _destination() + destination_identity = { + key: destination[key] for key in ("container", "database", "system_identifier", "server_version_num") + } + production_rollback_artifact = lifecycle.build_production_rollback_artifact( + proposal_before=proposal, + approval_snapshot=approval, + preflight=preflight, + apply_receipt=apply_receipt, + destination=destination_identity, + generated_at_utc="2026-07-15T10:49:00+00:00", + ) + packet = lifecycle.build_authorization_packet( + receipt, + destination, + repo_git_sha=lifecycle.current_git_sha(REPO_ROOT), + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + generated_at_utc="2026-07-15T10:50:00+00:00", + ) + record = { + "authorized": True, + "rollback_authorized": True, + "binding_sha256": packet["binding_sha256"], + "production_rollback_sql_sha256": production_rollback_artifact["rollback_sql_sha256"], + "operator_identity": "release-operator@example.test", + "received_at_utc": "2026-07-15T11:00:00+00:00", + "expires_at_utc": "2026-07-15T13:00:00+00:00", + } + record["authorization_text"] = lifecycle.expected_authorization_text(packet, record) + destination["observed_at_utc"] = "2026-07-15T11:30:00+00:00" + return packet, record, destination, production_rollback_artifact + + +def test_fresh_preflight_requires_every_owned_target_to_be_absent_and_is_deterministic(): + proposal = _proposal_before() + first = lifecycle.build_fresh_preflight( + proposal, + _empty_targets(), + captured_at_utc="2026-07-15T10:00:00+00:00", + ) + second = lifecycle.build_fresh_preflight( + proposal, + _empty_targets(), + captured_at_utc="2026-07-15T10:01:00+00:00", + ) + + assert first["fresh_owned_targets"] is True + assert first["preflight_sha256"] == second["preflight_sha256"] + assert first["preflight_artifact_sha256"] != second["preflight_artifact_sha256"] + assert first["captured_at_utc"] != second["captured_at_utc"] + + nonfresh = _empty_targets() + nonfresh["claims"] = [{"id": CLAIM_A}] + with pytest.raises(ValueError, match="absent target rows"): + lifecycle.build_fresh_preflight(proposal, nonfresh) + + incomplete = _empty_targets() + incomplete.pop("claim_edges") + with pytest.raises(ValueError, match="every approve_claim target table"): + lifecycle.build_fresh_preflight(proposal, incomplete) + + +@pytest.mark.parametrize( + "mutation", + ( + "artifact", + "schema", + "captured_at_utc", + "proposal_id", + "proposal_payload_sha256", + "apply_payload_sha256", + "target_family_missing", + "target_row_present", + "fresh_flag_type", + "state_hash", + "artifact_hash", + "extra_field", + ), +) +def test_rollback_refuses_every_preflight_contract_mutation(mutation: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed = copy.deepcopy(preflight) + if mutation == "artifact": + changed["artifact"] = "proposal_apply_fresh_preflight_tampered" + elif mutation == "schema": + changed["schema"] = "livingip.proposalApplyFreshPreflight.v0" + elif mutation == "captured_at_utc": + changed["captured_at_utc"] = "2026-07-15T10:00:01+00:00" + elif mutation == "proposal_id": + changed["proposal_id"] = CLAIM_A + elif mutation == "proposal_payload_sha256": + changed["proposal_payload_sha256"] = "0" * 64 + elif mutation == "apply_payload_sha256": + changed["apply_payload_sha256"] = "0" * 64 + elif mutation == "target_family_missing": + changed["target_rows_before"].pop("claim_edges") + elif mutation == "target_row_present": + changed["target_rows_before"]["claims"] = [{"id": CLAIM_A}] + elif mutation == "fresh_flag_type": + changed["fresh_owned_targets"] = 1 + elif mutation == "state_hash": + changed["preflight_sha256"] = "0" * 64 + elif mutation == "artifact_hash": + changed["preflight_artifact_sha256"] = "0" * 64 + else: + changed["unexpected"] = True + + with pytest.raises(ValueError): + lifecycle.build_compensating_rollback_sql(proposal, approval, changed, receipt) + + +def test_compensating_rollback_is_atomic_deterministic_and_refuses_state_drift(): + proposal, approval, preflight, receipt, rollback_sql = _materials() + + assert rollback_sql == lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, receipt) + assert rollback_sql.startswith("begin;") + assert rollback_sql.rstrip().endswith("commit;") + assert "proposal rollback drift:" in rollback_sql + assert "to_jsonb(t)" in rollback_sql + assert rollback_sql.index("proposal rollback drift:") < rollback_sql.index("delete from public.claim_evidence") + delete_positions = [ + rollback_sql.index(f"delete from {lifecycle.ROW_TABLES[family]}") for family in lifecycle.DELETE_ORDER + ] + assert delete_positions == sorted(delete_positions) + assert "set status = 'approved'" in rollback_sql + assert "immutable approval snapshot drifted" in rollback_sql + + drifted_receipt = copy.deepcopy(receipt) + drifted_receipt["proposal"]["payload"]["apply_payload"]["claims"][0]["text"] = "drift" + with pytest.raises(ValueError, match="payload does not match preflight"): + lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, drifted_receipt) + + +@given( + suffix=st.text( + alphabet=string.ascii_letters + string.digits + " -_", + min_size=1, + max_size=32, + ) +) +@settings(max_examples=30, deadline=None) +def test_rollback_refuses_any_canonical_row_tampering_without_rebuilt_receipt(suffix: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed = copy.deepcopy(receipt) + changed["canonical_rows"]["claims"][0]["text"] = f"mutated:{suffix}" + + with pytest.raises(ValueError, match="replay-contract validation"): + lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, changed) + + +@pytest.mark.parametrize( + "mutation", + ("contract_bool", "expected_count_bool", "actual_count_float", "check_integer"), +) +def test_replay_receipt_json_types_are_exact_not_python_equal(mutation: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed = copy.deepcopy(receipt) + if mutation == "contract_bool": + changed["receipt_contract_version"] = True + elif mutation == "expected_count_bool": + changed["expected_row_counts"]["sources"] = True + elif mutation == "actual_count_float": + changed["actual_row_counts"]["sources"] = 1.0 + else: + changed["checks"]["proposal_applied"] = 1 + + with pytest.raises(ValueError, match="replay-contract validation"): + lifecycle.build_compensating_rollback_sql(proposal, approval, preflight, changed) + + +def test_rollback_sql_hash_binds_every_receipt_observed_field(): + proposal, approval, preflight, receipt, rollback_sql = _materials() + changed_rows = copy.deepcopy(receipt["canonical_rows"]) + changed_rows["claims"][0]["created_at"] = "2026-07-15T10:15:00.123456+00:00" + changed_receipt = _apply_receipt(proposal, changed_rows) + + changed_sql = lifecycle.build_compensating_rollback_sql( + proposal, + approval, + preflight, + changed_receipt, + ) + + assert lifecycle.sha256_text(changed_sql) != lifecycle.sha256_text(rollback_sql) + assert "to_jsonb(t)" in changed_sql + assert "proposal rollback drift: claims[0] row does not match apply receipt" in changed_sql + + +@pytest.mark.parametrize("family", ("claim_evidence", "claim_edges")) +def test_fresh_apply_receipt_requires_deterministic_generated_row_ids(family: str): + proposal, approval, preflight, receipt, _rollback_sql = _materials() + changed_rows = copy.deepcopy(receipt["canonical_rows"]) + changed_rows[family][0]["id"] = "eeeeeeee-eeee-4eee-8eee-eeeeeeeeeeee" + changed_receipt = _apply_receipt(proposal, changed_rows) + + with pytest.raises(ValueError, match="is not deterministic"): + lifecycle.build_compensating_rollback_sql( + proposal, + approval, + preflight, + changed_receipt, + ) + + +def test_production_rollback_artifact_reconstructs_exact_sql_and_rejects_noop_substitution(): + proposal, approval, preflight, apply_receipt, rollback_sql = _materials() + destination = { + "container": "teleo-pg", + "database": "teleo", + "system_identifier": "7649789040005668902", + "server_version_num": "160014", + } + artifact = lifecycle.build_production_rollback_artifact( + proposal_before=proposal, + approval_snapshot=approval, + preflight=preflight, + apply_receipt=apply_receipt, + destination=destination, + generated_at_utc="2026-07-15T10:49:00+00:00", + ) + + assert ( + lifecycle.validate_production_rollback_artifact( + artifact, + expected_destination=destination, + ) + == artifact + ) + assert artifact["rollback_sql"] == rollback_sql + + noop = copy.deepcopy(artifact) + noop["rollback_sql"] = ( + "begin; -- proposal rollback drift: -- immutable approval snapshot drifted " + "-- set status = 'approved'\ncommit;\n" + ) + noop["rollback_sql_sha256"] = lifecycle.sha256_text(noop["rollback_sql"]) + unsigned = dict(noop) + unsigned.pop("artifact_sha256") + noop["artifact_sha256"] = lifecycle.sha256_json(unsigned) + + with pytest.raises(ValueError, match="reconstructed exact SQL"): + lifecycle.validate_production_rollback_artifact(noop, expected_destination=destination) + + +def test_lifecycle_receipt_proves_inverse_and_has_self_consistent_hashes(): + receipt = _passing_lifecycle_receipt() + unsigned = dict(receipt) + receipt_hash = unsigned.pop("lifecycle_receipt_sha256") + + assert receipt["pass"] is True + assert receipt["current_tier"] == "T2_runtime" + assert all(receipt["checks"].values()) + assert all(not rows for rows in receipt["rollback"]["target_rows_after"].values()) + assert receipt["rollback"]["counts_after_rollback"] == receipt["rollback"]["counts_before_apply"] + assert receipt["production_apply_executed"] is False + assert receipt["production_apply_authorization_present"] is False + assert receipt_hash == lifecycle.sha256_json(unsigned) + + +def test_lifecycle_receipt_downgrades_when_unrelated_state_changes(): + proposal, approval, preflight, receipt, rollback_sql = _materials() + result = lifecycle.build_lifecycle_receipt( + proposal_before=proposal, + approval_before=approval, + preflight=preflight, + apply_receipt=receipt, + rollback_sql=rollback_sql, + proposal_after=copy.deepcopy(proposal), + approval_after=copy.deepcopy(approval), + target_rows_after=_empty_targets(), + counts_before={family: 1 for family in ROW_FAMILIES}, + counts_after={family: 1 for family in ROW_FAMILIES}, + unrelated_before={"fingerprint": "before"}, + unrelated_after={"fingerprint": "after"}, + rollback_replay_refused=True, + ) + + assert result["pass"] is False + assert result["current_tier"] == "T1_model" + assert result["checks"]["unrelated_rows_unchanged"] is False + + +def test_retained_current_lifecycle_evidence_is_self_consistent(): + report_dir = REPO_ROOT / "docs" / "reports" / "leo-working-state-20260709" + canary = json.loads((report_dir / "proposal-apply-lifecycle-canary-current.json").read_text(encoding="utf-8")) + receipt = json.loads((report_dir / "proposal-apply-lifecycle-receipt-current.json").read_text(encoding="utf-8")) + rollback = (report_dir / "proposal-apply-clone-rollback-current.sql").read_text(encoding="utf-8") + unsigned = dict(receipt) + receipt_sha256 = unsigned.pop("lifecycle_receipt_sha256") + + assert canary["status"] == "pass" + assert canary["current_tier"] == "T2_runtime" + assert canary["applied_rollback_receipt"] == receipt + assert canary["outer_isolation"]["cleanup_readback"]["label_scoped_orphan_count"] == 0 + assert all(delta == 0 for delta in canary["outer_isolation"]["canonical_table_deltas"].values()) + assert receipt["pass"] is True + assert receipt["production_apply_executed"] is False + assert receipt_sha256 == lifecycle.sha256_json(unsigned) + assert hashlib.sha256(rollback.encode("utf-8")).hexdigest() == receipt["rollback"]["rollback_sql_sha256"] + + +def test_authorization_packet_is_exact_but_never_self_authorizes_production(): + packet, record, destination, production_rollback_artifact = _authorization_materials() + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert packet["required_tier"] == "T6_authorized_production" + assert packet["current_tier"] == "T3_live_readonly" + assert packet["safe_to_enter_apply_window"] is False + assert packet["production_apply_authorization_present"] is False + assert packet["production_apply_executed"] is False + assert verification["safe_to_enter_apply_window"] is True + assert verification["production_apply_executed"] is False + assert all(verification["checks"].values()) + + +def test_authorization_packet_names_missing_live_gate_before_requesting_auth(tmp_path): + receipt = _passing_lifecycle_receipt() + destination = _destination(candidate_present=False) + destination["approval_gate"] = {key: False for key in destination["approval_gate"]} + packet = lifecycle.build_authorization_packet( + receipt, + destination, + repo_git_sha=lifecycle.current_git_sha(REPO_ROOT), + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + ) + markdown_path = tmp_path / "authorization.md" + lifecycle.write_authorization_markdown(markdown_path, packet) + + assert packet["production_preconditions"]["approval_gate_ready"] is False + assert packet["production_preconditions"]["matching_candidate_count"] == 0 + assert packet["production_preconditions"]["exact_production_rollback_packet_ready"] is False + assert packet["binding"]["production_rollback"]["rollback_sql_sha256"] is None + assert lifecycle.sha256_json(packet["binding"]) == packet["binding_sha256"] + assert packet["safe_to_enter_apply_window"] is False + assert packet["production_apply_authorization_present"] is False + assert packet["production_apply_executed"] is False + assert packet["next_exact_action"].startswith("deploy and independently verify") + assert "This text is not actionable" in markdown_path.read_text(encoding="utf-8") + + +@given(value=st.sampled_from([False, None, 0, 1, "true", "yes", [], {}])) +@settings(max_examples=8, deadline=None) +def test_authorized_field_requires_the_exact_boolean_true(value): + packet, record, destination, production_rollback_artifact = _authorization_materials() + record["authorized"] = value + record["authorization_text"] = lifecycle.expected_authorization_text(packet, record) + + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert verification["checks"]["authorized_is_exact_true"] is False + assert verification["safe_to_enter_apply_window"] is False + + +AUTHORIZATION_MUTATIONS = ( + "packet_schema", + "packet_readonly", + "approval_gate_packet", + "production_rollback_packet", + "strict_candidate", + "rollback_authorized", + "production_rollback_sha256", + "binding_sha256", + "operator_identity", + "authorization_window", + "authorization_text", + "destination_container", + "destination_database", + "destination_system_identifier", + "destination_server_version", + "approval_gate_action", + "strict_candidate_action", + "apply_engine_sha256", + "approval_gate_sha256", + "binding_proposal", + "binding_gate_false", + "binding_preconditions", + "packet_safe_flag", + "scope_exclusion", + "authorization_template", + "record_extra_field", + "action_readback_stale", + "production_rollback_binding", +) + + +@given(mutation=st.sampled_from(AUTHORIZATION_MUTATIONS)) +@settings(max_examples=len(AUTHORIZATION_MUTATIONS), deadline=None) +def test_every_authorization_binding_field_fails_closed_when_mutated(mutation: str): + packet, record, destination, production_rollback_artifact = _authorization_materials() + expected_failed_check = { + "packet_schema": "packet_schema_exact", + "packet_readonly": "packet_destination_readback_was_read_only", + "approval_gate_packet": "approval_gate_ready_at_packet", + "production_rollback_packet": "exact_production_rollback_packet_ready", + "strict_candidate": "strict_proposal_present_and_approved", + "rollback_authorized": "rollback_authorized_is_exact_true", + "production_rollback_sha256": "production_rollback_sql_sha256_exact", + "binding_sha256": "binding_sha256_exact", + "operator_identity": "operator_identity_present", + "authorization_window": "authorization_window_valid", + "authorization_text": "authorization_text_exact", + "destination_container": "destination_identity_exact", + "destination_database": "destination_identity_exact", + "destination_system_identifier": "destination_identity_exact", + "destination_server_version": "destination_identity_exact", + "approval_gate_action": "approval_gate_identity_exact_at_action", + "strict_candidate_action": "strict_proposal_exact_at_action", + "apply_engine_sha256": "apply_engine_sha256_exact", + "approval_gate_sha256": "approval_gate_prerequisite_sha256_exact", + "binding_proposal": "packet_binding_sha256_self_consistent", + "binding_gate_false": "approval_gate_ready_at_packet", + "binding_preconditions": "packet_preconditions_bound_exactly", + "packet_safe_flag": "packet_never_self_authorizes_or_expands_scope", + "scope_exclusion": "packet_never_self_authorizes_or_expands_scope", + "authorization_template": "authorization_text_template_exact", + "record_extra_field": "authorization_record_contract_exact", + "action_readback_stale": "destination_readback_fresh_and_read_only_at_action", + "production_rollback_binding": "production_rollback_sql_sha256_exact", + }[mutation] + + if mutation == "packet_schema": + packet["schema"] = "livingip.proposalApplyAuthorizationPacket.v0" + elif mutation == "packet_readonly": + packet["production_preconditions"]["destination_readback_was_read_only"] = False + elif mutation == "approval_gate_packet": + packet["production_preconditions"]["approval_gate_ready"] = False + elif mutation == "production_rollback_packet": + packet["production_preconditions"]["exact_production_rollback_packet_ready"] = False + elif mutation == "strict_candidate": + packet["production_preconditions"]["strict_proposal_present_and_approved"] = False + elif mutation == "rollback_authorized": + record["rollback_authorized"] = False + elif mutation == "production_rollback_sha256": + record["production_rollback_sql_sha256"] = "f" * 64 + elif mutation == "binding_sha256": + record["binding_sha256"] = "0" * 64 + elif mutation == "operator_identity": + record["operator_identity"] = " " + elif mutation == "authorization_window": + record["expires_at_utc"] = "2026-07-15T11:30:00+00:00" + elif mutation == "authorization_text": + record["authorization_text"] += " altered" + elif mutation.startswith("destination_"): + key = { + "destination_container": "container", + "destination_database": "database", + "destination_system_identifier": "system_identifier", + "destination_server_version": "server_version_num", + }[mutation] + destination[key] = f"wrong-{key}" + elif mutation == "approval_gate_action": + destination["approval_gate"]["review_role_present"] = False + elif mutation == "strict_candidate_action": + destination["approved_strict_candidates"] = [] + elif mutation == "apply_engine_sha256": + packet["binding"]["engine"]["apply_engine_sha256"] = "0" * 64 + elif mutation == "approval_gate_sha256": + packet["binding"]["approval_gate_dependency"]["sha256"] = "0" * 64 + elif mutation == "binding_proposal": + packet["binding"]["proposal"]["id"] = CLAIM_A + elif mutation == "binding_gate_false": + packet["binding"]["approval_gate"]["review_role_present"] = False + elif mutation == "binding_preconditions": + packet["binding"]["production_preconditions"]["approval_gate_ready"] = False + elif mutation == "packet_safe_flag": + packet["safe_to_enter_apply_window"] = True + elif mutation == "scope_exclusion": + packet["scope_exclusions"]["telegram_send_authorized"] = True + elif mutation == "authorization_template": + packet["authorization_text_template"] += " Also authorize all other mutations." + elif mutation == "record_extra_field": + record["unexpected"] = True + elif mutation == "action_readback_stale": + destination["observed_at_utc"] = "2026-07-15T10:59:59+00:00" + elif mutation == "production_rollback_binding": + packet["binding"]["production_rollback"]["rollback_sql_sha256"] = "f" * 64 + + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert verification["checks"][expected_failed_check] is False + assert verification["safe_to_enter_apply_window"] is False + assert verification["production_apply_authorization_present"] is False + + +def test_false_bound_gate_cannot_pass_even_after_packet_and_record_are_rehashed(): + packet, record, destination, production_rollback_artifact = _authorization_materials() + packet["binding"]["approval_gate"]["review_role_present"] = False + destination["approval_gate"]["review_role_present"] = False + packet["binding_sha256"] = lifecycle.sha256_json(packet["binding"]) + packet["authorization_record_required"]["binding_sha256"] = packet["binding_sha256"] + packet["authorization_text_template"] = lifecycle._authorization_text_template( + packet["binding"], + packet["binding_sha256"], + ) + record["binding_sha256"] = packet["binding_sha256"] + record["authorization_text"] = lifecycle.expected_authorization_text(packet, record) + + verification = lifecycle.verify_authorization_record( + packet, + record, + observed_destination=destination, + apply_engine_path=REPO_ROOT / "scripts" / "apply_proposal.py", + production_rollback_artifact=production_rollback_artifact, + now=datetime(2026, 7, 15, 12, 0, tzinfo=timezone.utc), + ) + + assert verification["checks"]["packet_binding_sha256_self_consistent"] is True + assert verification["checks"]["approval_gate_ready_at_packet"] is False + assert verification["safe_to_enter_apply_window"] is False + + +def test_authorization_packet_uses_repo_relative_dependency_paths_only(): + packet, _record, _destination, _production_rollback_artifact = _authorization_materials() + encoded = json.dumps(packet, sort_keys=True) + + assert packet["binding"]["engine"]["apply_engine_path"] == "scripts/apply_proposal.py" + assert packet["binding"]["approval_gate_dependency"]["path"] == "scripts/kb_apply_prereqs.sql" + assert "/private/tmp" not in encoded + assert "/tmp" not in encoded diff --git a/tests/test_run_approve_claim_clone_canary_macos_path.py b/tests/test_run_approve_claim_clone_canary_macos_path.py index ad472db..ad238a9 100644 --- a/tests/test_run_approve_claim_clone_canary_macos_path.py +++ b/tests/test_run_approve_claim_clone_canary_macos_path.py @@ -2,6 +2,8 @@ from __future__ import annotations +import hashlib +import json import subprocess import sys from pathlib import Path @@ -11,8 +13,10 @@ import pytest ROOT = Path(__file__).resolve().parents[1] sys.path.insert(0, str(ROOT / "scripts")) WRAPPER = ROOT / "scripts" / "run_approve_claim_isolated_container_canary.sh" +FINALIZER = ROOT / "scripts" / "finalize_approve_claim_isolated_canary.py" import apply_proposal as apply # noqa: E402 +import finalize_approve_claim_isolated_canary as finalizer # noqa: E402 import run_approve_claim_clone_canary as canary # noqa: E402 @@ -24,9 +28,7 @@ def test_role_psql_resolves_docker_before_scrubbing_path( monkeypatch.setattr( canary.shutil, "which", - lambda executable: "/opt/homebrew/bin/docker" - if executable == "docker" - else None, + lambda executable: "/opt/homebrew/bin/docker" if executable == "docker" else None, ) def fake_run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]: @@ -58,9 +60,7 @@ def test_apply_tool_resolves_docker_before_scrubbing_path( monkeypatch.setattr( apply.shutil, "which", - lambda executable: "/opt/homebrew/bin/docker" - if executable == "docker" - else None, + lambda executable: "/opt/homebrew/bin/docker" if executable == "docker" else None, ) def fake_run(command: list[str], **kwargs: object) -> subprocess.CompletedProcess[str]: @@ -89,14 +89,76 @@ def test_apply_tool_resolves_docker_before_scrubbing_path( def test_outer_wrapper_enforces_local_source_and_networkless_tmpfs() -> None: - text = WRAPPER.read_text(encoding="utf-8") + wrapper = WRAPPER.read_text(encoding="utf-8") + finalizer = FINALIZER.read_text(encoding="utf-8") + text = wrapper + "\n" + finalizer assert 'source_canary_label" != "working-leo-approved-source"' in text assert "--network none" in text assert "--tmpfs /var/lib/postgresql/data:rw,nosuid,size=512m" in text assert 'CANARY_NETWORK_MODE="$container_network_mode"' in text assert 'CANARY_MOUNTS_JSON="$container_mounts_json"' in text - assert 'data["checks"]["outer_source_tier_enforced"]' in text - assert 'data["checks"]["outer_container_network_disabled"]' in text - assert 'data["checks"]["outer_container_has_no_docker_volumes"]' in text + assert 'checks["outer_source_tier_enforced"]' in text + assert 'checks["outer_container_network_disabled"]' in text + assert 'checks["outer_container_has_no_docker_volumes"]' in text assert 'source_tier != "live-readonly" or service_observable' in text + assert "<<'PY'" not in wrapper + assert "finalize_approve_claim_isolated_canary.py" in wrapper + + +def test_outer_finalizer_runs_from_a_normal_file_without_heredoc(tmp_path: Path) -> None: + content = FINALIZER.read_bytes() + report = tmp_path / "canary.json" + report.write_text( + json.dumps( + { + "status": "pass", + "checks": {}, + "source_binding": { + "unchanged_during_run": True, + "files": [ + { + "repo_relative_path": FINALIZER.relative_to(ROOT).as_posix(), + "sha256": hashlib.sha256(content).hexdigest(), + "size_bytes": len(content), + } + ], + }, + } + ), + encoding="utf-8", + ) + counts = json.dumps({"public.claims": 2, "kb_stage.kb_proposals": 0}) + data = finalizer.finalize( + report, + { + "SOURCE_COUNTS_BEFORE": counts, + "SOURCE_COUNTS_AFTER": counts, + "SOURCE_CONTAINER": "isolated-source", + "SOURCE_DB": "teleo", + "SERVICE_BEFORE": "Available=false\nReason=systemctl_unavailable\n", + "SERVICE_AFTER": "Available=false\nReason=systemctl_unavailable\n", + "REPO_ROOT": str(ROOT), + "OUTER_CONTAINER_REMOVE_RC": "0", + "OUTER_WORKDIR_REMOVE_RC": "0", + "CONTAINER_NAME_READBACK": "", + "CONTAINER_NAME_READBACK_RC": "0", + "CONTAINER_LABEL_READBACK": "", + "CONTAINER_LABEL_READBACK_RC": "0", + "CONTAINER_LABEL_ORPHAN_COUNT": "0", + "OUTER_WORKDIR_LEXISTS": "false", + "CANARY_RC": "0", + "SOURCE_TIER": "isolated-local", + "SOURCE_NETWORK_MODE": "none", + "SOURCE_CANARY_LABEL": "working-leo-approved-source", + "CANARY_NETWORK_MODE": "none", + "CANARY_MOUNTS_JSON": "[]", + "CANARY_LABEL": "proposal-apply-lifecycle", + "CANARY_INSTANCE_LABEL": "working-leo-approve-claim-test", + }, + ) + + assert data["status"] == "pass" + assert data["current_tier"] == "T2_runtime" + assert data["checks"]["outer_isolation_complete"] is True + assert data["outer_isolation"]["cleanup_readback"]["label_scoped_orphan_count"] == 0 From bd3c17628c6cf7da734be01149bbf1cc77a6d71b Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Wed, 15 Jul 2026 04:01:29 +0200 Subject: [PATCH 03/18] docs: retain production apply authorization boundary --- ...oduction-authorization-packet-current.json | 260 ++++++++++++++++++ ...production-authorization-packet-current.md | 62 +++++ tests/test_proposal_apply_lifecycle.py | 23 ++ 3 files changed, 345 insertions(+) create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json create mode 100644 docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json new file mode 100644 index 0000000..44db097 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json @@ -0,0 +1,260 @@ +{ + "artifact": "proposal_apply_production_authorization_packet", + "authorization_record_required": { + "authorization_text": "", + "authorized": true, + "binding_sha256": "e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345", + "expires_at_utc": "", + "operator_identity": "", + "production_rollback_sql_sha256": "", + "received_at_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 e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345. I also authorize conditional production rollback SHA-256 only if the bound postflight fails and no downstream dependency exists. Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. Operator ; received ; expires .", + "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-15T00:45:59.238006+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": "db64500a12a64206e1bb7fd0c9a0a21bca755c3d", + "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-15T00:45:59.238006+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": "e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345", + "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:00:26.190995+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-15T00:45:59.238006+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 + } +} diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md new file mode 100644 index 0000000..0cb4d42 --- /dev/null +++ b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md @@ -0,0 +1,62 @@ +# Proposal Apply Production Authorization Packet + +Generated UTC: `2026-07-15T02:00:26.190995+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: `e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345` + +## Production Preconditions + +- `approval_gate_ready`: `False` +- `destination_readback_observed_at_utc`: `2026-07-15T00:45:59.238006+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 e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345. I also authorize conditional production rollback SHA-256 only if the bound postflight fails and no downstream dependency exists. Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. Operator ; received ; expires . + +## 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 diff --git a/tests/test_proposal_apply_lifecycle.py b/tests/test_proposal_apply_lifecycle.py index 370dde1..916f32f 100644 --- a/tests/test_proposal_apply_lifecycle.py +++ b/tests/test_proposal_apply_lifecycle.py @@ -605,6 +605,29 @@ def test_retained_current_lifecycle_evidence_is_self_consistent(): assert hashlib.sha256(rollback.encode("utf-8")).hexdigest() == receipt["rollback"]["rollback_sql_sha256"] +def test_retained_production_authorization_packet_is_exact_and_unexecuted(): + packet_path = ( + REPO_ROOT + / "docs" + / "reports" + / "leo-working-state-20260709" + / "proposal-apply-production-authorization-packet-current.json" + ) + packet = json.loads(packet_path.read_text(encoding="utf-8")) + + assert packet["binding_sha256"] == lifecycle.sha256_json(packet["binding"]) + assert packet["binding"]["engine"]["apply_engine_path"] == "scripts/apply_proposal.py" + assert packet["binding"]["approval_gate_dependency"]["path"] == "scripts/kb_apply_prereqs.sql" + assert packet["production_preconditions"]["approval_gate_ready"] is False + assert packet["production_preconditions"]["strict_proposal_present_and_approved"] is False + assert packet["production_preconditions"]["exact_production_rollback_packet_ready"] is False + assert packet["safe_to_enter_apply_window"] is False + assert packet["production_apply_authorization_present"] is False + assert packet["production_apply_executed"] is False + assert all(value is False for value in packet["scope_exclusions"].values()) + assert "/private/tmp" not in json.dumps(packet, sort_keys=True) + + def test_authorization_packet_is_exact_but_never_self_authorizes_production(): packet, record, destination, production_rollback_artifact = _authorization_materials() verification = lifecycle.verify_authorization_record( From e9f208ef96de65575ccf3f5741d43934462a79b4 Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Wed, 15 Jul 2026 04:08:35 +0200 Subject: [PATCH 04/18] docs: refresh production apply non-change receipt --- ...-production-authorization-packet-current.json | 16 ++++++++-------- ...ly-production-authorization-packet-current.md | 8 ++++---- ...apply-production-target-readback-current.json | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json index 44db097..8245c9e 100644 --- a/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json +++ b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.json @@ -3,14 +3,14 @@ "authorization_record_required": { "authorization_text": "", "authorized": true, - "binding_sha256": "e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345", + "binding_sha256": "6f1984f3a69ad049d4bbcc408ed2542dc5f081806ec9baa9a35c8959e5dab62a", "expires_at_utc": "", "operator_identity": "", "production_rollback_sql_sha256": "", "received_at_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 e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345. I also authorize conditional production rollback SHA-256 only if the bound postflight fails and no downstream dependency exists. Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. Operator ; received ; expires .", + "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 only if the bound postflight fails and no downstream dependency exists. Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. Operator ; received ; expires .", "binding": { "approval_gate": { "apply_role_present": true, @@ -36,14 +36,14 @@ "system_identifier": "7649789040005668902" }, "destination_readback": { - "observed_at_utc": "2026-07-15T00:45:59.238006+00:00", + "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": "db64500a12a64206e1bb7fd0c9a0a21bca755c3d", + "repo_git_sha": "bd3c17628c6cf7da734be01149bbf1cc77a6d71b", "runtime_manifest": { "scripts/apply_proposal.py": "ca2c0a0c1031393c95a26102d57339654d2363f4c51cdf39aabb9071d0ca6c26", "scripts/kb_apply_prereqs.sql": "bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa", @@ -95,7 +95,7 @@ }, "production_preconditions": { "approval_gate_ready": false, - "destination_readback_observed_at_utc": "2026-07-15T00:45:59.238006+00:00", + "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, @@ -221,16 +221,16 @@ "scope": "disposable_clone_rehearsal" } }, - "binding_sha256": "e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345", + "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:00:26.190995+00:00", + "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-15T00:45:59.238006+00:00", + "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, diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md index 0cb4d42..64d8a01 100644 --- a/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md +++ b/docs/reports/leo-working-state-20260709/proposal-apply-production-authorization-packet-current.md @@ -1,6 +1,6 @@ # Proposal Apply Production Authorization Packet -Generated UTC: `2026-07-15T02:00:26.190995+00:00` +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` @@ -23,12 +23,12 @@ Production apply executed: `False` - Production rollback SQL SHA-256: `None` - Approval gate prerequisite SHA-256: `bdf8c5da05eed10665b05ad1e331e3c9c8d72464c59889a3cf8f34ab23822faa` - Approval gate prerequisite path: `scripts/kb_apply_prereqs.sql` -- Binding SHA-256: `e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345` +- Binding SHA-256: `6f1984f3a69ad049d4bbcc408ed2542dc5f081806ec9baa9a35c8959e5dab62a` ## Production Preconditions - `approval_gate_ready`: `False` -- `destination_readback_observed_at_utc`: `2026-07-15T00:45:59.238006+00:00` +- `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` @@ -49,7 +49,7 @@ Production apply executed: `False` 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 e6f38b0b2925f60a83a3210325ab3b35864185638ed173aeca7cc45352e5c345. I also authorize conditional production rollback SHA-256 only if the bound postflight fails and no downstream dependency exists. Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. Operator ; received ; expires . +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 only if the bound postflight fails and no downstream dependency exists. Do not send Telegram, mutate another proposal, expose Cloud SQL, or promote GCP. Operator ; received ; expires . ## Current Gate diff --git a/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json b/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json index c56352f..15d34a9 100644 --- a/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json +++ b/docs/reports/leo-working-state-20260709/proposal-apply-production-target-readback-current.json @@ -1,7 +1,7 @@ { "artifact": "proposal_apply_production_target_readback", "schema": "livingip.proposalApplyProductionTargetReadback.v1", - "observed_at_utc": "2026-07-15T00:45:59.238006+00:00", + "observed_at_utc": "2026-07-15T02:07:27.133665+00:00", "transport": "ssh_readonly_transaction", "read_only_transaction": true, "read_only_setting": "on", From 3afd1dbb5ea5cbfd04f92d4cdcb8e8cfa1ee6718 Mon Sep 17 00:00:00 2001 From: twentyOne2x Date: Wed, 15 Jul 2026 03:17:38 +0200 Subject: [PATCH 05/18] feat: reproduce Leo identity across disposable restarts --- docs/leo-reproducible-identity.md | 206 ++++ .../hermes-runtime/agent/prompt_builder.py | 3 + .../hermes-runtime/gateway/run.py | 3 + .../hermes-runtime/hermes_cli/tools_config.py | 3 + .../hermes-runtime/run_agent.py | 3 + .../hermes-runtime/tools/registry.py | 3 + .../leo-identity-v1/leo-constitution-v1.json | 18 + .../leo-database-fingerprint-v1.json | 25 + .../leo-database-identity-v1.json | 58 ++ .../profile-template/bin/identity_readback.py | 5 + .../profile-template/config.yaml | 24 + .../plugins/identity-boundary/__init__.py | 4 + .../plugins/identity-boundary/plugin.yaml | 3 + .../skills/identity-readback/SKILL.md | 8 + scripts/leo_behavior_manifest.py | 309 +++++- scripts/leo_identity_manifest.py | 878 ++++++++++++++++++ scripts/leo_identity_profile.py | 495 ++++++++++ .../run_leo_identity_reconstruction_canary.py | 439 +++++++++ tests/test_leo_behavior_manifest.py | 68 ++ tests/test_leo_identity_manifest.py | 721 ++++++++++++++ 20 files changed, 3259 insertions(+), 17 deletions(-) create mode 100644 docs/leo-reproducible-identity.md create mode 100644 fixtures/working-leo/leo-identity-v1/hermes-runtime/agent/prompt_builder.py create mode 100644 fixtures/working-leo/leo-identity-v1/hermes-runtime/gateway/run.py create mode 100644 fixtures/working-leo/leo-identity-v1/hermes-runtime/hermes_cli/tools_config.py create mode 100644 fixtures/working-leo/leo-identity-v1/hermes-runtime/run_agent.py create mode 100644 fixtures/working-leo/leo-identity-v1/hermes-runtime/tools/registry.py create mode 100644 fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json create mode 100644 fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json create mode 100644 fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json create mode 100644 fixtures/working-leo/leo-identity-v1/profile-template/bin/identity_readback.py create mode 100644 fixtures/working-leo/leo-identity-v1/profile-template/config.yaml create mode 100644 fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/__init__.py create mode 100644 fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/plugin.yaml create mode 100644 fixtures/working-leo/leo-identity-v1/profile-template/skills/identity-readback/SKILL.md create mode 100644 scripts/leo_identity_manifest.py create mode 100644 scripts/leo_identity_profile.py create mode 100644 scripts/run_leo_identity_reconstruction_canary.py create mode 100644 tests/test_leo_identity_manifest.py diff --git a/docs/leo-reproducible-identity.md b/docs/leo-reproducible-identity.md new file mode 100644 index 0000000..ff49a67 --- /dev/null +++ b/docs/leo-reproducible-identity.md @@ -0,0 +1,206 @@ +# Reproducible Leo identity + +## Definition of Working + +- **Working target:** generate one pinned Leo identity manifest, compile a + disposable profile, answer the fixed identity query in a fresh process, + restart in a second process, and reject any drift before an answer. +- **Operator path:** run + `python -m scripts.run_leo_identity_reconstruction_canary` with the committed + identity fixtures from a clean checkout. +- **Done means:** the T2 receipt reports two distinct stopped processes with the + same manifest, identity inputs, query, answer hash, and output provenance; + the second process starts from a newly compiled empty profile; the drift + attempt exits before answering; every process group and temporary profile is + removed. +- **Not done:** a hand-edited `SOUL.md`, a manifest self-hash, unit tests without + process restart, or prior VPS restart evidence that did not reconstruct from + this manifest. +- **Required tier:** `T2_runtime`. T3 VPS restart/readback is a post-merge + graduation target. + +## Identity input inventory + +`GatewayRunner` and the surrounding Hermes profile can change an answer through +the following surfaces. The manifest either pins each surface or explicitly +keeps it outside identity authority. + +| Surface | Binding | Authority | +| --- | --- | --- | +| Model provider, route, limits, and reasoning settings | secret-free semantic config hash | runtime input; the actual model remains a per-turn receipt | +| Hosted model weights | provider-managed, explicitly not locally hashable | never claimed as a local hash | +| Hermes and Teleo code | clean Git commits plus executable source-tree hashes | runtime input | +| Python ABI and imported runtime packages | exact implementation/version and curated package versions | runtime input | +| Skills, plugins, and database tools | content hashes | runtime input | +| Gateway/tool permissions | strict allowlisted config hash plus exact no-send/read-only policy | runtime input; this local compiler does not claim an authorizer readback | +| Static constitutional rules | committed source path, byte hash, and semantic hash | static authority | +| Database snapshot version | database/user/system identity plus content, row, structure, and capture-provenance hashes; only WAL position is excluded | T2 pins a noncanonical fixture; it cannot grant canonical authority | +| Database-derived identity rows | typed table/row/kind/rank/evidence bindings plus source mode | synthetic fixtures are explicitly noncanonical; T3 requires independently verified database-exporter output | +| `SOUL.md` and `identity.json` | deterministic compiled-view hashes | generated views, never authorities | +| Sessions, state, and memory | excluded from the identity input hash and labeled temporary | continuity only; never evidence | + +The disposable profile is compiled from a strict non-secret configuration +allowlist; unrecognized transport/credential fields are not copied. Credential +values are omitted rather than hashed. Rotating a bot token changes the broad +operational behavior observation but does not change Leo's identity. +Changing a non-secret permission, model setting, skill, code file, database +fingerprint, constitutional rule, database identity row, or compiled view does +change a pinned input and fails closed. + +### Current `GatewayRunner` consumption map + +The T2 compiler contract was checked against the current live runtime source. +This inventory defines what the post-merge T3 receipt must observe rather than +silently assuming that a profile file is the whole prompt: + +- Startup resolves `-p leoclean` to `HERMES_HOME`, then loads profile and project + environment files before `config.yaml`; environment expansion, legacy + `gateway.json`, and defaults can change the effective configuration. The T2 + compiler therefore enforces an exact top-level profile allowlist (including + rejection of dangling symlinks) rather than relying on a filename denylist. +- Routing consumes the requested default model, the top-level + `smart_model_routing` switch, provider endpoints/defaults, auth pool choice, + reasoning settings, token/turn limits, and fallbacks. The pinned top-level + routing switch must be a boolean; arbitrary nested routing data is rejected. + Credentials are runtime capability, not identity, and their values are never + retained. +- Prompt construction consumes generated `SOUL.md`, the gateway/agent system + message, persistent `MEMORY.md`/`USER.md`, compiled skills, project-context + files, current time/timezone, and the effective model/provider. T2 requires + memory and project context absent; T3 records the effective system-prompt and + compiled-skills-prompt hashes. +- Plugin discovery can include profile plugins, optional project plugins, and + installed entry points. The live database-context hook can inject a + query-bound contract before the model and can reject or replace the reply + afterward, so T3 retains plugin registry, contract, retrieval, and delivered + response hashes. +- Effective tools come from platform toolsets and the runtime registry, not a + descriptive fixture field. T3 must read back exact tool schemas, prove the + send tool absent, and keep terminal restricted to the clone-bound read-only + wrapper. +- Authorization consumes chat/user allowlists and pairing-store state. T2 starts + with pairing absent; T3 records the fixed synthetic source and the actual + authorization decision without exposing IDs or tokens. +- Session keys consume platform/chat/user/thread/topic fields. Auto-skill, + reply/media/file context, persisted transcripts, and a reused system prompt + can all change a turn. Verification therefore happens before every child + starts, and T3 binds session key, persisted session ID, message-context + absence, and prompt hashes. + +The committed T2 database/identity inputs are synthetic fixtures and the +compiled view labels them `synthetic_noncanonical_fixture`; they do not stand +in for approved production rows. This T2 schema never grants canonical +authority from caller-supplied or merely self-hashed JSON. T3 must use output +that is independently verified by the database-owning same-transaction exporter +and bound to the database fingerprint, database user, system identifier, and +row digest. + +Every pinned source tree and profile component is structurally revalidated: +the verifier recomputes its file count, total bytes, sorted unique paths, and +canonical content hash, and rejects missing files, unreadable entries, or +symlinks. Rehashing forged structural metadata cannot make it authoritative. + +The current live Hermes checkout is not clean, so its Git SHA alone is not a +reproducible source bundle. This T2 receipt intentionally uses the committed +clean local runtime and the committed database/identity snapshot fixture. It +does not claim to reconstruct the current dirty VPS runtime. T3 must run only +after the merged identity code is deployed and must bind the deployed clean +content (or a content-addressed dirty-source bundle if the live checkout has not +yet been repaired). + +## Commands + +The complete canary is the preferred operator command: + +```bash +python -m scripts.run_leo_identity_reconstruction_canary \ + --profile-template fixtures/working-leo/leo-identity-v1/profile-template \ + --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ + --deployment-root . \ + --source-root . \ + --database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \ + --constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \ + --database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \ + --output docs/reports/leo-working-state-20260709/leo-reproducible-identity-t2-current.json +``` + +The negative lifecycle is separately runnable. It proves drift is caught before +the second process starts: + +```bash +python -m scripts.run_leo_identity_reconstruction_canary \ + --profile-template fixtures/working-leo/leo-identity-v1/profile-template \ + --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ + --deployment-root . \ + --source-root . \ + --database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \ + --constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \ + --database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \ + --inject-drift-before-restart \ + --output /tmp/leo-identity-drift-rejection.json +``` + +For inspection, the lifecycle can be decomposed into explicit manifest and +compile commands: + +```bash +python -m scripts.leo_behavior_manifest \ + --profile fixtures/working-leo/leo-identity-v1/profile-template \ + --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ + --deployment-root . \ + --output /tmp/leo-behavior.json + +python -m scripts.leo_identity_manifest generate \ + --behavior-manifest /tmp/leo-behavior.json \ + --database-fingerprint fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json \ + --constitution fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json \ + --database-identity fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json \ + --source-root . \ + --output /tmp/leo-identity-manifest.json + +python -m scripts.leo_identity_profile compile \ + --manifest /tmp/leo-identity-manifest.json \ + --source-root . \ + --profile-template fixtures/working-leo/leo-identity-v1/profile-template \ + --profile /tmp/leo-disposable-profile \ + --hermes-root fixtures/working-leo/leo-identity-v1/hermes-runtime \ + --deployment-root . +``` + +All commands require a clean Git worktree because a commit plus an unretained +dirty source tree is not reproducible. + +## State inventory and transitions + +| State | Meaning | Next valid transition | +| --- | --- | --- | +| `inputs_unverified` | source paths exist but are not yet bound | validate clean Git, runtime, DB, rules, rows, and permissions | +| `manifest_pinned` | every material identity input has a stable hash | compile deterministic views | +| `profile_compiled` | static profile copied and generated views match the manifest | verify immediately before start | +| `runtime_verified` | runtime/code/view hashes match and session state is non-authoritative | answer fixed query | +| `stopped_cleanly` | child and process group are absent | compile a new empty profile from the manifest | +| `restart_reproduced` | child from the newly compiled profile has the same identity/query/provenance inputs | retain receipt and clean every profile | +| `drift_detected` | any input or generated view differs | fail closed; no answer and no next start | + +The irreversible boundary is not a database or production mutation: this T2 +canary is a local compiler/process runtime, no-send, database-read-only, and +disposable. The successful-restart receipt reaches `T2_runtime`; the standalone +pre-restart drift receipt is a passing negative component but reports +`T1_model` and `goal_tier_satisfied=false`. Neither proves the live VPS +`GatewayRunner`, Telegram delivery, hosted-model behavior, production database +state, or T3 restart recovery. + +Here `T2_runtime` uses the required Capability Tier Proof vocabulary: local +runtime behavior with restart. It does not imply Hermes/GatewayRunner or a +hosted model; those exclusions are explicit in the receipt's `runtime_variant`, +`tier_basis`, and `claim_ceiling`. + +## T3 graduation + +After merge and rollback proof, extend the schema with independent verification +of the database-owning same-transaction exporter, generate the manifest from +that live read-only canonical capture and deployed clean commits, compile a +disposable VPS profile, invoke the real no-send `GatewayRunner`, terminate that +child, open a new child from the same manifest, and retain model-call, DB-read, +service, cleanup, and drift-negative readbacks. Do not replace the production +profile during that graduation. diff --git a/fixtures/working-leo/leo-identity-v1/hermes-runtime/agent/prompt_builder.py b/fixtures/working-leo/leo-identity-v1/hermes-runtime/agent/prompt_builder.py new file mode 100644 index 0000000..b8aeeed --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/hermes-runtime/agent/prompt_builder.py @@ -0,0 +1,3 @@ +"""Pinned fixture prompt boundary.""" + +GENERATED_SOUL_IS_AUTHORITY = False diff --git a/fixtures/working-leo/leo-identity-v1/hermes-runtime/gateway/run.py b/fixtures/working-leo/leo-identity-v1/hermes-runtime/gateway/run.py new file mode 100644 index 0000000..ef70232 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/hermes-runtime/gateway/run.py @@ -0,0 +1,3 @@ +"""Pinned fixture process boundary; the canary uses the real profile verifier.""" + +SESSION_STATE_IS_IDENTITY = False diff --git a/fixtures/working-leo/leo-identity-v1/hermes-runtime/hermes_cli/tools_config.py b/fixtures/working-leo/leo-identity-v1/hermes-runtime/hermes_cli/tools_config.py new file mode 100644 index 0000000..63d3877 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/hermes-runtime/hermes_cli/tools_config.py @@ -0,0 +1,3 @@ +"""Pinned fixture tool configuration.""" + +ALLOWED_TOOLS = ("identity_readback",) diff --git a/fixtures/working-leo/leo-identity-v1/hermes-runtime/run_agent.py b/fixtures/working-leo/leo-identity-v1/hermes-runtime/run_agent.py new file mode 100644 index 0000000..774693b --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/hermes-runtime/run_agent.py @@ -0,0 +1,3 @@ +"""Pinned fixture entrypoint for the disposable identity runtime.""" + +RUNTIME_NAME = "leo-identity-readback-v1" diff --git a/fixtures/working-leo/leo-identity-v1/hermes-runtime/tools/registry.py b/fixtures/working-leo/leo-identity-v1/hermes-runtime/tools/registry.py new file mode 100644 index 0000000..e2ff210 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/hermes-runtime/tools/registry.py @@ -0,0 +1,3 @@ +"""Pinned fixture registry for the no-send identity readback tool.""" + +REGISTERED_TOOLS = ("identity_readback",) diff --git a/fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json b/fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json new file mode 100644 index 0000000..f116f1e --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/leo-constitution-v1.json @@ -0,0 +1,18 @@ +{ + "identity_name": "Leo", + "rules": [ + { + "id": "canonical-evidence", + "text": "Treat canonical database rows and their bound evidence as durable knowledge; never promote session memory to evidence." + }, + { + "id": "review-before-apply", + "text": "Proposals may be prepared, but review and guarded apply remain separate authorized actions." + }, + { + "id": "truthful-proof-tier", + "text": "State exactly what was observed, separate inference from proof, and fail closed when required provenance drifts." + } + ], + "schema": "livingip.leoConstitutionSource.v1" +} diff --git a/fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json b/fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json new file mode 100644 index 0000000..7fc20f1 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/leo-database-fingerprint-v1.json @@ -0,0 +1,25 @@ +{ + "environment": "disposable_fixture", + "fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111", + "read_consistency": { + "after": { + "database": "leo_identity_fixture", + "database_user": "leo_identity_reader", + "system_identifier": "7649789040005668902", + "wal_lsn": "0/16B6C50" + }, + "before": { + "database": "leo_identity_fixture", + "database_user": "leo_identity_reader", + "system_identifier": "7649789040005668902", + "wal_lsn": "0/16B6C50" + }, + "status": "stable_wal_marker" + }, + "schema": "livingip.teleoCanonicalDatabaseFingerprint.v1", + "status": "ok", + "structure_sha256": "3333333333333333333333333333333333333333333333333333333333333333", + "table_count": 7, + "table_rows_sha256": "2222222222222222222222222222222222222222222222222222222222222222", + "total_rows": 7 +} diff --git a/fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json b/fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json new file mode 100644 index 0000000..b5659e2 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/leo-database-identity-v1.json @@ -0,0 +1,58 @@ +{ + "database_fingerprint_sha256": "1111111111111111111111111111111111111111111111111111111111111111", + "identity_name": "Leo", + "source_provenance": { + "canonical_authority_granted": false, + "export_receipt_sha256": null, + "mode": "synthetic_noncanonical_fixture", + "same_transaction_as_fingerprint": false + }, + "records": [ + { + "evidence_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "kind": "persona", + "rank": 0, + "row_id": "11111111-1111-4111-8111-111111111111", + "status": "active", + "table": "public.personas", + "text": "Leo is the evidence-bound reasoning interface for the Teleo collective." + }, + { + "evidence_sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "kind": "strategy", + "rank": 0, + "row_id": "22222222-2222-4222-8222-222222222222", + "status": "active", + "table": "public.strategies", + "text": "Prefer source-grounded synthesis that exposes uncertainty and review boundaries." + }, + { + "evidence_sha256": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "kind": "belief", + "rank": 0, + "row_id": "33333333-3333-4333-8333-333333333333", + "status": "active", + "table": "public.beliefs", + "text": "A plausible answer without provenance is weaker than a bounded answer with an exact receipt." + }, + { + "evidence_sha256": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + "kind": "shared_root", + "rank": 0, + "row_id": "44444444-4444-4444-8444-444444444444", + "status": "active", + "table": "public.shared_root", + "text": "Collective intelligence must remain inspectable, reversible, and accountable to evidence." + }, + { + "evidence_sha256": "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", + "kind": "strategy_node", + "rank": 1, + "row_id": "55555555-5555-4555-8555-555555555555", + "status": "active", + "table": "public.strategy_nodes", + "text": "Compile identity from exact canonical inputs and reject drift before runtime startup." + } + ], + "schema": "livingip.leoDatabaseIdentitySource.v1" +} diff --git a/fixtures/working-leo/leo-identity-v1/profile-template/bin/identity_readback.py b/fixtures/working-leo/leo-identity-v1/profile-template/bin/identity_readback.py new file mode 100644 index 0000000..4ab40f9 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/profile-template/bin/identity_readback.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python3 +"""Fixture marker for the no-send identity readback tool.""" + +ACCESS_MODE = "read_only" +NETWORK_SEND_ENABLED = False diff --git a/fixtures/working-leo/leo-identity-v1/profile-template/config.yaml b/fixtures/working-leo/leo-identity-v1/profile-template/config.yaml new file mode 100644 index 0000000..8086613 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/profile-template/config.yaml @@ -0,0 +1,24 @@ +model: + provider: fixture-local + default: leo-identity-readback-v1 + smart_routing: false + max_tokens: 512 +agent: + reasoning_effort: deterministic +memory: + enabled: false + search: disabled +gateway: + execution_mode: local_no_send + telegram: + posting_enabled: false + bot_token: fixture-value-is-never-emitted-or-identity-hashed +tools: + allowed: + - identity_readback + write_enabled: false +identity_runtime: + network_send_enabled: false + database_write_enabled: false + allowed_tools: + - identity_readback diff --git a/fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/__init__.py b/fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/__init__.py new file mode 100644 index 0000000..19f0f68 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/__init__.py @@ -0,0 +1,4 @@ +"""Fixture marker for the pinned identity-boundary middleware.""" + +IDENTITY_VIEW_IS_AUTHORITY = False +SESSION_MEMORY_IS_EVIDENCE = False diff --git a/fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/plugin.yaml b/fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/plugin.yaml new file mode 100644 index 0000000..07cb632 --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/profile-template/plugins/identity-boundary/plugin.yaml @@ -0,0 +1,3 @@ +name: identity-boundary +version: 1 +description: Reject startup when a compiled identity view drifts. diff --git a/fixtures/working-leo/leo-identity-v1/profile-template/skills/identity-readback/SKILL.md b/fixtures/working-leo/leo-identity-v1/profile-template/skills/identity-readback/SKILL.md new file mode 100644 index 0000000..58c3a4b --- /dev/null +++ b/fixtures/working-leo/leo-identity-v1/profile-template/skills/identity-readback/SKILL.md @@ -0,0 +1,8 @@ +--- +name: identity-readback +description: Explain the pinned Leo identity and its noncanonical session boundary. +--- + +Read only the compiled identity view and its manifest receipt. Never treat a +session transcript, generated SOUL file, or unverified runtime memory as a +canonical source. diff --git a/scripts/leo_behavior_manifest.py b/scripts/leo_behavior_manifest.py index d8f6cad..39755a6 100644 --- a/scripts/leo_behavior_manifest.py +++ b/scripts/leo_behavior_manifest.py @@ -11,9 +11,13 @@ from __future__ import annotations import argparse import hashlib +import importlib.metadata import json import os +import platform +import re import subprocess +import sys from datetime import datetime, timezone from pathlib import Path from typing import Any @@ -24,6 +28,54 @@ SCHEMA = "livingip.leoBehaviorManifest.v1" DEFAULT_PROFILE = Path("/home/teleo/.hermes/profiles/leoclean") DEFAULT_HERMES_ROOT = Path("/home/teleo/.hermes/hermes-agent") DEFAULT_DEPLOYMENT_ROOT = Path("/opt/teleo-eval/workspaces/deploy-infra") +STATIC_RUNTIME_COMPONENTS = ( + "profile_config", + "procedural_skills", + "runtime_middleware", + "database_tools", +) +IDENTITY_RUNTIME_COMPONENTS = ( + "procedural_skills", + "runtime_middleware", + "database_tools", +) +STABLE_MANIFEST_FIELDS = ( + "schema", + "model_runtime", + "hermes_runtime", + "teleo_infrastructure_runtime", + "components", + "python_runtime", + "canonical_database", +) +SECRET_KEYS = frozenset( + { + "access_key", + "access_token", + "api_key", + "authorization", + "bot_token", + "client_secret", + "credential", + "credentials", + "password", + "private_key", + "refresh_token", + "secret", + "token", + } +) +SECRET_KEY_SUFFIXES = ( + "api_key", + "access_key", + "private_key", + "password", + "secret", + "token", + "credential", +) +RUNTIME_DISTRIBUTIONS = ("PyYAML",) +SAFE_CONFIG_SCALAR = re.compile(r"^[A-Za-z0-9._:/-]+$") def sha256_bytes(value: bytes) -> str: @@ -50,7 +102,9 @@ def _safe_relative(path: Path, root: Path) -> str: return str(path) -def _resolved_node_fingerprint(path: Path, seen_directories: frozenset[tuple[int, int]] = frozenset()) -> dict[str, Any]: +def _resolved_node_fingerprint( + path: Path, seen_directories: frozenset[tuple[int, int]] = frozenset() +) -> dict[str, Any]: """Hash a symlink target, following nested links while stopping directory cycles.""" try: @@ -141,21 +195,139 @@ def _nested(config: dict[str, Any], *path: str) -> Any: return value +def secret_free_config(value: Any) -> Any: + """Return a semantic config projection with secret-bearing fields omitted.""" + + if isinstance(value, dict): + return { + str(key): secret_free_config(item) + for key, item in sorted(value.items(), key=lambda pair: str(pair[0])) + if not _is_secret_key(str(key)) + } + if isinstance(value, list): + return [secret_free_config(item) for item in value] + return value + + +def _validate_identity_config_value(value: Any, *, path: str) -> None: + if value is None or isinstance(value, (bool, int)): + return + if isinstance(value, str): + if not SAFE_CONFIG_SCALAR.fullmatch(value) or "://" in value or "@" in value: + raise ValueError(f"unsafe identity config scalar: {path}") + return + if isinstance(value, list): + for index, item in enumerate(value): + _validate_identity_config_value(item, path=f"{path}[{index}]") + return + if isinstance(value, dict): + for key, item in value.items(): + if _is_secret_key(str(key)): + raise ValueError(f"secret-bearing identity config key: {path}.{key}") + _validate_identity_config_value(item, path=f"{path}.{key}") + return + raise ValueError(f"unsupported identity config value: {path}") + + +def identity_config_projection(raw: dict[str, Any]) -> dict[str, Any]: + """Build the only configuration surface copied into a disposable identity profile.""" + + section_fields = { + "model": ("provider", "default", "smart_routing", "smart_routing_model", "max_tokens"), + "agent": ("reasoning_effort",), + "memory": ("enabled", "search"), + "tools": ("allowed", "write_enabled"), + "identity_runtime": ("network_send_enabled", "database_write_enabled", "allowed_tools"), + } + projection: dict[str, Any] = {} + for section, fields in section_fields.items(): + source = raw.get(section) + if source is None: + continue + if not isinstance(source, dict): + raise ValueError(f"identity config section must be a mapping: {section}") + selected = {field: source[field] for field in fields if field in source} + _validate_identity_config_value(selected, path=section) + projection[section] = selected + + gateway = raw.get("gateway") + if gateway is not None: + if not isinstance(gateway, dict): + raise ValueError("identity config section must be a mapping: gateway") + selected_gateway = {key: gateway[key] for key in ("execution_mode",) if key in gateway} + telegram = gateway.get("telegram") + if telegram is not None: + if not isinstance(telegram, dict): + raise ValueError("identity config section must be a mapping: gateway.telegram") + selected_telegram = {key: telegram[key] for key in ("posting_enabled",) if key in telegram} + if selected_telegram: + selected_gateway["telegram"] = selected_telegram + _validate_identity_config_value(selected_gateway, path="gateway") + projection["gateway"] = selected_gateway + + if "smart_model_routing" in raw: + routing = raw["smart_model_routing"] + if not isinstance(routing, bool): + raise ValueError("smart_model_routing must be boolean") + projection["smart_model_routing"] = routing + return projection + + +def _is_secret_key(key: str) -> bool: + snake_case = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "_", key) + normalized = re.sub(r"[^a-z0-9]+", "_", snake_case.casefold()).strip("_") + return normalized in SECRET_KEYS or any(normalized.endswith(f"_{suffix}") for suffix in SECRET_KEY_SUFFIXES) + + +def python_runtime_manifest() -> dict[str, Any]: + distributions: dict[str, str | None] = {} + for name in RUNTIME_DISTRIBUTIONS: + try: + distributions[name] = importlib.metadata.version(name) + except importlib.metadata.PackageNotFoundError: + distributions[name] = None + return { + "implementation": platform.python_implementation(), + "python_version": platform.python_version(), + "abi_tag": sys.implementation.cache_tag, + "runtime_distributions": distributions, + "runtime_distributions_sha256": canonical_sha256(distributions), + } + + def safe_model_config(config_path: Path) -> dict[str, Any]: try: raw = yaml.safe_load(config_path.read_text(encoding="utf-8")) or {} except (OSError, TypeError, yaml.YAMLError) as exc: return {"status": "unavailable", "error": type(exc).__name__} + if not isinstance(raw, dict): + return {"status": "unavailable", "error": "ConfigNotMapping"} + semantic = secret_free_config(raw) + try: + identity_config = identity_config_projection(raw) + except ValueError: + return {"status": "unavailable", "error": "UnsafeIdentityConfig"} + model_semantic = identity_config.get("model") or {} return { "status": "observed", - "provider": _nested(raw, "model", "provider"), - "default_model": _nested(raw, "model", "default"), - "smart_routing": _nested(raw, "model", "smart_routing"), - "smart_routing_model": _nested(raw, "model", "smart_routing_model"), - "max_tokens": _nested(raw, "model", "max_tokens"), - "reasoning_effort": _nested(raw, "agent", "reasoning_effort"), - "memory_enabled": _nested(raw, "memory", "enabled"), - "memory_search": _nested(raw, "memory", "search"), + "config_sha256": file_sha256(config_path), + "semantic_config_sha256": canonical_sha256(semantic), + "identity_config_sha256": canonical_sha256(identity_config), + "model_section_sha256": canonical_sha256(model_semantic), + "gateway_permissions_sha256": canonical_sha256(identity_config.get("gateway")), + "tool_permissions_sha256": canonical_sha256(identity_config.get("tools")), + "memory_section_sha256": canonical_sha256(identity_config.get("memory")), + "agent_runtime_sha256": canonical_sha256(identity_config.get("agent")), + "identity_runtime_policy": identity_config.get("identity_runtime"), + "provider": _nested(identity_config, "model", "provider"), + "default_model": _nested(identity_config, "model", "default"), + "smart_routing": _nested(identity_config, "model", "smart_routing"), + "smart_routing_model": _nested(identity_config, "model", "smart_routing_model"), + "gateway_smart_model_routing": identity_config.get("smart_model_routing"), + "max_tokens": _nested(identity_config, "model", "max_tokens"), + "reasoning_effort": _nested(identity_config, "agent", "reasoning_effort"), + "memory_enabled": _nested(identity_config, "memory", "enabled"), + "memory_search": _nested(identity_config, "memory", "search"), } @@ -196,7 +368,13 @@ def git_state(repo: Path) -> dict[str, Any]: } -def source_code_manifest(profile: Path, paths: tuple[Path, ...]) -> dict[str, Any]: +def source_code_manifest( + profile: Path, + paths: tuple[Path, ...], + *, + source_root: Path | None = None, + namespace: str = "source", +) -> dict[str, Any]: """Hash executable source while excluding generated caches and environments.""" allowed_suffixes = { @@ -216,12 +394,21 @@ def source_code_manifest(profile: Path, paths: tuple[Path, ...]) -> dict[str, An excluded_parts = {".git", ".pytest_cache", "__pycache__", "node_modules", "venv", ".venv"} files: list[dict[str, Any]] = [] missing: list[str] = [] + symlinks: list[str] = [] + source_root = (source_root or profile).resolve() + + def source_label(path: Path) -> str: + return f"{namespace}:{_safe_relative(path, source_root)}" + for requested in paths: if not requested.exists() and not requested.is_symlink(): - missing.append(_safe_relative(requested, profile)) + missing.append(source_label(requested)) continue candidates = [requested] if requested.is_file() else sorted(requested.rglob("*")) for path in candidates: + if path.is_symlink(): + symlinks.append(source_label(path)) + continue if not path.is_file() or excluded_parts & set(path.parts): continue stat = path.stat() @@ -229,14 +416,14 @@ def source_code_manifest(profile: Path, paths: tuple[Path, ...]) -> dict[str, An continue files.append( { - "path": _safe_relative(path, profile), + "path": source_label(path), "bytes": stat.st_size, "mode": oct(stat.st_mode & 0o777), "sha256": file_sha256(path), } ) files.sort(key=lambda item: item["path"]) - stable = {"files": files, "missing": sorted(missing)} + stable = {"files": files, "missing": sorted(missing), "symlinks": sorted(symlinks)} return { **stable, "file_count": len(files), @@ -261,6 +448,72 @@ def component( } +def stable_manifest_payload(manifest: dict[str, Any]) -> dict[str, Any]: + """Return the canonical, path-independent behavior payload.""" + + return {field: manifest.get(field) for field in STABLE_MANIFEST_FIELDS} + + +def static_runtime_payload(manifest: dict[str, Any]) -> dict[str, Any]: + """Return the exact payload committed by ``static_runtime_sha256``.""" + + components = manifest.get("components") or {} + return { + "schema": manifest.get("schema"), + "model_runtime": manifest.get("model_runtime"), + "hermes_runtime": manifest.get("hermes_runtime"), + "teleo_infrastructure_runtime": manifest.get("teleo_infrastructure_runtime"), + "components": {name: components.get(name) for name in STATIC_RUNTIME_COMPONENTS}, + "python_runtime": manifest.get("python_runtime"), + "canonical_database": manifest.get("canonical_database"), + } + + +def identity_runtime_payload(manifest: dict[str, Any]) -> dict[str, Any]: + """Return the exact payload committed by ``identity_runtime_sha256``.""" + + model = manifest.get("model_runtime") or {} + components = manifest.get("components") or {} + teleo = manifest.get("teleo_infrastructure_runtime") or {} + identity_model_runtime = { + key: model.get(key) + for key in ( + "status", + "identity_config_sha256", + "model_section_sha256", + "gateway_permissions_sha256", + "tool_permissions_sha256", + "memory_section_sha256", + "agent_runtime_sha256", + "identity_runtime_policy", + "provider", + "default_model", + "smart_routing", + "smart_routing_model", + "gateway_smart_model_routing", + "max_tokens", + "reasoning_effort", + "memory_enabled", + "memory_search", + "base_model_weights", + "actual_per_turn_model_must_be_recorded_by_the_call_receipt", + ) + } + return { + "schema": manifest.get("schema"), + "model_runtime": identity_model_runtime, + "hermes_runtime": manifest.get("hermes_runtime"), + "teleo_infrastructure_runtime": { + "git_head": teleo.get("git_head"), + "git_state": teleo.get("git_state"), + "source_tree": teleo.get("identity_source_tree"), + }, + "components": {name: components.get(name) for name in IDENTITY_RUNTIME_COMPONENTS}, + "python_runtime": manifest.get("python_runtime"), + "canonical_database": manifest.get("canonical_database"), + } + + def build_manifest( profile: Path = DEFAULT_PROFILE, hermes_root: Path = DEFAULT_HERMES_ROOT, @@ -270,6 +523,12 @@ def build_manifest( hermes_root = hermes_root.resolve() deployment_root = deployment_root.resolve() config = safe_model_config(profile / "config.yaml") + identity_runtime_sources = ( + deployment_root / "scripts" / "leo_behavior_manifest.py", + deployment_root / "scripts" / "leo_identity_manifest.py", + deployment_root / "scripts" / "leo_identity_profile.py", + deployment_root / "scripts" / "run_leo_identity_reconstruction_canary.py", + ) components = { "profile_config": component( profile, @@ -294,10 +553,10 @@ def build_manifest( ), "runtime_middleware": component( profile, - role="Pre-LLM context injection and post-LLM validation or response replacement.", + role="Profile plugins for pre-LLM context injection and post-LLM validation or response replacement.", mutability="deployment_static", replayability="fully_hashable", - paths=(profile / "plugins", hermes_root / "run_agent.py"), + paths=(profile / "plugins",), ), "database_tools": component( profile, @@ -353,14 +612,28 @@ def build_manifest( hermes_root / "hermes_cli", hermes_root / "tools", ), + source_root=hermes_root, + namespace="hermes", ), }, "teleo_infrastructure_runtime": { "git_head": git_head(deployment_root), "git_state": git_state(deployment_root), - "source_tree": source_code_manifest(profile, (deployment_root,)), + "source_tree": source_code_manifest( + profile, + (deployment_root,), + source_root=deployment_root, + namespace="teleo", + ), + "identity_source_tree": source_code_manifest( + profile, + identity_runtime_sources, + source_root=deployment_root, + namespace="teleo-identity", + ), }, "components": components, + "python_runtime": python_runtime_manifest(), "canonical_database": { "included": False, "reason": "Fingerprint through a read-only Postgres manifest in the database-owning harness.", @@ -372,7 +645,9 @@ def build_manifest( "profile_root": str(profile), "hermes_root": str(hermes_root), "deployment_root": str(deployment_root), - "behavior_sha256": canonical_sha256(stable), + "static_runtime_sha256": canonical_sha256(static_runtime_payload(stable)), + "identity_runtime_sha256": canonical_sha256(identity_runtime_payload(stable)), + "behavior_sha256": canonical_sha256(stable_manifest_payload(stable)), } diff --git a/scripts/leo_identity_manifest.py b/scripts/leo_identity_manifest.py new file mode 100644 index 0000000..d9d2943 --- /dev/null +++ b/scripts/leo_identity_manifest.py @@ -0,0 +1,878 @@ +#!/usr/bin/env python3 +"""Build and verify Leo's pinned, reproducible identity manifest.""" + +from __future__ import annotations + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Any + +if __package__ in {None, ""}: + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from scripts import leo_behavior_manifest as behavior + +SCHEMA = "livingip.leoIdentityManifest.v1" +CONSTITUTION_SCHEMA = "livingip.leoConstitutionSource.v1" +DATABASE_IDENTITY_SCHEMA = "livingip.leoDatabaseIdentitySource.v1" +STRUCTURED_VIEW_SCHEMA = "livingip.leoCompiledIdentityView.v1" +DATABASE_FINGERPRINT_SCHEMA = "livingip.teleoCanonicalDatabaseFingerprint.v1" +SYNTHETIC_DATABASE_MODE = "synthetic_noncanonical_fixture" +CANONICAL_DATABASE_MODE = "canonical_database_same_transaction_export" +HEX_64 = re.compile(r"^[0-9a-f]{64}$") +HEX_40 = re.compile(r"^[0-9a-f]{40}$") +IDENTITY_TABLES = frozenset( + { + "public.personas", + "public.strategies", + "public.beliefs", + "public.shared_root", + "public.strategy_nodes", + "public.strategy_node_anchors", + "public.claims", + } +) + + +def expected_runtime_policy() -> dict[str, Any]: + return { + "network_send_enabled": False, + "database_write_enabled": False, + "allowed_tools": ["identity_readback"], + } + + +def expected_session_boundary() -> dict[str, Any]: + return { + "classification": "temporary_noncanonical", + "included_in_identity_inputs": False, + "may_be_used_as_evidence": False, + "restart_policy": "fresh_process_with_session_state_outside_identity_authority", + } + + +def expected_gatewayrunner_contract() -> dict[str, Any]: + return { + "profile_surfaces": ["config.yaml", "generated SOUL.md", "skills", "plugins", "bin tools"], + "required_absent_or_empty": { + "persistent_memory": behavior.canonical_sha256([]), + "pairing_store": behavior.canonical_sha256([]), + "project_context": behavior.canonical_sha256([]), + "generated_skill_prompt_cache": behavior.canonical_sha256([]), + "prior_sessions_at_first_start": behavior.canonical_sha256([]), + }, + "fixed_message_context": { + "auto_skill": None, + "reply_context": None, + "media_or_file_context": None, + }, + "required_t3_turn_receipt_fields": [ + "runner_class", + "authorization_decision", + "effective_system_prompt_sha256", + "compiled_skills_prompt_sha256", + "tool_registry_schema_sha256", + "plugin_registry_sha256", + "selected_model", + "selected_provider", + "api_mode", + "base_url_host", + "database_retrieval_receipt", + "session_key_sha256", + "persisted_session_id_sha256", + ], + } + + +class IdentityManifestError(ValueError): + """An identity input is incomplete, inconsistent, or drifted.""" + + +def _require(condition: bool, message: str) -> None: + if not condition: + raise IdentityManifestError(message) + + +def _is_sha256(value: Any) -> bool: + return isinstance(value, str) and bool(HEX_64.fullmatch(value)) + + +def load_json(path: Path, label: str) -> dict[str, Any]: + try: + value = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError) as exc: + raise IdentityManifestError(f"cannot read {label}: {type(exc).__name__}") from exc + if not isinstance(value, dict): + raise IdentityManifestError(f"{label} must be a JSON object") + return value + + +def canonical_rules(value: dict[str, Any]) -> list[dict[str, str]]: + _require(value.get("schema") == CONSTITUTION_SCHEMA, "unsupported constitution schema") + _require(value.get("identity_name") == "Leo", "constitution identity_name must be Leo") + raw_rules = value.get("rules") + _require(isinstance(raw_rules, list) and bool(raw_rules), "constitution rules must be non-empty") + rules: list[dict[str, str]] = [] + for raw in raw_rules: + _require(isinstance(raw, dict), "each constitutional rule must be an object") + rule_id = str(raw.get("id") or "").strip() + text = str(raw.get("text") or "").strip() + _require(bool(rule_id and text), "each constitutional rule needs id and text") + rules.append({"id": rule_id, "text": text}) + _require(len({item["id"] for item in rules}) == len(rules), "constitutional rule ids must be unique") + return sorted(rules, key=lambda item: item["id"]) + + +def canonical_database_records(value: dict[str, Any], *, fingerprint_sha256: str) -> list[dict[str, str | int]]: + _require(value.get("schema") == DATABASE_IDENTITY_SCHEMA, "unsupported database identity schema") + _require(value.get("identity_name") == "Leo", "database identity_name must be Leo") + _require( + value.get("database_fingerprint_sha256") == fingerprint_sha256, + "database identity export is not bound to the supplied database fingerprint", + ) + raw_records = value.get("records") + _require(isinstance(raw_records, list) and bool(raw_records), "database identity records must be non-empty") + records: list[dict[str, str | int]] = [] + for raw in raw_records: + _require(isinstance(raw, dict), "each database identity record must be an object") + rank = raw.get("rank", 0) + record: dict[str, str | int] = { + "table": str(raw.get("table") or "").strip(), + "row_id": str(raw.get("row_id") or "").strip(), + "kind": str(raw.get("kind") or "").strip(), + "rank": rank if isinstance(rank, int) and not isinstance(rank, bool) else -1, + "text": str(raw.get("text") or "").strip(), + "status": str(raw.get("status") or "").strip(), + "evidence_sha256": str(raw.get("evidence_sha256") or "").strip(), + } + _require(record["table"] in IDENTITY_TABLES, "database identity table is outside the allowlist") + _require(bool(record["row_id"] and record["kind"] and record["text"]), "database record fields are incomplete") + _require(isinstance(record["rank"], int) and record["rank"] >= 0, "database record rank is invalid") + _require(record["status"] == "active", "database identity records must be active selected rows") + _require(_is_sha256(record["evidence_sha256"]), "database record evidence_sha256 is invalid") + records.append(record) + keys = {(item["table"], item["row_id"]) for item in records} + _require(len(keys) == len(records), "database identity row bindings must be unique") + return sorted(records, key=lambda item: (str(item["table"]), int(item["rank"]), str(item["row_id"]))) + + +def database_fingerprint_semantic(value: dict[str, Any]) -> dict[str, Any]: + """Bind immutable capture provenance while excluding only the volatile WAL position.""" + + result = {key: value[key] for key in sorted(value) if key != "read_consistency"} + consistency = value["read_consistency"] + result["read_consistency"] = { + "status": consistency["status"], + "before": {key: item for key, item in consistency["before"].items() if key != "wal_lsn"}, + "after": {key: item for key, item in consistency["after"].items() if key != "wal_lsn"}, + } + return result + + +def database_identity_provenance( + value: dict[str, Any], + *, + fingerprint: dict[str, Any], +) -> dict[str, Any]: + raw = value.get("source_provenance") + _require(isinstance(raw, dict), "database identity source provenance is missing") + mode = raw.get("mode") + if mode == SYNTHETIC_DATABASE_MODE: + _require( + raw + == { + "mode": SYNTHETIC_DATABASE_MODE, + "canonical_authority_granted": False, + "same_transaction_as_fingerprint": False, + "export_receipt_sha256": None, + }, + "synthetic database provenance contract is invalid", + ) + _require( + fingerprint.get("environment") == "disposable_fixture", + "synthetic rows require a fixture fingerprint", + ) + _require("export_receipt" not in value, "synthetic database identity must not claim an export receipt") + return {**raw, "authority": "synthetic_noncanonical_fixture"} + + if mode == CANONICAL_DATABASE_MODE: + raise IdentityManifestError( + "canonical authority requires independently verified output from the database-owning same-transaction " + "exporter; caller-supplied T2 JSON cannot grant it" + ) + raise IdentityManifestError("unsupported database identity provenance mode") + + +def validate_database_fingerprint(value: dict[str, Any]) -> None: + _require(value.get("schema") == DATABASE_FINGERPRINT_SCHEMA, "unsupported database fingerprint schema") + _require(value.get("status") == "ok", "database fingerprint status must be ok") + for field in ("fingerprint_sha256", "table_rows_sha256", "structure_sha256"): + _require(_is_sha256(value.get(field)), f"database fingerprint {field} is invalid") + _require(isinstance(value.get("table_count"), int) and value["table_count"] > 0, "table_count must be positive") + _require(isinstance(value.get("total_rows"), int) and value["total_rows"] >= 0, "total_rows is invalid") + consistency = value.get("read_consistency") + _require(isinstance(consistency, dict), "database read_consistency is missing") + before = consistency.get("before") + after = consistency.get("after") + _require(consistency.get("status") == "stable_wal_marker", "database fingerprint was not read consistently") + _require(isinstance(before, dict) and before == after and bool(before), "database read markers differ") + for field in ("database", "database_user", "system_identifier", "wal_lsn"): + _require(bool(before.get(field)), f"database read marker {field} is missing") + + +def validate_content_manifest(value: Any, *, label: str) -> None: + """Recompute the structural metadata of a replayable content manifest.""" + + _require(isinstance(value, dict), f"{label} is invalid") + _require( + set(value) == {"files", "missing", "symlinks", "file_count", "total_bytes", "sha256"}, + f"{label} fields are invalid", + ) + files = value.get("files") + missing = value.get("missing") + symlinks = value.get("symlinks") + _require(isinstance(files, list) and bool(files), f"{label} is empty") + _require(missing == [], f"{label} has missing inputs") + _require(symlinks == [], f"{label} contains unsupported symlinks") + + paths: list[str] = [] + total_bytes = 0 + for item in files: + _require(isinstance(item, dict), f"{label} contains unreadable inputs") + _require(set(item) == {"path", "bytes", "mode", "sha256"}, f"{label} contains unreadable inputs") + path = item.get("path") + size = item.get("bytes") + mode = item.get("mode") + _require(isinstance(path, str) and bool(path), f"{label} contains an invalid path") + _require(isinstance(size, int) and not isinstance(size, bool) and size >= 0, f"{label} byte size is invalid") + _require(isinstance(mode, str) and bool(re.fullmatch(r"0o[0-7]{3}", mode)), f"{label} mode is invalid") + _require(_is_sha256(item.get("sha256")), f"{label} contains unreadable inputs") + paths.append(path) + total_bytes += size + + _require(paths == sorted(paths) and len(paths) == len(set(paths)), f"{label} paths are not unique and sorted") + _require(value.get("file_count") == len(files), f"{label} file count is invalid") + _require(value.get("total_bytes") == total_bytes, f"{label} total bytes is invalid") + stable = {"files": files, "missing": missing, "symlinks": symlinks} + _require(behavior.canonical_sha256(stable) == value.get("sha256"), f"{label} content hash is invalid") + + +def validate_behavior_manifest(value: dict[str, Any]) -> None: + _require(value.get("schema") == behavior.SCHEMA, "unsupported behavior manifest schema") + for field in ("behavior_sha256", "static_runtime_sha256", "identity_runtime_sha256"): + _require(_is_sha256(value.get(field)), f"behavior manifest {field} is invalid") + _require( + behavior.canonical_sha256(behavior.identity_runtime_payload(value)) == value["identity_runtime_sha256"], + "behavior identity runtime hash drift detected", + ) + _require( + behavior.canonical_sha256(behavior.static_runtime_payload(value)) == value["static_runtime_sha256"], + "behavior static runtime hash drift detected", + ) + _require( + behavior.canonical_sha256(behavior.stable_manifest_payload(value)) == value["behavior_sha256"], + "behavior manifest stable payload hash drift detected", + ) + model = value.get("model_runtime") + _require(isinstance(model, dict) and model.get("status") == "observed", "model runtime was not observed") + _require(bool(model.get("provider") and model.get("default_model")), "provider and default model must be pinned") + for field in ( + "semantic_config_sha256", + "identity_config_sha256", + "model_section_sha256", + "gateway_permissions_sha256", + "tool_permissions_sha256", + "memory_section_sha256", + "agent_runtime_sha256", + ): + _require(_is_sha256(model.get(field)), f"model runtime {field} is invalid") + _require( + model.get("base_model_weights") == "external_provider_managed_not_locally_hashable", + "hosted model weight boundary is missing", + ) + _require( + model.get("actual_per_turn_model_must_be_recorded_by_the_call_receipt") is True, "model receipt gate missing" + ) + policy = model.get("identity_runtime_policy") + _require(policy == expected_runtime_policy(), "identity runtime policy must be the exact local readback policy") + for field in ("hermes_runtime", "teleo_infrastructure_runtime"): + runtime = value.get(field) + _require(isinstance(runtime, dict), f"{field} is missing") + _require(bool(HEX_40.fullmatch(str(runtime.get("git_head") or ""))), f"{field} git_head is invalid") + git_state = runtime.get("git_state") + _require( + isinstance(git_state, dict) and git_state.get("worktree_clean") is True, + f"{field} is not reconstructible from clean Git", + ) + tree = runtime.get("identity_source_tree" if field == "teleo_infrastructure_runtime" else "source_tree") + validate_content_manifest(tree, label=f"{field} source tree") + components = value.get("components") + _require(isinstance(components, dict), "behavior components are missing") + for name in behavior.IDENTITY_RUNTIME_COMPONENTS: + content = (components.get(name) or {}).get("content") + validate_content_manifest(content, label=f"component {name}") + python_runtime = value.get("python_runtime") + _require(isinstance(python_runtime, dict), "Python runtime binding is missing") + _require( + bool(python_runtime.get("implementation") and python_runtime.get("python_version")), + "Python runtime is incomplete", + ) + _require(_is_sha256(python_runtime.get("runtime_distributions_sha256")), "runtime package binding is invalid") + _require( + all(python_runtime.get("runtime_distributions", {}).values()), "a required runtime distribution is missing" + ) + + +def _repo_path(path: Path, source_root: Path) -> str: + try: + return path.resolve(strict=True).relative_to(source_root.resolve(strict=True)).as_posix() + except (OSError, ValueError) as exc: + raise IdentityManifestError(f"identity source is outside the pinned source root: {path}") from exc + + +def source_binding( + path: Path, + *, + source_root: Path, + semantic_value: Any, + count: int, + pin_content: bool = True, +) -> dict[str, Any]: + result = { + "repo_path": _repo_path(path, source_root), + "semantic_sha256": behavior.canonical_sha256(semantic_value), + "record_count": count, + } + if pin_content: + result["content_sha256"] = behavior.file_sha256(path) + return result + + +def render_identity_views( + *, + identity_inputs_sha256: str, + database_fingerprint_sha256: str, + database_provenance: dict[str, Any], + rules: list[dict[str, str]], + records: list[dict[str, str | int]], +) -> dict[str, str]: + soul = [ + "", + "# Leo", + "", + f"Identity inputs: `{identity_inputs_sha256}`", + "", + "## Static constitutional rules", + "", + "These rules are static constitutional inputs, not database claims.", + "", + ] + soul.extend(f"- **{item['id']}**: {item['text']}" for item in rules) + database_description = ( + "canonical database capture" + if database_provenance["canonical_authority_granted"] + else "synthetic noncanonical fixture" + ) + soul.extend( + [ + "", + "## Database-derived identity", + "", + f"Generated from {database_description} fingerprint `{database_fingerprint_sha256}`.", + "", + ] + ) + soul.extend( + f"- `{item['table']}/{item['row_id']}` [{item['kind']}, rank {item['rank']}]: {item['text']} " + f"(evidence `{item['evidence_sha256']}`)" + for item in records + ) + soul.extend( + [ + "", + "## Authority and session boundary", + "", + "- This `SOUL.md` is a generated view and is never an authority by itself.", + "- Generated database identity is authoritative only when its same-transaction export receipt grants canonical authority.", + "- Runtime/session memory is temporary, noncanonical, and cannot serve as evidence.", + "- Hosted model weights are provider-managed and must be attributed by each turn receipt.", + "", + ] + ) + structured = { + "schema": STRUCTURED_VIEW_SCHEMA, + "identity_name": "Leo", + "identity_inputs_sha256": identity_inputs_sha256, + "static_constitution": {"authority": "pinned_static_source", "rules": rules}, + "database_identity": { + "authority": database_provenance["authority"], + "canonical_authority_granted": database_provenance["canonical_authority_granted"], + "source_mode": database_provenance["mode"], + "database_fingerprint_sha256": database_fingerprint_sha256, + "records": records, + }, + "session_boundary": { + "authority": "none", + "classification": "temporary_noncanonical", + "may_be_used_as_evidence": False, + }, + } + return { + "SOUL.md": "\n".join(soul), + "identity.json": json.dumps(structured, indent=2, sort_keys=True) + "\n", + } + + +def build_identity_manifest( + *, + behavior_manifest: dict[str, Any], + database_fingerprint_path: Path, + constitution_path: Path, + database_identity_path: Path, + source_root: Path, + source_commit: str | None = None, +) -> dict[str, Any]: + validate_behavior_manifest(behavior_manifest) + database_fingerprint = load_json(database_fingerprint_path, "database fingerprint") + validate_database_fingerprint(database_fingerprint) + constitution = load_json(constitution_path, "constitution source") + database_identity = load_json(database_identity_path, "database identity source") + rules = canonical_rules(constitution) + records = canonical_database_records( + database_identity, fingerprint_sha256=database_fingerprint["fingerprint_sha256"] + ) + database_provenance = database_identity_provenance( + database_identity, + fingerprint=database_fingerprint, + ) + runtime_commit = behavior_manifest["teleo_infrastructure_runtime"]["git_head"] + source_commit = source_commit or runtime_commit + _require(bool(HEX_40.fullmatch(str(source_commit))), "source commit is invalid") + _require(source_commit == runtime_commit, "source commit does not match the behavior manifest") + + model = behavior_manifest["model_runtime"] + components = behavior_manifest["components"] + marker = database_fingerprint["read_consistency"]["before"] + core = { + "identity_name": "Leo", + "source_commit": source_commit, + "runtime": { + "identity_runtime_sha256": behavior_manifest["identity_runtime_sha256"], + "hermes_git_head": behavior_manifest["hermes_runtime"]["git_head"], + "hermes_source_sha256": behavior_manifest["hermes_runtime"]["source_tree"]["sha256"], + "teleo_git_head": runtime_commit, + "teleo_source_sha256": behavior_manifest["teleo_infrastructure_runtime"]["identity_source_tree"]["sha256"], + "python": behavior_manifest["python_runtime"], + }, + "model": { + "provider": model["provider"], + "default_model": model["default_model"], + "smart_routing": model.get("smart_routing"), + "smart_routing_model": model.get("smart_routing_model"), + "gateway_smart_model_routing": model.get("gateway_smart_model_routing"), + "model_section_sha256": model["model_section_sha256"], + "hosted_weights": "external_provider_managed_not_locally_hashable", + "actual_model_required_in_turn_receipt": True, + }, + "skills": { + "content_sha256": components["procedural_skills"]["content"]["sha256"], + "file_count": components["procedural_skills"]["content"]["file_count"], + }, + "permissions": { + "identity_config_sha256": model["identity_config_sha256"], + "gateway_permissions_sha256": model["gateway_permissions_sha256"], + "tool_permissions_sha256": model["tool_permissions_sha256"], + "runtime_policy": model["identity_runtime_policy"], + "authorization_decision_required_in_runtime_receipt": True, + }, + "canonical_database": { + "database": marker["database"], + "database_user": marker["database_user"], + "system_identifier": marker["system_identifier"], + "authority": database_provenance["authority"], + "canonical_authority_granted": database_provenance["canonical_authority_granted"], + **{ + key: database_fingerprint[key] + for key in ("fingerprint_sha256", "table_rows_sha256", "structure_sha256", "table_count", "total_rows") + }, + "source": source_binding( + database_fingerprint_path, + source_root=source_root, + semantic_value=database_fingerprint_semantic(database_fingerprint), + count=database_fingerprint["table_count"], + pin_content=False, + ), + }, + "identity_sources": { + "static_constitution": { + "authority": "pinned_static_source", + **source_binding(constitution_path, source_root=source_root, semantic_value=rules, count=len(rules)), + }, + "database_derived_identity": { + "authority": database_provenance["authority"], + "provenance": database_provenance, + "database_fingerprint_sha256": database_fingerprint["fingerprint_sha256"], + **source_binding( + database_identity_path, + source_root=source_root, + semantic_value={"provenance": database_provenance, "records": records}, + count=len(records), + ), + }, + }, + "session_boundary": expected_session_boundary(), + "gatewayrunner_execution_contract": expected_gatewayrunner_contract(), + } + identity_inputs_sha256 = behavior.canonical_sha256(core) + views = render_identity_views( + identity_inputs_sha256=identity_inputs_sha256, + database_fingerprint_sha256=database_fingerprint["fingerprint_sha256"], + database_provenance=database_provenance, + rules=rules, + records=records, + ) + stable = { + "schema": SCHEMA, + "identity_inputs": core, + "identity_inputs_sha256": identity_inputs_sha256, + "compiled_views": { + name: { + "role": "generated_view_not_authority", + "sha256": behavior.sha256_bytes(content.encode("utf-8")), + "generated_from_identity_inputs_sha256": identity_inputs_sha256, + } + for name, content in sorted(views.items()) + }, + } + return {**stable, "manifest_sha256": behavior.canonical_sha256(stable)} + + +def validate_identity_manifest(value: dict[str, Any]) -> None: + _require(value.get("schema") == SCHEMA, "unsupported identity manifest schema") + expected = value.get("manifest_sha256") + _require(_is_sha256(expected), "identity manifest sha256 is invalid") + stable = {key: item for key, item in value.items() if key != "manifest_sha256"} + _require(behavior.canonical_sha256(stable) == expected, "identity manifest hash drift detected") + core = value.get("identity_inputs") + _require(isinstance(core, dict), "identity inputs are missing") + _require( + set(core) + == { + "identity_name", + "source_commit", + "runtime", + "model", + "skills", + "permissions", + "canonical_database", + "identity_sources", + "session_boundary", + "gatewayrunner_execution_contract", + }, + "identity input contract fields are invalid", + ) + identity_hash = value.get("identity_inputs_sha256") + _require(_is_sha256(identity_hash), "identity input hash is invalid") + _require(behavior.canonical_sha256(core) == identity_hash, "identity input hash drift detected") + _require(core.get("identity_name") == "Leo", "identity name must be Leo") + + runtime = core.get("runtime") + _require(isinstance(runtime, dict), "runtime binding is missing") + _require( + set(runtime) + == { + "identity_runtime_sha256", + "hermes_git_head", + "hermes_source_sha256", + "teleo_git_head", + "teleo_source_sha256", + "python", + }, + "runtime binding fields are invalid", + ) + _require(_is_sha256(runtime.get("identity_runtime_sha256")), "identity runtime binding is invalid") + for field in ("hermes_git_head", "teleo_git_head"): + _require(bool(HEX_40.fullmatch(str(runtime.get(field) or ""))), f"runtime {field} is invalid") + for field in ("hermes_source_sha256", "teleo_source_sha256"): + _require(_is_sha256(runtime.get(field)), f"runtime {field} is invalid") + _require(core.get("source_commit") == runtime["teleo_git_head"], "source commit is not bound to Teleo Git") + python_runtime = runtime.get("python") + _require(isinstance(python_runtime, dict), "Python runtime binding is missing") + _require( + bool(python_runtime.get("implementation") and python_runtime.get("python_version")), + "Python runtime binding is incomplete", + ) + _require(_is_sha256(python_runtime.get("runtime_distributions_sha256")), "Python distribution binding is invalid") + + model = core.get("model") + _require(isinstance(model, dict), "model binding is missing") + _require( + set(model) + == { + "provider", + "default_model", + "smart_routing", + "smart_routing_model", + "gateway_smart_model_routing", + "model_section_sha256", + "hosted_weights", + "actual_model_required_in_turn_receipt", + }, + "model binding fields are invalid", + ) + _require(bool(model.get("provider") and model.get("default_model")), "model provider and default must be pinned") + _require(_is_sha256(model.get("model_section_sha256")), "model section binding is invalid") + _require( + model.get("hosted_weights") == "external_provider_managed_not_locally_hashable", + "hosted model boundary is invalid", + ) + _require(model.get("actual_model_required_in_turn_receipt") is True, "per-turn model receipt gate is invalid") + + skills = core.get("skills") + _require(isinstance(skills, dict) and set(skills) == {"content_sha256", "file_count"}, "skills binding is invalid") + _require(_is_sha256(skills.get("content_sha256")), "skills content hash is invalid") + _require( + isinstance(skills.get("file_count"), int) + and not isinstance(skills.get("file_count"), bool) + and skills["file_count"] > 0, + "skills file count is invalid", + ) + + permissions = core.get("permissions") + _require(isinstance(permissions, dict), "permission binding is missing") + _require( + set(permissions) + == { + "identity_config_sha256", + "gateway_permissions_sha256", + "tool_permissions_sha256", + "runtime_policy", + "authorization_decision_required_in_runtime_receipt", + }, + "permission binding fields are invalid", + ) + for field in ("identity_config_sha256", "gateway_permissions_sha256", "tool_permissions_sha256"): + _require(_is_sha256(permissions.get(field)), f"permission binding {field} is invalid") + _require( + permissions.get("runtime_policy") == expected_runtime_policy(), + "identity runtime policy must be the exact local readback policy", + ) + _require( + permissions.get("authorization_decision_required_in_runtime_receipt") is True, + "runtime authorization receipt gate is invalid", + ) + + database = core.get("canonical_database") + _require(isinstance(database, dict), "canonical database binding is missing") + for field in ("fingerprint_sha256", "table_rows_sha256", "structure_sha256"): + _require(_is_sha256(database.get(field)), f"canonical database {field} is invalid") + _require( + bool(database.get("database") and database.get("database_user") and database.get("system_identifier")), + "canonical database identity is incomplete", + ) + allowed_authorities = {"synthetic_noncanonical_fixture": False} + _require(database.get("authority") in allowed_authorities, "database authority is invalid") + _require( + database.get("canonical_authority_granted") is allowed_authorities[database["authority"]], + "database canonical authority grant is invalid", + ) + _require( + isinstance(database.get("table_count"), int) + and not isinstance(database.get("table_count"), bool) + and database["table_count"] > 0, + "canonical database table_count is invalid", + ) + _require( + isinstance(database.get("total_rows"), int) + and not isinstance(database.get("total_rows"), bool) + and database["total_rows"] >= 0, + "canonical database total_rows is invalid", + ) + + def validate_source_binding(binding: Any, *, label: str, content_required: bool) -> None: + _require(isinstance(binding, dict), f"{label} source binding is missing") + repo_path = binding.get("repo_path") + _require( + isinstance(repo_path, str) + and bool(repo_path) + and not Path(repo_path).is_absolute() + and ".." not in Path(repo_path).parts, + f"{label} source path is invalid", + ) + _require(_is_sha256(binding.get("semantic_sha256")), f"{label} semantic binding is invalid") + _require( + isinstance(binding.get("record_count"), int) + and not isinstance(binding.get("record_count"), bool) + and binding["record_count"] > 0, + f"{label} record count is invalid", + ) + if content_required: + _require(_is_sha256(binding.get("content_sha256")), f"{label} content binding is invalid") + else: + _require("content_sha256" not in binding, f"{label} content binding must exclude volatile capture markers") + + validate_source_binding(database.get("source"), label="database fingerprint", content_required=False) + sources = core.get("identity_sources") + _require( + isinstance(sources, dict) and set(sources) == {"static_constitution", "database_derived_identity"}, + "identity source bindings are invalid", + ) + constitution = sources["static_constitution"] + derived = sources["database_derived_identity"] + _require(isinstance(constitution, dict), "constitution source binding is invalid") + _require(isinstance(derived, dict), "database identity source binding is invalid") + _require(constitution.get("authority") == "pinned_static_source", "constitution authority is invalid") + _require(derived.get("authority") == database["authority"], "database identity authority is invalid") + provenance = derived.get("provenance") + _require(isinstance(provenance, dict), "database identity provenance is missing") + _require(provenance.get("authority") == database["authority"], "database identity provenance authority is invalid") + _require( + provenance.get("canonical_authority_granted") is database["canonical_authority_granted"], + "database identity provenance grant is invalid", + ) + _require(provenance.get("mode") == SYNTHETIC_DATABASE_MODE, "synthetic database provenance mode is invalid") + _require( + provenance.get("same_transaction_as_fingerprint") is False, + "synthetic fixture transaction claim is invalid", + ) + _require(provenance.get("export_receipt_sha256") is None, "synthetic fixture must not claim an export receipt") + _require( + derived.get("database_fingerprint_sha256") == database["fingerprint_sha256"], + "database identity source is misbound", + ) + validate_source_binding(constitution, label="constitution", content_required=True) + validate_source_binding(derived, label="database identity", content_required=True) + _require(core.get("session_boundary") == expected_session_boundary(), "session authority boundary is invalid") + _require( + core.get("gatewayrunner_execution_contract") == expected_gatewayrunner_contract(), + "GatewayRunner execution contract is invalid", + ) + + views = value.get("compiled_views") + _require(isinstance(views, dict) and set(views) == {"SOUL.md", "identity.json"}, "compiled views are incomplete") + for name, view in views.items(): + _require(isinstance(view, dict) and _is_sha256(view.get("sha256")), f"compiled view {name} is invalid") + _require( + set(view) == {"role", "sha256", "generated_from_identity_inputs_sha256"}, + f"compiled view {name} fields are invalid", + ) + _require(view.get("role") == "generated_view_not_authority", f"compiled view {name} authority is invalid") + _require( + view.get("generated_from_identity_inputs_sha256") == identity_hash, f"compiled view {name} is misbound" + ) + + +def verify_bound_sources(value: dict[str, Any], source_root: Path) -> dict[str, str]: + validate_identity_manifest(value) + core = value["identity_inputs"] + bindings = { + "database fingerprint": core["canonical_database"]["source"], + "constitution source": core["identity_sources"]["static_constitution"], + "database identity source": core["identity_sources"]["database_derived_identity"], + } + paths = {label: source_root / binding["repo_path"] for label, binding in bindings.items()} + for label, binding in bindings.items(): + path = paths[label] + _require(path.is_file(), f"bound {label} is missing") + if binding.get("content_sha256"): + _require(behavior.file_sha256(path) == binding["content_sha256"], f"bound {label} content drift detected") + fingerprint = load_json(paths["database fingerprint"], "database fingerprint") + validate_database_fingerprint(fingerprint) + _require( + behavior.canonical_sha256(database_fingerprint_semantic(fingerprint)) + == bindings["database fingerprint"]["semantic_sha256"], + "database fingerprint semantic drift detected", + ) + marker = fingerprint["read_consistency"]["before"] + for field in ("database", "database_user", "system_identifier"): + _require(marker[field] == core["canonical_database"][field], f"canonical database {field} drift detected") + rules = canonical_rules(load_json(paths["constitution source"], "constitution source")) + database_identity = load_json(paths["database identity source"], "database identity source") + records = canonical_database_records( + database_identity, + fingerprint_sha256=fingerprint["fingerprint_sha256"], + ) + database_provenance = database_identity_provenance( + database_identity, + fingerprint=fingerprint, + ) + _require( + behavior.canonical_sha256(rules) == bindings["constitution source"]["semantic_sha256"], + "constitution semantic drift detected", + ) + _require( + behavior.canonical_sha256({"provenance": database_provenance, "records": records}) + == bindings["database identity source"]["semantic_sha256"], + "database identity semantic drift detected", + ) + _require( + database_provenance == core["identity_sources"]["database_derived_identity"]["provenance"], + "database identity provenance drift detected", + ) + _require( + fingerprint["fingerprint_sha256"] == core["canonical_database"]["fingerprint_sha256"], + "canonical database fingerprint drift detected", + ) + views = render_identity_views( + identity_inputs_sha256=value["identity_inputs_sha256"], + database_fingerprint_sha256=fingerprint["fingerprint_sha256"], + database_provenance=database_provenance, + rules=rules, + records=records, + ) + for name, content in views.items(): + _require( + behavior.sha256_bytes(content.encode("utf-8")) == value["compiled_views"][name]["sha256"], + f"compiled view contract drift detected for {name}", + ) + return views + + +def write_json(path: Path, value: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n", encoding="utf-8") + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + subparsers = parser.add_subparsers(dest="command", required=True) + generate = subparsers.add_parser("generate") + generate.add_argument("--behavior-manifest", type=Path, required=True) + generate.add_argument("--database-fingerprint", type=Path, required=True) + generate.add_argument("--constitution", type=Path, required=True) + generate.add_argument("--database-identity", type=Path, required=True) + generate.add_argument("--source-root", type=Path, required=True) + generate.add_argument("--source-commit") + generate.add_argument("--output", type=Path, required=True) + verify = subparsers.add_parser("verify") + verify.add_argument("--manifest", type=Path, required=True) + verify.add_argument("--source-root", type=Path) + args = parser.parse_args() + try: + if args.command == "generate": + manifest = build_identity_manifest( + behavior_manifest=load_json(args.behavior_manifest, "behavior manifest"), + database_fingerprint_path=args.database_fingerprint, + constitution_path=args.constitution, + database_identity_path=args.database_identity, + source_root=args.source_root, + source_commit=args.source_commit, + ) + write_json(args.output, manifest) + else: + manifest = load_json(args.manifest, "identity manifest") + validate_identity_manifest(manifest) + if args.source_root: + verify_bound_sources(manifest, args.source_root) + print(json.dumps({"status": "ok", "manifest_sha256": manifest["manifest_sha256"]})) + return 0 + except IdentityManifestError as exc: + print(json.dumps({"status": "error", "error": "identity_manifest_invalid", "message": str(exc)})) + return 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/leo_identity_profile.py b/scripts/leo_identity_profile.py new file mode 100644 index 0000000..11003ca --- /dev/null +++ b/scripts/leo_identity_profile.py @@ -0,0 +1,495 @@ +#!/usr/bin/env python3 +"""Compile, verify, and query a disposable Leo identity profile.""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import sys +import uuid +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +import yaml + +if __package__ in {None, ""}: + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from scripts import leo_behavior_manifest as behavior +from scripts import leo_identity_manifest as identity + +LOCK_SCHEMA = "livingip.leoIdentityProfileLock.v1" +COMPILE_RECEIPT_SCHEMA = "livingip.leoIdentityCompileReceipt.v1" +QUERY_RECEIPT_SCHEMA = "livingip.leoIdentityQueryReceipt.v1" +SESSION_RECORD_SCHEMA = "livingip.leoTemporarySessionRecord.v1" +FIXED_QUERY = "What defines Leo's identity, and what is temporary?" +STATIC_PROFILE_ENTRIES = ("config.yaml", "skills", "plugins", "bin") +COMPILED_PROFILE_ENTRIES = frozenset( + { + "config.yaml", + "skills", + "plugins", + "bin", + "SOUL.md", + "identity.json", + "identity-manifest.json", + "identity-lock.json", + "compile-receipt.json", + "sessions", + "state", + } +) +OMITTED_NONAUTHORITATIVE_ENTRIES = ( + "memories", + "MEMORY.md", + "USER.md", + "platforms/pairing", + ".skills_prompt_snapshot.json", + ".hermes.md", + "HERMES.md", + "AGENTS.md", + "CLAUDE.md", + ".cursorrules", +) + + +class IdentityProfileError(identity.IdentityManifestError): + """A compiled profile cannot be trusted or started.""" + + +def _require(condition: bool, message: str) -> None: + if not condition: + raise IdentityProfileError(message) + + +def _copy_static_profile(template: Path, target: Path) -> list[str]: + _require(template.is_dir(), "profile template is missing") + _require(not target.exists(), "compiled profile target already exists") + target.mkdir(parents=True, mode=0o700) + copied: list[str] = [] + for name in STATIC_PROFILE_ENTRIES: + source = template / name + if not source.exists(): + continue + destination = target / name + _require(not source.is_symlink(), f"unsafe symlinked static profile entry: {name}") + if source.is_dir(): + _require( + not any(path.is_symlink() for path in source.rglob("*")), + f"unsafe symlink inside static profile entry: {name}", + ) + shutil.copytree(source, destination, symlinks=False) + elif source.is_file() and not source.is_symlink(): + if name == "config.yaml": + try: + raw_config = yaml.safe_load(source.read_text(encoding="utf-8")) or {} + except (OSError, TypeError, yaml.YAMLError) as exc: + raise IdentityProfileError(f"cannot sanitize profile config: {type(exc).__name__}") from exc + _require(isinstance(raw_config, dict), "profile config must be a mapping") + destination.write_text( + yaml.safe_dump(behavior.identity_config_projection(raw_config), sort_keys=False), + encoding="utf-8", + ) + destination.chmod(0o600) + else: + shutil.copy2(source, destination) + else: + raise IdentityProfileError(f"unsafe static profile entry: {name}") + copied.append(name) + _require("config.yaml" in copied, "profile template config.yaml is missing") + return copied + + +def _behavior(profile: Path, hermes_root: Path, deployment_root: Path) -> dict[str, Any]: + return behavior.build_manifest(profile, hermes_root, deployment_root) + + +def _verify_runtime_binding( + manifest: dict[str, Any], + *, + profile: Path, + hermes_root: Path, + deployment_root: Path, +) -> dict[str, Any]: + observed = _behavior(profile, hermes_root, deployment_root) + identity.validate_behavior_manifest(observed) + expected = manifest["identity_inputs"]["runtime"] + _require( + observed["identity_runtime_sha256"] == expected["identity_runtime_sha256"], "identity runtime drift detected" + ) + _require(observed["hermes_runtime"]["git_head"] == expected["hermes_git_head"], "Hermes Git drift detected") + _require( + observed["hermes_runtime"]["source_tree"]["sha256"] == expected["hermes_source_sha256"], + "Hermes source drift detected", + ) + _require( + observed["teleo_infrastructure_runtime"]["git_head"] == expected["teleo_git_head"], + "Teleo Git drift detected", + ) + _require( + observed["teleo_infrastructure_runtime"]["identity_source_tree"]["sha256"] == expected["teleo_source_sha256"], + "Teleo identity source drift detected", + ) + _require(observed["python_runtime"] == expected["python"], "Python runtime drift detected") + _require( + manifest["identity_inputs"]["source_commit"] == observed["teleo_infrastructure_runtime"]["git_head"], + "source commit drift detected", + ) + + expected_model = manifest["identity_inputs"]["model"] + observed_model = observed["model_runtime"] + model_bindings = { + "provider": observed_model.get("provider"), + "default_model": observed_model.get("default_model"), + "smart_routing": observed_model.get("smart_routing"), + "smart_routing_model": observed_model.get("smart_routing_model"), + "gateway_smart_model_routing": observed_model.get("gateway_smart_model_routing"), + "model_section_sha256": observed_model.get("model_section_sha256"), + "hosted_weights": observed_model.get("base_model_weights"), + "actual_model_required_in_turn_receipt": observed_model.get( + "actual_per_turn_model_must_be_recorded_by_the_call_receipt" + ), + } + for field, observed_value in model_bindings.items(): + _require(expected_model.get(field) == observed_value, f"model runtime binding drift detected: {field}") + + expected_permissions = manifest["identity_inputs"]["permissions"] + permission_bindings = { + "identity_config_sha256": observed_model.get("identity_config_sha256"), + "gateway_permissions_sha256": observed_model.get("gateway_permissions_sha256"), + "tool_permissions_sha256": observed_model.get("tool_permissions_sha256"), + "runtime_policy": observed_model.get("identity_runtime_policy"), + "authorization_decision_required_in_runtime_receipt": True, + } + for field, observed_value in permission_bindings.items(): + _require( + expected_permissions.get(field) == observed_value, f"permission runtime binding drift detected: {field}" + ) + + observed_skills = observed["components"]["procedural_skills"]["content"] + expected_skills = manifest["identity_inputs"]["skills"] + _require(expected_skills["content_sha256"] == observed_skills["sha256"], "skills runtime binding drift detected") + _require(expected_skills["file_count"] == observed_skills["file_count"], "skills file count drift detected") + return observed + + +def _view_file_sha256(profile: Path, name: str) -> str: + path = profile / name + _require(path.is_file() and not path.is_symlink(), f"compiled view is missing or unsafe: {name}") + return behavior.file_sha256(path) + + +def _verify_nonauthoritative_surfaces( + profile: Path, + *, + require_empty_sessions: bool, + require_compile_receipt: bool, +) -> dict[str, Any]: + omitted = { + name: not (profile / name).exists() and not (profile / name).is_symlink() + for name in OMITTED_NONAUTHORITATIVE_ENTRIES + } + _require(all(omitted.values()), "profile contains a forbidden nonauthoritative input surface") + expected_entries = set(COMPILED_PROFILE_ENTRIES) + if not require_compile_receipt: + expected_entries.remove("compile-receipt.json") + profile_entries = list(profile.iterdir()) + actual_entries = {path.name for path in profile_entries} + _require(actual_entries == expected_entries, "compiled profile entry set is not exact") + _require(not any(path.is_symlink() for path in profile_entries), "compiled profile entries must not be symlinks") + if require_compile_receipt: + compile_receipt = identity.load_json(profile / "compile-receipt.json", "identity compile receipt") + _require(compile_receipt.get("schema") == COMPILE_RECEIPT_SCHEMA, "identity compile receipt schema is invalid") + _require(compile_receipt.get("status") == "pass", "identity compile receipt status is invalid") + state = profile / "state" + sessions = profile / "sessions" + for path, label in ((state, "state"), (sessions, "sessions")): + _require(path.is_dir() and not path.is_symlink(), f"profile {label} directory is missing or unsafe") + _require(not any(state.iterdir()), "profile state directory must remain empty") + session_files = list(sessions.iterdir()) + if require_empty_sessions: + _require(not session_files, "profile sessions must start empty") + for path in session_files: + _require(path.is_file() and not path.is_symlink() and path.suffix == ".json", "unsafe session entry detected") + record = identity.load_json(path, "temporary session record") + _require( + set(record) + == { + "schema", + "authority", + "classification", + "query_sha256", + "answer_sha256", + "may_be_used_as_evidence", + }, + "temporary session record fields are invalid", + ) + _require(record.get("schema") == SESSION_RECORD_SCHEMA, "temporary session record schema is invalid") + _require(record.get("authority") == "none", "temporary session authority is invalid") + _require(record.get("classification") == "temporary_noncanonical", "temporary session class is invalid") + _require(record.get("may_be_used_as_evidence") is False, "temporary session cannot be evidence") + _require(identity._is_sha256(record.get("query_sha256")), "temporary session query hash is invalid") + _require(identity._is_sha256(record.get("answer_sha256")), "temporary session answer hash is invalid") + return { + "nonauthoritative_inputs_omitted": omitted, + "session_file_count": len(session_files), + "state_empty": True, + } + + +def compile_profile( + manifest: dict[str, Any], + *, + source_root: Path, + profile_template: Path, + profile: Path, + hermes_root: Path, + deployment_root: Path, +) -> dict[str, Any]: + views = identity.verify_bound_sources(manifest, source_root) + template_behavior = _behavior(profile_template, hermes_root, deployment_root) + identity.validate_behavior_manifest(template_behavior) + _require( + template_behavior["identity_runtime_sha256"] + == manifest["identity_inputs"]["runtime"]["identity_runtime_sha256"], + "profile template does not match the pinned identity runtime", + ) + copied = _copy_static_profile(profile_template, profile) + try: + for name, content in views.items(): + path = profile / name + path.write_text(content, encoding="utf-8") + path.chmod(0o600) + identity.write_json(profile / "identity-manifest.json", manifest) + (profile / "identity-manifest.json").chmod(0o600) + for directory in (profile / "sessions", profile / "state"): + directory.mkdir(mode=0o700) + lock = { + "schema": LOCK_SCHEMA, + "manifest_sha256": manifest["manifest_sha256"], + "identity_inputs_sha256": manifest["identity_inputs_sha256"], + "compiled_views": {name: {"sha256": _view_file_sha256(profile, name)} for name in sorted(views)}, + "session_boundary": manifest["identity_inputs"]["session_boundary"], + } + identity.write_json(profile / "identity-lock.json", lock) + (profile / "identity-lock.json").chmod(0o600) + observed = _verify_runtime_binding( + manifest, + profile=profile, + hermes_root=hermes_root, + deployment_root=deployment_root, + ) + for name, expected in manifest["compiled_views"].items(): + _require(_view_file_sha256(profile, name) == expected["sha256"], f"compiled {name} hash mismatch") + soul_files = observed["components"]["runtime_identity"]["content"]["files"] + soul = next((item for item in soul_files if item.get("path") == "SOUL.md"), None) + _require( + isinstance(soul, dict) and soul.get("sha256") == manifest["compiled_views"]["SOUL.md"]["sha256"], + "SOUL runtime binding failed", + ) + surface_readback = _verify_nonauthoritative_surfaces( + profile, + require_empty_sessions=True, + require_compile_receipt=False, + ) + receipt = { + "schema": COMPILE_RECEIPT_SCHEMA, + "status": "pass", + "manifest_sha256": manifest["manifest_sha256"], + "identity_inputs_sha256": manifest["identity_inputs_sha256"], + "profile_static_entries": copied, + "compiled_view_sha256": { + name: manifest["compiled_views"][name]["sha256"] for name in sorted(manifest["compiled_views"]) + }, + "runtime_identity_sha256": observed["identity_runtime_sha256"], + "session_directories_started_empty": all( + not any((profile / name).iterdir()) for name in ("sessions", "state") + ), + "nonauthoritative_inputs_omitted": surface_readback["nonauthoritative_inputs_omitted"], + "generated_views_are_authority": False, + } + identity.write_json(profile / "compile-receipt.json", receipt) + return receipt + except BaseException: + shutil.rmtree(profile, ignore_errors=True) + raise + + +def verify_profile( + profile: Path, + *, + source_root: Path, + hermes_root: Path, + deployment_root: Path, +) -> tuple[dict[str, Any], dict[str, str], dict[str, Any]]: + manifest = identity.load_json(profile / "identity-manifest.json", "compiled identity manifest") + views = identity.verify_bound_sources(manifest, source_root) + lock = identity.load_json(profile / "identity-lock.json", "identity profile lock") + _verify_nonauthoritative_surfaces( + profile, + require_empty_sessions=False, + require_compile_receipt=True, + ) + _require(lock.get("schema") == LOCK_SCHEMA, "identity profile lock schema is invalid") + _require( + lock.get("manifest_sha256") == manifest["manifest_sha256"], "identity profile lock manifest drift detected" + ) + _require( + lock.get("identity_inputs_sha256") == manifest["identity_inputs_sha256"], + "identity profile lock input drift detected", + ) + compile_receipt = identity.load_json(profile / "compile-receipt.json", "identity compile receipt") + _require( + compile_receipt.get("manifest_sha256") == manifest["manifest_sha256"], + "identity compile receipt manifest drift detected", + ) + _require( + compile_receipt.get("identity_inputs_sha256") == manifest["identity_inputs_sha256"], + "identity compile receipt input drift detected", + ) + for name, expected_content in views.items(): + expected_hash = behavior.sha256_bytes(expected_content.encode("utf-8")) + _require(expected_hash == manifest["compiled_views"][name]["sha256"], f"manifest view drift detected: {name}") + _require(_view_file_sha256(profile, name) == expected_hash, f"compiled view drift detected: {name}") + _require( + (lock.get("compiled_views") or {}).get(name, {}).get("sha256") == expected_hash, + f"profile lock view drift detected: {name}", + ) + observed = _verify_runtime_binding( + manifest, + profile=profile, + hermes_root=hermes_root, + deployment_root=deployment_root, + ) + return manifest, views, observed + + +def query_profile( + profile: Path, + *, + query: str, + source_root: Path, + hermes_root: Path, + deployment_root: Path, +) -> dict[str, Any]: + _require(query == FIXED_QUERY, "only the pinned identity readback query is allowed") + manifest, _views, observed = verify_profile( + profile, + source_root=source_root, + hermes_root=hermes_root, + deployment_root=deployment_root, + ) + structured = identity.load_json(profile / "identity.json", "compiled structured identity view") + rule_count = len(structured["static_constitution"]["rules"]) + record_count = len(structured["database_identity"]["records"]) + fingerprint = structured["database_identity"]["database_fingerprint_sha256"] + canonical_authority = structured["database_identity"]["canonical_authority_granted"] + database_description = "canonical database" if canonical_authority else "synthetic noncanonical fixture" + answer = ( + f"Leo is defined by {rule_count} pinned static constitutional rules and {record_count} active " + f"database-derived identity rows from a {database_description} at fingerprint {fingerprint}. " + "SOUL.md is a generated view; " + "runtime/session memory is temporary, noncanonical, and cannot be evidence." + ) + query_sha256 = behavior.sha256_bytes(query.encode("utf-8")) + answer_sha256 = behavior.sha256_bytes(answer.encode("utf-8")) + instance_id = uuid.uuid4().hex + session_record = { + "schema": SESSION_RECORD_SCHEMA, + "authority": "none", + "classification": "temporary_noncanonical", + "query_sha256": query_sha256, + "answer_sha256": answer_sha256, + "may_be_used_as_evidence": False, + } + session_path = profile / "sessions" / f"{instance_id}.json" + identity.write_json(session_path, session_record) + receipt = { + "schema": QUERY_RECEIPT_SCHEMA, + "status": "pass", + "started_at_utc": datetime.now(UTC).isoformat(), + "process_id": os.getpid(), + "runtime_instance_id": instance_id, + "query": query, + "query_sha256": query_sha256, + "answer": answer, + "answer_sha256": answer_sha256, + "manifest_sha256": manifest["manifest_sha256"], + "identity_inputs_sha256": manifest["identity_inputs_sha256"], + "output_provenance": { + "static_constitution_sha256": manifest["identity_inputs"]["identity_sources"]["static_constitution"][ + "semantic_sha256" + ], + "database_identity_sha256": manifest["identity_inputs"]["identity_sources"]["database_derived_identity"][ + "semantic_sha256" + ], + "database_fingerprint_sha256": fingerprint, + "database_authority": structured["database_identity"]["authority"], + "database_canonical_authority_granted": canonical_authority, + "compiled_identity_sha256": manifest["compiled_views"]["identity.json"]["sha256"], + "compiled_soul_sha256": manifest["compiled_views"]["SOUL.md"]["sha256"], + "runtime_identity_sha256": observed["identity_runtime_sha256"], + "actual_model_call_performed": False, + }, + "authorization": { + "declared_runtime_policy": manifest["identity_inputs"]["permissions"]["runtime_policy"], + "authorization_decision_observed": False, + "claim": "local_policy_binding_not_an_authorizer_readback", + }, + "session": { + "record_sha256": behavior.file_sha256(session_path), + "classification": "temporary_noncanonical", + "included_in_output_provenance": False, + "may_be_used_as_evidence": False, + }, + } + return receipt + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + subparsers = parser.add_subparsers(dest="command", required=True) + compile_command = subparsers.add_parser("compile") + compile_command.add_argument("--manifest", type=Path, required=True) + compile_command.add_argument("--source-root", type=Path, required=True) + compile_command.add_argument("--profile-template", type=Path, required=True) + compile_command.add_argument("--profile", type=Path, required=True) + compile_command.add_argument("--hermes-root", type=Path, required=True) + compile_command.add_argument("--deployment-root", type=Path, required=True) + query_command = subparsers.add_parser("query") + query_command.add_argument("--profile", type=Path, required=True) + query_command.add_argument("--query", default=FIXED_QUERY) + query_command.add_argument("--source-root", type=Path, required=True) + query_command.add_argument("--hermes-root", type=Path, required=True) + query_command.add_argument("--deployment-root", type=Path, required=True) + args = parser.parse_args() + try: + if args.command == "compile": + result = compile_profile( + identity.load_json(args.manifest, "identity manifest"), + source_root=args.source_root, + profile_template=args.profile_template, + profile=args.profile, + hermes_root=args.hermes_root, + deployment_root=args.deployment_root, + ) + else: + result = query_profile( + args.profile, + query=args.query, + source_root=args.source_root, + hermes_root=args.hermes_root, + deployment_root=args.deployment_root, + ) + print(json.dumps(result, sort_keys=True)) + return 0 + except identity.IdentityManifestError as exc: + print(json.dumps({"status": "error", "error": "identity_drift", "message": str(exc)})) + return 3 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/run_leo_identity_reconstruction_canary.py b/scripts/run_leo_identity_reconstruction_canary.py new file mode 100644 index 0000000..2850804 --- /dev/null +++ b/scripts/run_leo_identity_reconstruction_canary.py @@ -0,0 +1,439 @@ +#!/usr/bin/env python3 +"""Prove manifest-driven Leo identity reconstruction across a cold restart.""" + +from __future__ import annotations + +import argparse +import json +import os +import shutil +import signal +import subprocess +import sys +import tempfile +import time +from datetime import UTC, datetime +from pathlib import Path +from typing import Any + +if __package__ in {None, ""}: + sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from scripts import leo_behavior_manifest as behavior +from scripts import leo_identity_manifest as identity +from scripts import leo_identity_profile as profile_runtime + +SCHEMA = "livingip.leoIdentityReconstructionReceipt.v1" + + +class CanaryError(RuntimeError): + """The disposable identity runtime did not satisfy its T2 contract.""" + + +def _require(condition: bool, message: str) -> None: + if not condition: + raise CanaryError(message) + + +def _process_group_absent(process_group: int) -> bool: + try: + os.killpg(process_group, 0) + except ProcessLookupError: + return True + except PermissionError: + return False + return False + + +def _wait_for_process_group_absence(process_group: int, timeout_seconds: float = 5) -> bool: + deadline = time.monotonic() + timeout_seconds + while time.monotonic() < deadline: + if _process_group_absent(process_group): + return True + time.sleep(0.02) + return _process_group_absent(process_group) + + +def _signal_process_group(process_group: int, signal_number: signal.Signals) -> bool: + try: + os.killpg(process_group, signal_number) + return True + except (ProcessLookupError, PermissionError): + return False + + +def _cleanup_remaining_process_group(process_group: int) -> str | None: + if not _signal_process_group(process_group, signal.SIGTERM): + return None + if _wait_for_process_group_absence(process_group, timeout_seconds=1): + return "SIGTERM" + if not _signal_process_group(process_group, signal.SIGKILL): + return "SIGTERM" + return "SIGKILL" + + +def _run_query_child( + *, + deployment_root: Path, + profile: Path, + source_root: Path, + hermes_root: Path, + timeout_seconds: float = 60, +) -> dict[str, Any]: + command = [ + sys.executable, + "-m", + "scripts.leo_identity_profile", + "query", + "--profile", + str(profile), + "--source-root", + str(source_root), + "--hermes-root", + str(hermes_root), + "--deployment-root", + str(deployment_root), + "--query", + profile_runtime.FIXED_QUERY, + ] + process = subprocess.Popen( + command, + cwd=deployment_root, + env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"}, + text=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + start_new_session=True, + ) + timed_out = False + terminated_with: str | None = None + stdout = "" + stderr = "" + process_group_absent_after_wait = False + orphan_cleanup_required = False + try: + try: + stdout, stderr = process.communicate(timeout=timeout_seconds) + except subprocess.TimeoutExpired: + timed_out = True + if _signal_process_group(process.pid, signal.SIGTERM): + terminated_with = "SIGTERM" + try: + stdout, stderr = process.communicate(timeout=5) + except subprocess.TimeoutExpired: + if _signal_process_group(process.pid, signal.SIGKILL): + terminated_with = "SIGKILL" + stdout, stderr = process.communicate(timeout=5) + finally: + if process.poll() is None: + if _signal_process_group(process.pid, signal.SIGTERM): + terminated_with = terminated_with or "SIGTERM" + try: + process.communicate(timeout=5) + except subprocess.TimeoutExpired: + if _signal_process_group(process.pid, signal.SIGKILL): + terminated_with = "SIGKILL" + process.communicate(timeout=5) + orphan_cleanup_required = not _process_group_absent(process.pid) + if orphan_cleanup_required: + terminated_with = _cleanup_remaining_process_group(process.pid) or terminated_with + process_group_absent_after_wait = _wait_for_process_group_absence(process.pid) + try: + payload = json.loads(stdout.strip().splitlines()[-1]) if stdout.strip() else {} + except json.JSONDecodeError as exc: + raise CanaryError("identity child returned invalid JSON") from exc + return { + "command": command, + "launcher_pid": process.pid, + "returncode": process.returncode, + "timed_out": timed_out, + "terminated_with": terminated_with, + "orphan_cleanup_required": orphan_cleanup_required, + "stdout": payload, + "stderr_sha256": behavior.sha256_bytes(stderr.encode("utf-8")), + "process_group_absent_after_wait": process_group_absent_after_wait, + } + + +def _query_passed(child: dict[str, Any]) -> bool: + payload = child.get("stdout") or {} + return bool( + child.get("returncode") == 0 + and child.get("timed_out") is False + and child.get("orphan_cleanup_required") is False + and child.get("process_group_absent_after_wait") is True + and payload.get("schema") == profile_runtime.QUERY_RECEIPT_SCHEMA + and payload.get("status") == "pass" + ) + + +def _drift_attempt( + *, + deployment_root: Path, + source_root: Path, + hermes_root: Path, + drift_profile: Path, +) -> dict[str, Any]: + with (drift_profile / "SOUL.md").open("a", encoding="utf-8") as handle: + handle.write("\nInjected drift must fail closed.\n") + child = _run_query_child( + deployment_root=deployment_root, + profile=drift_profile, + source_root=source_root, + hermes_root=hermes_root, + ) + child["fail_closed"] = bool( + child["returncode"] == 3 + and child["timed_out"] is False + and child["orphan_cleanup_required"] is False + and child["process_group_absent_after_wait"] is True + and (child.get("stdout") or {}).get("error") == "identity_drift" + and "compiled view drift detected" in str((child.get("stdout") or {}).get("message")) + ) + child["answer_returned"] = bool((child.get("stdout") or {}).get("answer")) + return child + + +def run_canary(args: argparse.Namespace) -> tuple[dict[str, Any], int]: + output = args.output.resolve() + temporary_root = Path(tempfile.mkdtemp(prefix="leo-identity-reconstruction-")) + report: dict[str, Any] = { + "schema": SCHEMA, + "generated_at_utc": datetime.now(UTC).isoformat(), + "required_tier": "T2_runtime", + "mode": "drift_before_restart" + if args.inject_drift_before_restart + else "successful_restart_plus_drift_negative", + "fixed_query": profile_runtime.FIXED_QUERY, + "fixed_query_sha256": behavior.sha256_bytes(profile_runtime.FIXED_QUERY.encode("utf-8")), + "production_profile_replaced": False, + "production_database_contacted": False, + "telegram_message_sent": False, + "actual_hosted_model_call_performed": False, + "errors": [], + } + exit_code = 1 + try: + behavior_manifest = behavior.build_manifest(args.profile_template, args.hermes_root, args.deployment_root) + identity.validate_behavior_manifest(behavior_manifest) + manifest = identity.build_identity_manifest( + behavior_manifest=behavior_manifest, + database_fingerprint_path=args.database_fingerprint, + constitution_path=args.constitution, + database_identity_path=args.database_identity, + source_root=args.source_root, + ) + report["manifest"] = manifest + report["behavior_observation_before_compile"] = { + "behavior_sha256": behavior_manifest["behavior_sha256"], + "identity_runtime_sha256": behavior_manifest["identity_runtime_sha256"], + "source_commit": behavior_manifest["teleo_infrastructure_runtime"]["git_head"], + } + compiled_profile = temporary_root / "first-profile" + report["compile"] = profile_runtime.compile_profile( + manifest, + source_root=args.source_root, + profile_template=args.profile_template, + profile=compiled_profile, + hermes_root=args.hermes_root, + deployment_root=args.deployment_root, + ) + compiled_behavior_before_query = behavior.build_manifest( + compiled_profile, args.hermes_root, args.deployment_root + ) + report["compiled_profile_before_query"] = { + "behavior_sha256": compiled_behavior_before_query["behavior_sha256"], + "identity_runtime_sha256": compiled_behavior_before_query["identity_runtime_sha256"], + } + first = _run_query_child( + deployment_root=args.deployment_root, + profile=compiled_profile, + source_root=args.source_root, + hermes_root=args.hermes_root, + ) + report["first_start"] = first + _require(_query_passed(first), "first disposable identity process did not answer successfully") + after_first = behavior.build_manifest(compiled_profile, args.hermes_root, args.deployment_root) + report["session_boundary_readback"] = { + "full_behavior_changed_after_session": after_first["behavior_sha256"] + != compiled_behavior_before_query["behavior_sha256"], + "identity_runtime_unchanged_after_session": after_first["identity_runtime_sha256"] + == compiled_behavior_before_query["identity_runtime_sha256"], + "session_file_count": len(list((compiled_profile / "sessions").glob("*.json"))), + "session_classification": "temporary_noncanonical", + "session_used_as_output_provenance": False, + } + if args.inject_drift_before_restart: + with (compiled_profile / "SOUL.md").open("a", encoding="utf-8") as handle: + handle.write("\nInjected pre-restart drift.\n") + rejected = _run_query_child( + deployment_root=args.deployment_root, + profile=compiled_profile, + source_root=args.source_root, + hermes_root=args.hermes_root, + ) + report["pre_restart_drift"] = rejected + report["drift_target"] = "compiled SOUL.md generated view" + report["second_start_attempted"] = False + report["gates"] = { + "first_start_passed": True, + "first_process_stopped": first["process_group_absent_after_wait"], + "drift_detected_before_restart": rejected["returncode"] == 3 + and (rejected.get("stdout") or {}).get("error") == "identity_drift", + "drift_returned_no_answer": not bool((rejected.get("stdout") or {}).get("answer")), + "drift_process_stopped": rejected["process_group_absent_after_wait"], + "drift_process_had_no_orphan_cleanup": rejected["orphan_cleanup_required"] is False, + "second_start_not_attempted": True, + } + report["status"] = "pass" if all(report["gates"].values()) else "fail" + else: + profile_runtime.verify_profile( + compiled_profile, + source_root=args.source_root, + hermes_root=args.hermes_root, + deployment_root=args.deployment_root, + ) + report["pre_restart_verification"] = "pass" + report["second_start_attempted"] = True + restarted_profile = temporary_root / "restart-profile" + report["restart_compile"] = profile_runtime.compile_profile( + manifest, + source_root=args.source_root, + profile_template=args.profile_template, + profile=restarted_profile, + hermes_root=args.hermes_root, + deployment_root=args.deployment_root, + ) + profile_runtime.verify_profile( + restarted_profile, + source_root=args.source_root, + hermes_root=args.hermes_root, + deployment_root=args.deployment_root, + ) + second = _run_query_child( + deployment_root=args.deployment_root, + profile=restarted_profile, + source_root=args.source_root, + hermes_root=args.hermes_root, + ) + report["restart"] = second + _require(_query_passed(second), "restarted disposable identity process did not answer successfully") + first_payload = first["stdout"] + second_payload = second["stdout"] + drift_profile = temporary_root / "drift-profile" + report["drift_compile"] = profile_runtime.compile_profile( + manifest, + source_root=args.source_root, + profile_template=args.profile_template, + profile=drift_profile, + hermes_root=args.hermes_root, + deployment_root=args.deployment_root, + ) + drift = _drift_attempt( + deployment_root=args.deployment_root, + source_root=args.source_root, + hermes_root=args.hermes_root, + drift_profile=drift_profile, + ) + report["drift_negative"] = drift + report["drift_target"] = "compiled SOUL.md generated view" + report["gates"] = { + "manifest_generated": identity._is_sha256(manifest["manifest_sha256"]), + "views_compiled_and_bound": report["compile"]["status"] == "pass", + "first_start_passed": True, + "first_process_stopped": first["process_group_absent_after_wait"], + "pre_restart_verification_passed": True, + "restart_profile_recompiled_from_manifest": report["restart_compile"]["status"] == "pass", + "restart_profile_started_without_sessions": report["restart_compile"][ + "session_directories_started_empty" + ], + "restart_passed": True, + "restart_process_is_distinct": first_payload["process_id"] != second_payload["process_id"] + and first_payload["runtime_instance_id"] != second_payload["runtime_instance_id"], + "identity_inputs_reproduced": first_payload["identity_inputs_sha256"] + == second_payload["identity_inputs_sha256"], + "manifest_reproduced": first_payload["manifest_sha256"] == second_payload["manifest_sha256"], + "query_reproduced": first_payload["query_sha256"] == second_payload["query_sha256"], + "answer_and_provenance_explainable": first_payload["answer_sha256"] == second_payload["answer_sha256"] + and first_payload["output_provenance"] == second_payload["output_provenance"], + "session_excluded_from_identity": report["session_boundary_readback"][ + "identity_runtime_unchanged_after_session" + ], + "drift_failed_closed": drift["fail_closed"] and not drift["answer_returned"], + "drift_profile_started_without_sessions": report["drift_compile"]["session_directories_started_empty"], + "drift_process_stopped": drift["process_group_absent_after_wait"], + "drift_process_had_no_orphan_cleanup": drift["orphan_cleanup_required"] is False, + "restart_process_stopped": second["process_group_absent_after_wait"], + } + report["status"] = "pass" if all(report["gates"].values()) else "fail" + if report["status"] == "pass": + exit_code = 0 + except (CanaryError, identity.IdentityManifestError, OSError, subprocess.SubprocessError) as exc: + report["status"] = "fail" + report["errors"].append({"type": type(exc).__name__, "message": str(exc)}) + finally: + shutil.rmtree(temporary_root, ignore_errors=True) + report["cleanup"] = { + "temporary_root_removed": not temporary_root.exists(), + "no_profile_retained": not temporary_root.exists(), + } + if not all(report["cleanup"].values()): + report["status"] = "fail" + exit_code = 1 + positive_restart_passed = report.get("status") == "pass" and not args.inject_drift_before_restart + report["current_tier"] = "T2_runtime" if positive_restart_passed else "T1_model" + report["goal_tier_satisfied"] = positive_restart_passed + report["runtime_component_proven"] = ( + "fresh_profile_recompile_plus_distinct_process_restart" + if positive_restart_passed + else "first_local_process_plus_pre_restart_drift_refusal" + ) + report["runtime_variant"] = "local_deterministic_identity_compiler_readback" + report["tier_basis"] = ( + "Capability Tier Proof defines T2_runtime as local API/runtime behavior with restart where relevant; " + "this is compiler-process runtime proof and explicitly excludes GatewayRunner and hosted-model proof." + if positive_restart_passed + else "This negative component does not complete the required restart path, so it remains below T2_runtime." + ) + report["claim_ceiling"] = ( + "Local disposable identity compilation, fresh-profile reconstruction, and cold-process restart only; " + "not a live GatewayRunner, hosted-model, Telegram, production database, VPS restart, or T3 proof." + if positive_restart_passed + else "Local first-process and pre-restart drift-refusal component only; no successful restart proof, " + "GatewayRunner, hosted model, production database, VPS, or T3 claim." + ) + stable = {key: value for key, value in report.items() if key != "receipt_sha256"} + report["receipt_sha256"] = behavior.canonical_sha256(stable) + identity.write_json(output, report) + return report, exit_code + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--profile-template", type=Path, required=True) + parser.add_argument("--hermes-root", type=Path, required=True) + parser.add_argument("--deployment-root", type=Path, required=True) + parser.add_argument("--source-root", type=Path, required=True) + parser.add_argument("--database-fingerprint", type=Path, required=True) + parser.add_argument("--constitution", type=Path, required=True) + parser.add_argument("--database-identity", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--inject-drift-before-restart", action="store_true") + args = parser.parse_args() + report, exit_code = run_canary(args) + print( + json.dumps( + { + "status": report.get("status"), + "current_tier": report.get("current_tier"), + "receipt_sha256": report.get("receipt_sha256"), + "output": str(args.output), + }, + sort_keys=True, + ) + ) + return exit_code + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tests/test_leo_behavior_manifest.py b/tests/test_leo_behavior_manifest.py index 5b87992..f8959c2 100644 --- a/tests/test_leo_behavior_manifest.py +++ b/tests/test_leo_behavior_manifest.py @@ -4,6 +4,8 @@ import json import subprocess from pathlib import Path +import pytest + from scripts import leo_behavior_manifest as manifest ROOT = Path(__file__).resolve().parents[1] @@ -58,6 +60,11 @@ gateway: assert '{"private":"conversation"}' not in encoded +def test_identity_config_rejects_nonboolean_smart_model_routing() -> None: + with pytest.raises(ValueError, match="smart_model_routing must be boolean"): + manifest.identity_config_projection({"smart_model_routing": {"route": "untrusted"}}) + + def test_behavior_manifest_changes_when_a_behavior_layer_changes(tmp_path: Path) -> None: profile = tmp_path / "profile" hermes = tmp_path / "hermes" @@ -156,3 +163,64 @@ def test_git_state_marks_untracked_files_dirty(tmp_path: Path) -> None: assert state["worktree_clean"] is False assert len(str(state["status_sha256"])) == 64 + + +def test_identity_runtime_omits_secret_values_but_pins_semantic_model_settings(tmp_path: Path) -> None: + profile = tmp_path / "profile" + hermes = tmp_path / "hermes" + deployment = tmp_path / "deployment" + config = """model: + provider: fixture + default: identity-v1 + max_tokens: 512 +gateway: + telegram: + bot_token: token-a + botToken: camel-token-a +provider: + apiKey: camel-api-key-a +transport: + endpoint: https://fixture-user:fixture-password-a@example.invalid/v1 + headers: ['Authorization: Bearer fixture-header-a'] +tools: + allowed: [identity_readback] +identity_runtime: + network_send_enabled: false + database_write_enabled: false + allowed_tools: [identity_readback] +""" + _write(profile / "config.yaml", config) + _write(profile / "skills" / "identity" / "SKILL.md", "identity\n") + _write(profile / "plugins" / "identity" / "__init__.py", "BOUNDARY = True\n") + _write(profile / "bin" / "identity.py", "READ_ONLY = True\n") + _write(hermes / "run_agent.py", "runtime\n") + _write(hermes / "agent" / "prompt_builder.py", "prompts\n") + for name in ( + "leo_behavior_manifest.py", + "leo_identity_manifest.py", + "leo_identity_profile.py", + "run_leo_identity_reconstruction_canary.py", + ): + _write(deployment / "scripts" / name, f"# {name}\n") + + before = manifest.build_manifest(profile, hermes, deployment) + _write( + profile / "config.yaml", + config.replace("token-a", "token-b") + .replace("camel-api-key-a", "camel-api-key-b") + .replace("fixture-password-a", "fixture-password-b") + .replace("fixture-header-a", "fixture-header-b"), + ) + secret_rotated = manifest.build_manifest(profile, hermes, deployment) + _write(profile / "config.yaml", config.replace("max_tokens: 512", "max_tokens: 1024")) + model_changed = manifest.build_manifest(profile, hermes, deployment) + + assert before["behavior_sha256"] != secret_rotated["behavior_sha256"] + assert before["identity_runtime_sha256"] == secret_rotated["identity_runtime_sha256"] + assert before["identity_runtime_sha256"] != model_changed["identity_runtime_sha256"] + encoded = json.dumps(before) + assert "token-a" not in encoded + assert "camel-token-a" not in encoded + assert "camel-api-key-a" not in encoded + assert "fixture-password-a" not in encoded + assert "fixture-header-a" not in encoded diff --git a/tests/test_leo_identity_manifest.py b/tests/test_leo_identity_manifest.py new file mode 100644 index 0000000..35dd4d6 --- /dev/null +++ b/tests/test_leo_identity_manifest.py @@ -0,0 +1,721 @@ +from __future__ import annotations + +import argparse +import copy +import json +import os +import subprocess +import time +from pathlib import Path + +import pytest + +from scripts import leo_behavior_manifest as behavior +from scripts import leo_identity_manifest as identity +from scripts import leo_identity_profile as profile_runtime +from scripts import run_leo_identity_reconstruction_canary as canary + +ROOT = Path(__file__).resolve().parents[1] + + +def _write(path: Path, value: str) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(value, encoding="utf-8") + + +def _write_json(path: Path, value: dict) -> None: + _write(path, json.dumps(value, indent=2, sort_keys=True) + "\n") + + +def _fixture(tmp_path: Path) -> dict[str, Path | dict]: + repo = tmp_path / "repo" + profile = repo / "profile-template" + hermes = repo / "hermes-runtime" + compiled = tmp_path / "compiled-profile" + _write( + profile / "config.yaml", + """model: + provider: fixture-local + default: identity-v1 + max_tokens: 512 +memory: + enabled: false +gateway: + execution_mode: local_no_send + telegram: + bot_token: never-emit-this-fixture-value +transport: + endpoint: https://fixture-user:fixture-password@example.invalid/v1 + headers: ['Authorization: Bearer fixture-secret'] +tools: + allowed: [identity_readback] + write_enabled: false +identity_runtime: + network_send_enabled: false + database_write_enabled: false + allowed_tools: [identity_readback] +""", + ) + _write(profile / "skills" / "identity" / "SKILL.md", "# identity readback\n") + _write(profile / "plugins" / "identity" / "__init__.py", "BOUNDARY = True\n") + _write(profile / "bin" / "identity_readback.py", "READ_ONLY = True\n") + _write(hermes / "run_agent.py", "RUNTIME = 'identity-v1'\n") + _write(hermes / "agent" / "prompt_builder.py", "SOUL_IS_GENERATED = True\n") + _write(hermes / "gateway" / "run.py", "SESSION_IS_IDENTITY = False\n") + _write(hermes / "hermes_cli" / "tools_config.py", "TOOLS = ('identity_readback',)\n") + _write(hermes / "tools" / "registry.py", "READ_ONLY = True\n") + for name in ( + "leo_behavior_manifest.py", + "leo_identity_manifest.py", + "leo_identity_profile.py", + "run_leo_identity_reconstruction_canary.py", + ): + _write(repo / "scripts" / name, (ROOT / "scripts" / name).read_text(encoding="utf-8")) + + fingerprint_path = repo / "fixtures" / "database-fingerprint.json" + constitution_path = repo / "fixtures" / "constitution.json" + database_identity_path = repo / "fixtures" / "database-identity.json" + fingerprint = { + "schema": identity.DATABASE_FINGERPRINT_SCHEMA, + "status": "ok", + "environment": "disposable_fixture", + "fingerprint_sha256": "1" * 64, + "table_rows_sha256": "2" * 64, + "structure_sha256": "3" * 64, + "table_count": 2, + "total_rows": 2, + "read_consistency": { + "status": "stable_wal_marker", + "before": { + "database": "fixture", + "database_user": "reader", + "system_identifier": "12345", + "wal_lsn": "0/1", + }, + "after": { + "database": "fixture", + "database_user": "reader", + "system_identifier": "12345", + "wal_lsn": "0/1", + }, + }, + } + constitution = { + "schema": identity.CONSTITUTION_SCHEMA, + "identity_name": "Leo", + "rules": [ + {"id": "evidence", "text": "Never use temporary session memory as canonical evidence."}, + {"id": "truth", "text": "Fail closed when a pinned identity input drifts."}, + ], + } + database_identity = { + "schema": identity.DATABASE_IDENTITY_SCHEMA, + "identity_name": "Leo", + "database_fingerprint_sha256": "1" * 64, + "source_provenance": { + "mode": identity.SYNTHETIC_DATABASE_MODE, + "canonical_authority_granted": False, + "same_transaction_as_fingerprint": False, + "export_receipt_sha256": None, + }, + "records": [ + { + "table": "public.personas", + "row_id": "persona-1", + "kind": "persona", + "rank": 0, + "text": "Leo is an evidence-bound reasoning interface.", + "status": "active", + "evidence_sha256": "a" * 64, + }, + { + "table": "public.beliefs", + "row_id": "belief-1", + "kind": "belief", + "rank": 0, + "text": "Exact provenance is stronger than plausible recollection.", + "status": "active", + "evidence_sha256": "b" * 64, + }, + ], + } + _write_json(fingerprint_path, fingerprint) + _write_json(constitution_path, constitution) + _write_json(database_identity_path, database_identity) + + subprocess.run(["git", "init", "-q", str(repo)], check=True) + subprocess.run(["git", "-C", str(repo), "config", "user.email", "test@example.com"], check=True) + subprocess.run(["git", "-C", str(repo), "config", "user.name", "Test"], check=True) + subprocess.run(["git", "-C", str(repo), "add", "."], check=True) + subprocess.run( + ["git", "-C", str(repo), "commit", "-qm", "fixture"], + check=True, + env={ + **dict(__import__("os").environ), + "GIT_AUTHOR_DATE": "2026-01-01T00:00:00Z", + "GIT_COMMITTER_DATE": "2026-01-01T00:00:00Z", + }, + ) + behavior_manifest = behavior.build_manifest(profile, hermes, repo) + manifest = identity.build_identity_manifest( + behavior_manifest=behavior_manifest, + database_fingerprint_path=fingerprint_path, + constitution_path=constitution_path, + database_identity_path=database_identity_path, + source_root=repo, + ) + return { + "repo": repo, + "profile": profile, + "hermes": hermes, + "compiled": compiled, + "fingerprint": fingerprint_path, + "constitution": constitution_path, + "database_identity": database_identity_path, + "behavior": behavior_manifest, + "manifest": manifest, + } + + +def _fully_rehash_manifest(fixture: dict[str, Path | dict], manifest: dict) -> None: + identity_hash = behavior.canonical_sha256(manifest["identity_inputs"]) + manifest["identity_inputs_sha256"] = identity_hash + fingerprint = identity.load_json(fixture["fingerprint"], "database fingerprint") + rules = identity.canonical_rules(identity.load_json(fixture["constitution"], "constitution")) + records = identity.canonical_database_records( + identity.load_json(fixture["database_identity"], "database identity"), + fingerprint_sha256=fingerprint["fingerprint_sha256"], + ) + database_provenance = identity.database_identity_provenance( + identity.load_json(fixture["database_identity"], "database identity"), + fingerprint=fingerprint, + ) + rendered = identity.render_identity_views( + identity_inputs_sha256=identity_hash, + database_fingerprint_sha256=fingerprint["fingerprint_sha256"], + database_provenance=database_provenance, + rules=rules, + records=records, + ) + for name, content in rendered.items(): + manifest["compiled_views"][name]["sha256"] = behavior.sha256_bytes(content.encode("utf-8")) + manifest["compiled_views"][name]["generated_from_identity_inputs_sha256"] = identity_hash + stable = {key: value for key, value in manifest.items() if key != "manifest_sha256"} + manifest["manifest_sha256"] = behavior.canonical_sha256(stable) + + +def _rehash_behavior_manifest(manifest: dict) -> None: + manifest["identity_runtime_sha256"] = behavior.canonical_sha256(behavior.identity_runtime_payload(manifest)) + manifest["static_runtime_sha256"] = behavior.canonical_sha256(behavior.static_runtime_payload(manifest)) + manifest["behavior_sha256"] = behavior.canonical_sha256(behavior.stable_manifest_payload(manifest)) + + +def _canary_args(fixture: dict[str, Path | dict], output: Path, *, inject_drift: bool = False) -> argparse.Namespace: + return argparse.Namespace( + profile_template=fixture["profile"], + hermes_root=fixture["hermes"], + deployment_root=fixture["repo"], + source_root=fixture["repo"], + database_fingerprint=fixture["fingerprint"], + constitution=fixture["constitution"], + database_identity=fixture["database_identity"], + output=output, + inject_drift_before_restart=inject_drift, + ) + + +def test_manifest_compiles_generated_views_and_reproduces_identity_across_queries(tmp_path: Path) -> None: + fixture = _fixture(tmp_path) + manifest = fixture["manifest"] + assert isinstance(manifest, dict) + rebuilt = identity.build_identity_manifest( + behavior_manifest=fixture["behavior"], + database_fingerprint_path=fixture["fingerprint"], + constitution_path=fixture["constitution"], + database_identity_path=fixture["database_identity"], + source_root=fixture["repo"], + ) + assert rebuilt == manifest + + compile_receipt = profile_runtime.compile_profile( + manifest, + source_root=fixture["repo"], + profile_template=fixture["profile"], + profile=fixture["compiled"], + hermes_root=fixture["hermes"], + deployment_root=fixture["repo"], + ) + first = profile_runtime.query_profile( + fixture["compiled"], + query=profile_runtime.FIXED_QUERY, + source_root=fixture["repo"], + hermes_root=fixture["hermes"], + deployment_root=fixture["repo"], + ) + second = profile_runtime.query_profile( + fixture["compiled"], + query=profile_runtime.FIXED_QUERY, + source_root=fixture["repo"], + hermes_root=fixture["hermes"], + deployment_root=fixture["repo"], + ) + + assert compile_receipt["status"] == "pass" + assert "never-emit-this-fixture-value" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8") + assert "fixture-password" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8") + assert "fixture-secret" not in (fixture["compiled"] / "config.yaml").read_text(encoding="utf-8") + assert (fixture["compiled"] / "SOUL.md").read_text(encoding="utf-8").startswith("