From 635daec6b4265ccbd066b96b53228919ac4cbd79 Mon Sep 17 00:00:00 2001 From: codex Date: Thu, 15 Oct 2026 01:57:25 +0200 Subject: [PATCH] Prepare reviewed attended reuse-reader custody execution Assistant: codex Assistant-Model: gpt-6.1-sol Assistant-Session: 01a137ca-5c77-7a71-b9ae-23ad9b0b5684 --- .../attended-custody-handoff-r1.json | 93 +++++++ ...ional-approved-ccr-state-candidate-r1.json | 13 + .../platform-attended-runner-review-r1.json | 18 ++ scripts/apply-reuse-surface-reader-custody.py | 242 ++++++++++++++++++ ...test_reuse_surface_reader_custody_apply.py | 116 +++++++++ ...RPF-WP-0058-reuse-private-source-reader.md | 14 + 6 files changed, 496 insertions(+) create mode 100644 docs/proposals/2026-10-15-reuse-private-reader/attended-custody-handoff-r1.json create mode 100644 docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json create mode 100644 docs/proposals/2026-10-15-reuse-private-reader/platform-attended-runner-review-r1.json create mode 100755 scripts/apply-reuse-surface-reader-custody.py create mode 100644 tests/test_reuse_surface_reader_custody_apply.py diff --git a/docs/proposals/2026-10-15-reuse-private-reader/attended-custody-handoff-r1.json b/docs/proposals/2026-10-15-reuse-private-reader/attended-custody-handoff-r1.json new file mode 100644 index 0000000..449267d --- /dev/null +++ b/docs/proposals/2026-10-15-reuse-private-reader/attended-custody-handoff-r1.json @@ -0,0 +1,93 @@ +{ + "id": "CCR-2026-0046-ATTENDED-CUSTODY-HANDOFF-R3", + "revision": 3, + "work_item": "RPF-WP-0058-T03", + "status": "prepared; founder/live execution approval pending", + "source_published_commit": "6e8955fcd09b278a6f16c14beffd076b72d10c95", + "consumer_image_digest": "sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0", + "commands": { + "read_only_claims_preflight": "env PATH=\"/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/warden-browser-opener-r1:$PATH\" BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- python3 /home/worsch/railiance-platform/scripts/apply-reuse-surface-reader-custody.py --preflight --receipt /tmp/cust81-installed-claims-r1.jsonl", + "approved_policy_and_bootstrap": "env PATH=\"/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/warden-browser-opener-r1:$PATH\" BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- python3 /home/worsch/railiance-platform/scripts/apply-reuse-surface-reader-custody.py --execute --approval /tmp/cust81-approved-custody-r1.json --receipt /tmp/cust81-policy-custody-r1.jsonl --bootstrap-receipt /tmp/cust81-forge-reader-r1.jsonl", + "existing_inner_forgejo_route": "warden access forgejo-admin-api-token --field API_TOKEN --exec -- python3 /home/worsch/railiance-platform/scripts/provision-reuse-surface-forgejo-source.py --inner --approval /tmp/cust81-approved-custody-r1.json --receipt /tmp/cust81-forge-reader-r1.jsonl" + }, + "approval_receipt_schema": { + "ccr_id": "CCR-2026-0046", + "decision": "approved only after actual founder disposition", + "supervisor": "recorded supervisor identity", + "decision_ref": "resolved exact founder credential-change decision reference", + "approved_artifacts": { + "credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml": "23039fae6137e7bdd71de0ed3278596b1ed60ae636018eb4bfcc83e53c4ada02", + "docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json": "2bb72671e0a5f230497ab8ef2967a20ee68d1f2cf081f07c84f6db3442e18d8b", + "scripts/provision-reuse-surface-forgejo-source.py": "abf60a754596baa1610d23b1fd367385b0222ac8aeaebba0f2cfb7ec4c62055b", + "openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl": "6320f6c2d262d924ea16291d301d5f9eeb7efdb6ce7ea141c1a41d277d86b01b" + }, + "runner_artifacts": { + "scripts/apply-reuse-surface-reader-custody.py": "934b421c357e154f6508ab4b7c30f1aadfea2bf9a2f187d83968928d5eecad42", + "openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl": "6320f6c2d262d924ea16291d301d5f9eeb7efdb6ce7ea141c1a41d277d86b01b", + "docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json": "178dd2aeddf3544438d584fa16e3ceb1a541a357d4dff5b8b4b0e95db2f357ac" + } + }, + "authorization_caveat": "Schema is explanatory, not an approved receipt. Authentic authority relies on governed attended supervisor lane. Proposal binds exact future CCR bytes plus predecessor and only-two-field transition rule; actual installed role proof is mandatory before transition. StoreReady, mode labels or candidate fields never establish observed claims.", + "immutable_role_preflight": { + "path": "auth/kubernetes/role/reuse-surface-runtime-eso", + "bound_service_account_names": [ + "reuse-surface-eso" + ], + "bound_service_account_namespaces": [ + "reuse" + ], + "audience": "openbao", + "token_policies": [ + "workload-kv-read-reuse-surface-runtime-eso" + ], + "token_ttl": 900, + "token_max_ttl": 900, + "token_explicit_max_ttl": 900, + "token_no_default_policy": true, + "token_period": 0, + "token_bound_cidrs": [] + }, + "policy_only_reason": "Generic CCR metadata applier would rewrite existing ESO role; CCR0046 does not carry all CCR0005 maxTTL/no-default fields. This runner NEVER writes role and only adds exact approved ACL path after old-or-approved byte comparison. OpenBao ACL has no CAS; immediate predecessor recheck and successor verification are attended guards, not atomicCAS.", + "receipt_semantics": "Durable sanitized JSONL; no role objects/secret/network output stored. Output mismatch or ambiguity stops; inspect child receipt before retry. Warden envelope owns login persistence, session selfrevoke and cleanup; its13 exit means child may have succeeded, revocation unconfirmed.", + "gates": [ + "Integrate/publish/review new quiet runner and tests before handing concrete command to founder.", + "Existing owner-prepared WSL opener must match pinned cmd.exe/xdg-open bytes and be prefixed through task-local PATH for attended identity act; no global/browser/BROWSER workaround or capturedURL fallback.", + "Actual founder credential provisioning approval, independent owner review of new exact-policy runner, and confirmed installedclaims before execute.", + "No default/personal/root session fallback; nested Forgejo route must resolve using existing platform-admin caller custody and capture output.", + "No delivery by this runner: apps owner separately deploys approved image+exact14URLconfig with live headroom/dryrun and confirms currentsource inventory.", + "Final nativefetch verification and scopedwriter seven registrations plus fresh hosted70/70 remain required." + ], + "validation": { + "runner_boundary_tests": 17, + "result": "passed", + "target": "wrongclaims/ttl/policy refused, approved conditional-state scope, verified policy snapshot stdin, predecessor/idempotent drift, final immutable role+desired ACL revalidation before verified terminal, output suppression" + }, + "live_actions_performed": false, + "conditional_ccr_transition": { + "candidate_wrapper": "docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json", + "candidate_wrapper_sha256": "178dd2aeddf3544438d584fa16e3ceb1a541a357d4dff5b8b4b0e95db2f357ac", + "source_preimage_sha256": "90522912fb78fed72e625382fb7114a0e9aafe76d59e36bffecab83c5bb7d643", + "approved_future_ccr_sha256": "23039fae6137e7bdd71de0ed3278596b1ed60ae636018eb4bfcc83e53c4ada02", + "state": "Preparation only: candidate approved/confirmed fields are not observed facts, not active approval, not installed proof.", + "rule": "After actual founder decision exact receipt, validate all artifact/candidate/predecessor hashes; prove actual existing role claims from suppressed read; only then atomically install exact two-field approved/confirmed CCR bytes with durable receipt, preserve immutable role. Unchanged provisioner approval check now sees prebound future CCR bytes. Any mismatch stops without installing candidate.", + "post_execution_recording": "Source transition must be committed/projected by platform owner with observedclaim receipt and actual supervisor decision. No additional edits to approved CCR bytes are required to execute; preserve original proposed revision and this candidate hash." + }, + "browser_opener": { + "path": "/home/worsch/the-custodian/docs/assessments/netkingdom-2026-10-14/warden-browser-opener-r1", + "cmd_exe_sha256": "4b91a4dc635e464143bfe1f484eccb2456ccd992326ac7a126b53d0a9a3c7f96", + "xdg_open_sha256": "4b91a4dc635e464143bfe1f484eccb2456ccd992326ac7a126b53d0a9a3c7f96", + "status": "owner prepared/validated; no GUI or login executed", + "contract": "Installed Bao2.5.5 capv0.12.0 WSL calls cmd.exe /c start; task-local PATH adapter preserves existing HOME/token envelope. BROWSER unsupported; no global opener change.", + "validation_sha256": "9cbd8be4d9acfc19241e70bcda5df93eb2eacc83cb00771a5543022acc4ed474" + }, + "r2_changes_requested": { + "review_path": "/tmp/cust81-forge-reader-owner/docs/proposals/cust81-private-reader/forge-owner-attended-runner-review-r2.json", + "review_sha256": "c4e61cd185bfc738fe8e2f7e89b71408ed35d441dfa38183cd83bad9c0832445", + "confirmed_defects": [ + "Mutable approved policy filepath reopened at write", + "Idempotent path/final receipt lacked refreshed ACL and immutable-role verification" + ], + "r3_fix": "Capture exact policy bytes and reverify cached approved digest after baseapproval; stdin-only policywrite snapshot. Both before and after bootstrap verify approved desired ACL and immutable role including idempotent path. Drift stops without overwrite or retry.", + "same_proposal": "CCR0046 refinement; preserve R2 failures, no new scored trial" + } +} diff --git a/docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json b/docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json new file mode 100644 index 0000000..dd5cdb0 --- /dev/null +++ b/docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json @@ -0,0 +1,13 @@ +{ + "id": "CCR-2026-0046-CONDITIONAL-STATE-CANDIDATE-R1", + "status": "prepared candidate; not approved, not applied, claims not observed or verified", + "condition": "Only actual founder decision plus installed-claim exact metadata preflight may activate these bytes. StoreReady is not claim proof.", + "source_sha256": "90522912fb78fed72e625382fb7114a0e9aafe76d59e36bffecab83c5bb7d643", + "source_yaml": "id: CCR-2026-0046\nkind: credential-change-request\nschema_version: 1\nrequest_type: workload-kv-read\ntitle: Reuse Surface exact seven private-source repository reader\nstatus: proposed\ncreated: '2026-10-15'\nupdated: '2026-10-15'\nrequester:\n agent: platform-reader-owner delegated preparation worker\n reason: CUST-WP-0081 hosted63/70; founder metadata consent excludes credential provisioning.\n Prepare separate seven-source reader.\nreview:\n required: true\n required_approvers:\n - platform-operator\n - railiance-forge-owner\n - reuse-surface-owner\n - railiance-apps-owner\n comments: []\ntarget:\n domain: infotech\n tenant: reuse\n workload: reuse-surface\n environment: production\n purpose: Native acquisition of seven owner-consented pinned capability indexes.\n Entire private repository code becomes readable by this dedicated reader; publication\n remains only approved metadata.\nopenbao:\n mount: platform\n kv_path: platform/workloads/reuse/reuse-surface/forgejo-source-read\n fields:\n - FORGEJO_SOURCE_TOKEN\n - API_USER\n - API_BASE_URL\n - TOKEN_SCOPES\n - GENERATED_AT\n - CCR_ID\n policy_name: workload-kv-read-reuse-surface-runtime-eso\n policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl\n auth:\n method: kubernetes\n mount: kubernetes\n role: reuse-surface-runtime-eso\n bound_claims:\n service_account_names:\n - reuse-surface-eso\n service_account_namespaces:\n - reuse\n bound_claims_confirmed: false\n policies:\n - workload-kv-read-reuse-surface-runtime-eso\n ttl: 15m\n audience: openbao\naccess_frontdoor:\n type: external-secrets\n catalog_id: reuse-surface-forgejo-source-read\n readiness: pending-review\n resolvable: false\ndelivery:\n surface: external-secrets\n bootstrap_command: warden access openbao-platform-admin-login --exec -- python3\n scripts/provision-reuse-surface-forgejo-source.py --approval \n --receipt <0600-durable-receipt>\n provisioning_script: scripts/provision-reuse-surface-forgejo-source.py\n target: Existing namespace-limited store openbao-reuse. Apps owner adds only proposed\n data item into ExternalSecret reuse/reuse-surface-runtime. Shared target reuse-surface-env\n remains intact.\n forgejo_identity: reuse-surface-source\n forgejo_team: reuse-surface-source-readers\n repositories:\n - coulomb/rapp-openbao\n - coulomb/rapp-policy-nexus\n - coulomb/rapp-postgres\n - coulomb/rapp-sbom-nexus\n - coulomb/rapp-telemetry\n - coulomb/rapp-tenant-engine\n - coulomb/rapp-user-engine\n token_scopes:\n - read:repository\n consumer_gate: Reader must not be delivered until reuse-surface owner supplies verified\n image digest containing BOTH authenticated redirect rejection/HTTPS enforcement\n AND runtime-controlled exact URL allowlist checked before credential lookup. Apps\n owner must deploy REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST mapping the reader environment\n to exactly fourteen consented pinned index/entry URLs and inventory existing authenticated\n sources for migration. Current image and redirect-only fix are not approved. Native\n owner helper must verify all seven pinned indexes and entry hashes after rollout.\nrisk:\n classification: high\n cost_estimate_eur: 0\n notes:\n - Repository grants disclose entire source of these seven repositories to hub; PAT\n cannot restrict access to index files.\n - No all-repositories membership, unrelated private grants, admin privileges or\n repository writes.\n - Existing ESO role expands by one exact data-read path; writer OIDC caller remains\n unchanged and denied new path.\n - Current consumer accepts arbitrary authenticated source URLs and redirects, allowing\n bearer disclosure. Delivery barred until full reviewed redirect/HTTPS and runtime-controlled\n exact URL allowlist patch image plus config is verified.\n - PAT has no server-side expiry; rotate quarterly and immediately upon incident.\n - Bootstrap writes only exact new identity/team/PAT/KV. Policy apply, ESO delivery,\n consumer rollout and registrations require their separate reviewed owner steps.\n - R1 pagination defect preserved; R2 pages until empty with duplicate/count/bound\n checks. Deployed-version live complete-grant inventory remains required; code\n review is not live scope proof.\n - Quarterly rotation is a proposed manual requirement, not deployed expiry enforcement;\n due-date owner task/receipt and separate exact-token recovery approval must be\n established before activation.\nverification:\n positive:\n - All seven repositories private; exact team/code-read flags, one dedicated team\n member, exact seven memberships, effective permissions read and exactly one read:repository\n PAT.\n - Each consented index digest matches from authenticated Forgejo read; final hosted\n native owner helper verifies pinned entry hashes as well.\n - OpenBao stdin CAS=0 field write readback matches without printing or persisting\n value.\n - ESO binding and namespace condition surveyed and confirmed; native fixed consumer\n image verified before delivery; final hosted70/70 exactly once.\n negative:\n - Known excluded private rapp-core-hub reader request denied (403/404); administrative\n endpoint and Actions secrets denied.\n - No additional private effective repositories or teams; no write probing that could\n itself cause a change.\n - Scoped writer and default OpenBao identities denied new data path; ESO role denied\n unrelated siblings and wrong SA/namespace/audience login refused.\n - Missing/anonymous native authentication refused with no privileged fallback.\n activation_conditions:\n - Independent platform/Forgejo/reuse/apps owner reviews of exact artifacts and founder\n CCR provisioning disposition recorded; no approval inferred from metadata consent.\n - Confirm existing ESO Kubernetes binding before any delegated metadata apply.\n - Consumer security patch image digest verified and apps rollout artifacts reviewed/approved\n before delivery.\n - Durable bounded bootstrap and positive/negative receipts pass; owner native fetch\n proof passes all seven pinned sources.\n - Separate scoped writer registration batch followed by fresh hosted70/70 completion\n audit.\n evidence: []\nlifecycle:\n deactivate: Separate explicitly approved silent scripts/revoke-reuse-surface-forgejo-source.py\n validates reviewed single PAT ID/name, admin PATCH only dedicated user to fresh\n process-memory password, Basic-auth DELETE exact token, then reconciles complete\n inventory absence. Admin PAT/Sudo alone cannot delete. KV/shared runtime Secret\n preserved. Apps/platform owners remove only new ESO item/policy stanza through\n their reviewed path; preserve writer/webhook/public registrations. Recovery helper\n approval is separate from mint/bootstrap approval.\n rotate: Attended owner revokes old identified PAT then governed bootstrap remints\n and verifies; never overwrite differing/orphan KV through bootstrap. Explicit\n scoped CAS update plan required for rotation.\n compromised: Revoke all dedicated service-user PATs, remove delivery data item,\n roll fixed consumer with owners, retain failed sanitized evidence; investigate\n all seven private source exposure. Do not rotate unrelated credentials without\n separate scope.\nrevision: 2\n", + "candidate_sha256": "23039fae6137e7bdd71de0ed3278596b1ed60ae636018eb4bfcc83e53c4ada02", + "candidate_yaml": "id: CCR-2026-0046\nkind: credential-change-request\nschema_version: 1\nrequest_type: workload-kv-read\ntitle: Reuse Surface exact seven private-source repository reader\nstatus: approved\ncreated: '2026-10-15'\nupdated: '2026-10-15'\nrequester:\n agent: platform-reader-owner delegated preparation worker\n reason: CUST-WP-0081 hosted63/70; founder metadata consent excludes credential provisioning.\n Prepare separate seven-source reader.\nreview:\n required: true\n required_approvers:\n - platform-operator\n - railiance-forge-owner\n - reuse-surface-owner\n - railiance-apps-owner\n comments: []\ntarget:\n domain: infotech\n tenant: reuse\n workload: reuse-surface\n environment: production\n purpose: Native acquisition of seven owner-consented pinned capability indexes.\n Entire private repository code becomes readable by this dedicated reader; publication\n remains only approved metadata.\nopenbao:\n mount: platform\n kv_path: platform/workloads/reuse/reuse-surface/forgejo-source-read\n fields:\n - FORGEJO_SOURCE_TOKEN\n - API_USER\n - API_BASE_URL\n - TOKEN_SCOPES\n - GENERATED_AT\n - CCR_ID\n policy_name: workload-kv-read-reuse-surface-runtime-eso\n policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl\n auth:\n method: kubernetes\n mount: kubernetes\n role: reuse-surface-runtime-eso\n bound_claims:\n service_account_names:\n - reuse-surface-eso\n service_account_namespaces:\n - reuse\n bound_claims_confirmed: true\n policies:\n - workload-kv-read-reuse-surface-runtime-eso\n ttl: 15m\n audience: openbao\naccess_frontdoor:\n type: external-secrets\n catalog_id: reuse-surface-forgejo-source-read\n readiness: pending-review\n resolvable: false\ndelivery:\n surface: external-secrets\n bootstrap_command: warden access openbao-platform-admin-login --exec -- python3\n scripts/provision-reuse-surface-forgejo-source.py --approval \n --receipt <0600-durable-receipt>\n provisioning_script: scripts/provision-reuse-surface-forgejo-source.py\n target: Existing namespace-limited store openbao-reuse. Apps owner adds only proposed\n data item into ExternalSecret reuse/reuse-surface-runtime. Shared target reuse-surface-env\n remains intact.\n forgejo_identity: reuse-surface-source\n forgejo_team: reuse-surface-source-readers\n repositories:\n - coulomb/rapp-openbao\n - coulomb/rapp-policy-nexus\n - coulomb/rapp-postgres\n - coulomb/rapp-sbom-nexus\n - coulomb/rapp-telemetry\n - coulomb/rapp-tenant-engine\n - coulomb/rapp-user-engine\n token_scopes:\n - read:repository\n consumer_gate: Reader must not be delivered until reuse-surface owner supplies verified\n image digest containing BOTH authenticated redirect rejection/HTTPS enforcement\n AND runtime-controlled exact URL allowlist checked before credential lookup. Apps\n owner must deploy REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST mapping the reader environment\n to exactly fourteen consented pinned index/entry URLs and inventory existing authenticated\n sources for migration. Current image and redirect-only fix are not approved. Native\n owner helper must verify all seven pinned indexes and entry hashes after rollout.\nrisk:\n classification: high\n cost_estimate_eur: 0\n notes:\n - Repository grants disclose entire source of these seven repositories to hub; PAT\n cannot restrict access to index files.\n - No all-repositories membership, unrelated private grants, admin privileges or\n repository writes.\n - Existing ESO role expands by one exact data-read path; writer OIDC caller remains\n unchanged and denied new path.\n - Current consumer accepts arbitrary authenticated source URLs and redirects, allowing\n bearer disclosure. Delivery barred until full reviewed redirect/HTTPS and runtime-controlled\n exact URL allowlist patch image plus config is verified.\n - PAT has no server-side expiry; rotate quarterly and immediately upon incident.\n - Bootstrap writes only exact new identity/team/PAT/KV. Policy apply, ESO delivery,\n consumer rollout and registrations require their separate reviewed owner steps.\n - R1 pagination defect preserved; R2 pages until empty with duplicate/count/bound\n checks. Deployed-version live complete-grant inventory remains required; code\n review is not live scope proof.\n - Quarterly rotation is a proposed manual requirement, not deployed expiry enforcement;\n due-date owner task/receipt and separate exact-token recovery approval must be\n established before activation.\nverification:\n positive:\n - All seven repositories private; exact team/code-read flags, one dedicated team\n member, exact seven memberships, effective permissions read and exactly one read:repository\n PAT.\n - Each consented index digest matches from authenticated Forgejo read; final hosted\n native owner helper verifies pinned entry hashes as well.\n - OpenBao stdin CAS=0 field write readback matches without printing or persisting\n value.\n - ESO binding and namespace condition surveyed and confirmed; native fixed consumer\n image verified before delivery; final hosted70/70 exactly once.\n negative:\n - Known excluded private rapp-core-hub reader request denied (403/404); administrative\n endpoint and Actions secrets denied.\n - No additional private effective repositories or teams; no write probing that could\n itself cause a change.\n - Scoped writer and default OpenBao identities denied new data path; ESO role denied\n unrelated siblings and wrong SA/namespace/audience login refused.\n - Missing/anonymous native authentication refused with no privileged fallback.\n activation_conditions:\n - Independent platform/Forgejo/reuse/apps owner reviews of exact artifacts and founder\n CCR provisioning disposition recorded; no approval inferred from metadata consent.\n - Confirm existing ESO Kubernetes binding before any delegated metadata apply.\n - Consumer security patch image digest verified and apps rollout artifacts reviewed/approved\n before delivery.\n - Durable bounded bootstrap and positive/negative receipts pass; owner native fetch\n proof passes all seven pinned sources.\n - Separate scoped writer registration batch followed by fresh hosted70/70 completion\n audit.\n evidence: []\nlifecycle:\n deactivate: Separate explicitly approved silent scripts/revoke-reuse-surface-forgejo-source.py\n validates reviewed single PAT ID/name, admin PATCH only dedicated user to fresh\n process-memory password, Basic-auth DELETE exact token, then reconciles complete\n inventory absence. Admin PAT/Sudo alone cannot delete. KV/shared runtime Secret\n preserved. Apps/platform owners remove only new ESO item/policy stanza through\n their reviewed path; preserve writer/webhook/public registrations. Recovery helper\n approval is separate from mint/bootstrap approval.\n rotate: Attended owner revokes old identified PAT then governed bootstrap remints\n and verifies; never overwrite differing/orphan KV through bootstrap. Explicit\n scoped CAS update plan required for rotation.\n compromised: Revoke all dedicated service-user PATs, remove delivery data item,\n roll fixed consumer with owners, retain failed sanitized evidence; investigate\n all seven private source exposure. Do not rotate unrelated credentials without\n separate scope.\nrevision: 2\n", + "allowed_changes": [ + "status proposed -> approved", + "openbao.auth.bound_claims_confirmed false -> true after exact installedclaim verification" + ] +} diff --git a/docs/proposals/2026-10-15-reuse-private-reader/platform-attended-runner-review-r1.json b/docs/proposals/2026-10-15-reuse-private-reader/platform-attended-runner-review-r1.json new file mode 100644 index 0000000..cde65e0 --- /dev/null +++ b/docs/proposals/2026-10-15-reuse-private-reader/platform-attended-runner-review-r1.json @@ -0,0 +1,18 @@ +{ + "id": "RPF-WP-0058-PLATFORM-ATTENDED-RUNNER-REVIEW-R3", + "work_item": "RPF-WP-0058-T03", + "reviewer": "platform-reader-owner delegated worker", + "owner_repo": "railiance-platform", + "decision": "approved", + "scope": "Exact-policy runner and conditional CCR state transition PREPARATION only; no founder/live authorization, candidate remains unapplied/unverified", + "basis": "INTENT shared custody/Q1 delegated apply and stable delivery. Existing ESO role must retain CCR0005 constraints; scoped ACL-only change avoids generic role rewrite. Existing approved credential lane transports retained.", + "reviewed_artifacts": { + "scripts/apply-reuse-surface-reader-custody.py": "934b421c357e154f6508ab4b7c30f1aadfea2bf9a2f187d83968928d5eecad42", + "tests/test_reuse_surface_reader_custody_apply.py": "cb2c4b5f8a96cfb71015d94c5d51de0e1bbf5c8e3d90e866b207a070c1a7a266", + "docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json": "178dd2aeddf3544438d584fa16e3ceb1a541a357d4dff5b8b4b0e95db2f357ac", + "docs/proposals/2026-10-15-reuse-private-reader/attended-custody-handoff-r1.json": "1247ad5aa73289a26a48e999b870ce2d60abb409132783e14456ea318689acff" + }, + "tests": "17 privileged-boundary tests passed;45 allreader tests. Verifiedsnapshot stdin, before/after bootstrap actualrole+desiredACL rechecks on idempotent path; R2 failures preserved, sameproposal refinement. No live calls/credentials/mutations.", + "pending": "Founder exact execution disposition, independent owner finalreview of R3, successful installedclaimspreflight. Owner-pinned WSL opener prepared/validated; login itself not performed. No authrole migration or admin-token bypass.", + "revision": 3 +} diff --git a/scripts/apply-reuse-surface-reader-custody.py b/scripts/apply-reuse-surface-reader-custody.py new file mode 100755 index 0000000..cc0ad52 --- /dev/null +++ b/scripts/apply-reuse-surface-reader-custody.py @@ -0,0 +1,242 @@ +#!/usr/bin/env python3 +"""Silent attended exact-policy update and existing reviewed reader bootstrap. + +Unlike generic CCR metadata apply, this command NEVER writes the reused ESO role. +Installed role drift and differing ACL stop before mutations. --preflight reads +only role/ACL metadata; it does not request Forgejo credentials. --execute needs +approved CCR plus exact bootstrap and runner artifact receipts. Outer sanctioned +OpenBao login owns identity, browser, helper containment and self-revocation. +""" +import argparse +import hashlib +import importlib.util +import json +import os +import tempfile +from pathlib import Path +import subprocess +import sys +import yaml + +ROOT=Path(__file__).resolve().parent.parent +SCRIPT='scripts/apply-reuse-surface-reader-custody.py' +CANDIDATE_FILE='docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json' +POLICY_FILE='openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl' +POLICY='workload-kv-read-reuse-surface-runtime-eso' +ROLE='auth/kubernetes/role/reuse-surface-runtime-eso' +BASELINE='''path "platform/data/workloads/reuse/reuse-surface/runtime-secrets" { + capabilities = ["read"] +} + +path "auth/token/lookup-self" { + capabilities = ["read"] +} + +path "auth/token/revoke-self" { + capabilities = ["update"] +} +''' +spec=importlib.util.spec_from_file_location('reuse_reader_bootstrap',ROOT/'scripts/provision-reuse-surface-forgejo-source.py') +p=importlib.util.module_from_spec(spec);spec.loader.exec_module(p) + + +def validate_role(data): + expected={'bound_service_account_names':['reuse-surface-eso'],'bound_service_account_namespaces':['reuse'], + 'audience':'openbao','token_ttl':900,'token_max_ttl':900,'token_explicit_max_ttl':900, + 'token_no_default_policy':True,'token_policies':[POLICY]} + if not isinstance(data,dict) or any(data.get(k)!=v for k,v in expected.items()): + raise p.Refused('installed_eso_role_drift') + if 'policies' in data and data['policies']!=[POLICY]: + raise p.Refused('installed_eso_role_drift') + if data.get('token_period',0)!=0 or data.get('token_bound_cidrs',[]) or data.get('bound_service_account_namespace_selector',''): + raise p.Refused('installed_eso_role_drift') + + +def read_policy(): + result=p.Bao.run(['policy','read',POLICY]) + if result.returncode!=0: + raise p.Refused('policy_read_failed') + try: + return result.stdout.decode().strip() + except Exception: + raise p.Refused('policy_invalid_encoding') from None + + +def preflight(journal, desired_text=None): + result=p.Bao.run(['read','-format=json',ROLE]) + if result.returncode!=0: + raise p.Refused('installed_eso_role_read_failed') + try: + data=json.loads(result.stdout)['data'] + except Exception: + raise p.Refused('installed_eso_role_invalid') from None + validate_role(data) + desired=(ROOT/POLICY_FILE).read_text().strip() if desired_text is None else desired_text + current=read_policy() + if current not in (BASELINE.strip(),desired): + raise p.Refused('installed_policy_drift') + journal.put('verified',operation='installed_eso_role_and_policy',status='matched_immutable_role') + return current,desired + + +def approval_envelope(path): + """Bind conditional future CCR bytes without recording imaginary consent.""" + try: + receipt=json.loads(Path(path).read_text()) + if receipt['ccr_id']!=p.CCR or receipt['decision']!='approved' or not receipt['supervisor'] or not receipt['decision_ref']: + raise p.Refused('runner_approval_invalid') + if set(receipt['runner_artifacts'])!={SCRIPT,POLICY_FILE,CANDIDATE_FILE}: + raise p.Refused('runner_approval_artifact_set') + for rel in (SCRIPT,POLICY_FILE,CANDIDATE_FILE): + if hashlib.sha256((ROOT/rel).read_bytes()).hexdigest()!=receipt['runner_artifacts'][rel]: + raise p.Refused('runner_approved_revision_mismatch') + wrapper=json.loads((ROOT/CANDIDATE_FILE).read_text()) + before=wrapper['source_yaml'].encode() + after=wrapper['candidate_yaml'].encode() + if hashlib.sha256(before).hexdigest()!=wrapper['source_sha256'] or hashlib.sha256(after).hexdigest()!=wrapper['candidate_sha256']: + raise p.Refused('candidate_digest_invalid') + old=yaml.safe_load(before); new=yaml.safe_load(after) + if old['id']!=p.CCR or old['status']!='proposed' or old['openbao']['auth']['bound_claims_confirmed'] is not False: + raise p.Refused('candidate_predecessor_invalid') + old['status']='approved'; old['openbao']['auth']['bound_claims_confirmed']=True + if old!=new: + raise p.Refused('candidate_changes_exceed_scope') + exact={p.CCR_FILE,p.PLAN_FILE,p.SCRIPT_FILE,POLICY_FILE} + if set(receipt['approved_artifacts'])!=exact or receipt['approved_artifacts'][p.CCR_FILE]!=wrapper['candidate_sha256']: + raise p.Refused('candidate_approval_invalid') + for rel in exact-{p.CCR_FILE}: + if hashlib.sha256((ROOT/rel).read_bytes()).hexdigest()!=receipt['approved_artifacts'][rel]: + raise p.Refused('bootstrap_approved_revision_mismatch') + current=(ROOT/p.CCR_FILE).read_bytes() + if current not in (before,after): + raise p.Refused('ccr_source_precondition_failed') + return before,after,receipt['approved_artifacts'][POLICY_FILE] + except p.Refused: + raise + except Exception: + raise p.Refused('runner_approval_invalid') from None + + +def activate_candidate(before,after,journal): + """Called ONLY after receipt validation and installed claims verification.""" + path=ROOT/p.CCR_FILE + if path.read_bytes()==after: + return + if path.read_bytes()!=before: + raise p.Refused('ccr_source_precondition_failed') + journal.put('intent',operation='conditional_ccr_state_transition') + temporary=None + try: + with tempfile.NamedTemporaryFile(dir=path.parent,prefix='.cust81-ccr-',delete=False) as file: + temporary=Path(file.name) + file.write(after);file.flush();os.fsync(file.fileno()) + os.chmod(temporary,path.stat().st_mode & 0o777) + if path.read_bytes()!=before: + raise p.Refused('ccr_source_precondition_failed') + os.replace(temporary,path);temporary=None + directory=os.open(path.parent,os.O_DIRECTORY) + try: os.fsync(directory) + finally: os.close(directory) + if path.read_bytes()!=after: + raise p.Refused('ccr_transition_unverified') + journal.put('verified',operation='conditional_ccr_state_transition',status='exact_candidate_installed_after_claim_proof') + finally: + if temporary is not None: + temporary.unlink(missing_ok=True) + + +def verify_ready(journal,desired): + current,_=preflight(journal,desired_text=desired) + if current!=desired: + raise p.Refused('approved_policy_not_installed') + journal.put('verified',operation='desired_acl_and_immutable_role',status='matched') + + +def execute(approval,receipt,journal): + before,after,approved_policy_sha256=approval_envelope(approval) + current,_=preflight(journal) + activate_candidate(before,after,journal) + p.approval(approval) + # Capture exact approved bytes once. Later repo-file changes cannot alter + # the authorized ACL input: Bao receives only this snapshot through stdin. + policy_snapshot=(ROOT/POLICY_FILE).read_bytes() + if hashlib.sha256(policy_snapshot).hexdigest()!=approved_policy_sha256: + raise p.Refused('approved_policy_snapshot_mismatch') + try: + desired=policy_snapshot.decode().strip() + except Exception: + raise p.Refused('policy_invalid_encoding') from None + if current!=desired: + # Recheck immediately before the exact write. OpenBao ACL has no CAS; + # this is an attended owner operation, never an unattended reconcile. + if read_policy()!=current: + raise p.Refused('policy_changed_since_preflight') + journal.put('intent',operation='exact_eso_policy_update') + try: + result=p.Bao.run(['policy','write',POLICY,'-'],stdin=policy_snapshot) + except p.Refused: + journal.put('outcome',operation='exact_eso_policy_update',status='ambiguous') + raise + journal.put('outcome',operation='exact_eso_policy_update',status='observed' if result.returncode==0 else 'ambiguous') + if read_policy()!=desired: + raise p.Refused('policy_update_unverified') + journal.put('verified',operation='exact_eso_policy_update',status='matched') + # Verify both immutable role and desired ACL even on the idempotent path. + verify_ready(journal,desired) + journal.put('intent',operation='reviewed_forgejo_bootstrap_route') + try: + result=subprocess.run([sys.executable,str(ROOT/p.SCRIPT_FILE),'--approval',str(Path(approval).resolve()),'--receipt',str(Path(receipt).resolve())],capture_output=True,check=False,timeout=1000,cwd=ROOT) + except Exception: + journal.put('outcome',operation='reviewed_forgejo_bootstrap_route',status='ambiguous') + raise p.Refused('bootstrap_observation_failed') from None + # Even whitespace violates quiet owner command. Raw child/network bytes + # never enter receipts or caller output; failed/ambiguous writes retained. + if result.returncode!=0 or result.stdout or result.stderr: + journal.put('outcome',operation='reviewed_forgejo_bootstrap_route',status='stopped_check_child_receipt') + raise p.Refused('bootstrap_route_unverified') + journal.put('outcome',operation='reviewed_forgejo_bootstrap_route',status='observed_success') + verify_ready(journal,desired) + journal.put('terminal',outcome='policy_and_custody_verified_delivery_pending') + + +def main(argv=None): + parser=argparse.ArgumentParser() + mode=parser.add_mutually_exclusive_group(required=True) + mode.add_argument('--preflight',action='store_true') + mode.add_argument('--execute',action='store_true') + parser.add_argument('--approval') + parser.add_argument('--receipt',required=True) + parser.add_argument('--bootstrap-receipt') + args=parser.parse_args(argv) + journal=None + lane_lock=None + try: + if args.execute and (not args.approval or not args.bootstrap_receipt or Path(args.receipt).resolve()==Path(args.bootstrap_receipt).resolve()): + raise p.Refused('execute_inputs_invalid') + lane_lock=os.open('/tmp/'+p.CCR+'-policy-state-'+str(os.getuid())+'.lock',os.O_RDWR|os.O_CREAT|os.O_NOFOLLOW,0o600) + if os.fstat(lane_lock).st_uid!=os.getuid() or os.fstat(lane_lock).st_mode & 0o077: + raise p.Refused('lane_lock_owner_or_permissions') + p.fcntl.flock(lane_lock,p.fcntl.LOCK_EX|p.fcntl.LOCK_NB) + journal=p.Journal(args.receipt) + if args.preflight: + preflight(journal) + journal.put('terminal',outcome='read_only_preflight_verified') + else: + execute(args.approval,args.bootstrap_receipt,journal) + return 0 + except Exception as error: + if journal: + journal.put('terminal',outcome='stopped_reconcile_before_retry',error_code=str(error) if isinstance(error,p.Refused) else 'internal_failure') + return 1 + finally: + if journal: + journal.close() + if lane_lock is not None: + os.close(lane_lock) + +if __name__=='__main__': + try: + result=main() + except Exception: + result=1 + raise SystemExit(result) diff --git a/tests/test_reuse_surface_reader_custody_apply.py b/tests/test_reuse_surface_reader_custody_apply.py new file mode 100644 index 0000000..f9465e9 --- /dev/null +++ b/tests/test_reuse_surface_reader_custody_apply.py @@ -0,0 +1,116 @@ +import importlib.util +from pathlib import Path +import unittest +import json +import yaml +import hashlib +import tempfile +from unittest.mock import patch + +spec=importlib.util.spec_from_file_location('custody_apply',Path(__file__).parents[1]/'scripts/apply-reuse-surface-reader-custody.py');r=importlib.util.module_from_spec(spec);spec.loader.exec_module(r) +class Journal: + def __init__(self): self.rows=[] + def put(self,event,**kw): self.rows.append((event,kw)) +class Tests(unittest.TestCase): + def role(self): return {'bound_service_account_names':['reuse-surface-eso'],'bound_service_account_namespaces':['reuse'],'audience':'openbao','token_ttl':900,'token_max_ttl':900,'token_explicit_max_ttl':900,'token_no_default_policy':True,'token_policies':[r.POLICY]} + def envelope_fixture(self,root,extra=False): + old={'id':r.p.CCR,'status':'proposed','openbao':{'auth':{'bound_claims_confirmed':False}}} + new={'id':r.p.CCR,'status':'approved','openbao':{'auth':{'bound_claims_confirmed':True}}} + if extra: new['unexpected_privilege']='broader' + before=yaml.safe_dump(old).encode();after=yaml.safe_dump(new).encode() + files={r.SCRIPT,r.POLICY_FILE,r.CANDIDATE_FILE,r.p.CCR_FILE,r.p.PLAN_FILE,r.p.SCRIPT_FILE} + for rel in files: + f=root/rel;f.parent.mkdir(parents=True,exist_ok=True);f.write_text('mock') + (root/r.p.CCR_FILE).write_bytes(before) + wrapper={'source_yaml':before.decode(),'candidate_yaml':after.decode(),'source_sha256':hashlib.sha256(before).hexdigest(),'candidate_sha256':hashlib.sha256(after).hexdigest()} + (root/r.CANDIDATE_FILE).write_text(json.dumps(wrapper)) + record={'ccr_id':r.p.CCR,'decision':'approved','supervisor':'founder','decision_ref':'mock','runner_artifacts':{rel:hashlib.sha256((root/rel).read_bytes()).hexdigest() for rel in (r.SCRIPT,r.POLICY_FILE,r.CANDIDATE_FILE)},'approved_artifacts':{rel:hashlib.sha256((root/rel).read_bytes()).hexdigest() for rel in (r.p.CCR_FILE,r.p.PLAN_FILE,r.p.SCRIPT_FILE,r.POLICY_FILE)}} + record['approved_artifacts'][r.p.CCR_FILE]=wrapper['candidate_sha256'] + approval=root/'approval.json';approval.write_text(json.dumps(record));return approval,before,after + def test_conditional_candidate_exact_future_hash_without_premature_install(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp);approval,before,after=self.envelope_fixture(root) + with patch.object(r,'ROOT',root): self.assertEqual(r.approval_envelope(approval),(before,after,hashlib.sha256(b'mock').hexdigest())) + self.assertEqual((root/r.p.CCR_FILE).read_bytes(),before) + def test_candidate_cannot_change_other_privilege_fields(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp);approval,_,_=self.envelope_fixture(root,extra=True) + with patch.object(r,'ROOT',root),self.assertRaisesRegex(r.p.Refused,'changes_exceed_scope'): r.approval_envelope(approval) + def test_candidate_predecessor_changed_refused(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp);approval,_,_=self.envelope_fixture(root);(root/r.p.CCR_FILE).write_text('different') + with patch.object(r,'ROOT',root),self.assertRaisesRegex(r.p.Refused,'source_precondition'):r.approval_envelope(approval) + def test_candidate_atomic_exact_transition_idempotent(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp);_,before,after=self.envelope_fixture(root);j=Journal() + with patch.object(r,'ROOT',root): + r.activate_candidate(before,after,j);r.activate_candidate(before,after,j) + self.assertEqual((root/r.p.CCR_FILE).read_bytes(),after) + self.assertEqual(len(j.rows),2) + def test_role_claims_policy_ttl_drift_rejected(self): + for key,value in [('audience','other'),('bound_service_account_names',['other']),('token_ttl',0),('token_no_default_policy',False),('token_policies',[r.POLICY,'default'])]: + data=self.role(); data[key]=value + with self.subTest(key=key),self.assertRaisesRegex(r.p.Refused,'role_drift'): r.validate_role(data) + def test_role_unchanged_baseline_accepted(self): r.validate_role(self.role()) + def test_unapproved_inputs_before_metadata_reads_or_writes(self): + with patch.object(r,'approval_envelope',side_effect=r.p.Refused('runner_approval_invalid')),patch.object(r.p.Bao,'run') as run: + with self.assertRaises(r.p.Refused): r.execute('mock','mock',Journal()) + run.assert_not_called() + def test_installed_claim_failure_prevents_candidate_activation(self): + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'preflight',side_effect=r.p.Refused('installed_eso_role_drift')),patch.object(r,'activate_candidate') as activate,patch.object(r.subprocess,'run') as child: + with self.assertRaisesRegex(r.p.Refused,'role_drift'): r.execute('mock','mock',Journal()) + activate.assert_not_called(); child.assert_not_called() + def test_policy_drift_after_preflight_prevents_write_and_credential_route(self): + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',return_value=('old','new')),patch.object(r,'read_policy',return_value='changed'),patch.object(r.p.Bao,'run') as run,patch.object(r.subprocess,'run') as child: + with self.assertRaisesRegex(r.p.Refused,'changed_since'): r.execute('mock','mock',Journal()) + run.assert_not_called(); child.assert_not_called() + def snapshot(self): return (r.ROOT/r.POLICY_FILE).read_bytes().decode().strip() + def test_exact_policy_only_then_existing_bootstrap_route(self): + j=Journal(); ok=type('R',(),{'returncode':0,'stdout':b'','stderr':b''})() + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',return_value=('old','new')),patch.object(r,'read_policy',side_effect=['old',self.snapshot()]),patch.object(r,'verify_ready') as verify,patch.object(r.p.Bao,'run',return_value=ok) as run,patch.object(r.subprocess,'run',return_value=ok) as child: + r.execute('mockapproval','mockreceipt',j) + self.assertEqual(verify.call_count,2);self.assertEqual(run.call_count,1); self.assertEqual(run.call_args.args[0],['policy','write',r.POLICY,'-']);self.assertEqual(run.call_args.kwargs['stdin'],(r.ROOT/r.POLICY_FILE).read_bytes()); self.assertIn(r.p.SCRIPT_FILE,str(child.call_args.args[0])); self.assertNotIn('auth/kubernetes',str(run.call_args)) + def test_policy_write_timeout_recorded_without_bootstrap_retry(self): + j=Journal() + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',return_value=('old','new')),patch.object(r,'read_policy',return_value='old'),patch.object(r.p.Bao,'run',side_effect=r.p.Refused('bao_observation_failed')),patch.object(r.subprocess,'run') as child: + with self.assertRaises(r.p.Refused): r.execute('mock','mock',j) + self.assertEqual(j.rows[-1],('outcome',{'operation':'exact_eso_policy_update','status':'ambiguous'})); child.assert_not_called() + def test_mutable_policy_replaced_at_write_uses_approved_stdin_snapshot(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp);f=root/r.POLICY_FILE;f.parent.mkdir(parents=True);f.write_bytes(b'approved-policy') + j=Journal();seen=[];ok=type('R',(),{'returncode':0,'stdout':b'','stderr':b''})() + def write(args,stdin=None): + f.write_bytes(b'unapproved-replacement');seen.append((args,stdin));return ok + with patch.object(r,'ROOT',root),patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256(b'approved-policy').hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',return_value=('old','approved-policy')),patch.object(r,'read_policy',side_effect=['old','approved-policy']),patch.object(r.p.Bao,'run',side_effect=write),patch.object(r,'verify_ready'),patch.object(r.subprocess,'run',return_value=ok): + r.execute('mock','mock',j) + self.assertEqual(seen,[(['policy','write',r.POLICY,'-'],b'approved-policy')]) + def test_policy_changed_before_snapshot_refuses_before_write(self): + with tempfile.TemporaryDirectory() as tmp: + root=Path(tmp);f=root/r.POLICY_FILE;f.parent.mkdir(parents=True);f.write_bytes(b'approved-policy') + def late_approval(*args):f.write_bytes(b'unapproved-replacement') + with patch.object(r,'ROOT',root),patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256(b'approved-policy').hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval',side_effect=late_approval),patch.object(r,'preflight',return_value=('old','approved-policy')),patch.object(r.p.Bao,'run') as write,patch.object(r.subprocess,'run') as child: + with self.assertRaisesRegex(r.p.Refused,'snapshot_mismatch'):r.execute('mock','mock',Journal()) + write.assert_not_called();child.assert_not_called() + def test_idempotent_acl_drift_before_bootstrap_refuses(self): + desired=self.snapshot();j=Journal() + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',side_effect=[(desired,desired),(r.BASELINE.strip(),desired)]),patch.object(r.p.Bao,'run') as write,patch.object(r.subprocess,'run') as child: + with self.assertRaisesRegex(r.p.Refused,'not_installed'):r.execute('mock','mock',j) + write.assert_not_called();child.assert_not_called() + self.assertFalse(any(e=='terminal' for e,kw in j.rows)) + def test_acl_drift_after_bootstrap_prevents_verified_terminal(self): + desired=self.snapshot();j=Journal();ok=type('R',(),{'returncode':0,'stdout':b'','stderr':b''})() + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',side_effect=[(desired,desired),(desired,desired),(r.BASELINE.strip(),desired)]),patch.object(r.subprocess,'run',return_value=ok) as child: + with self.assertRaisesRegex(r.p.Refused,'not_installed'):r.execute('mock','mock',j) + child.assert_called_once() + self.assertFalse(any(e=='terminal' for e,kw in j.rows)) + def test_immutable_role_drift_after_bootstrap_prevents_verified_terminal(self): + desired=self.snapshot();j=Journal();ok=type('R',(),{'returncode':0,'stdout':b'','stderr':b''})() + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',side_effect=[(desired,desired),(desired,desired),r.p.Refused('installed_eso_role_drift')]),patch.object(r.subprocess,'run',return_value=ok): + with self.assertRaisesRegex(r.p.Refused,'role_drift'):r.execute('mock','mock',j) + self.assertFalse(any(e=='terminal' for e,kw in j.rows)) + def test_child_output_stops_without_exposing_bytes(self): + j=Journal(); bad=type('R',(),{'returncode':0,'stdout':b'fake-secret-provider-output','stderr':b''})() + with patch.object(r,'approval_envelope',return_value=(b'before',b'after',hashlib.sha256((r.ROOT/r.POLICY_FILE).read_bytes()).hexdigest())),patch.object(r,'activate_candidate'),patch.object(r.p,'approval'),patch.object(r,'preflight',return_value=(self.snapshot(),self.snapshot())),patch.object(r,'verify_ready'),patch.object(r.subprocess,'run',return_value=bad): + with self.assertRaisesRegex(r.p.Refused,'bootstrap_route_unverified'): r.execute('mock','mock',j) + self.assertNotIn('secret',str(j.rows)) +if __name__=='__main__': unittest.main() diff --git a/workplans/RPF-WP-0058-reuse-private-source-reader.md b/workplans/RPF-WP-0058-reuse-private-source-reader.md index 6bd7046..c498cbb 100644 --- a/workplans/RPF-WP-0058-reuse-private-source-reader.md +++ b/workplans/RPF-WP-0058-reuse-private-source-reader.md @@ -87,3 +87,17 @@ Revision2 of the same CCR preserves R1 capped-page failure and the independently corrected preliminary owned-only endpoint concern. Twenty bounded pages until empty, duplicate IDs and supplied total-count consistency prevent silent short page acceptance. No live complete-grant proof is claimed by these mocks. + + +Attended execution preparation R3 is complete, while T03 stays `wait` because no +founder credential decision, login, installed-claim proof or live write has run. +The quiet owner runner captures hash-verified policy bytes for stdin, preserves +the existing ESO role, and verifies the actual immutable role/desired ACL before +and after bootstrap, including idempotent runs. A conditional CCR candidate binds +only approved/confirmed state transitions; these fields are not observed facts +until actual founder disposition and installed-role proof satisfy the rule. +Independent Forge preparation review R3 approved the exact runner; its receipt +SHA256 is e4a17ee59fb1691f7b99a09d2473178740721aad0c1394cae79b503118c97040. +Forty-five targeted reader tests passed (17 attended-runner boundary cases). +The handoff binds the owner-prepared task-local WSL opener and existing sanctioned +OpenBao/Forgejo routes. No secret value or live state has been acquired or changed.