diff --git a/.claude/rules/stack-and-commands.md b/.claude/rules/stack-and-commands.md index fa374d6..f3a6cde 100644 --- a/.claude/rules/stack-and-commands.md +++ b/.claude/rules/stack-and-commands.md @@ -12,7 +12,7 @@ make apps-pg-status # shared apps-pg cluster health (read-only) make pg-status # confirms retired Bitnami postgresql-ha is absent make valkey-status # confirms Valkey is absent make consumption-preflight CONSUMING_ENTITY=entity: ESTIMATE_EUR= -make forgejo-db-deploy | apps-pg-deploy # production-touching +make db-deploy | apps-pg-deploy # production-touching ``` `pg-deploy` and `valkey-deploy` are fail-closed. `*-status` / `*-logs` diff --git a/.custodian-brief.md b/.custodian-brief.md new file mode 100644 index 0000000..b480010 --- /dev/null +++ b/.custodian-brief.md @@ -0,0 +1,136 @@ + +# Custodian Brief — railiance-platform + +**Domain:** financials +**Last synced:** 2026-10-06 12:27 UTC +**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)* + +## Active Workstreams + +### Coordinate audit-core temporary custody and recovery exercises +Progress: 2/4 done | workplan_id: `f4640325-e89c-591d-b58e-ec6b087900ac` + +**Open tasks:** +- ! T02 — Define the runtime database lease recovery exercise `fda4262a` + *(wait: Value-safe lease revoke/expire exercise must be coordinated with rapp-postgres and audit-core.)* +- ! T03 — Define the coordinated railiance01 reboot exercise `ba729d18` + *(wait: Single-node outage exercise needs operator access and a declared restart order coordinated with railiance-infra, railiance-cluster, audit-core and the OpenBao package owner.)* + +### ArgoCD phase B: adopt the four existing Applications on railiance01 +Progress: 2/8 done | workplan_id: `98140775-3b9a-5cf9-9af6-722502d487dc` + +**Open tasks:** +- ! Adopt openbao-secretstore (lowest risk) `6d5fc801` + *(wait: Adopted 2026-09-21; the follow-on step (issue-core adoption, then automation) waits on the founder's go-ahead.)* +- ! Adopt issue-core (from rapp-issue-core) `0e619658` + *(wait: Founder go-ahead plus rapp-issue-core's agreement (hub message) that ArgoCD owns production and make deploy stops being a production path.)* +- ! Adopt target-revenue `d418068a` + *(wait: Adopted 2026-09-21; remaining preconditions and the next step wait on the founder's go-ahead.)* +- ! Adopt external-secrets (highest risk) `4ca54868` + *(wait: Founder go-ahead for the ESO adoption after adding CRD Prune=false,Delete=false and re-running the diff.)* +- ! Turn the root back to the production lane `844ff115` + *(wait: Founder go-ahead to restore automated prune/selfHeal on railiance-apps-root once T03-T06 are proven.)* +- ! Plan phase C: retire coulombcore's ArgoCD `55d1382f` + *(wait: Planning only; needs an operator read-only check of coulombcore's ArgoCD, outside agent session scope.)* + +### Close Forgejo primary backup coverage on Scaleway +Progress: 3/4 done | workplan_id: `7beec1a7-aa82-5a36-9a66-6b60008a2455` + +**Open tasks:** +- ! Establish primary full-archive delivery and application recovery `a4807df0` + *(wait: Operator chooses the independent archive prefix and confirms retention/caller contract and bounded worker credential delivery before the primary archive retrieval.)* + +### Coordinate KeyCape live Secret exposure recovery +Progress: 4/6 done | workplan_id: `b2c25a01-4a80-55c1-90cf-8538000f7e0e` + +**Open tasks:** +- ! T03 — Collect exact owner acknowledgements `714ae011` + *(wait: Attended resolver run: postchecks outstanding; NetKingdom asked to package the complete sequence as one receipt-producing command for the next attended session.)* +- ! T06 — Publish the Railiance/OpenBao custody handoff `3b9748c4` + *(wait: Platform/OpenBao owner must publish the non-secret routing receipt for both lanes through the attended handoff; values are never inferred.)* + +### Close S3 service assurance and ownership gaps +Progress: 4/7 done | workplan_id: `ca639c3d-3a87-5fa4-ad13-6f2e014b0c84` + +**Open tasks:** +- ! Make backup freshness and recurring recovery evidence checkable `d64446fb` + *(wait: activity-core replied (not accepted yet): role acceptance is the founder decision ACTIVITY-WP-0042-T01 (D2); then ACTIVITY-WP-0042 bounded operation, S3 OpenBao credential route, repaired openbao-activity-core store and per-run approval-engine approval.)* +- ! Produce S3 signals and prove their delivery to the evidence owner `5351e0e4` + *(wait: Needs a concrete receiving contract from railiance-telemetry (RTEL-WP-0002) before health transport is accepted; unmonitored state retained meanwhile.)* +- ! Obtain acceptance for compatibility assets and derived-record cleanup `3b74f79c` + *(wait: Owner-ready handoffs to railiance-forge, rapp-openbao and the ArgoCD bootstrap owner await acceptance; S3 custody contracts stay here until then.)* + +### Adopt activity-core application runtime into railiance01 GitOps +Progress: 2/3 done | workplan_id: `b8cf2fc2-60c2-5d4e-a60a-55f1304d9f54` + +**Open tasks:** +- ! Observe adoption and enforce bounded promotion readiness `63b44949` + *(wait: 24h soak after adoption, then founder confirmation of the scoped identity and checks before automated sync; ACTIVITY-WP-0041 owns publication and the promotion guard.)* + +### Prepare generated attended windows and explicit value-custody choice +Progress: 3/4 done | workplan_id: `0c7e3b0c-1c36-568c-af51-b06f912a0f1d` + +**Open tasks:** +- ! Bind a real dated window and owner receipt manifests `1ae5a6b8` + *(wait: Platform preparation and founder design choice are complete. Independent informed-decision runtime source/publication, KeyCape custody/registration/issuance, secrets-engine consumer custody coordinates and provider setup-link handoff remain; dependency-requirements.md names the exact owner deliveries.)* + +### Implement reviewed credential lanes with separate owner gates +Progress: 3/9 done | workplan_id: `975db491-5412-5e27-8e34-14a2417bb039` + +**Open tasks:** +- ! Accept and provision secrets-engine service JWT login `e0c82ea9` + *(wait: KEY-WP-0037 now owns protected custody, real registration and native issuance. Platform issuer-reader source CCR-2026-0033 is prepared. secrets-engine must accept exact consumer host/file/delivery/rotation coordinates before the separate custody child is finalized; KeyCape exact-primary identity repair and separate live go remain.)* +- ! Implement the platform operator-write CCR contract and Fluid lane `f47e5bc5` + *(wait: MASON-WP-0005-T02 source contract is done (ops-mason message 79570eb8). The attended policy/schema apply remains with the open provisioning decision.)* +- ! Admit the separate approval client-side readers `68bff751` + *(wait: INFD-WP-0001-T07/T08 terminal; remaining gate is human approval of the scoped delivery proof; front door stays non-resolvable.)* +- ! Dispose of the ungoverned whynot-design npm duplicate `0dd7c9a5` + *(wait: Founder HOLD (2026-09-21) on the legacy whynot-design npm pointer; custody disposition is an attended decision.)* +- ! Admit the two factory audit-sender custody lanes `0ef52c26` + *(wait: Provisioned and delivered 2026-09-11 (CCR-2026-0021/0022 applied); do not repeat. Receiver rollout with evidence_kind and native producer acceptance passed 2026-09-11. Remaining human acceptance: independent archive readback and revocation of the interim audit bearer tokens.)* +- ! Founder approval of the CCR-2026-0033 custody child `7c753be6` + +### Onboard rapp-policy-nexus to the ArgoCD production lane +Progress: 3/5 done | workplan_id: `ec41a4bd-df18-5b07-9b63-ccb80d9f001c` + +**Open tasks:** +- ! Adopt policy-nexus (live, founder go-ahead) `8638fa73` + *(wait: Founder go-ahead: the merge is a live production change (CONSTRUCT @ railiance-platform, activation=APPROVED); also needs the root automation restored in RPF-WP-0044-T07.)* +- ! Prepare the Application and the dry-run evidence `2afb1aac` + *(wait: Draft is prepared. The attended read-only helm history / kubectl diff remains. This session did not touch the cluster.)* + +### Coordinate KeyCape live Secret exposure recovery +Progress: 4/6 done | workplan_id: `038bc3c0-4492-5b91-95eb-ae515ca205df` + +**Open tasks:** +- ! T03 — Collect exact owner acknowledgements `e019c166` + *(wait: Canonical RPF-WP-0027-T03: complete platform custody acknowledgements remain missing.)* +- ! T06 — Publish the Railiance/OpenBao custody handoff `69326850` + *(wait: Canonical RPF-WP-0027-T06: complete field/auth/expiry/handoff contract absent; September 15 operator block remains in force.)* + +### Coordinate audit-core temporary custody and recovery exercises +Progress: 2/4 done | workplan_id: `88c4ef7f-0af8-580e-90dc-a2bae2675a4d` + +**Open tasks:** +- ! T02 — Define the runtime database lease recovery exercise `caba7fcd` + *(wait: Canonical RPF-WP-0015-T02: approved sender identity, fresh bounded attended window, named abort operator and live recovery receipt required.)* +- ! T03 — Define the coordinated railiance01 reboot exercise `09cf4065` + *(wait: Canonical RPF-WP-0015-T03: fresh outage approval, off-host snapshot, quorum/access evidence and owner execution required.)* + +### Retract public OpenBao listener behind operator-only access +Progress: 2/3 done | workplan_id: `6f8a6cbc-c076-5f0a-ade2-281a7ec71360` + +**Open tasks:** +- ! T03 — Complete the attended operator cutover `99f8b41f` + +## Inbox Hygiene + +**Missing thread_id:** 1 unread message(s) lack supersession chains. + +--- +## MCP Orientation (when available) + +If the state-hub MCP server is reachable, call: +`get_domain_summary("financials")` +This provides richer cross-domain context. +If the MCP call fails, use this file as your orientation source. diff --git a/.forgejo/workflows/ccr-intake.yaml b/.forgejo/workflows/ccr-intake.yaml deleted file mode 100644 index e3fe8df..0000000 --- a/.forgejo/workflows/ccr-intake.yaml +++ /dev/null @@ -1,43 +0,0 @@ -# RPF-WP-0061-T03: self-service CCR intake gate. -# ccr/* branches: exactly one new in-profile CCR (+ its new policy file) from a -# listed submitter, nothing else changed. main: post-merge id uniqueness. -# Source comes from the exact-revision archive, as in keycape-factor-acceptance. -name: CCR intake gate -on: - push: - branches: ["ccr/**", main] - workflow_dispatch: -jobs: - gate: - runs-on: ubuntu-latest - container: - image: python:3.12-slim@sha256:d764629ce0ddd8c71fd371e9901efb324a95789d2315a47db7e4d27e78f1b0e9 - steps: - - name: Fetch exact revision and main - run: | - set -eu - python3 - <<'PYCODE' - import os,tarfile,tempfile,urllib.request - from pathlib import Path - base='https://forgejo.coulomb.social/'+os.environ['GITHUB_REPOSITORY']+'/archive/' - for name,ref in (('branch',os.environ['GITHUB_SHA']),('main','main')): - root=Path('/tmp/ccr-intake')/name;root.mkdir(parents=True) - with urllib.request.urlopen(base+ref+'.tar.gz',timeout=30) as response,tempfile.TemporaryFile() as archive: - archive.write(response.read());archive.seek(0) - with tarfile.open(fileobj=archive,mode='r:gz') as tar:tar.extractall(root,filter='data') - PYCODE - pip install --quiet --disable-pip-version-check --root-user-action=ignore --timeout 60 pyyaml==6.0.2 jsonschema==4.23.0 - - name: Gate ccr branch - if: startsWith(github.ref, 'refs/heads/ccr/') - run: | - set -eu - cd /tmp/ccr-intake/branch/* - main_root=$(echo /tmp/ccr-intake/main/*) - PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s tests -p 'test_ccr_intake.py' - python3 scripts/credential-change.py check-branch --branch "$GITHUB_REF" --main-root "$main_root" - - name: Check CCR ids on main - if: github.ref == 'refs/heads/main' - run: | - set -eu - cd /tmp/ccr-intake/branch/* - python3 scripts/credential-change.py check-ids diff --git a/.generated-work-records.json b/.generated-work-records.json deleted file mode 100644 index c79e8bc..0000000 --- a/.generated-work-records.json +++ /dev/null @@ -1 +0,0 @@ -{"version":1,"repo_slug":"railiance-platform"} diff --git a/.generated-work-records.mk b/.generated-work-records.mk deleted file mode 100644 index 751f25c..0000000 --- a/.generated-work-records.mk +++ /dev/null @@ -1,8 +0,0 @@ -# Explicit opt-in recipe; no environment files or external make includes. -CUSTODIAN_SOURCE ?= /home/worsch/the-custodian -GENERATED_REPO_SLUG ?= railiance-platform -.PHONY: generated-work-records generated-work-records-check -generated-work-records: - python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)" -generated-work-records-check: - python3 "$(CUSTODIAN_SOURCE)/scripts/generate_staged_work_records.py" --repo . --repo-slug "$(GENERATED_REPO_SLUG)" --check diff --git a/.gitignore b/.gitignore index f721a05..9fd4227 100644 --- a/.gitignore +++ b/.gitignore @@ -13,7 +13,6 @@ helm/*.yaml !helm/*-probe.yaml !helm/vergabe-demo-company-role.yaml !helm/vergabe-demo-company-database.yaml -!helm/playwright-consumer-access.yaml # ArgoCD repository credentials — encrypt locally, never commit argocd/repositories/*.repository.sops.yaml @@ -33,6 +32,3 @@ tools/vendor/bin/kubectl # Derived local Repo Manager cache .repo-manager/ - -# Generated workstation orientation; keep local, regenerate from State Hub. -/.custodian-brief.md diff --git a/Makefile b/Makefile index a316076..3b1eb71 100644 --- a/Makefile +++ b/Makefile @@ -58,10 +58,18 @@ CREDENTIAL_HELPER_PURPOSE ?= flex-auth-openbao-smoke ##@ CloudNative PG (cnpg) — primary database operator -db-deploy db-status db-shell db-logs: ## Retired Gitea-era gitea-db targets; use forgejo-db-* - @echo "gitea-db was the CoulombCore-era Gitea database and does not exist on railiance01." >&2 - @echo "Use forgejo-db-deploy / forgejo-db-status / forgejo-db-shell (RAPPS-WP-0019-T05)." >&2 - @exit 2 +db-deploy: ## Apply Gitea cnpg Cluster (creates gitea-db in databases namespace) + $(KUBECTL) apply -f helm/gitea-db-cluster.yaml + +db-status: ## Show cnpg cluster health + $(KUBECTL) cnpg status gitea-db -n databases 2>/dev/null || \ + $(KUBECTL) get cluster gitea-db -n databases -o wide + +db-shell: ## Open psql shell on gitea-db primary + $(KUBECTL) cnpg psql gitea-db -n databases -- -U gitea gitea + +db-logs: ## Tail gitea-db primary logs + $(KUBECTL) logs -n databases -l cnpg.io/cluster=gitea-db -f --tail=50 ##@ Forgejo database (railiance01 production forge) @@ -305,23 +313,6 @@ openbao-validate-emergency-evidence: ## Validate non-secret OpenBao emergency se OPENBAO_EMERGENCY_EVIDENCE='$(OPENBAO_EMERGENCY_EVIDENCE)' \ scripts/openbao-validate-emergency-drill-evidence.sh -OPENBAO_REHEARSAL_IMAGE ?= quay.io/openbao/openbao:2.5.4 - -openbao-isolated-restore-test: ## RPF-WP-0064 offline tests: admission, v2 receipt verifier, launcher (no Docker) - PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_openbao_isolated_restore - -openbao-isolated-restore-image: ## Pull the public OpenBao image used by the synthetic rehearsal (no custody) - docker pull -q $(OPENBAO_REHEARSAL_IMAGE) - -openbao-isolated-restore-rehearsal: ## RPF-WP-0064-T04 native rehearsal: --network none containers, generated material only - PYTHONDONTWRITEBYTECODE=1 python3 -m unittest -v tests.test_openbao_isolated_restore_native - -openbao-verify-restore-receipt: ## Verify a v2 isolated-restore receipt (RECEIPT=/abs/path SOURCE_ROOT=clean checkout [NOT_AFTER=]) - python3 scripts/openbao-isolated-restore/verify-restore-receipt.py --receipt '$(RECEIPT)' \ - --source-root '$(or $(SOURCE_ROOT),$(CURDIR))' $(if $(NOT_AFTER),--not-after '$(NOT_AFTER)') - -.PHONY: openbao-isolated-restore-test openbao-isolated-restore-image openbao-isolated-restore-rehearsal openbao-verify-restore-receipt - ##@ Credential broker credential-grants-validate: ## Validate non-secret credential grant catalog @@ -460,9 +451,6 @@ argocd-repo-apply: ## Apply a SOPS-encrypted ArgoCD repository Secret (set ARGOC (echo "ERROR: set ARGOCD_REPOSITORY_SECRET=argocd/repositories/.repository.sops.yaml"; exit 1) sops -d $(ARGOCD_REPOSITORY_SECRET) | $(KUBECTL) apply -f - -argocd-pin-drift: ## Read-only: compare railiance01 child Application pins in Git with live (run before any root sync) - python3 scripts/argocd_child_pin_drift.py - argocd-status: ## Show Railiance ArgoCD projects, root app, and registered repos $(KUBECTL) get appprojects.argoproj.io -n $(ARGOCD_NAMESPACE) \ railiance-bootstrap railiance-tenants railiance-platform-addons @@ -538,82 +526,17 @@ audit-database-lease-approval-check: ## Validate an approved T02 receipt without python3 scripts/audit-core-database-lease-recovery.py validate-approval \ --approval "$(AUDIT_DATABASE_LEASE_APPROVAL)" -ATTENDED_EXEC = python3 scripts/openbao-attended-exec.py -- -AUDIT_T02_NEED_APPROVAL = @test -n "$(AUDIT_DATABASE_LEASE_APPROVAL)" || { echo 'set AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json' >&2; exit 2; } -AUDIT_T02_NEED_DRIVER = @test -n "$(AUDIT_DATABASE_LEASE_DRIVER)" || { echo 'set AUDIT_DATABASE_LEASE_DRIVER=/path/to/approved-load-driver' >&2; exit 2; } -AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM = @test -n "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)" || { echo 'set AUDIT_SYNTHETIC_SENDER_CONFIRM to the confirm string printed by preflight' >&2; exit 2; } -AUDIT_T02_NEED_BEARER = @test -n "$(AUDIT_T02_TOKEN_FILE)" || { echo 'set AUDIT_T02_TOKEN_FILE=/abs/path/outside/git (0700 dir)' >&2; exit 2; } - -audit-t02-attended-window: ## One contained login: project, port-forward, T02 exercise, evidence, cleanup - $(AUDIT_T02_NEED_APPROVAL) - $(AUDIT_T02_NEED_DRIVER) - $(AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM) - $(AUDIT_T02_NEED_BEARER) - @test -n "$(AUDIT_T02_EVIDENCE_DIR)" || { echo 'set AUDIT_T02_EVIDENCE_DIR=/abs/dir/outside/git (0700)' >&2; exit 2; } - $(ATTENDED_EXEC) scripts/t02-attended-window.py \ - --approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \ - --load-driver "$(abspath $(AUDIT_DATABASE_LEASE_DRIVER))" \ - --bearer-file "$(AUDIT_T02_TOKEN_FILE)" --evidence-dir "$(AUDIT_T02_EVIDENCE_DIR)" \ - --confirm "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)" $(if $(AUDIT_T02_PORT),--port $(AUDIT_T02_PORT)) \ - $(if $(AUDIT_T02_MODE),--mode "$(AUDIT_T02_MODE)") - -audit-database-lease-recovery-exercise: ## Exercise step alone, inside its own contained login (prefer audit-t02-attended-window) - $(AUDIT_T02_NEED_APPROVAL) - $(AUDIT_T02_NEED_DRIVER) - @test -n "$(AUDIT_T02_RESULT_FILE)" || { echo 'set AUDIT_T02_RESULT_FILE=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; } - $(ATTENDED_EXEC) scripts/audit-core-database-lease-recovery.py exercise --contained --result-file "$(AUDIT_T02_RESULT_FILE)" \ - --approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \ - --load-driver "$(abspath $(AUDIT_DATABASE_LEASE_DRIVER))" \ +audit-database-lease-recovery-exercise: ## Run the separately approved attended T02 exercise + @test -n "$(AUDIT_DATABASE_LEASE_APPROVAL)" || { echo 'set AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json' >&2; exit 2; } + @test -n "$(AUDIT_DATABASE_LEASE_DRIVER)" || { echo 'set AUDIT_DATABASE_LEASE_DRIVER=/path/to/approved-load-driver' >&2; exit 2; } + python3 scripts/audit-core-database-lease-recovery.py exercise \ + --approval "$(AUDIT_DATABASE_LEASE_APPROVAL)" \ + --load-driver "$(AUDIT_DATABASE_LEASE_DRIVER)" \ --confirm RAILIANCE-WP-0024-T02:attended -audit-synthetic-sender-test: ## Unit tests for the synthetic sender lane and T02 window (no network) - PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_audit_core_synthetic_sender_lane tests.test_t02_attended_window - -audit-synthetic-sender-preflight: ## Read-only T02 sender gate; cluster checks only (registry metadata needs --contained) - $(AUDIT_T02_NEED_APPROVAL) - python3 scripts/audit_core_synthetic_sender_lane.py preflight --approval "$(AUDIT_DATABASE_LEASE_APPROVAL)" \ - $(if $(AUDIT_T02_TOKEN_FILE),--bearer-file "$(AUDIT_T02_TOKEN_FILE)") $(AUDIT_SYNTHETIC_SENDER_PREFLIGHT_ARGS) - -audit-synthetic-sender-project: ## Projection step alone, inside its own contained login (prefer audit-t02-attended-window) - $(AUDIT_T02_NEED_APPROVAL) - $(AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM) - $(AUDIT_T02_NEED_BEARER) - @test -n "$(AUDIT_T02_RESULT_FILE)" || { echo 'set AUDIT_T02_RESULT_FILE=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; } - $(ATTENDED_EXEC) scripts/audit_core_synthetic_sender_lane.py project --contained --result-file "$(AUDIT_T02_RESULT_FILE)" \ - --approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \ - --bearer-file "$(AUDIT_T02_TOKEN_FILE)" --confirm "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)" - -audit-synthetic-sender-cleanup: ## Idempotent cleanup inside a contained login (evidence or abort reason required) - $(AUDIT_T02_NEED_APPROVAL) - $(AUDIT_SYNTHETIC_SENDER_NEED_CONFIRM) - @test -n "$(AUDIT_T02_EVIDENCE)$(AUDIT_SYNTHETIC_SENDER_ABORT_REASON)" || { echo 'set AUDIT_T02_EVIDENCE=/path/to/exercise-result.json or AUDIT_SYNTHETIC_SENDER_ABORT_REASON=...' >&2; exit 2; } - @test -n "$(AUDIT_T02_RESULT_FILE)" || { echo 'set AUDIT_T02_RESULT_FILE=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; } - $(ATTENDED_EXEC) scripts/audit_core_synthetic_sender_lane.py cleanup --contained --result-file "$(AUDIT_T02_RESULT_FILE)" \ - --approval "$(abspath $(AUDIT_DATABASE_LEASE_APPROVAL))" \ - --confirm "$(AUDIT_SYNTHETIC_SENDER_CONFIRM)" \ - $(if $(AUDIT_T02_TOKEN_FILE),--bearer-file "$(AUDIT_T02_TOKEN_FILE)") \ - $(if $(AUDIT_T02_EVIDENCE),--exercise-evidence "$(abspath $(AUDIT_T02_EVIDENCE))") \ - $(if $(AUDIT_SYNTHETIC_SENDER_ABORT_REASON),--abort-reason "$(AUDIT_SYNTHETIC_SENDER_ABORT_REASON)") - railiance01-reboot-preflight: ## Read-only gate for the coordinated single-node reboot python3 scripts/audit-core-recovery-preflight.py node-reboot $(RAILIANCE01_REBOOT_PREFLIGHT_ARGS) -.PHONY: openbao-reboot-snapshot openbao-reboot-snapshot-test -openbao-reboot-snapshot: ## Attended Raft snapshot -> age -> verify -> offsite; silent child writes SNAPSHOT_RECEIPT - @test -n "$(SNAPSHOT_SCENARIO_ID)" || { echo 'set SNAPSHOT_SCENARIO_ID=' >&2; exit 2; } - @test -n "$(SNAPSHOT_RECEIPT)" || { echo 'set SNAPSHOT_RECEIPT=/abs/new/file outside Git (the contained child must stay silent)' >&2; exit 2; } - @test ! -e "$(SNAPSHOT_RECEIPT)" || { echo 'SNAPSHOT_RECEIPT already exists' >&2; exit 2; } - @test -n "$(SNAPSHOT_CUSTODIAN_ESCROW_REF)" || { echo 'set SNAPSHOT_CUSTODIAN_ESCROW_REF=' >&2; exit 2; } - env -u OPENBAO_TOKEN -u VAULT_TOKEN -u BAO_TOKEN \ - $(ATTENDED_EXEC) scripts/openbao-reboot-snapshot.py \ - --scenario-id "$(SNAPSHOT_SCENARIO_ID)" --receipt-file "$(abspath $(SNAPSHOT_RECEIPT))" \ - $(if $(SNAPSHOT_PRIVATE_DIR),--private-dir "$(abspath $(SNAPSHOT_PRIVATE_DIR))") \ - --custodian-escrow-ref "$(SNAPSHOT_CUSTODIAN_ESCROW_REF)" \ - $(if $(SNAPSHOT_CUSTODIAN_RECIPIENT),--custodian-recipient "$(SNAPSHOT_CUSTODIAN_RECIPIENT)") - -openbao-reboot-snapshot-test: ## Offline unit tests for the attended snapshot lane (no OpenBao, no network) - PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_openbao_reboot_snapshot - wp0024-owner-show: ## Show one owner's hash-bound review contract (set OWNER) @test -n "$(OWNER)" || { echo 'set OWNER=audit-core|rapp-postgres|railiance-cluster|railiance-infra' >&2; exit 2; } @python3 scripts/wp0024-owner-review.py show --owner "$(OWNER)" @@ -637,7 +560,7 @@ wp0024-t02-driver-register: ## Register committed audit-core driver source witho @python3 scripts/wp0024-t02-driver-candidate.py register \ --repo "$(abspath ../audit-core)" --driver "$(DRIVER)" --reviewer "$(REVIEWER)" -.PHONY: audit-t02-attended-window audit-synthetic-sender-test audit-synthetic-sender-preflight audit-synthetic-sender-project audit-synthetic-sender-cleanup audit-whitehat-e2-preflight audit-whitehat-e2-project audit-whitehat-e2-cleanup audit-database-lease-recovery-preflight audit-database-lease-approval-check audit-database-lease-recovery-exercise railiance01-reboot-preflight wp0024-owner-show wp0024-owner-verify wp0024-owner-status wp0024-t02-driver-show wp0024-t02-driver-status wp0024-t02-driver-register apps-pg-isolation-probe +.PHONY: audit-whitehat-e2-preflight audit-whitehat-e2-project audit-whitehat-e2-cleanup audit-database-lease-recovery-preflight audit-database-lease-approval-check audit-database-lease-recovery-exercise railiance01-reboot-preflight wp0024-owner-show wp0024-owner-verify wp0024-owner-status wp0024-t02-driver-show wp0024-t02-driver-status wp0024-t02-driver-register apps-pg-isolation-probe .PHONY: db-deploy db-status db-shell db-logs forgejo-db-deploy forgejo-db-status forgejo-db-shell apps-pg-deploy apps-pg-status apps-pg-shell apps-pg-logs net-kingdom-pg-inter-hub-networkpolicy-deploy pg-deploy pg-status pg-pgpool-check valkey-deploy valkey-status openbao-repo openbao-dry-run openbao-overlay-apply openbao-verify-login-overlay openbao-deploy openbao-status openbao-verify openbao-verify-post-unseal openbao-configure-initial openbao-configure-ssh openbao-verify-ssh openbao-verify-authenticated openbao-configure-external-secrets-issue-core openbao-configure-external-secrets-activity-core openbao-configure-external-secrets-forgejo openbao-validate-restore-evidence openbao-validate-emergency-evidence credential-grants-validate credential-change-applier-dry-run credential-change-applier-apply-plan credential-change-applier-apply credential-change-runbook credential-change-record-evidence credential-change-lifecycle-plan credential-change-lifecycle-event credential-change-import-inventory agent-high-risk-boundary-check openbao-credential-change-appliers-dry-run openbao-configure-credential-change-appliers openbao-token-grants-dry-run openbao-configure-token-grants openbao-verify-token-grants-dry-run openbao-verify-token-grants openbao-verify-token-grants-smoke credential-helper-dry-run credential-tests credential-exec-ops-warden-smoke argocd-bootstrap-dry-run argocd-bootstrap-deploy argocd-repo-apply argocd-status backup forgejo-backup forgejo-backup-dry-run forgejo-backup-status forgejo-package-prune forgejo-package-prune-dry-run reef-deployables apps-pg-evidence consumption-preflight help ##@ S3 service assurance (read-only) @@ -659,21 +582,3 @@ keycape-factor-tests: ## Validate factor custody, scope, renewal and config pres .PHONY: keycape-factor-live-acceptance keycape-factor-live-acceptance: ## Verify native scope and rotation (creates bounded proof/renewal Jobs) PYTHONDONTWRITEBYTECODE=1 python3 scripts/keycape_factor_acceptance.py - -# CUST-WP-0079-T05: staged-source generation; hook uses the isolated recipe. -.PHONY: generated-work-records generated-work-records-check -generated-work-records: - $(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records -generated-work-records-check: - $(MAKE) --no-print-directory -f .generated-work-records.mk generated-work-records-check - -##@ Service-identity metadata (NK-WP-0054-T04, RPF-WP-0067) -.PHONY: service-identity-metadata-tests service-identity-metadata-openbao service-identity-metadata-postgresql-sql -service-identity-metadata-tests: ## Offline fixture tests for the value-safe identity metadata exporter - PYTHONDONTWRITEBYTECODE=1 python3 -m unittest -v tests.test_service_identity_metadata_export - -service-identity-metadata-openbao: ## Read-only OpenBao auth-role/policy export via the operator's bao login (OUT=file) - python3 scripts/service_identity_metadata_export.py --output "$${OUT:-openbao-identity-metadata.json}" openbao - -service-identity-metadata-postgresql-sql: ## Print the read-only pg_roles/pg_auth_members SELECT - @python3 scripts/service_identity_metadata_export.py postgresql-sql diff --git a/SCOPE.md b/SCOPE.md index 40e1f25..b693503 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -106,13 +106,6 @@ description: S3 custody, policy and approved runtime credential delivery; packag keywords: [openbao, secrets, vault-compatible, secret-management, dynamic-credentials, audit, kubernetes-auth] ``` -```capability -type: security -title: Supervised agent observer access -description: custodian-agents namespace and ServiceAccount with ClusterRole custodian-supervised-observer (curated read; no secrets, configmaps, logs, exec or writes) for founder-issued short-lived agent tokens, plus the OpenBao agt SSH role limited to permit-pty. Writes are routed through gated-executor approvals. -keywords: [rbac, serviceaccount, supervised-agents, read-only, openbao, ssh-role] -``` - ## Getting oriented Read [AGENTS.md](AGENTS.md), [INTENT.md](INTENT.md), then @@ -161,41 +154,3 @@ remain RPF-WP-0038-T04. The account cutover and archive-integrity fix do not establish that coverage. WP-0029 retains secondary credential invalidation/recovery; its completion must not be presented as full primary-backup assurance. - - -## Supervised agent access — 2026-10-20 - -RPF-WP-0062 adds the `custodian-agents` namespace, the `custodian-agent` -ServiceAccount and ClusterRole `custodian-supervised-observer`: curated read -only, with no secrets, configmaps, logs, exec or writes. It is synced and -pinned at `567d2c6`; the founder issues 24-hour tokens for it under -CUST-WP-0073. The OpenBao `agt` SSH role now allows only the `permit-pty` -extension (`e415da9`). `scripts/openbao-apply-ssh-engine.sh` previously never -wrote roles, because it passed `bao` twice and exited 127; it is fixed at `4b2b72a`. -Privileged actions for these agents go through gated-executor and not through -this role. - - -## Repository maintenance reuse - -The tracked `.generated-work-records.json` adopts the Custodian staged-source -adapter for WORK-RECORDS.md. `make generated-work-records` uses the existing -State Hub renderer through `.generated-work-records.mk`; the shared pre-commit -hook runs this isolated recipe only for opted-in repositories. The corresponding -`make generated-work-records-check` is read-only. Canonical repository identity -is explicit and does not depend on the checkout directory. - -Unstaged source changes are preserved; conflicts and unstaged generated-file -changes stop the commit. For `git commit --only`, generate first and include -WORK-RECORDS.md with the selected source paths. Existing generated-only guards -and exact-scope secret scans remain in place. This is repository maintenance, -not a runtime service, authorization decision, deployment or publication. -Other index generators and hub-derived briefs retain their owner boundaries. - - -## Local orientation brief - -`.custodian-brief.md` is a generated local orientation snapshot. It remains on -disk for offline use and is root-ignored rather than versioned. Regenerate it -with the State Hub brief renderer; workplans and other authored repository -records remain authoritative. Brief refreshes must not create Git commits. diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index cdfbc63..3b5f292 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -8,10 +8,7 @@ | Kind | ID | Status | Lane | Source | | --- | --- | --- | --- | --- | -| workplan | RPF-WP-ADHOC-2026-10-10 | finished | — | workplans/ADHOC-2026-10-10.md | -| workplan | RPF-WP-ADHOC-2026-10-11 | finished | — | workplans/ADHOC-2026-10-11.md | -| workplan | RPF-WP-ADHOC-2026-10-19 | finished | — | workplans/ADHOC-2026-10-19.md | -| workplan | RPF-WP-0015 | active | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | +| workplan | RPF-WP-0015 | blocked | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | workplan | RPF-WP-0025 | finished | — | workplans/RPF-WP-0025-openbao-operator-only-access.md | | workplan | RPF-WP-0027 | blocked | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md | | workplan | RPF-WP-0029 | finished | — | workplans/RPF-WP-0029-backup-credential-default-removal.md | @@ -29,35 +26,10 @@ | workplan | RPF-WP-0047 | finished | — | workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md | | workplan | RPF-WP-0048 | blocked | — | workplans/RPF-WP-0048-activity-core-gitops-adoption.md | | workplan | RPF-WP-0049 | finished | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md | -| workplan | RPF-WP-0050 | finished | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md | +| workplan | RPF-WP-0050 | blocked | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md | | workplan | RPF-WP-0051 | finished | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md | -| workplan | RPF-WP-0052 | finished | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md | -| workplan | RPF-WP-0053 | active | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md | -| workplan | RPF-WP-0054 | finished | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| workplan | RPF-WP-0055 | backlog | — | workplans/RPF-WP-0055-keycape-client-secret-rotation-residual.md | -| workplan | RPF-WP-0056 | blocked | — | workplans/RPF-WP-0056-playwright-consumer-admission.md | -| workplan | RPF-WP-0057 | active | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md | -| workplan | RPF-WP-0058 | active | — | workplans/RPF-WP-0058-reuse-private-source-reader.md | -| workplan | RPF-WP-0059 | active | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md | -| workplan | RPF-WP-0060 | active | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md | -| workplan | RPF-WP-0061 | active | — | workplans/RPF-WP-0061-self-service-ccr-intake.md | -| workplan | RPF-WP-0062 | finished | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | -| workplan | RPF-WP-0063 | finished | — | workplans/RPF-WP-0063-wave2-resource-right-sizing.md | -| workplan | RPF-WP-0064 | active | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| workplan | RPF-WP-0065 | proposed | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| workplan | RPF-WP-0066 | proposed | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md | -| workplan | RPF-WP-0067 | active | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md | -| workplan | RPF-WP-0068 | active | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md | -| workplan | RPF-WP-0069 | active | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | -| task | RPF-WP-ADHOC-2026-10-10-T01 | done | — | workplans/ADHOC-2026-10-10.md | -| task | RPF-WP-ADHOC-2026-10-11-T01 | done | — | workplans/ADHOC-2026-10-11.md | -| task | RPF-WP-ADHOC-2026-10-11-T02 | done | — | workplans/ADHOC-2026-10-11.md | -| task | RPF-WP-ADHOC-2026-10-11-T03 | done | — | workplans/ADHOC-2026-10-11.md | -| task | RPF-WP-ADHOC-2026-10-19-T01 | done | — | workplans/ADHOC-2026-10-19.md | -| task | RPF-WP-ADHOC-2026-10-19-T02 | done | — | workplans/ADHOC-2026-10-19.md | -| task | RPF-WP-ADHOC-2026-10-19-T03 | done | — | workplans/ADHOC-2026-10-19.md | | task | RPF-WP-0015-T01 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | -| task | RPF-WP-0015-T02 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | +| task | RPF-WP-0015-T02 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0015-T03 | wait | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0015-T04 | done | — | workplans/RPF-WP-0015-audit-core-custody-and-recovery-coordination.md | | task | RPF-WP-0025-T01 | done | — | workplans/RPF-WP-0025-openbao-operator-only-access.md | @@ -77,15 +49,10 @@ | task | RPF-WP-0035-T03 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0035-T04 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0035-T05 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | -| task | RPF-WP-0035-T06 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | +| task | RPF-WP-0035-T06 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0035-T07 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0035-T08 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | -| task | RPF-WP-0035-T09 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | -| task | RPF-WP-0035-T10 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | -| task | RPF-WP-0035-T11 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | -| task | RPF-WP-0035-T12 | progress | — | workplans/RPF-WP-0035-credential-lane-implementation.md | -| task | RPF-WP-0035-T13 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md | -| task | RPF-WP-0035-T14 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | +| task | RPF-WP-0035-T09 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md | | task | RPF-WP-0036-T01 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md | | task | RPF-WP-0036-T02 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md | | task | RPF-WP-0036-T03 | wait | — | workplans/RPF-WP-0036-platform-service-assurance.md | @@ -113,12 +80,12 @@ | task | RPF-WP-0043-T05 | done | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md | | task | RPF-WP-0044-T01 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | | task | RPF-WP-0044-T02 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | -| task | RPF-WP-0044-T03 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | -| task | RPF-WP-0044-T04 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | -| task | RPF-WP-0044-T05 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | +| task | RPF-WP-0044-T03 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | +| task | RPF-WP-0044-T04 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | +| task | RPF-WP-0044-T05 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | | task | RPF-WP-0044-T06 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | | task | RPF-WP-0044-T07 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | -| task | RPF-WP-0044-T08 | done | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | +| task | RPF-WP-0044-T08 | wait | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md | | task | RPF-WP-0045-T01 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md | | task | RPF-WP-0045-T02 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md | | task | RPF-WP-0045-T03 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md | @@ -144,95 +111,11 @@ | task | RPF-WP-0049-T05 | done | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md | | task | RPF-WP-0050-T01 | done | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md | | task | RPF-WP-0050-T02 | done | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md | -| task | RPF-WP-0050-T03 | cancel | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md | +| task | RPF-WP-0050-T03 | wait | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md | | task | RPF-WP-0050-T04 | done | — | workplans/RPF-WP-0050-attended-provisioning-preparation.md | | task | RPF-WP-0051-T01 | done | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md | | task | RPF-WP-0051-T02 | done | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md | | task | RPF-WP-0051-T03 | done | — | workplans/RPF-WP-0051-informed-decision-requester-workload-delivery.md | -| task | RPF-WP-0052-T01 | done | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md | -| task | RPF-WP-0052-T02 | done | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md | -| task | RPF-WP-0052-T03 | done | — | workplans/RPF-WP-0052-authelia-platform-smtp-admission.md | -| task | RPF-WP-0053-T01 | done | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md | -| task | RPF-WP-0053-T02 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md | -| task | RPF-WP-0053-T03 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md | -| task | RPF-WP-0053-T04 | todo | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md | -| task | RPF-WP-0053-T05 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md | -| task | RPF-WP-0053-T06 | done | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md | -| task | RPF-WP-0054-T01 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| task | RPF-WP-0054-T02 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| task | RPF-WP-0054-T03 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| task | RPF-WP-0054-T04 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| task | RPF-WP-0054-T05 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| task | RPF-WP-0054-T06 | done | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| task | RPF-WP-0054-T07 | cancel | — | workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md | -| task | RPF-WP-0055-T01 | todo | — | workplans/RPF-WP-0055-keycape-client-secret-rotation-residual.md | -| task | RPF-WP-0056-T01 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md | -| task | RPF-WP-0056-T02 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md | -| task | RPF-WP-0056-T03 | wait | — | workplans/RPF-WP-0056-playwright-consumer-admission.md | -| task | RPF-WP-0056-T04 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md | -| task | RPF-WP-0056-T05 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md | -| task | RPF-WP-0056-T06 | done | — | workplans/RPF-WP-0056-playwright-consumer-admission.md | -| task | RPF-WP-0057-T01 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md | -| task | RPF-WP-0057-T02 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md | -| task | RPF-WP-0057-T03 | done | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md | -| task | RPF-WP-0057-T04 | progress | — | workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md | -| task | RPF-WP-0058-T01 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md | -| task | RPF-WP-0058-T02 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md | -| task | RPF-WP-0058-T03 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md | -| task | RPF-WP-0058-T04 | done | — | workplans/RPF-WP-0058-reuse-private-source-reader.md | -| task | RPF-WP-0058-T05 | wait | — | workplans/RPF-WP-0058-reuse-private-source-reader.md | -| task | RPF-WP-0059-T01 | done | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md | -| task | RPF-WP-0059-T02 | done | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md | -| task | RPF-WP-0059-T03 | done | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md | -| task | RPF-WP-0059-T04 | progress | — | workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md | -| task | RPF-WP-0060-T01 | progress | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md | -| task | RPF-WP-0060-T02 | done | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md | -| task | RPF-WP-0060-T03 | todo | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md | -| task | RPF-WP-0060-T04 | todo | — | workplans/RPF-WP-0060-browser-case-platform-bindings.md | -| task | RPF-WP-0061-T01 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md | -| task | RPF-WP-0061-T02 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md | -| task | RPF-WP-0061-T03 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md | -| task | RPF-WP-0061-T04 | done | — | workplans/RPF-WP-0061-self-service-ccr-intake.md | -| task | RPF-WP-0061-T05 | wait | — | workplans/RPF-WP-0061-self-service-ccr-intake.md | -| task | RPF-WP-0062-T01 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | -| task | RPF-WP-0062-T02 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | -| task | RPF-WP-0062-T03 | done | — | workplans/RPF-WP-0062-custodian-agent-observer-identity.md | -| task | RPF-WP-0063-T01 | done | — | workplans/RPF-WP-0063-wave2-resource-right-sizing.md | -| task | RPF-WP-0064-T01 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T02 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T03 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T04 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T05 | wait | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T06 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T07 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T08 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T09 | todo | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0064-T10 | done | — | workplans/RPF-WP-0064-isolated-original-openbao-restore.md | -| task | RPF-WP-0065-T01 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T02 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T03 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T04 | done | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0065-T05 | wait | — | workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md | -| task | RPF-WP-0066-T01 | todo | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md | -| task | RPF-WP-0066-T02 | wait | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md | -| task | RPF-WP-0066-T03 | todo | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md | -| task | RPF-WP-0066-T04 | wait | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md | -| task | RPF-WP-0066-T05 | wait | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md | -| task | RPF-WP-0066-T06 | todo | — | workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md | -| task | RPF-WP-0067-T01 | done | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md | -| task | RPF-WP-0067-T02 | done | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md | -| task | RPF-WP-0067-T03 | wait | — | workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md | -| task | RPF-WP-0068-T01 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md | -| task | RPF-WP-0068-T02 | done | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md | -| task | RPF-WP-0068-T03 | wait | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md | -| task | RPF-WP-0068-T04 | todo | — | workplans/RPF-WP-0068-email-connect-platform-smtp.md | -| task | RPF-WP-0069-T01 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | -| task | RPF-WP-0069-T02 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | -| task | RPF-WP-0069-T03 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | -| task | RPF-WP-0069-T04 | done | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | -| task | RPF-WP-0069-T05 | wait | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | -| task | RPF-WP-0069-T06 | cancel | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | -| task | RPF-WP-0069-T07 | todo | — | workplans/RPF-WP-0069-bounded-executor-lanes.md | | intake | RPF-IN-0001 | closed | — | intakes/intakes.md | | intake | RPF-IN-0002 | open | — | intakes/intakes.md | | intake | RPF-IN-0003 | open | — | intakes/intakes.md | diff --git a/argocd/platform-addons/bao-notice/default.conf b/argocd/platform-addons/bao-notice/default.conf new file mode 100644 index 0000000..7493c08 --- /dev/null +++ b/argocd/platform-addons/bao-notice/default.conf @@ -0,0 +1,35 @@ +# Static notice for bao.coulomb.social (RPF-WP-0047). Serves one page; proxies nothing. +server { + listen 8080; + server_name _; + server_tokens off; + root /usr/share/nginx/html; + + add_header Content-Security-Policy "default-src 'none'; style-src 'unsafe-inline'; frame-ancestors 'none'" always; + add_header Strict-Transport-Security "max-age=31536000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header Referrer-Policy "no-referrer" always; + add_header Cache-Control "no-store" always; + add_header X-Robots-Tag "noindex, nofollow" always; + + location = / { + try_files /index.html =404; + } + location = /robots.txt { + default_type text/plain; + return 200 "User-agent: *\nDisallow: /\n"; + } + location = /healthz { + access_log off; + default_type text/plain; + return 200 "ok\n"; + } + # Any other path (e.g. /v1/sys/health, /ui/) is 404 with the same notice, never 200. + location / { + error_page 404 /index.html; + return 404; + } + location = /index.html { + internal; + } +} diff --git a/argocd/platform-addons/bao-notice/index.html b/argocd/platform-addons/bao-notice/index.html new file mode 100644 index 0000000..6346724 --- /dev/null +++ b/argocd/platform-addons/bao-notice/index.html @@ -0,0 +1,19 @@ + + + + + + +Not publicly available + + + +

Not publicly available

+

This address belongs to an internal service that is not offered publicly.

+

There is nothing to sign in to here. If you were sent to this address, please contact the person who gave it to you.

+ + diff --git a/argocd/platform-addons/bao-notice/kustomization.yaml b/argocd/platform-addons/bao-notice/kustomization.yaml new file mode 100644 index 0000000..e454999 --- /dev/null +++ b/argocd/platform-addons/bao-notice/kustomization.yaml @@ -0,0 +1,18 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +# RPF-WP-0047. Read only by argocd/railiance01/applications/bao-notice.application.yaml. + +resources: + - manifests.yaml + +generatorOptions: + disableNameSuffixHash: false + +configMapGenerator: + - name: bao-notice-conf + namespace: bao-notice + files: [default.conf] + - name: bao-notice-html + namespace: bao-notice + files: [index.html] diff --git a/argocd/platform-addons/bao-notice/manifests.yaml b/argocd/platform-addons/bao-notice/manifests.yaml new file mode 100644 index 0000000..755857b --- /dev/null +++ b/argocd/platform-addons/bao-notice/manifests.yaml @@ -0,0 +1,177 @@ +# RPF-WP-0047: static "not publicly available" notice for bao.coulomb.social. +# OpenBao stays non-public (RMASTER-WP-0020-T09). This namespace has no route +# to OpenBao: ingress only from Traefik, no egress at all. +apiVersion: v1 +kind: Namespace +metadata: + name: bao-notice + labels: + app.kubernetes.io/part-of: railiance-platform +--- +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: bao-notice-isolation + namespace: bao-notice +spec: + podSelector: {} + policyTypes: [Ingress, Egress] + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + app.kubernetes.io/name: traefik + ports: + - {protocol: TCP, port: 8080} + egress: [] +--- +# cert-manager HTTP-01 solver pods run in this namespace on 8089 during +# issuance and renewal; Traefik must reach them or the challenge gets 502. +apiVersion: networking.k8s.io/v1 +kind: NetworkPolicy +metadata: + name: bao-notice-acme-solver + namespace: bao-notice +spec: + podSelector: + matchLabels: + acme.cert-manager.io/http01-solver: "true" + policyTypes: [Ingress] + ingress: + - from: + - namespaceSelector: + matchLabels: + kubernetes.io/metadata.name: kube-system + podSelector: + matchLabels: + app.kubernetes.io/name: traefik + ports: + - {protocol: TCP, port: 8089} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: bao-notice + namespace: bao-notice + labels: + app.kubernetes.io/name: bao-notice +spec: + replicas: 1 + revisionHistoryLimit: 2 + selector: + matchLabels: + app.kubernetes.io/name: bao-notice + template: + metadata: + labels: + app.kubernetes.io/name: bao-notice + spec: + automountServiceAccountToken: false + enableServiceLinks: false + securityContext: + runAsNonRoot: true + runAsUser: 101 + runAsGroup: 101 + seccompProfile: + type: RuntimeDefault + containers: + - name: nginx + image: nginxinc/nginx-unprivileged@sha256:65e3e85dbaed8ba248841d9d58a899b6197106c23cb0ff1a132b7bfe0547e4c0 + ports: + - {name: http, containerPort: 8080} + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] + resources: + requests: {cpu: 5m, memory: 16Mi} + limits: {cpu: 100m, memory: 64Mi} + readinessProbe: + httpGet: {path: /healthz, port: http} + periodSeconds: 20 + livenessProbe: + httpGet: {path: /healthz, port: http} + periodSeconds: 60 + volumeMounts: + - {name: conf, mountPath: /etc/nginx/conf.d, readOnly: true} + - {name: html, mountPath: /usr/share/nginx/html, readOnly: true} + - {name: tmp, mountPath: /tmp} + volumes: + - name: conf + configMap: + name: bao-notice-conf + items: [{key: default.conf, path: default.conf}] + - name: html + configMap: + name: bao-notice-html + items: [{key: index.html, path: index.html}] + - name: tmp + emptyDir: {sizeLimit: 16Mi} +--- +apiVersion: v1 +kind: Service +metadata: + name: bao-notice + namespace: bao-notice +spec: + selector: + app.kubernetes.io/name: bao-notice + ports: + - {name: http, port: 80, targetPort: http} +--- +apiVersion: traefik.io/v1alpha1 +kind: Middleware +metadata: + name: redirect-https + namespace: bao-notice +spec: + redirectScheme: + scheme: https + permanent: true +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: bao-notice + namespace: bao-notice + annotations: + cert-manager.io/cluster-issuer: letsencrypt-prod + traefik.ingress.kubernetes.io/router.entrypoints: websecure + traefik.ingress.kubernetes.io/router.tls: "true" +spec: + ingressClassName: traefik + tls: + - hosts: [bao.coulomb.social] + secretName: bao-notice-tls + rules: + - host: bao.coulomb.social + http: + paths: + - path: / + pathType: Prefix + backend: + service: {name: bao-notice, port: {number: 80}} +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: bao-notice-http-redirect + namespace: bao-notice + annotations: + traefik.ingress.kubernetes.io/router.entrypoints: web + traefik.ingress.kubernetes.io/router.middlewares: bao-notice-redirect-https@kubernetescrd + traefik.ingress.kubernetes.io/router.priority: "1" +spec: + ingressClassName: traefik + rules: + - host: bao.coulomb.social + http: + paths: + - path: / + pathType: Prefix + backend: + service: {name: bao-notice, port: {number: 80}} diff --git a/argocd/platform-addons/custodian-agents/clusterrole.yaml b/argocd/platform-addons/custodian-agents/clusterrole.yaml deleted file mode 100644 index 00a62e8..0000000 --- a/argocd/platform-addons/custodian-agents/clusterrole.yaml +++ /dev/null @@ -1,53 +0,0 @@ -# Curated read-only observer (founder decision 2026-10-20, CUST-WP-0073-T02). -# Verbs: get/list/watch only. Explicitly excluded: secrets, configmaps, -# pods/log, pods/exec, pods/attach, pods/portforward, serviceaccounts/token, -# nodes/proxy, any write verb, any wildcard. Privileged actions go through the -# separate gated executor; nothing here grants writes. -# Enforced offline by tests/test_custodian_agent_rbac.py. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: custodian-supervised-observer - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: custodian-agent-identity -rules: - - apiGroups: [""] - resources: - - namespaces - - nodes - - pods - - services - - endpoints - - events - - persistentvolumeclaims - verbs: ["get", "list", "watch"] - - apiGroups: ["apps"] - resources: - - deployments - - statefulsets - - daemonsets - - replicasets - verbs: ["get", "list", "watch"] - - apiGroups: ["discovery.k8s.io"] - resources: ["endpointslices"] - verbs: ["get", "list", "watch"] - - apiGroups: ["networking.k8s.io"] - resources: ["ingresses"] - verbs: ["get", "list", "watch"] - - apiGroups: ["events.k8s.io"] - resources: ["events"] - verbs: ["get", "list", "watch"] - - apiGroups: ["argoproj.io"] - resources: - - applications - - appprojects - verbs: ["get", "list", "watch"] - - apiGroups: ["postgresql.cnpg.io"] - resources: - - clusters - - backups - - scheduledbackups - verbs: ["get", "list", "watch"] - - nonResourceURLs: ["/version", "/healthz", "/livez", "/readyz"] - verbs: ["get"] diff --git a/argocd/platform-addons/custodian-agents/clusterrolebinding.yaml b/argocd/platform-addons/custodian-agents/clusterrolebinding.yaml deleted file mode 100644 index 4cd74a9..0000000 --- a/argocd/platform-addons/custodian-agents/clusterrolebinding.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: custodian-agent-supervised-observer - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: custodian-agent-identity -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: custodian-supervised-observer -subjects: - - kind: ServiceAccount - name: custodian-agent - namespace: custodian-agents diff --git a/argocd/platform-addons/custodian-agents/kustomization.yaml b/argocd/platform-addons/custodian-agents/kustomization.yaml deleted file mode 100644 index 647c117..0000000 --- a/argocd/platform-addons/custodian-agents/kustomization.yaml +++ /dev/null @@ -1,14 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -# RPF-WP-0062 / CUST-WP-0073-T02. Read only by -# argocd/railiance01/applications/custodian-agents.application.yaml. -# Read-only observer identity for supervised custodian agents. No Secret, no -# static token, no write verb. Credentials are short-lived TokenRequest tokens -# issued by the supervisor (see workplans/RPF-WP-0062-custodian-agent-observer-identity.md). - -resources: - - namespace.yaml - - serviceaccount.yaml - - clusterrole.yaml - - clusterrolebinding.yaml diff --git a/argocd/platform-addons/custodian-agents/namespace.yaml b/argocd/platform-addons/custodian-agents/namespace.yaml deleted file mode 100644 index ee64b53..0000000 --- a/argocd/platform-addons/custodian-agents/namespace.yaml +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: custodian-agents - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: custodian-agent-identity diff --git a/argocd/platform-addons/custodian-agents/serviceaccount.yaml b/argocd/platform-addons/custodian-agents/serviceaccount.yaml deleted file mode 100644 index 648d6dd..0000000 --- a/argocd/platform-addons/custodian-agents/serviceaccount.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# No token Secret is declared for this ServiceAccount, and none may be added. -# Tokens are issued per session by the supervisor via TokenRequest. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: custodian-agent - namespace: custodian-agents - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: custodian-agent-identity -automountServiceAccountToken: false diff --git a/argocd/platform-addons/custodian-allocation-review/README.md b/argocd/platform-addons/custodian-allocation-review/README.md deleted file mode 100644 index 15ff789..0000000 --- a/argocd/platform-addons/custodian-allocation-review/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# custodian-allocation-review (CUST-WP-0071-T05) — prepared, not deployed - -Weekly workload-demand and allocation assessment for railiance01, selected by -the founder on 2026-10-21 (the-custodian -`docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md`). Not -referenced by any Application. The draft Application is in -`argocd/railiance01/drafts/`. The CronJob is `suspend: true`. - -| Item | Value | -|---|---| -| Executor owner | railiance-platform (platform-addons, ArgoCD `railiance-platform-addons` project) | -| Assessment/report owner | the-custodian (CUST-WP-0071-T05); code in `the-custodian/scripts/weekly_allocation_incluster.py` | -| Schedule | `0 8 * * 1`, `timeZone: Europe/Berlin` (Monday 08:00), `concurrencyPolicy: Forbid`, `startingDeadlineSeconds: 21600` | -| Telemetry window | 7d ending Monday 06:00 UTC of the run's ISO week (DST-independent, so consecutive weeks are comparable) | -| Image | `python:3.12-slim` pinned by digest (same as eso-token-renewer); code and data come from a ConfigMap | -| Identity | SA `custodian-assessment/custodian-allocation-review`, ClusterRole `custodian-allocation-review-reader` | -| RBAC | get/list on core `nodes`, `pods`, `persistentvolumeclaims`, `persistentvolumes`; `storage.k8s.io/storageclasses`; `metrics.k8s.io/nodes,pods`. No watch, no writes, no secrets, configmaps, logs, exec or proxy | -| Prometheus | `http://telemetry-prometheus.telemetry.svc:9090`, plain HTTP, no credential | -| Delivery | One State Hub message per newly retained week, `custodian-allocation-review` → `the-custodian`, sent to `STATE_HUB_URL=http://state-hub.state-hub.svc:8000`. The message id is recorded in `receipts.jsonl` | -| Network | `networkpolicy.yaml`: the namespace denies all ingress and egress by default. Egress is allowed only to DNS (kube-system/kube-dns 53 UDP/TCP), kube-apiserver (10.43.0.1:443 and k3s endpoint 92.205.62.239:6443), Prometheus (telemetry, 9090) and the State Hub API (state-hub `app=state-hub`, 8000) | -| Storage | PVC `custodian-allocation-review-data` (local-path, 1Gi, ArgoCD `Prune=false,Delete=false`): `reports/-W.json`, `runs///` inputs, append-only `receipts.jsonl` | -| Resources | requests 20m/96Mi, limits 500m/256Mi, `activeDeadlineSeconds: 900`, `backoffLimit: 2` | - -## Behaviour - -- Writes only to the PVC and the one hub message per retained week. It never - resizes anything or creates tasks. The next Custodian session reviews the - message and records owner-repo tasks (ADR-001). -- Idempotency: an existing report for the week means the run does nothing and - succeeds. A failed attempt writes a `failed` receipt and no report, so the Job - retry or a manual Job collects again. -- Missed weeks: each retained receipt lists `missed_weeks` since the previous - report. Comparison across a gap stays unknown. -- Delivery is idempotent. A week that already has a message id is never - reposted. A failed post exits 1, and the Job retry posts for the - already-retained week. -- Missed and failed runs are caught by the-custodian's daily security flow - (`scripts/weekly_allocation_check.py`). It flags a failed latest receipt, or - a missing report for the due ISO week after Monday 12:00 Europe/Berlin. It - stays inactive until `CUSTODIAN_ALLOCATION_RECEIPTS=hub` is set. -- Workload rows remain `investigate`. The queries are namespace-level because - kube-state-metrics does not export `kube_pod_labels`. - -## Vendored sources - -`src/` is generated by `scripts/vendor_custodian_allocation_review.py` from -clean owner commits, and `vendor.lock.json` records each commit and sha256. -`--check` verifies them, and `tests/test_custodian_allocation_review.py` -enforces the check offline. To change the job code, commit in the owner repo -and re-vendor. Never edit `src/` by hand. - -## Activation (founder go-ahead required; after CUST-WP-0071-T04 completes) - -1. Review this directory and the vendored sources. Re-vendor if the owner - repositories have moved on, and keep `--check` passing. -2. rapp-telemetry: - - Merge `helm/values-railiance01-retention.pending.yaml` (10d / 10GB) into - `helm/values-railiance01.yaml` and run the attended helm upgrade. - - Apply `manifests/custodian-assessment-prometheus-ingress.pending.yaml`. - Without it, `telemetry-isolation` blocks port 9090 and every query - records a transport error. -3. Widen the ArgoCD project. This commit already adds `PersistentVolumeClaim` - and `networking.k8s.io/NetworkPolicy` to the `namespaceResourceWhitelist` in - `argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml`. - The bootstrap is applied by hand, so apply that AppProject during the - change window. -4. Set `targetRevision` in the draft Application to the reviewed commit. Move - the draft to `argocd/railiance01/applications/`, run - `make argocd-pin-drift`, then hand-sync `railiance-apps-root` and then - `custodian-allocation-review`. -5. Acceptance run, still suspended: - `kubectl -n custodian-assessment create job --from=cronjob/custodian-allocation-review accept-1`. - Check that `receipts.jsonl` shows `retained`, `failed_queries: []`, - `delivery: posted` with a `message_id`, and that the hub message reached - `the-custodian`. Run it on a Monday, because it claims that ISO week's - report slot. -6. Activate the daily missed/failed-run check: add - `Environment=CUSTODIAN_ALLOCATION_RECEIPTS=hub` to the-custodian - `infra/security-flow/custodian-security-flow.service`. Exercise it once by - leaving a week without a report, or by pointing `--allocation-receipts` at a - failed receipt. -7. Unsuspend through a reviewed commit (`suspend: false`) and a new - `targetRevision`, not a live patch. - -## Rollback - -- Stop: commit `suspend: true` and sync. In an emergency, run - `kubectl -n custodian-assessment patch cronjob custodian-allocation-review -p '{"spec":{"suspend":true}}'`, - then commit the same change. -- Remove: delete the Application (it has no finalizer, so the resources stay), - then delete the CronJob, RBAC, NetworkPolicies and ConfigMap. Keep the PVC unless the history - is explicitly abandoned, because local-path deletes the data with the claim. - -## Still open before T05 can be accepted - -These items are not provided here: an explicit owner acknowledgment beyond -the hub message being read, how owner reviews reach `/data/reviews.json`, and -workload-level metrics. Reports stay at namespace level; the founder declined a -pod-label allowlist for now. diff --git a/argocd/platform-addons/custodian-allocation-review/cronjob.yaml b/argocd/platform-addons/custodian-allocation-review/cronjob.yaml deleted file mode 100644 index 24d6107..0000000 --- a/argocd/platform-addons/custodian-allocation-review/cronjob.yaml +++ /dev/null @@ -1,82 +0,0 @@ -# CUST-WP-0071-T05: weekly workload-demand and allocation assessment. -# Founder decision 2026-10-21 (the-custodian -# docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md): in-cluster -# CronJob, Monday 08:00 Europe/Berlin, scoped read-only ServiceAccount, -# persisted weekly aggregates. suspend: true until review and T04 completion; -# unsuspend only by a reviewed commit (see README.md). -apiVersion: batch/v1 -kind: CronJob -metadata: - name: custodian-allocation-review - namespace: custodian-assessment - labels: - app.kubernetes.io/name: custodian-allocation-review - app.kubernetes.io/part-of: railiance-platform -spec: - schedule: '0 8 * * 1' - timeZone: Europe/Berlin - suspend: true - concurrencyPolicy: Forbid - # Late starts lose coverage at the 7-day retention edge; after 6h the week - # is left to the next run, whose receipt lists it under missed_weeks. - startingDeadlineSeconds: 21600 - successfulJobsHistoryLimit: 4 - failedJobsHistoryLimit: 4 - jobTemplate: - spec: - activeDeadlineSeconds: 900 - backoffLimit: 2 - ttlSecondsAfterFinished: 1209600 - template: - metadata: - labels: - app.kubernetes.io/name: custodian-allocation-review - spec: - serviceAccountName: custodian-allocation-review - # Token needed for the read-only API calls; the SA itself does not - # automount, so only this pod template receives it. - automountServiceAccountToken: true - restartPolicy: Never - securityContext: - runAsNonRoot: true - runAsUser: 65534 - runAsGroup: 65534 - fsGroup: 65534 - seccompProfile: - type: RuntimeDefault - containers: - - name: assess - image: python:3.12-slim@sha256:d764629ce0ddd8c71fd371e9901efb324a95789d2315a47db7e4d27e78f1b0e9 - command: [python3, /app/weekly_allocation_incluster.py] - env: - - {name: PYTHONDONTWRITEBYTECODE, value: '1'} - - {name: ASSESSMENT_DATA_DIR, value: /data} - - {name: ASSESSMENT_SOURCE_DIR, value: /app} - - {name: ASSESSMENT_OBSERVER, value: /app/observe_cluster_resources.py} - - {name: ASSESSMENT_QUERIES, value: /app/weekly-allocation-queries.json} - - {name: ASSESSMENT_OWNERSHIP, value: /app/namespace-ownership.json} - - {name: ASSESSMENT_INVENTORY, value: /app/reef-railiance-k3s.json} - - {name: ASSESSMENT_REVIEWS, value: /data/reviews.json} - - {name: ASSESSMENT_EXECUTOR, value: railiance-platform/custodian-assessment/cronjob/custodian-allocation-review} - - {name: PROMETHEUS_URL, value: 'http://telemetry-prometheus.telemetry.svc:9090'} - # One message per newly retained week (custodian-allocation-review -> the-custodian). - - {name: STATE_HUB_URL, value: 'http://state-hub.state-hub.svc:8000'} - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: - drop: [ALL] - resources: - requests: {cpu: 20m, memory: 96Mi} - limits: {cpu: 500m, memory: 256Mi} - volumeMounts: - - {name: source, mountPath: /app, readOnly: true} - - {name: data, mountPath: /data} - - {name: tmp, mountPath: /tmp} - volumes: - - name: source - configMap: {name: custodian-allocation-review-source, defaultMode: 0444} - - name: data - persistentVolumeClaim: {claimName: custodian-allocation-review-data} - - name: tmp - emptyDir: {sizeLimit: 16Mi} diff --git a/argocd/platform-addons/custodian-allocation-review/kustomization.yaml b/argocd/platform-addons/custodian-allocation-review/kustomization.yaml deleted file mode 100644 index 5254e66..0000000 --- a/argocd/platform-addons/custodian-allocation-review/kustomization.yaml +++ /dev/null @@ -1,28 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -# CUST-WP-0071-T05. NOT referenced by any Application yet: the draft lives in -# argocd/railiance01/drafts/custodian-allocation-review.application.yaml. -# src/ is vendored from owner repos (the-custodian, railiance-cluster, -# resource-control); vendor.lock.json pins the source commits and sha256. -# Refresh with tools/vendor_custodian_allocation_review.py, never by hand. - -namespace: custodian-assessment - -resources: - - namespace.yaml - - rbac.yaml - - pvc.yaml - - networkpolicy.yaml - - cronjob.yaml - -configMapGenerator: - - name: custodian-allocation-review-source - files: - - src/weekly_allocation_incluster.py - - src/weekly_allocation_report.py - - src/reconcile_railiance_allocation.py - - src/observe_cluster_resources.py - - src/weekly-allocation-queries.json - - src/namespace-ownership.json - - src/reef-railiance-k3s.json diff --git a/argocd/platform-addons/custodian-allocation-review/namespace.yaml b/argocd/platform-addons/custodian-allocation-review/namespace.yaml deleted file mode 100644 index 18ed7cd..0000000 --- a/argocd/platform-addons/custodian-allocation-review/namespace.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: custodian-assessment - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: custodian-allocation-review - pod-security.kubernetes.io/enforce: restricted - pod-security.kubernetes.io/enforce-version: latest diff --git a/argocd/platform-addons/custodian-allocation-review/networkpolicy.yaml b/argocd/platform-addons/custodian-allocation-review/networkpolicy.yaml deleted file mode 100644 index 89c5c86..0000000 --- a/argocd/platform-addons/custodian-allocation-review/networkpolicy.yaml +++ /dev/null @@ -1,70 +0,0 @@ -# CUST-WP-0071-T05 (founder decision 2026-10-21): default-deny egress and no -# ingress for the custodian-assessment namespace. The job may reach only: -# DNS kube-system/kube-dns (CoreDNS) 53 UDP/TCP -# kube-apiserver 10.43.0.1:443 (the `kubernetes` Service) and its k3s -# endpoint 92.205.62.239:6443 (same pair as rapp-telemetry's -# telemetry-isolation, since k3s may match either side of DNAT) -# Prometheus telemetry/prometheus pods 9090 -# State Hub API state-hub/state-hub pods 8000 (the Service listens on 8000) -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: custodian-assessment-default-deny - namespace: custodian-assessment - labels: - app.kubernetes.io/name: custodian-allocation-review - app.kubernetes.io/part-of: railiance-platform -spec: - podSelector: {} - policyTypes: [Ingress, Egress] ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: custodian-allocation-review-egress - namespace: custodian-assessment - labels: - app.kubernetes.io/name: custodian-allocation-review - app.kubernetes.io/part-of: railiance-platform -spec: - podSelector: - matchLabels: - app.kubernetes.io/name: custodian-allocation-review - policyTypes: [Egress] - egress: - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: kube-system - podSelector: - matchLabels: - k8s-app: kube-dns - ports: - - {protocol: UDP, port: 53} - - {protocol: TCP, port: 53} - - to: - - ipBlock: {cidr: 10.43.0.1/32} - ports: - - {protocol: TCP, port: 443} - - to: - - ipBlock: {cidr: 92.205.62.239/32} - ports: - - {protocol: TCP, port: 6443} - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: telemetry - podSelector: - matchLabels: - app.kubernetes.io/name: prometheus - ports: - - {protocol: TCP, port: 9090} - - to: - - namespaceSelector: - matchLabels: - kubernetes.io/metadata.name: state-hub - podSelector: - matchLabels: - app: state-hub - ports: - - {protocol: TCP, port: 8000} diff --git a/argocd/platform-addons/custodian-allocation-review/pvc.yaml b/argocd/platform-addons/custodian-allocation-review/pvc.yaml deleted file mode 100644 index da2e117..0000000 --- a/argocd/platform-addons/custodian-allocation-review/pvc.yaml +++ /dev/null @@ -1,21 +0,0 @@ -# Weekly reports, per-attempt inputs and receipts.jsonl outlive the 7-day -# Prometheus retention here. local-path reclaimPolicy is Delete, so ArgoCD must -# never prune or delete this claim: removing it destroys the history. -# Requires PersistentVolumeClaim in the railiance-platform-addons AppProject -# namespaceResourceWhitelist before the Application can sync (activation step). -apiVersion: v1 -kind: PersistentVolumeClaim -metadata: - name: custodian-allocation-review-data - namespace: custodian-assessment - labels: - app.kubernetes.io/name: custodian-allocation-review - app.kubernetes.io/part-of: railiance-platform - annotations: - argocd.argoproj.io/sync-options: Prune=false,Delete=false -spec: - accessModes: [ReadWriteOnce] - storageClassName: local-path - resources: - requests: - storage: 1Gi diff --git a/argocd/platform-addons/custodian-allocation-review/rbac.yaml b/argocd/platform-addons/custodian-allocation-review/rbac.yaml deleted file mode 100644 index 161062e..0000000 --- a/argocd/platform-addons/custodian-allocation-review/rbac.yaml +++ /dev/null @@ -1,49 +0,0 @@ -# CUST-WP-0071-T05 (founder decision 2026-10-21): read-only, scoped to exactly -# the reads made by weekly_allocation_incluster.py (see its API_PATHS and -# METRICS_PATHS). Verbs get/list only; no watch, no write verb, no wildcard, -# no secrets, configmaps, logs, exec, proxy or token subresources. Prometheus -# access is network-level (rapp-telemetry NetworkPolicy), not RBAC. -# Enforced offline by tests/test_custodian_allocation_review.py. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: custodian-allocation-review - namespace: custodian-assessment - labels: - app.kubernetes.io/name: custodian-allocation-review - app.kubernetes.io/part-of: railiance-platform -automountServiceAccountToken: false ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: custodian-allocation-review-reader - labels: - app.kubernetes.io/name: custodian-allocation-review - app.kubernetes.io/part-of: railiance-platform -rules: - - apiGroups: [""] - resources: [nodes, pods, persistentvolumeclaims, persistentvolumes] - verbs: [get, list] - - apiGroups: [storage.k8s.io] - resources: [storageclasses] - verbs: [get, list] - - apiGroups: [metrics.k8s.io] - resources: [nodes, pods] - verbs: [get, list] ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: custodian-allocation-review-reader - labels: - app.kubernetes.io/name: custodian-allocation-review - app.kubernetes.io/part-of: railiance-platform -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: custodian-allocation-review-reader -subjects: - - kind: ServiceAccount - name: custodian-allocation-review - namespace: custodian-assessment diff --git a/argocd/platform-addons/custodian-allocation-review/src/namespace-ownership.json b/argocd/platform-addons/custodian-allocation-review/src/namespace-ownership.json deleted file mode 100644 index a1b4967..0000000 --- a/argocd/platform-addons/custodian-allocation-review/src/namespace-ownership.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "host": { - "cluster_resource_id": "resource:railiance:reef-railiance:k3s", - "owner": "railiance-cluster", - "resource_id": "resource:hosteurope:railiance01", - "same_cpus_as_cluster": true - }, - "namespaces": { - "activity-core": { - "owner": "activity-core", - "service": "activity-core", - "tenant": "unknown" - }, - "approval-engine": { - "owner": "approval-engine", - "service": "approval-engine", - "tenant": "unknown" - }, - "argocd": { - "owner": "railiance-enablement", - "service": "argocd", - "tenant": "unknown" - }, - "audit-core": { - "owner": "audit-core", - "service": "audit-core", - "tenant": "unknown" - }, - "bao-notice": { - "owner": "railiance-platform", - "service": "bao-notice", - "tenant": "unknown" - }, - "canned-prompts": { - "owner": "canned-prompts", - "service": "canned-prompts", - "tenant": "unknown" - }, - "cert-manager": { - "owner": "railiance-cluster", - "service": "cert-manager", - "tenant": "unknown" - }, - "cnpg-system": { - "owner": "rapp-postgres", - "service": "cloudnative-pg", - "tenant": "unknown" - }, - "core-hub": { - "owner": "core-hub", - "service": "core-hub", - "tenant": "unknown" - }, - "coulomb": { - "owner": "unknown", - "service": "ihp-railiance-probe", - "tenant": "unknown" - }, - "coulomb-social": { - "owner": "coulomb-social", - "service": "coulomb-social", - "tenant": "unknown" - }, - "databases": { - "owner": "rapp-postgres", - "service": "apps-pg", - "tenant": "unknown" - }, - "email-connect": { - "owner": "email-connect", - "service": "email-connect", - "tenant": "unknown" - }, - "external-secrets": { - "owner": "railiance-platform", - "service": "external-secrets", - "tenant": "unknown" - }, - "flex-auth": { - "owner": "flex-auth", - "service": "flex-auth", - "tenant": "unknown" - }, - "forgejo": { - "owner": "railiance-forge", - "service": "forgejo", - "tenant": "unknown" - }, - "informed-decision": { - "owner": "informed-decision", - "service": "informed-decision", - "tenant": "unknown" - }, - "issue-core": { - "owner": "issue-core", - "service": "issue-core", - "tenant": "unknown" - }, - "knative-serving": { - "owner": "rail-knative", - "service": "knative-serving", - "tenant": "unknown" - }, - "kourier-system": { - "owner": "rail-knative", - "service": "kourier", - "tenant": "unknown" - }, - "kube-system": { - "owner": "railiance-cluster", - "service": "k3s-control-plane", - "tenant": "unknown" - }, - "mas": { - "owner": "mail-address-semantics", - "service": "mas-live-test", - "tenant": "unknown" - }, - "mfa": { - "owner": "net-kingdom", - "service": "lldap-mfa", - "tenant": "unknown" - }, - "openbao": { - "owner": "railiance-platform", - "service": "openbao", - "tenant": "unknown" - }, - "platform-pg-drill": { - "owner": "rapp-postgres", - "service": "platform-pg-drill", - "tenant": "unknown" - }, - "playwright": { - "owner": "railiance-platform", - "service": "rapp-playwright", - "tenant": "unknown" - }, - "policy-nexus": { - "owner": "policy-nexus", - "service": "policy-nexus", - "tenant": "unknown" - }, - "rapp-qonto": { - "owner": "rapp-qonto", - "service": "qonto", - "tenant": "unknown" - }, - "rapp-qonto-egress": { - "owner": "rapp-qonto", - "service": "qonto-egress", - "tenant": "tenant:friendly:binky" - }, - "rein-aharness": { - "owner": "rein-aharness", - "service": "rein-aharness", - "tenant": "unknown" - }, - "reuse": { - "owner": "reuse-surface", - "service": "reuse-surface", - "tenant": "unknown" - }, - "sbom-nexus": { - "owner": "sbom-nexus", - "service": "sbom-nexus", - "tenant": "unknown" - }, - "sso": { - "owner": "net-kingdom", - "service": "keycape-authelia", - "tenant": "unknown" - }, - "state-hub": { - "owner": "state-hub", - "service": "state-hub", - "tenant": "unknown" - }, - "target-revenue": { - "owner": "target-revenue", - "service": "target-revenue", - "tenant": "unknown" - }, - "telemetry": { - "owner": "rapp-telemetry", - "service": "prometheus-grafana", - "tenant": "unknown" - }, - "tenant-engine": { - "owner": "tenant-engine", - "service": "tenant-engine", - "tenant": "unknown" - }, - "user-engine": { - "owner": "user-engine", - "service": "user-engine", - "tenant": "unknown" - }, - "vergabe-demo-company": { - "owner": "railiance-apps", - "service": "vergabe-teilnahme", - "tenant": "unknown" - } - } -} diff --git a/argocd/platform-addons/custodian-allocation-review/src/observe_cluster_resources.py b/argocd/platform-addons/custodian-allocation-review/src/observe_cluster_resources.py deleted file mode 100644 index 08e5a33..0000000 --- a/argocd/platform-addons/custodian-allocation-review/src/observe_cluster_resources.py +++ /dev/null @@ -1,334 +0,0 @@ -#!/usr/bin/env python3 -"""Capture non-secret cluster capacity, utilization, and allocation evidence.""" - -from __future__ import annotations - -import argparse -import json -import re -import subprocess -import sys -from datetime import UTC, datetime -from pathlib import Path -from typing import Any - - -CPU_FACTORS = {"n": 0.000001, "u": 0.001, "m": 1.0, "": 1000.0} -BYTE_FACTORS = { - "": 1, - "Ki": 1024, - "Mi": 1024**2, - "Gi": 1024**3, - "Ti": 1024**4, - "K": 1000, - "M": 1000**2, - "G": 1000**3, - "T": 1000**4, -} - - -def cpu_millicores(value: str | None) -> int: - match = re.fullmatch(r"([0-9]+(?:\.[0-9]+)?)(n|u|m)?", str(value or "0")) - if not match: - raise ValueError(f"unsupported CPU quantity: {value}") - return round(float(match.group(1)) * CPU_FACTORS[match.group(2) or ""]) - - -def bytes_value(value: str | None) -> int: - match = re.fullmatch(r"([0-9]+(?:\.[0-9]+)?)(Ki|Mi|Gi|Ti|K|M|G|T)?", str(value or "0")) - if not match: - raise ValueError(f"unsupported byte quantity: {value}") - return round(float(match.group(1)) * BYTE_FACTORS[match.group(2) or ""]) - - -def workload_name(pod: dict[str, Any]) -> str: - metadata = pod.get("metadata") or {} - labels = metadata.get("labels") or {} - for key in ("app.kubernetes.io/part-of", "app.kubernetes.io/name", "app"): - if labels.get(key): - return str(labels[key]) - owners = metadata.get("ownerReferences") or [] - if owners and owners[0].get("name"): - return str(owners[0]["name"]) - return re.sub(r"-[a-f0-9]{6,}(?:-[a-z0-9]{5})?$", "", str(metadata.get("name", "unknown"))) - - -def pod_effective_requests(pod: dict[str, Any]) -> tuple[int, int]: - """Pod request is max(sum(containers), max(init containers)).""" - spec = pod.get("spec") or {} - containers = spec.get("containers") or [] - inits = spec.get("initContainers") or [] - - def req(container: dict[str, Any], key: str) -> str | None: - return ((container.get("resources") or {}).get("requests") or {}).get(key) - - cpu = sum(cpu_millicores(req(c, "cpu")) for c in containers) - mem = sum(bytes_value(req(c, "memory")) for c in containers) - init_cpu = max((cpu_millicores(req(c, "cpu")) for c in inits), default=0) - init_mem = max((bytes_value(req(c, "memory")) for c in inits), default=0) - return max(cpu, init_cpu), max(mem, init_mem) - - -def parse_top(lines: str | None, *, pods: bool) -> dict[tuple[str, str] | str, dict[str, int]]: - result: dict[tuple[str, str] | str, dict[str, int]] = {} - for line in (lines or "").splitlines(): - fields = line.split() - if pods and len(fields) >= 4: - result[(fields[0], fields[1])] = { - "cpu_millicores": cpu_millicores(fields[2]), - "memory_bytes": bytes_value(fields[3]), - } - elif not pods and len(fields) >= 4: - result[fields[0]] = { - "cpu_millicores": cpu_millicores(fields[1]), - # kubectl top nodes: NAME CPU CPU% MEMORY MEMORY% - "memory_bytes": bytes_value(fields[3]), - } - return result - - -def build_observation(snapshots: dict[str, Any], captured_at: str) -> dict[str, Any]: - gaps: list[str] = [] - node_top = parse_top(snapshots.get("node_top", ""), pods=False) - pod_top = parse_top(snapshots.get("pod_top", ""), pods=True) - if snapshots.get("node_top") is None: - gaps.append("node metrics unavailable") - if snapshots.get("pod_top") is None: - gaps.append("pod metrics unavailable") - - nodes = [] - alloc_cpu = alloc_memory = alloc_pods = 0 - for node in snapshots["nodes"].get("items") or []: - meta = node.get("metadata") or {} - status = node.get("status") or {} - alloc = status.get("allocatable") or {} - labels = meta.get("labels") or {} - name = str(meta.get("name")) - topology = { - "region": labels.get("topology.kubernetes.io/region"), - "zone": labels.get("topology.kubernetes.io/zone"), - "hostname": labels.get("kubernetes.io/hostname"), - "provider_id": (node.get("spec") or {}).get("providerID"), - } - if not topology["region"] or not topology["zone"]: - gaps.append(f"node {name} lacks independent region/zone labels") - row = { - "name": name, - "ready": any(c.get("type") == "Ready" and c.get("status") == "True" for c in status.get("conditions") or []), - "allocatable": { - "cpu_millicores": cpu_millicores(alloc.get("cpu")), - "memory_bytes": bytes_value(alloc.get("memory")), - "ephemeral_storage_bytes": bytes_value(alloc.get("ephemeral-storage")), - "pods": int(alloc.get("pods") or 0), - }, - "topology": topology, - "observed": node_top.get(name), - } - nodes.append(row) - alloc_cpu += row["allocatable"]["cpu_millicores"] - alloc_memory += row["allocatable"]["memory_bytes"] - alloc_pods += row["allocatable"]["pods"] - - workloads: dict[tuple[str, str], dict[str, Any]] = {} - pending_unscheduled: list[dict[str, Any]] = [] - node_allocated: dict[str, dict[str, int]] = { - node["name"]: {"cpu_millicores": 0, "memory_bytes": 0} for node in nodes - } - scheduled_cpu = scheduled_memory = 0 - for pod in snapshots["pods"].get("items") or []: - meta = pod.get("metadata") or {} - spec = pod.get("spec") or {} - status = pod.get("status") or {} - phase = str(status.get("phase") or "") - if phase in {"Succeeded", "Failed"}: - continue - namespace = str(meta.get("namespace", "default")) - pod_name = str(meta.get("name")) - cpu, mem = pod_effective_requests(pod) - node_name = spec.get("nodeName") - if phase == "Pending" and not node_name: - pending_unscheduled.append({ - "namespace": namespace, - "name": pod_name, - "cpu_millicores": cpu, - "memory_bytes": mem, - }) - continue - if node_name in node_allocated: - node_allocated[node_name]["cpu_millicores"] += cpu - node_allocated[node_name]["memory_bytes"] += mem - scheduled_cpu += cpu - scheduled_memory += mem - name = workload_name(pod) - key = (namespace, name) - row = workloads.setdefault(key, { - "namespace": namespace, - "workload": name, - "pods": 0, - "requests": {"cpu_millicores": 0, "memory_bytes": 0}, - "limits": {"cpu_millicores": 0, "memory_bytes": 0}, - "observed": {"cpu_millicores": 0, "memory_bytes": 0} if snapshots.get("pod_top") is not None else None, - }) - row["pods"] += 1 - row["requests"]["cpu_millicores"] += cpu - row["requests"]["memory_bytes"] += mem - for container in spec.get("containers") or []: - limits = (container.get("resources") or {}).get("limits") or {} - row["limits"]["cpu_millicores"] += cpu_millicores(limits.get("cpu")) - row["limits"]["memory_bytes"] += bytes_value(limits.get("memory")) - metric = pod_top.get((namespace, pod_name)) - if metric and row["observed"] is not None: - row["observed"]["cpu_millicores"] += metric["cpu_millicores"] - row["observed"]["memory_bytes"] += metric["memory_bytes"] - - storage_classes = [] - for item in snapshots["storageclasses"].get("items") or []: - storage_classes.append({ - "name": (item.get("metadata") or {}).get("name"), - "provisioner": item.get("provisioner"), - "reclaim_policy": item.get("reclaimPolicy"), - "volume_binding_mode": item.get("volumeBindingMode"), - "default": (item.get("metadata") or {}).get("annotations", {}).get("storageclass.kubernetes.io/is-default-class") == "true", - }) - pv_capacity = sum(bytes_value(((item.get("spec") or {}).get("capacity") or {}).get("storage")) for item in snapshots["pvs"].get("items") or []) - pvc_rows = [] - pvc_requested = 0 - for item in snapshots["pvcs"].get("items") or []: - meta = item.get("metadata") or {} - spec = item.get("spec") or {} - requested = bytes_value(((spec.get("resources") or {}).get("requests") or {}).get("storage")) - pvc_requested += requested - pvc_rows.append({ - "namespace": meta.get("namespace"), - "name": meta.get("name"), - "storage_class": spec.get("storageClassName"), - "requested_bytes": requested, - "phase": (item.get("status") or {}).get("phase"), - }) - - workload_rows = sorted(workloads.values(), key=lambda row: (row["namespace"], row["workload"])) - requested_cpu = scheduled_cpu - requested_memory = scheduled_memory - pending_cpu = sum(item["cpu_millicores"] for item in pending_unscheduled) - pending_memory = sum(item["memory_bytes"] for item in pending_unscheduled) - preflight_nodes = [] - for node in nodes: - allocated = node_allocated.get(node["name"], {"cpu_millicores": 0, "memory_bytes": 0}) - preflight_nodes.append({ - "name": node["name"], - "ready": node["ready"], - "unschedulable": False, - "allocatable_cpu_m": node["allocatable"]["cpu_millicores"], - "allocatable_memory_bytes": node["allocatable"]["memory_bytes"], - "allocated_cpu_m": allocated["cpu_millicores"], - "allocated_memory_bytes": allocated["memory_bytes"], - }) - return { - "schema_version": "railiance.cluster-resource-observation.v1", - "record_type": "usage_observation", - "resource_id": "resource:railiance:reef-railiance:k3s", - "source": "railiance-cluster", - "workplan_id": "RCLUSTER-WP-0014", - "reef": "reef-railiance", - "captured_at": captured_at, - "capacity": { - "nodes": nodes, - "totals": {"cpu_millicores": alloc_cpu, "memory_bytes": alloc_memory, "pods": alloc_pods, "pv_bytes": pv_capacity}, - }, - "utilization": {"workloads": workload_rows}, - "storage": {"classes": storage_classes, "claims": pvc_rows}, - "failure_domain": { - "current_contract": "single Ready node on railiance01; local storage is correlated with node loss", - "independent_domains_claimed": 1 if nodes else 0, - "threephoenix_target_is_live": False, - }, - "allocation": { - "method_version": "cluster-raw-drivers-v1", - "drivers": ["cpu_requests", "memory_requests", "pvc_requested_bytes", "observed_usage"], - "denominators": {"cpu_requested_millicores": requested_cpu, "memory_requested_bytes": requested_memory, "pvc_requested_bytes": pvc_requested}, - "pending_unscheduled": { - "cpu_millicores": pending_cpu, - "memory_bytes": pending_memory, - "pods": pending_unscheduled, - }, - "residual_capacity": {"cpu_millicores": max(alloc_cpu - requested_cpu, 0), "memory_bytes": max(alloc_memory - requested_memory, 0), "pv_bytes": max(pv_capacity - pvc_requested, 0)}, - "not_a_scheduling_guarantee": True, - "selection_owner": "resource-control", - }, - "state_hub_preflight_observation": { - "schema": "state-hub.release-headroom-preflight.v1-input", - "observed_at": captured_at, - "freshness_seconds": 0, - "nodes": preflight_nodes, - "pending_unrelated": [ - { - "name": f"{item['namespace']}/{item['name']}", - "cpu_m": item["cpu_millicores"], - "memory_bytes": item["memory_bytes"], - } - for item in pending_unscheduled - ], - }, - "measurement_gaps": sorted(set(gaps)), - "provenance": { - "commands": [ - "kubectl get nodes -o json", "kubectl get pods -A -o json", - "kubectl get pvc -A -o json", "kubectl get pv -o json", - "kubectl get storageclass -o json", "kubectl top nodes --no-headers", - "kubectl top pods -A --no-headers", - ], - "secret_surfaces_read": False, - }, - } - - -def run_json(command: list[str]) -> dict[str, Any]: - return json.loads(subprocess.check_output(command, text=True)) - - -def run_optional(command: list[str]) -> str | None: - completed = subprocess.run(command, text=True, capture_output=True, check=False) - return completed.stdout if completed.returncode == 0 else None - - -def collect(kubectl: str, remote: str | None = None) -> dict[str, Any]: - base = ["ssh", "-o", "BatchMode=yes", remote, kubectl] if remote else [kubectl] - snapshots = { - "nodes": run_json(base + ["get", "nodes", "-o", "json"]), - "pods": run_json(base + ["get", "pods", "-A", "-o", "json"]), - "pvcs": run_json(base + ["get", "pvc", "-A", "-o", "json"]), - "pvs": run_json(base + ["get", "pv", "-o", "json"]), - "storageclasses": run_json(base + ["get", "storageclass", "-o", "json"]), - "node_top": run_optional(base + ["top", "nodes", "--no-headers"]), - "pod_top": run_optional(base + ["top", "pods", "-A", "--no-headers"]), - } - captured_at = datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - return build_observation(snapshots, captured_at) - - -def main() -> int: - parser = argparse.ArgumentParser() - parser.add_argument("--kubectl", default="kubectl") - parser.add_argument("--remote", help="Optional SSH host on which kubectl runs") - parser.add_argument("--output-dir", type=Path) - args = parser.parse_args() - try: - observation = collect(args.kubectl, args.remote) - except (OSError, subprocess.CalledProcessError, json.JSONDecodeError, ValueError) as exc: - print(f"cluster observation failed: {exc}", file=sys.stderr) - return 1 - rendered = json.dumps(observation, indent=2) + "\n" - if args.output_dir: - args.output_dir.mkdir(parents=True, exist_ok=True) - stamp = observation["captured_at"].replace(":", "").replace("-", "") - destination = args.output_dir / f"{stamp}.json" - destination.write_text(rendered, encoding="utf-8") - (args.output_dir / "latest.json").write_text(rendered, encoding="utf-8") - print(destination) - else: - sys.stdout.write(rendered) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/argocd/platform-addons/custodian-allocation-review/src/reconcile_railiance_allocation.py b/argocd/platform-addons/custodian-allocation-review/src/reconcile_railiance_allocation.py deleted file mode 100644 index a7ca729..0000000 --- a/argocd/platform-addons/custodian-allocation-review/src/reconcile_railiance_allocation.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -"""CUST-WP-0071-T01: join live cluster allocation with owner/service/tenant.""" -from __future__ import annotations - -import argparse -import importlib.util -import json -import sys -from datetime import UTC, datetime -from pathlib import Path -from typing import Any - -ROOT = Path(__file__).resolve().parents[1] -CLUSTER_OBS = Path.home() / "railiance-cluster" / "tools" / "observe_cluster_resources.py" -HEADROOM = Path.home() / "state-hub" / "scripts" / "release_headroom_preflight.py" -OWNERS = ROOT / "docs" / "evidence" / "namespace-ownership.yaml" - - -def load_module(path: Path, name: str): - spec = importlib.util.spec_from_file_location(name, path) - if spec is None or spec.loader is None: - raise RuntimeError(f"cannot load {path}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def join(observation: dict[str, Any], owners: dict[str, Any]) -> dict[str, Any]: - mapping = owners.get("namespaces") or {} - rows = [] - unknown = [] - zero_request = [] - for workload in observation.get("utilization", {}).get("workloads") or []: - ns = workload["namespace"] - meta = mapping.get(ns, {"owner": "unknown", "service": "unknown", "tenant": "unknown"}) - row = { - "namespace": ns, - "workload": workload["workload"], - "pods": workload["pods"], - "cpu_request_m": workload["requests"]["cpu_millicores"], - "memory_request_bytes": workload["requests"]["memory_bytes"], - "owner": meta.get("owner", "unknown"), - "service": meta.get("service", "unknown"), - "tenant": meta.get("tenant", "unknown"), - } - rows.append(row) - if ns not in mapping: - unknown.append(ns) - if workload["requests"]["cpu_millicores"] == 0: - zero_request.append(f"{ns}/{workload['workload']}") - pending = observation.get("allocation", {}).get("pending_unscheduled") or {} - alloc = observation.get("allocation") or {} - capacity = observation.get("capacity", {}).get("totals") or {} - residual = (alloc.get("residual_capacity") or {}).get("cpu_millicores", 0) - requested = (alloc.get("denominators") or {}).get("cpu_requested_millicores", 0) - return { - "schema": "custodian.allocation-reconcile.v1", - "workplan_id": "CUST-WP-0071-T01", - "captured_at": observation.get("captured_at"), - "cluster_observation_schema": observation.get("schema_version"), - "count_host_and_cluster_cpus_once": True, - "host": owners.get("host"), - "capacity_cpu_m": capacity.get("cpu_millicores"), - "scheduled_request_cpu_m": requested, - "pending_unscheduled_cpu_m": pending.get("cpu_millicores", 0), - "residual_cpu_m": residual, - "not_a_scheduling_guarantee": True, - "workloads": sorted(rows, key=lambda r: (-r["cpu_request_m"], r["namespace"], r["workload"])), - "unknown_namespaces": sorted(set(unknown)), - "zero_request_workloads": sorted(zero_request), - "pending_unscheduled": pending.get("pods") or [], - "measurement_gaps": observation.get("measurement_gaps") or [], - "state_hub_preflight_observation": observation.get("state_hub_preflight_observation"), - "signal_notes": [ - "Host resource:hosteurope:railiance01 and cluster resource:railiance:reef-railiance:k3s are the same 4 vCPU; do not add them.", - "Prometheus namespace_cpu:kube_pod_container_resource_requests:sum omitted Forgejo 100m on 2026-09-11; Kubernetes API is the scheduler-effective source.", - "node-exporter was disabled; host CPU usage is not a reservation and is not treated as spare capacity.", - "Zero-request pods are demand, not zero. Missing metrics are listed as gaps, not zeros.", - "STATE-WP-0091 preflight consumes state_hub_preflight_observation; residual millicores are not admission.", - ], - } - - -def render_markdown(report: dict[str, Any], preflight: dict[str, Any] | None) -> str: - lines = [ - f"# Railiance allocation reconcile — {report['captured_at']}", - "", - "CUST-WP-0071-T01. Scheduler-effective requests from the Kubernetes API", - "via `railiance-cluster` observe (RCLUSTER-WP-0014 / RAIL-BS-WP-0014).", - "Host and cluster are the same 4 vCPU and are counted once.", - "", - f"- Capacity: {report['capacity_cpu_m']}m", - f"- Scheduled requests: {report['scheduled_request_cpu_m']}m", - f"- Pending unscheduled: {report['pending_unscheduled_cpu_m']}m", - f"- Residual (not a guarantee): {report['residual_cpu_m']}m", - f"- Unknown namespaces: {', '.join(report['unknown_namespaces']) or 'none'}", - f"- Zero-request workloads: {len(report['zero_request_workloads'])}", - "", - "| Namespace | Workload | Owner | Tenant | CPU request (m) | Pods |", - "|---|---|---|---|---:|---:|", - ] - for row in report["workloads"]: - lines.append( - f"| {row['namespace']} | {row['workload']} | {row['owner']} | {row['tenant']} | {row['cpu_request_m']} | {row['pods']} |" - ) - lines += ["", "## STATE-WP-0091 preflight", ""] - if preflight: - lines.append(f"- ok: `{preflight.get('ok')}`") - lines.append(f"- remaining_cpu_m: {preflight.get('remaining_cpu_m')}") - for reason in preflight.get("reasons") or []: - lines.append(f"- refuse: {reason}") - for note in preflight.get("notes") or []: - lines.append(f"- note: {note}") - lines += ["", "## Signal notes", ""] - for note in report["signal_notes"]: - lines.append(f"- {note}") - lines.append("") - return "\n".join(lines) - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--observation", type=Path, help="Existing cluster observation JSON") - parser.add_argument("--kubectl", default="kubectl") - parser.add_argument("--output-dir", type=Path, default=ROOT / "docs" / "evidence") - args = parser.parse_args() - cluster = load_module(CLUSTER_OBS, "cluster_resources") - if args.observation: - observation = json.loads(args.observation.read_text()) - else: - observation = cluster.collect(args.kubectl) - import yaml # main only: join() stays stdlib-only for the in-cluster runner - - owners = yaml.safe_load(OWNERS.read_text()) - report = join(observation, owners) - preflight = None - if observation.get("state_hub_preflight_observation"): - headroom = load_module(HEADROOM, "release_headroom") - preflight = headroom.evaluate(observation["state_hub_preflight_observation"]) - report["state_hub_preflight"] = preflight - stamp = datetime.now(UTC).strftime("%Y-%m-%d") - out_json = args.output_dir / f"{stamp}-allocation-reconcile.json" - out_md = args.output_dir / f"{stamp}-allocation-reconcile.md" - args.output_dir.mkdir(parents=True, exist_ok=True) - out_json.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") - out_md.write_text(render_markdown(report, preflight), encoding="utf-8") - print(out_json) - print(out_md) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/argocd/platform-addons/custodian-allocation-review/src/reef-railiance-k3s.json b/argocd/platform-addons/custodian-allocation-review/src/reef-railiance-k3s.json deleted file mode 100644 index 86a601c..0000000 --- a/argocd/platform-addons/custodian-allocation-review/src/reef-railiance-k3s.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "schema_version": "0.3", "record_scope": "inventory", - "id": "resource:railiance:reef-railiance:k3s", - "financial_entity_id": null, - "procuring_entity_id": "entity:railiance", - "entity_gap": "no allocation driver; cost remains a Railiance residual", - "description": "Single-node k3s runtime on railiance01. Cost and consumption view of reef-railiance; not a copy of IPs or kubeconfig.", - "decision": { - "status": "gap", - "chosen": "Live discovered cluster; no formal procurement decision in this repo", - "rejected": [], - "approved_by": null, - "approved_on": null, - "ref": "/home/worsch/railiance-cluster/docs/rail-kubernetes-substrate-profile.md" - }, - "operational_refs": [ - "reef:railiance/declarations/reef.yaml" - ], - "credential_handles": [], - "consumers": { - "potential": [ - "rapp-postgres", - "rapp-qonto", - "rapp-openbao", - "coulomb-social", - "helix-forge", - "railiance-platform" - ], - "actual": [ - "rapp-postgres", - "rapp-qonto", - "coulomb-social", - "helix-forge", - "railiance-platform", - "state-hub" - ] - }, - "resource_class": "kubernetes_capacity", - "status": "active", "management_model": "self_managed", - "provider": {"name": "Railiance", "account_ref": null, "product_ref": "k3s", "provider_resource_id": "k3s://239.62.205.92.host.secureserver.net"}, - "service": {"name": "reef-railiance k3s", "service_id": "reef-railiance", "class": "single-node Kubernetes runtime", "capacity_model": "shared"}, - "location": {"region": null, "country": null, "failure_domains": ["provider:host-europe", "host:railiance01", "cluster:reef-railiance"], "residency": null}, - "capacity": [ - {"metric": "cpu", "value": 4, "unit": "vCPU", "kind": "usable", "observed_at": "2026-08-11"}, - {"metric": "memory", "value": 15.62, "unit": "GiB", "kind": "usable", "observed_at": "2026-08-11"}, - {"metric": "ephemeral_storage", "value": 183.06, "unit": "GiB", "kind": "usable", "observed_at": "2026-08-11"}, - {"metric": "pods", "value": 110, "unit": "count", "kind": "limit", "observed_at": "2026-08-11"}, - {"metric": "nodes", "value": 1, "unit": "count", "kind": "observed", "observed_at": "2026-08-11"}, - {"metric": "cpu_usage", "value": 0.564, "unit": "vCPU", "kind": "observed", "observed_at": "2026-08-11"}, - {"metric": "memory_usage", "value": 5.83, "unit": "GiB", "kind": "observed", "observed_at": "2026-08-11"} - ], - "ownership": {"owner": "railiance-cluster", "environment": "production", "workload_ids": ["helix-forge", "coulomb-social", "rapp-qonto", "railiance-platform", "state-hub"], "tenant_id": null, "allocation": {"mode": "unattributed", "cost_attribution_key": null, "driver": null, "method_version": null}}, - "cost": {"currency": "EUR", "tax_status": "unknown", "billing_model": "allocated share of railiance01 plus cluster operations labor; method not established", "commitment_ref": null, "price_evidence": null}, - "lifecycle": {"proposed_on": null, "ordered_on": null, "commissioned_on": "2026-03-10", "renews_on": null, "cancel_by": null, "retired_on": null, "exit_path": "Deploy a replacement cluster, restore stateful services, promote workloads and ingress, verify health, then remove the old node and credentials."}, - "relationships": [{"type": "hosted_on", "resource_id": "resource:hosteurope:railiance01"}], - "requirements": [{"kind": "service_objective", "ref": "/home/worsch/railiance-cluster/INTENT.md"}, {"kind": "recovery", "ref": "/home/worsch/railiance-cluster/workplans/RAIL-BS-WP-0007-threephoenix-ha-cluster.md"}], - "evidence": [ - {"kind": "telemetry", "ref": "kubernetes:reef-railiance/node/239.62.205.92.host.secureserver.net@2026-08-11T09:05:25Z", "observed_at": "2026-08-11", "authority": "reef-railiance Kubernetes API"}, - {"kind": "workload", "ref": "/home/worsch/railiance-cluster/docs/rail-kubernetes-substrate-profile.md", "observed_at": "2026-08-11", "authority": "railiance-cluster"}, - {"kind": "workload", "ref": "STATE-WP-0091: state-hub MCP CPU request 50m→10m after partial telemetry (p99 2.35m, peak 30.45m, 1078 samples). 105m node remaining is not reef admission and does not authorize procurement or scaling.", "observed_at": "2026-09-14", "authority": "state-hub"}, - {"kind": "workload", "ref": "docs/evidence/2026-10-19-custodian-allocation-review.md", "observed_at": "2026-10-19", "authority": "resource-control review of Custodian8e67cfb and Vergabe11f7c8a; reservation snapshot and provisional synthetic evidence, not admission"} - ] -} diff --git a/argocd/platform-addons/custodian-allocation-review/src/weekly-allocation-queries.json b/argocd/platform-addons/custodian-allocation-review/src/weekly-allocation-queries.json deleted file mode 100644 index 8170e56..0000000 --- a/argocd/platform-addons/custodian-allocation-review/src/weekly-allocation-queries.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "schema": "custodian.weekly-allocation-queries.v1", - "note": "CUST-WP-0071-T05. Namespace-level aggregates proven on 2026-09-28 against telemetry/telemetry-prometheus. They populate namespace_context only; workload rows stay 'investigate' until workload-labelled queries exist (kube_pod_labels is not exported by the current kube-state-metrics). Changing any query text changes the report query_signature and breaks week-on-week comparability for one week.", - "window": "7d", - "step": "5m", - "queries": { - "cpu_p95_m": "quantile_over_time(0.95, (sum by(namespace) (rate(container_cpu_usage_seconds_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[5m])) * 1000)[7d:5m])", - "cpu_peak_m": "max_over_time((sum by(namespace) (rate(container_cpu_usage_seconds_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[5m])) * 1000)[7d:5m])", - "cpu_sample_count": "count_over_time((sum by(namespace) (rate(container_cpu_usage_seconds_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[5m])) * 1000)[7d:5m])", - "memory_p95_bytes": "quantile_over_time(0.95, (sum by(namespace) (container_memory_working_set_bytes{container!=\"\",container!=\"POD\",namespace!=\"\"}))[7d:5m])", - "memory_peak_bytes": "max_over_time((sum by(namespace) (container_memory_working_set_bytes{container!=\"\",container!=\"POD\",namespace!=\"\"}))[7d:5m])", - "memory_sample_count": "count_over_time((sum by(namespace) (container_memory_working_set_bytes{container!=\"\",container!=\"POD\",namespace!=\"\"}))[7d:5m])", - "cpu_throttle_ratio": "sum by(namespace) (increase(container_cpu_cfs_throttled_periods_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[7d])) / sum by(namespace) (increase(container_cpu_cfs_periods_total{container!=\"\",container!=\"POD\",namespace!=\"\"}[7d]))", - "restarts": "sum by(namespace) (increase(kube_pod_container_status_restarts_total[7d]))" - } -} diff --git a/argocd/platform-addons/custodian-allocation-review/src/weekly_allocation_incluster.py b/argocd/platform-addons/custodian-allocation-review/src/weekly_allocation_incluster.py deleted file mode 100644 index be72ea4..0000000 --- a/argocd/platform-addons/custodian-allocation-review/src/weekly_allocation_incluster.py +++ /dev/null @@ -1,335 +0,0 @@ -#!/usr/bin/env python3 -"""CUST-WP-0071-T05 in-cluster weekly runner: read-only collection, join, retain. - -Standard library only, so it runs on a pinned python:3.12-slim image with the -scripts mounted from a ConfigMap. It reads the Kubernetes API with the pod's -ServiceAccount (get/list only), queries Prometheus over plain HTTP, reuses the -existing owner code unchanged (railiance-cluster build_observation, custodian -reconcile join, weekly_allocation_report build_report/retain) and writes only -under the data directory. It never resizes, creates tasks or delivers messages. - -Week identity is the ISO week of (run time - 6h); the telemetry window always -ends at Monday 06:00 UTC of that week, so consecutive reports are exactly -604800 s apart regardless of DST or scheduler jitter. 06:00 UTC is at or before -the Monday 08:00 Europe/Berlin schedule in both CET and CEST and keeps the -window start as close as possible to the seven-day Prometheus retention edge. A week whose report is already -retained is a no-op success (idempotent retry); a failed attempt leaves no -report, so the Job's retry or the next manual run collects again. - -Delivery (founder decision 2026-10-21, "Hub message + repo tasks"): after a -week is retained, one State Hub message goes from custodian-allocation-review -to the-custodian with a short summary, report path and sha256. The message id -is recorded in receipts.jsonl; a week that already has a message id is never -posted again, and a retained-but-undelivered week is posted by the next run. -""" -from __future__ import annotations - -import argparse -from datetime import datetime, timedelta, timezone -import hashlib -import importlib.util -import json -import os -from pathlib import Path -import re -import ssl -import sys -import urllib.error -import urllib.parse -import urllib.request - -HERE = Path(__file__).resolve().parent -SA_DIR = Path("/var/run/secrets/kubernetes.io/serviceaccount") -WEEK = re.compile(r"^(\d{4})-W(\d{2})\.json$") -# Exactly the reads the collector performs; mirrored by the ClusterRole. -API_PATHS = { - "nodes": "/api/v1/nodes", - "pods": "/api/v1/pods", - "pvcs": "/api/v1/persistentvolumeclaims", - "pvs": "/api/v1/persistentvolumes", - "storageclasses": "/apis/storage.k8s.io/v1/storageclasses", -} -METRICS_PATHS = { - "node_top": "/apis/metrics.k8s.io/v1beta1/nodes", - "pod_top": "/apis/metrics.k8s.io/v1beta1/pods", -} - - -def load(path: Path, name: str): - spec = importlib.util.spec_from_file_location(name, path) - if spec is None or spec.loader is None: - raise RuntimeError(f"cannot load {path}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def sha256(path: Path) -> str: - return hashlib.sha256(path.read_bytes()).hexdigest() - - -def iso(value: datetime) -> str: - return value.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -WINDOW_END_HOUR = 6 - - -def week_of(now: datetime) -> tuple[str, datetime]: - """ISO week label and its Monday 06:00 UTC (the telemetry window end).""" - shifted = now.astimezone(timezone.utc) - timedelta(hours=WINDOW_END_HOUR) - year, week, weekday = shifted.isocalendar() - monday = (shifted - timedelta(days=weekday - 1)).replace(hour=0, minute=0, second=0, microsecond=0) - return f"{year}-W{week:02d}", monday + timedelta(hours=WINDOW_END_HOUR) - - -def missed_weeks(previous: str | None, current: str) -> list[str]: - if previous is None: - return [] - def monday(label): - year, week = int(label[:4]), int(label[6:8]) - return datetime.fromisocalendar(year, week, 1).replace(tzinfo=timezone.utc, hour=WINDOW_END_HOUR) - out, cursor = [], monday(previous) + timedelta(days=7) - while cursor < monday(current): - out.append(week_of(cursor)[0]) - cursor += timedelta(days=7) - return out - - -def previous_report(reports: Path, label: str) -> Path | None: - earlier = sorted(p for p in reports.glob("*.json") if WEEK.match(p.name) and p.stem < label) - return earlier[-1] if earlier else None - - -class Kube: - """Minimal in-cluster API reader; the token is never logged or persisted.""" - - def __init__(self, base: str, token_path: Path, ca_path: Path): - self.base = base.rstrip("/") - self.token_path = token_path - self.context = ssl.create_default_context(cafile=str(ca_path)) - - def get(self, path: str): - request = urllib.request.Request(self.base + path, headers={ - "Authorization": "Bearer " + self.token_path.read_text().strip(), - "Accept": "application/json"}) - with urllib.request.urlopen(request, context=self.context, timeout=60) as response: - return json.load(response) - - -def top_lines(metrics: dict, *, pods: bool, observer) -> str: - """Render metrics.k8s.io lists in the `kubectl top` shape parse_top reads.""" - lines = [] - for item in metrics.get("items") or []: - meta = item.get("metadata") or {} - if pods: - cpu = sum(observer.cpu_millicores((c.get("usage") or {}).get("cpu")) for c in item.get("containers") or []) - mem = sum(observer.bytes_value((c.get("usage") or {}).get("memory")) for c in item.get("containers") or []) - lines.append(f"{meta.get('namespace')} {meta.get('name')} {cpu}m {mem}") - else: - usage = item.get("usage") or {} - cpu = observer.cpu_millicores(usage.get("cpu")) - mem = observer.bytes_value(usage.get("memory")) - lines.append(f"{meta.get('name')} {cpu}m 0% {mem} 0%") - return "\n".join(lines) + ("\n" if lines else "") - - -def collect_snapshots(get, observer) -> dict: - snapshots = {name: get(path) for name, path in API_PATHS.items()} - for name, path in METRICS_PATHS.items(): - try: - snapshots[name] = top_lines(get(path), pods=name == "pod_top", observer=observer) - except (OSError, ValueError, urllib.error.URLError): - snapshots[name] = None # recorded by build_observation as a measurement gap - return snapshots - - -def prometheus_query(base: str, promql: str, at: datetime, opener=urllib.request.urlopen) -> dict: - """Return Prometheus' JSON response, including error bodies, never raise on 4xx/5xx.""" - url = base.rstrip("/") + "/api/v1/query?" + urllib.parse.urlencode( - {"query": promql, "time": f"{at.timestamp():.3f}"}) - try: - with opener(url, timeout=120) as response: - return json.load(response) - except urllib.error.HTTPError as exc: - try: - return json.loads(exc.read()) - except ValueError: - return {"status": "error", "errorType": "http", "error": f"HTTP {exc.code}"} - except (OSError, ValueError) as exc: - return {"status": "error", "errorType": "transport", "error": type(exc).__name__} - - -def write_json(path: Path, value) -> None: - path.parent.mkdir(parents=True, exist_ok=True) - temporary = path.with_name(path.name + ".tmp") - temporary.write_text(json.dumps(value, indent=2, sort_keys=True) + "\n") - os.replace(temporary, path) - - -def append_receipt(data: Path, receipt: dict) -> None: - data.mkdir(parents=True, exist_ok=True) - with (data / "receipts.jsonl").open("a") as stream: - stream.write(json.dumps(receipt, sort_keys=True) + "\n") - stream.flush() - os.fsync(stream.fileno()) - print(json.dumps(receipt, sort_keys=True)) - - -def delivered_message(data: Path, week: str) -> str | None: - path = data / "receipts.jsonl" - if not path.exists(): - return None - for line in reversed(path.read_text().splitlines()): - try: - entry = json.loads(line) - except ValueError: - continue - if entry.get("week") == week and entry.get("message_id"): - return entry["message_id"] - return None - - -def summary(receipt: dict) -> dict: - report = json.loads(Path(receipt["report"]).read_text()) - decisions: dict[str, int] = {} - for item in report["recommendations"]: - decisions[item["decision"]] = decisions.get(item["decision"], 0) + 1 - return {"week": receipt["week"], "report": receipt["report"], "report_sha256": receipt["report_sha256"], - "capture_end": report["period"]["capture_end"], "workloads": len(report["workloads"]), - "namespaces_with_metrics": len(report["namespace_context"]), - "recommendations": dict(sorted(decisions.items())), - "comparison_compatible": report["comparison"]["compatible"], - "measurement_gaps": len(report["measurement_gaps"]), - "failed_queries": sorted(n for n, q in report["queries"].items() if q.get("response_status") != "success"), - "next_step": "Custodian session reviews and records owner-repo tasks (ADR-001); no automatic resizing."} - - -def hub_post(hub: str, body: dict) -> str: - request = urllib.request.Request(hub.rstrip("/") + "/messages/", data=json.dumps(body).encode(), - headers={"Content-Type": "application/json"}, method="POST") - with urllib.request.urlopen(request, timeout=30) as response: - return str(json.load(response)["id"]) - - -def deliver(data: Path, receipt: dict, hub: str | None, post=hub_post) -> tuple[dict, bool]: - """Post one hub message per retained week; returns (receipt, ok).""" - if receipt.get("status") not in {"retained", "already-retained"}: - return receipt, True - existing = delivered_message(data, receipt["week"]) - if existing: - return {**receipt, "delivery": "already-posted", "message_id": existing}, True - if not hub: - return {**receipt, "delivery": "not configured"}, True - content = summary(receipt) - body = {"from_agent": "custodian-allocation-review", "to_agent": "the-custodian", - "subject": f"Weekly allocation assessment {receipt['week']}", - "body": json.dumps(content, sort_keys=True)} - try: - return {**receipt, "delivery": "posted", "message_id": post(hub, body)}, True - except (OSError, ValueError, KeyError) as exc: - return {**receipt, "delivery": f"failed: {type(exc).__name__}"}, False - - -def run(args, *, now: datetime, get, query) -> dict: - sources = { - "weekly_allocation_report": args.source_dir / "weekly_allocation_report.py", - "reconcile_railiance_allocation": args.source_dir / "reconcile_railiance_allocation.py", - "observe_cluster_resources": args.observer, - "weekly_allocation_incluster": Path(__file__).resolve(), - "queries": args.queries, "ownership": args.ownership, "inventory": args.inventory, - } - weekly = load(sources["weekly_allocation_report"], "weekly_allocation_report") - reconcile = load(sources["reconcile_railiance_allocation"], "reconcile_railiance_allocation") - observer = load(sources["observe_cluster_resources"], "observe_cluster_resources") - label, window_end = week_of(now) - reports = args.data_dir / "reports" - target = reports / f"{label}.json" - receipt = {"schema": "custodian.weekly-allocation-receipt.v1", "week": label, - "run_at": iso(now), "report": str(target), "delivery": "not emitted", - "acknowledgment": None} - if target.exists(): - return {**receipt, "status": "already-retained", "report_sha256": sha256(target)} - previous_path = previous_report(reports, label) - previous_label = previous_path.stem if previous_path else None - - observation = observer.build_observation(collect_snapshots(get, observer), iso(now)) - owners = json.loads(args.ownership.read_text()) - allocation = reconcile.join(observation, owners) - spec = json.loads(args.queries.read_text()) - if spec.get("schema") != "custodian.weekly-allocation-queries.v1": - raise ValueError("unsupported query specification") - telemetry = {"captured_at": window_end.isoformat(), "window": spec["window"], "step": spec["step"], - "source": args.prometheus_url, - "queries": {name: {"promql": promql, "response": query(promql, window_end)} - for name, promql in sorted(spec["queries"].items())}} - provenance = { - "schema": "custodian.allocation-capture-provenance.v1", "workplan": "CUST-WP-0071-T05", - "source_observation": observation["captured_at"], "read_only": True, - "secret_surfaces_read": False, "executor": args.executor, - "kubernetes_reads": sorted(API_PATHS.values()) + sorted(METRICS_PATHS.values()), - "prometheus": args.prometheus_url, "telemetry_window_end": window_end.isoformat(), - "inputs": [{"name": n, "path": str(p), "sha256": sha256(p)} for n, p in sorted(sources.items())], - } - inventory = json.loads(args.inventory.read_text()) - inputs = {"cluster": observation, "allocation": allocation, "telemetry": telemetry, - "inventory": inventory, "provenance": provenance} - attempt = args.data_dir / "runs" / label / iso(now).replace(":", "") - for name, value in inputs.items(): - write_json(attempt / f"{name}.json", value) - report = weekly.build_report( - **inputs, as_of=iso(now), max_age_hours=args.max_age_hours, - previous=json.loads(previous_path.read_text()) if previous_path else None, - reviews=json.loads(args.reviews.read_text()) if args.reviews and args.reviews.exists() else None) - weekly.retain(target, report) - failed = sorted(n for n, q in telemetry["queries"].items() if q["response"].get("status") != "success") - return {**receipt, "status": "retained", "report_sha256": sha256(target), - "inputs_dir": str(attempt), "previous_week": previous_label, - "missed_weeks": missed_weeks(previous_label, label), - "comparison_compatible": report["comparison"]["compatible"], - "workloads": len(report["workloads"]), "failed_queries": failed, - "measurement_gaps": len(report["measurement_gaps"])} - - -def parser() -> argparse.ArgumentParser: - env = os.environ.get - p = argparse.ArgumentParser(description=__doc__) - p.add_argument("--data-dir", type=Path, default=Path(env("ASSESSMENT_DATA_DIR", "/data"))) - p.add_argument("--source-dir", type=Path, default=Path(env("ASSESSMENT_SOURCE_DIR", str(HERE)))) - p.add_argument("--observer", type=Path, default=Path(env("ASSESSMENT_OBSERVER", - str(Path(env("ASSESSMENT_SOURCE_DIR", str(HERE))) / "observe_cluster_resources.py")))) - p.add_argument("--queries", type=Path, default=Path(env("ASSESSMENT_QUERIES", str(HERE.parent / "config/weekly-allocation-queries.json")))) - p.add_argument("--ownership", type=Path, default=env("ASSESSMENT_OWNERSHIP") and Path(env("ASSESSMENT_OWNERSHIP"))) - p.add_argument("--inventory", type=Path, default=env("ASSESSMENT_INVENTORY") and Path(env("ASSESSMENT_INVENTORY"))) - p.add_argument("--reviews", type=Path, default=env("ASSESSMENT_REVIEWS") and Path(env("ASSESSMENT_REVIEWS"))) - p.add_argument("--prometheus-url", default=env("PROMETHEUS_URL", "http://telemetry-prometheus.telemetry.svc:9090")) - p.add_argument("--hub-url", default=env("STATE_HUB_URL"), help="State Hub API; unset disables delivery") - p.add_argument("--executor", default=env("ASSESSMENT_EXECUTOR", "unspecified")) - p.add_argument("--max-age-hours", type=float, default=float(env("ASSESSMENT_MAX_AGE_HOURS", "48"))) - return p - - -def main() -> int: - args = parser().parse_args() - now = datetime.now(timezone.utc) - label = week_of(now)[0] - try: - if not args.ownership or not args.inventory: - raise ValueError("ownership and inventory inputs must be bound") - host = os.environ["KUBERNETES_SERVICE_HOST"] - port = os.environ.get("KUBERNETES_SERVICE_PORT", "443") - kube = Kube(f"https://{host}:{port}", SA_DIR / "token", SA_DIR / "ca.crt") - receipt = run(args, now=now, get=kube.get, - query=lambda promql, at: prometheus_query(args.prometheus_url, promql, at)) - except Exception as exc: # noqa: BLE001 - every failure must leave a receipt - append_receipt(args.data_dir, {"schema": "custodian.weekly-allocation-receipt.v1", - "week": label, "run_at": iso(now), "status": "failed", - "error": f"{type(exc).__name__}: {exc}"[:500]}) - return 1 - receipt, delivered = deliver(args.data_dir, receipt, args.hub_url) - append_receipt(args.data_dir, receipt) - return 0 if delivered else 1 # undelivered: Job retry posts it (report already retained) - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/argocd/platform-addons/custodian-allocation-review/src/weekly_allocation_report.py b/argocd/platform-addons/custodian-allocation-review/src/weekly_allocation_report.py deleted file mode 100644 index 4917a23..0000000 --- a/argocd/platform-addons/custodian-allocation-review/src/weekly_allocation_report.py +++ /dev/null @@ -1,321 +0,0 @@ -#!/usr/bin/env python3 -"""Retain CUST-WP-0071 sizing evidence; no network, resizing or task emission.""" -from __future__ import annotations - -import argparse -from datetime import datetime, timedelta, timezone -import hashlib -import json -import math -import os -from pathlib import Path -import re -import tempfile - -SCHEMA = "custodian.weekly-allocation.v1" -METRICS = ( - "cpu_p95_m", "cpu_peak_m", "memory_p95_bytes", "memory_peak_bytes", - "cpu_throttle_ratio", "latency_p95_seconds", "errors", "request_count", - "restarts", "oom_events", "cpu_sample_count", "memory_sample_count", -) -REQUIRED = tuple(m for m in METRICS if not m.endswith("sample_count")) - - -def timestamp(value): - result = datetime.fromisoformat(value.replace("Z", "+00:00")) - if result.tzinfo is None: - raise ValueError("timestamps must include timezone") - return result.astimezone(timezone.utc) - - -def duration(value): - match = re.fullmatch(r"([1-9][0-9]*)([smhdw])", value) - if not match: - raise ValueError(f"unsupported duration: {value}") - return int(match[1]) * {"s": 1, "m": 60, "h": 3600, "d": 86400, "w": 604800}[match[2]] - - -def digest(value): - return hashlib.sha256(json.dumps(value, sort_keys=True, allow_nan=False).encode()).hexdigest() - - -def finite(value): - number = float(value) - if not math.isfinite(number) or number < 0: - raise ValueError("metric must be finite and nonnegative") - return number - - -def metric_series(telemetry): - """Do not distribute namespace aggregates over workloads, or sum percentiles.""" - series, queries = {}, {} - seconds = duration(telemetry["window"]) - for name, query in sorted(telemetry.get("queries", {}).items()): - response = query.get("response", {}) - data = response.get("data", {}) - queries[name] = { - "promql": query.get("promql"), "response_status": response.get("status"), - "result_type": data.get("resultType"), "warnings": response.get("warnings", []), - "error": response.get("error"), "response_sha256": digest(response), - "evaluation_times": [], - } - if name not in METRICS or response.get("status") != "success" or data.get("resultType") != "vector": - continue - for sample in data.get("result", []): - labels = sample.get("metric", {}) - namespace, workload = labels.get("namespace"), labels.get("workload") - # Additional dimensions are ambiguous: do not silently collapse pod/job series. - if not namespace or set(labels) - {"namespace", "workload", "__name__"}: - queries[name].setdefault("rejected", []).append("unaggregated or absent identity labels") - continue - key = (namespace, workload) - values = series.setdefault(key, {}) - if name in values: - values[name] = {"value": None, "reason": "ambiguous duplicate series"} - continue - try: - epoch, raw = sample["value"] - end = datetime.fromtimestamp(finite(epoch), timezone.utc) - number = finite(raw) - except (KeyError, ValueError, TypeError, OverflowError): - values[name] = {"value": None, "reason": "invalid sample"} - continue - queries[name]["evaluation_times"].append(end.isoformat()) - values[name] = { - "value": number, "window_start": (end - timedelta(seconds=seconds)).isoformat(), - "window_end": end.isoformat(), "query": name, - "capture_alignment_seconds": (end - timestamp(telemetry["captured_at"])).total_seconds(), - } - return series, queries - - -def recommendation_id(resource, namespace, workload): - return "allocation-" + digest([resource, namespace, workload, "sizing-review"])[:24] - - -def build_report(cluster, allocation, telemetry, inventory, provenance, *, as_of, - previous=None, reviews=None, max_age_hours=48): - now = timestamp(as_of) - if cluster.get("schema_version") != "railiance.cluster-resource-observation.v1": - raise ValueError("unsupported cluster schema") - if allocation.get("schema") != "custodian.allocation-reconcile.v1": - raise ValueError("unsupported allocation schema") - resource = cluster["resource_id"] - if inventory.get("id") != resource or allocation.get("host", {}).get("cluster_resource_id") != resource: - raise ValueError("resource identity mismatch") - if allocation.get("count_host_and_cluster_cpus_once") is not True: - raise ValueError("allocation must deduplicate host and cluster CPUs") - if cluster["captured_at"] != allocation["captured_at"]: - raise ValueError("cluster and reconcile snapshots differ") - if provenance.get("source_observation") != cluster["captured_at"]: - raise ValueError("provenance does not identify cluster snapshot") - seconds = duration(telemetry["window"]) - if seconds != 604800: - raise ValueError("weekly report requires a seven-day telemetry window") - step = duration(telemetry["step"]) - series, queries = metric_series(telemetry) - captured = {"cluster": cluster["captured_at"], "telemetry": telemetry["captured_at"]} - freshness = {} - for name, value in captured.items(): - age = (now - timestamp(value)).total_seconds() - freshness[name] = {"age_seconds": age, "status": "future" if age < 0 else "stale" if age > max_age_hours * 3600 else "fresh"} - query_signature = digest({name: q["promql"] for name, q in queries.items()}) - end = timestamp(telemetry["captured_at"]) - comparison_reason = "no previous retained report" - compatible = False - if previous: - prev_end = timestamp(previous["period"]["capture_end"]) - compatible = ( - previous.get("schema") == SCHEMA and previous.get("resource_id") == resource - and previous["period"].get("seconds") == seconds - and previous["period"].get("step_seconds") == step - and previous.get("query_signature") == query_signature - and abs((end - prev_end).total_seconds() - seconds) <= step - ) - comparison_reason = "comparable consecutive capture windows" if compatible else "nonconsecutive windows or changed query/resource/step contract" - previous_rows = {(r["namespace"], r["workload"]): r for r in (previous or {}).get("workloads", [])} - owners = {(r["namespace"], r["workload"]): r for r in allocation.get("workloads", [])} - if len(owners) != len(allocation.get("workloads", [])): - raise ValueError("duplicate allocation workload identity") - reviews = reviews or {} - rows, recommendations = [], [] - seen = set() - for workload in sorted(cluster.get("utilization", {}).get("workloads", []), key=lambda x: (x["namespace"], x["workload"])): - key = workload["namespace"], workload["workload"] - if key in seen: - raise ValueError("duplicate cluster workload identity") - seen.add(key) - owner = owners.get(key, {}) - measurements = {m: series.get(key, {}).get(m, {"value": None, "reason": "no workload series"}) for m in METRICS} - gaps = [m for m in REQUIRED if measurements[m]["value"] is None] - metric_stale = [m for m, v in measurements.items() if v["value"] is not None and not 0 <= (now - timestamp(v["window_end"])).total_seconds() <= max_age_hours * 3600] - metric_unaligned = [m for m, v in measurements.items() if v["value"] is not None and abs(v["capture_alignment_seconds"]) > step] - rid = recommendation_id(resource, *key) - reasons = [] - if gaps: - reasons.append("missing workload metrics: " + ", ".join(gaps)) - if metric_stale or any(v["status"] != "fresh" for v in freshness.values()): - reasons.append("stale or future observation") - if metric_unaligned: - reasons.append("metric evaluation windows differ from capture by more than one step") - if owner.get("owner", "unknown") == "unknown": - reasons.append("unknown owner") - if workload.get("requests", {}).get("cpu_millicores") == 0: - reasons.append("zero CPU request requires owner review") - review = reviews.get(rid) - decision, confidence, evidence = "investigate", "low", [] - if review: - if review.get("decision") not in {"keep", "increase", "decrease", "investigate"}: - raise ValueError("invalid owner review decision") - required = ("owner", "evidence_refs", "receiving_task", "configuration", "rollback_ref", "reviewed_at", "period_end", "coverage_ref", "service_acceptance_ref", "margin_ref") - if any(not review.get(k) for k in required): - raise ValueError("owner review missing evidence, receiving task or configuration/rollback") - if review["owner"] != owner.get("owner") or review["period_end"] != telemetry["captured_at"]: - reasons.append("owner review does not match this owner/window") - elif timestamp(review["reviewed_at"]) > now: - reasons.append("owner review is future-dated") - elif not reasons: - decision, confidence, evidence = review["decision"], "owner-reviewed", review["evidence_refs"] - else: - reasons.append("owner proposal retained; incomplete evidence prevents accepted sizing recommendation") - else: - reasons.append("no owner-reviewed sizing decision") - row = { - "namespace": key[0], "workload": key[1], "owner": owner.get("owner", "unknown"), - "service": owner.get("service", "unknown"), "tenant": owner.get("tenant", "unknown"), - "pods": workload.get("pods"), "requests": workload.get("requests"), - "limits": workload.get("limits"), "instantaneous_observed": workload.get("observed"), - "metrics": measurements, "missing_metrics": gaps, - "namespace_context_ref": key[0] if (key[0], None) in series else None, - "raw_scrape_coverage": "unknown; evaluation counts do not prove source coverage", - "recommendation_id": rid, "week_on_week": {}, "comparison_reasons": {}, - } - prior = previous_rows.get(key) - for metric in METRICS: - current = measurements[metric]["value"] - old_measurement = (prior or {}).get("metrics", {}).get(metric, {}) - old = old_measurement.get("value") - metric_compatible = False - reason = comparison_reason - if compatible and current is not None and old is not None: - try: - old_end, old_start = timestamp(old_measurement["window_end"]), timestamp(old_measurement["window_start"]) - new_end, new_start = timestamp(measurements[metric]["window_end"]), timestamp(measurements[metric]["window_start"]) - metric_compatible = ( - (old_end - old_start).total_seconds() == seconds - and (new_end - new_start).total_seconds() == seconds - and abs((new_end - old_end).total_seconds() - seconds) <= step - and abs((new_start - old_end).total_seconds()) <= step - and abs(measurements[metric]["capture_alignment_seconds"]) <= step - and abs((old_end - timestamp(previous["period"]["capture_end"])).total_seconds()) <= step - ) - reason = "comparable consecutive metric windows" if metric_compatible else "metric evaluation windows are not consecutive or capture-aligned" - except (KeyError, ValueError, TypeError): - reason = "missing or invalid previous metric window" - elif compatible: - reason = "missing current or previous metric" - row["week_on_week"][metric] = current - old if metric_compatible else None - row["comparison_reasons"][metric] = reason - rows.append(row) - prior_recommendation = next((r for r in (previous or {}).get("recommendations", []) if r["id"] == rid), {}) - recommendations.append({ - "id": rid, "decision": decision, "confidence": confidence, "owner": row["owner"], - "reasons": reasons, "evidence_refs": evidence, "owner_proposal": review, - "receiving_task": review.get("receiving_task") if review else prior_recommendation.get("receiving_task"), - "previous_decision": prior_recommendation.get("decision"), - "owner_outcome": review.get("outcome") if review else None, - "delivery": "not emitted", "acknowledgment": None, - }) - if seen != set(owners): - raise ValueError("cluster and allocation workload inventories differ") - return { - "schema": SCHEMA, "workplan": "CUST-WP-0071-T05", "as_of": as_of, - "adapter_sha256": hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), - "resource_id": resource, "period": {"capture_end": telemetry["captured_at"], "seconds": seconds, "step_seconds": step, - "note": "Actual per-query windows are retained on samples; capture time is not a common query evaluation time."}, - "freshness": freshness, "max_age_hours": max_age_hours, "query_signature": query_signature, - "queries": queries, "collector_provenance": provenance, - "source_sha256": {"cluster": digest(cluster), "allocation": digest(allocation), "telemetry": digest(telemetry), "inventory": digest(inventory)}, - "node": {"capacity": cluster.get("capacity"), "allocation": cluster.get("allocation"), - "scheduled_request_cpu_m": allocation.get("scheduled_request_cpu_m"), - "pending_unscheduled_cpu_m": allocation.get("pending_unscheduled_cpu_m"), - "reservation_residual_cpu_m": allocation.get("residual_cpu_m"), - "release_preflight": allocation.get("state_hub_preflight_observation"), - "operating_margin": None, "release_margin_acceptance": "not established by reservation residual", - "host_cluster_counted_once": True}, - "portfolio": {k: inventory.get(k) for k in ("id", "schema_version", "ownership", "capacity", "relationships", "evidence")}, - "workloads": rows, - "namespace_context": {ns: values for (ns, workload), values in sorted(series.items(), key=lambda x: str(x[0])) if workload is None}, - "comparison": {"compatible": compatible, "reason": comparison_reason, - "previous_report_sha256": digest(previous) if previous else None, - "disappeared_workloads": [list(key) for key in sorted(set(previous_rows) - seen)]}, - "measurement_gaps": cluster.get("measurement_gaps", []) + allocation.get("measurement_gaps", []), - "recommendations": recommendations, - "limitations": ["No representative pilot acceptance inferred from telemetry.", - "Namespace percentiles and peaks are not workload metrics or additive node peaks.", - "Portfolio capacity is dated evidence, not additional host capacity.", - "No procurement, sizing changes, owner-task creation, delivery or acknowledgment performed.", - "Installed executor, scoped reads, independent missed-run handling and first unattended receipt remain T05 gates."], - } - - -def retain(path, report): - """Never overwrite a retained week; identical retries succeed.""" - encoded = json.dumps(report, indent=2, sort_keys=True, allow_nan=False) + "\n" - path.parent.mkdir(parents=True, exist_ok=True) - temporary = None - try: - with tempfile.NamedTemporaryFile(mode="w", dir=path.parent, delete=False) as stream: - temporary = Path(stream.name) - stream.write(encoded) - stream.flush() - os.fsync(stream.fileno()) - try: - os.link(temporary, path) - except FileExistsError: - if path.is_symlink() or path.read_text() != encoded: - raise ValueError("retained report differs; choose a new explicit revision path") - finally: - if temporary is not None: - temporary.unlink(missing_ok=True) - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--request", type=Path, help="Executor-owned JSON request; paths relative to its directory") - for name in ("cluster", "allocation", "telemetry", "inventory", "provenance"): - parser.add_argument("--" + name, type=Path) - parser.add_argument("--previous", type=Path) - parser.add_argument("--reviews", type=Path) - parser.add_argument("--as-of") - parser.add_argument("--max-age-hours", type=float, default=48) - parser.add_argument("--output", type=Path) - args = parser.parse_args() - try: - if args.request: - request = json.loads(args.request.read_text()) - if request.get("schema") != "custodian.weekly-allocation-request.v1": - raise ValueError("unsupported executor request schema") - if any(getattr(args, key) for key in ("cluster", "allocation", "telemetry", "inventory", "provenance", "previous", "reviews", "as_of", "output")): - raise ValueError("request cannot be mixed with input/output overrides") - for key in ("cluster", "allocation", "telemetry", "inventory", "provenance", "previous", "reviews", "output"): - value = request.get(key) - setattr(args, key, args.request.parent / value if value else None) - args.as_of = request.get("as_of") - if any(not getattr(args, key) for key in ("cluster", "allocation", "telemetry", "inventory", "provenance", "as_of", "output")): - raise ValueError("executor input paths, as_of and retention output must be explicitly bound") - if not math.isfinite(args.max_age_hours) or args.max_age_hours <= 0: - raise ValueError("max-age-hours must be positive and finite") - inputs = {name: json.loads(getattr(args, name).read_text()) for name in ("cluster", "allocation", "telemetry", "inventory", "provenance")} - report = build_report(**inputs, as_of=args.as_of, max_age_hours=args.max_age_hours, - previous=json.loads(args.previous.read_text()) if args.previous else None, - reviews=json.loads(args.reviews.read_text()) if args.reviews else None) - retain(args.output, report) - except (OSError, ValueError, KeyError, TypeError) as exc: - parser.exit(2, f"weekly allocation report refused: {exc}\n") - print(json.dumps({"status": "retained", "path": str(args.output), "sha256": digest(report), - "workloads": len(report["workloads"]), "delivery": "not emitted"})) - - -if __name__ == "__main__": - main() diff --git a/argocd/platform-addons/custodian-allocation-review/vendor.lock.json b/argocd/platform-addons/custodian-allocation-review/vendor.lock.json deleted file mode 100644 index 39fec96..0000000 --- a/argocd/platform-addons/custodian-allocation-review/vendor.lock.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "files": { - "namespace-ownership.json": { - "commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f", - "path": "docs/evidence/namespace-ownership.yaml", - "repo": "the-custodian", - "source_sha256": "a3663c03f685db35d5e0eabca59e1d74810b1b56c39db0892a58903302bb771f", - "vendored_sha256": "dd73079defcc26db460ee1ed0ef17a5eab39b04d9dbc209cb6d02064df8f3f05" - }, - "observe_cluster_resources.py": { - "commit": "a5948408a565d383a99f43d414145277a43ce233", - "path": "tools/observe_cluster_resources.py", - "repo": "railiance-cluster", - "source_sha256": "7a613f7ba692169ec9ddf498f7250ee2a56f8fb1088585498af6df2302e15002", - "vendored_sha256": "7a613f7ba692169ec9ddf498f7250ee2a56f8fb1088585498af6df2302e15002" - }, - "reconcile_railiance_allocation.py": { - "commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f", - "path": "scripts/reconcile_railiance_allocation.py", - "repo": "the-custodian", - "source_sha256": "3e118023397bbb6621116f6f3009cb6ec7f894809671efeb5c165dd2c3e32a3a", - "vendored_sha256": "3e118023397bbb6621116f6f3009cb6ec7f894809671efeb5c165dd2c3e32a3a" - }, - "reef-railiance-k3s.json": { - "commit": "3d9b1bd08043735e71246213e46c91f98f41d6ce", - "path": "data/resources/reef-railiance-k3s.json", - "repo": "resource-control", - "source_sha256": "c97cb2c505cc52dcf1e1f394d35a8d87dfec09a9033f6cd4c0c05717111f913f", - "vendored_sha256": "c97cb2c505cc52dcf1e1f394d35a8d87dfec09a9033f6cd4c0c05717111f913f" - }, - "weekly-allocation-queries.json": { - "commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f", - "path": "config/weekly-allocation-queries.json", - "repo": "the-custodian", - "source_sha256": "83bde8a6e53c4b86b9624195b9a00297310eefb1e271ba4506622c1efdd7e7a9", - "vendored_sha256": "83bde8a6e53c4b86b9624195b9a00297310eefb1e271ba4506622c1efdd7e7a9" - }, - "weekly_allocation_incluster.py": { - "commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f", - "path": "scripts/weekly_allocation_incluster.py", - "repo": "the-custodian", - "source_sha256": "de21c18111f818bf31d9d02763e170a83d1e7581152759f7e1962c4402120d77", - "vendored_sha256": "de21c18111f818bf31d9d02763e170a83d1e7581152759f7e1962c4402120d77" - }, - "weekly_allocation_report.py": { - "commit": "9edd3151ac5c93e5319a9581c0df145f8d93c68f", - "path": "scripts/weekly_allocation_report.py", - "repo": "the-custodian", - "source_sha256": "93f546243331308d7e60f8db36e988d9847537a2fe46cce49ee2fffba1f7f247", - "vendored_sha256": "93f546243331308d7e60f8db36e988d9847537a2fe46cce49ee2fffba1f7f247" - } - }, - "schema": "railiance-platform.vendor-lock.v1", - "workplan": "CUST-WP-0071-T05" -} diff --git a/argocd/platform-addons/ops-mason-builder/clusterrole.yaml b/argocd/platform-addons/ops-mason-builder/clusterrole.yaml deleted file mode 100644 index 9b9f8fa..0000000 --- a/argocd/platform-addons/ops-mason-builder/clusterrole.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# Scoped ops-mason builder (founder decision 2026-10-22, MASON-IN-0006 (b)). -# Kinds kubernetes_plane applies, plus get/list/watch on services for dependency -# reads, plus create on self-subject access reviews so kubectl auth can-i works. -# Excluded: pods, secrets, configmaps, serviceaccounts/token, impersonate, -# roles, rolebindings, clusterroles, clusterrolebindings, nodes/proxy, pods/exec, -# escalate, bind, wildcards. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: ops-mason-builder - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: ops-mason-builder -rules: - - apiGroups: [""] - resources: ["namespaces"] - verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] - - apiGroups: [""] - resources: ["serviceaccounts"] - verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] - - apiGroups: ["networking.k8s.io"] - resources: ["networkpolicies"] - verbs: ["create", "get", "list", "watch", "update", "patch", "delete"] - - apiGroups: [""] - resources: ["services"] - verbs: ["get", "list", "watch"] - - apiGroups: ["authorization.k8s.io"] - resources: ["selfsubjectaccessreviews", "selfsubjectrulesreviews"] - verbs: ["create"] diff --git a/argocd/platform-addons/ops-mason-builder/clusterrolebinding.yaml b/argocd/platform-addons/ops-mason-builder/clusterrolebinding.yaml deleted file mode 100644 index 41ec5d6..0000000 --- a/argocd/platform-addons/ops-mason-builder/clusterrolebinding.yaml +++ /dev/null @@ -1,15 +0,0 @@ -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: ops-mason-builder - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: ops-mason-builder -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: ops-mason-builder -subjects: - - kind: User - name: ops-mason-builder - apiGroup: rbac.authorization.k8s.io diff --git a/argocd/platform-addons/ops-mason-builder/kustomization.yaml b/argocd/platform-addons/ops-mason-builder/kustomization.yaml deleted file mode 100644 index a37d052..0000000 --- a/argocd/platform-addons/ops-mason-builder/kustomization.yaml +++ /dev/null @@ -1,11 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -# MASON-IN-0006 choice (b), decision 30d29060-8d14-4a8e-9cde-34aa826eb8d1. -# Scoped builder for ops-mason. User ops-mason-builder, no ServiceAccount token, -# no Secret. Applied by argocd/railiance01/applications/ops-mason-builder.application.yaml. -# Manual sync. targetRevision is pinned to the commit that introduced these manifests. - -resources: - - clusterrole.yaml - - clusterrolebinding.yaml diff --git a/argocd/railiance01/applications/README.md b/argocd/railiance01/applications/README.md index 87be28a..903e353 100644 --- a/argocd/railiance01/applications/README.md +++ b/argocd/railiance01/applications/README.md @@ -12,8 +12,3 @@ with the founder's go-ahead: no `automated` block, no finalizer, and a pinned `targetRevision`. A merge here plus a hand sync of the root is a live production change on railiance01. Drafts wait in `../drafts/`, which no root syncs. - -A release that changes a child's `targetRevision` commits that pin here in the -same change window, whether or not the live Application was patched directly. -Before any hand sync of the root, run `make argocd-pin-drift`; any `DRIFT` -line means the sync would roll that child back, so stop and reconcile first. diff --git a/argocd/railiance01/applications/activity-core.application.yaml b/argocd/railiance01/applications/activity-core.application.yaml index 55bd466..9ae85cb 100644 --- a/argocd/railiance01/applications/activity-core.application.yaml +++ b/argocd/railiance01/applications/activity-core.application.yaml @@ -1,7 +1,4 @@ # ACTIVITY-WP-0041 / RPF-WP-0048: founder-authorized adoption, 2026-09-27. -# Pin = live revision of the 2026-10-20 founder-approved release -# (activity-core docs/evidence/2026-10-20-release-health-probe-release.json). -# Every activity-core release commits its pin here; see RPF-WP-ADHOC-2026-10-19. apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -13,7 +10,7 @@ spec: project: activity-core source: repoURL: https://forgejo.coulomb.social/coulomb/activity-core.git - targetRevision: 1fe2fcc0d51c086db00d5f4d8b4e2fadb1d89a1d + targetRevision: ce9a8f8edf4ec162bae3aa087803bd57a1b33a66 path: k8s/gitops destination: server: https://kubernetes.default.svc diff --git a/argocd/railiance01/applications/bao-notice.application.yaml b/argocd/railiance01/applications/bao-notice.application.yaml index bdb0a22..9436272 100644 --- a/argocd/railiance01/applications/bao-notice.application.yaml +++ b/argocd/railiance01/applications/bao-notice.application.yaml @@ -1,8 +1,6 @@ # railiance01 child Application (RPF-WP-0047, founder request 2026-09-23 via # user-engine 7d8982c2). Static notice only; OpenBao stays non-public. # No automated sync, no finalizer; targetRevision is the reviewed commit. -# Manifests moved to rapp-openbao on 2026-10-23. This Application stays the -# GitOps attachment. Sync it by hand after that commit is on the remote. apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -16,9 +14,9 @@ metadata: spec: project: railiance-tenants source: - repoURL: https://forgejo.coulomb.social/coulomb/rapp-openbao.git - targetRevision: a1f5851e8ad16df5689ef8c91273d77590863089 - path: manifests/bao-notice + repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git + targetRevision: 9ee20967ca7b50a8926c99ae86fa027f8bfb0fa3 + path: argocd/platform-addons/bao-notice destination: server: https://kubernetes.default.svc namespace: bao-notice diff --git a/argocd/railiance01/applications/custodian-agents.application.yaml b/argocd/railiance01/applications/custodian-agents.application.yaml deleted file mode 100644 index 746615a..0000000 --- a/argocd/railiance01/applications/custodian-agents.application.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# railiance01 child Application (RPF-WP-0062 / CUST-WP-0073-T02, founder decision 2026-10-20). -# Read-only custodian agent observer identity. No automated sync, no finalizer; -# targetRevision is the reviewed commit that introduced the manifests. -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: custodian-agents - namespace: argocd - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: custodian-agent-identity - annotations: - argocd.argoproj.io/sync-wave: "10" -spec: - project: railiance-platform-addons - source: - repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git - targetRevision: 567d2c671bb0e36bef2f7f2767414ea76aa8cf9a - path: argocd/platform-addons/custodian-agents - destination: - server: https://kubernetes.default.svc - namespace: custodian-agents - syncPolicy: - syncOptions: - - ApplyOutOfSyncOnly=true - - PruneLast=true diff --git a/argocd/railiance01/applications/informed-decision.yaml b/argocd/railiance01/applications/informed-decision.yaml index 6253a0e..068a555 100644 --- a/argocd/railiance01/applications/informed-decision.yaml +++ b/argocd/railiance01/applications/informed-decision.yaml @@ -7,7 +7,7 @@ spec: project: informed-decision-lifecycle source: repoURL: https://forgejo.coulomb.social/coulomb/informed-decision.git - targetRevision: 312ab97e876e17341bf16f9e95397e14c2ac2720 + targetRevision: 9207a5134f92904f3843c200645ced91f247af72 path: deploy/lifecycle destination: server: https://kubernetes.default.svc diff --git a/argocd/railiance01/applications/ops-mason-builder.application.yaml b/argocd/railiance01/applications/ops-mason-builder.application.yaml deleted file mode 100644 index f14f5c0..0000000 --- a/argocd/railiance01/applications/ops-mason-builder.application.yaml +++ /dev/null @@ -1,27 +0,0 @@ -# railiance01 child Application (MASON-IN-0006 choice (b), decision -# 30d29060-8d14-4a8e-9cde-34aa826eb8d1). Scoped ops-mason builder identity. -# No automated sync, no finalizer; targetRevision is the commit that -# introduced the manifests. -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: ops-mason-builder - namespace: argocd - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: ops-mason-builder - annotations: - argocd.argoproj.io/sync-wave: "10" -spec: - project: railiance-platform-addons - source: - repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git - targetRevision: bd6660a91672c76b78e017c22394705381d8767d - path: argocd/platform-addons/ops-mason-builder - destination: - server: https://kubernetes.default.svc - namespace: default - syncPolicy: - syncOptions: - - ApplyOutOfSyncOnly=true - - PruneLast=true diff --git a/argocd/railiance01/applications/target-revenue.application.yaml b/argocd/railiance01/applications/target-revenue.application.yaml index a84a088..48c55e7 100644 --- a/argocd/railiance01/applications/target-revenue.application.yaml +++ b/argocd/railiance01/applications/target-revenue.application.yaml @@ -14,7 +14,7 @@ spec: project: railiance-tenants source: repoURL: https://forgejo.coulomb.social/coulomb/target-revenue.git - targetRevision: f110a4ea96be42335586d2921a987db73fa6b930 + targetRevision: a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0 path: k8s/railiance destination: server: https://kubernetes.default.svc diff --git a/argocd/railiance01/bootstrap/01-railiance-tenants-project.yaml b/argocd/railiance01/bootstrap/01-railiance-tenants-project.yaml index 9b4ea7f..3aadff6 100644 --- a/argocd/railiance01/bootstrap/01-railiance-tenants-project.yaml +++ b/argocd/railiance01/bootstrap/01-railiance-tenants-project.yaml @@ -12,7 +12,6 @@ spec: - https://forgejo.coulomb.social/coulomb/*.git # Emergency rollback only (T12 Option A through 2026-08-07) - https://gitea.coulomb.social/coulomb/*.git - - ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git destinations: - server: https://kubernetes.default.svc namespace: "*" diff --git a/argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml b/argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml index 5c1c559..2bc5e2a 100644 --- a/argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml +++ b/argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml @@ -46,12 +46,6 @@ spec: kind: Deployment - group: batch kind: CronJob - # CUST-WP-0071-T05 (founder decision 2026-10-21): custodian-allocation-review - # needs its retained-report claim and default-deny egress policy. - - group: "" - kind: PersistentVolumeClaim - - group: networking.k8s.io - kind: NetworkPolicy - group: rbac.authorization.k8s.io kind: Role - group: rbac.authorization.k8s.io diff --git a/argocd/railiance01/drafts/custodian-allocation-review.application.yaml b/argocd/railiance01/drafts/custodian-allocation-review.application.yaml deleted file mode 100644 index ca38df6..0000000 --- a/argocd/railiance01/drafts/custodian-allocation-review.application.yaml +++ /dev/null @@ -1,29 +0,0 @@ -# DRAFT — not synced by any root (drafts/ is never referenced). CUST-WP-0071-T05. -# Activation (founder go-ahead, after CUST-WP-0071-T04 and review): add -# PersistentVolumeClaim to the railiance-platform-addons AppProject, set -# targetRevision to the reviewed commit, move this file to ../applications/, -# run `make argocd-pin-drift`, then hand-sync the root and this Application. -# See argocd/platform-addons/custodian-allocation-review/README.md. -apiVersion: argoproj.io/v1alpha1 -kind: Application -metadata: - name: custodian-allocation-review - namespace: argocd - labels: - app.kubernetes.io/part-of: railiance-gitops - railiance-platform/component: custodian-allocation-review - annotations: - argocd.argoproj.io/sync-wave: "10" -spec: - project: railiance-platform-addons - source: - repoURL: https://forgejo.coulomb.social/coulomb/railiance-platform.git - targetRevision: REPLACE_WITH_REVIEWED_COMMIT_SHA - path: argocd/platform-addons/custodian-allocation-review - destination: - server: https://kubernetes.default.svc - namespace: custodian-assessment - syncPolicy: - syncOptions: - - ApplyOutOfSyncOnly=true - - PruneLast=true diff --git a/argocd/railiance01/drafts/external-secrets.application.yaml b/argocd/railiance01/drafts/external-secrets.application.yaml index a813671..eebc9e1 100644 --- a/argocd/railiance01/drafts/external-secrets.application.yaml +++ b/argocd/railiance01/drafts/external-secrets.application.yaml @@ -1,5 +1,5 @@ # DRAFT for railiance01 (RPF-WP-0044). Not synced by any root: move to -# ../applications/ only after predecessor automated proof under accepted D2. +# ../applications/ only in this app's adoption task, with the founder's go-ahead. # No automated sync, no finalizer. CRDs survive Application pruning/deletion. apiVersion: argoproj.io/v1alpha1 kind: Application @@ -27,15 +27,6 @@ spec: serviceAccount: create: true name: external-secrets - # Wave-2 requests (RPF-WP-0063-T01); mirrors helm/external-secrets-resources.values.yaml - resources: - requests: {cpu: 10m, memory: 64Mi} - certController: - resources: - requests: {cpu: 10m, memory: 64Mi} - webhook: - resources: - requests: {cpu: 10m, memory: 80Mi} destination: server: https://kubernetes.default.svc namespace: external-secrets diff --git a/argocd/railiance01/applications/issue-core.application.yaml b/argocd/railiance01/drafts/issue-core.application.yaml similarity index 53% rename from argocd/railiance01/applications/issue-core.application.yaml rename to argocd/railiance01/drafts/issue-core.application.yaml index cfaf4bc..4d2a7c8 100644 --- a/argocd/railiance01/applications/issue-core.application.yaml +++ b/argocd/railiance01/drafts/issue-core.application.yaml @@ -1,6 +1,6 @@ -# railiance01 issue-core (RPF-WP-0044-T04, adopted 2026-10-20 under D2 89f1058f + founder go). -# No automated sync, no finalizer. Read-only SSH deploy key: argocd/rapp-issue-core-repository. -# targetRevision: rapp-issue-core 11af3cb (wave-2 requests 5m/48Mi, ISSUE-WP-0008-T01; on top of 805e124 forge-http URL). +# DRAFT for railiance01 (RPF-WP-0044). Not synced by any root: move to +# ../applications/ only in this app's adoption task, with the founder's go-ahead. +# No automated sync, no finalizer. targetRevision: rapp-issue-core commit diffed 2026-09-21. apiVersion: argoproj.io/v1alpha1 kind: Application metadata: @@ -14,8 +14,8 @@ metadata: spec: project: railiance-tenants source: - repoURL: ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git - targetRevision: 11af3cbf359ba850c8a807d7cc8b369c037862df + repoURL: https://forgejo.coulomb.social/coulomb/rapp-issue-core.git + targetRevision: a78f38cfd8afb29ed23d150a4ea3e3dcd1f35381 path: manifests destination: server: https://kubernetes.default.svc diff --git a/config/playwright/authentication-config.candidate.yaml b/config/playwright/authentication-config.candidate.yaml deleted file mode 100644 index 58742a3..0000000 --- a/config/playwright/authentication-config.candidate.yaml +++ /dev/null @@ -1,30 +0,0 @@ -# Owner-review candidate only. Not a kubectl resource and not activated. -# Cluster owner must merge with existing authenticators, validate natively and -# preserve existing admin/service-account authentication and rollback access. -apiVersion: apiserver.config.k8s.io/v1 -kind: AuthenticationConfiguration -jwt: - - issuer: - url: https://kc.coulomb.social - audiences: - - playwright-kubernetes - audienceMatchPolicy: MatchAny - claimValidationRules: - - expression: "claims.aud == 'playwright-kubernetes' || claims.aud == ['playwright-kubernetes']" - message: dedicated Playwright audience required - - expression: "has(claims.scope) && 'playwright:connect' in claims.scope.split(' ')" - message: Playwright access scope required - - expression: "claims.principal_type == 'human' || (claims.principal_type == 'service' && has(claims.roles) && 'coding-agent' in claims.roles)" - message: identified human or registered coding agent required - - expression: "claims.sub != '' && claims.exp > claims.iat && claims.exp - claims.iat <= 3600" - message: nonempty subject and bounded token lifetime required - claimMappings: - username: - expression: "'keycape:' + claims.principal_type + ':' + claims.sub" - groups: - expression: "['playwright-consumers']" - userValidationRules: - - expression: "!user.username.startsWith('system:')" - message: reserved Kubernetes identity prefix refused - - expression: "user.groups == ['playwright-consumers']" - message: only the Playwright consumer group may be mapped diff --git a/config/playwright/keycape-public-client.candidate.json b/config/playwright/keycape-public-client.candidate.json deleted file mode 100644 index 85bb789..0000000 --- a/config/playwright/keycape-public-client.candidate.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "clientId": "playwright-cli", - "audience": "playwright-kubernetes", - "displayName": "Playwright platform consumer CLI", - "redirectUris": ["http://127.0.0.1:18764/callback"], - "allowedScopes": ["openid", "profile", "groups", "playwright:connect"], - "grantTypes": ["authorization_code"], - "clientType": "public" -} diff --git a/config/playwright/railiance01-ca.pem b/config/playwright/railiance01-ca.pem deleted file mode 100644 index 11eadf5..0000000 --- a/config/playwright/railiance01-ca.pem +++ /dev/null @@ -1,10 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIBeDCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy -dmVyLWNhQDE3NzMxMDM2MjAwHhcNMjYwMzEwMDA0NzAwWhcNMzYwMzA3MDA0NzAw -WjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE3NzMxMDM2MjAwWTATBgcqhkjO -PQIBBggqhkjOPQMBBwNCAAT44CzyUUGic6xYSLOApGq8A61OcoP/v+nns47p3F1z -M89iyNSZqtIHRpg0BxiMtTcdwXKart6aPIC8/8PfpVyAo0IwQDAOBgNVHQ8BAf8E -BAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUG/vhGaKEp4uD2KaSPDTU -249sq2EwCgYIKoZIzj0EAwIDSQAwRgIhALUc7a0LcGfMiAI+auFTByXlL4hTH0Cr -DxsOiRhV9D0vAiEAjTVKYP/KFPe68cAL8S76mLKdKhd564XiVAeISh5NkG4= ------END CERTIFICATE----- diff --git a/credential-change-requests/CCR-2026-0004-railiance-backup-offsite-lane.yaml b/credential-change-requests/CCR-2026-0004-railiance-backup-offsite-lane.yaml index 68da1a1..69ca933 100644 --- a/credential-change-requests/CCR-2026-0004-railiance-backup-offsite-lane.yaml +++ b/credential-change-requests/CCR-2026-0004-railiance-backup-offsite-lane.yaml @@ -5,7 +5,7 @@ request_type: workload-kv-read title: Railiance offsite backup lane (Nextcloud WebDAV + age recovery) status: active created: '2026-07-07' -updated: '2026-10-07' +updated: '2026-09-05' requester: agent: grok reason: Move railiance-backup / forgejo-backup credentials from hardcoded script @@ -20,12 +20,6 @@ review: decision: approved comment: "Approved in chat \u2014 establish backup lane credentials in OpenBao\ \ for workstation forgejo-backup and railiance-backup tooling (Option A Nextcloud)." - - at: '2026-10-07' - reviewer: platform-operator (founder Bernd Worsch) - decision: approved - comment: Founder decision 73fff3d3-49fd-4535-9040-b2754a3e2024 approves removing - the retired bao.coulomb.social OIDC callback from allowed_redirect_uris and - one delegated re-apply. Nothing else changes. target: domain: financials tenant: railiance @@ -46,6 +40,7 @@ openbao: mount: netkingdom role: railiance-backup-workload-kv-read allowed_redirect_uris: + - https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback - http://localhost:8250/oidc/callback - http://127.0.0.1:8250/oidc/callback oidc_scopes: @@ -77,13 +72,13 @@ risk: classification: high notes: - NC_WEBDAV_TOKEN grants upload to the offsite backup file drop. - - Since 2026-09-05 the file drop belongs to Backup, folder /railiance-backups, with - create-only permissions and a server-enforced 10 GiB account quota. - - Operator credentials remain in KVv2 operators/nextcloud/backup, fields BACKUP_USERNAME - and BACKUP_PASSWORD; never deliver them to production. - - Existing Bernd-owned retained backups and recovery access remain separate; predecessor - file-drop unshared 2026-09-15 by operator attestation; age-key exposure remains - open. + - Since 2026-09-05 the file drop belongs to Backup, folder /railiance-backups, + with create-only permissions and a server-enforced 10 GiB account quota. + - Operator credentials remain in KVv2 operators/nextcloud/backup, fields + BACKUP_USERNAME and BACKUP_PASSWORD; never deliver them to production. + - Existing Bernd-owned retained backups and recovery access remain separate; + predecessor file-drop unshared 2026-09-15 by operator attestation; age-key + exposure remains open. - "AGE_PRIVATE_KEY decrypts all age-encrypted backup artifacts \u2014 recovery escrow\ \ only." - Credentials must not be stored on production hosts with delete permission. @@ -122,20 +117,13 @@ verification: kind: capabilities_safe_verify result: passed details: - - 'WP-0026 T07: capabilities-safe re-verify on bao.coulomb.social (no secret values - read).' - - "Positive: token with policy workload-kv-read-railiance-backup-offsite-lane\ - \ \u2192 capabilities read on platform/data/workloads/railiance/backup/offsite-lane." - - "Negative: default-policy token \u2192 deny on the same data path; token revoked\ - \ after check." - - "Agent boundary: policy agent-high-risk-boundary \u2192 deny on data path, read\ - \ on metadata path." - - Field presence via JSON key inventory only (NC_WEBDAV_TOKEN, NC_WEBDAV_URL, - AGE_PRIVATE_KEY); lengths recorded, values not printed. - - 'EXPOSED taint set: custom_metadata exposed_at=2026-07-16T00:00:00Z exposed_version=2 - (WP-0026 T05).' - - "ops-warden catalog promoted draft\u2192active; fetch_command pinned to NC_WEBDAV_TOKEN;\ - \ resolvable=true; risk=high." + - 'WP-0026 T07: capabilities-safe re-verify on bao.coulomb.social (no secret values read).' + - 'Positive: token with policy workload-kv-read-railiance-backup-offsite-lane → capabilities read on platform/data/workloads/railiance/backup/offsite-lane.' + - 'Negative: default-policy token → deny on the same data path; token revoked after check.' + - 'Agent boundary: policy agent-high-risk-boundary → deny on data path, read on metadata path.' + - 'Field presence via JSON key inventory only (NC_WEBDAV_TOKEN, NC_WEBDAV_URL, AGE_PRIVATE_KEY); lengths recorded, values not printed.' + - 'EXPOSED taint set: custom_metadata exposed_at=2026-07-16T00:00:00Z exposed_version=2 (WP-0026 T05).' + - 'ops-warden catalog promoted draft→active; fetch_command pinned to NC_WEBDAV_TOKEN; resolvable=true; risk=high.' - at: '2026-09-05' actor: codex kind: dedicated_backup_account_cutover @@ -143,8 +131,8 @@ verification: details: - User authorized dedicated Backup account and operator custody coordinates. - Verified 10737418240-byte quota and Backup-owned create-only share. - - Encrypted fixture PUT 201, owner download and escrow decryption passed; runtime - GET and DELETE returned 405 on the actual upload endpoint. + - Encrypted fixture PUT 201, owner download and escrow decryption passed; + runtime GET and DELETE returned 405 on the actual upload endpoint. - CAS advanced workload KV version 2 to 3, preserving age escrow and other fields. - Evidence docs/evidence/RPF-WP-0029-backup-account-2026-09-05.json. - at: '2026-09-15' @@ -156,15 +144,7 @@ verification: - No predecessor value, fingerprint, length or shape was recorded. - HTTP 401/403 probe not run; predecessor must not be reconstructed. - Evidence docs/evidence/2026-09-15-rpf-wp-0029-predecessor-share-invalidated.json. - - at: '2026-10-07T13:10:39+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-railiance-backup-offsite-lane' - - 'Auth role metadata write: auth/netkingdom/role/railiance-backup-workload-kv-read' - - No secret values were read, written, printed, or accepted in argv. + lifecycle: deactivate: Disable ops-warden catalog entry and detach OIDC role policy; rotate Nextcloud file-drop token at provider. diff --git a/credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml b/credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml index 0bef3f0..64b2ec8 100644 --- a/credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml +++ b/credential-change-requests/CCR-2026-0019-secrets-engine-approval-client-read.yaml @@ -5,7 +5,7 @@ request_type: workload-kv-read title: secrets-engine client-side read of its approval client secret status: applied created: '2026-09-09' -updated: '2026-10-07' +updated: '2026-09-13' requester: agent: claude reason: RPF-WP-0035-T06 residual from the completed verifier custody (CCR-2026-0017). @@ -55,12 +55,6 @@ review: not a fabricated separate owner vote or approval-engine human decision. Live delivery verification remains pending and the credential front door remains non-resolvable. - - at: '2026-10-07' - reviewer: platform-operator, secrets-engine-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 73fff3d3-49fd-4535-9040-b2754a3e2024 approves removing - the retired bao.coulomb.social OIDC callback from allowed_redirect_uris and - one delegated re-apply. Nothing else changes. target: domain: financials tenant: platform @@ -81,6 +75,7 @@ openbao: mount: netkingdom role: secrets-engine-approval-client-workload-kv-read allowed_redirect_uris: + - https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback - http://localhost:8250/oidc/callback - http://127.0.0.1:8250/oidc/callback oidc_scopes: @@ -162,15 +157,6 @@ verification: - 'Policy metadata write: sys/policies/acl/workload-kv-read-secrets-engine-approval-client' - 'Auth role metadata write: auth/netkingdom/role/secrets-engine-approval-client-workload-kv-read' - No secret values were read, written, printed, or accepted in argv. - - at: '2026-10-07T13:10:43+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-secrets-engine-approval-client' - - 'Auth role metadata write: auth/netkingdom/role/secrets-engine-approval-client-workload-kv-read' - - No secret values were read, written, printed, or accepted in argv. lifecycle: deactivate: Detach the policy from role secrets-engine-approval-client-workload-kv-read and disable the ops-warden catalog entry. The KeyCape verifier lane and its delivery diff --git a/credential-change-requests/CCR-2026-0033.yaml b/credential-change-requests/CCR-2026-0033.yaml index 50ccf65..5319de9 100644 --- a/credential-change-requests/CCR-2026-0033.yaml +++ b/credential-change-requests/CCR-2026-0033.yaml @@ -3,62 +3,29 @@ kind: credential-change-request schema_version: 1 request_type: workload-kv-read title: KeyCape secrets-engine-openbao issuer workload reader and authoritative custody -status: applied +status: proposed created: '2026-10-04' -updated: '2026-10-07' +updated: '2026-10-06' requester: agent: key-cape reason: 'KEY-WP-0037-T02 owner packet; issuer-side read of the authoritative secrets-engine-openbao - client secret. Revised 2026-10-06 per founder direction (the secret lives only - in OpenBao) and ops-mason plan keycape-secrets-engine-openbao-client-custody (658cdbc, - MASON-WP-0009): consumer file and secrets-engine D1-D4 removed.' + client secret. Revised 2026-10-06 per founder direction (the secret lives only in OpenBao) + and ops-mason plan keycape-secrets-engine-openbao-client-custody (658cdbc, MASON-WP-0009): + consumer file and secrets-engine D1-D4 removed.' review: required: true required_approvers: - platform-owner - key-cape-owner - net-kingdom-owner - comments: - - at: '2026-10-07' - reviewer: platform-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06) - approves CCR-2026-0033 as platform-owner. Covers A1 (ops-mason design 658cdbc), - A2 (this CCR and CCR-2026-0034 at 4a284f0, bound claims of both readers confirmed) - and A3 (exactly one attended custody-child provision run, cas=0, KV version - 1). Does not approve the policy/role/ESO apply, the NetKingdom env patch, the - KeyCape registration, the restart or any rotation. - - at: '2026-10-07' - reviewer: key-cape-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06) - approves CCR-2026-0033 as key-cape-owner. Covers A1 (ops-mason design 658cdbc), - A2 (this CCR and CCR-2026-0034 at 4a284f0, bound claims of both readers confirmed) - and A3 (exactly one attended custody-child provision run, cas=0, KV version - 1). Does not approve the policy/role/ESO apply, the NetKingdom env patch, the - KeyCape registration, the restart or any rotation. - - at: '2026-10-07' - reviewer: net-kingdom-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06) - approves CCR-2026-0033 as net-kingdom-owner. Covers A1 (ops-mason design 658cdbc), - A2 (this CCR and CCR-2026-0034 at 4a284f0, bound claims of both readers confirmed) - and A3 (exactly one attended custody-child provision run, cas=0, KV version - 1). Does not approve the policy/role/ESO apply, the NetKingdom env patch, the - KeyCape registration, the restart or any rotation. - - at: '2026-10-07' - reviewer: platform-owner, key-cape-owner, net-kingdom-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 1fdd993b-8899-4f87-8a8e-636346bb9692 approves the token_self_lifecycle - amendment (lookup-self and revoke-self on the ESO reader's own token) and one - delegated re-apply of this CCR's policy and role. Nothing else changes. + comments: [] target: domain: infotech tenant: platform workload: keycape environment: production - purpose: Hold the secrets-engine-openbao client secret authoritatively in platform - KV and deliver it to the KeyCape issuer through sso-only ESO + purpose: Hold the secrets-engine-openbao client secret authoritatively in platform KV and + deliver it to the KeyCape issuer through sso-only ESO openbao: mount: platform kv_path: platform/workloads/keycape/secrets-engine-openbao @@ -67,7 +34,6 @@ openbao: policy_name: workload-kv-read-keycape-secrets-engine-openbao-eso policy_file: openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-eso.hcl metadata_read: false - token_self_lifecycle: true auth: method: kubernetes mount: kubernetes @@ -84,7 +50,7 @@ openbao: - external-secrets service_account_namespaces: - external-secrets - bound_claims_confirmed: true + bound_claims_confirmed: false kv_metadata: cas_required: true max_versions: 2 @@ -107,47 +73,27 @@ risk: classification: high notes: - Reads the whole path holding the secrets-engine-openbao confidential value - - Only ExternalSecrets in namespace sso may use the store; sso namespace writers - are within this boundary - - 'One value, two copies: KV (authoritative) and the sso Secret (issuer delivery). - The consumer holds no copy at rest; it reads in memory through CCR-2026-0034.' - - 'Must stay on kubernetes auth: a KeyCape-issued identity would make KeyCape startup - depend on KeyCape.' + - Only ExternalSecrets in namespace sso may use the store; sso namespace writers are within + this boundary + - 'One value, two copies: KV (authoritative) and the sso Secret (issuer delivery). The consumer + holds no copy at rest; it reads in memory through CCR-2026-0034.' + - 'Must stay on kubernetes auth: a KeyCape-issued identity would make KeyCape startup depend + on KeyCape.' verification: positive: - Owner-reviewed native exact identity and exact path proof required negative: - Owner-reviewed wrong identity, sibling GET and parent LIST denials required - - Metadata shows cas_required=true, max_versions=2 and the declared custom_metadata - (value-free). + - Metadata shows cas_required=true, max_versions=2 and the declared custom_metadata (value-free). activation_conditions: - Exact CCR approval, confirmed identity, attended custody and native receipt acceptance required - evidence: - - at: '2026-10-07T00:03:08+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-eso' - - 'Auth role metadata write: auth/kubernetes/role/external-secrets-keycape-secrets-engine-openbao' - - No secret values were read, written, printed, or accepted in argv. - - at: '2026-10-07T00:10:09+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-eso' - - 'Auth role metadata write: auth/kubernetes/role/external-secrets-keycape-secrets-engine-openbao' - - No secret values were read, written, printed, or accepted in argv. lifecycle: - deactivate: Owner-reviewed delivery withdrawal and token revocation; exact rollback - receipt required - rotate: 'Roll-forward only: custody child writes a new version with cas= - under a standing rotation approval for this CCR (event-driven + 90d); never a - read or restore of an old version' + deactivate: Owner-reviewed delivery withdrawal and token revocation; exact rollback receipt + required + rotate: 'Roll-forward only: custody child writes a new version with cas= under + a standing rotation approval for this CCR (event-driven + 90d); never a read or restore + of an old version' compromised: Owner incident process; no automatic read, reseed or cleanup scaffold: draft_only: true @@ -161,65 +107,64 @@ state_hub: related_workplan: KEY-WP-0037 delivery: surface: external-secrets - target: sso/keycape-secrets-engine-openbao-client key client-secret; issuer-side - delivery only + target: sso/keycape-secrets-engine-openbao-client key client-secret; issuer-side delivery + only review_bundle: docs/provisioning-windows/2026-10-04-1000/keycape - consumer_delivery: none at rest; attended operator OIDC reader CCR-2026-0034 reads - into secrets-engine process memory per command + consumer_delivery: none at rest; attended operator OIDC reader CCR-2026-0034 reads into + secrets-engine process memory per command boundary_decision: id: 632a8893-0d6a-4591-a50b-40bbdb798f32 - approves: custody boundary only (platform authoritative KV and least-privilege issuer - delivery; secrets-engine-owned mode-0600 consumer file outside every worktree) + approves: custody boundary only (platform authoritative KV and least-privilege issuer delivery; + secrets-engine-owned mode-0600 consumer file outside every worktree) does_not_approve: generation, KV write, fetch, delivery, registration or deployment - note: 'The design narrows this boundary: the secrets-engine consumer file it approved - is withdrawn (founder direction 2026-10-06).' -path_correction: 'Candidate path platform/services/keycape/secrets-engine-openbao - from the KeyCape packet is outside the platform-mount applier scope (WARDEN-WP-0028: - platform/workloads/ only). Corrected to platform/workloads/keycape/secrets-engine-openbao, - same field CLIENT_SECRET. The issuer binds to the sso Secret; the operator reader - (CCR-2026-0034) reads the exact data path.' + note: 'The design narrows this boundary: the secrets-engine consumer file it approved is + withdrawn (founder direction 2026-10-06).' +path_correction: 'Candidate path platform/services/keycape/secrets-engine-openbao from the + KeyCape packet is outside the platform-mount applier scope (WARDEN-WP-0028: platform/workloads/ + only). Corrected to platform/workloads/keycape/secrets-engine-openbao, same field CLIENT_SECRET. + The issuer binds to the sso Secret; the operator reader (CCR-2026-0034) reads the exact + data path.' custody_child: script: scripts/keycape_secrets_engine_openbao_custody.py tests: tests/test_keycape_secrets_engine_openbao_custody.py status: source-only; never executed requires: - - --approval-decision ; refused when absent, malformed, equal to - the boundary decision, or not recorded in an approved review comment of this CCR - - this CCR and sibling CCR-2026-0034 approved with platform-owner, key-cape-owner - and net-kingdom-owner approvals and bound_claims_confirmed true + - --approval-decision ; refused when absent, malformed, equal to the boundary + decision, or not recorded in an approved review comment of this CCR + - this CCR and sibling CCR-2026-0034 approved with platform-owner, key-cape-owner and net-kingdom-owner + approvals and bound_claims_confirmed true - attended warden platform-admin envelope; no BAO_TOKEN/VAULT_TOKEN in environment - umask 077, shell tracing off - - --rotate for any run after the first (cas=); without - it an existing entry is refused - generation: one secrets.token_urlsafe(48) value in process memory (64 chars); never - in argv, environment, stdout, stderr, logs, Git or the hub - authoritative_write: KV-v2 write with options.cas=0 (absent) or cas= (rotation, --rotate); value only in process memory and the bao stdin/HTTP - body; metadata-only preflight + - --rotate for any run after the first (cas=); without it an existing + entry is refused + generation: one secrets.token_urlsafe(48) value in process memory (64 chars); never in argv, + environment, stdout, stderr, logs, Git or the hub + authoritative_write: KV-v2 write with options.cas=0 (absent) or cas= + (rotation, --rotate); value only in process memory and the bao stdin/HTTP body; metadata-only + preflight receipt: O_EXCL 0600 JSON with booleans, KV version, request id, CAS, metadata applied/unchanged, CCR/decision ids and CCR digest only - failure_cleanup: nothing to clean locally; a written KV version is retained and - recorded; recovery is a fresh roll-forward run with --rotate (cas=), - never a KV read - metadata_write: kv metadata put of openbao.kv_metadata, compared before write against - a metadata-only read; skipped when equal + failure_cleanup: nothing to clean locally; a written KV version is retained and recorded; + recovery is a fresh roll-forward run with --rotate (cas=), never a KV read + metadata_write: kv metadata put of openbao.kv_metadata, compared before write against a + metadata-only read; skipped when equal rotation_revocation: generation_and_authoritative_version: railiance-platform custody child, version-guarded, roll-forward - issuer_pickup: 'key-cape / net-kingdom: ESO refresh then KeyCape restart immediately - (exchanges fail closed 401 between KV write and restart)' - consumer_pickup: 'automatic: the next secrets-engine command reads the latest version - in memory (CCR-2026-0034)' - disable_on_compromise: key-cape / net-kingdom remove registration first; platform - rotates and detaches the CCR-2026-0034 role policy + issuer_pickup: 'key-cape / net-kingdom: ESO refresh then KeyCape restart immediately (exchanges + fail closed 401 between KV write and restart)' + consumer_pickup: 'automatic: the next secrets-engine command reads the latest version in + memory (CCR-2026-0034)' + disable_on_compromise: key-cape / net-kingdom remove registration first; platform rotates + and detaches the CCR-2026-0034 role policy retire_versions: railiance-platform per separate destroy approval live_order: -- 0 founder approvals A1 design, A2 this CCR + CCR-2026-0034 (three reviews, bound - claims confirmed for both readers), A3 custody-child decision id +- 0 founder approvals A1 design, A2 this CCR + CCR-2026-0034 (three reviews, bound claims + confirmed for both readers), A3 custody-child decision id - 1 attended custody child (this repo) writes KV v1 and metadata; non-secret receipt -- 2 platform applies policies/roles (this CCR and CCR-2026-0034) and the sso-only - ClusterSecretStore + ExternalSecret; Secret sso/keycape-secrets-engine-openbao-client - exists (metadata receipt only) +- 2 platform applies policies/roles (this CCR and CCR-2026-0034) and the sso-only ClusterSecretStore + + ExternalSecret; Secret sso/keycape-secrets-engine-openbao-client exists (metadata receipt + only) - 3 net-kingdom env patch secrets-engine-openbao-client.proposed.patch.yaml (NK-WP-0046) - 4 KeyCape registration merge (KEY-WP-0037-T03) - 5 KeyCape restart immediately after step 4 @@ -227,19 +172,19 @@ live_order: - 7 platform OpenBao JWT role/policy secrets-engine-openbao and login proof (RPF-WP-0035-T02) rollback: before_step_3: delete the CCR-2026-0034 OIDC role, then its policy; delete ExternalSecret - (uid precondition), then the ESO role/policy; KV entry retained pending separate - destroy approval - after_step_3: net-kingdom rollout helper restores prior config/Deployment first, - then the before_step_3 sequence - cleanup_receipts: native, value-free, per step owner; a failed cleanup is recorded - as open evidence + (uid precondition), then the ESO role/policy; KV entry retained pending separate destroy + approval + after_step_3: net-kingdom rollout helper restores prior config/Deployment first, then the + before_step_3 sequence + cleanup_receipts: native, value-free, per step owner; a failed cleanup is recorded as open + evidence founder_confirmation_items: -- 'bound_claims_confirmed stays false until the founder confirms BOTH readers: this - CCR (kubernetes, external-secrets/external-secrets, audience openbao) and sibling - CCR-2026-0034 (OIDC, groups=net-kingdom-admins and /assurance/level=aal2).' -- 'Approvals: A1 ops-mason design; A2 this CCR plus CCR-2026-0034 with platform-owner, - key-cape-owner and net-kingdom-owner reviews; A3 the exact custody-child decision - id (must differ from boundary decision 632a8893-0d6a-4591-a50b-40bbdb798f32).' +- 'bound_claims_confirmed stays false until the founder confirms BOTH readers: this CCR (kubernetes, + external-secrets/external-secrets, audience openbao) and sibling CCR-2026-0034 (OIDC, groups=net-kingdom-admins + and /assurance/level=aal2).' +- 'Approvals: A1 ops-mason design; A2 this CCR plus CCR-2026-0034 with platform-owner, key-cape-owner + and net-kingdom-owner reviews; A3 the exact custody-child decision id (must differ from + boundary decision 632a8893-0d6a-4591-a50b-40bbdb798f32).' runbook: docs/provisioning-windows/2026-10-04-1000/keycape/founder-runbook.md related: sibling_ccr: CCR-2026-0034 diff --git a/credential-change-requests/CCR-2026-0034.yaml b/credential-change-requests/CCR-2026-0034.yaml index f20d722..3ed74d4 100644 --- a/credential-change-requests/CCR-2026-0034.yaml +++ b/credential-change-requests/CCR-2026-0034.yaml @@ -3,16 +3,16 @@ kind: credential-change-request schema_version: 1 request_type: workload-kv-read title: KeyCape secrets-engine-openbao attended operator reader (in-memory, per command) -status: applied +status: proposed created: '2026-10-06' -updated: '2026-10-07' +updated: '2026-10-06' requester: agent: railiance-platform - reason: Founder direction 2026-10-06 - the secrets-engine-openbao client secret - lives only in OpenBao; ops-mason plan keycape-secrets-engine-openbao-client-custody + reason: Founder direction 2026-10-06 - the secrets-engine-openbao client secret lives + only in OpenBao; ops-mason plan keycape-secrets-engine-openbao-client-custody (658cdbc, MASON-WP-0009) section 3.3. Sibling of CCR-2026-0033 (issuer ESO reader); - one auth role per CCR is this repo's convention (cf. CCR-2026-0019). Replaces - the superseded secrets-engine consumer file (SECRETS-WP-0012-T02 D1-D4). + one auth role per CCR is this repo's convention (cf. CCR-2026-0019). Replaces the + superseded secrets-engine consumer file (SECRETS-WP-0012-T02 D1-D4). review: required: true required_approvers: @@ -23,39 +23,12 @@ review: - at: '2026-10-06' reviewer: key-cape (owner packet, key-cape 7d96ee4) decision: claim_shape_reported - comment: KeyCape reports the groups claim is always emitted, so groups_claim groups - bound to net-kingdom-admins works as in CCR-2026-0019. MFA is not pinned on - the openbao-admin client; it holds only through the provider default requireForAll. - KeyCape asks that the role also bind the assurance claim (/assurance/level = - aal2, issued only after a verified factor). This is an owner report, not an - approval and not the founder confirmation of bound claims. - - at: '2026-10-07' - reviewer: platform-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06) - approves CCR-2026-0034 as platform-owner. Covers A2 for this operator OIDC reader - (groups=net-kingdom-admins and /assurance/level=aal2 confirmed). Applying the - role and policy needs its own go. - - at: '2026-10-07' - reviewer: key-cape-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06) - approves CCR-2026-0034 as key-cape-owner. Covers A2 for this operator OIDC reader - (groups=net-kingdom-admins and /assurance/level=aal2 confirmed). Applying the - role and policy needs its own go. - - at: '2026-10-07' - reviewer: net-kingdom-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 2ab717e1-7881-491e-aed4-28946550c289 (resolved 2026-10-06) - approves CCR-2026-0034 as net-kingdom-owner. Covers A2 for this operator OIDC - reader (groups=net-kingdom-admins and /assurance/level=aal2 confirmed). Applying - the role and policy needs its own go. - - at: '2026-10-07' - reviewer: platform-owner, key-cape-owner, net-kingdom-owner (founder Bernd Worsch) - decision: approved - comment: Founder decision 73fff3d3-49fd-4535-9040-b2754a3e2024 approves removing - the retired bao.coulomb.social OIDC callback from allowed_redirect_uris and - one delegated re-apply. Nothing else changes. + comment: 'KeyCape reports the groups claim is always emitted, so groups_claim groups + bound to net-kingdom-admins works as in CCR-2026-0019. MFA is not pinned on the + openbao-admin client; it holds only through the provider default requireForAll. + KeyCape asks that the role also bind the assurance claim (/assurance/level = aal2, + issued only after a verified factor). This is an owner report, not an approval + and not the founder confirmation of bound claims.' target: domain: infotech tenant: platform @@ -78,6 +51,7 @@ openbao: mount: netkingdom role: keycape-secrets-engine-openbao-operator-workload-kv-read allowed_redirect_uris: + - https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback - http://localhost:8250/oidc/callback - http://127.0.0.1:8250/oidc/callback oidc_scopes: @@ -92,7 +66,7 @@ openbao: groups: - net-kingdom-admins /assurance/level: aal2 - bound_claims_confirmed: true + bound_claims_confirmed: false policies: - workload-kv-read-keycape-secrets-engine-openbao-operator ttl: 5m @@ -104,8 +78,8 @@ openbao: founder_confirmation_items: - bound_claims_confirmed stays false until the founder confirms BOTH readers' bindings (this role's groups=net-kingdom-admins plus /assurance/level=aal2, and CCR-2026-0033's - external-secrets/external-secrets ServiceAccount). An unconfirmed binding blocks - the applier (applier-dry-run refuses). + external-secrets/external-secrets ServiceAccount). An unconfirmed binding blocks the + applier (applier-dry-run refuses). - The aal2 binding is the only MFA guarantee on this lane; the openbao-admin client itself does not pin MFA (KeyCape 7d96ee4). access_frontdoor: @@ -145,25 +119,6 @@ verification: - Founder approvals A1 (ops-mason design), A2 (this CCR and CCR-2026-0033 with three reviews and bound_claims_confirmed true) and a completed CCR-2026-0033 custody run (A3). - evidence: - - at: '2026-10-07T00:03:24+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-operator' - - 'Auth role metadata write: auth/netkingdom/role/keycape-secrets-engine-openbao-operator-workload-kv-read' - - No secret values were read, written, printed, or accepted in argv. - - at: '2026-10-07T13:10:47+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-secrets-engine-openbao-operator' - - 'Auth role metadata write: auth/netkingdom/role/keycape-secrets-engine-openbao-operator-workload-kv-read' - - No secret values were read, written, printed, or accepted in argv. lifecycle: deactivate: Delete the OIDC role first, then the policy; value-free receipt. rotate: Not applicable to the reader; the value rotates through the CCR-2026-0033 diff --git a/credential-change-requests/CCR-2026-0036-coulomb-social-backup-primary.yaml b/credential-change-requests/CCR-2026-0036-coulomb-social-backup-primary.yaml deleted file mode 100644 index 94fb048..0000000 --- a/credential-change-requests/CCR-2026-0036-coulomb-social-backup-primary.yaml +++ /dev/null @@ -1,211 +0,0 @@ -{ - "id": "CCR-2026-0036", - "kind": "credential-change-request", - "schema_version": 1, - "request_type": "workload-kv-read", - "title": "coulomb-social primary archive node-delivery reader", - "status": "applied", - "created": "2026-10-08", - "updated": "2026-10-27", - "requester": { - "agent": "codex", - "reason": "User asked to arrange platform credential delivery; preparation under RPF-WP-0053, no live grant inferred." - }, - "review": { - "required": true, - "required_approvers": [ - "platform-owner", - "coulomb-social-owner" - ], - "comments": [ - { - "at": "2026-10-08T12:40:00Z", - "reviewer": "User (platform-owner)", - "decision": "approved", - "comment": "Founder accepted this reader on 2026-10-08. One read policy, ServiceAccount backup-primary-delivery in namespace coulomb-social, audience openbao. Node delivery is not admitted.", - "source_ref": "founder-acceptance-2026-10-08" - }, - { - "at": "2026-10-08T12:40:00Z", - "reviewer": "User (coulomb-social-owner)", - "decision": "approved", - "comment": "Founder accepted the coulomb-social reader record. Node delivery of its pair was not requested.", - "source_ref": "founder-acceptance-2026-10-08" - }, - { - "at": "2026-10-27", - "reviewer": "User (platform-owner)", - "decision": "approved", - "comment": "User explicitly approved the scoped Coulomb-only node executor and governed pair delivery. Native reader/provider denials verified before delivery; independent recovery/retention and timer activation remain separately gated.", - "source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e" - }, - { - "at": "2026-10-27", - "reviewer": "User (coulomb-social-owner)", - "decision": "approved", - "comment": "User explicitly approved the scoped Coulomb-only node executor and governed pair delivery. Native reader/provider denials verified before delivery; independent recovery/retention and timer activation remain separately gated.", - "source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e" - } - ] - }, - "target": { - "domain": "communication", - "tenant": "platform", - "workload": "coulomb-social", - "environment": "production", - "purpose": "Read this application backup publisher key pair into the admitted node delivery process; no other workload credential or escrow." - }, - "openbao": { - "mount": "platform", - "kv_path": "platform/workloads/coulomb-social/backup-primary", - "fields": [ - "AWS_ACCESS_KEY_ID", - "AWS_SECRET_ACCESS_KEY" - ], - "policy_name": "workload-kv-read-coulomb-social-backup-primary", - "policy_file": "openbao/policies/workload-kv-read-coulomb-social-backup-primary.hcl", - "metadata_read": false, - "token_self_lifecycle": true, - "auth": { - "method": "kubernetes", - "mount": "kubernetes", - "role": "coulomb-social-backup-primary-workload-kv-read", - "audience": "openbao", - "ttl": "5m", - "token_max_ttl": "5m", - "token_explicit_max_ttl": "5m", - "token_no_default_policy": true, - "token_num_uses": 4, - "token_type": "service", - "policies": [ - "workload-kv-read-coulomb-social-backup-primary" - ], - "bound_claims": { - "service_account_names": [ - "backup-primary-delivery" - ], - "service_account_namespaces": [ - "coulomb-social" - ] - }, - "bound_claims_confirmed": true - } - }, - "access_frontdoor": { - "type": "platform-owner", - "catalog_id": "coulomb-social-backup-primary-delivery", - "readiness": "applied-pending-verify", - "resolvable": false - }, - "delivery": { - "surface": "node-private-file", - "host": "railiance01", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "target": "/etc/coulomb-social-backup/primary.env", - "owner": "root:root", - "mode": "0600", - "directory_mode": "0700", - "procedure": "scripts/application_backup_credentials.py", - "admission": { - "node_executor_approved": true, - "provider_scope_verified": true, - "reader_boundary_verified": true, - "source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e" - } - }, - "provider": { - "endpoint": "https://s3.nl-ams.scw.cloud", - "bucket": "railiance-platform-pg-backup", - "prefix": "platform-pg/application-archives/coulomb-social/", - "identity": "IAM application coulomb-social-backup-primary 681d00bf-b965-4323-b657-97bfde423511; key only in OpenBao; bucket policy limits the prefix and withholds object delete", - "retained_delete": false - }, - "risk": { - "classification": "high", - "notes": [ - "Admitted node root may request the dedicated ServiceAccount JWT; no RBAC grant or mounted token for this account. Namespace writers and node administrators are inside this credential boundary.", - "TokenRequest JWT and five-minute, four-use OpenBao token stay in process memory; only the provider pair is delivered at rest.", - "No worker receives IAM/bucket administration, native Barman access, sibling archive access or decryption identity.", - "Policy/role construction does not seed values or activate capture, timers, retention, deletion or a production restore." - ] - }, - "verification": { - "positive": [ - "Exact namespace/ServiceAccount/audience authenticates with this one policy, no default/identity policy, five-minute maximum TTL and four-use bound.", - "One own-path KV read followed by self-revocation yields only these two fields at a positive pinned KV version.", - "Node delivery produces root-owned 0600 file in 0700 directory; contained readback equality and metadata-only receipt; no secrets in outputs/argv.", - "Provider caller conditional multipart publication and version-pinned readback within exact archive prefix work." - ], - "negative": [ - "Wrong namespace, ServiceAccount and audience cannot log in.", - "Sibling application data GET, parent LIST, metadata GET, any KV write and platform admin endpoints are denied.", - "Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.", - "Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery." - ], - "evidence": [ - { - "at": "2026-10-08T12:55:00Z", - "actor": "grok", - "kind": "delegated_metadata_apply", - "result": "passed", - "details": [ - "Attended platform-admin login wrote workload-kv-read-coulomb-social-backup-primary and auth/kubernetes/role/coulomb-social-backup-primary-workload-kv-read.", - "Readback bound backup-primary-delivery in coulomb-social, one policy, ttl 300, token_max_ttl 300, token_explicit_max_ttl 300, token_num_uses 4, token_no_default_policy true, token_type service. Node delivery remains unadmitted." - ] - }, - { - "at": "2026-10-27", - "actor": "codex", - "kind": "native_reader_provider_boundary_proof", - "result": "passed", - "details": { - "completed_objects_created": false, - "credential_values_printed": false, - "kv_version": 2, - "negative_login_denials": { - "wrong_audience": 403, - "wrong_serviceaccount": 403 - }, - "own_multipart_create_abort": true, - "positive_reader_and_revoke": true, - "provider_denials": { - "bucket_policy": 403, - "delete": 403, - "iam_list": 403, - "native_prefix": 403, - "sibling_prefix": 403 - }, - "reader_denials": { - "admin": 403, - "list": 403, - "metadata": 403, - "sibling": 403, - "write_cas0_existing": 403 - }, - "schema": "coulomb.backup-boundary-proof.v1", - "status": "verified" - }, - "source_ref": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e" - } - ], - "activation_conditions": [ - "Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.", - "Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.", - "Pin the final reviewed CCR SHA256 and expected KV version; initialise designated root directory only after admission. Initial delivery may resume identical bytes but cannot rotate.", - "Independent age escrow and metadata receipt custody, retention/lifecycle/capacity, first capture/recovery and timer activation remain separate RPF-WP-0053 gates." - ] - }, - "lifecycle": { - "deactivate": "Owner detaches this Kubernetes role first, revokes tokens, and removes only admitted local delivery through an explicit withdrawal record. Do not delete retained archives.", - "rotate": "Separate owner-approved CAS rotation and atomic host replacement contract required. This helper refuses different existing bytes; retain old provider identity until new publication/recovery acceptance, then revoke predecessor at provider.", - "compromised": "Fence this worker and disable its provider identity first. Owner revokes reader tokens, withdraws reader, rotates pair and reviews retained-object access under incident authority; no automated archive deletion." - }, - "state_hub": { - "workplan_id": "RPF-WP-0053", - "task_id": "RPF-WP-0053-T02" - }, - "related": { - "consumer_workplan": "CSOC-WP-0008", - "design": "docs/credential-lane-designs/application-backup-delivery-2026-10-08.md" - } -} diff --git a/credential-change-requests/CCR-2026-0037-email-connect-backup-primary.yaml b/credential-change-requests/CCR-2026-0037-email-connect-backup-primary.yaml deleted file mode 100644 index 3ffde6e..0000000 --- a/credential-change-requests/CCR-2026-0037-email-connect-backup-primary.yaml +++ /dev/null @@ -1,173 +0,0 @@ -{ - "id": "CCR-2026-0037", - "kind": "credential-change-request", - "schema_version": 1, - "request_type": "workload-kv-read", - "title": "email-connect primary archive node-delivery reader", - "status": "applied", - "created": "2026-10-08", - "updated": "2026-10-08", - "requester": { - "agent": "codex", - "reason": "User asked to arrange platform credential delivery; preparation under RPF-WP-0053, no live grant inferred." - }, - "review": { - "required": true, - "required_approvers": [ - "platform-owner", - "email-connect-owner" - ], - "comments": [ - { - "at": "2026-10-08T12:40:00Z", - "reviewer": "User (platform-owner)", - "decision": "approved", - "comment": "Founder accepted this reader on 2026-10-08. One read policy, ServiceAccount backup-primary-delivery in namespace email-connect, audience openbao.", - "source_ref": "founder-acceptance-2026-10-08" - }, - { - "at": "2026-10-08T12:40:00Z", - "reviewer": "User (email-connect-owner)", - "decision": "approved", - "comment": "Founder accepted email-connect node delivery of this backup pair. Retention, escrow, capture and the timer stay separate.", - "source_ref": "founder-acceptance-2026-10-08" - } - ] - }, - "target": { - "domain": "communication", - "tenant": "platform", - "workload": "email-connect", - "environment": "production", - "purpose": "Read this application backup publisher key pair into the admitted node delivery process; no other workload credential or escrow." - }, - "openbao": { - "mount": "platform", - "kv_path": "platform/workloads/email-connect/backup-primary", - "fields": [ - "AWS_ACCESS_KEY_ID", - "AWS_SECRET_ACCESS_KEY" - ], - "policy_name": "workload-kv-read-email-connect-backup-primary", - "policy_file": "openbao/policies/workload-kv-read-email-connect-backup-primary.hcl", - "metadata_read": false, - "token_self_lifecycle": true, - "auth": { - "method": "kubernetes", - "mount": "kubernetes", - "role": "email-connect-backup-primary-workload-kv-read", - "audience": "openbao", - "ttl": "5m", - "token_max_ttl": "5m", - "token_explicit_max_ttl": "5m", - "token_no_default_policy": true, - "token_num_uses": 4, - "token_type": "service", - "policies": [ - "workload-kv-read-email-connect-backup-primary" - ], - "bound_claims": { - "service_account_names": [ - "backup-primary-delivery" - ], - "service_account_namespaces": [ - "email-connect" - ] - }, - "bound_claims_confirmed": true - } - }, - "access_frontdoor": { - "type": "platform-owner", - "catalog_id": "email-connect-backup-primary-delivery", - "readiness": "applied-pending-verify", - "resolvable": false - }, - "delivery": { - "surface": "node-private-file", - "host": "railiance01", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "target": "/etc/email-connect-backup/primary.env", - "owner": "root:root", - "mode": "0600", - "directory_mode": "0700", - "procedure": "scripts/application_backup_credentials.py", - "admission": { - "node_executor_approved": true, - "provider_scope_verified": true, - "reader_boundary_verified": true, - "source_ref": "backup-reader-boundary-2026-10-08" - } - }, - "provider": { - "endpoint": "https://s3.nl-ams.scw.cloud", - "bucket": "railiance-platform-pg-backup", - "prefix": "platform-pg/application-archives/email-connect/", - "identity": "IAM application email-connect-backup-primary 0eb0f958-77a4-4c70-9aa7-14ba46411415; key only in OpenBao; bucket policy limits the prefix and withholds object delete", - "retained_delete": false - }, - "risk": { - "classification": "high", - "notes": [ - "Admitted node root may request the dedicated ServiceAccount JWT; no RBAC grant or mounted token for this account. Namespace writers and node administrators are inside this credential boundary.", - "TokenRequest JWT and five-minute, four-use OpenBao token stay in process memory; only the provider pair is delivered at rest.", - "No worker receives IAM/bucket administration, native Barman access, sibling archive access or decryption identity.", - "Policy/role construction does not seed values or activate capture, timers, retention, deletion or a production restore." - ] - }, - "verification": { - "positive": [ - "Exact namespace/ServiceAccount/audience authenticates with this one policy, no default/identity policy, five-minute maximum TTL and four-use bound.", - "One own-path KV read followed by self-revocation yields only these two fields at a positive pinned KV version.", - "Node delivery produces root-owned 0600 file in 0700 directory; contained readback equality and metadata-only receipt; no secrets in outputs/argv.", - "Provider caller conditional multipart publication and version-pinned readback within exact archive prefix work." - ], - "negative": [ - "Wrong namespace, ServiceAccount and audience cannot log in.", - "Sibling application data GET, parent LIST, metadata GET, any KV write and platform admin endpoints are denied.", - "Provider denies sibling/native prefixes, retained object/version deletion and IAM/bucket administration.", - "Draft/unconfirmed/source-changed contract, wrong cluster, symlinks, hard links, insecure modes, changed KV version, unconfirmed token revocation and different existing file refuse delivery." - ], - "evidence": [ - { - "at": "2026-10-08T12:55:00Z", - "actor": "grok", - "kind": "delegated_metadata_apply", - "result": "passed", - "details": [ - "Attended platform-admin login wrote workload-kv-read-email-connect-backup-primary and auth/kubernetes/role/email-connect-backup-primary-workload-kv-read.", - "Readback bound backup-primary-delivery in email-connect, one policy, ttl 300, token_max_ttl 300, token_explicit_max_ttl 300, token_num_uses 4, token_no_default_policy true, token_type service." - ] - }, - { - "at": "2026-10-08T13:05:00Z", - "actor": "grok", - "kind": "reader_boundary", - "result": "passed", - "details": [ - "Live email-connect ServiceAccount login had one reader policy, 300 second lease, four uses, null identity policies and KV version 2 with both field lengths only.", - "Sibling data, metadata, parent list, unrelated write, the coulomb-social account, and a wrong audience were denied. The token was revoked. Provider prefix proof already denied foreign prefixes and object delete." - ] - } - ], - "activation_conditions": [ - "Named owner reviews and confirmed native reader bindings, applied policy/role with live positive/negative evidence, provider prefix boundary proof and explicit node execution admission.", - "Separate platform-owned attended pair provision with initial KV CAS=0, no partial/second single-field PUT; retain only version metadata. Current reader does not create or rotate values.", - "Pin the final reviewed CCR SHA256 and expected KV version; initialise designated root directory only after admission. Initial delivery may resume identical bytes but cannot rotate.", - "Independent age escrow and metadata receipt custody, retention/lifecycle/capacity, first capture/recovery and timer activation remain separate RPF-WP-0053 gates." - ] - }, - "lifecycle": { - "deactivate": "Owner detaches this Kubernetes role first, revokes tokens, and removes only admitted local delivery through an explicit withdrawal record. Do not delete retained archives.", - "rotate": "Separate owner-approved CAS rotation and atomic host replacement contract required. This helper refuses different existing bytes; retain old provider identity until new publication/recovery acceptance, then revoke predecessor at provider.", - "compromised": "Fence this worker and disable its provider identity first. Owner revokes reader tokens, withdraws reader, rotates pair and reviews retained-object access under incident authority; no automated archive deletion." - }, - "state_hub": { - "workplan_id": "RPF-WP-0053", - "task_id": "RPF-WP-0053-T05" - }, - "related": { - "consumer_workplan": "EMAIL-WP-0007", - "design": "docs/credential-lane-designs/application-backup-delivery-2026-10-08.md" - } -} diff --git a/credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml b/credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml deleted file mode 100644 index 812661b..0000000 --- a/credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml +++ /dev/null @@ -1,130 +0,0 @@ -id: CCR-2026-0038 -kind: credential-change-request -schema_version: 1 -request_type: service-jwt-login -title: secrets-engine-openbao KeyCape service-JWT login (isolated mount, login-only - policy) -status: applied -created: '2026-10-09' -updated: '2026-10-08' -requester: - agent: railiance-platform - reason: RPF-WP-0035-T02. KEY-WP-0037 finished 2026-10-07 (registration live, issuance - verified 10/10, key-cape e0b5fae); CCR-2026-0033/0034 applied. Design docs/credential-lane-designs/secrets-engine-service-jwt.md; - source bundle prepared 2026-09-27. ops-mason MASON-WP-0009-T05 waits on this lane - to recheck invariant (ii). -review: - required: true - required_approvers: - - platform-owner - - key-cape-owner - - secrets-engine-owner - comments: - - at: '2026-10-08T22:42:07+00:00' - reviewer: platform-owner, key-cape-owner, secrets-engine-owner (founder Bernd - Worsch) - decision: approved - comment: 'Approve service-JWT login lane: mount keycape-services, role secrets-engine-openbao, - policy secrets-engine-login-self (self endpoints + deny on the client-secret - path).' - - at: '2026-10-08T22:42:07+00:00' - reviewer: founder - decision: binding_confirmed - comment: 'Bound claims match KeyCape registration: aud secrets-engine-openbao, - sub service:secrets-engine, principal_type service, tenant tenant:platform, - roles secrets-engine, scope openbao:login; issuer https://kc.coulomb.social - RS256.' -target: - domain: infotech - tenant: platform - workload: secrets-engine - environment: production - purpose: Let secrets-engine log in to OpenBao with a KeyCape-issued service JWT - and receive a 5-minute, 8-use token that can only inspect and revoke itself. -openbao: - policy_name: secrets-engine-login-self - policy_file: openbao/policies/secrets-engine-login-self.hcl - guarded_paths: - - platform/data/workloads/keycape/secrets-engine-openbao - - platform/metadata/workloads/keycape/secrets-engine-openbao - auth: - method: jwt - mount: keycape-services - config_file: openbao/auth/keycape-services-config.json - role: secrets-engine-openbao - role_file: openbao/auth/secrets-engine-jwt-role.json - bound_claims_confirmed: true -founder_confirmation_items: -- bound_claims_confirmed stays false until the founder confirms the binding against - the KeyCape registration (aud secrets-engine-openbao, sub service:secrets-engine, - principal_type service, tenant tenant:platform, roles secrets-engine, scope openbao:login; - issuer https://kc.coulomb.social, RS256). applier-dry-run refuses until then. -- The isolated mount keycape-services is new (ops-mason survey 2026-10-07 found no - auth/jwt or keycape-services mount). The applier refuses if the path exists with - a type other than jwt. -access_frontdoor: - type: none - catalog_id: none - readiness: pending-review - resolvable: false -delivery: - surface: none - target: No value is delivered. Before the login proof, the founder may write a local, - non-secret probe input from the applied values (mount keycape-services, role - secrets-engine-openbao, bound_issuer https://kc.coulomb.social; secrets-engine - docs/keycape-openbao-consumer-custody.md, e94dc07). That file is probe input, - not the platform contract. railiance-platform publishes the consumer contract - file, or confirms the probe input as it, only after the login proof passes. -risk: - classification: medium - notes: - - Login identity only. The policy holds lookup-self, capabilities-self and revoke-self, - plus explicit deny on the client-secret data and metadata paths (MASON-WP-0009 - invariant (ii)); no KV read, policy/auth write, SecretID or child token. - - A JWT may be replayed while valid (15m); residual access is bounded by the 5m/8-use - OpenBao token. In an incident, delete the role, revoke issued tokens; do not disable - a shared mount. -verification: - positive: - - Readback after apply matches source for the policy text, mount type jwt, config - and role (done by applier-apply; prints nothing). - - A secrets-engine --auth service-jwt run (SECRETS-WP-0012-T03 probe-openbao) logs - in; the token's policies are exactly [secrets-engine-login-self]; revoke-self - succeeds. - negative: - - sys/capabilities-self for the service token returns deny on both guarded paths - and on sys/policies/acl/*, auth/*/role/*, auth/token/create. - - Wrong audience, subject, tenant, principal_type, role or scope, a coding-agent - JWT and an expired JWT are refused. - - A failed login never falls back to bootstrap/env providers. - activation_conditions: - - Founder approval of this CCR (three reviews) and bound_claims_confirmed true. - evidence: - - at: '2026-10-08T22:42:13+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/secrets-engine-login-self' - - 'JWT auth mount ensured: sys/auth/keycape-services (type jwt)' - - 'Auth config write: auth/keycape-services/config' - - 'Auth role metadata write: auth/keycape-services/role/secrets-engine-openbao' - - Readback matched source for policy, mount type, config and role. - - No secret values were read, written, printed, or accepted in argv. -lifecycle: - deactivate: Delete auth/keycape-services/role/secrets-engine-openbao, then the policy; - removing the exclusively owned empty mount is a later review. - rotate: Not applicable; the client secret rotates through the CCR-2026-0033 custody - child. - compromised: KeyCape disables the registration; platform deletes the role and revokes - issued tokens; tokens are 5m/8-use and self-revoked. -state_hub: - workplan_id: RPF-WP-0035 - task_id: RPF-WP-0035-T02 - related_workplan: KEY-WP-0037 -related: - design: docs/credential-lane-designs/secrets-engine-service-jwt.md - sibling_ccrs: - - CCR-2026-0033 - - CCR-2026-0034 diff --git a/credential-change-requests/CCR-2026-0039-fluid-telegram-hall-of-helix-operator-matrix.yaml b/credential-change-requests/CCR-2026-0039-fluid-telegram-hall-of-helix-operator-matrix.yaml deleted file mode 100644 index 3dcf286..0000000 --- a/credential-change-requests/CCR-2026-0039-fluid-telegram-hall-of-helix-operator-matrix.yaml +++ /dev/null @@ -1,138 +0,0 @@ -id: CCR-2026-0039 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-operator-matrix -title: fluid-telegram hall-of-helix attended operator KV lane (per-path matrix, OIDC - aal2) -status: applied -created: '2026-10-09' -updated: '2026-10-09' -requester: - agent: railiance-platform - reason: RPF-WP-0035-T03. Founder approved ops-mason plan fluid-telegram-operator-credential-lane - 2026-10-09 (approved_spec_sha256 41a0ca2746aa93b6ee9d1ca631517d680ade95c56e7a38b75a55209ef2316b73) - and ruled the tenant segment stays coulomb (ops-mason message b72ff77d). Design - docs/credential-lane-designs/fluid-telegram-operator-kv.md; key-cape confirmed - groups and aal2 claims (message b2ed890f, key-cape e0b5fae). -review: - required: true - required_approvers: - - platform-owner - - fluid-telegram-owner - - key-cape-owner - comments: - - at: '2026-10-09T10:29:55+00:00' - reviewer: platform-owner, fluid-telegram-owner, key-cape-owner (founder Bernd - Worsch) - decision: approved - comment: 'Approve ops-mason plan fluid-telegram-operator-credential-lane (spec - 41a0ca27): four-entry matrix at coulomb/fluid-telegram/hall-of-helix, OIDC role - on netkingdom, agent denies.' - - at: '2026-10-09T10:29:55+00:00' - reviewer: founder - decision: binding_confirmed - comment: groups=[fluid-telegram-operators] (LLDAP, member tegwick) and /assurance/level=aal2 - match key-cape b2ed890f. -target: - domain: infotech - tenant: coulomb - workload: fluid-telegram - environment: production - purpose: Let a member of fluid-telegram-operators with a verified second factor - run the attended provisioner for campaign hall-of-helix with exactly the four-entry - matrix. -openbao: - mount: platform - campaign_path: platform/workloads/coulomb/fluid-telegram/hall-of-helix - policy_name: workload-kv-fluid-telegram-hall-of-helix-operator - policy_file: openbao/policies/workload-kv-fluid-telegram-hall-of-helix-operator.hcl - boundary_overlay_file: openbao/fluid-telegram/agent-deny-overlay.hcl - matrix: - - entry: operator-app - fields: - - api_id - - api_hash - capabilities: - - read - - entry: operator-session - fields: - - session_b64 - capabilities: - - create - - read - - update - - entry: bot-token - fields: - - token - capabilities: - - create - - read - - update - - entry: redaction-salt - fields: - - salt - capabilities: - - create - - read - auth: - method: oidc - mount: netkingdom - role: fluid-telegram-hall-of-helix-workload-kv - role_file: openbao/auth/fluid-telegram-hall-of-helix-oidc-role.json - bound_claims_confirmed: true -founder_confirmation_items: -- bound_claims_confirmed stays false until the founder confirms groups=[fluid-telegram-operators] - (LLDAP group, single member tegwick) and /assurance/level=aal2 against the key-cape - confirmation b2ed890f. applier-dry-run refuses until then. -- The matrix grants no metadata, list, patch or delete. The salt is create-only by - ACL; first-write CAS 0 is the consumer's job (FT-WP-0002) and is verified by an - owner-run probe, not by ACL parameters. -- The apply appends exact coding-agent denies on all four data and metadata paths - to the live agent-high-risk-boundary policy, preserving its other content verbatim, - and refuses if only some of those denies are already present. -access_frontdoor: - type: none - catalog_id: none -risk: - level: high - notes: - - operator-session is a full Telegram account session. Membership of fluid-telegram-operators - is the whole access decision; adding a member widens this lane's reach. - - Tokens are 15m (30m maximum), no default policy, num_uses 0 so MTProto session - persistence is not interrupted; the provisioner self-revokes at completion. -verification: - positive: - - Readback after apply matches source for boundary denies, policy text and role - (done by applier-apply; prints nothing). - - An approved member's token reports exactly the matrix capabilities on each entry. - negative: - - Parent, sibling workload and sibling campaign paths report deny. - - A non-member, or a member without aal2, cannot obtain the role. - - Coding-agent identities report deny on all four data and metadata paths. - - Owner-run disposable probe: salt create succeeds, a second write is denied. - activation_conditions: - - Founder approval (three reviews) and bound_claims_confirmed true. - - Custody moved from the infotech prefix with scripts/fluid_telegram_custody_move.py. - evidence: - - at: '2026-10-09T10:30:01+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Agent boundary denies ensured: sys/policies/acl/agent-high-risk-boundary (8 - exact paths)' - - 'Policy metadata write: sys/policies/acl/workload-kv-fluid-telegram-hall-of-helix-operator' - - 'OIDC role metadata write: auth/netkingdom/role/fluid-telegram-hall-of-helix-workload-kv' - - Readback matched source for boundary denies, policy and role. - - No secret values were read, written, printed, or accepted in argv. -lifecycle: - deactivate: Delete auth/netkingdom/role/fluid-telegram-hall-of-helix-workload-kv - first, then the policy; KV entries stay in custody pending an explicit retention - decision. - rotate: Session compromise needs Telegram-side session revocation; bot-token rotation - needs provider replacement; salt rotation needs a data-owner decision. - compromised: Delete the role, revoke issued tokens, revoke the Telegram session. -state_hub: - workplan: RPF-WP-0035 - task: RPF-WP-0035-T03 diff --git a/credential-change-requests/CCR-2026-0040-keycape-client-secret-generate-grant.yaml b/credential-change-requests/CCR-2026-0040-keycape-client-secret-generate-grant.yaml deleted file mode 100644 index 2624966..0000000 --- a/credential-change-requests/CCR-2026-0040-keycape-client-secret-generate-grant.yaml +++ /dev/null @@ -1,105 +0,0 @@ -id: CCR-2026-0040 -kind: credential-change-request -schema_version: 1 -request_type: custody-generate-grant -title: Broker grant keycape/client-secret-generate (create-only, generate in custody) -status: applied -created: '2026-10-09' -updated: '2026-10-10' -requester: - agent: railiance-platform - reason: RPF-WP-0054-T04. net-kingdom SecurityCaseType oidc-browser-relying-party - step mint-secret (lane keycape-client-secret-generate); ops-warden d9f889b1, ops-mason - 789feb39. Founder chose the templated create-only grant 2026-10-09 (T02 option - A). -review: - required: true - required_approvers: - - platform-owner - - key-cape-owner - comments: - - at: '2026-10-09T17:18:53+00:00' - reviewer: platform-owner, key-cape-owner (founder Bernd Worsch) - decision: approved - comment: Approve create-only broker grant keycape/client-secret-generate (RPF-WP-0054-T02 - option A). -target: - domain: infotech - tenant: platform - workload: keycape - environment: production - purpose: Let the credential broker create a new KeyCape confidential-client secret - at platform/workloads/keycape/ once, without any value leaving custody. -openbao: - grant_id: keycape/client-secret-generate - kv_prefix: platform/workloads/keycape - reserved_entries: - - secrets-engine-openbao - policy_name: keycape-client-secret-generate - policy_file: openbao/policies/keycape-client-secret-generate.hcl - issuer_policy: credential-broker-keycape-client-secret-generate-issuer - issuer_policy_file: openbao/policies/credential-broker-keycape-client-secret-generate-issuer.hcl - token_role: keycape-client-secret-generate - token_ttl: 5m -founder_confirmation_items: -- This apply writes two policies and one token role. It binds the issuer policy to - no identity, so nothing can mint the token until a separate review binds it to the - caller named in RPF-WP-0054-T01. -- Create-only on keycape/+ means a token can create a new client entry once and can - never change an existing one. Its widest misuse is an unused entry for a client - that does not exist. -access_frontdoor: - type: none - catalog_id: none -risk: - level: medium - notes: - - No data read, no update, no metadata write, no list, no delete. Exact denies on - reserved entries take precedence over the + glob. - - The token lives at most 5m and only inside the broker process. -verification: - positive: - - Readback after apply matches source for both policies and the token role (done - by applier-apply; prints nothing). - - Disposable probe (T05) creates probe-secret-generate- at version 1. - negative: - - A second create on the probe path, a data read, update, delete and any path outside - keycape/ are denied for the grant token (sys/capabilities-self). - - The reserved entry keycape/secrets-engine-openbao reports deny on data and metadata. - activation_conditions: - - Founder approval (two reviews). - - Issuer binding reviewed after RPF-WP-0054-T01; probe passed (T05). - evidence: - - at: '2026-10-09T17:19:06+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/keycape-client-secret-generate' - - 'Issuer policy metadata write: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer' - - 'Token role metadata write: auth/token/roles/keycape-client-secret-generate' - - Readback matched source for both policies and the token role. - - Issuer policy is not bound to any identity by this apply. - - No secret values were read, written, printed, or accepted in argv. - - at: '2026-10-10T09:15:46+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/keycape-client-secret-generate' - - 'Issuer policy metadata write: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer' - - 'Token role metadata write: auth/token/roles/keycape-client-secret-generate' - - Readback matched source for both policies and the token role. - - Issuer policy is not bound to any identity by this apply. - - No secret values were read, written, printed, or accepted in argv. -lifecycle: - deactivate: Delete auth/token/roles/keycape-client-secret-generate, then both policies; - generated entries stay in custody with their cases. - rotate: Not applicable; rotation is a separate lane (RPF-WP-0054-T07). - compromised: Delete the token role and revoke outstanding tokens by accessor; review - metadata for entries without an accepted case. -state_hub: - workplan: RPF-WP-0054 - task: RPF-WP-0054-T04 diff --git a/credential-change-requests/CCR-2026-0041-forgejo-netkingdom-oidc-keycape-eso-reader.yaml b/credential-change-requests/CCR-2026-0041-forgejo-netkingdom-oidc-keycape-eso-reader.yaml deleted file mode 100644 index 2a4eb1c..0000000 --- a/credential-change-requests/CCR-2026-0041-forgejo-netkingdom-oidc-keycape-eso-reader.yaml +++ /dev/null @@ -1,111 +0,0 @@ -id: CCR-2026-0041 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: 'forgejo-netkingdom-oidc: keycape-reader-policy (oidc-browser-relying-party@1.0)' -status: applied -created: '2026-10-09' -updated: '2026-10-09' -requester: - agent: ops-mason - reason: 'MASON-WP-0010-T03 build of SecurityCase forgejo-netkingdom-oidc (oidc-browser-relying-party@1.0), - render sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23. - Accepted by owner, founder on 2026-10-08: key-cape docs/forgejo-oidc-client-request.yaml - (status accepted-2026-10-08, KEY-WP-0038-T01); type acknowledged via informed-decision - memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e. The - case acceptance replaces a fresh executive summary.' -review: - required: true - required_approvers: - - platform-owner - comments: - - at: '2026-10-09T18:09:10+00:00' - reviewer: platform-owner (founder Bernd Worsch) - decision: approved - comment: Approve ESO reader for SecurityCase forgejo-netkingdom-oidc (accepted - 2026-10-08). - - at: '2026-10-09T18:09:10+00:00' - reviewer: founder - decision: binding_confirmed - comment: Bound to external-secrets/external-secrets, mirrors CCR-2026-0033. -target: - domain: infotech - tenant: platform - workload: keycape - environment: production - purpose: KeyCape reads its client secret only through ESO. No metadata, list, parent - or sibling access. -openbao: - mount: platform - kv_path: platform/workloads/keycape/forgejo - fields: - - CLIENT_SECRET - policy_name: workload-kv-read-keycape-forgejo-eso - policy_file: openbao/policies/workload-kv-read-keycape-forgejo-eso.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: external-secrets-keycape-forgejo - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - workload-kv-read-keycape-forgejo-eso - bound_claims: - service_account_names: - - external-secrets - service_account_namespaces: - - external-secrets - bound_claims_confirmed: true -access_frontdoor: - type: external-secrets - catalog_id: forgejo-keycape-client - readiness: pending-review - resolvable: false -risk: - classification: high - notes: - - Rendered from an acknowledged SecurityCaseType; shape reviewed with the type, - names checked against live structure. - - Exact read on one KV path; no metadata, list, parent or sibling access. -verification: - positive: - - policy workload-kv-read-keycape-forgejo-eso grants read on platform/data/workloads/keycape/forgejo - only (capabilities check) - negative: - - a default-only identity has no read capability on the path - - sibling GET and parent LIST are denied - activation_conditions: - - case acceptance present - - bound claims confirmed by the platform owner - evidence: - - at: '2026-10-09T18:09:18+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-keycape-forgejo-eso' - - 'Auth role metadata write: auth/kubernetes/role/external-secrets-keycape-forgejo' - - No secret values were read, written, printed, or accepted in argv. -lifecycle: - deactivate: delete the role, then policy workload-kv-read-keycape-forgejo-eso (case - rollback) - rotate: roll-forward through the credential broker (mint-secret); never a read of - the old value - compromised: owner incident process; no automatic read, reseed or cleanup -security_case: - case: forgejo-netkingdom-oidc - type: oidc-browser-relying-party@1.0 - render_digest: sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23 - acceptance_record: key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, - KEY-WP-0038-T01); type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, - hub decision e418263e - accepted_at: '2026-10-08' - accepted_by: - - owner - - founder diff --git a/credential-change-requests/CCR-2026-0042-forgejo-netkingdom-oidc-consumer-reader.yaml b/credential-change-requests/CCR-2026-0042-forgejo-netkingdom-oidc-consumer-reader.yaml deleted file mode 100644 index a4a58d3..0000000 --- a/credential-change-requests/CCR-2026-0042-forgejo-netkingdom-oidc-consumer-reader.yaml +++ /dev/null @@ -1,81 +0,0 @@ -id: CCR-2026-0042 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: 'forgejo-netkingdom-oidc: consumer-reader-policy (oidc-browser-relying-party@1.0)' -status: in_flight -created: '2026-10-09' -updated: '2026-10-09' -requester: - agent: ops-mason - reason: 'MASON-WP-0010-T03 build of SecurityCase forgejo-netkingdom-oidc (oidc-browser-relying-party@1.0), render - sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23. Accepted by owner, founder on 2026-10-08: - key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01); type acknowledged - via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e. The case acceptance - replaces a fresh executive summary.' -review: - required: true - required_approvers: - - platform-owner - comments: [] -target: - domain: infotech - tenant: platform - workload: keycape - environment: production - purpose: The consuming service reads the same secret. No metadata, list, parent or sibling access. -openbao: - mount: platform - kv_path: platform/workloads/keycape/forgejo - fields: - - CLIENT_SECRET - policy_name: workload-kv-read-keycape-forgejo-forgejo - policy_file: openbao/policies/workload-kv-read-keycape-forgejo-forgejo.hcl - metadata_read: false - token_self_lifecycle: true -access_frontdoor: - type: none - catalog_id: forgejo-keycape-client - readiness: pending-review - resolvable: false -risk: - classification: high - notes: - - Rendered from an acknowledged SecurityCaseType; shape reviewed with the type, names checked against live structure. - - Exact read on one KV path; no metadata, list, parent or sibling access. -verification: - positive: - - policy workload-kv-read-keycape-forgejo-forgejo grants read on platform/data/workloads/keycape/forgejo only - (capabilities check) - negative: - - a default-only identity has no read capability on the path - - sibling GET and parent LIST are denied - activation_conditions: - - case acceptance present - - bound claims confirmed by the platform owner -lifecycle: - deactivate: delete the role, then policy workload-kv-read-keycape-forgejo-forgejo (case rollback) - rotate: roll-forward through the credential broker (mint-secret); never a read of the old value - compromised: owner incident process; no automatic read, reseed or cleanup -security_case: - case: forgejo-netkingdom-oidc - type: oidc-browser-relying-party@1.0 - render_digest: sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23 - acceptance_record: key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01); - type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e - accepted_at: '2026-10-08' - accepted_by: - - owner - - founder -in_flight: - missing_fields: - - openbao.auth - owner: railiance-apps - blocking_reason: >- - railiance-apps must choose and review the consumer delivery/auth identity. Native - forgejo-gitea and forgejo-runner both use forgejo/default; that shared identity - must not be filled as a dedicated consumer binding. RAPPS-WP-0016 intends ESO - delivery; current type1.1 templates only the KeyCape ESO role, so a consumer - role/delivery refinement needs explicit owner review. Historical type1.0 CCR - provenance remains unchanged; reconcile exact current1.1 structure before - application. See docs/evidence/2026-10-11-forgejo-first-case/owner-return.md. diff --git a/credential-change-requests/CCR-2026-0043-keycape-client-secret-generate-issuer-binding.yaml b/credential-change-requests/CCR-2026-0043-keycape-client-secret-generate-issuer-binding.yaml deleted file mode 100644 index a5ce649..0000000 --- a/credential-change-requests/CCR-2026-0043-keycape-client-secret-generate-issuer-binding.yaml +++ /dev/null @@ -1,106 +0,0 @@ -id: CCR-2026-0043 -kind: credential-change-request -schema_version: 1 -request_type: broker-issuer-binding -title: Operator OIDC role for the keycape-client-secret-generate broker issuer (net-kingdom-admins, - aal2) -status: applied -created: '2026-10-10' -updated: '2026-10-10' -requester: - agent: railiance-platform - reason: RPF-WP-0054-T05. ops-warden (23985370) names the caller as the human operator - who runs warden case run, using their own OIDC session. Founder chose net-kingdom-admins - + aal2 on 2026-10-10. The issuer policy is live from CCR-2026-0040. -review: - required: true - required_approvers: - - platform-owner - - key-cape-owner - comments: - - at: '2026-10-10T08:27:32+00:00' - reviewer: platform-owner, key-cape-owner (founder Bernd Worsch) - decision: approved - comment: 'Approve issuer binding for keycape-client-secret-generate: net-kingdom-admins - + aal2 (RPF-WP-0054).' - - at: '2026-10-10T08:27:32+00:00' - reviewer: founder - decision: binding_confirmed - comment: groups=[net-kingdom-admins] and /assurance/level=aal2, same binding as - CCR-2026-0034. -target: - domain: infotech - tenant: platform - workload: credential-broker - environment: production - purpose: Let a net-kingdom-admins member with a verified second factor log in for - 15 minutes with only the broker issuer policy, so credential.py generate can mint - the create-only keycape/client-secret-generate token. -openbao: - issuer_policy: credential-broker-keycape-client-secret-generate-issuer - auth: - method: oidc - mount: netkingdom - role: credential-broker-keycape-client-secret-generate - role_file: openbao/auth/credential-broker-keycape-client-secret-generate-oidc-role.json - bound_claims_confirmed: true -founder_confirmation_items: -- bound_claims_confirmed stays false until the founder confirms groups=[net-kingdom-admins] - and /assurance/level=aal2, the same binding as CCR-2026-0034 (key-cape b2ed890f). -- The role carries only the issuer policy. It cannot read or write KV; it can mint - keycape-client-secret-generate tokens through that token role, and look up and revoke - tokens by accessor. -- Loopback CLI callbacks only (no UI callback); 15m with unlimited uses inside that - lifetime (a limited-use token cannot create the child grant token), no default policy. -access_frontdoor: - type: none - catalog_id: none -risk: - level: medium - notes: - - Membership of net-kingdom-admins is the access decision; that group already holds - the KeyCape operator reader (CCR-2026-0034). - - Worst case within 15m is unused keycape/ entries; existing entries - cannot be overwritten (CCR-2026-0040 is create-only). -verification: - positive: - - Readback after apply matches source for the role (done by applier-apply; prints - nothing). - - A member with aal2 logs in with role credential-broker-keycape-client-secret-generate - and the token's policies are exactly [credential-broker-keycape-client-secret-generate-issuer]. - negative: - - A non-member or a member without aal2 cannot obtain the role. - - The issuer token is denied on platform/* and on token creation through any other - role. - activation_conditions: - - Founder approval (two reviews) and bound_claims_confirmed true. - - CCR-2026-0040 applied (done 2026-10-09). - evidence: - - at: '2026-10-10T08:27:38+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Issuer policy confirmed live and matching source: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer' - - 'OIDC role metadata write: auth/netkingdom/role/credential-broker-keycape-client-secret-generate' - - Readback matched source for the role. - - No secret values were read, written, printed, or accepted in argv. - - at: '2026-10-10T09:15:56+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Issuer policy confirmed live and matching source: sys/policies/acl/credential-broker-keycape-client-secret-generate-issuer' - - 'OIDC role metadata write: auth/netkingdom/role/credential-broker-keycape-client-secret-generate' - - Readback matched source for the role. - - No secret values were read, written, printed, or accepted in argv. -lifecycle: - deactivate: Delete auth/netkingdom/role/credential-broker-keycape-client-secret-generate. - rotate: Not applicable (no stored credential). - compromised: Delete the role, revoke issued tokens; review keycape/ metadata for - entries without an accepted case. -state_hub: - workplan: RPF-WP-0054 - task: RPF-WP-0054-T05 diff --git a/credential-change-requests/CCR-2026-0044-reuse-surface-hub-writer-caller.yaml b/credential-change-requests/CCR-2026-0044-reuse-surface-hub-writer-caller.yaml deleted file mode 100644 index fd9003d..0000000 --- a/credential-change-requests/CCR-2026-0044-reuse-surface-hub-writer-caller.yaml +++ /dev/null @@ -1,144 +0,0 @@ -id: CCR-2026-0044 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: Name the dedicated reuse-surface hub-writer caller -status: applied -created: '2026-10-14' -updated: '2026-10-14' -requester: - agent: ops-mason - reason: CUST-WP-0081 / State Hub message 08b254af. CCR-2026-0005 is active and does - not name a caller. Entity 4a1f663c-0621-0483-edd0-f852274f4b6b carries default, - operator-custody and platform-admin, so its token can reach the data path, the - metadata path and siblings. Do not attach workload-kv-read-reuse-surface-runtime - to that entity. Bind a new OIDC role to a different principal. No secret value - is rotated or read by this request. -review: - required: true - required_approvers: - - platform-operator - - reuse-surface-owner - comments: - - at: '2026-10-14T16:40:00Z' - reviewer: grok - decision: filed_proposed - comment: Filed from ops-mason fd2c5a5 (State Hub 3642bdc4, MASON-WP-0011). Status - stays proposed. bound_claims_confirmed stays false until key-cape confirms group - reuse-surface-hub-writers and a member entity other than 4a1f663c-0621-0483-edd0-f852274f4b6b. - The caller policy file is unchanged; apply, after platform-operator and reuse-surface-owner - approval, is what adds lookup-self and revoke-self. CCR-2026-0005 remains the - KV and ESO authority. No KV write and no attachment to the platform-admin entity. - - at: '2026-10-14T17:44:49+00:00' - reviewer: platform-operator, reuse-surface-owner (founder Bernd Worsch) - decision: approved - comment: Founder go-ahead 2026-10-14. Approves CCR-2026-0044 as platform-operator - and reuse-surface-owner. Group reuse-surface-hub-writers, member bernd.worsch-3, - second factor enrolled, entity f94b7c34-e151-30bb-b473-df3ccdbb9bf1. That entity - is not 4a1f663c-0621-0483-edd0-f852274f4b6b. No KV write. Do not attach the - policy to the admin entity. - - at: '2026-10-14T17:44:49+00:00' - reviewer: founder Bernd Worsch - decision: binding_confirmed - comment: Bound claims are groups=reuse-surface-hub-writers and /assurance/level=aal2. - Member is bernd.worsch-3, entity f94b7c34-e151-30bb-b473-df3ccdbb9bf1. The apply - checks that entity's policy names and stops if platform-admin or operator-custody - is present. -target: - domain: financials - tenant: reuse - workload: reuse-surface - environment: production - purpose: Attended read of the existing hub writer token by one dedicated caller. -openbao: - mount: platform - kv_path: platform/workloads/reuse/reuse-surface/runtime-secrets - fields: - - REUSE_SURFACE_TOKEN - - REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET - policy_name: workload-kv-read-reuse-surface-runtime - policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime.hcl - metadata_read: true - token_self_lifecycle: true - auth: - method: oidc - mount: netkingdom - role: reuse-surface-hub-writer-workload-kv-read - allowed_redirect_uris: - - http://localhost:8250/oidc/callback - - http://127.0.0.1:8250/oidc/callback - - http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback - oidc_scopes: - - openid - - profile - - email - - groups - user_claim: sub - groups_claim: groups - bound_claims_type: string - bound_claims: - groups: - - reuse-surface-hub-writers - /assurance/level: aal2 - bound_claims_confirmed: true - policies: - - workload-kv-read-reuse-surface-runtime - ttl: 15m - token_max_ttl: 30m - token_explicit_max_ttl: 30m - token_no_default_policy: true - token_type: service -access_frontdoor: - type: ops-warden - catalog_id: reuse-surface-hub-write-token - selector: reuse-surface federation hub write bearer token - command: After a passing member receipt, run warden access reuse-surface-hub-write-token - --exec --field REUSE_SURFACE_TOKEN -- with the unprinted lane - token. Do not use --fetch. - resolvable: false - readiness: pending-review -risk: - classification: high - notes: - - REUSE_SURFACE_TOKEN writes to the production federation hub. This request does - not read or rotate it. - - The platform-admin entity must not receive this policy. OpenBao merges entity - policies into every token for that entity. - - The ESO policy workload-kv-read-reuse-surface-runtime-eso stays on its Kubernetes - role. This role does not attach it. - - bound_claims_confirmed stays false until key-cape confirms group reuse-surface-hub-writers - and a member entity other than 4a1f663c-0621-0483-edd0-f852274f4b6b. - - Public bao.coulomb.social ingress is not a callback. Use the workstation tunnel. -verification: - positive: - - A member token's policy set is exactly workload-kv-read-reuse-surface-runtime. - - That token's capabilities are read on the exact data path and the exact metadata - path. - negative: - - The same token is deny on platform/data/workloads/reuse/reuse-surface, its metadata - parent, and platform/data/workloads/issue-core/issue-core/issue-core-runtime. - - A principal outside reuse-surface-hub-writers is refused by the role. - - Entity 4a1f663c-0621-0483-edd0-f852274f4b6b is not the member token's entity. - activation_conditions: - - platform-operator and reuse-surface-owner approve this caller binding. - - key-cape confirms the group and that the member entity has no platform-admin or - operator-custody identity policy. - - Founder applies policy and role through the delegated applier. No KV write. - - scripts/verify-reuse-surface-hub-writer.py member and non-member both pass. Receipts - stay mode 0600 and contain no token or field value. - evidence: - - at: '2026-10-14T17:47:43+00:00' - actor: founder - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as founder using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-reuse-surface-runtime' - - 'Auth role metadata write: auth/netkingdom/role/reuse-surface-hub-writer-workload-kv-read' - - No secret values were read, written, printed, or accepted in argv. -lifecycle: - deactivate: delete auth/netkingdom/role/reuse-surface-hub-writer-workload-kv-read. - Leave the KV path, the ESO role, and CCR-2026-0005 delivery in place. - rotate: unchanged from CCR-2026-0005. This request does not rotate field values. - compromised: detach the role, revoke outstanding tokens for the role, and follow - the CCR-2026-0005 compromise procedure if a field value was exposed. diff --git a/credential-change-requests/CCR-2026-0045-reuse-surface-hub-writer-capabilities-self.yaml b/credential-change-requests/CCR-2026-0045-reuse-surface-hub-writer-capabilities-self.yaml deleted file mode 100644 index 925d411..0000000 --- a/credential-change-requests/CCR-2026-0045-reuse-surface-hub-writer-capabilities-self.yaml +++ /dev/null @@ -1,158 +0,0 @@ -id: CCR-2026-0045 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: Let the reuse-surface hub-writer caller query its own capabilities -status: applied -created: '2026-10-14' -updated: '2026-10-15' -supersedes: CCR-2026-0044 -requester: - agent: ops-mason - reason: NK-WP-0051-T06 / MASON-WP-0012. The CCR-2026-0044 member proof called sys/capabilities - with a root or platform-admin token because the lane policy has no capabilities-self - grant. This request adds a generated sys/capabilities-self grant (update; the - endpoint is a POST and returns capability names only) so the lane token proves - its own reach. Policy, role, bound claims and token bounds are otherwise unchanged - from CCR-2026-0044. Do not attach workload-kv-read-reuse-surface-runtime to entity - 4a1f663c-0621-0483-edd0-f852274f4b6b. No secret value is read, written or rotated. -review: - required: true - required_approvers: - - platform-operator - - reuse-surface-owner - comments: - - at: '2026-10-14T21:00:00Z' - reviewer: claude - decision: filed_proposed - comment: Filed for NK-WP-0051-T06 (RPF-WP-0057). Repo policy file now carries - the generated sys/capabilities-self grant; the live policy is unchanged until - approval and a delegated apply. Bound claims are those confirmed under CCR-2026-0044 - (groups=reuse-surface-hub-writers, /assurance/level=aal2); this request does - not change them. - - at: '2026-10-15T08:55:59+00:00' - reviewer: Bernd Worsch (founder; platform-operator and reuse-surface-owner) - decision: approved - comment: 'Approved in the NetKingdom NK-WP-0051 attended session on 2026-10-15, - in response to the explicit request covering both approval roles, CCR-2026-0045 - capabilities-self metadata apply, and the prepared RPF-WP-0057 login-page deployment. - Negative fixture selected: tegwick; founder confirms MFA enrolled and railiance-admins - membership only.' -target: - domain: financials - tenant: reuse - workload: reuse-surface - environment: production - purpose: Attended read of the existing hub writer token by one dedicated caller. -openbao: - mount: platform - kv_path: platform/workloads/reuse/reuse-surface/runtime-secrets - fields: - - REUSE_SURFACE_TOKEN - - REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET - policy_name: workload-kv-read-reuse-surface-runtime - policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime.hcl - metadata_read: true - token_self_lifecycle: true - auth: - method: oidc - mount: netkingdom - role: reuse-surface-hub-writer-workload-kv-read - allowed_redirect_uris: - - http://localhost:8250/oidc/callback - - http://127.0.0.1:8250/oidc/callback - - http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback - oidc_scopes: - - openid - - profile - - email - - groups - user_claim: sub - groups_claim: groups - bound_claims_type: string - bound_claims: - groups: - - reuse-surface-hub-writers - /assurance/level: aal2 - bound_claims_confirmed: true - policies: - - workload-kv-read-reuse-surface-runtime - ttl: 15m - token_max_ttl: 30m - token_explicit_max_ttl: 30m - token_no_default_policy: true - token_type: service - token_capabilities_self: true -access_frontdoor: - type: ops-warden - catalog_id: reuse-surface-hub-write-token - selector: reuse-surface federation hub write bearer token - command: After a passing member receipt, run warden access reuse-surface-hub-write-token - --exec --field REUSE_SURFACE_TOKEN -- with the unprinted lane - token. Do not use --fetch. - resolvable: false - readiness: pending-review -risk: - classification: high - notes: - - sys/capabilities-self reports capability names for paths the caller names. It - returns no secret value and grants no access. - - The platform-admin entity must not receive this policy. OpenBao merges entity - policies into every token for that entity. - - The ESO policy workload-kv-read-reuse-surface-runtime-eso stays on its Kubernetes - role and is unchanged. - - Until this apply, the live lane policy differs from openbao/policies/workload-kv-read-reuse-surface-runtime.hcl - by this one stanza; CCR-2026-0044 member proofs stay on the earlier operator-token - path. -verification: - positive: - - A member token's policy set is exactly workload-kv-read-reuse-surface-runtime. - - That token's capabilities are read on the exact data path and the exact metadata - path. - - The member token can call sys/capabilities-self on the five proof paths without - any operator or root token. - negative: - - The same token is deny on platform/data/workloads/reuse/reuse-surface, its metadata - parent, and platform/data/workloads/issue-core/issue-core/issue-core-runtime. - - A principal outside reuse-surface-hub-writers is refused by the role. - - Entity 4a1f663c-0621-0483-edd0-f852274f4b6b is not the member token's entity. - activation_conditions: - - platform-operator and reuse-surface-owner approve this request. - - Founder applies the policy through the delegated applier. No KV write, no role - change beyond re-asserting the CCR-2026-0044 role. - - ops-mason scripts/verify-reuse-surface-hub-writer.py member passes using capabilities-self - only, and non-member records the OpenBao refusal verbatim. Receipts stay mode - 0600 with no token or field value. - evidence: - - at: '2026-10-15T17:13:16+00:00' - actor: Bernd Worsch (founder), via codex conduit - kind: delegated_metadata_apply - result: passed - details: - - Delegated metadata applier ran as Bernd Worsch (founder), via codex conduit - using local bao CLI ambient authority. - - 'Policy metadata write: sys/policies/acl/workload-kv-read-reuse-surface-runtime' - - 'Auth role metadata write: auth/netkingdom/role/reuse-surface-hub-writer-workload-kv-read' - - No secret values were read, written, printed, or accepted in argv. - - at: '2026-10-15T17:22:52+00:00' - actor: Bernd Worsch (founder), via codex conduit - kind: positive_verification - result: passed - details: - - Attended bernd.worsch-3 diagnosis aal2 and hub-writers group confirmed; exact - workload policy, intended reads, parent/sibling denial and self-revocation passed - using capabilities-self. Receipt docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-member.txt - - at: '2026-10-15T17:22:52+00:00' - actor: Bernd Worsch (founder), via codex conduit - kind: negative_verification - result: passed - details: - - 'Founder-selected tegwick diagnosis showed railiance-admins only and aal2 before - the workload role refused the groups bound claim. Native sanitized receipt names - same uid and preserves error: docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-non-member.txt' -lifecycle: - deactivate: Re-apply the CCR-2026-0044 policy body (drop the sys/capabilities-self - stanza). Leave the role, KV path and ESO lane in place. - rotate: unchanged from CCR-2026-0005. This request does not rotate field values. - compromised: detach the role, revoke outstanding tokens for the role, and follow - the CCR-2026-0005 compromise procedure if a field value was exposed. diff --git a/credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml b/credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml deleted file mode 100644 index 4a5a4dc..0000000 --- a/credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml +++ /dev/null @@ -1,155 +0,0 @@ -id: CCR-2026-0046 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: Reuse Surface exact seven private-source repository reader -status: approved -created: '2026-10-15' -updated: '2026-10-15' -requester: - agent: platform-reader-owner delegated preparation worker - reason: CUST-WP-0081 hosted63/70; founder metadata consent excludes credential provisioning. - Prepare separate seven-source reader. -review: - required: true - required_approvers: - - platform-operator - - railiance-forge-owner - - reuse-surface-owner - - railiance-apps-owner - comments: [] -target: - domain: infotech - tenant: reuse - workload: reuse-surface - environment: production - purpose: Native acquisition of seven owner-consented pinned capability indexes. - Entire private repository code becomes readable by this dedicated reader; publication - remains only approved metadata. -openbao: - mount: platform - kv_path: platform/workloads/reuse/reuse-surface/forgejo-source-read - fields: - - FORGEJO_SOURCE_TOKEN - - API_USER - - API_BASE_URL - - TOKEN_SCOPES - - GENERATED_AT - - CCR_ID - policy_name: workload-kv-read-reuse-surface-runtime-eso - policy_file: openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl - auth: - method: kubernetes - mount: kubernetes - role: reuse-surface-runtime-eso - bound_claims: - service_account_names: - - reuse-surface-eso - service_account_namespaces: - - reuse - bound_claims_confirmed: true - policies: - - workload-kv-read-reuse-surface-runtime-eso - ttl: 15m - audience: openbao -access_frontdoor: - type: external-secrets - catalog_id: reuse-surface-forgejo-source-read - readiness: pending-review - resolvable: false -delivery: - surface: external-secrets - bootstrap_command: warden access openbao-platform-admin-login --exec -- python3 - scripts/provision-reuse-surface-forgejo-source.py --approval - --receipt <0600-durable-receipt> - provisioning_script: scripts/provision-reuse-surface-forgejo-source.py - target: Existing namespace-limited store openbao-reuse. Apps owner adds only proposed - data item into ExternalSecret reuse/reuse-surface-runtime. Shared target reuse-surface-env - remains intact. - forgejo_identity: reuse-surface-source - forgejo_team: reuse-surface-source-readers - repositories: - - coulomb/rapp-openbao - - coulomb/rapp-policy-nexus - - coulomb/rapp-postgres - - coulomb/rapp-sbom-nexus - - coulomb/rapp-telemetry - - coulomb/rapp-tenant-engine - - coulomb/rapp-user-engine - token_scopes: - - read:repository - consumer_gate: Reader must not be delivered until reuse-surface owner supplies verified - image digest containing BOTH authenticated redirect rejection/HTTPS enforcement - AND runtime-controlled exact URL allowlist checked before credential lookup. Apps - owner must deploy REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST mapping the reader environment - to exactly fourteen consented pinned index/entry URLs and inventory existing authenticated - sources for migration. Current image and redirect-only fix are not approved. Native - owner helper must verify all seven pinned indexes and entry hashes after rollout. -risk: - classification: high - cost_estimate_eur: 0 - notes: - - Repository grants disclose entire source of these seven repositories to hub; PAT - cannot restrict access to index files. - - No all-repositories membership, unrelated private grants, admin privileges or - repository writes. - - Existing ESO role expands by one exact data-read path; writer OIDC caller remains - unchanged and denied new path. - - Current consumer accepts arbitrary authenticated source URLs and redirects, allowing - bearer disclosure. Delivery barred until full reviewed redirect/HTTPS and runtime-controlled - exact URL allowlist patch image plus config is verified. - - PAT has no server-side expiry; rotate quarterly and immediately upon incident. - - Bootstrap writes only exact new identity/team/PAT/KV. Policy apply, ESO delivery, - consumer rollout and registrations require their separate reviewed owner steps. - - R1 pagination defect preserved; R2 pages until empty with duplicate/count/bound - checks. Deployed-version live complete-grant inventory remains required; code - review is not live scope proof. - - Quarterly rotation is a proposed manual requirement, not deployed expiry enforcement; - due-date owner task/receipt and separate exact-token recovery approval must be - established before activation. -verification: - positive: - - All seven repositories private; exact team/code-read flags, one dedicated team - member, exact seven memberships, effective permissions read and exactly one read:repository - PAT. - - Each consented index digest matches from authenticated Forgejo read; final hosted - native owner helper verifies pinned entry hashes as well. - - OpenBao stdin CAS=0 field write readback matches without printing or persisting - value. - - ESO binding and namespace condition surveyed and confirmed; native fixed consumer - image verified before delivery; final hosted70/70 exactly once. - negative: - - Known excluded private rapp-core-hub reader request denied (403/404); administrative - endpoint and Actions secrets denied. - - No additional private effective repositories or teams; no write probing that could - itself cause a change. - - Scoped writer and default OpenBao identities denied new data path; ESO role denied - unrelated siblings and wrong SA/namespace/audience login refused. - - Missing/anonymous native authentication refused with no privileged fallback. - activation_conditions: - - Independent platform/Forgejo/reuse/apps owner reviews of exact artifacts and founder - CCR provisioning disposition recorded; no approval inferred from metadata consent. - - Confirm existing ESO Kubernetes binding before any delegated metadata apply. - - Consumer security patch image digest verified and apps rollout artifacts reviewed/approved - before delivery. - - Durable bounded bootstrap and positive/negative receipts pass; owner native fetch - proof passes all seven pinned sources. - - Separate scoped writer registration batch followed by fresh hosted70/70 completion - audit. - evidence: [] -lifecycle: - deactivate: Separate explicitly approved silent scripts/revoke-reuse-surface-forgejo-source.py - validates reviewed single PAT ID/name, admin PATCH only dedicated user to fresh - process-memory password, Basic-auth DELETE exact token, then reconciles complete - inventory absence. Admin PAT/Sudo alone cannot delete. KV/shared runtime Secret - preserved. Apps/platform owners remove only new ESO item/policy stanza through - their reviewed path; preserve writer/webhook/public registrations. Recovery helper - approval is separate from mint/bootstrap approval. - rotate: Attended owner revokes old identified PAT then governed bootstrap remints - and verifies; never overwrite differing/orphan KV through bootstrap. Explicit - scoped CAS update plan required for rotation. - compromised: Revoke all dedicated service-user PATs, remove delivery data item, - roll fixed consumer with owners, retain failed sanitized evidence; investigate - all seven private source exposure. Do not rotate unrelated credentials without - separate scope. -revision: 2 diff --git a/credential-change-requests/CCR-2026-0047-rapp-qonto-audit-sender.yaml b/credential-change-requests/CCR-2026-0047-rapp-qonto-audit-sender.yaml deleted file mode 100644 index 3b6c749..0000000 --- a/credential-change-requests/CCR-2026-0047-rapp-qonto-audit-sender.yaml +++ /dev/null @@ -1,133 +0,0 @@ -id: CCR-2026-0047 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: rapp-qonto collector exact audit sender custody and delivery -status: applied -created: '2026-10-15' -updated: '2026-10-15' -requester: - agent: codex - reason: NK-WP-0035 operational receiver admission requires actual Qonto delivery. This proposed new - grant requires explicit owner approval of the concrete packet; prior timing rollout approval does - not approve it. -review: - required: true - required_approvers: - - platform-operator - - audit-core-owner - - rapp-qonto-owner - - qonto-assistant-owner - comments: - - at: '2026-10-15T19:27:15.042944+00:00' - reviewer: user explicit session approval (platform-operator, audit-core-owner, rapp-qonto-owner, - qonto-assistant-owner) - decision: approved - comment: User selected "Approve packet and proceed" for the complete exact packet including CCR-2026-0047 - new bearer/ESO lane, compatible source/receiver upgrades and migrations, exact registry admission, - collector and one-hour observation. This approval applies to reviewed exact scopes; prerequisites, - attended custody and independent receipts remain required. -target: - domain: financials - tenant: friendly:binky - workload: rapp-qonto - environment: production - purpose: Sender rapp-qonto-collector, sole source qonto-assistant, sole tenant tenant:friendly:binky, - may_write=true, may_read=false, secret_policy=redact. Envelope admission load-bearing; preserve - the source declaration per-class distinction. One source heartbeat maps to audit-core.heartbeat - covering only audit.deny. Wait 2f=7200s, effective deadline3f=10800s. No bank calls or authority - changes. -openbao: - mount: platform - kv_path: platform/workloads/rapp-qonto/audit-sender - fields: - - AUDIT_TOKEN - - CUSTODY_REQUEST - policy_name: workload-kv-read-rapp-qonto-audit - policy_file: openbao/policies/workload-kv-read-rapp-qonto-audit.hcl - metadata_read: false - auth: - method: kubernetes - mount: kubernetes - role: external-secrets-rapp-qonto-audit - bound_claims: - service_account_names: - - external-secrets - service_account_namespaces: - - external-secrets - bound_claims_confirmed: true - policies: - - workload-kv-read-rapp-qonto-audit - ttl: 15m -access_frontdoor: - type: external-secrets - catalog_id: rapp-qonto-audit - readiness: pending-review - resolvable: false -delivery: - surface: external-secrets - target: ClusterSecretStore openbao-rapp-qonto-audit restricted to namespace rapp-qonto; ExternalSecret - and Secret rapp-qonto/rapp-qonto-audit-sender key bearer. Source manifests/rapp-qonto-audit-sender.yaml. - No namespace is created. -risk: - classification: high - notes: - - A bearer permits append only for exact collector sender/source/tenant. Own-source reconciliation - is available as receiver contract, cross-source and other read routes remain refused. - - The registry update reads existing sender credentials inside the attended platform process only. - No registry is delivered to a producer. - - OpenBao login TTL limits the ESO reader session, not the audit bearer. Revocation must remove the - sender token from the receiver registry and prove refusal. - - New paths extend the existing coding-agent deny boundary. No existing high-risk grant is widened. -verification: - positive: - - CAS=0 first provision; durable provenance-bound version1 resume without rotation; exact sender registry - CAS merge preserves all existing credentials/senders/other fields. - - ESO copies only AUDIT_TOKEN to the named bearer key, exact source/delivery readback, receiver registry - delivery and compatible reload. - - Actual capture, periodic real elapsed frame, per-instance reconciliation and independent observer - acceptance remain required after provisioning. - negative: - - Proposed or missing owner approval refuses before native calls; drifted custody, sender or source - hashes refuse. - - Reader cannot read registry, sibling custody, metadata or parent listing; wrong service account/namespace - refused; coding-agent boundary deny wins. - - Wrong sender source/tenant and non-own-source read routes refused. Audit bearer revocation is separate - from reader-session revoke. - activation_conditions: - - Explicit approval of this complete concrete packet from platform-operator, audit-core-owner, rapp-qonto-owner - and qonto-assistant-owner. - - Compatible immutable receiver release with cadence parser and per-instance reconciliation is deployed/ready - before credential reads. - - Use attended Warden platform-admin --exec envelope, no shell-exported credentials; keep nonsecret - independent receipt and self-revoke. - - Apply only reviewed exact policy/role/projection; retain custody on failure and resume explicitly; - no destructive rollback or blind rotation. - - No claim of operational completion until native positive/negative and independent observer receipts. - evidence: - - at: '2026-10-15T19:40:23.546597+00:00' - actor: codex via attended user platform-admin (root execution) - kind: cadence_receiver_custody_and_native_sender - result: passed - details: - - "Native custody receipt receiver-custody-live.json: protected registry CAS15\u219216, version1\ - \ new sender custody, Approval credentials and all other senders preserved; exact readers/negative\ - \ checks/revocation, ESO values and loaded receiver registry matched." - - Independent attended wrapper exit0/session self-revocation confirmed by root; initial rejected - OPENBAO_TOKEN-ambient attempt made no credential mutation and session self-revocation was confirmed. - Retry launch removed all three token aliases before OIDC. - - 'Native collector source/tenant admission: heartbeat1 and deny1 reconciled for one exact instance; - duplicate200, wrong-source400, wrong-tenant400, archive-read403, invalid-bearer401; pending0. - Original202 receipts persisted across actual collector pod restart.' - - Source receipts copied unchanged to docs/evidence/2026-10-15-cadence-receiver/. Status applied - only; elapsed-hour periodic observation, independent protected observer readback and complete - native matrix remain pending. -lifecycle: - deactivate: Stop only collector; remove only its bearer from receiver via CAS and prove refusal; detach - exact reader role and ESO projection; retain custody versions and evidence. - rotate: Separate reviewed overlap-first change; this helper only provisions version1 and resumes without - rotation. - compromised: Stop collector and revoke its exact receiver bearer first; preserve evidence and perform - separate reviewed rotation. -state_hub: - related_workplan: NK-WP-0035 diff --git a/credential-change-requests/CCR-2026-0048-net-kingdom-backup-publisher.yaml b/credential-change-requests/CCR-2026-0048-net-kingdom-backup-publisher.yaml deleted file mode 100644 index 35f4c9f..0000000 --- a/credential-change-requests/CCR-2026-0048-net-kingdom-backup-publisher.yaml +++ /dev/null @@ -1,174 +0,0 @@ -{ - "id": "CCR-2026-0048", - "kind": "credential-change-request", - "schema_version": 1, - "request_type": "workload-kv-read", - "title": "NetKingdom identity backup publisher delivery", - "status": "verified", - "created": "2026-10-16", - "updated": "2026-10-16", - "requester": { - "agent": "codex", - "reason": "Operator requested implementation of missing full identity backup coverage under NK-WP-0049." - }, - "review": { - "required": true, - "required_approvers": [ - "platform-owner", - "net-kingdom-owner", - "backup-share-owner" - ], - "comments": [ - { - "reviewer": "User (platform-owner)", - "decision": "approved", - "source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971", - "comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.", - "reviewed_at": "2026-10-15T23:51:21.372915+00:00" - }, - { - "reviewer": "User (net-kingdom-owner)", - "decision": "approved", - "source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971", - "comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.", - "reviewed_at": "2026-10-15T23:51:21.372938+00:00" - }, - { - "reviewer": "User (backup-share-owner)", - "decision": "approved", - "source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971", - "comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.", - "reviewed_at": "2026-10-15T23:51:21.372944+00:00" - } - ] - }, - "target": { - "domain": "infotech", - "tenant": "platform", - "workload": "net-kingdom-identity-backup", - "environment": "production", - "purpose": "publisher limited to new encrypted identity bundle folder; no age private key or Backup account password" - }, - "openbao": { - "mount": "platform", - "kv_path": "platform/workloads/net-kingdom/identity-backup/publisher", - "fields": [ - "NC_WEBDAV_TOKEN" - ], - "policy_name": "net-kingdom-backup-publisher-eso", - "policy_file": "openbao/policies/net-kingdom-backup-publisher-eso.hcl", - "metadata_read": false, - "token_self_lifecycle": true, - "auth": { - "method": "kubernetes", - "mount": "kubernetes", - "role": "net-kingdom-backup-publisher-eso", - "audience": "openbao", - "ttl": "15m", - "token_max_ttl": "15m", - "token_explicit_max_ttl": "15m", - "token_no_default_policy": true, - "policies": [ - "net-kingdom-backup-publisher-eso" - ], - "bound_claims": { - "service_account_names": [ - "net-kingdom-backup-publisher-eso" - ], - "service_account_namespaces": [ - "sso" - ] - }, - "bound_claims_confirmed": true - } - }, - "provider": { - "host": "nx4069.your-storageshare.de", - "account_owner": "Backup", - "folder": "/railiance-backups/net-kingdom-identity", - "share_type": 3, - "share_permissions": 21, - "label": "net-kingdom-identity-publisher", - "age_private_key_delivered": false, - "backup_account_password_delivered": false, - "provider_added_resharing_approved": true, - "scope_approval_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971" - }, - "delivery": { - "surface": "ESO-managed Secret", - "namespace": "sso", - "secret": "net-kingdom-backup-publisher", - "key": "token", - "service_account": "net-kingdom-backup-publisher-eso", - "reader": "owner-controlled root backup publisher process on railiance01; no agent credential delivery", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786" - }, - "risk": { - "classification": "high", - "notes": [ - "Secret-bearing capture is limited to the four reviewed identity services; source/cluster/PVC bindings and private staging are mandatory.", - "Publisher can read/create in only this folder; retention can read/delete in only this folder. Both also include explicitly owner-approved provider-added re-sharing, but neither grants update, other folders or account administration.", - "Compromise of the retention token can destroy backups in this folder; keep it separate from the publisher and retain independent custody/recovery points.", - "Node root and ESO administration are inside this credential boundary. Coding agents must be denied the new data/metadata paths.", - "KV initial CAS=0 only; preserve existing values and refuse drift. No rotation of service keys, age keys, operator credentials or existing shares.", - "Nextcloud adds permission16 to read-enabled public links. User approved exact normalized21/25 on2026-10-16; tokens remain confined to the encrypted backup folder. Original5/9 attempt failed before credential publication." - ] - }, - "verification": { - "positive": [ - "Exact share path/permissions and bounded KV/ESO role readback", - "Ciphertext create/readback/hash and exact runtime token delivery", - "Retention protects recovered point/newest/seven daily/two weekly and uses ETag-conditional deletion" - ], - "negative": [ - "Publisher overwrite/delete denied; retention create/overwrite denied", - "Sibling namespace/path/audience and KV write/metadata access denied", - "Unapproved or changed CCR/source, wrong cluster, resource drift and malformed archive refuse execution" - ], - "activation_conditions": [ - "Explicit platform, NetKingdom and Backup-share owner approval of this exact request and source packet.", - "Native exact namespace/ServiceAccount/audience verification and bounded role readback; no coding-agent access.", - "First real encrypted capture, off-host readback, independent-key recovery and cleanup before enabling timers.", - "Retention protects the independently recovered point and newest backup; no automatic deletion of pre-existing backups." - ], - "actual_native_receipt": { - "repository": "net-kingdom", - "path": "docs/evidence/2026-10-16-identity-backup-live/publisher-native-delivery-verified.json", - "source_ccr_sha256": "a7d5c356ce0c4df9de439a58ad3d36e1578f27cda327c9387e97cfc2e9dc7cdc", - "status": "reader_and_delivery_verified", - "kv_version": 1, - "wrong_service_account_denied": true, - "wrong_namespace_denied": true, - "wrong_audience_denied": true, - "bounded_policy_ttl": true, - "eso_equality_verified": true, - "temporary_reader_revoked": true - }, - "actual_archive_and_runtime_acceptance": { - "owner_repo": "net-kingdom", - "archive_recovery": "docs/evidence/2026-10-16-identity-backup-live/actual-full-recovery-review.json", - "activation": "docs/evidence/2026-10-16-identity-backup-live/activation-review.json", - "recurring_job": "docs/evidence/2026-10-16-identity-backup-live/recurring-job-review.json", - "independent_owner_retrieval_and_key_recovery_passed": true, - "systemd_job_result": "success", - "timers_enabled": true, - "retention_pinned_and_newest_protected": true, - "native_full_service_authentication_claimed": false - } - }, - "state_hub": { - "workplan_id": "NK-WP-0049", - "task_id": "NK-WP-0049-T03" - }, - "access_frontdoor": { - "type": "platform-owner", - "catalog_id": "net-kingdom-identity-backup-publisher", - "readiness": "pending-review", - "resolvable": false - }, - "lifecycle": { - "deactivate": "Stop the corresponding timer, withdraw only this reader and disable its dedicated share under owner control; retain archives and recovery keys.", - "rotate": "Separate owner-approved share replacement and KV CAS rotation, preserved old access until new readback and recovery pass; no silent replacement by this setup tool.", - "compromised": "Fence this executor and revoke its dedicated share/reader. Treat retention-token compromise as possible loss of this folder; preserve independent recovery points and rotate only under incident authority." - } -} diff --git a/credential-change-requests/CCR-2026-0049-net-kingdom-backup-retention.yaml b/credential-change-requests/CCR-2026-0049-net-kingdom-backup-retention.yaml deleted file mode 100644 index 255eef7..0000000 --- a/credential-change-requests/CCR-2026-0049-net-kingdom-backup-retention.yaml +++ /dev/null @@ -1,174 +0,0 @@ -{ - "id": "CCR-2026-0049", - "kind": "credential-change-request", - "schema_version": 1, - "request_type": "workload-kv-read", - "title": "NetKingdom identity backup retention delivery", - "status": "verified", - "created": "2026-10-16", - "updated": "2026-10-16", - "requester": { - "agent": "codex", - "reason": "Operator requested implementation of missing full identity backup coverage under NK-WP-0049." - }, - "review": { - "required": true, - "required_approvers": [ - "platform-owner", - "net-kingdom-owner", - "backup-share-owner" - ], - "comments": [ - { - "reviewer": "User (platform-owner)", - "decision": "approved", - "source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971", - "comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.", - "reviewed_at": "2026-10-15T23:51:21.374014+00:00" - }, - { - "reviewer": "User (net-kingdom-owner)", - "decision": "approved", - "source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971", - "comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.", - "reviewed_at": "2026-10-15T23:51:21.374033+00:00" - }, - { - "reviewer": "User (backup-share-owner)", - "decision": "approved", - "source_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971", - "comment": "Operator approved initial live packet and subsequently explicitly approved provider-added folder-only re-sharing (publisher21/retention25). Native admission and independent recovery gates remain.", - "reviewed_at": "2026-10-15T23:51:21.374039+00:00" - } - ] - }, - "target": { - "domain": "infotech", - "tenant": "platform", - "workload": "net-kingdom-identity-backup", - "environment": "production", - "purpose": "retention limited to new encrypted identity bundle folder; no age private key or Backup account password" - }, - "openbao": { - "mount": "platform", - "kv_path": "platform/workloads/net-kingdom/identity-backup/retention", - "fields": [ - "NC_WEBDAV_TOKEN" - ], - "policy_name": "net-kingdom-backup-retention-eso", - "policy_file": "openbao/policies/net-kingdom-backup-retention-eso.hcl", - "metadata_read": false, - "token_self_lifecycle": true, - "auth": { - "method": "kubernetes", - "mount": "kubernetes", - "role": "net-kingdom-backup-retention-eso", - "audience": "openbao", - "ttl": "15m", - "token_max_ttl": "15m", - "token_explicit_max_ttl": "15m", - "token_no_default_policy": true, - "policies": [ - "net-kingdom-backup-retention-eso" - ], - "bound_claims": { - "service_account_names": [ - "net-kingdom-backup-retention-eso" - ], - "service_account_namespaces": [ - "sso" - ] - }, - "bound_claims_confirmed": true - } - }, - "provider": { - "host": "nx4069.your-storageshare.de", - "account_owner": "Backup", - "folder": "/railiance-backups/net-kingdom-identity", - "share_type": 3, - "share_permissions": 25, - "label": "net-kingdom-identity-retention", - "age_private_key_delivered": false, - "backup_account_password_delivered": false, - "provider_added_resharing_approved": true, - "scope_approval_ref": "statehub:decision:5edd5203-fae7-461b-8775-5d22b7873971" - }, - "delivery": { - "surface": "ESO-managed Secret", - "namespace": "sso", - "secret": "net-kingdom-backup-retention", - "key": "token", - "service_account": "net-kingdom-backup-retention-eso", - "reader": "owner-controlled root backup retention process on railiance01; no agent credential delivery", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786" - }, - "risk": { - "classification": "high", - "notes": [ - "Secret-bearing capture is limited to the four reviewed identity services; source/cluster/PVC bindings and private staging are mandatory.", - "Publisher can read/create in only this folder; retention can read/delete in only this folder. Both also include explicitly owner-approved provider-added re-sharing, but neither grants update, other folders or account administration.", - "Compromise of the retention token can destroy backups in this folder; keep it separate from the publisher and retain independent custody/recovery points.", - "Node root and ESO administration are inside this credential boundary. Coding agents must be denied the new data/metadata paths.", - "KV initial CAS=0 only; preserve existing values and refuse drift. No rotation of service keys, age keys, operator credentials or existing shares.", - "Nextcloud adds permission16 to read-enabled public links. User approved exact normalized21/25 on2026-10-16; tokens remain confined to the encrypted backup folder. Original5/9 attempt failed before credential publication." - ] - }, - "verification": { - "positive": [ - "Exact share path/permissions and bounded KV/ESO role readback", - "Ciphertext create/readback/hash and exact runtime token delivery", - "Retention protects recovered point/newest/seven daily/two weekly and uses ETag-conditional deletion" - ], - "negative": [ - "Publisher overwrite/delete denied; retention create/overwrite denied", - "Sibling namespace/path/audience and KV write/metadata access denied", - "Unapproved or changed CCR/source, wrong cluster, resource drift and malformed archive refuse execution" - ], - "activation_conditions": [ - "Explicit platform, NetKingdom and Backup-share owner approval of this exact request and source packet.", - "Native exact namespace/ServiceAccount/audience verification and bounded role readback; no coding-agent access.", - "First real encrypted capture, off-host readback, independent-key recovery and cleanup before enabling timers.", - "Retention protects the independently recovered point and newest backup; no automatic deletion of pre-existing backups." - ], - "actual_native_receipt": { - "repository": "net-kingdom", - "path": "docs/evidence/2026-10-16-identity-backup-live/retention-native-delivery-verified.json", - "source_ccr_sha256": "d391018abbda0fbc1acec4b5a22c0f99ce65525de940431df376a7552b3746cf", - "status": "reader_and_delivery_verified", - "kv_version": 1, - "wrong_service_account_denied": true, - "wrong_namespace_denied": true, - "wrong_audience_denied": true, - "bounded_policy_ttl": true, - "eso_equality_verified": true, - "temporary_reader_revoked": true - }, - "actual_archive_and_runtime_acceptance": { - "owner_repo": "net-kingdom", - "archive_recovery": "docs/evidence/2026-10-16-identity-backup-live/actual-full-recovery-review.json", - "activation": "docs/evidence/2026-10-16-identity-backup-live/activation-review.json", - "recurring_job": "docs/evidence/2026-10-16-identity-backup-live/recurring-job-review.json", - "independent_owner_retrieval_and_key_recovery_passed": true, - "systemd_job_result": "success", - "timers_enabled": true, - "retention_pinned_and_newest_protected": true, - "native_full_service_authentication_claimed": false - } - }, - "state_hub": { - "workplan_id": "NK-WP-0049", - "task_id": "NK-WP-0049-T03" - }, - "access_frontdoor": { - "type": "platform-owner", - "catalog_id": "net-kingdom-identity-backup-retention", - "readiness": "pending-review", - "resolvable": false - }, - "lifecycle": { - "deactivate": "Stop the corresponding timer, withdraw only this reader and disable its dedicated share under owner control; retain archives and recovery keys.", - "rotate": "Separate owner-approved share replacement and KV CAS rotation, preserved old access until new readback and recovery pass; no silent replacement by this setup tool.", - "compromised": "Fence this executor and revoke its dedicated share/reader. Treat retention-token compromise as possible loss of this folder; preserve independent recovery points and rotate only under incident authority." - } -} diff --git a/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml b/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml deleted file mode 100644 index 7ea8138..0000000 --- a/credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml +++ /dev/null @@ -1,149 +0,0 @@ -id: CCR-2026-0050 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: Retained flex-auth lifecycle PDP Ed25519 seed ESO read lane -status: proposed -created: '2026-10-16' -updated: '2026-10-21' -requester: - agent: railiance-platform - reason: CUST-WP-0080 / RPF-WP-0035-T12 / FLEX-WP-0024-T06 source proposal. Exact - new custody/read scope requires founder review; no approval or native binding - is inferred. -review: - required: true - required_approvers: - - founder - - railiance-platform-owner - - access-engine-owner - - secrets-engine-owner - - cluster-runtime-storage-owner - comments: - - reviewer: railiance-platform-owner (agent railiance-platform, codex) - decision: approved - comment: "INTENT Secure Custody/Stable Interfaces/Operable and Observable; RPF-WP-0035-T12. Approved Platform source/plan review only for frozen P4 manifest sha256:9eec861838a8e9138f2c821cff3014998fbe74bb01447642bf50d44e8b3b7cb3 and R2 manifest sha256:5be7a7f438624dcb35b7dbc96e7c9e5a8e2d5d1b1966cb4338aa5aecbe3a8b59; own bounded review artifact docs/evidence/2026-10-16-flex-auth-native-plan-review.json; sanitized supplement sha256:f02a9b595d9246a358d19b37bdad8fde158b7f0c9657a6ff088874732511a32c. Exact read policy/role/ESO proposal and one-use CAS0 writer stay separate. Identified manual Applications and recovery gates are mandatory. No founder decision, native execution, confirmed bindings or completed admission assent; current proposed status unchanged." - - reviewer: secrets-engine-owner (agent secrets-engine, hub message e73c9ffc-8a6d-4a17-9604-84324c678dc0) - decision: approved - comment: "2026-10-21, consumer side only. Consumer has no KV read role and receives no seed; public trust is admitted through a founder ssh-keygen -Y signed trust file (secrets-engine ebd8e49, docs/founder-admission.md, SECRETS-WP-0014-T04, decision e4eee965). Conditions: (1) the public receipt (kid, Ed25519 public key as 43-char unpadded base64url, evaluator/mode/policy_package/policy_version/policy_package_digest) is emitted by the attended writer at creation time or derived from its public output, never from a later read of the KV document; (2) the receipt carries the KV version and source/image provenance hashes. Not founder approval, native execution or a binding." - - reviewer: access-engine-owner (agent access-engine, hub message 6529bacf-6065-4fb9-a9a2-a41b7ed8fa83) - decision: approved - comment: "2026-10-21, Access scope only (retained workload/release source, Helm values, candidate image 84c78e0 / sha256:09a83b27...981f, signer delivery/mount, process/outbox fencing/rollback). Reviewed CCR sha256:66b3ea12d3f3bb870fa0aa0e7598e32abdb9bf39efe1adcfac286407abeb98e4 (before the 2026-10-21 review-comment edits), policy sha256:b9f816da25c7770863a5fc7b3b873e9da90b087e71a9052e766bf0a187ff85ef, native plan sha256:22306d50e082fc35c5ff22c31e739a53c7584176969063f7907bc01d77e506e5, R2 manifest sha256:5be7a7f438624dcb35b7dbc96e7c9e5a8e2d5d1b1966cb4338aa5aecbe3a8b59. Notes: seed read once at startup so rotation stays a fenced restart; bound_claims_confirmed needs native readback before activation; consumer policy pin comes from access-engine docs/native-signer-public-pin.json and the public receipt is prepared with access-engine tools/prepare_signer_public_receipt.py (public fields only); keep writer receipt fields kid/public_key/public_fingerprint/kv_version/status/path/source_ccr_sha256 stable. Access rollout founder go is separate: decision 4fab263d-686b-4a06-9cea-8df619c87c03 (FLEX-WP-0024-T07). Not founder approval or native execution." - - reviewer: railiance-platform-owner (agent railiance-platform, claude) - decision: approved - comment: "2026-10-21 runbook review: docs/runbooks/flex-auth-envelope-signing-attended-creation.md prepared. Secrets condition 1 is met for kid/public key/fingerprint/kv_version/source/image hashes by deriving the consumer receipt from the writer receipt (no KV read); the evaluator/mode/policy_package/policy_version/policy_package_digest fields are not in the writer receipt and must come from Access's published pin. Review-scope digest no longer binds expires_at so owner reviews can precede the 120 s window. Pending: cluster-runtime-storage-owner (railiance-cluster, requested f8b4b229), founder. Nothing executed." -target: - domain: infotech - tenant: platform - workload: flex-auth-secrets-engine - environment: production - purpose: Deliver the Ed25519 seed only to the retained lifecycle PDP startup file. - No taskpilot enablement, caller policy changes or private consumer trust discovery. -openbao: - mount: platform - kv_path: platform/workloads/flex-auth/envelope-signing - fields: - - ED25519_PRIVATE_KEY - metadata_read: false - token_self_lifecycle: true - policy_name: workload-kv-read-flex-auth-envelope-signing - policy_file: openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl - auth: - method: kubernetes - mount: kubernetes - role: external-secrets-flex-auth-envelope-signing - bound_claims: - service_account_names: - - flex-auth-envelope-signing-eso - service_account_namespaces: - - flex-auth - bound_claims_confirmed: false - policies: - - workload-kv-read-flex-auth-envelope-signing - ttl: 15m - audience: openbao - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true -access_frontdoor: - type: external-secrets - catalog_id: flex-auth-decision-envelope-signing-key - selector: Retained flex-auth/flex-auth-secrets-engine signer only - readiness: pending-review - resolvable: false -delivery: - surface: external-secrets - target: Namespace flex-auth; dedicated SA flex-auth-envelope-signing-eso; SecretStore - openbao-flex-auth-envelope-signing; ExternalSecret/Secret flex-auth-secrets-engine-envelope-signing; - seed property only -> startup file /var/run/secrets/decision-envelope-signing/seed0440, - readOnly uid/gid/fsGroup65532; no envFrom - bootstrap_command: Attended writer is a distinct exact-source action proposal in - docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json; this - read CCR authorizes neither seed writes nor any execution by itself. -risk: - classification: high - notes: - - Possession of seed can forge lifecycle PDP envelopes; independent consumer public - trust admission and fixed policy pins remain required. - - 'KV read is document-granular: dedicated ESO identity can read all three fields; - only private field is projected. Signer and Secrets consumer have no KV read role.' - - No parent/list/metadata/write/delete capability on ESO role. No token/Secret read - or ExternalSecret mutation granted to signer/consumer. - - Dedicated role is new scope; founder approval required. Canonical local draft - rapp-flex-auth-secrets-engine declaration committed d52f811ed5ec0be27c9c367e71d476125b3228be - (tree d4fbad71bdc8ef870603526ccd1f53195d0322af) resolves locally. Remote publication, - placement and actual runtime/package join remain open; no missing-declaration waiver. - - Controlled outage fence stops every signer pod and disables autoscaling before - creation/rotation. Recreate single writer and retained PVC remain Access-owned - review gates. - - fields declares only projected ED25519_PRIVATE_KEY. The same KV document also - has public key/kid, and OpenBao data-read is not field-filtered; do not claim - field-level authorization. -verification: - positive: - - Exact SA/namespace/audience login; native exact data read; ESO Ready/SecretSynced; - safe metadata resourceVersion and admitted KV version correspondence. - - All retained signer replicas restart with admitted kid/source/image and seed/public - correspondence; signed non-effect lifecycle allow/deny with durable originals - and independent consumer signature verification. - negative: - - Wrong SA/namespace/audience login refused; sibling/list/metadata/write/delete - denied; signer/consumer TokenRequest and Secret/ExternalSecret mutation denied. - - Unknown/old kid, wrong policy, tamper, expired/unsigned envelope, wrong caller - and missing seed refuse; no taskpilot activation. - - Receipt write failure/lost backend response retained unknown; no blind retry/remint; - stale CAS cannot overwrite version. - activation_conditions: - - All five required approvers independently dispose the exact CCR and native writer/fence - proposal; founder exact new custody scope approved. - - Exact proposed candidate source Git revision 84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0 and immutable - OCI reference forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f - published by successful ordinary CI402; source OS-lock/64MiB/startup hardening present. - Access reviews exact workload/Helm image/mount/outbox source and actual stopped fence; - Platform reviews existing GitOps Application sync/freeze; cluster reviews runtime, - headroom and new PVC1Gi/local-path actual PV retention. Local draft package d52 - remains source2b/current05a, not candidate84 adoption; remote placement/runtime join - and native mounted-runtime acceptance remain open. Enablement only if separately - proposed ArgoCD installation/controller configuration changes; none granted here. - - Exact native bound claims verified without printing JWT/Secret; read grant policy - and role native readback match source. - - Protected child source pinned, silent attended OIDC context verified, target cluster/backend - pinned, fence verified and durable receipt custody installed. - - Consumer independently admits owner-authenticated public receipt and exact policy/image/trust - pins before protected acceptance. - evidence: [] -lifecycle: - deactivate: Fence all retained signer processes, stop consumers, disable delivery - role/ESO and online trust; retain private KV and audit receipts under explicit - owner retention until reviewed destruction. - rotate: Pre-admit successor public receipt offline; fence consumers/all signer replicas/no - HPA; CAS exact version with new in-memory seed; wait exact ESO version, restart - single writer, verify native allow/deny and old-key refusal before reopening. - compromised: Immediately remove old online trust and stop all signer processes; - forward-only CAS recovery with independently approved successor. Never restore - compromised seed; archive historical public keys for offline audit only. -state_hub: - workplan_id: RPF-WP-0035 - task_id: RPF-WP-0035-T14 - decision_id: 18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508 - decision_api_url: http://127.0.0.1:8000/decisions/18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508 diff --git a/credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml b/credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml deleted file mode 100644 index eee70d5..0000000 --- a/credential-change-requests/CCR-2026-0051-mas-tem-relay-smtp.yaml +++ /dev/null @@ -1,152 +0,0 @@ -id: CCR-2026-0051 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: Temporary MAS Scaleway TEM relay SMTP ESO read lane -status: applied -created: '2026-10-18' -updated: '2026-10-18' -requester: - agent: ops-mason - reason: ops-mason plans/mas-tem-relay-smtp.md (MASON-WP-0014, reviewed 2026-10-18T10:13:55Z) - for consumer mail-address-semantics MAS-WP-0003. Hub messages daa119e6, a77e5879 - (kv_path correction) and 049afa1f. No approval is implied by this filing. -review: - required: true - required_approvers: - - founder - - railiance-platform-owner - - mail-address-semantics-owner - comments: - - at: '2026-10-18T10:36:53+00:00' - reviewer: railiance-platform-owner (policy-as-code ccr-standard-lane v1) - decision: approved - comment: 'Standard lane: passes ccr-standard-lane v1 sha256:5481be201456739b9e01b14b0b11211923d7d838a2e71e2e417af038a05aec95. - Platform-owner source review only; founder approval and confirm-binding still - required.' - - at: '2026-10-18T10:42:00+00:00' - reviewer: founder (Bernd Worsch) - decision: approved - comment: 'Approved 2026-10-18 for ops-mason plans/mas-tem-relay-smtp.md (MASON-WP-0014): - build policy workload-kv-read-mas-tem-relay and kubernetes role external-secrets-mas-tem-relay - under ops-mason-construct. Temporary; teardown with namespace mas.' - - at: '2026-10-18T10:46:25+00:00' - reviewer: founder - decision: approved - comment: 'State Hub decision e633607e-ff3e-46ec-bb6f-143e8b44f1af: APPROVE: CCR-2026-0051 - path=platform/suppliers/scaleway/mas-tem-relay policy=workload-kv-read-mas-tem-relay - auth_role=auth/kubernetes/role/external-secrets-mas-tem-relay; rationale=temporary - MAS TEM relay lane, read-only, namespace mas, torn down with mas' - - at: '2026-10-18T10:58:26+00:00' - reviewer: railiance-platform-owner (claude, native evidence) - decision: binding_confirmed - comment: 'Native 2026-10-18: SA mas-tem-relay-eso exists in ns mas (consumer mail-address-semantics - deploy/railiance/manifests.yaml 2e9a074, automount false). SecretStore mas/openbao-mas-tem-relay - references mount kubernetes, role external-secrets-mas-tem-relay, SA mas-tem-relay-eso, - audience openbao, and validated Ready at 10:49:56Z, i.e. a successful login - with exactly the CCR bound claims. ESO reads the path; the 10:56:56Z failure - is consumer-side admission, not auth.' -target: - domain: infotech - tenant: platform - workload: mas-tem-relay - environment: production - purpose: Deliver Scaleway TEM SMTP relay username/password to namespace mas only. - Temporary lane; torn down with namespace mas. -openbao: - mount: platform - kv_path: platform/suppliers/scaleway/mas-tem-relay - fields: - - username - - password - metadata_read: false - token_self_lifecycle: true - policy_name: workload-kv-read-mas-tem-relay - policy_file: openbao/policies/workload-kv-read-mas-tem-relay.hcl - auth: - method: kubernetes - mount: kubernetes - role: external-secrets-mas-tem-relay - bound_claims: - service_account_names: - - mas-tem-relay-eso - service_account_namespaces: - - mas - bound_claims_confirmed: true - policies: - - workload-kv-read-mas-tem-relay - ttl: 15m - audience: openbao - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true -access_frontdoor: - type: external-secrets - catalog_id: mas-tem-relay-smtp - selector: Namespace mas, SA mas-tem-relay-eso only - readiness: pending-review - resolvable: false -delivery: - surface: external-secrets - target: Namespace mas; dedicated SA mas-tem-relay-eso; ExternalSecret projecting - username and password only. - bootstrap_command: Not applicable. KV value already written by the founder (v2); - this CCR creates no value and authorizes no value read. -risk: - classification: medium - notes: - - Supplier SMTP credential can send mail as the TEM sending domain; exposure is - limited to namespace mas. - - New read scope on a supplier path; founder approval required. No other lane reads - this path (ops-mason live survey 2026-10-18T10:13:55Z). - - Apply may be run by ops-mason under ops-mason-construct (MASON-IN-0005) once installed - (MASON-WP-0015-T02) and this CCR is approved; that grant covers workload-kv-read-* - policies and external-secrets-* roles only. -verification: - positive: - - Exact SA/namespace/audience login succeeds; ESO Ready/SecretSynced; metadata version - matches admitted KV version 2 without printing values. - negative: - - Wrong SA, namespace or audience login refused; sibling paths under platform/suppliers/scaleway, - list, metadata, write and delete denied. - activation_conditions: - - Founder, platform owner and consumer owner approve the exact CCR. - - Native bound claims confirmed (confirm-binding) and native policy/role readback - matches source. - evidence: - - at: '2026-10-18T10:44:23+00:00' - actor: ops-mason (claude-MASON-WP-0014-T03, ops-mason-construct) - kind: apply - result: success - details: - - '2026-10-18: policy workload-kv-read-mas-tem-relay written from openbao/policies/workload-kv-read-mas-tem-relay.hcl; - read back byte-equal' - - 'kubernetes role external-secrets-mas-tem-relay written: SA mas-tem-relay-eso, - ns mas, audience openbao, token_policies [workload-kv-read-mas-tem-relay], ttl/max/explicit_max - 15m, token_no_default_policy true; read back matches CCR' - - KV platform/suppliers/scaleway/mas-tem-relay present (v2, founder-written); - no value read - - 'pending: SA mas-tem-relay-eso not yet applied (bound_claims_confirmed false); - confirm-binding and ESO sync next' - - at: '2026-10-18T10:58:27+00:00' - actor: railiance-platform (claude) - kind: native-binding - result: success - details: - - SecretStoreValid 2026-10-18T10:49:56Z with the exact CCR SA/namespace/audience/role; - ESO provider read succeeded; ops-mason apply readback 102668f. - - 'Open: ExternalSecret mas/mas-relay refused by admission policy reject-secret-last-applied - (client-side apply annotation copied to target); fix sent to mail-address-semantics - 1a76a832. Negative checks (wrong SA/namespace/audience, sibling, list, metadata, - write) not yet run.' -lifecycle: - deactivate: Delete ExternalSecret and role external-secrets-mas-tem-relay, then - policy workload-kv-read-mas-tem-relay, when namespace mas is torn down. - rotate: Founder writes a new KV version via UI; ESO refresh picks it up; verify - metadata version only. - compromised: Revoke the SMTP credential at Scaleway, disable the role, rotate the - KV value, then re-enable after review. -state_hub: - decision_id: e633607e-ff3e-46ec-bb6f-143e8b44f1af - decision_api_url: http://127.0.0.1:8000/decisions/e633607e-ff3e-46ec-bb6f-143e8b44f1af - decision_resolved_at: '2026-10-18T10:46:17.864665Z' diff --git a/credential-change-requests/CCR-2026-0052-net-kingdom-identity-provisioner-bind.yaml b/credential-change-requests/CCR-2026-0052-net-kingdom-identity-provisioner-bind.yaml deleted file mode 100644 index 0f8b43b..0000000 --- a/credential-change-requests/CCR-2026-0052-net-kingdom-identity-provisioner-bind.yaml +++ /dev/null @@ -1,141 +0,0 @@ -id: CCR-2026-0052 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: NetKingdom identity-provisioner directory bind (svc-provisioner) delivery -status: verified -created: '2026-10-30' -updated: '2026-10-30' -requester: - agent: claude-code - reason: 'NK-WP-0056-T04 pilot: move the first LLDAP service bind out of the founder''s - personal SOPS custody into platform custody, so later rotations need neither a - decrypted custody copy nor a person handling the value. svc-provisioner is the - simplest consumer: its own Secret, two keys, no embedded config.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: - - at: '2026-10-30T22:04:42+00:00' - reviewer: User (platform-owner) - decision: approved - comment: 'Approved as proposed: read-only svc-provisioner lane, CAS=0 from live - value, ESO Merge of password only.' - - at: '2026-10-30T22:04:43+00:00' - reviewer: User (net-kingdom-owner) - decision: approved - comment: Approved as proposed for NK-WP-0056-T04 pilot. - - at: '2026-10-30T22:06:09+00:00' - reviewer: User (platform-owner) - decision: binding_confirmed - comment: 'Binding confirmed: ServiceAccount net-kingdom-svc-provisioner-bind-eso - in namespace sso only, audience openbao, as declared in net-kingdom sso-mfa/k8s/identity-provisioner/bind-eso.yaml.' -target: - domain: infotech - tenant: platform - workload: net-kingdom-identity-provisioner - environment: production - purpose: Hold the svc-provisioner LLDAP bind password in platform custody and project - only that value into the existing Secret sso/identity-provisioner-ldap; no ld-admin, - admin or other service bind -openbao: - mount: platform - kv_path: platform/workloads/net-kingdom/directory-binds/svc-provisioner - fields: - - LDAP_BIND_PASSWORD - policy_name: net-kingdom-svc-provisioner-bind-eso - policy_file: openbao/policies/net-kingdom-svc-provisioner-bind-eso.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: net-kingdom-svc-provisioner-bind-eso - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - net-kingdom-svc-provisioner-bind-eso - bound_claims: - service_account_names: - - net-kingdom-svc-provisioner-bind-eso - service_account_namespaces: - - sso - bound_claims_confirmed: true -access_frontdoor: - type: platform-owner - catalog_id: net-kingdom-svc-provisioner-bind - readiness: pending-review - resolvable: false -lifecycle: - deactivate: Delete the ExternalSecret (deletionPolicy Retain keeps the working Secret), - then withdraw the role and policy; the KV entry stays as custody until the founder's - copy is confirmed current. - rotate: Change the svc-provisioner password in LLDAP and write the new value as - the next KV version with CAS on the current version; ESO projects it and the provisioner - restarts. Until NK-WP-0056 executors exist this is founder-run. - compromised: Disable or reset svc-provisioner in LLDAP first (it is in lldap_admin), - then rotate as above and review directory changes made under that account; fence - the reader role if its boundary is in doubt. -delivery: - surface: ESO-managed Secret key (Merge into existing Secret) - namespace: sso - secret: identity-provisioner-ldap - key: password - service_account: net-kingdom-svc-provisioner-bind-eso - reader: identity-provisioner Deployment in sso (existing secretKeyRef, unchanged); - no agent credential delivery - manifest: 'net-kingdom: sso-mfa/k8s/identity-provisioner/bind-eso.yaml' - cluster_uid: a553c742-0115-43d4-99a4-a5ca56fe0786 -risk: - classification: medium - notes: - - 'svc-provisioner is a member of lldap_admin (founder decision 2026-10-28, exception - recorded in the Service Identity standard): a leak grants directory administration. - Treat the path as high-value even though the lane itself is a single-field read.' - - Initial KV write is CAS=0 with the value already live in the Secret and the founder's - custody; no rotation happens in this request. An existing different KV value refuses. - - 'ESO uses creationPolicy Merge so the existing Secret (created by apply-service-secrets.py) - keeps its username key and owner; only password is projected. deletionPolicy Retain: - removing the ExternalSecret leaves the working Secret in place.' - - Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary - before the first write. - - Node root, ESO administration and platform-admin remain inside this credential - boundary. -verification: - positive: - - Exact policy, role and bound claims read back; native reader login as the ESO - ServiceAccount gets only this policy with a lease of at most 15m - - KV value equals the live Secret password before ESO is applied (setup) and after - ESO sync (verify), compared in memory only - - ExternalSecret Ready/SecretSynced; identity-provisioner readiness (includes directory - login) stays Ready after a restart - negative: - - Reader cannot read metadata, a sibling path, the other directory-bind paths or - create tokens - - Wrong ServiceAccount, wrong audience and wrong namespace logins are denied - - Coding-agent boundary denies data and metadata of the path - activation_conditions: - - Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound - by the setup tool). - - Attended platform-admin (aal2) session for setup; the founder runs it, value read - from the decrypted custody copy without display. - - Policy, role and KV in place and equal to live before the ExternalSecret is applied. - - Positive and negative results recorded in value-free receipts. - evidence: - - at: '2026-10-30T22:23:46+00:00' - actor: founder (attended), claude-code guiding - kind: verifier_custody_and_rollout - result: passed - details: - - '2026-10-30: bind-lane.py setup (custody equal to live, coding agents fenced, - KV v1 CAS=0, policy+role) and verify (exact policy/TTL, wrong SA and audience - denied, metadata/sibling/token-create denied, KV equals delivered Secret, reader - revoked) passed; identity-provisioner rolled out Ready. First ESO apply refused - by reject-secret-last-applied, fixed with target.template.metadata {} (net-kingdom - 985cfc3). Evidence: net-kingdom docs/evidence/2026-10-30-nk56-svc-provisioner-bind/ - (87e57aa).' diff --git a/credential-change-requests/CCR-2026-0053-net-kingdom-authelia-bind.yaml b/credential-change-requests/CCR-2026-0053-net-kingdom-authelia-bind.yaml deleted file mode 100644 index 7001883..0000000 --- a/credential-change-requests/CCR-2026-0053-net-kingdom-authelia-bind.yaml +++ /dev/null @@ -1,141 +0,0 @@ -id: CCR-2026-0053 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: NetKingdom Authelia directory bind (svc-authelia) delivery -status: verified -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: claude-code - reason: 'NK-WP-0056-T04, second lane after the svc-provisioner pilot (CCR-2026-0052): - move the svc-authelia LLDAP bind out of the founder''s personal SOPS custody into - platform custody, so its rotation needs neither a decrypted custody copy nor a - person handling the value.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: - - at: '2026-10-31T09:03:46+00:00' - reviewer: platform-owner - decision: approved - comment: 'Approved as proposed: read-only svc-authelia lane, CAS=0 from live value, - ESO Merge of ldap_password only.' - - at: '2026-10-31T09:03:53+00:00' - reviewer: net-kingdom-owner - decision: approved - comment: Approved as proposed for NK-WP-0056-T04. - - at: '2026-10-31T09:04:03+00:00' - reviewer: platform-owner - decision: binding_confirmed - comment: 'Binding confirmed: ServiceAccount net-kingdom-svc-authelia-bind-eso - in namespace sso only, audience openbao, as declared in net-kingdom sso-mfa/k8s/authelia/bind-eso.yaml.' -target: - domain: infotech - tenant: platform - workload: net-kingdom-authelia - environment: production - purpose: Hold the svc-authelia LLDAP bind password in platform custody and project - only that value into the key ldap_password of the existing Secret sso/authelia-secrets; - no session, storage or OIDC key, no ld-admin, admin or other service bind -openbao: - mount: platform - kv_path: platform/workloads/net-kingdom/directory-binds/svc-authelia - fields: - - LDAP_BIND_PASSWORD - policy_name: net-kingdom-svc-authelia-bind-eso - policy_file: openbao/policies/net-kingdom-svc-authelia-bind-eso.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: net-kingdom-svc-authelia-bind-eso - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - net-kingdom-svc-authelia-bind-eso - bound_claims: - service_account_names: - - net-kingdom-svc-authelia-bind-eso - service_account_namespaces: - - sso - bound_claims_confirmed: true -access_frontdoor: - type: platform-owner - catalog_id: net-kingdom-svc-authelia-bind - readiness: pending-review - resolvable: false -lifecycle: - deactivate: Delete the ExternalSecret (deletionPolicy Retain keeps the working Secret), - then withdraw the role and policy; the KV entry stays as custody until the founder's - copy is confirmed current. - rotate: Change the svc-authelia password in LLDAP and write the new value as the - next KV version with CAS on the current version; ESO projects it and Authelia - is restarted, because it reads ldap_password only at start-up. Until NK-WP-0056 - executors exist this is founder-run. - compromised: Reset svc-authelia in LLDAP first (read-only bind, lldap_strict_readonly), - then rotate as above; fence the reader role if its boundary is in doubt. -delivery: - surface: ESO-managed Secret key (Merge into existing Secret) - namespace: sso - secret: authelia-secrets - key: ldap_password - service_account: net-kingdom-svc-authelia-bind-eso - reader: Authelia Deployment in sso (existing secret volume, file /run/secrets/authelia/ldap_password, - unchanged); no agent credential delivery - manifest: 'net-kingdom: sso-mfa/k8s/authelia/bind-eso.yaml' - cluster_uid: a553c742-0115-43d4-99a4-a5ca56fe0786 -risk: - classification: medium - notes: - - svc-authelia is a read-only directory bind (lldap_strict_readonly); a leak exposes - directory attributes (users, groups, emails), not write access. Authelia's users - filter excludes svc-* accounts from login. - - authelia-secrets also holds the session, storage-encryption and OIDC keys. ESO - Merge projects only ldap_password; the reader policy cannot reach any other path. - - Initial KV write is CAS=0 with the value already live in the Secret and the founder's - custody; no rotation happens in this request. An existing different KV value refuses. - - Setup also refuses unless the live authelia-config binds uid=svc-authelia, so - the projected value cannot belong to another account. - - Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary - before the first write. - - Node root, ESO administration and platform-admin remain inside this credential - boundary. -verification: - positive: - - Exact policy, role and bound claims read back; native reader login as the ESO - ServiceAccount gets only this policy with a lease of at most 15m - - KV value equals the live Secret ldap_password before ESO is applied (setup) and - after ESO sync (verify), compared in memory only - - ExternalSecret Ready/SecretSynced; Authelia stays Ready and a first-factor login - works after a restart - negative: - - Reader cannot read metadata, a sibling path, the other directory-bind paths or - create tokens - - Wrong ServiceAccount, wrong audience and wrong namespace logins are denied - - Coding-agent boundary denies data and metadata of the path - activation_conditions: - - Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound - by the setup tool). - - Attended platform-admin (aal2) session for setup; the founder runs it, value read - from the decrypted custody copy without display. - - Policy, role and KV in place and equal to live before the ExternalSecret is applied. - - Positive and negative results recorded in value-free receipts. - evidence: - - at: '2026-10-31T09:11:39+00:00' - actor: founder (attended), claude-code guiding - kind: verifier_custody_and_rollout - result: passed - details: - - '2026-10-31: bind-lane.py setup (live authelia-config binds svc-authelia, custody - equal to live, coding agents fenced, KV v1 CAS=0, policy+role) and verify (exact - policy/TTL, wrong SA and audience denied, metadata/sibling/token-create denied, - KV equals delivered ldap_password, reader revoked) passed; ExternalSecret Ready - on first apply; Authelia restarted and admin signed in with OTP. Evidence: net-kingdom - docs/evidence/2026-10-31-nk56-svc-authelia-bind/ (7f37e25).' diff --git a/credential-change-requests/CCR-2026-0054-net-kingdom-keycape-bind.yaml b/credential-change-requests/CCR-2026-0054-net-kingdom-keycape-bind.yaml deleted file mode 100644 index 30ac915..0000000 --- a/credential-change-requests/CCR-2026-0054-net-kingdom-keycape-bind.yaml +++ /dev/null @@ -1,148 +0,0 @@ -id: CCR-2026-0054 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: NetKingdom KeyCape directory bind (svc-keycape) delivery -status: verified -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: claude-code - reason: 'NK-WP-0056-T04, third lane (after CCR-2026-0052 svc-provisioner and CCR-2026-0053 - svc-authelia): move the svc-keycape LLDAP bind out of the founder''s personal - SOPS custody into platform custody. KeyCape reads it from KEYCAPE_LLDAP_BIND_PW - (key-cape KEY-WP-0046), so ESO owns a dedicated Secret instead of one field inside - keycape-config.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: - - at: '2026-10-31T09:36:13+00:00' - reviewer: platform-owner - decision: approved - comment: 'Approved as proposed: read-only svc-keycape lane, CAS=0 from live value, - ESO-owned dedicated Secret keycape-lldap-bind.' - - at: '2026-10-31T09:36:25+00:00' - reviewer: net-kingdom-owner - decision: approved - comment: Approved as proposed for NK-WP-0056-T04, including the KeyCape image - main-e2b5fa1. - - at: '2026-10-31T09:36:35+00:00' - reviewer: platform-owner - decision: binding_confirmed - comment: 'Binding confirmed: ServiceAccount net-kingdom-svc-keycape-bind-eso in - namespace sso only, audience openbao, as declared in net-kingdom sso-mfa/k8s/keycape/bind-eso.yaml.' -target: - domain: infotech - tenant: platform - workload: net-kingdom-keycape - environment: production - purpose: Hold the svc-keycape LLDAP bind password in platform custody and deliver - it as the dedicated Secret sso/keycape-lldap-bind (key password), read by KeyCape - as KEYCAPE_LLDAP_BIND_PW; no signing key, client or other keycape-config value, - no ld-admin, admin or other service bind -openbao: - mount: platform - kv_path: platform/workloads/net-kingdom/directory-binds/svc-keycape - fields: - - LDAP_BIND_PASSWORD - policy_name: net-kingdom-svc-keycape-bind-eso - policy_file: openbao/policies/net-kingdom-svc-keycape-bind-eso.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: net-kingdom-svc-keycape-bind-eso - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - net-kingdom-svc-keycape-bind-eso - bound_claims: - service_account_names: - - net-kingdom-svc-keycape-bind-eso - service_account_namespaces: - - sso - bound_claims_confirmed: true -access_frontdoor: - type: platform-owner - catalog_id: net-kingdom-svc-keycape-bind - readiness: pending-review - resolvable: false -lifecycle: - deactivate: Restore bindPW in keycape-config (directory-bind-rollout.py --operation - switch), remove the KEYCAPE_LLDAP_BIND_PW env from the Deployment, then delete - the ExternalSecret (deletionPolicy Retain keeps the Secret) and withdraw the role - and policy. - rotate: Change the svc-keycape password in LLDAP and write the new value as the - next KV version with CAS on the current version; ESO projects it and KeyCape is - restarted. Only after bindPW is cleared from keycape-config, because KeyCape refuses - differing values. Until NK-WP-0056 executors exist this is founder-run. - compromised: Reset svc-keycape in LLDAP first (read-only bind, lldap_strict_readonly), - then rotate as above; fence the reader role if its boundary is in doubt. -delivery: - surface: ESO-owned Secret (creationPolicy Owner, new dedicated Secret) - namespace: sso - secret: keycape-lldap-bind - key: password - service_account: net-kingdom-svc-keycape-bind-eso - reader: KeyCape Deployment in sso, env KEYCAPE_LLDAP_BIND_PW from secretKeyRef keycape-lldap-bind/password - (image with key-cape KEY-WP-0046); no agent credential delivery - manifest: 'net-kingdom: sso-mfa/k8s/keycape/bind-eso.yaml' - cluster_uid: a553c742-0115-43d4-99a4-a5ca56fe0786 -risk: - classification: medium - notes: - - svc-keycape is a read-only directory bind (lldap_strict_readonly); a leak exposes - directory attributes (users, groups, emails), not write access. - - keycape-config also holds the signing key and client configuration; this request - delivers none of it, and setup compares only lldap.bindDN/bindPW in memory. - - Initial KV write is CAS=0 with the value already live in keycape-config and the - founder's custody; no rotation happens in this request. An existing different - KV value refuses. - - Setup refuses unless the live config binds uid=svc-keycape. KeyCape refuses to - start if KEYCAPE_LLDAP_BIND_PW and bindPW differ, so a wrong delivery cannot be - used silently. - - Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary - before the first write. - - Node root, ESO administration and platform-admin remain inside this credential - boundary. -verification: - positive: - - Exact policy, role and bound claims read back; native reader login as the ESO - ServiceAccount gets only this policy with a lease of at most 15m - - KV value equals the live keycape-config bindPW before ESO is applied (setup), - and the delivered Secret (and bindPW while present) after ESO sync (verify), compared - in memory only - - ExternalSecret Ready/SecretSynced; KeyCape with the env rolls out Ready and a - portal login works, again after bindPW is cleared from keycape-config - negative: - - Reader cannot read metadata, a sibling path, the other directory-bind paths or - create tokens - - Wrong ServiceAccount, wrong audience and wrong namespace logins are denied - - Coding-agent boundary denies data and metadata of the path - activation_conditions: - - Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound - by the setup tool). - - Attended platform-admin (aal2) session for setup; the founder runs it, value read - from the decrypted custody copy without display. - - Policy, role and KV in place and equal to live before the ExternalSecret is applied; - the KeyCape image with KEY-WP-0046 deployed before the env is added. - - Positive and negative results recorded in value-free receipts. - evidence: - - at: '2026-10-31T09:52:22+00:00' - actor: founder (attended), claude-code guiding - kind: verifier_custody_and_rollout - result: passed - details: - - '2026-10-31: bind-lane.py setup (live keycape-config binds svc-keycape, custody - equal to bindPW, coding agents fenced, KV v1 CAS=0, policy+role) and verify - passed with env and config, then again env-only after directory-bind-rollout.py - clear-password; KeyCape main-e2b5fa1 (KEY-WP-0046) rolled out with KEYCAPE_LLDAP_BIND_PW; - admin signed in with OTP before and after. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-svc-keycape-bind/ - (a656739).' diff --git a/credential-change-requests/CCR-2026-0055-net-kingdom-privacyidea-bind.yaml b/credential-change-requests/CCR-2026-0055-net-kingdom-privacyidea-bind.yaml deleted file mode 100644 index 3755a61..0000000 --- a/credential-change-requests/CCR-2026-0055-net-kingdom-privacyidea-bind.yaml +++ /dev/null @@ -1,109 +0,0 @@ -id: CCR-2026-0055 -kind: credential-change-request -schema_version: 1 -request_type: attended-service-credential-delivery -title: NetKingdom privacyIDEA resolver directory bind (svc-privacyidea) custody -status: verified -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: claude-code - reason: 'NK-WP-0056-T04, fourth lane (after CCR-2026-0052/0053/0054): move the svc-privacyidea - LLDAP bind out of the founder''s personal SOPS custody into platform custody. - privacyIDEA stores the resolver password encrypted in its own database and accepts - it only through its pi-admin API; an unattended writer would need pi-admin''s - password standing in the cluster, so no workload reads it: delivery is an attended - write-through.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: - - at: '2026-10-31T10:10:37+00:00' - reviewer: platform-owner - decision: approved - comment: 'Approved as proposed: svc-privacyidea held in OpenBao, no workload reader, - attended write-through.' - - at: '2026-10-31T10:10:44+00:00' - reviewer: net-kingdom-owner - decision: approved - comment: Approved as proposed for NK-WP-0056-T04. -target: - domain: infotech - tenant: platform - workload: net-kingdom-privacyidea - environment: production - purpose: Hold the svc-privacyidea LLDAP bind password in platform custody so that - its rotation needs no decrypted custody copy; privacyIDEA receives it only through - the attended resolver write. No other privacyIDEA, LLDAP or service credential. -openbao: - mount: platform - kv_path: platform/workloads/net-kingdom/directory-binds/svc-privacyidea - fields: - - LDAP_BIND_PASSWORD - auth: - method: attended-oidc - mount: netkingdom - role: platform-admin - runtime_access: false - initial_cas: 0 -access_frontdoor: - type: attended-service-config - catalog_id: openbao-platform-admin-login - readiness: pending-review - resolvable: false -lifecycle: - deactivate: Delete the KV entry only after the founder custody copy is confirmed - current; privacyIDEA keeps its stored resolver password. - rotate: Change the svc-privacyidea password in LLDAP, write the next KV version - with CAS on the current version, then run realm_bootstrap.py --rotate-bind --from-openbao - (attended pi-admin; --verify-user MFA login required) and verify with an OTP login. - Founder-run. - compromised: Reset svc-privacyidea in LLDAP first (read-only bind, lldap_strict_readonly), - then rotate as above. -delivery: - surface: attended-service-config - service: privacyIDEA (sso), LDAP resolver netkingdom-lldap - target: resolver BINDPW, stored encrypted in the privacyIDEA database - procedure: net-kingdom sso-mfa/k8s/privacyidea/realm_bootstrap.py --rotate-bind - --from-openbao - gate: attended platform-admin (aal2) OpenBao session, attended pi-admin password, - and a fresh MFA login through the resolver (--verify-user) that proves the write -risk: - classification: medium - notes: - - svc-privacyidea is a read-only directory bind (lldap_strict_readonly); a leak - exposes directory attributes, not write access. - - No workload, Kubernetes or ESO reader exists; only the attended platform-admin - session reads the value, in memory. - - Initial KV write is CAS=0 from custody after the value is proven current by an - LLDAP sign-in as svc-privacyidea; no rotation happens in this request. - - Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary - before the first write. -verification: - positive: - - The KV value signs in to LLDAP as svc-privacyidea (current password), compared - without display - - An OTP login resolves the user through the resolver after any write-through - negative: - - Coding-agent boundary denies data and metadata of the path - - No auth role or policy grants the path to a workload - activation_conditions: - - Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound - by the setup tool). - - Attended platform-admin (aal2) session; the founder runs setup with the decrypted - custody copy and the write-through with pi-admin and an MFA login through the - resolver. - - Positive and negative results recorded in value-free receipts. - evidence: - - at: '2026-10-31T10:19:36+00:00' - actor: founder (attended), claude-code guiding - kind: verifier_custody_and_write_through - result: passed - details: - - '2026-10-31: bind-lane.py setup (custody signs in to LLDAP as svc-privacyidea, - coding agents fenced, KV v1 CAS=0, no workload role) and verify passed; realm_bootstrap.py - --rotate-bind --from-openbao wrote BINDPW from OpenBao and admin passed token-backed - MFA through the resolver. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-svc-privacyidea-bind/ - (ad7d1cd).' diff --git a/credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml b/credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml deleted file mode 100644 index c2e3061..0000000 --- a/credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml +++ /dev/null @@ -1,89 +0,0 @@ -id: CCR-2026-0056 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: Email-connect platform SMTP password projection -status: proposed -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: coulomb-social - reason: CSOC-WP-0015-T02 platform sender migration; user selected platform-root with Bernd as the human reply and bounce operator. This ownership choice is not credential admission. -review: - required: true - required_approvers: - - founder - - railiance-platform-owner - - email-connect-owner - comments: [] -target: - domain: communication - tenant: platform - workload: email-connect - environment: production - purpose: Fixed admitted registration and invitation templates from platform@coulomb.social; no new caller or arbitrary notification authority. -openbao: - mount: platform - kv_path: platform/workloads/railiance-telemetry/smtp - fields: - - SMTP_PASSWORD - metadata_read: false - token_self_lifecycle: true - policy_name: email-connect-platform-smtp-eso - policy_file: openbao/policies/email-connect-platform-smtp-eso.hcl - auth: - method: kubernetes - mount: kubernetes - role: email-connect-platform-smtp-eso - bound_claims: - service_account_names: - - email-connect-platform-smtp-eso - service_account_namespaces: - - email-connect - bound_claims_confirmed: false - policies: - - email-connect-platform-smtp-eso - ttl: 15m - audience: openbao - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true -access_frontdoor: - type: external-secrets - catalog_id: email-connect-platform-smtp - selector: Namespace email-connect and ServiceAccount email-connect-platform-smtp-eso only - readiness: pending-review - resolvable: false -delivery: - surface: external-secrets - namespace: email-connect - service_account: email-connect-platform-smtp-eso - secret_store: openbao-email-connect-platform-smtp - external_secret: email-connect-platform-smtp - target_secret: email-connect-platform-smtp - target_key: password - target: Dedicated namespace SecretStore and password-only ExternalSecret; caller tokens unchanged. - bootstrap_command: Existing mailbox is reused; this request does not create or rotate a value. -risk: - classification: high - notes: - - Shared mailbox reader outside the automatic single-reader profile; existing telemetry and Authelia grants must be preserved. - - OpenBao authorizes the whole exact KV record, not individual fields. ESO projects only SMTP_PASSWORD; this is not field-level OpenBao access control. - - SMTP credential can send as the platform mailbox. No IMAP access or mailbox forwarding operation is included. - - No new application sender, mailbox scan, suppression operator or timer is activated by admission alone. -verification: - positive: - - Exact scoped role and policy readback; certificate-verified STARTTLS AUTH without mail submission. - - Native service account binding confirmation precedes apply; actual workload login and ESO Ready are verified separately after projection. - negative: - - Changed policy, role, settings, approval, source digest or unconfirmed binding refuses. - - Agent boundary denies both KV data and metadata; workload token denies metadata, sibling and mutation capabilities. - activation_conditions: - - Exact CCR approval and native bound claims confirmed through the owner workflow. - - Attended owner helper receipt verified with unchanged mailbox version and other consumers; Warden session revocation confirmed. - - Native workload positive and negative login, scoped projection Ready and separate supervised application sender rollout accepted. - evidence: [] -lifecycle: - deactivate: Stop the admitted consumer and reconcile pending sends, revoke only this dedicated reader role/policy, and remove only its projection after owner verification. Preserve the mailbox and other consumers. - rotate: Coordinate provider rotation across telemetry, Authelia and email-connect; verify all projections and consumers before retiring the predecessor. Never rotate a single consumer copy independently. - compromised: Pause affected sending and coordinate shared-mailbox credential rotation through the owner; preserve ledger evidence and investigate all admitted consumers. Do not auto-resend unknown outcomes. diff --git a/credential-change-requests/CCR-2026-0057-net-kingdom-synthetic-journey-identity.yaml b/credential-change-requests/CCR-2026-0057-net-kingdom-synthetic-journey-identity.yaml deleted file mode 100644 index bbf29cc..0000000 --- a/credential-change-requests/CCR-2026-0057-net-kingdom-synthetic-journey-identity.yaml +++ /dev/null @@ -1,125 +0,0 @@ -id: CCR-2026-0057 -kind: credential-change-request -schema_version: 1 -request_type: attended-service-credential-delivery -title: NetKingdom synthetic journey identity (syn-journey) password and TOTP seed - custody -status: verified -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: claude-code - reason: 'NK-WP-0056-T09: a synthetic, unprivileged sign-in identity whose MFA journey - through KeyCape, Authelia and privacyIDEA replaces the founder''s sign-in as the - check between consumers in a bounded bind rotation (NK-WP-0056-T06). Its password - and TOTP seed are generated in memory and held only in OpenBao; LLDAP and privacyIDEA - receive them through one attended procedure. No workload reads them until the - executor identity exists (T03/T10), which needs its own request.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: - - at: '2026-10-31T11:50:37+00:00' - reviewer: platform-owner - decision: approved - comment: Approved by the founder in session 2026-10-31 ('i accept the ccr') for - NK-WP-0056-T09. - - at: '2026-10-31T11:50:37+00:00' - reviewer: net-kingdom-owner - decision: approved - comment: Approved by the founder in session 2026-10-31 ('i accept the ccr') for - NK-WP-0056-T09. -target: - domain: infotech - tenant: platform - workload: net-kingdom-synthetic-journey - environment: production - purpose: Hold the password and TOTP seed of the LLDAP user syn-journey (no groups, - no rights) so that a synthetic journey can prove sign-in with MFA without a person. - No other identity, directory, privacyIDEA or service credential. -openbao: - mount: platform - kv_path: platform/workloads/net-kingdom/synthetic-journey/syn-journey - fields: - - PASSWORD - - TOTP_SEED - auth: - method: attended-oidc - mount: netkingdom - role: platform-admin - runtime_access: false - initial_cas: 0 -access_frontdoor: - type: attended-service-config - catalog_id: openbao-platform-admin-login - readiness: pending-review - resolvable: false -lifecycle: - deactivate: lane.py teardown deletes the privacyIDEA token SYNJOURNEY01, the LLDAP - user syn-journey (refused if it has gained any group) and the KV entry with all - versions; the KeyCape client nk-synthetic-journey is removed with keycape-client.py - --operation remove. The coding-agent fence stays. - rotate: Tear down and set up again (new password and seed, CAS=0 on the fresh path). - No in-place rotation path in this request. - compromised: Delete the privacyIDEA token first (the account then cannot pass MFA), - then tear down. The identity has no groups, so a leak grants a sign-in that reaches - no protected service. -delivery: - surface: attended-service-config - service: LLDAP (sso) user syn-journey; privacyIDEA (mfa) TOTP token SYNJOURNEY01 - in realm coulomb - target: the LLDAP user's password (set with the pinned lldap_set_password, OPAQUE) - and the privacyIDEA token's OTP key (stored encrypted in the privacyIDEA database) - procedure: net-kingdom sso-mfa/k8s/synthetic-journey/lane.py setup - gate: attended platform-admin (aal2) OpenBao session, the human admin's LLDAP password, - the attended pi-admin password, and a passing synthetic journey (lane.py verify) - against the live issuer -risk: - classification: medium - notes: - - The identity is new and holds no group. Authelia admits it (it is not svc-* or - ld-admin), KeyCape issues it tokens, but no consuming service grants it anything. - KeyCape has no per-client group gate, so every client that admits any authenticated - user would admit it too; none of the registered clients grants rights without - a group. - - KeyCape marks its tokens principal_type human (key-cape token.go); the account - is declared SERVICE in the service-identity declarations. Recorded, not changed - here. - - Values are generated in memory by lane.py and written CAS=0; neither is printed, - logged or written to disk. The journey's receipts are value-free. - - Coding agents are fenced from the data and metadata paths in agent-high-risk-boundary - before the first write. - - No workload, Kubernetes or ESO reader exists; T10 needs a separate workload-kv-read - request for the executor identity. -verification: - positive: - - The KV password signs in to LLDAP as syn-journey (compared without display) - - The synthetic journey passes against https://kc.coulomb.social at aal2 with the - KV values (lane.py verify), with a value-free receipt - negative: - - Coding-agent boundary denies data and metadata of the path - - No auth role or policy grants the path to a workload - - setup refuses if the KV path, the LLDAP user or the privacyIDEA token already - exist - activation_conditions: - - Explicit platform-owner and net-kingdom-owner approval of this exact request (sha256-bound - by lane.py). - - The KeyCape client nk-synthetic-journey registered through security-cases/nk-synthetic-journey.yaml - before lane.py verify. - - Attended platform-admin (aal2) session; the founder runs setup and verify. - - Positive and negative results recorded in value-free receipts. - evidence: - - at: '2026-10-31T12:33:37+00:00' - actor: founder (attended), claude-code guiding - kind: verifier_identity_custody_and_journey - result: passed - details: - - '2026-10-31: KeyCape client nk-synthetic-journey registered (SecurityCase accepted, - net-kingdom d654f8a); lane.py setup (coding agents fenced, syn-journey with - no groups, TOTP token SYNJOURNEY01 with a privacyIDEA-generated key confirmed - by code, KV v1 CAS=0, no workload role) and verify (KV current by LLDAP sign-in, - live synthetic journey passed at aal2 with otp) passed. One partial setup removed - by lane.py teardown first. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-t09-synthetic-identity/ - (661ab6a).' diff --git a/credential-change-requests/CCR-2026-0058-net-kingdom-bind-executor-svc-authelia-write.yaml b/credential-change-requests/CCR-2026-0058-net-kingdom-bind-executor-svc-authelia-write.yaml deleted file mode 100644 index 19169aa..0000000 --- a/credential-change-requests/CCR-2026-0058-net-kingdom-bind-executor-svc-authelia-write.yaml +++ /dev/null @@ -1,188 +0,0 @@ -id: CCR-2026-0058 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-write-next-version -title: NetKingdom bounded executor writes the next svc-authelia bind version (pilot) -status: approved -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: claude-code - reason: 'NK-WP-0056-T02 owner request (hub 9d08288d), for NK-WP-0056-T06: the bounded - executor service:nk-bind-executor rotates the svc-authelia LLDAP bind unattended - under one founder admission (Bounded Execution Admission v0.1, proposed). The - executor must write the next KV version of the bind with CAS and read it back - for its checks and revert. Drafted by the railiance-platform worker; not approved.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: - - at: '2026-10-31T14:26:48+00:00' - reviewer: platform-owner - decision: approved - comment: Approved by the founder in session 2026-10-31 as amended (window-scoped - role, bind self-rotation) for NK-WP-0056-T06. - - at: '2026-10-31T14:27:33+00:00' - reviewer: net-kingdom-owner - decision: approved - comment: Approved by the founder in session 2026-10-31 as amended (window-scoped - role, bind self-rotation) for NK-WP-0056-T06. Recorded by hand in the approve - tool's format; the tool refuses the second approval for this request type once - the first sets status approved. - - at: '2026-10-31T15:46:26+00:00' - reviewer: platform-owner - decision: approved - comment: Re-approved by the founder in session 2026-10-31 with the corrected policy - (required_parameters removed; NK-WP-0056-T12 synthetic pilot evidence). - - at: '2026-10-31T15:46:26+00:00' - reviewer: net-kingdom-owner - decision: approved - comment: Re-approved by the founder in session 2026-10-31 with the corrected policy - (required_parameters removed; NK-WP-0056-T12 synthetic pilot evidence). - - at: '2026-10-31T18:38:22+00:00' - reviewer: platform-owner - decision: binding_confirmed - comment: 'Binding confirmed: ServiceAccount nk-bind-executor in namespace sso - only (uid bd15569c-8a85-4625-b001-d9f799cb857b), audience openbao, from rapp-bounded-executor.' -target: - domain: infotech - tenant: platform - workload: net-kingdom-bind-executor - environment: production - purpose: Let only the executor ServiceAccount sso/nk-bind-executor read and write - the next version of platform/workloads/net-kingdom/directory-binds/svc-authelia, - with CAS on the current version; no create, metadata, delete, destroy, list or - sibling path -admission: - standard: net-kingdom canon/standards/bounded-execution-admission_v0.1.md (proposed) - executor_identity: service:nk-bind-executor - example: net-kingdom examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml - note: The OpenBao role does not evaluate the admission. The admission (approval-engine - consume, window, plan digest) is enforced by the executor and its owners; this - request bounds what a compromised or unadmitted executor can reach to one value. -openbao: - mount: platform - kv_path: platform/workloads/net-kingdom/directory-binds/svc-authelia - fields: - - LDAP_BIND_PASSWORD - policy_name: net-kingdom-bind-executor-svc-authelia-write - policy_file: openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl - write: - mode: next-version - cas_required: true - create: false - metadata_read: false - delete: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: net-kingdom-bind-executor-svc-authelia-write - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - net-kingdom-bind-executor-svc-authelia-write - bound_claims: - service_account_names: - - nk-bind-executor - service_account_namespaces: - - sso - bound_claims_confirmed: true -access_frontdoor: - type: platform-owner - catalog_id: net-kingdom-bind-executor-svc-authelia-write - readiness: pending-review - resolvable: false -agent_fence: - data_and_metadata_denied: true - note: The svc-authelia data and metadata paths are already denied to coding agents - since CCR-2026-0053; setup re-checks the deny before the role is created. -amendments: -- at: '2026-10-31' - by: claude-code - change: 'Policy file corrected: removed required_parameters = ["options"]. OpenBao - 2.5.4 applies it to reads as well, so the executor could not read the version - it must name for CAS (net-kingdom NK-WP-0056-T12 synthetic pilot, docs/evidence/2026-10-31-nk56-t12-synthetic-pilot/). - Writes without options.cas are still refused by cas_required on the key; metadata, - delete, sibling and new paths stay denied (tested). Status reset to proposed: - the approved policy file changed, re-approval needed.' -lifecycle: - role_window: 'Window-scoped (founder decision 2026-10-31, hub c29c6eda): the policy - is applied once and grants nothing on its own; the Kubernetes-auth role is created - by the attended platform-admin when an admission naming this lane is approved, - bounded to that admission''s window, and deleted after the run''s receipt or at - window end, whichever comes first. No write role exists between admitted runs.' - deactivate: Delete the role, then the policy; the KV entry and the ESO reader lane - (CCR-2026-0053) are unchanged. The bind falls back to founder-run rotation. - rotate: Executor reads the current version N, signs in to LLDAP as svc-authelia - with it and changes that account's own password (self-rotation, founder decision - 2026-10-31, hub 3d0c8f86; no lldap_password_manager account), writes version N+1 - with options.cas=N; ESO projects it, Authelia restarts, the synthetic journey - proves sign-in. Revert changes the password back the same way and writes the prior - value as version N+2 with options.cas=N+1. - compromised: Delete the role first (stops new logins; existing tokens expire within - 15m, or revoke by accessor), then reset svc-authelia in LLDAP and rotate attended - as in CCR-2026-0053. -delivery: - surface: native-workload-login - namespace: sso - service_account: nk-bind-executor - reader: Executor runtime (net-kingdom source, rapp-* package per ADR-0015, NK-WP-0056-T03); - logs in with its projected ServiceAccount token, audience openbao; no Secret is - created by this request -risk: - classification: medium - notes: - - A write role on the svc-authelia bind is new; until now only the attended platform-admin - wrote this path. It exists only during an admitted window. Within it, a compromised - executor ServiceAccount can replace the bind value (Authelia sign-in outage on - next restart) but cannot read any other path, create paths, delete versions or - read metadata; outside it, the ServiceAccount cannot log in to this role at all. - - cas_required on the key makes every write state the version it replaces; a racing - or replayed write is refused by the engine, not by the executor. - - The executor also reads the current value (needed for CAS and revert). svc-authelia - is a read-only directory bind (lldap_strict_readonly). - - The role is a separate role from the ESO reader (CCR-2026-0053) and from the journey - read lane (NK-WP-0056-T10); policies are never combined in one role. - - Node root, ESO administration, cluster admins able to mint tokens for sso/nk-bind-executor - and platform-admin remain inside this credential boundary. -verification: - positive: - - Exact policy and role read back; login as sso/nk-bind-executor (audience openbao) - gets only this policy with a lease of at most 15m - - Write of the next version with options.cas equal to the current version succeeds - in a rehearsal on a synthetic sibling-free test path or under the T06 admission; - the value is compared in memory only - negative: - - Write without options or with a stale cas is refused - - Create of a new sibling path, metadata read, delete, destroy, undelete and list - are refused - - Wrong ServiceAccount, wrong namespace and wrong audience logins are denied - - Coding-agent boundary denies data and metadata of the path - activation_conditions: - - Explicit platform-owner and net-kingdom-owner approval of this exact request. - - ServiceAccount sso/nk-bind-executor exists from the reviewed rapp-* package (bound_claims_confirmed - then set to true by the platform-owner). - - Attended platform-admin (aal2) session sets cas_required on the key and applies - the policy once from a reviewed commit. - - Per admitted run, the attended platform-admin creates the role for the admission's - window and deletes it after the receipt (or at window end); both steps are recorded - with the admission id. - - Positive and negative results recorded in value-free receipts. - evidence: - - at: '2026-10-31T18:48:55+00:00' - actor: founder (attended), claude-code guiding - kind: executor_lane_policy_setup - result: passed - details: - - '2026-10-31: policy applied and cas_required set on the svc-authelia key (metadata - write naming only cas_required; max_versions, delete_version_after and custom_metadata - unchanged) by net-kingdom tools/service-binds/executor-lanes.py; the window-scoped - role was not created and its login is denied (verified). Role creation remains - per admitted window. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-t10-executor-lanes/ - (ef6284c).' diff --git a/credential-change-requests/CCR-2026-0059-net-kingdom-bind-executor-syn-journey-read.yaml b/credential-change-requests/CCR-2026-0059-net-kingdom-bind-executor-syn-journey-read.yaml deleted file mode 100644 index 71b9f09..0000000 --- a/credential-change-requests/CCR-2026-0059-net-kingdom-bind-executor-syn-journey-read.yaml +++ /dev/null @@ -1,169 +0,0 @@ -id: CCR-2026-0059 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: NetKingdom bounded executor reads the synthetic journey identity (syn-journey) -status: verified -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: claude-code - reason: 'NK-WP-0056-T10/T06: the bounded executor service:nk-bind-executor runs - the synthetic MFA journey (KeyCape, Authelia, privacyIDEA) as the check after - each consumer restart, with no person signing in. It needs the syn-journey password - and TOTP seed held under CCR-2026-0057. Lane design: railiance-platform docs/credential-lane-designs/bounded-executor.md - "Journey secrets (T10)". Drafted by net-kingdom; not approved.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: - - at: '2026-10-31T18:06:02+00:00' - reviewer: platform-owner - decision: approved - comment: 'Approved by the founder 2026-10-31: standing journey reader for NK-WP-0056-T10, - accepting the CCR-2026-0057 runtime_access widening.' - - at: '2026-10-31T18:06:12+00:00' - reviewer: net-kingdom-owner - decision: approved - comment: Approved by the founder 2026-10-31 for NK-WP-0056-T10/T06. - - at: '2026-10-31T18:38:23+00:00' - reviewer: platform-owner - decision: binding_confirmed - comment: 'Binding confirmed: ServiceAccount nk-bind-executor in namespace sso - only (uid bd15569c-8a85-4625-b001-d9f799cb857b), audience openbao, from rapp-bounded-executor.' -supersedes_boundary: - ccr: CCR-2026-0057 - field: openbao.runtime_access - was: false - change: 'This request gives one workload reader to the path CCR-2026-0057 holds - with runtime_access: false. CCR-2026-0057''s attended setup, teardown and rotation - stay valid and unchanged; its rotate (teardown + setup) does not need this role.' -target: - domain: infotech - tenant: platform - workload: net-kingdom-bind-executor - rapp: rapp-bounded-executor - environment: production - purpose: Let only the executor ServiceAccount sso/nk-bind-executor read the syn-journey - PASSWORD and TOTP_SEED to run the synthetic journey; no write, metadata, list, - sibling path, or other identity -admission: - standard: net-kingdom canon/standards/bounded-execution-admission_v0.1.md (proposed) - executor_identity: service:nk-bind-executor - example: net-kingdom examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml - note: The journey is a check step under an admission. The OpenBao role does not - evaluate the admission; it bounds what the ServiceAccount can read to one synthetic, - group-less identity. -openbao: - mount: platform - kv_path: platform/workloads/net-kingdom/synthetic-journey/syn-journey - fields: - - PASSWORD - - TOTP_SEED - policy_name: net-kingdom-bind-executor-syn-journey-read - policy_file: openbao/policies/net-kingdom-bind-executor-syn-journey-read.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: net-kingdom-bind-executor-syn-journey-read - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - net-kingdom-bind-executor-syn-journey-read - bound_claims: - service_account_names: - - nk-bind-executor - service_account_namespaces: - - sso - bound_claims_confirmed: true -access_frontdoor: - type: platform-owner - catalog_id: net-kingdom-bind-executor-syn-journey-read - readiness: pending-review - resolvable: false -agent_fence: - data_and_metadata_denied: true - note: The syn-journey data and metadata paths are denied to coding agents since - CCR-2026-0057; setup re-checks the deny before the role is created. -lifecycle: - role_window: Standing (founder decision 2026-10-31, net-kingdom session). Unlike - the CCR-2026-0058 write role, this role only reads a group-less synthetic identity, - and a standing reader lets the journey run as a scheduled check (T10) without - a platform-admin per run. - deactivate: Delete the role, then the policy. The KV entry, the LLDAP user and the - privacyIDEA token stay under CCR-2026-0057. - rotate: As in CCR-2026-0057 (lane.py teardown and setup); the reader needs no change, - because the path stays the same. - compromised: Delete the privacyIDEA token SYNJOURNEY01 first (the account then cannot - pass MFA), delete this role, then tear down and set up as in CCR-2026-0057. The - identity has no groups, so a leak grants a sign-in that reaches no protected service. -delivery: - surface: native-workload-login - namespace: sso - service_account: nk-bind-executor - reader: Executor runtime (net-kingdom bounded-executor/, deployed by rapp-bounded-executor); - logs in with its projected ServiceAccount token, audience openbao, separately - from the CCR-2026-0058 write role; values stay in memory and never reach a Secret, - log or receipt - manifest: 'rapp-bounded-executor: manifests/rbac.yaml, templates/job.yaml' -risk: - classification: low - notes: - - Reachable value is one synthetic identity with no groups. Authelia and KeyCape - admit it, but no consuming service grants it anything (CCR-2026-0057 risk notes). - - A reader holding both values can complete an MFA sign-in as syn-journey, which - is the purpose. privacyIDEA refuses a reused TOTP time step, so a parallel use - and the journey can race; the journey then fails closed. - - The role is separate from the CCR-2026-0058 write role and from the ESO reader - (CCR-2026-0053); policies are never combined in one role. - - Node root, cluster admins able to mint tokens for sso/nk-bind-executor and platform-admin - remain inside this credential boundary. -verification: - positive: - - Exact policy and role read back; login as sso/nk-bind-executor (audience openbao) - gets only this policy with a lease of at most 15m - - The synthetic journey passes at aal2 with the values read through this role, with - a value-free receipt - negative: - - Write, metadata read, list, delete and sibling paths (including directory-binds) - are refused - - Wrong ServiceAccount, wrong namespace and wrong audience logins are denied - - Coding-agent boundary denies data and metadata of the path - activation_conditions: - - Explicit platform-owner and net-kingdom-owner approval of this exact request, - which includes the change to CCR-2026-0057's runtime_access boundary. - - ServiceAccount sso/nk-bind-executor exists from the reviewed rapp-bounded-executor - package (bound_claims_confirmed then set to true by the platform-owner). - - Attended platform-admin (aal2) session applies the policy and role from a reviewed - commit. - - Positive and negative results recorded in value-free receipts. - evidence: - - at: '2026-10-31T18:48:55+00:00' - actor: founder (attended), claude-code guiding - kind: executor_lane_setup_and_proof - result: passed - details: - - '2026-10-31: policy and standing role applied by net-kingdom tools/service-binds/executor-lanes.py - setup (lanes_prepared); verify (lanes_verified): sso/nk-bind-executor audience - openbao gets exactly this policy, lease <=15m, reads only syn-journey data (fields - checked in memory), metadata/directory-binds/token-create denied, default SA, - wrong audience and write-role login denied, coding-agent fence effective. The - positive journey run through this role is the open part of NK-WP-0056-T10. Evidence: - net-kingdom docs/evidence/2026-10-31-nk56-t10-executor-lanes/ (ef6284c).' - - at: '2026-10-31T19:50:05+00:00' - actor: founder started the Job; no person in the journey - kind: executor_journey_live - result: passed - details: - - '2026-10-31: rapp-bounded-executor make journey, Job sso/bounded-executor-journey-20261031-194719 - (image sha256:71da4a51), logged in as sso/nk-bind-executor with this role only - (openbao audience), read syn-journey and passed the synthetic journey at aal2 - (pwd+otp, signature verified), effects false. Evidence: net-kingdom docs/evidence/2026-10-31-nk56-t10-journey-only/live-receipt.json - (a020758).' diff --git a/credential-change-requests/CCR-2026-0060-nk-bind-executor-approval-keycape-eso-reader.yaml b/credential-change-requests/CCR-2026-0060-nk-bind-executor-approval-keycape-eso-reader.yaml deleted file mode 100644 index 959d371..0000000 --- a/credential-change-requests/CCR-2026-0060-nk-bind-executor-approval-keycape-eso-reader.yaml +++ /dev/null @@ -1,98 +0,0 @@ -id: CCR-2026-0060 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: 'nk-bind-executor-approval: keycape-reader-policy and eso-role (oidc-service-client@0.1)' -status: proposed -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: railiance-platform - reason: 'NK-WP-0056-T14 first case of SecurityCaseType oidc-service-client 0.1 (net-kingdom - 5d5f15d, proposed; hub decision f8044f81; memo nk-wp-0056-t14-oidc-service-client-r1). - Drafted by the platform owner in its review (docs/credential-lane-designs/oidc-service-client-review.md, - R6). KeyCape reads the client secret of nk-bind-executor-approval only through ESO, as - CCR-2026-0041 for the browser type. Not approved.' -review: - required: true - required_approvers: - - platform-owner - comments: [] -target: - domain: infotech - tenant: platform - workload: keycape - environment: production - purpose: KeyCape reads the nk-bind-executor-approval client secret only through ESO. No metadata, - list, parent or sibling access. -openbao: - mount: platform - kv_path: platform/workloads/keycape/nk-bind-executor-approval - fields: - - CLIENT_SECRET - policy_name: workload-kv-read-keycape-nk-bind-executor-approval-eso - policy_file: openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-eso.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: external-secrets-keycape-nk-bind-executor-approval - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - workload-kv-read-keycape-nk-bind-executor-approval-eso - bound_claims: - service_account_names: - - external-secrets - service_account_namespaces: - - external-secrets - bound_claims_confirmed: false -access_frontdoor: - type: external-secrets - catalog_id: bounded-executor-keycape-nk-bind-executor-approval - readiness: pending-review - resolvable: false -risk: - classification: high - notes: - - Rendered from a proposed SecurityCaseType; the type is not yet acknowledged and the case - is not yet accepted. - - Exact read on one KV path; no metadata, list, parent or sibling access. - - The value lets its holder obtain approval-engine tokens as service:nk-bind-executor - (approval:create/read/consume, tenant:platform). - - Only ExternalSecrets in namespace sso may use the store; sso namespace writers are - within this boundary (as CCR-2026-0033/0041). -verification: - positive: - - policy workload-kv-read-keycape-nk-bind-executor-approval-eso grants read on - platform/data/workloads/keycape/nk-bind-executor-approval only (capabilities check) - - ExternalSecret sso/keycape-nk-bind-executor-approval-client reports Ready=True (status - only) - negative: - - a default-only identity has no read capability on the path - - sibling GET, parent LIST and metadata read are denied - - wrong ServiceAccount and wrong audience logins are denied - activation_conditions: - - oidc-service-client 0.1 acknowledged by the founder (informed-decision memo, hub decision - f8044f81). - - SecurityCase nk-bind-executor-approval accepted by owner and founder. - - Custody entry minted at version 1 by keycape-client-secret-generate, after the broker - admits the type (review R1). - - platform-owner approval of this exact request and bound_claims_confirmed set to true. -lifecycle: - deactivate: delete ExternalSecret sso/keycape-nk-bind-executor-approval-client, then role - external-secrets-keycape-nk-bind-executor-approval, then the policy (case rollback) - rotate: no rotation lane exists yet (RPF-WP-0055 residual); until then a rotation is an - attended platform-admin write with CAS=current, an ESO refresh and a KeyCape restart - compromised: owner incident process; delete the role first; no automatic read, reseed or - cleanup -security_case: - case: nk-bind-executor-approval - type: oidc-service-client@0.1 - type_status: proposed - type_source: net-kingdom security-case-types/oidc-service-client/type.yaml (5d5f15d) - acceptance_record: pending diff --git a/credential-change-requests/CCR-2026-0061-nk-bind-executor-approval-consumer-reader.yaml b/credential-change-requests/CCR-2026-0061-nk-bind-executor-approval-consumer-reader.yaml deleted file mode 100644 index 6349516..0000000 --- a/credential-change-requests/CCR-2026-0061-nk-bind-executor-approval-consumer-reader.yaml +++ /dev/null @@ -1,127 +0,0 @@ -id: CCR-2026-0061 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: 'nk-bind-executor-approval: consumer reader for sso/nk-bind-executor (oidc-service-client@0.1)' -status: proposed -created: '2026-10-31' -updated: '2026-10-31' -requester: - agent: railiance-platform - reason: 'NK-WP-0056-T14 first case of SecurityCaseType oidc-service-client 0.1 (net-kingdom - 5d5f15d, proposed; hub decision f8044f81). The bounded executor service:nk-bind-executor - reads its approval-engine client secret natively per call to file, read and consume its - own approvals. Drafted by the platform owner in its review - (docs/credential-lane-designs/oidc-service-client-review.md, R2-R6). Not approved.' -review: - required: true - required_approvers: - - platform-owner - - net-kingdom-owner - comments: [] -target: - domain: infotech - tenant: platform - workload: net-kingdom-bind-executor - rapp: rapp-bounded-executor - environment: production - purpose: Let only the executor ServiceAccount sso/nk-bind-executor read the - nk-bind-executor-approval CLIENT_SECRET in memory for its client_credentials exchange; - no write, metadata, list, sibling path, Secret copy or other identity -admission: - standard: net-kingdom canon/standards/bounded-execution-admission_v0.1.md (proposed) - executor_identity: service:nk-bind-executor - note: The role does not evaluate the admission. It bounds what the ServiceAccount can read - to one client secret whose tokens carry approval:create/read/consume at approval-engine. -openbao: - mount: platform - kv_path: platform/workloads/keycape/nk-bind-executor-approval - fields: - - CLIENT_SECRET - policy_name: workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor - policy_file: openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - # Review R4: role name ends with -workload-kv-read so the delegated applier admits it; - # the type r1 template (role name = policy name) would be refused. - role: keycape-nk-bind-executor-approval-bounded-executor-workload-kv-read - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor - bound_claims: - service_account_names: - - nk-bind-executor - service_account_namespaces: - - sso - bound_claims_confirmed: false -access_frontdoor: - type: platform-owner - catalog_id: bounded-executor-keycape-nk-bind-executor-approval - readiness: pending-review - resolvable: false -agent_fence: - data_and_metadata_denied: true - note: Coding agents are denied data and metadata of platform/workloads/keycape/nk-bind-executor-approval; - setup checks the deny before the role is created. -lifecycle: - role_window: Standing, as CCR-2026-0059. The executor exchanges a fresh token per call and - keeps neither the secret nor a token beyond the call. - deactivate: Delete the role, then the policy. The custody entry and the ESO reader stay - under CCR-2026-0060 until the case rollback. - rotate: No rotation lane exists yet (RPF-WP-0055 residual). The reader needs no change on - rotation because the path stays the same. - compromised: Remove client nk-bind-executor-approval from KeyCape (register-client rollback, - attended), delete this role, then rotate attended. Approvals already created by the - client still need a human entry under human_control. -delivery: - surface: native-workload-login - namespace: sso - service_account: nk-bind-executor - reader: Executor runtime (net-kingdom bounded-executor/, deployed by rapp-bounded-executor); - logs in with its projected ServiceAccount token, audience openbao, separately from the - CCR-2026-0058 and CCR-2026-0059 roles; the value stays in memory and never reaches a - Secret, log or receipt -risk: - classification: high - notes: - - Reachable value is one confidential client secret; its tokens act as service:nk-bind-executor - at approval-engine (create approvals naming itself, read and consume approvals of - tenant:platform). Approvals still need a human entry under human_control. - - The role is separate from the CCR-2026-0058 write role, the CCR-2026-0059 journey reader - and the CCR-2026-0060 ESO reader; policies are never combined in one role. - - No Secret copy in namespace sso beyond KeyCape's own ESO target; other workloads in sso - without the ServiceAccount cannot log in. - - Node root, cluster admins able to mint tokens for sso/nk-bind-executor and platform-admin - remain inside this credential boundary. -verification: - positive: - - Exact policy and role read back; login as sso/nk-bind-executor (audience openbao) gets - only this policy with a lease of at most 15m - - The executor reports the claims of one client_credentials exchange per scope (sub - service:nk-bind-executor, aud approval-engine, tenant:platform, lifetime <= 15m), value-free - negative: - - Write, metadata read, list, delete and sibling paths (including keycape/secrets-engine-openbao - and directory-binds) are refused - - Wrong ServiceAccount, wrong namespace and wrong audience logins are denied - - Coding-agent boundary denies data and metadata of the path - activation_conditions: - - oidc-service-client acknowledged by the founder and case nk-bind-executor-approval accepted. - - Custody entry minted (CCR-2026-0040 lane, after broker admission R1). - - Explicit platform-owner and net-kingdom-owner approval of this exact request. - - bound_claims_confirmed set to true by the platform-owner (ServiceAccount sso/nk-bind-executor - already exists, uid bd15569c, CCR-2026-0058/0059). - - Attended platform-admin (aal2) session or the delegated applier applies policy and role - from a reviewed commit; positive and negative results recorded in value-free receipts. -security_case: - case: nk-bind-executor-approval - type: oidc-service-client@0.1 - type_status: proposed - type_source: net-kingdom security-case-types/oidc-service-client/type.yaml (5d5f15d) - acceptance_record: pending diff --git a/credential-grants/catalog.yaml b/credential-grants/catalog.yaml index afb17dd..3add477 100644 --- a/credential-grants/catalog.yaml +++ b/credential-grants/catalog.yaml @@ -104,82 +104,6 @@ grants: audit: {openbao_audit_required: true, state_hub_metadata_allowed: true, record_secret_values: false} revocation: {required: true, by_accessor: true, on_exec_exit: true, on_denied_request: false} - - id: keycape/client-secret-generate - title: KeyCape confidential-client secret generation in custody - status: pilot - grant_class: approval-required - credential_type: openbao-token - issuer: openbao - audience: railiance-platform-credential-broker - description: > - RPF-WP-0054 / CCR-2026-0040. Short-lived token the broker uses inside its - own process to create platform/workloads/keycape/ once (CAS 0) - for an accepted oidc-browser-relying-party case. Create-only: it cannot - update, read values, write metadata, list or delete. The generated value - never leaves the broker process and the bao child's stdin; the caller gets a - receipt only. Founder decision 2026-10-09 (RPF-WP-0054-T02 option A). - openbao: - namespace: openbao - token_role: keycape-client-secret-generate - issuer_policy: credential-broker-keycape-client-secret-generate-issuer - policies: - - keycape-client-secret-generate - disallowed_policies: - - root - - platform-admin - mount_paths: - - platform/data/workloads/keycape/+ - - platform/metadata/workloads/keycape/+ - ttl: - default: 5m - max: 5m - renewable: false - requires_human_above: 5m - actors: - allowed_types: - - human-operator - required_subject_binding: auth/netkingdom/role/credential-broker-keycape-client-secret-generate - # CCR-2026-0043: human net-kingdom-admins membership and /assurance/level=aal2. - authorization: - flex_auth_required: false - flex_auth_mode: optional-preflight - approval_required: true - purpose_required: true - allowed_purpose_examples: - - oidc-browser-relying-party-mint-secret - delivery: - allowed: - - exec-env - preferred: exec-env - denied: - - chat - - state-hub-body - - git - - command-line-token-argument - - llm-prompt - exec_env: - variable: VAULT_TOKEN - child_only: true - redact_logs: true - audit: - openbao_audit_required: true - state_hub_metadata_allowed: true - record_secret_values: false - metadata_fields: - - grant_id - - actor - - subject - - purpose - - case_ref - - client_id - - kv_version - - lease_accessor - - status - revocation: - required: true - by_accessor: true - on_exec_exit: true - on_denied_request: false - id: ops-warden/warden-sign title: Ops Warden OpenBao SSH signing smoke token status: pilot diff --git a/docs/activity-core-release-admission.md b/docs/activity-core-release-admission.md index b87fd04..99f15f0 100644 --- a/docs/activity-core-release-admission.md +++ b/docs/activity-core-release-admission.md @@ -54,45 +54,3 @@ receipts, concurrent release, restart, failed health and successful rollback. Run destructive failure fixtures in an isolated environment; production must not be intentionally broken to manufacture proof. Admit only after tests and the observation gate pass. Access, budget or scope expansion stays a monthly decision. - -## Deterministic platform readiness gate - -`python3 scripts/activity_promotion_readiness.py --live --observation observation.json ---receipt readiness.json` verifies only the current bounded adoption and reports -an explicit owner-proof wait until ACTIVITY-WP-0041-T03 acceptance exists. This -read-only check neither creates a principal nor activates a release worker. D2 -removes the founder reapproval; the identity/custody and runtime proofs above -remain required. ACTIVITY-WP-0042 scheduling acceptance does not supply them. - -Later activation checks require operator-owned `--trusted-owner-key` (raw -32-byte Ed25519 public key) and `--trusted-owner-principal`, supplied separately -from `--owner-acceptance`. No signer key supplied inside a receipt is trusted. -The acceptance envelope has exactly `payload` and `signature` (base64 Ed25519 -signature of sorted compact JSON). Its payload uses -`schema_version: activity-core-release-acceptance/v1`, binds the existing -`owner_task: ACTIVITY-WP-0041-T03`, issuer, dedicated release principal, -`authority: ACTIVITY-WP-0041-image-only-v1`, application, exact estate cluster UID, -adoption revision and full broker implementation revision. It expires within -five minutes; the operator owns signer admission/revocation and actual native -evidence verification. An agent must not synthesize this production acceptance -from test fixtures or a producer's booleans. - -The `targets` mapping is the exact single platform child -`targetRevision` path on `main`, selective root sync of only activity-core, -child activity-core and prune false, as named in the checker. `checks` must -contain exactly its named acceptance checks, all actual owner-verified: bounded -identity/custody/revocation/denials, independent authenticated receipts/CI digest -binding, continuous observer, image-only scope, source CAS/single-flight, -restart/lost-response recovery, retention, real failed-health rollback, Temporal -registration, durable audit sink and report/schedule invariants. -`continuous_health` binds the adoption revision and a current actual 24h sampled -interval with at most 90-second gaps. A source timestamp or snapshot is not this -proof. Signatures authenticate the accepted owner's assertions; the owner still -must ground them in actual runtime receipts. - -Native adoption mismatch or malformed/forged/stale/widened owner acceptance -returns unverified. Missing owner acceptance/trust binding returns waiting on -the existing owner task, with its cross-owner coordination case. Only ready -returns exit code zero. No automatic-sync source candidate is generated: -ACTIVITY-WP-0041's current broker contract still relies on explicit selective -manual child/root sync and forbids an automated child policy. diff --git a/docs/adr/ADR-0006-playwright-consumer-group.md b/docs/adr/ADR-0006-playwright-consumer-group.md deleted file mode 100644 index d7316f1..0000000 --- a/docs/adr/ADR-0006-playwright-consumer-group.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -id: RPLAT-ADR-0006 -title: "ADR-0006 — Give Playwright consumers a dedicated namespace-scoped group" -status: accepted -revision: "1.0" -owner: railiance-platform -date: "2026-10-13" -last_reviewed: "2026-10-13" -review_interval: 6m ---- - -## Context - -rapp-playwright's published private pilot has a namespace Role but deliberately -no consumer binding. The user requested establishing a group during the Platform -open-work review. Existing cluster documentation provides administrative access; -the live binding inventory contains no non-system consumer group. - -## Decision - -Establish Kubernetes RBAC group subject `playwright-consumers`, bound only to -the package-owned `playwright-consumer` Role in namespace `playwright`. Platform -owns the RoleBinding; rapp-playwright retains the Role and workload source. -The initial membership policy includes all identified platform humans and coding -agents, as explicitly directed by the user. No per-use-case membership restriction -is introduced now. The identity/cluster owners must implement an authenticated -group claim for those entities before general consumer availability is accepted. - -## Consequences - -The Role permits pod get/list, pod port-forward creation and get of Deployment -`playwright`. It grants no Secrets, pod exec, workload writes, identity -impersonation or other namespaces. The namespace must remain dedicated to this -service because pod discovery and forwarding cover all its pods. Membership -confers browser-job submission for the reviewed trusted targets; it does not -authorize private targets, hostile pages or mutually untrusted tenant workloads. - -Kubernetes has no separate Group object to create. This establishes an RBAC -subject and permissions, not a KeyCape group, login integration or membership. -Administrator impersonation can verify the permission boundary but cannot prove -consumer authentication. RPF-WP-0056 retains those outstanding acceptance steps. - -Rollback removes only the Platform RoleBinding after reviewing its exact live -identity; it does not delete the Namespace, workload or artifact PVC. Existing -port-forwards require separate termination when revoking a consumer's active -access. Do not treat removal of a binding as proof that a running tunnel closed. - -## Alternatives considered - -Binding all authenticated users would broaden browser access beyond the human/coding-agent -identity population. Sharing the administrator kubeconfig would convey unrelated cluster -authority. Both are rejected. Selecting OIDC configuration or issuing client -certificates without an owner-supported membership route remains outside this -bounded group-binding change. diff --git a/docs/audit-core-database-lease-approval.example.json b/docs/audit-core-database-lease-approval.example.json index f7aab7b..f6b72d8 100644 --- a/docs/audit-core-database-lease-approval.example.json +++ b/docs/audit-core-database-lease-approval.example.json @@ -25,11 +25,5 @@ "synthetic_load": { "contract_id": null, "driver_revision": null - }, - "synthetic_sender": { - "identity": null, - "source": null, - "tenant": null, - "expires_at": null } } diff --git a/docs/audit-core-database-lease-recovery.md b/docs/audit-core-database-lease-recovery.md index c6bbd3b..3087fe6 100644 --- a/docs/audit-core-database-lease-recovery.md +++ b/docs/audit-core-database-lease-recovery.md @@ -11,7 +11,7 @@ pod. It does not authorize a live revocation. | Boundary | Exact scope | Responsible owner | | --- | --- | --- | | Consumer | Deployment `audit-core/audit-core`; Secret `audit-core/audit-core-database`; ExternalSecret `audit-core/audit-core-database` | `audit-core` | -| Lease | `database/creds/audit-core-runtime`; the one handle issued for the current ExternalSecret refresh (older predecessors are left alone) | `railiance-platform` custody operator | +| Lease | `database/creds/audit-core-runtime`; exactly one live handle whose issue time coheres with the current ExternalSecret refresh | `railiance-platform` custody operator | | Database | CNPG `databases/platform-pg`, database `audit_core`, runtime group `audit_core_app` | `rapp-postgres` | | Load | One separately approved synthetic sender/tenant contract; no production event or identity | `audit-core` + load owner | | Coordination and evidence | `RAILIANCE-WP-0024-T02` | `railiance-platform` | @@ -48,124 +48,6 @@ The separately approved load driver is an executable that accepts `baseline`, shape. Unexpected keys—including a bearer accidentally returned as `token`— make the harness fail without echoing driver output. -## Synthetic sender projection lane - -The load driver (`audit-core` `scripts/t02_synthetic_load_driver.py`, registered -at `8c8bcf4`) needs exactly one temporary audit-core sender identity. The -reviewed, reusable lane is `scripts/audit_core_synthetic_sender_lane.py`. It -reuses the live-proven registry/reload helpers of the dated Whitehat `-02` -procedure by import and the T02 receipt, attest-chain and preflight gates of -the lease harness; the dated `-01`/`-02` procedures are not copied. - -**Receipt binding.** The identity is named only in the approved T02 receipt -(see `docs/audit-core-database-lease-approval.example.json`), in a -`synthetic_sender` block with exactly these keys: - -```json -"synthetic_sender": { - "identity": "t02-synthetic-", - "source": "", - "tenant": "tenant:trial:", - "expires_at": "" -} -``` - -The lane accepts the receipt only if it also passes the lease harness's own -approval validation (approved status, ≤15-minute window, abort operator, all -three owner acknowledgements) and `synthetic_load.contract_id` equals the -registered driver interface `RAILIANCE-WP-0024-T02-SYNTHETIC-01`. The registry -entry it adds is `sources=[source]`, `tenants=[tenant]`, `may_write=true`, -`may_read=true`, `expires_at=`; projection refuses when the -name, source or tenant already appears on any existing identity. The same -`identity`, `source` and `tenant` values are what the operator passes to the -driver as `AUDIT_T02_SOURCE`/`AUDIT_T02_TENANT`; the confirm string is -`audit-core-synthetic-sender::attended` (printed by preflight). - -**Ordering.** The bearer reaches audit-core only through the registry at -`platform/workloads/audit-core/senders` → ExternalSecret `audit-core-senders` -→ env `AUDIT_CORE_SENDERS`, so both projection and cleanup restart -`deploy/audit-core`. The restarts must stay outside the restart-free proof: -project → restart → T02 exercise → evidence → cleanup → restart. - -**Contained OpenBao access only.** Every live command (lane `project` and -`cleanup`, harness `exercise`) requires `--contained` and refuses unless it runs -as the child of `scripts/openbao-attended-exec.py`, i.e. inside `warden access -openbao-platform-admin-login --exec`: `WARDEN_ROUTING_CATALOG` must be set, -`BAO_ADDR` must be the operator tunnel `http://127.0.0.1:18200`, and -`BAO_TOKEN`/`VAULT_TOKEN`/`OPENBAO_TOKEN` must be absent; `bao` authenticates -through Warden's isolated token helper. There is no token-file path any more: -the harness's former default `https://bao.coulomb.social` was retracted on -2026-09-15 (RMASTER-WP-0020-T09), and a standing 0600 platform-admin token file -is outside the accepted custody path. Outside the login, the lane's `preflight` -runs the cluster checks only; `--contained` adds registry metadata and -`--inspect-registry` (attended, in-memory) adds the conflict check. - -**One login per window.** `scripts/t02-attended-window.py` is the single reviewed -child of the contained login, so the ≤15-minute window needs one MFA login: - -```bash -make audit-t02-attended-window \ - AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json \ - AUDIT_DATABASE_LEASE_DRIVER=/path/to/audit-core/scripts/t02_synthetic_load_driver.py \ - AUDIT_SYNTHETIC_SENDER_CONFIRM=audit-core-synthetic-sender::attended \ - AUDIT_T02_TOKEN_FILE=/abs/0700-dir/t02.bearer \ - AUDIT_T02_EVIDENCE_DIR=/abs/0700-dir/evidence -``` - -It runs, in order: - -1. lane `project` — only within the first 3 minutes of the window and with - ≥10 minutes of identity lifetime left. Writes the bearer to a new mode-`0600` - file (absolute, operator-owned `0700` directory outside every Git work tree; - never stdout), adds the one identity, forces `audit-core-senders` sync, runs - `rollout restart deploy/audit-core`, and waits for one Ready restarted pod. - Any failure removes the identity (restarting again) and shreds the file. -2. port-forward — the lane and harness reach the cluster only via - `ssh railiance01 kubectl`, so the window uses the same path: `ssh -L - 127.0.0.1::127.0.0.1: railiance01 timeout 900 kubectl -n - audit-core port-forward --address 127.0.0.1 deploy/audit-core :8080` - (default port 18081, `AUDIT_T02_PORT` to override). No local kubeconfig is - needed; the remote forward is bounded by `timeout` even if SSH is cut. - audit-core's NetworkPolicies admit only named namespaces, which is why the - operator-side driver needs the forward. -3. harness `exercise --contained` with the registered driver; the driver env is - derived from the receipt: `AUDIT_T02_BASE_URL=http://127.0.0.1:`, - `AUDIT_T02_TOKEN_FILE`, `AUDIT_T02_SOURCE`/`AUDIT_T02_TENANT` from - `synthetic_sender`, `AUDIT_T02_FIXTURE_ID=t02-`, - `AUDIT_T02_OCCURRED_AT=window.start`. Its baseline captures the - post-projection pod UID. -4. evidence — the exercise JSON is written `0600` as - `/-exercise.json`; a value-safe window summary is - written as `-window.json` on success and on abort. -5. lane `cleanup` with that evidence — refuses unless it names this - `approval_id`. Removes only the exact named identity (refusing if the name - carries a different scope), restarts audit-core only if something was - removed, shreds the bearer file, and confirms 1/1 Ready. - -The port-forward is stopped before cleanup and on every exit. Any failure after -projection was attempted (including a failed `project`, whose registry write may -already have reached OpenBao) runs `cleanup --abort-reason` before the window -script exits non-zero. Children's stdout is captured and only their value-safe -JSON is re-emitted. - -The single-step targets (`audit-synthetic-sender-project`, -`audit-database-lease-recovery-exercise`, `audit-synthetic-sender-cleanup`) -remain for the abort path; each costs its own contained login. A missed cleanup -is run as `make audit-synthetic-sender-cleanup -AUDIT_SYNTHETIC_SENDER_ABORT_REASON=...` and is a no-op if nothing remains. - -If cleanup is missed, audit-core's `expires_at` enforcement makes the identity -inert at window end; the attended cleanup is still required to remove it. -Projection and cleanup each restart audit-core once (Recreate, no surge), so -production senders see two short retryable gaps per window; announce them with -the window. - -The contained login discards child output and fails closed (exit 12, token -revoked) on any byte the child prints. The window script therefore prints -nothing: its summary, exercise evidence or `window-error-*.json` land only in the -evidence directory. Single-step targets require `AUDIT_T02_RESULT_FILE` so the -lane/harness write their JSON there instead of stdout. - ## Attended sequence Keep one terminal attached from baseline through recovery. A value-handling @@ -181,15 +63,10 @@ bodies and clear all transient JSON from memory or mode-`0600` temporary files. 3. Inside the attended process, list only lease handles below `sys/leases/lookup/database/creds/audit-core-runtime`. OpenBao's lease lookup returns issue/expiry/TTL metadata but not the leased username, so exact - selection is deliberately strict: at most 8 handles may exist under that - exact runtime prefix; exactly one must have been issued within 5 seconds of - the current database Secret refresh (the mounted credential), with at least - five minutes of TTL left; every other handle must be an older predecessor. - ESO refreshes every 15 minutes while the role TTL is 15 minutes (max 1 hour), - so a predecessor can legitimately still be live; it is never revoked. Abort - if no handle, several handles, or a newer unmatched handle match the refresh. - (2026-10-14: the first attended window aborted here under the earlier - "exactly one handle" rule with two live handles.) Retain only a one-way + selection is deliberately stricter: there must be exactly one live handle + under that exact runtime prefix, and its issue/expiry interval must contain + the current database Secret refresh time. Abort for database-owner + disambiguation if zero or multiple handles exist. Retain only a one-way handle fingerprint in evidence and clear the lookup response immediately. 4. Recheck that the Secret `resourceVersion`, pod UID, and selected handle have not changed. Revoke only that handle. Do not revoke the role prefix and do @@ -205,16 +82,17 @@ bodies and clear all transient JSON from memory or mode-`0600` temporary files. 7. Prove recovery: `/readyz` returns 200, the retrying synthetic request reaches accepted or duplicate, the pre-exercise event remains readable, pod UID is unchanged, and restart count is unchanged. -8. Stop the load, confirm the predecessor lease is revoked, record the - evidence, then remove the identity with the synthetic sender lane cleanup - (which restarts audit-core after the evidence is captured). The replacement remains managed by ESO +8. Stop the load, remove its separately approved fixture/identity, and confirm + the predecessor lease is revoked. The replacement remains managed by ESO and expires through its normal database role TTL. -Only inside the approved window, with the receipt and reviewed driver, run the -whole window through `make audit-t02-attended-window` (see the synthetic sender -section above); the exercise step alone is -`make audit-database-lease-recovery-exercise`, which also runs inside the -contained login. +Only inside the approved window, with the receipt and reviewed driver: + +```bash +make audit-database-lease-recovery-exercise \ + AUDIT_DATABASE_LEASE_APPROVAL=/path/to/approved-receipt.json \ + AUDIT_DATABASE_LEASE_DRIVER=/path/to/approved-load-driver +``` The exact confirmation string is embedded in the target. The harness rechecks the Secret/pod/lease immediately before revocation, requires at least five diff --git a/docs/cadence-receiver-custody.md b/docs/cadence-receiver-custody.md deleted file mode 100644 index 9022f6a..0000000 --- a/docs/cadence-receiver-custody.md +++ /dev/null @@ -1,154 +0,0 @@ -# Hourly cadence receiver admission and Qonto sender custody - -NK-WP-0035 / RPF-WP-0059 / CCR-2026-0047. User explicitly approved this complete production packet on 2026-10-15 -with "Approve packet and proceed", including the new sender grant. The founder's timing-rollout approval did not approve this new bearer -or ESO reader. The approved scope binds platform operator, Audit Core, rapp-qonto and -qonto-assistant owner roles; native prerequisites still apply. - -The prepared change preserves every existing sender credential, source/tenant -scope and other registry field. It updates only Approval Engine's canonical -cadence and four heartbeat waits (2f=7200s; receiver grace1.5 gives3f=10800s), -and adds one new exact sender: - -| Coordinate | Proposed value | -| --- | --- | -| Sender | rapp-qonto-collector | -| Source | qonto-assistant | -| Tenant | tenant:friendly:binky | -| Permission | write=true, read=false; receiver own-source reconciliation remains contract-defined | -| Evidence | envelope load-bearing/redact; complete source cadence retains each class's kind | -| Heartbeat | one source beat maps once to audit-core.heartbeat, data.class=audit.deny | -| Custody | platform/workloads/rapp-qonto/audit-sender: AUDIT_TOKEN,CUSTODY_REQUEST | -| Reader policy | workload-kv-read-rapp-qonto-audit; exact data path read only, no metadata/list | -| Kubernetes role | external-secrets-rapp-qonto-audit; external-secrets/external-secrets,15m | -| Delivery | namespace-restricted openbao-rapp-qonto-audit store; rapp-qonto/rapp-qonto-audit-sender:bearer | - -Plan without credentials or network: - -```bash -python3 scripts/credential-change.py validate CCR-2026-0047 -python3 scripts/cadence_receiver_custody.py plan -``` - -The plan hashes CCR, exact policy/projection, executable helpers and enforcing -receiver parser sources, raw source declarations and Approval metadata proposal. -Record owner approvals in the CCR, regenerate the plan, and review its resulting -SHA256. Any subsequent source change requires a newly reviewed packet. The -runner verifies the exact packet hash; a proposed CCR refuses before native -calls. The immutable deployed receiver must support full cadence parsing and -per-instance reconciliation, verified with fixture-only native sender parsing -before any credential read. A source compatibility test alone cannot satisfy -that deployed gate. - -Run `apply` only inside the existing attended Warden platform-admin `--exec` -envelope. Use credential routing first; do not export a token into a shell. -Before starting OIDC, remove all three ambient token aliases from the launcher -environment without reading or printing their values: - -```bash -env -u BAO_TOKEN -u VAULT_TOKEN -u OPENBAO_TOKEN \ - warden access openbao-platform-admin-login --exec -- \ - python3 /home/worsch/railiance-platform/scripts/cadence_receiver_custody.py apply -``` - -This is required for the custody and fresh observer-readback launches. The -initial native attempt demonstrated that OPENBAO_TOKEN is not scrubbed by the -existing Warden wrapper; the contained helper correctly refused it. Session -self-revocation was confirmed and no custody mutation occurred in that attempt. -Do not inspect, copy or relax the guard for an ambient credential. - -The owner wrapper must preserve its independent exit/self-revocation receipt. -The contained command is: - -```bash -python3 scripts/cadence_receiver_custody.py apply \ - --kubeconfig '' --server '' \ - --expected-receiver-image 'forgejo.coulomb.social/coulomb/audit-core@sha256:' \ - --confirm-packet-sha256 '' \ - --receipt '' -``` - -The command is silent; all subprocess outputs are captured, exceptions expose -fixed diagnostics only, and the0600 O_EXCL receipt contains metadata. It checks -the primary cluster UID, single ready receiver/pinned image, existing registry, -exact projection and custody provenance before reader grant writes. It extends -the coding-agent deny boundary for only the new data/metadata paths, provisions -CAS0 version1 with request provenance, and merges with exact registry-version -CAS. A concurrent edit fails; it never retries against an unseen registry. -It verifies exact reader path and scope/TTL, denied registry/sibling/metadata/list, -wrong service identity, revoked reader session and deny-boundary precedence; -then delivers ESO, compares bearer values privately, refreshes the receiver -registry secret, restarts the receiver and verifies loaded registry equality. -The new store is restricted to rapp-qonto, and source projection shape forbids -extra data or arbitrary templates. API-observed default fields are explicitly -pinned so a legitimate resumed projection remains equal. - -A partial failure retains custody/version1 for investigation. Inspect its -nonsecret receipt, confirm unchanged reviewed source, and rerun with a new -receipt path plus `--resume`. The same request's durable token is reused; -conflicting versions, provenance, registry scope or role/policy drift refuse. -No helper rotation, secret deletion or broad rollback occurs. A restart after -successful delivery may be repeated by an explicitly resumed attempt; it -preserves the token and registry content. - -Success is `delivered_pending_native_producer_and_independent_acceptance`. -It does not prove collector source capture, denial refusal, bearer revocation, -independent receiver reads, elapsed-hour heartbeat observation, or profile -acceptance. Read-only observer custody must remain independent; do not upgrade -the collector bearer. Keep the runtime active using the owner's authorized -health polling for the real hour, without changing scale-to-zero. A session -lasting15m must close normally; use a fresh attended session for hour-end reads. -Source-global missing-heartbeat findings must be interpreted alongside actual -active-instance inventory: intended scale-to-zero is not unexpected active -process silence. Preserve shutdown/restart scope separately. - -Residual native checks include isolated wrong-namespace store denial and -former-valid collector bearer revocation, alongside the source/tenant/read -refusal and per-instance observer matrix. The runner's wrong-service-identity -probes do not separately isolate wrong namespace while preserving the correct -service account name. Record that check honestly rather than treating the -combined refusal as the complete namespace test. - -## Independent hour-end custody readback - -Use a fresh attended platform-admin envelope after the observation hour; the -initial session must not be held open. `scripts/cadence_observer_readback.py` -reuses the existing uniquely selected independent read-only full-tenant -operator credential in process memory. It changes no credential or workload -and never grants the collector archive read permission. Its only transport -is a temporary primary-cluster localhost port-forward that is terminated on -exit, with redirects and environment HTTP proxies disabled. - -```bash -python3 scripts/cadence_observer_readback.py \ - --source qonto-assistant --tenant tenant:friendly:binky \ - --instance '' \ - --since '' \ - --until '' \ - --event-id '' \ - --event-id '' \ - --kubeconfig '' --server '' \ - --expected-receiver-image 'forgejo.coulomb.social/coulomb/audit-core@sha256:' \ - --receipt '' -``` - -Provide all capture event IDs (maximum256, unique). The reader only opens -those IDs, the exact source/tenant/instance reconciliation query, and the -protected global stream-findings surface. It outputs only findings for the -selected source; no unrelated findings or request payload is persisted. -Every event must match exact ID/source/tenant/instance and acceptance window. -The sanitized event projection includes instance/sequence, action, observed -and accepted timestamps, and exact heartbeat coverage. Actual custody -accepted_at can distinguish a duplicate delivery receipt from original custody -time; a collector duplicate200 is not itself first-acceptance timing proof. -Approval Engine source/tenant is also allowed; omit --instance for its -instance-free lifecycle records. The helper does not invent process identity -or sequence for Approval and labels its conclusion as a bounded source -lifecycle acceptance window. Qonto still requires an explicit exact instance. -Receiver-normalized timezone spelling is compared by timestamp semantics; -source, tenant and actual instance bindings remain exact. - -Exit0 reports `independent_custody_readback_complete`, which proves only the -bounded supplied custody reads. The source snapshot, contiguous startup/ -periodic capture, process inventory and negative observation matrix remain -independent inputs to Kings Guard's assessment. diff --git a/docs/coding-agent-openbao-identity.md b/docs/coding-agent-openbao-identity.md index 0a20b63..6762bf7 100644 --- a/docs/coding-agent-openbao-identity.md +++ b/docs/coding-agent-openbao-identity.md @@ -55,8 +55,8 @@ coordinates: | --- | --- | | `aud` / client id | `codex-railiance-platform` | | `sub` | `service:codex:railiance-platform` | -| `principal_type` | `service` today; `agent` after the KeyCape change (RPF-WP-0065). The role accepts both during the transition | -| `tenant` | `tenant:platform` | +| `principal_type` | `service` | +| `tenant` | `tenant:coulomb` | | `roles` | contains `coding-agent` | | requested scope | `openbao:login` | @@ -115,16 +115,3 @@ check. Revoke the issued OpenBao token if it remains live. differs from the reviewed body or its verifier fails. - Revoke the test OpenBao token, disable the KeyCape client, and rotate its confidential secret after suspected disclosure. - - -## Source clarification — 2026-10-11 - -The tenant table now follows current `coding-agent-jwt-role.json`, KeyCape -`config/service-clients.example.yaml` and the 2026-09-27 Platform tenant review. -This correction grants no additional policy or custody access. Historical -AppRole verification and earlier migration examples above do not establish -current named coding-client registration, confidential-secret delivery or a -KEY39 producer admission. The source-only candidate in -`proposals/2026-10-11-key39-coding-client-custody-proposal.json` is not an installed -CCR, grant, catalogue entry or credential request. Follow its separate owner -and founder prerequisites before any client-secret retrieval or JWT exchange. diff --git a/docs/contracts/secrets-engine-openbao-jwt-login.yaml b/docs/contracts/secrets-engine-openbao-jwt-login.yaml deleted file mode 100644 index c91616e..0000000 --- a/docs/contracts/secrets-engine-openbao-jwt-login.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Consumer contract for secrets-engine service-JWT login to OpenBao (CCR-2026-0038). -# Published by railiance-platform 2026-10-10 after the login proof passed -# (secrets-engine docs/evidence/2026-10-10-keycape-openbao-probe.json, a963a77). -# Non-secret. Point SECRETS_ENGINE_OPENBAO_JWT_LOGIN at a copy of this file. -# Identical to the founder's probe input; that local copy may be kept as is. -mount: keycape-services -role: secrets-engine-openbao -bound_issuer: https://kc.coulomb.social diff --git a/docs/credential-change-approval.md b/docs/credential-change-approval.md index 8dc31e8..6d10430 100644 --- a/docs/credential-change-approval.md +++ b/docs/credential-change-approval.md @@ -303,70 +303,3 @@ Use the whynot-design npm token lane as the pilot: Once that path feels good, reuse it for the sibling workload-KV lanes and the credential broker's OpenBao token-role gates. - -## Self-Service Intake (RPF-WP-0061) - -Clients file standard CCRs without a railiance-platform worker session. The -platform's judgement is in [`registry/ccr-standard-profile.yaml`](../registry/ccr-standard-profile.yaml); -`review-standard` applies it, and `.forgejo/workflows/ccr-intake.yaml` re-checks -it in CI. Allowed submitters: **ops-mason** only (profile `submitters`). - -**In profile** means: a `workload-kv-read` lane on mount `platform`; one exact path -`platform/workloads//[/]` or `platform/suppliers//`; -`metadata_read: false`; a policy file equal to the generated HCL; Kubernetes auth -with role `external-secrets-*`, audience `openbao`, all ttls 15m or less, -`token_no_default_policy: true`; one service account in one non-platform -namespace; risk `low` or `medium`; and no other live CCR reading the path. -Everything else (OIDC roles, AppRole, token roles, second readers, high risk) -still goes to the `railiance-platform` inbox. - -### Client steps - -Run these in a railiance-platform checkout that is up to date with `main`: - -```bash -# 0. Generate the policy instead of writing it, so it is byte-equal by construction. -python3 scripts/credential-change.py render-policy --kv-path > -# Optional: plan_ref: {repo, path, commit} in the draft links the client's -# construction plan; submit keeps it and review-standard only checks its shape. - -# 1. File the draft. Allocates the CCR id, copies the policy, runs the review. -python3 scripts/credential-change.py submit \ - --ccr --policy --slug \ - --requester ops-mason --push --notify -# [NEEDS-PLATFORM] lines mean out of profile: stop. --notify messaged -# railiance-platform. Do not edit the review comment to get past it. - -# 2. Wait for the "CCR intake gate" run on ccr/- to pass, then -# fast-forward main to it. Never merge a red or unchecked ccr/* branch. -git switch main && git merge --ff-only ccr/- && git push origin main - -# 3. Open the founder decision and commit the decision id. -python3 scripts/credential-change.py request-decision -git commit -m ": request founder decision" -- credential-change-requests/-.yaml -git push origin main -``` - -After step 3, the client waits and does not edit `status` or `review.comments` -itself. Approvals reach the file only through `sync-decision`, even when the -founder approved the client's plan elsewhere. The hub decision is the single -approval record for both the client plan and the CCR; clients copy their plan -approval *from* it after the sync. The **founder** resolves the State Hub decision. -Then a **platform session or the founder** runs `sync-decision `. Clients do -not run `sync-decision` on their own CCRs. State Hub does not authenticate who -resolved a decision (`decided_by` is free text). Until founder approval can be -verified (RPF-WP-0061 follow-up B), the sync must come from someone other than -the requester. After the CCR is `approved`, the usual steps apply: -`confirm-binding` against native state, apply (ops-mason under -ops-mason-construct for in-profile lanes, otherwise the applier), then -`record-evidence`. - -### Known limits - -- All workstation agents push to Forgejo as the same account, so `ccr/*` - cannot yet be restricted to ops-mason. The submitter list and the - merge-only-green rule are enforced by tooling and convention, not by Forgejo. - The main-branch `check-ids` job and `check-branch` detect violations after the - fact (follow-up C). -- The automated review is a source review only. It never sets `approved` and - never touches OpenBao. diff --git a/docs/credential-lane-designs/activity-core-repo-manager-forgejo-read.md b/docs/credential-lane-designs/activity-core-repo-manager-forgejo-read.md index 5596592..7d99db7 100644 --- a/docs/credential-lane-designs/activity-core-repo-manager-forgejo-read.md +++ b/docs/credential-lane-designs/activity-core-repo-manager-forgejo-read.md @@ -1,17 +1,10 @@ -# Activity Core repo-manager Forgejo read lane (no CCR filed) +# Activity Core repo-manager Forgejo read lane (CCR-2026-0034, number to confirm) -Status: **proposed design only**, drafted 2026-10-04 for STATE-WP-0094 memo 3 +Status: **proposed**, drafted 2026-10-04 for STATE-WP-0094 memo 3 (`freshness-runner-deploy-trial-e77f8b4`, accepted by the founder 2026-10-04, approval `4ae04c8e`) and RMGR-WP-0020-T04. Nothing here is approved, applied or -provisioned. No Forgejo, OpenBao or cluster object was created. - -An earlier draft of this page cited CCR-2026-0034. That number is the KeyCape -secrets-engine operator reader -(`credential-change-requests/CCR-2026-0034.yaml`, applied 2026-10-07). No -Forgejo-read CCR exists for the Activity Core worker. CCR-2026-0029 and -CCR-2026-0030 are ops_run worker tokens, and CCR-2026-0031 is the core-hub -Forgejo source read bound to another namespace. Do not treat this design as a -CCR id. Assign the next free id only when this lane is admitted. +provisioned. No Forgejo, OpenBao or cluster object was created. Confirm the CCR +number with the CCR tooling before registering; 0033 was the highest seen. ## Why @@ -54,7 +47,7 @@ worker's other secrets and is the default here. ## Draft manifests (land only after metadata apply) ```yaml -# No CCR id. Do not apply until this design is admitted, numbered and approved. +# CCR-2026-0034 (to confirm). Do not apply until approval and confirm-binding. apiVersion: external-secrets.io/v1 kind: ClusterSecretStore metadata: diff --git a/docs/credential-lane-designs/application-backup-delivery-2026-10-08.md b/docs/credential-lane-designs/application-backup-delivery-2026-10-08.md deleted file mode 100644 index 0028572..0000000 --- a/docs/credential-lane-designs/application-backup-delivery-2026-10-08.md +++ /dev/null @@ -1,178 +0,0 @@ -# Application backup credential delivery — 2026-10-08 - -Prepared for RPF-WP-0053-T02/T05, CSOC-WP-0008-T02 and EMAIL-WP-0007-T03. -Both requests are **draft**, bindings unconfirmed and delivery admission false. -This packet creates no provider identity, OpenBao structure/value, ServiceAccount, -node file, backup capture or timer. Source preparation is not owner acceptance. - -| Consumer | Reader request | Authoritative KV pair | Node destination | -| --- | --- | --- | --- | -| Coulomb paired archive | CCR-2026-0036 | platform/workloads/coulomb-social/backup-primary | /etc/coulomb-social-backup/primary.env | -| Email ledger archive | CCR-2026-0037 | platform/workloads/email-connect/backup-primary | /etc/email-connect-backup/primary.env | - -Both records contain exactly AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY. Each -has its own Scaleway IAM application/key, Kubernetes ServiceAccount namespace, -OpenBao role and read policy. The destinations are root:root0600 in root:root0700 -directories. Workers receive provider credentials; the delivery reader alone -uses OpenBao, without a persistent bootstrap credential. Age decryption identities -belong to independent recovery custody. - -## Public review sources - -- credential-change-requests/CCR-2026-0036-coulomb-social-backup-primary.yaml -- credential-change-requests/CCR-2026-0037-email-connect-backup-primary.yaml -- openbao/policies/workload-kv-read-{coulomb-social,email-connect}-backup-primary.hcl -- openbao/backup-primary-serviceaccounts.yaml: two accounts, automount=false; - no RoleBinding, Secret, mounted JWT or workload deployment. -- scripts/application_backup_credentials.py: dependency-free node reader/delivery. -- scripts/render_backup_credential_contract.py: existing platform validator and - PyYAML export YAML source to a public JSON contract; the node needs no PyYAML. -- tests/test_application_backup_credentials.py: synthetic fixtures only. - -The exporter verifies exact reader coordinates, delegated metadata-applier scope -and policy equality. It embeds source CCR/policy hashes. After every owner/status -update, regenerate and review the JSON export and pin **its** SHA256 in the node -command; an old draft export cannot execute. The CCR remains the source of truth. - -## Required owner setup, in order - -1. Admit the proposed railiance01 root executor under T01. It already needs k3s - administration for coherent capture; this design explicitly records that node - administrators and namespace writers can exercise the reader identity. This - is not a narrower alternative to root execution admission. -2. Survey the existing bucket policy, IAM principals and lifecycle. Preserve - native Barman/Forgejo principals and existing policy statements. Create two - separate IAM applications; another key on an existing broad application does - not establish isolation. Bind each caller to its exact application archive - prefix with IAM plus bucket policy, including explicit effective denial where - project-wide IAM would otherwise allow sibling access. Review the complete - merged policy before its replacement; this packet supplies no blind overwrite. -3. Admit each CCR with platform-owner and its named application-owner reviews, - confirmed namespace/account/audience, and a reviewed structure change. Use - railiance-platform's existing delegated metadata applier for policies/roles; - ops-mason now surveys read-only under ops-mason-survey (MASON-IN-0004), and - cannot apply structure. The existing kubernetes auth mount must use its admitted - reviewer; no TokenReview authority is granted to these delivery accounts. -4. Provision each provider **pair atomically** under an explicit platform custody - act, initial KV CAS=0. Retain only its version receipt. The generic Warden - paste-once desk currently runs a single-field KV PUT, replacing the record: - two desk runs would discard the first field. It is unsuitable for this pair. - A reviewed pair-aware custody child/front door is still required under T06; - neither the node reader nor the administration-login lane provisions values. -5. Verify positive reader identity and negative wrong namespace/account/audience, - sibling GET, metadata GET, parent LIST, KV write and administration denials. - Verify provider conditional multipart publication, version GET, foreign-prefix - and retained-delete denial. These are contained owner proofs, not regression - tests against production. Record native evidence before setting the CCR's - delivery admission booleans and source_ref; do not infer them from code tests. -6. Export the final admitted CCRs, review their hashes, create only the designated - private node directories and stage public source/export artifacts. Run delivery - at the pinned positive KV version; preserve the metadata receipt independently. - Admission covers initial delivery, not capture, service activation or rotation. - -Scaleway destination is s3://railiance-platform-pg-backup with endpoint -https://s3.nl-ams.scw.cloud. Prefixes are -platform-pg/application-archives/coulomb-social/ and -platform-pg/application-archives/email-connect/. Publisher rights are conditional -multipart create/parts/completion, abort of its own unfinished uploads, HEAD, -version-pinned GET and prefix-constrained listing only if needed. Deny retained -object/version deletion, native/sibling prefixes, bucket/IAM administration and -escrow. Owner admission must also reconcile the existing 30-day lifecycle with -14 daily/4 weekly protected recovery points, capacity and independent receipts. - -Scaleway's [IAM/bucket policy guide](https://www.scaleway.com/en/docs/object-storage/api-cli/combining-iam-and-object-storage/) -explains the project-wide IAM boundary and granular bucket policy. No effective -provider boundary is claimed before native proof under these specific callers. - -## Routing verification - -Installed Warden 0.1.2 was rebuilt from the corrected checkout, preserving -phase-memory. Bundled catalog hash 5e6b217aa1f6 matches current source. WARDEN-WP-0043 -fixes keyword collisions without granting authority: - -```bash -warden plan "attended OpenBao platform administration to configure read policies and auth roles for Coulomb and email backup delivery" --json -warden plan "provision a Scaleway object storage backup publisher key pair into OpenBao custody" --json -``` - -The first selects openbao-platform-admin-login, founder_required/oidc_login; -survey still selects the read-only ops-mason ceremony. The second selects -openbao-api-key, founder_required/paste_once_provision: this remains a generic -owner pointer, not an admitted pair writer. Do not run the single-field desk for -these pairs or use the administration identity as an unreviewed KV writer. -An attended OIDC command must use Warden's contained child envelope, including -its output and self-revocation requirements, for its exact reviewed operation. - -## Preview and admitted delivery shape - -Public export (platform environment with the existing PyYAML validator): - -```bash -python3 scripts/render_backup_credential_contract.py \ - --application coulomb-social \ - --ccr credential-change-requests/CCR-2026-0036-coulomb-social-backup-primary.yaml \ - --output /tmp/coulomb-backup-reader-review.json -python3 scripts/application_backup_credentials.py \ - --application coulomb-social --ccr /tmp/coulomb-backup-reader-review.json -``` - -Repeat with email-connect/CCR-2026-0037 and its own output. Default delivery preview -has no network, TokenRequest or filesystem effects. --execute requires the exact -reviewed export hash, an applied/verified/active CCR, confirmed bindings, named -owner reviews with source references, native provider/reader proof and admitted -node execution. The implementation does not authenticate these source statements; -the owner must bind and approve the exact immutable export before the node run. - -After those owner steps, the node command shape is: - -```text -sudo python3 /opt/railiance-platform/scripts/application_backup_credentials.py \ - --application APPLICATION \ - --ccr /etc/APPLICATION-backup/credential-reader.json \ - --expected-ccr-sha256 REVIEWED_JSON_EXPORT_SHA256 \ - --expected-kv-version APPROVED_POSITIVE_KV_VERSION --execute -``` - -This is not yet an executable activation instruction: neither installed source -placement nor the approved values above exist. Source/export transfers contain -no credentials. Do not transport provider pairs through scp, chat or /tmp files. - -The reader pins kube-system UID a553c742-0115-43d4-99a4-a5ca56fe0786 before issuing -a ten-minute TokenRequest JWT for backup-primary-delivery/openbao in its own -namespace. Kubernetes has a ten-minute minimum; the resulting OpenBao token has -a separate five-minute hard maximum and four-use limit, one policy and no default -or identity policy. JWT and token stay in process memory. The helper checks the -returned JWT binding/expiry, OpenBao token bounds and exact current KV version, -revokes its token **before** file publication and verifies contained readback. -Backend bodies, exception text and key hashes never enter receipts or outputs. -Proxy environment and HTTP redirects are disabled for the OpenBao exchange. - -Initial file publication uses a private temporary file, fsync and no-overwrite -hard link. Symlink ancestors, unsafe target modes/owners, aliases and special -files refuse. A lock excludes overlapping deliveries. SIGTERM unwinds through -revocation; host loss/SIGKILL can leave a short-lived token until expiry and -requires metadata reconciliation, never an automatic success claim. A crash after -publication but before receipt can resume only by a new scoped read with exact -file equality. Different existing bytes require a separate version-guarded -rotation contract; this helper cannot overwrite them. - -## Evidence and remaining gates - -Thirty synthetic delivery cases and four YAML/export cases pass, alongside thirty primary-archive/retention -cases. They cover both profiles, draft/source/owner admission, narrower token -checks, revocation failure, private-file attacks, payload injection, version -changes, atomic failures and identical-file recovery. Current draft exports and -policies validate without errors; the expected unconfirmed-binding warning blocks -apply. Warden's complete regression gate passes, with the human stdout test now -explicitly modelling a human independently of an agent session environment. - -Shared security orientation was retrieved as emergent-security 0.2.6, digest -8d1d6a9d4d07fec7b619618de7dfe2be2254c1790f85f4198c7fe19db9751c02. It was withheld -for source drift, so current ops-mason INTENT, custodian agent orientation and -informed-decision README were read directly. No card was treated as authority. - -No live credential delivery, provider upload or timer activation is evidenced. -RPF-WP-0053-T02/T05/T06 retain the custody/admission work. T03/T04 retain the first -publication, independent application recovery, schedule and monitoring/retention -gates. CSOC-WP-0008 and CSOC-WP-0010 remain active; this preparation alone cannot -complete either plan. diff --git a/docs/credential-lane-designs/application-backup-pair-custody-2026-10-08.md b/docs/credential-lane-designs/application-backup-pair-custody-2026-10-08.md deleted file mode 100644 index 43519c8..0000000 --- a/docs/credential-lane-designs/application-backup-pair-custody-2026-10-08.md +++ /dev/null @@ -1,50 +0,0 @@ -# Backup publisher pair custody — preview, 2026-10-08 - -RPF-WP-0053-T06. The node reader in `scripts/application_backup_credentials.py` -can deliver an already stored pair. It cannot create one. Warden's paste-once -desk writes one field per PUT and would drop the other half of -`AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY`. - -`scripts/application_backup_pair_custody.py` is the prepared front door for -both draft records: - -| Application | CCR | KV path | -| --- | --- | --- | -| coulomb-social | CCR-2026-0036 | platform/workloads/coulomb-social/backup-primary | -| email-connect | CCR-2026-0037 | platform/workloads/email-connect/backup-primary | - -Default command: - -```bash -python3 scripts/application_backup_pair_custody.py --application email-connect -``` - -The preview reads the draft CCR and prints field names, CAS 0 and one intended -write. `writer_admitted` is false. There is no stdin, provider call, OpenBao -call, key generation, file write or timer change. - -`provision()` is the future attended shape, covered by synthetic tests only: - -- writer token policies must be exactly `backup-pair-writer`, with no default - policy and no `workload-kv-read-*-backup-primary` reader policy -- TTL at most 5 minutes and at most 2 uses, then revoke -- metadata absence is required; an existing version is not overwritten -- one HTTP-style body carries `cas: 0` and both fields -- the receipt keeps version and request id, never the values - -The founder admitted this identity on 2026-10-08. The words used were -"bucket-pair-writer"; the OpenBao policy name is `backup-pair-writer`. -No implemented SecurityCaseType covers the pair: `workload-kv-read` is still -only an inventory candidate, and ops-mason can survey structure but cannot -write it. The attended install is `scripts/install-backup-pair-writer.sh`. -The installed policy matches the file -(`86e435900bc6912fada10c526bfa865127a0e28acd328cf5f5f69e8d62bda310`), so -`WRITER_ADMITTED` is true. The command-line preview still does not write. - -Both KV paths are absent. The bootstrap key is denied IAM application and -policy list (HTTP 403), so it cannot create the two publisher applications. -The live bucket policy `resource-control-scoped-backup` allows one principal, -`application_id:ee80333e-5bab-4a04-85a9-5bdb46b7b3c1`, under `platform-pg/*`, -including object delete. No second application was added and no key was -stored. CCR-2026-0036 and CCR-2026-0037 stay draft. The next founder act is -creating the two IAM applications in the console, then one CAS=0 write each. diff --git a/docs/credential-lane-designs/attended-service-credential-delivery.md b/docs/credential-lane-designs/attended-service-credential-delivery.md deleted file mode 100644 index f3bb2cb..0000000 --- a/docs/credential-lane-designs/attended-service-credential-delivery.md +++ /dev/null @@ -1,22 +0,0 @@ -# Attended service credential delivery - -Request type `attended-service-credential-delivery` (`scripts/attended_service_delivery.py`), first used by -CCR-2026-0055 (NetKingdom `svc-privacyidea`, NK-WP-0056-T04). - -Some services keep a credential in their own encrypted store and accept it only through an administrator API -that a person operates. privacyIDEA's LDAP resolver is one: its `BINDPW` lives encrypted in the privacyIDEA -database and only the `pi-admin` API may change it. ESO cannot deliver such a value, and an unattended job would -need the administrator password standing in the cluster. - -The contract therefore holds the value in OpenBao and nothing else: - -- `openbao.auth` is the attended `platform-admin` OIDC session on `netkingdom`; `runtime_access: false`; - `initial_cas: 0`. No policy, auth role, ESO object or other access may be declared. -- The runtime frontdoor stays disabled (`access_frontdoor.resolvable: false`). -- `delivery` names the service, the target inside it, the owner's procedure and the gate (the attended person and - the proof they run). -- Approval follows `review.required_approvers`; from `applied` onward every listed role must have approved. - -The owner's procedure reads the value in memory under the attended session and writes it into the service; the -person runs it and proves the write with an MFA login through the service. A rotation needs no decrypted custody copy. Modelled on -`attended-host-key-delivery` (CCR-2026-0028), which delivers to a host file instead of a service. diff --git a/docs/credential-lane-designs/bounded-executor.md b/docs/credential-lane-designs/bounded-executor.md deleted file mode 100644 index d7de9dc..0000000 --- a/docs/credential-lane-designs/bounded-executor.md +++ /dev/null @@ -1,107 +0,0 @@ -# Bounded executor: OpenBao lanes and audit sender (owner answer, NK-WP-0056-T02) - -Owner answer from railiance-platform to NetKingdom's request -(net-kingdom `docs/proposals/2026-10-31-nk56-t02-owner-requests.md` §railiance-platform; -State Hub message `9d08288d`). Basis: Bounded Execution Admission v0.1 (proposed) and its pilot -example `bind-rotation-svc-authelia.yaml`. Workplan: `workplans/RPF-WP-0069-bounded-executor-lanes.md`. - -Everything here is source and proposals. No role, policy, Secret or KV value was created or read. -CCR-2026-0058 is `proposed`; approvals and the founder's choices below stay open. - -## Q1. Kubernetes auth role for `sso/nk-bind-executor`: which request types? - -**Answer: one role per lane, all bound to the one ServiceAccount, two request types.** - -The existing `workload-kv-read` contract requires a role to carry exactly its own policy -(`scripts/credential-change.py` `validate_workload_kv_read`, "openbao.auth.policies must contain exactly -the policy used by the auth method"; applier check "must contain only openbao.policy_name"). We keep that -rule. The executor logs in once per lane with the same projected ServiceAccount token (audience -`openbao`); each token carries one policy and lives at most 15 minutes. A combined role would make every -lane's review depend on every other lane. - -| Executor need | Request type | Status | -| --- | --- | --- | -| Write the next version of `platform/workloads/net-kingdom/directory-binds/svc-authelia` (CAS) and read it for checks/revert (T06) | `workload-kv-write-next-version` (**new**, `scripts/workload_kv_write_next_version.py`) | Type in source; CCR-2026-0058 `proposed` | -| Read `platform/workloads/net-kingdom/synthetic-journey/syn-journey` (`PASSWORD`, `TOTP_SEED`) for the journey (T10) | `workload-kv-read`, native workload login (`token_self_lifecycle: true`, `metadata_read: false`), as the native reader proof in CCR-2026-0053 | Not drafted; see below | -| LLDAP credential to set `svc-authelia`'s password | `workload-kv-read` of a new custody entry, after secrets-engine answers its Q2 | Not drafted | -| audit-core sender bearer (receipts) | `workload-kv-read` with ESO delivery, as CCR-2026-0021/0022 | Not drafted; see Q2 | - -### The new type `workload-kv-write-next-version` - -Prepared the way `attended-service-credential-delivery` was added: module -`scripts/workload_kv_write_next_version.py`, tests `tests/test_workload_kv_write_next_version.py`, dispatch in -`scripts/credential-change.py` (validate, plan, operator-commands, runbook). It is **contract-only**: there -is no delegated applier (it is not in `LANE_MODULES`). The attended platform-admin applies policy and role -from a reviewed commit, like CCR-2026-0052..0055. - -The contract refuses anything wider than: - -- one exact `platform/workloads/...` path on mount `platform`; no wildcard, no sibling; -- policy (generated by `policy_hcl`, artifact `openbao/policies/.hcl`): - `read` + `update` on `platform/data/` only (no `required_parameters`: OpenBao applies it to reads too, NK-WP-0056-T12), - `allowed_parameters = {data, options}`; plus `lookup-self`/`revoke-self`. No `create` (a missing path is - refused, so the executor cannot mint new entries), no `metadata/`, `delete/`, `destroy/`, `undelete/`, list; -- key-level `cas_required = true`, set by the attended platform-admin at setup. The engine then refuses - every write that does not name the version it replaces; races and replays fail closed in OpenBao, not in - executor code. (`required_parameters = ["options"]` alone cannot force `options.cas`; the key setting does.) -- Kubernetes auth: one ServiceAccount, one namespace, audience `openbao`, `ttl`/`token_max_ttl`/ - `token_explicit_max_ttl` = 15m, `token_no_default_policy: true`, exactly one policy; -- `access_frontdoor.resolvable: false`; `agent_fence.data_and_metadata_denied: true` (coding agents fenced); -- `admission` names the standard and executor identity; full `verification` and `lifecycle`; explicit - approval by every `review.required_approvers` role from `approved` onward. - -Revert (`restore-kv` in the example) is a further next-version write of the prior value with CAS. Reading -an older version is a `read` on `data/?version=N`, which the policy already grants; no extra -capability is needed. - -**What the role does not do.** OpenBao does not evaluate the admission. The role is standing: a compromised -`nk-bind-executor` ServiceAccount can replace the svc-authelia bind (sign-in outage on Authelia's next -restart) but reach nothing else. The admission window, consume-once and plan digest are enforced by the -executor, approval-engine and access-engine. If that is not enough, the founder can choose a -window-scoped role instead (created by the attended platform-admin at the window start, removed after); -that removes the "unattended" property of T06 for the platform side. Recommendation: standing role for the -pilot, because the reachable damage is one read-only directory bind already covered by an attended -recovery path (CCR-2026-0053 `lifecycle.compromised`). - -### Journey secrets (T10) - -CCR-2026-0057 is `attended-service-credential-delivery` with `runtime_access: false`; its contract forbids -any role or policy on that path. A `workload-kv-read` request for the executor therefore changes -CCR-2026-0057's stated boundary. It must say so, cite CCR-2026-0057, and be approved by both -platform-owner and net-kingdom-owner. CCR-2026-0057's attended procedure stays valid. Shape: role -`net-kingdom-bind-executor-syn-journey-read`, policy `read` on the data path only, native login, 15m. -Draft it when T10 is scheduled and the ServiceAccount exists. - -## Q2. Can the executor pod receive an ESO-projected audit-core sender bearer? - -**Yes, with the CCR-2026-0021/0022 shape, once audit-core admits the sender.** The receipt is an event to -audit-core; per RPF-WP-0067-T01 the wire is `POST /v1/events` to -`http://audit-core.audit-core.svc.cluster.local:8080`, with a per-sender bearer from the registry at -`platform/workloads/audit-core/senders`. `Idempotency-Key` is the envelope `id` and `correlation_id` is the -`run_id`. The envelope `source` equals the sender name. - -Shape (as `docs/credential-lane-designs/factory-audit-senders-review.md`): - -- custody `platform/workloads/net-kingdom/bounded-executor/audit-sender` (`AUDIT_TOKEN`), generated CAS=0 in - the attended platform process and merged into the registry as one exact sender (proposed name - `nk-bounded-executor`, `tenant:platform`, write true, read false, `evidence_kind: load-bearing`, - `secret_policy: redact`); no existing sender changes; -- exact-path policy, Kubernetes role for `external-secrets/external-secrets` only, ClusterSecretStore - restricted to namespace `sso`; ExternalSecret into `sso/nk-bind-executor-audit` key `audit-token`, - mounted as a file in the executor pod; -- coding-agent fence on the new data and metadata paths. - -The bearer lets the executor append its own receipts only. It is not an OpenBao identity. The ESO -manifests belong in the `rapp-*` package, as in CCR-2026-0053, which points to net-kingdom -`bind-eso.yaml`. Prerequisites: audit-core says yes to its Q1 (net-kingdom request §audit-core) and -names the sender. Then a `workload-kv-read` CCR follows here. - -## Open items for the founder and other owners - -1. Review and approve or change CCR-2026-0058 (platform-owner, net-kingdom-owner). It stays `proposed`. -2. Standing vs window-scoped write role (recommendation: standing for the pilot). -3. Accept that a journey-read CCR widens CCR-2026-0057's `runtime_access: false`. -4. secrets-engine: the LLDAP credential lane shape (its Q2) before a custody CCR can be drafted. -5. audit-core: sender admission and name, before the sender CCR. -6. railiance-apps / rapp package: the ServiceAccount `sso/nk-bind-executor` must exist before - `bound_claims_confirmed` can be set. diff --git a/docs/credential-lane-designs/coulomb-paired-backup-primary.md b/docs/credential-lane-designs/coulomb-paired-backup-primary.md deleted file mode 100644 index 2795ec8..0000000 --- a/docs/credential-lane-designs/coulomb-paired-backup-primary.md +++ /dev/null @@ -1,75 +0,0 @@ -# Coulomb paired-backup primary custody review — 2026-10-07 - -Pending admission under RPF-WP-0053-T01/T02; this is a concrete consumer request, -not an approved credential grant. No existing Secret, policy or grant is changed. - -The revised consumer uses Scaleway primary storage, separate from native Barman -objects. The earlier Nextcloud overwrite-slot proposal is withdrawn: the current -Nextcloud workload uploader is create-only and remains the independent secondary. - -| Coordinate | Requested contract | -| --- | --- | -| Consumer | Node-side coulomb-social-paired-backup.service; proposed root executor guarded by current cluster UID/app digest, subject to T01 review | -| Cluster | railiance01; kube-system UID a553c742-0115-43d4-99a4-a5ca56fe0786 | -| Destination | s3://railiance-platform-pg-backup/platform-pg/application-archives/coulomb-social/ | -| Provider | Scaleway Standard Multi-AZ, nl-ams; https://s3.nl-ams.scw.cloud | -| Source scope | coulomb_social_db on apps-pg plus the coulomb-social-content PVC, one quiesced capture | -| Publisher rights | Multipart creation/parts/completion, abort its own unfinished uploads, HEAD and version-pinned GET within the exact archive prefix; prefix-constrained listing if required for HEAD missing-object semantics | -| Publisher exclusions | No retained-object/version deletion, native Barman writes, other application prefixes, bucket/IAM configuration, escrow identity or bootstrap provider key | -| Worker delivery | Owner writes /etc/coulomb-social-backup/primary.env, root:root0600, parent0700; AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY only | -| Recovery custody | Independent read authority plus the age identity; public object key/version/hash/size receipt retained outside the node/workstation | -| Retention | Separate owner; proposed 14 daily/4 weekly recovery generations, deduplicated and protecting newest plus last successfully recovered point; not activated | -| Runtime | Dedicated /opt/railiance-platform/backup-venv, hash-locked requirements/backup-primary.lock; SDK conditional-completion field verified offline | - -Existing CCR-2026-0012 identifies the custody owner and source -platform/workloads/railiance/backup/object-storage fields ACCESS_KEY/SECRET_KEY. -It authorizes the existing databases/platform-pg-backup-s3 delivery, not this -consumer. Do not copy that Secret, broaden its ESO namespace conditions, reuse the -bootstrap key or manufacture a successor grant. Owner must determine whether a -separate scoped IAM application/key is needed: Scaleway policies attach to the -application, so merely minting another key under a broad application does not -establish prefix isolation. A worker intentionally has no delete authority even -if an existing provider identity has wider capabilities. - -Routing check: warden route show backup-object-storage returns unknown on this -workstation, despite that historical catalog id in CCR-2026-0012. Broad route -lookup returns the Nextcloud lane and generic OpenBao pointer. No secret retrieval -was attempted. Owner must reconcile the current primary front door and delivery; -record its review/grant reference here before execution. Node metadata-only check -on 2026-10-07: primary.env absent. Source presence is not custody admission. - -Before first publication, prove bucket versioning and conditional create behavior -under this caller, missing-object HEAD behavior, denied foreign-prefix access and -retained-object deletion denial. Metadata/code tests do not prove those live -rights. The worker does not contact a provider during default preview. Its check -requires the SDK model and delivered fields, without printing values or contacting -Scaleway. Live transfer needs --execute; it never reads Kubernetes Secrets. - -The platform documents a 30-day current/noncurrent lifecycle; exact prefix coverage -must be checked before accepting a 14/4 calendar contract. Four weekly selections, -protected recovery points and monthly drills can outlive an age-based expiration. -Do not activate an incompatible lifecycle or promise protected points survive it. -Owner must bind capacity/headroom, local encrypted-copy retention and independent -receipt retention before enabling an unattended timer. No live deletion/lifecycle -change is included in this request. - -First run: approved current-image capture, app resumes, age sealing, immutable -conditional completion, version-pinned GET and ciphertext SHA-256 check. Receipt -explicitly says application_restore_verified=false. Recovery on a different -workspace uses the retained receipt with primary_application_archive.py retrieve, -then consumer recover-paired-backup.py and restore-paired-snapshot.py. Retrieve does -only version-pinned GET and needs no original archive. Decryption belongs to the -attended custodian, never the scheduled publisher. Complete rendering and denied -access checks and record full elapsed time before calling this recoverable service. - -Provider contract reference: [Scaleway conditional writes](https://www.scaleway.com/en/docs/object-storage/api-cli/using-conditional-writes/). -If-None-Match is applied at multipart completion. The lock pins boto3 1.40.18 and -botocore 1.40.76; the actual botocore service model exposes IfNoneMatch. Synthetic -provider tests cover lost completion response, abort of own pending upload, -changed versions, corrupted readback, scope/hash binding, and node-loss retrieval. - -Email-connect has a separate archive profile in the platform helper. That is a -transport capability only: this request neither captures its SQLite ledger nor -admits an email worker. EMAIL-WP-0006 deployment evidence is complete; ongoing -ledger recovery stays tracked by CSOC-WP-0010-T04/T05 and requires a separate -producer/custody/rehearsal contract. diff --git a/docs/credential-lane-designs/coulomb-recovery-api.json b/docs/credential-lane-designs/coulomb-recovery-api.json deleted file mode 100644 index 7abbceb..0000000 --- a/docs/credential-lane-designs/coulomb-recovery-api.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "schema": "platform.coulomb-recovery-api.v1", - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "region": "nl-ams", - "custody_owner": "railiance-platform/platform-root", - "public_recipient": "age1qm30jk8q0cezey652dk2dhcr3d9vs528v6lg8mavl6d4q3e3vgnqmrmyy3", - "materials": { - "age": { - "id": "634e4b9f-0b61-4329-a83c-1ed0e76c4636", - "revision": 1, - "name": "coulomb-social-age-recovery" - }, - "reader-pair": { - "id": "241742f7-d3b5-4163-8d87-3a3d9585778d", - "revision": 1, - "name": "coulomb-social-recovery-reader-api" - }, - "custodian-pair": { - "id": "cf44e9bd-9edc-450b-9f10-564ee3008305", - "revision": 1, - "name": "coulomb-social-recovery-custodian-api" - } - }, - "applications": { - "custodian": "f80037a2-87c9-44d2-9163-3e6907af3ced", - "reader": "7a4055fc-a3f5-40bf-8013-cd0eec9028de" - }, - "routine_key_expirations": { - "reader": "2027-10-28T19:24:59.647270Z", - "custodian": "2027-10-28T19:25:00.458446Z" - }, - "buckets": { - "kits": { - "name": "railiance-coulomb-social-recovery", - "prefix": "kits/", - "automatic_expiry_enabled": false - }, - "archives": { - "name": "railiance-coulomb-social-archives", - "prefix": "archives/", - "automatic_expiry_enabled": false - } - }, - "native_application_recovery_verified": true, - "protected_recovered_point": { - "captured_at": "2026-10-31T07:13:52Z", - "primary_object_key": "platform-pg/application-archives/coulomb-social/2026/10/31/20261031T071352Z-abf5997f01a14d9cb05e4bba.tar.age", - "primary_version_id": "1793430889823443", - "ciphertext_sha256": "840e633047fddd88c6e6a1a3d6909d9af1ad80eaba8c90f83d8e81188dfc17a4", - "protected_kit": { - "schema": "platform.coulomb-protected-recovery-kit.v1", - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "bucket": "railiance-coulomb-social-recovery", - "object_key": "kits/4166eb8ba97f7bd79e912c2efaee2622fcd472b05ed80070ccf7e6728fec5bd6/recovery-kit.tar", - "version_id": "1793432303941465", - "sha256": "4166eb8ba97f7bd79e912c2efaee2622fcd472b05ed80070ccf7e6728fec5bd6", - "bytes": 276019200, - "automatic_expiry": false - }, - "recovery_seconds": 46.692, - "proof": "docs/evidence/2026-10-31-coulomb-provider-kit-post-date-recovery.json", - "protected_recovery_proof": { - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "bucket": "railiance-coulomb-social-recovery", - "object_key": "kits/receipts/b93e3003627581eb510086ba207bf283085d88cb9b3dfd0e74ffa8ebf02c39f0/recovery-proof.json", - "version_id": "1793432409572654", - "sha256": "b93e3003627581eb510086ba207bf283085d88cb9b3dfd0e74ffa8ebf02c39f0", - "bytes": 4365, - "independent_readback_verified": true, - "automatic_expiry": false - } - } -} diff --git a/docs/credential-lane-designs/coulomb-recovery-api.md b/docs/credential-lane-designs/coulomb-recovery-api.md deleted file mode 100644 index 23b2d91..0000000 --- a/docs/credential-lane-designs/coulomb-recovery-api.md +++ /dev/null @@ -1,84 +0,0 @@ -# Coulomb recovery API custody - -Owner: railiance-platform / platform-root. Admission decisions -27bbc1ac-e275-42be-ba34-6cf16eae4e78 and 6139e02b-3fb7-4eff-be8a-efe127c45ce5. -Non-secret coordinates: coulomb-recovery-api.json. Native evidence: -../evidence/2026-10-29-coulomb-recovery-api-bootstrap.json. - -Routine access uses separate reader/custodian API pairs under an owner-private -directory outside Git, normally ~/.config/railiance-platform/coulomb-social-recovery/. -The worker holds only its original publisher pair and the dedicated public age -recipient. Never deliver the custodian pair, reader pair or private age identity -to that worker. No sibling credentials or private keys are reused. - -Check independent retrieval without writing a private identity: - -```sh -python3 scripts/application_recovery_access.py --execute --material age -``` - -For an isolated attended restore, add --output with a fresh absolute filename -under an existing owner-private directory outside Git. The helper validates exact -project/name/protection/revision/nonexpiry and public recipient before writing -mode0600. Materials reader-pair and custodian-pair use the same front door; -omit --output to verify retrieval without creating another secret copy. -No value goes to stdout, Git, State Hub, shell arguments or environment. - -The reader performs only prefix/version reads and listing in the isolated project. -The custodian can also publish new kit versions and read the dedicated escrow; -neither can delete retained versions or administer buckets/IAM. Scaleway's current -2023-04-17 bucket policies intersect IAM with an explicit allowlist. Deletion is -absent while publisher multipart abort is included; native checks prove both. -Negative PUT probes use separate transports because a denied upload can leave -the provider connection unusable for its next request. - -Both buckets are private and versioned; no automatic expiry has been enabled. -The protected bucket never participates in rolling expiry. First coherent app -capture, independent exact-version decryption and isolated application recovery -are now verified below. Rolling retention, capacity, copy disposition, monitoring -and timers remain WP-0053/CSOC-WP-0012 acceptance work. The earlier synthetic -fixtures remain boundary evidence, distinct from this real application backup. - -The routine pairs and age identity each have a separate protected, enabled, -nonexpiring provider secret version. They survive node/workstation loss. If all -local routine credentials are lost, platform-root uses its existing independent -account/MFA recovery to retrieve only the custodian escrow coordinate in the -public contract, then returns to the API front door. Emergency owner recovery is -separate from routine drill access; preserve the platform-root recovery mechanism. - -Routine API keys expire on 2027-10-28; exact timestamps are in the JSON contract. -Platform-root owns renewal and independently read-back escrow before those -deadlines, using a bounded attended construction window. No standing organization -administration key or publisher IAM administration is admitted. The construction -key was revoked, its temporary pair removed and the personal browser context -closed without saving state. Routine drills do not rerun identity bootstrap. - -The first provider-only kit recovery is now verified in30.232s monotonic. The -public JSON contract records the complete protected kit, exact primary recovery -point and separately protected recovery-proof coordinates. Native receipt: -../evidence/2026-10-29-coulomb-provider-kit-recovery.json. All2098kit members, -13tables/112rows/sequences,82files and39rendered pages/access denials pass. -Images/tools/data came from the kit; the age identity came from separate escrow. -Held API callers and the existing Docker daemon were used. Account-recovery -latency, a fresh daemon and real-browser identity journeys are excluded. - -application_recovery_drill.py previews without effects and executes only with ---execute, --kit-reference and a fresh --destination under an owner-private -directory outside Git. It never restores production. Retention/copy disposition, -monitoring and scheduled activation remain open; this receipt closes only the -consumer's first-paired-recovery task. - -2026-10-31 corrected-date protected recovery refresh: the 07:13:52Z point is now -independently retained in kit4166eb8b/version1793432303941465 (276,019,200bytes) -and recovered using only the provider kit plus separate age escrow. All2098members, -13tables/114rows/sequences,86files and40owner renders/40outsider denials pass in -46.692s. The matching captured image is51281946; this point predates the revision19 -image update but includes the verified source-date correction. Current image522bcd7f -has separate same-store export/view proof. The successful recovery proof is itself -protected and independently read back at version1793432409572654. The fresh drill's -temporary identity file was removed; pre-existing custody and historical copies -remain untouched. No production restore, new grants, expiry or timer activation. -Public API coordinates now identify this verified point; the former pointer still -named October29 and was stale. Historical kits/proofs remain retained. Receipt: -docs/evidence/2026-10-31-coulomb-provider-kit-post-date-recovery.json. Existing -retention/capacity/monitoring/copy-disposition obligations remain open. diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md b/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md deleted file mode 100644 index 4f719c5..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-adapter.md +++ /dev/null @@ -1,205 +0,0 @@ -# Attended native signer custody adapter source - -RPF-WP-0035-T12 / CUST-WP-0080. This source implements the protected native -adapter described by the separate frozen native-preparation R1. It does not -admit an execution. Its CLI is silent and intended only as the exact reviewed -child of `warden access openbao-platform-admin-login --exec` in the founder's -attended OIDC envelope. The existing Warden owner contains the token helper, -checks child output and revokes/removes the session on exit. No new service, -credential broker, standing grant, API or key-returning interface is created. - -## Current routing blocker - -During preparation the required `warden plan` query for attended OpenBao -platform administration of signer CAS0/metadata selected `openbao-structure-survey` -with an approve ceremony, rather than `openbao-platform-admin-login` with -`oidc_login`. The installed CLI reports wheel-bundled catalog0.1.2 fallback. -This is preserved as a live refusal, not routed around. Exact `route show` names -the existing active attended login, but that pointer does not cure dispatch. -The adapter requires an independently retained routing receipt selecting the -correct lane/verdict/identity act. No founder execution step is ready until the -routing defect and all other native approvals are resolved by their owners. - -## Independent input and authority boundary - -The CLI requires an absolute owner-private admission file, its independently -approved exact SHA256, the exact private receipt directory selected in the -reviewed native proposal, and an original receipt only for recovery. No default -image/package/caller trust, founder identity, approval, route or source pin is -usable. The admission schema is closed in `validate_admission`; it includes the -immutable published producer image, retained caller, deployment projection, -selected consumer trust baseline, exact native plan/CCR/source bundle, fresh -UTC authority expiry (5..120seconds), founder entity and five distinct externally -reviewed owner dispositions. Each disposition binds the same approval-free -scope digest. An admission shape, local digest or review string never approves -itself: the founder and owners must independently review the exact supplied -command/file hash through existing governance before attended invocation. - -The trust hash selects the independently reviewed consumer verifier/policy/trust -baseline for this custody action. It is not a claim that the newly generated -public key is already online: generation happens later, and independent delivery -and successor trust installation are required after the public receipt. The -writer never reads consumer private credentials or discovers trust from KV. -All successful custody states remain pending independent public admission. - -`checked_sources` verifies the actual selected native action plan, exact proposed -kid, read CCR hash/status/binding, generated policy source, and source bundle. -The read CCR must independently be approved/binding-confirmed; the canonical -proposed CCR remains unapproved, so ordinary source checkout refuses. Source -bundle pins execution modules, not arbitrary request-selected code. The native -image/caller/seed projection is compared to the actual stopped Deployment. -The lifecycle policy digest/version stays unchanged; execute-ccr probes must -prove no ALLOW/effect. Current generic runtime guard can return HTTP503 before -the lifecycle evaluator, so signed unknown_action is not promised. - -## Actual checks and effects - -Before private work, the child checks OpenBao cluster ID, unsealed state, exact -non-root platform-admin OIDC entity, enabled audit, actual exact read-policy HCL -and role bindings/15m limits. The selected fixed loopback transport is the existing -owner-admin bridge; Kubernetes reads use fixed strict-host SSH to railiance01. -The OpenBao token lookup-self identifier stays only in the contained custody -process to authenticate fixed HTTP calls; it never reaches the agent, a receipt, -argv or another token file. This is a native protected-process implementation, -not evidence that agent runtime isolation has been promoted or granted. - -The fence checks the pinned kube-system UID, stopped Deployment generation and -replica status, exact immutable image/caller/seed mount/kid projection, zero owned -ReplicaSet replicas, no live or terminating signer-SA/owned/seed-mount pods, and -no targeting HPA. It rechecks before metadata setup and immediately before CAS. -A private filesystem lock excludes a second local custody window. The lock and -observations do not provide atomic Kubernetes fencing: the independently reviewed -exclusive window must keep GitOps/Helm/other creators stopped. No rollout or -cluster mutation is performed by this adapter, and no concurrency guarantee is -inferred from source alone. Access/cluster owners retain that admission. - -Bootstrap requires the exact OpenBao empty-errors HTTP404 metadata absence. A -durable private intent precedes exact CAS-required/max_versions2/no-auto-delete -metadata setup, followed by exact native readback. Only then does the shared -Ed25519 primitive generate one in-memory seed and CAS0 the fixed three-field -KV document. Independent version1 private readback and final metadata verification -precede the durable success receipt. The seed is never put in argv/env/local -files or output. The receipt carries public key, ASCII-base64url fingerprint, -version, bindings and closed phases only. The old fixture's live-dev-process gate -is preserved; its shared receipt initialization was extracted without weakening -that gate. Python cannot promise seed zeroization. - -Every backend/body/close and tool call has a two-second absolute budget and -1MiB combined output bound. The complete operation is bounded by the supplied -fresh expiry. CLI validation and runtime failures remain silent. Existing0600/ -0700 no-follow/ownership checks, exclusive creation, fsync/atomic replacement and -directory fsync retain evidence. Same-UID hostile code is not isolated by modes; -Warden's contained home, trusted runtime and independently reviewed custody -location are required, not inferred from approval frequency or shell prefixes. - -## Process loss and independent read-only recovery - -Any failed/ambiguous metadata/write/readback/final receipt outcome remains -unconfirmed. A success receipt is withheld until final metadata verification. -A child death immediately after the real native CAS leaves the earlier public -intent with its derived public key and no private value. A fresh `recover` -admission must bind that exact original receipt SHA and the same immutable scope. -It reserves a new durable recovery intent before private reads, verifies current -version1 metadata and exact private seed/public/kid correspondence, and records -separate public reconciliation. It never creates a writer, generates a seed, -POSTs metadata/data, restores a seed, consumes approval or dispatches business -operations. Original unknown evidence is never rewritten. Changed versions, -damaged/typed-invalid receipts, missing fresh reviews and denied reads refuse; -replaying a recovery ID cannot repeat even the private backend read. - -This source implements bootstrap and read-only version1 reconciliation only. -Rotation needs a separately reviewed exact-version native action; the disposable -primitive's CAS1 test is not a native rotation grant. No automatic retry, remint, -rollback or general recovery API exists. Metadata-only setup uncertainty without -a public seed intent requires separate attended reconciliation, not this path. - -Access observed zero PVCs in flex-auth. Its proposed new1Gi/local-path outbox, -PV Retain/reclaim disposition and runtime process-lock/capacity/startup bounds -remain separate reviews. This writer grants no PVC, audit egress, policy role, -SecretStore/Secret or producer/consumer deployment admission. - -## R2 phase-aware recovery and source lineage - -Frozen R1's metadata-only crash counterexample remains valid historical evidence. -R2 accepts a strict original metadata-setup receipt with no public key, or a -seed-cas receipt with a canonical derived public key. Fresh recovery reserves -durable evidence before target reads. Exact current metadata absence AND exact -version1 data absence yields `current-custody-absent`. Exact configured metadata -with current_version0 AND exact version1 data absence yields -`metadata-only-confirmed`. Both results have no public key/fingerprint/KV version, -key_custody_verified=false, historical_effect=unresolved, original_unknown_preserved, -and an explicit fresh independently reviewed action requirement. A locally -generated but unstored seed's public key is not promoted into public admission. -Neither observation means the original attempt historically made no effects. - -Denied/malformed/misleading404 metadata or data, timeout, changed configuration, -unexpected versions, or version1 after a metadata-only/no-public phase keep the -new recovery attempt unconfirmed. They never grant generation, write, retry, -remint, consume or business authority. A subsequent metadata-only bootstrap is -not silently enabled: existing metadata still refuses CAS0 bootstrap admission. -A distinct exact reviewed action/source revision must choose any cleanup/reuse -or generation step. The original receipt remains byte-for-byte unchanged. - -Native plan bootstrap.source_primitive_sha256 now equals the actual shared -primitive in the admitted source bundle and writer receipt; checked_sources -enforces the join. The prior8ed pin is explicitly historical. The plan pins the -shared primitive, not itself or the complete native executor, so no self-hash -cycle exists. The exact native executor is independently pinned by source_bundle -and its digest in bindings. A recover admission must additionally supply -original_execution (original receipt bindings, writer SHA and CCR SHA). Fresh -owner reviews bind both original and current lineages. This can describe an older -executed source/proposal without labeling it current. Image/deployment/policy/ -consumer trust bindings must still match; recovery cannot change that authority. - -## Exact120-second expiry and attended scheduling assessment - -The unchanged validate_admission rule requires an absolute UTC expires_at with -5 < expires_at-now <=120seconds, and the entire child runs beneath that fixed -remaining absolute/monotonic budget. The five external reviews cover the scope -digest including expires_at. No deadline is generated, refreshed or widened by -the child; rechecking cannot extend it. - -Existing ops-warden proxy.proxy_attended_login_exec runs the OIDC client before the child. -Its _contained_run captures output through subprocess.run without a timeout. -Therefore neither OIDC/MFA nor five external reviews have a demonstrated bound -that fits120seconds from preparing an admission. Creating a now+120seconds -admission before unbounded review/login is not a reliable operational ceremony. -Source success tests start from synthetic ready identities; they prove no human -OIDC/review timing SLO. - -A coherent ceremony under the current rule is to pre-review one exact future -absolute expiry/window, including all artifact/source/image/trust/identity and -review references, then arrange attended login so the child starts within its -implicit [expires_at-120seconds, expires_at-5seconds) interval. Review may occur -before that interval; it does not change runtime authority. The founder must aim -for the beginning of the interval, not its last five seconds. The fixed path has -26 bounded native/tool requests before success (52seconds at their2second caps), -plus key/source/receipt work. Its expiry still wins if those operations cannot -finish; no completion or filesystem timing guarantee is inferred. - -If login completes too early/late, a review changes any bytes, or adequate time -remains uncertain, refuse this revision. A new expires_at changes the approved -scope digest and needs explicit dispositions for the new revision; existing -source-only reviews cannot be copied into native approval. Do not silently -refresh after OIDC, reinterpret expiry as start-only, widen120seconds, skip -reviews or start a general window-grant service. An alternative authority/window -contract would itself require separate owner/founder design and is not supplied -by R2. Current native admission remains unapproved. - -## R3 precise owner review boundary - -The native review slot is now `cluster-runtime-storage-owner`, held by -railiance-cluster for runtime/storage/headroom only. The previous -`cluster-gitops-owner` slot conflated separate authorities and is rejected; -no historic review is reinterpreted as a combined assent. Platform's existing -review covers its custody/read lane and existing GitOps Applications/freeze -integration. Access retains workload/release/Helm-values and signer/outbox -ownership. Enablement review is needed only when the action actually changes -ArgoCD installation/controller configuration; this action does not authorize -that. Changed scope requires a separate exact revision and any affected owner. - -This is a source-schema correction, not a new approval. Existing R1/R2 source -reviews and P1/P2 packets remain frozen. A successor packet must pin the new -executor/source-bundle/plan/CCR hashes and collect five exact fresh dispositions -under the corrected role set. Admission-file digest approval and CCR -approved/binding-confirmed status/native checks remain unchanged. diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json deleted file mode 100644 index 165d331..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "schema": "railiance-platform.signer-native-action-proposal.v1", - "proposal_id": "RPF35-PDP-SIGNER-NATIVE-20261016-R1", - "status": "prepared-unapproved", - "read_ccr": "CCR-2026-0050", - "proposal_scope": "attended bootstrap of retained lifecycle PDP seed; no taskpilot enablement, rotation requires a new exact-version action revision", - "actor": "founder attended OIDC via warden openbao-platform-admin-login; reviewed silent Platform custody child", - "executor_ownership": "railiance-platform; founder executes exact child; agent receives closed metadata only", - "abort_owner": "founder; Platform preserves unknown custody/evidence, Access keeps signer/consumers fenced", - "target": { - "cluster": "primary/railiance01", - "kube_system_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "openbao_cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf", - "openbao_origin": "http://127.0.0.1:18200", - "namespace": "flex-auth", - "deployment": "flex-auth-secrets-engine", - "signer_sa": "flex-auth-secrets-engine" - }, - "credential_route": "openbao-platform-admin-login; flex-auth-decision-envelope-signing-key remains draft/nonresolvable; route is not a seed vending service", - "bootstrap": { - "action": "CAS0 fresh in-memory Ed25519 seed", - "kid": "flex-auth-lifecycle-20261016-r1", - "expected_version": 0, - "data_path": "platform/data/workloads/flex-auth/envelope-signing", - "document_fields": [ - "ED25519_PRIVATE_KEY", - "ED25519_PUBLIC_KEY", - "kid" - ], - "metadata_path": "platform/metadata/workloads/flex-auth/envelope-signing", - "metadata_contract": { - "cas_required": true, - "max_versions": 2, - "delete_version_after": "0s" - }, - "allowed_native_methods": [ - { - "method": "POST", - "path": "platform/metadata/workloads/flex-auth/envelope-signing", - "purpose": "CAS-required/version-retention configuration after absence/no-drift validation" - }, - { - "method": "GET", - "path": "platform/metadata/workloads/flex-auth/envelope-signing", - "purpose": "exact metadata readback, no private value output" - }, - { - "method": "POST", - "path": "platform/data/workloads/flex-auth/envelope-signing", - "purpose": "single CAS0 seed document write" - }, - { - "method": "GET", - "path": "platform/data/workloads/flex-auth/envelope-signing?version=1", - "purpose": "exact private readback in custody, closed public receipt only" - } - ], - "source_primitive_sha256": "sha256:f169ad12f90d729b647085c3bed2c0d8d005db23a2341b6d3d5f1793bcfd110c", - "historical_source_primitive_sha256": "sha256:8ed281a49c1f1cdb23772efe3593e75c62572a12b8d7a6a4c574f27b38722e75", - "source_lineage_rule": "source_primitive_sha256 must equal current admitted source_bundle[scripts/flex_auth_signer_custody.py] and exact writer receipt; historical_source_primitive_sha256 is non-authoritative R1 evidence only" - }, - "fencing": { - "before_key_generation": [ - "Verify cluster/backend IDs, exact current source/CCR and founder disposition hashes.", - "Read exact deployment/ReplicaSets/pods by ownerReference and SA; refuse a second workload/pod using signer SA or selecting signer Secret.", - "Access stops protected consumers and scales retained deployment0 through its reviewed release path; this child does not mutate replicas.", - "Verify deployment spec replicas0, observed generation caught up, no live or terminating owned ReplicaSet/pods, no matching signer-SA pods and no HPA targeting deployment.", - "Reject a differing/previously existing KV entry at bootstrap. Hold exclusive owner action receipt lock and recheck fence immediately before CAS." - ], - "after_cas": "Keep consumers/signer stopped until independent public admission and exact ESO/readback/source/image verification. Access published source Git revision 84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0 with OS process lock,64MiB capacity/startup bounds and ordinary CI402 success, immutable OCI reference forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f. This supplies reviewed-source/publication evidence, not native mounted-runtime/durable-record acceptance. Access owns Recreate1 activation; actual stopped fence, single writer, PV retention and protected consumer acceptance remain open.", - "concurrency": "Single attended owner window/exclusive receipt; Access owns workload/Helm release and freeze of competing workload writers; Platform owns existing GitOps Applications and sync/freeze integration; cluster owner reviews runtime/storage/headroom only. Enablement reviews actual ArgoCD installation/controller configuration changes only, which this action excludes. Actual competing creator IDs/revisions and stopped signer fence need separately reviewed authenticated observations. Plan is not a Kubernetes atomic fence or same-UID isolation." - }, - "protected_adapter": { - "state": "Native adapter bootstrap/phase-aware read-only recovery source implemented and tested at published Platform a3e8eb64bae110393ba0aeab043de03a92b8421a; owner-boundary source R3/R4 is a successor exact review revision, not a native execution approval. Exact source/CCR, distinct owner/founder dispositions, entity/window/expiry and actual backend/fence remain mandatory. Fixture dev-process gate remains preserved.", - "reuse": [ - "scripts/state_hub_preflight_lane.py attended silent child/cluster/platform-admin/fencing patterns", - "scripts/flex_auth_signer_custody.py protected Ed25519 CAS/readback and receipt state machine", - "scripts/task_execution_native_fixture.py absolute main-thread2s I/O budget", - "scripts/task_execution_recovery.py strict bounded JSON/no-follow/private custody patterns" - ], - "requirements": [ - "New fixed native adapter consumes exact approved CCR/source/native-action digest; cannot accept arbitrary URL/path/command/payload/seed.", - "Read OIDC token only within warden child from attended context; require platform-admin and reject root; never print/store bearer. No credential comes back to agent.", - "Pin exact kube-system/OpenBao cluster IDs before private calls, refuse ambient address override; enforce2s absolute reads and1MiB cap; no redirect/shell/tracing.", - "Verify metadata absence/exact admitted values before CAS-required change; any differing existing object refuses. Exact response contracts and duplicate/nonfinite JSON rejection.", - "Private seed is generated once, never imported or persisted outside OpenBao; no subprocess argv/env seed; readback compares exact version/fields and derived public correspondence.", - "Receipt intent is durable before native effect; final success requires exact KV/metadata correspondence and receipt fsync. Process loss/error remains unknown without remint/retry." - ] - }, - "receipts": { - "directory": "/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing", - "mode_directory": "0700", - "mode_file": "0600", - "native_execution_location": "founder workstation inside reviewed warden child; no agent-owned shared /tmp custody", - "basename": "RPF35-PDP-SIGNER-NATIVE-20261016-R1.json", - "maximum_bytes": 65536, - "atomic": "exclusive initial receipt, fsync-before-effect, atomic replacement and directory fsync, no-follow/owner/mode/nlink validation", - "public_fields": [ - "proposal/executed source/CCR/identity/cluster/image/policy/trust digests", - "actor/time", - "closed phase/status", - "exact KV path/version/kid", - "public key and explicitly encoded fingerprint", - "fence/metadata/readback checks", - "rollback or unknown recovery disposition" - ], - "fingerprint": "sha256: lowercase SHA256 of canonical unpadded base64url public-key ASCII, exactly43bytes; independent Secrets owner agreement before admission", - "delivery": "Founder/Platform separately authenticate metadata receipt to Secrets owner; public receipt shape/file does not create consumer trust; raw private receipt directory is not a shared agent intake" - }, - "rollback": { - "before_cas": "Abort without private write; retain receipt/fence; restore prior Access image/values only via reviewed Access action.", - "after_cas_unknown": "Retain unknown and fence; independently attended owner reconciles exact metadata/version/public correspondence. Never rerun CAS0 or restore prior seed automatically.", - "compromise": "Remove online old public key immediately; stop signer, independent new reviewed forward-only action; old private seed never restored." - }, - "requested_scope": { - "read_lane": "Exact CCR0050 policy/role/delivery source only after separate founder review. No extra ESO RBAC permitted.", - "custody_writer": "One attended exact-target CAS0 plus fixed KV metadata setup/readback; no standing writer grant or broker role. This authority is separate from read CCR.", - "kubernetes": "Metadata-only fence observations by child; Access separately reviews workload/Helm stop/restart/delivery; Platform owns GitOps Application sync/freeze integration; cluster owner reviews runtime/storage/headroom/PV retention; no ArgoCD installation/controller configuration mutation included.", - "new_infrastructure_spend_eur": 0, - "new_resources": "Platform creates no storage resources. Access proposes separate exact new flex-auth-secrets-engine-outbox1Gi local-path Helmkeep. Actual provisioning/PV identity, StorageClass Delete/WaitForFirstConsumer to reviewed PV Retain disposition and native bounded-runtime evidence remain open. Source lock/64MiB/startup bounds are published in candidate84, not evidence of native mounted-runtime acceptance.", - "risk_bound": "Controlled loss of retained PDP availability during explicit attended fence; seed compromise can forge lifecycle decisions until independent public trust withdrawal; founder must approve this concrete high-risk scope." - }, - "reviews_required": [ - { - "role": "railiance-platform-owner", - "disposition": "pending exact source/artifact review; no approval recorded" - }, - { - "role": "access-engine-owner", - "disposition": "pending exact source/artifact review; no approval recorded" - }, - { - "role": "secrets-engine-owner", - "disposition": "pending exact source/artifact review; no approval recorded" - }, - { - "role": "cluster-runtime-storage-owner", - "disposition": "pending exact source/artifact review; no approval recorded" - }, - { - "role": "founder", - "disposition": "pending exact source/artifact review; no approval recorded" - } - ], - "authority_granted": false, - "candidate_delivery_amendments": { - "supersedes_unadmitted_R3_role": "external-secrets-flex-auth-envelope-signing replaces flex-auth-envelope-signing-eso to fit existing delegated-applier role convention; dedicated SA unchanged", - "read_ccr_field_projection": "Only ED25519_PRIVATE_KEY is delivered; public sibling fields remain visible to dedicated document-reader identity but never become consumer trust discovery", - "projection_source": "docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed", - "managed_declaration": "Canonical local rapp-flex-auth-secrets-engine declaration committed d52f811ed5ec0be27c9c367e71d476125b3228be, exact13file tree d4fbad71bdc8ef870603526ccd1f53195d0322af; tuple rapp-flex-auth-secrets-engine/flex-auth-secrets-engine/flex-auth-secrets-engine resolves locally as draft. Frozen source2b/current05a package baseline does not adopt candidate84/signing. Remote publication/placement and actual runtime/package join remain open. No missing-declaration claim or migration-debt waiver is applicable to the established local declaration." - }, - "owner_review_scope": { - "railiance-platform-owner": "OpenBao custody/read lane; existing GitOps Applications and their sync/freeze/controller ownership; covers explicitly identified workload/Helm competing-writer integration with Access, not Access workload semantic ownership.", - "access-engine-owner": "Retained workload/release source, Helm values, candidate image, signer delivery/mount and process/outbox fencing/rollback.", - "cluster-runtime-storage-owner": "railiance-cluster: cluster identity/runtime/storage/headroom, local-path provisioning and actual PV retention; excludes workload and GitOps Application ownership.", - "enablement_condition": "railiance-enablement additional exact review required only if proposed action changes ArgoCD installation/controller configuration; this fixed custody action authorizes no such change. Changed scope needs a separately reviewed exact revision rather than blanket extra reviewer." - } -} diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.md b/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.md deleted file mode 100644 index 01853bb..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.md +++ /dev/null @@ -1,37 +0,0 @@ -# Native signer action preparation - -CCR-2026-0050 is a schema-valid proposed ESO read grant. It does not authorize -secret writes. The separate exact native-action JSON requests one attended CAS0 -and fixed metadata setup/readback; founder disposition is still pending. No -standing writer/broker grant, private consumer lookup, rapp declaration or policy -change is introduced. Native private custody remains in the founder's reviewed -warden child, not an agent prompt or temporary file. - -The JSON fixes the target/cluster/backend, new kid, CAS version, exact native -methods, retention/CAS metadata, existing custody machinery, signer fence, -receipt location/encoding, abort owner and forward-only recovery. It separately -identifies Access/cluster-owned stop/restart/ESO/PVC and Secrets-owned independent -public admission. These owners must review their exact inputs; Platform cannot -sign their dispositions. The program that enforces this native contract still -requires source implementation and independent tests before executable approval. -The existing fixture adapter must not be promoted by deleting its process check. - -Source preparation validated the real credential-change schema and generated -HCL. The expected unconfirmed-bound-claims warning and proposed status preserve -apply refusal. No live observation/identity confirmation, credential read, CAS, -policy write, role, Kubernetes or route mutation occurred. Missing managed -package declaration remains explicit migration debt for the retained workload. - -The native candidate explicitly amends two unadmitted R3 read-lane choices: -role `external-secrets-flex-auth-envelope-signing` follows the existing delegated -applier convention; read CCR fields names the projected ED25519_PRIVATE_KEY only. -The same document has public sibling fields; document-granular read means ESO -can read those too, so this is not field-level authorization. The sibling native -projection proposal and CCR agree on the new role. Historical R3 is not mutated. - -Access metadata observation found zero PVCs in flex-auth. Its exact proposed -new outbox PVC1Gi/local-path is a separate resource/retention scope, with Delete/ -WaitForFirstConsumer PV behavior needing explicit treatment. Existing outbox code -lacks process locking/byte ceiling/bounded startup and compaction. Neither -Recreate1 nor Helmkeep supplies these guarantees; production durable acceptance -remains closed until Access-owned hardening and cluster/storage review. diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed deleted file mode 100644 index 589a7ca..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed +++ /dev/null @@ -1,50 +0,0 @@ -# SOURCE PROPOSAL ONLY: no CCR admission, no apply, no plaintext key. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: flex-auth-envelope-signing-eso - namespace: flex-auth -automountServiceAccountToken: false ---- -apiVersion: external-secrets.io/v1 -kind: SecretStore -metadata: - name: openbao-flex-auth-envelope-signing - namespace: flex-auth -spec: - provider: - vault: - server: http://openbao.openbao.svc:8200 - path: platform - version: v2 - auth: - kubernetes: - mountPath: kubernetes - role: external-secrets-flex-auth-envelope-signing - serviceAccountRef: - name: flex-auth-envelope-signing-eso - audiences: [openbao] ---- -apiVersion: external-secrets.io/v1 -kind: ExternalSecret -metadata: - name: flex-auth-secrets-engine-envelope-signing - namespace: flex-auth -spec: - refreshInterval: 5m - secretStoreRef: - kind: SecretStore - name: openbao-flex-auth-envelope-signing - target: - name: flex-auth-secrets-engine-envelope-signing - creationPolicy: Owner - deletionPolicy: Retain - template: - metadata: - annotations: - railiance.io/custody-proposal: RPF35-PDP-SIGNER-NATIVE-20261016-R1 - data: - - secretKey: seed - remoteRef: - key: workloads/flex-auth/envelope-signing - property: ED25519_PRIVATE_KEY diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed deleted file mode 100644 index e96f5f9..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing-policy.hcl.proposed +++ /dev/null @@ -1,10 +0,0 @@ -# SOURCE PROPOSAL ONLY. New scope remains founder-gated; no metadata/list/write. -path "platform/data/workloads/flex-auth/envelope-signing" { - capabilities = ["read"] -} -path "auth/token/lookup-self" { - capabilities = ["read"] -} -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed b/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed deleted file mode 100644 index ccd064e..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing-projection.yaml.proposed +++ /dev/null @@ -1,50 +0,0 @@ -# SOURCE PROPOSAL ONLY: no CCR admission, no apply, no plaintext key. -apiVersion: v1 -kind: ServiceAccount -metadata: - name: flex-auth-envelope-signing-eso - namespace: flex-auth -automountServiceAccountToken: false ---- -apiVersion: external-secrets.io/v1 -kind: SecretStore -metadata: - name: openbao-flex-auth-envelope-signing - namespace: flex-auth -spec: - provider: - vault: - server: http://openbao.openbao.svc:8200 - path: platform - version: v2 - auth: - kubernetes: - mountPath: kubernetes - role: flex-auth-envelope-signing-eso - serviceAccountRef: - name: flex-auth-envelope-signing-eso - audiences: [openbao] ---- -apiVersion: external-secrets.io/v1 -kind: ExternalSecret -metadata: - name: flex-auth-secrets-engine-envelope-signing - namespace: flex-auth -spec: - refreshInterval: 5m - secretStoreRef: - kind: SecretStore - name: openbao-flex-auth-envelope-signing - target: - name: flex-auth-secrets-engine-envelope-signing - creationPolicy: Owner - deletionPolicy: Retain - template: - metadata: - annotations: - railiance.io/custody-proposal: RPF35-PDP-SIGNER-CUSTODY-20261016-R3 - data: - - secretKey: seed - remoteRef: - key: workloads/flex-auth/envelope-signing - property: ED25519_PRIVATE_KEY diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing.md b/docs/credential-lane-designs/flex-auth-envelope-signing.md deleted file mode 100644 index 76bdae5..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing.md +++ /dev/null @@ -1,171 +0,0 @@ -# Retained lifecycle PDP signer custody proposal - -Proposal ID: RPF35-PDP-SIGNER-CUSTODY-20261016-R3. Preparation under -RPF-WP-0035-T12, beneficiary FLEX-WP-0024-T06, coordinated by CUST-WP-0080. -Status: source proposal only; no key generated, credential read, CCR admission, -policy scope granted, package published or deployment changed. - -Platform INTENT principles 3–5 require durable custody, recovery and bounded -interfaces. Platform is Staff/PEP-shaped and is not an authorization authority. -The exact candidate contract is `flex-auth-envelope-signing.proposal.json`. -Sibling `.proposed` files are review inputs outside deployment discovery; they -carry no approved CCR id and MUST NOT be applied as an admitted lane. - -## Existing workload and custody boundary - -Retain namespace/release/deployment `flex-auth/flex-auth-secrets-engine`, signer -SA `system:serviceaccount:flex-auth:flex-auth-secrets-engine`, and the twelve-action -`secrets-engine.catalog-lane.lifecycle` v2 policy. The consumer SA remains -`system:serviceaccount:secrets-engine:secrets-engine`; retained caller audience is -`flex-auth`. Neither consumer nor TokenReview credentials confer seed read rights. -Access source revision `919ce25` integrates `tenancy.yaml#/workload_identity`; -this source declaration remains separate from runtime admission. An authoritative rapp declaration remains explicit managed -package migration debt, with reference null; no synthetic rapp tuple is created. -Legacy image package remains `forgejo.coulomb.social/coulomb/flex-auth`. - -Reuse CCR-2026-0015's dedicated namespace SecretStore, exact data-only policy, -short-lived Kubernetes delivery login and separate protected CAS writer patterns. -The dedicated delivery SA is NOT the signer SA. No RoleBinding, Secret read, -TokenRequest or ExternalSecret-create permission is given to the signer/consumer. -The controller's existing ability to mint only the delivery-SA token and namespace -mutation restrictions must be independently proven before activation; source YAML -does not establish those facts. Do not silently broaden ESO RBAC. - -KV data `platform/data/workloads/flex-auth/envelope-signing` is read only by the -reviewed ESO delivery role. There is no parent/list/metadata/write permission. -Lookup-self/read and revoke-self/update support bounded session lifecycle. If ESO -needs metadata, retain a failing receipt and review that exact scope amendment. -The KV document has private `ED25519_PRIVATE_KEY` and public `ED25519_PUBLIC_KEY`, -`kid`; only the private property is projected to the Secret. A public receipt -contains kid, public key/fingerprint, KV version and exact source/deployment/policy -bindings, authenticated independently through reviewed owner custody. Consumers -must never read the private KV document to obtain the public key, trust `/v1/keys`, -or derive authority from receipt shape, policy digest or this proposal. - -## Seed, projection and protected writer - -The attended protected writer generates a fresh 32-byte CSPRNG Ed25519 seed IN -MEMORY in the custody process and writes unpadded strict base64url via CAS zero. -It derives the public key there, verifies correspondence, and emits metadata only. -No seed in argv, environment, temporary files, logs, diffs, chat, Hub or CI. -Rotation requires exact current-version CAS. No fixture seed is promotable. -`state_hub_preflight_lane.py` is a reusable design pattern, not an Ed25519 writer; -the signer-specific primitive now exists in `scripts/flex_auth_signer_custody.py`, -with a fixed disposable-only adapter. Production command/CCR admission is still -missing and the module rejects direct live execution. -Do not run the HMAC writer unchanged or claim D1 admission without that command. -The read lane does not authorize writer scope. Use the existing attended -OpenBao/warden access route; ops-warden routes and never vends this seed. - -Proposed namespace SecretStore uses dedicated SA, audience `openbao`, TTL/max/ -explicit max 15m, no default policy, no periodic token. Proposed Secret name -`flex-auth-secrets-engine-envelope-signing`, key `seed`; owner creation and Retain -deletion policy avoid implicit removal on custody outage. Retain also means a -compromised seed survives until explicit cleanup: disabling ESO is not revocation. -The current Access chart supplies readOnly volume, defaultMode 0440 and uid/gid/ -fsGroup 65532. Seed path is `/var/run/secrets/decision-envelope-signing/seed`; -private data is never envFrom. Runtime validates file mode and reads once at -startup, so ESO refresh alone cannot rotate process memory. Freeze exact Secret -resourceVersion, KV version, public admission and startup kid together; require -all replicas loaded the same admitted version before probes. No extra key reload -mechanism is proposed. - -## Durable evidence and output limits - -Reuse current Access `outbox.enabled`, existingClaim and source support: -`--outbox-dir` plus `--emission-source`, replicaCount 1, Recreate, one writer/PVC. -Do not combine with `--log`. Outbox AppendBatch fsyncs the source envelope before -returning; ordinary JSONL alone does not prove native independent custody. -Existing outbox has no total-byte retention ceiling: chart size 1Gi is requested -storage, NOT an enforced log quota. Before rollout name admitted existing PVC, -capacity/headroom, alerts, bounded export/retention, disk-full fail-closed behavior, -restart/partial-write readback tests and independent receiver/audit receipt. -Audit sender credential/receiver admission is separate, not created by this lane. -Do not silently create a PVC/spend commitment or enable arbitrary audit egress. -No current native durable/audit acceptance follows from the source feature. - -Protected writer success/failure receipts must be closed metadata objects, at -most 64KiB, stored owner-only mode 0600 with atomic durable writes. Fields: proposal -and executed revision digests, actor/time, exact path, CAS version, kid/public -fingerprint, deployment/image/policy/trust digests, boolean checks, rollback outcome. -Never include response bodies, seed, bearer tokens, accessors, wrapped values, -process environment or exception text. Bound backend reads to 1MiB and safe error -codes; no shell tracing. Public admission may carry the public key and must have -independent authenticated delivery. Access R2 native proof reader bounds receipt -reads to 524289 bytes, rejecting >524288; that is not a durable writer quota. - -## Rotation, revocation and rollback - -Admit successor public key to each consumer first through independent custody -and verify seed/public correspondence in the protected custody writer. Fence -protected consumers and stop every signer replica, prove no old-key process -remains, CAS-rotate, wait ESO version, restart the one-writer pin, then verify -successor-signed non-effect lifecycle allow/deny and durable originals before -reopening protected consumers. -After last old issuance retain old key online no longer than actual maximum allow -TTL plus a reviewed bounded uncertainty margin (default TTL is not a guarantee). -Keep archived historical keys for offline audit separate from online trust; verify -expired envelopes remain refused. Emergency compromise removes the old online key -immediately, stops old processes, and recovers forward; never restore revoked seed. -No scope/admission change to task policy: service pilot stays hard DENY and -execute-ccr stays unknown_action on lifecycle policy. - -On signer rollout failure, stop protected consumers and restore exact previous -producer image/values only as availability rollback; enforcing consumers continue -to reject unsigned decisions. Outbox/PVC and source receipts are retained. Revert -seed only for independently reviewed non-compromise recovery with corresponding -public admission; otherwise forward recovery. If grant or effect outcome is unknown, -retain unknown and reconcile; never auto-retry or translate it to success. - -## Gates and next owner work - -The JSON lists exact missing inputs and approvals. Oct11 D1 preapproves a reviewed -receipt-producing lane for an attended window; it does not create an unbuilt lane. -Oct11 standing build-mode rule permits qualifying non-destructive rollout after -reviewed diff/dry-run, rollback, abort owner and receipts; new secret/access scope -still needs founder approval. Source reviews are not human approval. Supervision -record remains supervised; costs/risk and exact executor must be admitted for the -executable revision. Source preparation costs EUR 0 incremental infrastructure; -future resource/custody loss bounds are null rather than guessed or self-approved. - -Next Platform admission task under RPF-WP-0035-T12: stage a schema-valid proposed -CCR through existing credential-change tooling and independently admit the -protected production executor/fencing/receipt adapter after required approvers -confirm the exact new custody scope. The primitive is source-tested only. Reuse existing lane -machinery. A receipt-producing command does not grant its write/read scope. -Keep CCR-2026-0048/0049, WORK-RECORDS and unrelated work unchanged. - -## Executable source primitive R2 - -The fixed disposable owner adapter reuses the native fixture's absolute two-second -transport/body/close bound and private receipt checks. It generates a new Ed25519 -seed only in the custody process, derives its public key, CAS-writes the exact -three-field document, and independently reads that exact KV version. The private -value is compared in memory; receipts carry public key/fingerprint and version -only. Rotation must CAS the expected current version and uses a fresh seed/kid. -The fixture requires explicit owner fencing and a still-live exact loopback dev -process. It cannot establish production fencing, process isolation or grant scope. - -The owner-only receipt is exclusive per operation, bounded64KiB, fsynced before -any effect, atomically replaced and directory-fsynced. Once write intent exists, -any denied/malformed/lost response or mismatched readback retains unknown. Neither -a repeated object call nor process restart using that operation receipt can retry. -There is no automatic reconciliation, rollback or seed restore. A public receipt -requires independent authenticated owner delivery before any consumer admits it. -Python cannot guarantee seed zeroization; the protected process lifetime remains -the custody boundary. Same-UID code is not isolated by0600 files. - -Actual disposable tests prove bootstrap CAS0, versioned readback, seed/public -correspondence, rotation CAS1, stale CAS conflict, response-loss uncertainty, -read denial/mismatch, pre-effect durable-intent failure, receipt secrecy, retry -refusal and invalid admission/fencing rejection. No fixture key is promotable. - -R3 metadata correction: every current proposal identifier is -`RPF35-PDP-SIGNER-CUSTODY-20261016-R3`, including the Secret annotation. Frozen -R1/R2 evidence remains historical. The writer public_fingerprint convention is -`sha256:` plus lowercase SHA-256 hex of the canonical unpadded base64url public -key ASCII string (43 bytes for a 32-byte Ed25519 key). It does not hash the raw -decoded bytes. This names the existing writer convention without changing code -or admitting any consumer. Public admission must explicitly agree this encoding -with the independent consumer; a raw-byte fingerprint must use a separately -named field/version and never be silently substituted. diff --git a/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json b/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json deleted file mode 100644 index a3d3a8a..0000000 --- a/docs/credential-lane-designs/flex-auth-envelope-signing.proposal.json +++ /dev/null @@ -1,119 +0,0 @@ -{ - "schema": "railiance-platform.signer-custody-proposal.v1", - "proposal_id": "RPF35-PDP-SIGNER-CUSTODY-20261016-R3", - "status": "source-proposal-unadmitted", - "owner": "railiance-platform", - "work_item": "RPF-WP-0035-T12", - "coordination": "CUST-WP-0080", - "beneficiary": "FLEX-WP-0024-T06", - "scope": { - "namespace": "flex-auth", - "release": "flex-auth-secrets-engine", - "signer_principal": "system:serviceaccount:flex-auth:flex-auth-secrets-engine", - "consumer_principal": "system:serviceaccount:secrets-engine:secrets-engine", - "rapp_declaration_ref": null, - "migration_debt": "Managed package declaration not established; retained Helm workload is not an exemption.", - "access_identity_source": "access-engine integrated source revision 919ce25 tenancy.yaml#/workload_identity; runtime admission remains separate" - }, - "proposed_lane": { - "catalog_id": "flex-auth-decision-envelope-signing-key", - "route_status": "draft/nonresolvable", - "kv_mount": "platform", - "kv_path": "workloads/flex-auth/envelope-signing", - "private_field": "ED25519_PRIVATE_KEY", - "public_fields": [ - "ED25519_PUBLIC_KEY", - "kid" - ], - "policy": "workload-kv-read-flex-auth-envelope-signing", - "auth_mount": "kubernetes", - "auth_role": "flex-auth-envelope-signing-eso", - "delivery_sa": "flex-auth-envelope-signing-eso", - "audience": "openbao", - "token_ttl": "15m", - "token_max_ttl": "15m", - "token_explicit_max_ttl": "15m", - "token_no_default_policy": true, - "secret_store": "openbao-flex-auth-envelope-signing", - "secret_name": "flex-auth-secrets-engine-envelope-signing", - "secret_key": "seed", - "mount_path": "/var/run/secrets/decision-envelope-signing/seed", - "mode": "0440", - "private_encoding": "unpadded strict base64url of 32-byte Ed25519 seed", - "seed_load": "startup only" - }, - "independent_public_admission": { - "kid": null, - "public_key": null, - "fingerprint": null, - "custody_receipt": null, - "consumer_trust_digest": null, - "rule": "Owner-authenticated public receipt independently installed with exact evaluator/mode/policy pins; no self-discovery or private KV read by consumer." - }, - "required_approvals": [ - { - "actor": "founder", - "scope": "New exact KV read/write custody policy scope and dedicated delivery role; exact proposed CCR/revision and owner reviews required; no approval inferred." - }, - { - "actor": "Access owner", - "scope": "Exact retained workload identity/source, signer interface, unchanged policy/caller binding, image provenance and producer rollout." - }, - { - "actor": "Platform owner", - "scope": "Custody path, protected CAS writer, delivery identity/role and recovery receipts; this source review only." - }, - { - "actor": "Secrets owner", - "scope": "Independent public key/trust pins, installed helper provenance and fail-closed native consumer acceptance." - }, - { - "actor": "cluster/GitOps owner", - "scope": "Namespace SecretStore/ESO TokenRequest restrictions, manifests, rollout/headroom, existing PVC and recovery." - } - ], - "missing_deployment_inputs": { - "schema_valid_reviewed_CCR": null, - "human_exact_scope_decision": null, - "protected_signer_writer_command_digest": null, - "approved_executor_and_abort_owner": null, - "access_identity_integrated_revision": "919ce25", - "managed_package_admission_or_explicit_debt_disposition": null, - "CI_source_to_image_receipt": null, - "candidate_image_digest": null, - "KV_and_Secret_safe_version_receipt": null, - "native_identity_and_negative_scope_receipt": null, - "consumer_native_allow_deny_receipts": null, - "PVC_and_capacity_retention_admission": null, - "durable_original_and_independent_audit_receipt": null, - "reviewed_diff_server_dryrun_and_headroom": null, - "rotation_revocation_and_recovery_receipts": null, - "activation_cost_bound_eur": null, - "activation_risk_bound_eur": null - }, - "unchanged_policy": { - "package": "secrets-engine.catalog-lane.lifecycle", - "version": "v2", - "digest": "sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4", - "caller_auth": "enforce", - "caller_audience": "flex-auth", - "execute_ccr": "DENY unknown_action", - "service_pilot": "runtime_admission false" - }, - "execution": { - "authorized_by_this_proposal": false, - "key_created": false, - "route_promoted": false, - "live_changes": false, - "preparation_incremental_spend_eur": 0 - }, - "source_writer": { - "path": "scripts/flex_auth_signer_custody.py", - "scope": "protected in-memory Ed25519 CAS/readback primitive, disposable-only fixed owner fixture adapter", - "production_entry": "refused pending exact CCR/founder scope/protected runtime/fencing/receipt admission", - "independent_public_admission": "required after custody verification; receipt shape supplies no trust authority", - "retry": "none after durable intent; unknown requires independent reconciliation, no remint", - "rollback": "forward recovery; no seed restore primitive", - "public_fingerprint_convention": "sha256: plus lowercase SHA-256 hex of canonical unpadded base64url public-key ASCII (43 bytes for Ed25519); not raw decoded key bytes" - } -} diff --git a/docs/credential-lane-designs/fluid-telegram-operator-kv.md b/docs/credential-lane-designs/fluid-telegram-operator-kv.md index 8bdc694..98abf0e 100644 --- a/docs/credential-lane-designs/fluid-telegram-operator-kv.md +++ b/docs/credential-lane-designs/fluid-telegram-operator-kv.md @@ -1,8 +1,6 @@ # Fluid-telegram attended operator KV lane -Status: source ready as CCR-2026-0039 (2026-10-09), not provisioned. Owner: -railiance-platform, RPF-WP-0033 → RPF-WP-0035-T03. Tenant `coulomb` ruled by -the founder 2026-10-09. +Status: proposed, not provisioned. Owner: railiance-platform, RPF-WP-0033. Demand: State Hub message `170b9127-f18f-4045-8f64-5c211a3aa187`. Construction reference: `../ops-mason/plans/fluid-telegram-operator-credential-lane.md`. Consumer source: `../fluid-telegram/internal/secrets/secrets.go`, `session.go` diff --git a/docs/credential-lane-designs/oidc-service-client-review.md b/docs/credential-lane-designs/oidc-service-client-review.md deleted file mode 100644 index a4b9d69..0000000 --- a/docs/credential-lane-designs/oidc-service-client-review.md +++ /dev/null @@ -1,162 +0,0 @@ -# oidc-service-client 0.1: custody lane and consumer role (owner review, railiance-platform) - -Owner review from railiance-platform for net-kingdom's proposed SecurityCaseType `oidc-service-client` 0.1 -(net-kingdom `security-case-types/oidc-service-client/`, commit 5d5f15d; hub decision `f8044f81`; memo -`nk-wp-0056-t14-oidc-service-client-r1`). Request: State Hub message `4ec57765`. Per canon -security-case-types_v0.1 section 8 this review comes before the founder acknowledges the type. - -Everything here is source review and proposed drafts. No OpenBao role, policy, KV entry or Secret was -created, read or changed. - -## Verdict - -**Shape accepted with changes.** The custody path family, the mint grant and the consumer-role shape fit -railiance-platform's contracts. Four type-side corrections are needed (R3, R4, R5, R7), which makes this a -memo r2. One platform-side change is needed before the first mint (R1). Each case needs its own CCRs (R6). -For the first case they are drafted: CCR-2026-0060 (ESO reader) and CCR-2026-0061 (consumer reader), both -`proposed`. - -## Findings - -### R1. Mint lane: the OpenBao grant covers the path, the broker does not admit the type yet. Verdict: platform change required - -- The grant (CCR-2026-0040, policy `keycape-client-secret-generate`) is create-only on - `platform/data/workloads/keycape/+`. It denies exactly the reserved `secrets-engine-openbao`. - `nk-bind-executor-approval` is one segment, matches the type's `client_id` pattern (the same pattern as - the browser type) and is not reserved. The issuer binding (CCR-2026-0043, net-kingdom-admins + aal2) - covers it too. **At the OpenBao layer no new grant, policy or CCR is needed** for service-client paths. -- The broker entry point `scripts/keycape_client_secret_generate.py` refuses the case today, for three - reasons: - - `ACKNOWLEDGED_TYPES` admits only `oidc-browser-relying-party@1.1`. The result is - `case_type_unacknowledged`. - - It requires `parameters.client_type == confidential`, but the service type has no `client_type` - parameter, because it is confidential by construction. The result is `case_client_not_confidential`. - - It requires case status `materialized`. -- Needed: an owner-reviewed source change that adds `oidc-service-client@` to - `ACKNOWLEDGED_TYPES`, with its type-byte and `PARAMETER_PINS` fingerprint. The change must make the - confidential check type-specific: the service type is implicitly confidential. Refusal tests must cover an - unacknowledged service type, a drifted source and a client_id mismatch. It follows the RPF-WP-0060-T01 - rule: no speculative pin before genuine acknowledgment. It can therefore only land after the founder - acknowledges the type. Until then, `mint-secret` for this case refuses (fail closed). -- The KeyCape secret format (48 bytes, URL-safe, confirmed by key-cape for confidential clients) applies - unchanged to `client_credentials` clients. - -### R2. Consumer-reader role shape. Verdict: consistent - -The role shape matches the one-role-per-lane rule and the CCR-2026-0058/0059 shape: - -- Kubernetes auth; -- one ServiceAccount and one namespace; -- audience `openbao`; -- `ttl`, `token_max_ttl` and `token_explicit_max_ttl` all 15m; -- `token_no_default_policy`; -- exactly one policy. - -It is a separate role from the executor's CCR-2026-0058 write role, the CCR-2026-0059 journey reader and the -ESO reader. The policies are never combined. `scripts/credential-change.py` enforces this: the validator -requires `policies == [policy_name]`, and the applier requires "only openbao.policy_name". - -The `auth/token/lookup-self` (read) and `auth/token/revoke-self` (update) stanzas are **correct and -expected**. They are exactly what `token_self_lifecycle: true` generates (`generated_policy_hcl`), and they -match CCR-2026-0033's amendment, CCR-2026-0041/0042, CCR-2026-0059 and the 0058 write policy. Together they let -the reader end its own token after each call. The browser type omitted them, and the platform had to add them -itself for CCR-2026-0042. The service type gets this right. - -Minor wording: verification `custody-allowed-to-consumer` says the policy grants read on the path "and -nothing else". It should say "and nothing else besides its own token's lookup-self and revoke-self". (Type -text; fold into r2.) - -### R3. ESO reader (keycape-reader-policy, eso-role) lags behind the platform shape. Verdict: change the type (r2) - -The type inherited the browser type's bodies: - -- the policy has no self-lifecycle stanzas; -- the role has no `audience: openbao` and no `token_explicit_max_ttl`. - -The applied browser instance (CCR-2026-0041) and CCR-2026-0033 carry `token_self_lifecycle: true`, -`audience: openbao` and `token_explicit_max_ttl: 15m`. A policy rendered from the type as written would fail -the applier's "policy_file does not match generated CCR policy" check. Align both bodies with the consumer -reader. The same gap exists in `oidc-browser-relying-party`; that is a separate note for its owner, not a -blocker here. - -### R4. Consumer role name: the delegated applier refuses the template. Verdict: change the type (r2) - -The type names the role the same as the policy: `workload-kv-read-keycape-{client_id}-{consuming_service}`. -The delegated applier (`applier_policy_violations`) admits Kubernetes roles only if they end with -`-workload-kv-read` or `-secrets-read`, or start with `external-secrets-`. As written, every case could only -be applied by an attended out-of-band tool. CCR-2026-0059's role was applied that way: its name fails the same -check, and it was applied through net-kingdom `executor-lanes.py`. - -Proposed template: `keycape-{client_id}-{consuming_service}-workload-kv-read`. The policy name stays as it -is, because it satisfies the required `workload-kv-read-` prefix. The `hand-over` step, the -`consumer-login-bound` verification and the rollback text must use the role name. CCR-2026-0061 is drafted -with the proposed name, and `applier_policy_violations` returns no violations for it. - -### R5. Rotation is not mint-secret. Verdict: change the type text (r2) - -The README ("Rotation is mint-secret, then the consumer's next exchange") and the catalog entry ("rotation: -mint-secret then verification") are wrong for this lane. The grant is create-only (CAS 0). It refuses an -existing entry with exit 3 and can never write a second version. No rotation lane exists: - -- RPF-WP-0054-T07 was cancelled as transferred; -- the follow-up is the residual RPF-WP-0055, which needs its own CAS/update authority and ESO and KeyCape - coordination. - -State it plainly: until that lane exists, rotation is an attended platform-admin write with CAS=current, -followed by an ESO refresh and a KeyCape restart. The consumer needs no change, because the path stays the -same. - -### R6. Each case needs its own CCRs; the type does not stand in. Verdict: per-case CCRs required (drafted for the first case) - -This is the same as for browser cases. The forgejo-netkingdom-oidc case got its own CCR-2026-0041 (ESO -reader) and CCR-2026-0042 (consumer reader), rendered by ops-mason and carrying a `security_case` block. - -What the acknowledged type replaces: - -- the shape review: the platform does not re-argue the role and policy form; -- a fresh executive summary: the case acceptance is cited instead. - -What it does not replace is the per-case platform-owner approval of the exact names, paths and bound claims, -nor `binding_confirmed`, nor the per-case apply and verification receipts. Only the mint needs no per-case -CCR: the standing CCR-2026-0040 and CCR-2026-0043 grant covers the family, gated by the broker's case -admission (R1). - -For nk-bind-executor-approval, drafted `proposed` (both pass `scripts/credential-change.py validate` and the -delegated-applier checks; `bound_claims_confirmed: false`, so apply stays blocked): - -- `credential-change-requests/CCR-2026-0060-nk-bind-executor-approval-keycape-eso-reader.yaml` - - role `external-secrets-keycape-nk-bind-executor-approval` - - policy `openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-eso.hcl` - - approver: platform-owner -- `credential-change-requests/CCR-2026-0061-nk-bind-executor-approval-consumer-reader.yaml` - - role `keycape-nk-bind-executor-approval-bounded-executor-workload-kv-read` - - policy `openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor.hcl` - - bound to `sso/nk-bind-executor`, audience openbao, 15m - - approvers: platform-owner and net-kingdom-owner (an executor lane, as CCR-2026-0058/0059) - -Activation conditions for both: - -- the type is acknowledged; -- the case is accepted; -- the custody entry is minted (after R1); -- the CCR is approved and the binding confirmed. - -If ops-mason later renders the case, these drafts are what the render must reconcile to; they are not -duplicates to be added beside it. The full-repository validate still reports the known, unrelated -CCR-2026-0035 failure. - -### R7. KeyCape gets a Secret copy; the consumer does not. Verdict: accepted, with a note - -KeyCape's ESO target `sso/keycape--client` is a Secret in namespace `sso`, the same namespace as -the consumer. "No Secret copy in the consumer namespace" holds for the consumer's own delivery, but not for -the namespace. The README's control text should say "no copy for the consumer" and name the KeyCape Secret -as the one at-rest copy, as CCR-2026-0033's risk note does ("one value, two copies"). This is a wording -change for r2; the boundary is unchanged (sso namespace writers are already inside it, CCR-2026-0033). - -## Open items - -1. net-kingdom: memo r2 with R3, R4, R5 and R7, plus the R2 wording. -2. Founder: acknowledge the type (r2), then accept the case. -3. railiance-platform: broker admission for `oidc-service-client@` after acknowledgment (R1). This - needs a workplan task here. -4. Founder: approve CCR-2026-0060 and CCR-2026-0061 and confirm their bindings (R6). diff --git a/docs/credential-lane-designs/secrets-engine-service-jwt.md b/docs/credential-lane-designs/secrets-engine-service-jwt.md index ec670dc..6d4b947 100644 --- a/docs/credential-lane-designs/secrets-engine-service-jwt.md +++ b/docs/credential-lane-designs/secrets-engine-service-jwt.md @@ -1,7 +1,6 @@ # Secrets-engine service JWT login -Status: CCR-2026-0038 applied 2026-10-08 (founder, readback matched); login proof -(SECRETS-WP-0012-T03 probe-openbao) pending. Owner: railiance-platform, RPF-WP-0035-T02 (design origin RPF-WP-0032). +Status: proposed, not provisioned. Owner: railiance-platform, RPF-WP-0035-T02 (design origin RPF-WP-0032). Demand: State Hub message `38b47122-07eb-4a7f-a5df-13a38f50e110`. ## Contract and ownership @@ -151,14 +150,3 @@ its source contract or local managed IDs. `docs/provisioning-windows/2026-10-04-1000/dependency-requirements.md` names all source/acceptance/reopen boundaries. RPF-WP-0035-T02 remains wait; its workplan is blocked on those real dependencies and separate attended execution approval. - -## Source complete — 2026-10-09 - -KEY-WP-0037 finished 2026-10-07 and CCR-2026-0033/0034 are applied, so the -"before writing" preconditions are met. The reviewed bundle is now packaged as -CCR-2026-0038 (`request_type: service-jwt-login`), applied by the delegated -applier (`scripts/service_jwt_lane.py`): policy, `auth enable` of -`keycape-services` only if absent (refuses a foreign type), config, role, then -a value-free readback compare. The self policy gained explicit denies on the -client-secret data/metadata paths (ops-mason MASON-WP-0009 invariant (ii)). -The founder command is recorded in RPF-WP-0035-T02. diff --git a/docs/credential-lane-designs/secrets-engine-task-execution-entitlement.md b/docs/credential-lane-designs/secrets-engine-task-execution-entitlement.md deleted file mode 100644 index 9021191..0000000 --- a/docs/credential-lane-designs/secrets-engine-task-execution-entitlement.md +++ /dev/null @@ -1,165 +0,0 @@ -# Secrets Engine task execution entitlement — Platform owner design - -Prepared under RPF-WP-0035-T11; implementation belongs to RPF-WP-0035-T12. -This is a proposed source design, not an installed endpoint, approved CCR, -founder disposition or native acceptance. CCR-2026-0038 stays login-self-only. -No service default changes and no standing service administrative ACL. - -## Existing machinery and exact first action - -Platform INTENT §§ Stable Interfaces, Secure Custody and Operable/Observable -require a stable constrained backend interface. Platform owns OpenBao enforcement -and credential lifecycle; KeyCape owns identity, access-engine decides, approval-engine -consumes, and Secrets Engine owns the lifecycle PEP. Platform is Staff with declared -Tooling gaps, not a replacement identity or authorization engine. - -Current `cli._require_lane_approval` consumes before `_open_backend`, which gives -`apply_plan` the login-self token. That token cannot read/write target ACLs or -AppRole roles. Login proof cannot complete functional Lifecycle adoption. - -First action: a new explicit **execute-ccr** interface applying exactly one reviewed -`workload-kv-read` CCR's two structure objects: its generated reader ACL and one -OIDC/Kubernetes role on an existing mount. Refuse mount enable/config, AppRole, -KV data/metadata operations, SecretID issuance, token delivery, arbitrary argv, -free-form policy bodies, broader typed CCR dispatch and generic `apply_plan`. -Generic Secrets Engine apply supports mount/AppRole work; silently translating -that action to this owner interface would change its meaning. - -Reuse `credential-change.py`'s workload-kv-read validator, generated policy, -`auth_payload`, readiness/binding checks and metadata applier structure. Existing -`service-jwt-login` remains identity only; existing `custody-generate-grant` supplies -patterns for exact generated policy/issuer/token-role TTL/no-default/cleanup, but -its `platform/workloads/keycape/+` create grant cannot perform structure writes. -A new separately reviewed request type **task-execution-grant** is necessary; -it is not yet in `LANE_MODULES`, so presenting it today must be refused. - -## Bound request and interface - -Proposed version `platform-task-execution/v1`, initially a founder-attended owner -primitive, not an unattended HTTP service. Input is a bounded private pipe containing -a non-secret request envelope; service authentication is a separate memory-only -credential channel. No credential enters command argv, environment, receipt or Git. -The owner launches the exact reviewed primitive through the existing attended -platform-admin envelope only after the scoped founder go is recorded. - -Envelope fields: unique `operation_id`; `action=execute-ccr`; platform realm and -stage; service principal `service:secrets-engine` of type service; consumer catalogue -revision/hash; exact CCR id/content SHA256; all authoritative generated source/body -SHA256s; ordered endpoint/method/payload digests; exact canonical CheckRequest and -its digest; served approval id/claim binding; PDP DecisionEnvelope id/package/version, -not-before/expiry; successful consume binding; owner admission/source revision. -The canonical request includes operation id, CCR digest, catalogue digest and the -whole target/body plan, not merely a friendly lane name. Local owner files are the -source; supplied payloads, file paths or HCL are never applied. Secret-bearing -request fields and unknown fields are refused. - -Action/PDP spelling is a new consumer contract to publish with access-engine. -Reuse its canonical digest/claim validation, do not invent an interchangeable -`apply` decision. Service identity and caller subject are independently matched; -a caller assertion of `service:secrets-engine`, a decoded JWT or a successful -human OIDC login is insufficient. Authenticate against CCR38's exact native role -and independently verify lookup creation path, role, effective token/identity -policies and service entity provenance. If the existing metadata does not bind -subject adequately, use KeyCape signature/issuer/audience/expiry validation plus -owner-correlated native role login; do not label role-only metadata a trusted subject. - -## Admission, consume and execution ordering - -1. Secrets Engine and Platform perform only local/static checks and public - unauthenticated discovery before consume: envelope/schema, owner CCR readiness, - exact source hashes, generated policy/role equality, full target/body plan, - owner primitive revision, journal availability and available bounded lifetime. - The founder's independently approved owner envelope is separate bootstrap - authority; it grants no consumer lifecycle action by itself. -2. Platform durably reserves `(approval_id, request_digest, operation_id)` with a - unique constraint and confirms the exact plan. Journal write must be fsynced - and atomic before acknowledgement. No database content is copied to evidence. -3. Secrets Engine obtains validated claim/PDP correspondence and performs the - existing approval-engine CAS consume for that same request. No protected consumer - login, client-secret read or backend write precedes this step. Platform accepts - neither a caller boolean nor an unsigned copy as proof: it independently validates - the served claim/PDP and verifies consume with approval-engine's authenticated - replay/verification contract. If this is unavailable, it refuses execution. -4. Bind the authenticated service identity after successful consume. Authenticate - once; refusal or timeout burns this admission. Platform atomically transitions - the same reservation to `consume-confirmed`, then `applying` before any effect. - A racing second call cannot acquire the state. Consume idempotency is not permission - to execute twice. Repeated calls return the recorded terminal receipt only. -5. Inside the founder owner envelope, mint an **owner-held ephemeral task token** - with only exact target paths. Apply exactly the pinned typed plan, compare - value-free native readback, revoke the task token, remove only this operation's - temporary grant role/policy after revoke, and finalize the durable receipt. - -New approval-engine verification/replay semantics and owner journal are required -implementation gates. They are not supplied by current `ConsumedApproval` dataclasses -or caller receipts. If approval-engine offers only idempotent consume, Platform may -make the authenticated same-digest consume call itself after the client call, but -must verify exact original correspondence and keep the independent execution -journal; never interpret idempotent success as a fresh admission. - -Journal states: `reserved → consume-confirmed → applying → verified|failed|unknown`. -Consume/auth failures become `failed`; uncertain consume, lost post-write response, -crash after `applying`, or cleanup failure become `unknown`. None automatically -re-executes. A retry requires owner reconciliation, native readback and a fresh -approval/operation id if another effect is needed. No rollback reuses the consumed -approval; retain partial state and prepare a separately approved exact recovery. - -## Backend policy, payload and time enforcement - -Ephemeral role/policy names derive from the operation hash and are owner-controlled. -Proposed token: service, orphan, nonrenewable, no default/identity policies, TTL and -explicit maximum at most **120s**, and **12 uses maximum** including cleanup. -Actual expiry is the earliest approval/PDP expiry, founder window expiry, authenticated -identity/session expiry and now+120s; refuse insufficient remaining time. Budget -is a reviewed fixed operation plan, never an arbitrary caller num_uses. Reserve -at least one self-revoke use and refuse exhausted/expired executions. - -Grant endpoints are exactly `sys/policies/acl/` and -`auth//role/` with only required read/create/update; -self lookup/capabilities/revoke have explicit minimal grants. Explicit deny applies -to guarded client-secret data/metadata, token create/renew, secret-id and all -unrelated structure targets. Do not publish blanket deny on all ACL paths if it -would also override the two intended target grants. The target-set allowlist and -native denied sibling/admin/data probes define this exact scope. - -The founder-held issuer may mint only the unique task role; the service role -never receives issuer/token-create authority. Existing platform-admin bootstrap -remains founder-attended; no persistent administrative credential is introduced -for Secrets Engine. No task token is returned to the caller or reused by generic -`_open_backend`. Returning one would allow direct OpenBao bypass of lifecycle checks. - -OpenBao ACL limits endpoint paths/capabilities, but **cannot constrain the HCL or -role JSON body or bind an approval digest by itself**. Independent Platform mediation -validates the exact body hashes against locally generated owner-approved source -immediately before each call; this is part of backend enforcement. A policy-write -token exported to a caller would let it replace the target ACL with broad powers, -and an auth-role-write token could bind a privileged policy. Therefore raw exact-path -ACL is not sufficient execution authority. Native ACL probes prove endpoint scope; -independent mediator counterexamples must prove body/action/approval scope. - -## Evidence, acceptance and rollback - -Value-free receipt: proposal/source/CCR/request/decision/consume/journal digests, -principal fingerprint and role, reserved/executed timestamps, fixed endpoint/payload -hashes, native request ids or safe hashes, readback equality, task/session accessor -fingerprints, exact granted TTL/uses, revoke and temporary-object cleanup booleans, -terminal state. No raw tokens, JWTs, full accessors, errors, KV responses or secrets. -Receipt return is separate from protected-action success: unknown cleanup never -reports verified. Retain outcome after client disconnect for safe read-only recovery. - -Required implementation and native proofs: intended disposable metadata-only action -passes once; wrong identity/type/issuer/audience/claims, altered CCR/catalogue/body, -wrong action/target, stale revision, TTL/use exhaustion, invalid/expired/mismatched -PDP or consume, missing journal, racing/replayed request, direct raw-token sibling -write, policy-body escalation and role-policy substitution all refuse. Crash/timeout -at reserve/consume/mint/write/readback/revoke/temporary cleanup must preserve unknown -and prevent retry. Native ACL denials and mediator refusals must be distinguished -from transport failure. Restore/delete only the disposable objects by their owning -reviewed recovery action; never broaden the service policy to discover gaps. - -The initial pilot does not cover provision/rotate/handoff/wrap/exec/deactivate, -KV destruction or the existing generic AppRole apply. Each needs its own typed -operation/body contract and owner evidence. Full steady-state adoption additionally -needs dispatcher integration, real intended result and accounting/reservation -reconciliation, credential-free caller execution and reviewed default migration. -RPF35-T02 and Secrets T06 cannot close from the login verifier or this design. diff --git a/docs/email-connect-smtp-admission-2026-10-31.md b/docs/email-connect-smtp-admission-2026-10-31.md deleted file mode 100644 index 7f9de85..0000000 --- a/docs/email-connect-smtp-admission-2026-10-31.md +++ /dev/null @@ -1,99 +0,0 @@ -# Email-connect platform mailbox reader preparation - -CCR-2026-0056 proposes one additional reader of the existing platform SMTP entry. -RPF-WP-0068 tracks preparation, foundation/binding proof, attended admission and -native consumer delivery. The parent requirement is CSOC-WP-0015-T02. - -**The complete controller is now prepared** in -[the foundation packet](email-connect-smtp-foundation-review-2026-10-31.md). -Its41 SMTP tests pass. **Execution still awaits review/approval.** The dedicated service -account/store/projection have only passed server dry-run. Native binding is not -confirmed, the CCR is proposed, and no new policy or role has been installed. - -## Exact proposed authority - -The Kubernetes role and policy are `email-connect-platform-smtp-eso`, bound only -to the same-named service account in namespace `email-connect`, audience `openbao`, -TTL/max/explicit-max15m, with no default policy. The policy reads only -`platform/data/workloads/railiance-telemetry/smtp` plus self lookup/revoke. -There is no metadata, list, write, sibling-path or administrative authority. - -OpenBao grants access to the exact KV record, not individual fields. The consumer -ExternalSecret projects only SMTP_PASSWORD. Existing telemetry and Authelia -readers remain separate and unchanged. No mailbox value is copied, created or -rotated. The custody helper does not access IMAP or submit an email. - -User decision9392ef52 names platform-root and Bernd as return-mail owner/operator. -It is not approval of this new credential reader. The existing Authelia approval -also does not grant an additional consumer. The [CCR workflow](credential-change-approval.md) -requires approved requests and confirmed native bindings before apply; shared -readers are outside its automatic standard-lane profile. - -## Implemented helper - -`scripts/email_connect_smtp_delivery.py` defaults to an offline proposal. Its -digest binds the complete functional CCR scope, exact role/policy, seven source -files and observed OpenBao clusterfd28df5d. Approval comments/status and the native -binding-confirmation marker do not change that functional digest; execution -separately requires status approved and binding confirmed. - -The helper runs silently within the existing Warden attended platform-admin -login envelope. Before any mutation it requires the pinned initialized/unsealed -OpenBao instance, platform-admin without root, existing exact agent denial, and -nondivergent new reader targets. It snapshots the existing telemetry/Authelia -roles and policies plus the agent boundary. Existing mailbox settings/password -are validated privately; only fixed receipt fields and version may leave. - -It reuses the existing telemetry lane for certificate-verified STARTTLS AUTH, -exact role/policy admission and coding-agent denial/revocation proof. It then -compares all protected metadata and complete mailbox data/version with the -preimage. Drift or a failed check prevents a verified result. It does not modify -the shared helper or the existing admission lanes. - -The receipt is a new mode0600 **JSON Lines journal** in an existing caller-owned -private directory. It fsyncs the initial record and directory before owner calls, -then appends an admitting/unconfirmed record before the shared lane runs. Final -success/failure is appended rather than overwriting prior checkpoints. A process -interruption can leave an incomplete last line; inspect the last complete record -and native state, not merely the process exit code. Existing receipt paths refuse. -No automatic rollback or retry is attempted after an uncertain admission. - -A successful custody result is `reader_admitted_verified`. It deliberately keeps -`native_consumer_auth_verified`, `projection_ready`, `kubernetes_resources_installed` -and `application_sender_changed` false: these are separate native returns, not -claims inferred from role configuration. Warden must also report successful -session revocation/cleanup; a successful child receipt cannot override an -unconfirmed owner session. - -## Validation - -`python3 -m unittest discover -s tests -p 'test_*smtp*.py' -q` passes29 tests, -including15 new fixed-consumer cases. They cover contract/approval/binding/digest -refusal; wrong instance/sealed/root; unchanged readers and mailbox; divergent -role/policy refusal before creation; idempotent existing exact admission; -private append-only receipts; interruption; silent refusal and output containment. -Provider/Bao calls are synthetic; no native credential was fetched. - -CCR validation passes with the intentional unconfirmed-binding warning. Default -proposal and Python compilation pass. Source hashes, proposal and observation -are in `docs/evidence/email-connect-smtp-admission-preparation-2026-10-31.json`. - -## Remaining sequence - -1. Complete the supervised foundation/projection controller, including native - identity checks, progress receipts and failure inspection. The current consumer - candidate has three new objects and two later sender patches; only the first - three belong to this custody workplan. -2. Present the complete scope for CCR approval. Install and verify the reviewed - dedicated service account through the attended path, then confirm its native - binding using the existing owner workflow. -3. After approved CCR and confirmed binding, execute this silent helper through - `warden access openbao-platform-admin-login --exec -- `. - Use an absolute private receipt path because Warden uses an isolated HOME. - Do not execute now: the guards currently refuse before owner operations. -4. Verify actual scoped login and ESO delivery, retain denied-other-consumer - evidence, and record this third consumer in shared-mailbox rotation inventory. -5. The consumer owner performs its separate supervised sender rollout and - productive message/inbox/return-handling acceptance under CSOC-WP-0015-T02. - -No new destination approval or reapproval of platform-root ownership is needed. diff --git a/docs/email-connect-smtp-foundation-review-2026-10-31.md b/docs/email-connect-smtp-foundation-review-2026-10-31.md deleted file mode 100644 index 12f7f24..0000000 --- a/docs/email-connect-smtp-foundation-review-2026-10-31.md +++ /dev/null @@ -1,128 +0,0 @@ -# Email-connect platform SMTP foundation review - -Prepared for CCR-2026-0056 and RPF-WP-0068, supporting CSOC-WP-0015-T02. -This is a concrete review packet, not evidence of live installation. - -## Decision scope - -Admit email-connect as a third reader of the existing platform SMTP mailbox, -alongside telemetry and Authelia. Create exactly one service account, namespace -SecretStore and ExternalSecret in email-connect. The scoped Kubernetes role reads -the whole exact OpenBao KV record at -`platform/data/workloads/railiance-telemetry/smtp`; ESO projects only SMTP_PASSWORD -into the new Secret's `password` key. OpenBao does not enforce field-level access. -The role has no default policy, a maximum 15-minute lifetime and only the exact -service-account/namespace binding with audience openbao. - -This does not change the email-connect deployment or sender, create a caller, -send email, scan a mailbox, activate backup monitoring, rotate the shared password, -or grant suppression authority. Those live obligations remain in CSOC-WP-0015. -The user already selected platform-root, with Bernd as human operator, for replies -and bounces. This new reader requires its own admission review. - -The source-backed foundation proposal is: - -`sha256:f8da6c831160e1c6354ce54f936809d2d4fb48eb45eb23484dc0bcdfe0bb52f0` - -Reproduce it without credentials or network operations: - -```bash -python3 /home/worsch/railiance-platform/scripts/email_connect_smtp_foundation.py -``` - -The digest binds the controller, all three resources including server defaults, -cluster/namespace/application identities, fixed kubeconfig path and admission -digest. The latter binds the CCR's functional scope, policy and seven dependency -files. Changes to execution code or scope require a new proposal. Approval and -native binding evidence do not change functional scope. - -## Preconditions and approval boundary - -CCR-2026-0056 remains **proposed**, with native binding unconfirmed. The -[credential workflow](credential-change-approval.md) states: “Only `approved` -requests may be applied.” Its shared-reader lane requires platform review; -automatic single-reader approval does not apply. Its client workflow also states: -“The **founder** resolves the State Hub decision” and “Clients do not run -`sync-decision` on their own CCRs.” No review, approval or sync is impersonated here. - -The completed packet is ready for that platform/consumer review and founder -decision. After the owner workflow has made the exact CCR approved, the founder -can run the attended command below. The controller creates and verifies the -dedicated service account, then uses canonical confirm-binding itself before -admitting the OpenBao role. The operator need not falsely confirm an absent SA. - -## Attended command after approval - -Run in the founder's terminal; this is not an agent execution instruction. -The OpenBao relay must be available at port18200 and the fixed primary kubeconfig -must be present. `warden plan` resolves this operation to -openbao-platform-admin-login with one interactive OIDC/MFA login. - -```bash -mkdir -m 700 /home/worsch/email-connect-smtp-admission-20261031 -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/email_connect_smtp_foundation.py \ - --execute \ - --expect-digest sha256:f8da6c831160e1c6354ce54f936809d2d4fb48eb45eb23484dc0bcdfe0bb52f0 \ - --receipt /home/worsch/email-connect-smtp-admission-20261031/receipt.jsonl -``` - -The directory creation is intentionally exclusive. If it already exists, inspect -its contents and prior receipts before proceeding; do not remove or overwrite -evidence. Do not run the second command if the first failed. The helper separately -requires a private caller-owned directory and a new receipt path. - -The child must be silent because Warden rejects child output. It records a private -mode0600 append-only JSON Lines journal, fsynced before changes. Share only the -last complete receipt and Warden outcome; never login tokens or provider values. -The expected final child status is `foundation_admitted_projection_ready`. -Warden must also report successful session revocation and cleanup. - -## What execution verifies - -Before mutation, pinned OpenBao and Kubernetes cluster/namespace identities, -platform-admin without root, current deployment identity/generation and absence -of all three new objects and the target Secret are required. Objects are created -exclusively; none is applied over an existing object. No automatic rollback or -retry follows a partial result. - -The new service account disables automount. Its new short-lived JWT passes native -TokenReview for its UID, full service-account name and openbao audience. Only then -does canonical confirm-binding run. Guarded admission preserves the shared mailbox, -telemetry/Authelia roles and policies, and existing agent denial. It authenticates -to SMTP using certificate-verified STARTTLS without submitting mail. - -Actual workload OpenBao login must have only the dedicated policy and bounded TTL, -read its exact record, and deny metadata, siblings and mutation capabilities. -Wrong service account and wrong audience must fail. The workload token is revoked -and lookup must fail afterward. This does not claim an independently exercised -same-name/wrong-namespace fixture; exact namespace binding is checked in role -metadata and the positive namespace identity is pinned. - -Both store and projection must report Ready with exact reviewed specs; the new -Secret must name the new ExternalSecret as controller owner. Secret checks use -PartialObjectMetadata content negotiation through a private Unix-socket proxy, -with no full-object fallback. Secret values are neither requested nor journaled. -Existing application/configuration/old projection fingerprints must remain equal. - -If execution fails, retain the journal and inspect its last phase and native state. -There may be a created SA, admitted role or projection even if no final success -was written. Do not rerun blindly, revoke unrelated readers, rotate the mailbox or -roll out the application while outcome is uncertain. - -## Validation and remaining work - -41 synthetic SMTP tests pass, including12 controller cases and15 admission cases. -They exercise ordering, drift refusal, target collisions, binding refusal, partial -creation, privacy/exclusive journals, interruption, native token capability and -revocation checks, and metadata-only response enforcement/proxy cleanup. -All three resources passed a fresh native server dry-run. A read-only native -metadata query confirmed the target Secret absent. No credential was fetched by -the agent and no production resource was installed. - -Evidence: `docs/evidence/email-connect-smtp-foundation-preparation-2026-10-31.json`. -After the attended return, verify receipt and native state, record CCR verification -and rotation inventory, and hand the custody result to CSOC-WP-0015-T02. The sender -rollout, actual delivered-message authentication, inbox access, authoritative return -handling, suppression admission and retention remain separate unfinished work. diff --git a/docs/evidence/2026-10-09-fluid-telegram-t03-founder-session.json b/docs/evidence/2026-10-09-fluid-telegram-t03-founder-session.json deleted file mode 100644 index 08b1264..0000000 --- a/docs/evidence/2026-10-09-fluid-telegram-t03-founder-session.json +++ /dev/null @@ -1,147 +0,0 @@ -{ - "actor": "founder", - "ccr": "CCR-2026-0039", - "date": "2026-10-09", - "note": "Receipts copied verbatim; they hold versions, booleans and outcome codes only.", - "receipts": { - "custody_move": { - "credential_values_emitted": false, - "entries": { - "bot-token": { - "outcome": "copied", - "source_deleted": true, - "source_present": true, - "source_version": 1, - "target_present": false, - "target_version": 1 - }, - "operator-app": { - "outcome": "copied", - "source_deleted": true, - "source_present": true, - "source_version": 1, - "target_present": false, - "target_version": 1 - }, - "operator-session": { - "outcome": "copied", - "source_deleted": true, - "source_present": true, - "source_version": 4, - "target_present": false, - "target_version": 1 - }, - "redaction-salt": { - "outcome": "copied", - "source_deleted": true, - "source_present": true, - "source_version": 1, - "target_present": false, - "target_version": 1 - } - }, - "exit_code": 0, - "mode": "move", - "outcome": "moved", - "schema": "platform.fluid-telegram-custody-move.v1", - "source": "platform/workloads/infotech/fluid-telegram/hall-of-helix", - "target": "platform/workloads/coulomb/fluid-telegram/hall-of-helix" - }, - "custody_move_dry_run": { - "credential_values_emitted": false, - "entries": { - "bot-token": { - "source_present": true, - "source_version": 1, - "target_present": false - }, - "operator-app": { - "source_present": true, - "source_version": 1, - "target_present": false - }, - "operator-session": { - "source_present": true, - "source_version": 4, - "target_present": false - }, - "redaction-salt": { - "source_present": true, - "source_version": 1, - "target_present": false - } - }, - "exit_code": 0, - "mode": "dry-run", - "outcome": "dry_run_would_move", - "schema": "platform.fluid-telegram-custody-move.v1", - "source": "platform/workloads/infotech/fluid-telegram/hall-of-helix", - "target": "platform/workloads/coulomb/fluid-telegram/hall-of-helix", - "value_reads": 0 - }, - "duplicate_retire": { - "canonical": "platform/workloads/activity-core/llm-connect/llm-connect-provider-secrets", - "canonical_live": true, - "duplicate": "platform/workloads/activity-core/default/llm-connect-provider-secrets", - "duplicate_deleted": true, - "duplicate_present_before": true, - "duplicate_versions": 1, - "exit_code": 0, - "granting_policies": {}, - "metadata_only_policies": { - "ops-mason-survey": [ - "platform/metadata/*" - ], - "platform-readonly": [ - "platform/metadata/*" - ] - }, - "mode": "retire", - "outcome": "retired", - "schema": "platform.kv-duplicate-retire.v1", - "value_reads": 0 - }, - "duplicate_retire_dry_run": { - "canonical": "platform/workloads/activity-core/llm-connect/llm-connect-provider-secrets", - "canonical_live": true, - "duplicate": "platform/workloads/activity-core/default/llm-connect-provider-secrets", - "duplicate_present_before": true, - "duplicate_versions": 1, - "exit_code": 0, - "granting_policies": {}, - "metadata_only_policies": { - "ops-mason-survey": [ - "platform/metadata/*" - ], - "platform-readonly": [ - "platform/metadata/*" - ] - }, - "mode": "dry-run", - "outcome": "dry_run_would_retire", - "schema": "platform.kv-duplicate-retire.v1", - "value_reads": 0 - }, - "duplicate_retire_dry_run_refused": { - "canonical": "platform/workloads/activity-core/llm-connect/llm-connect-provider-secrets", - "canonical_live": true, - "duplicate": "platform/workloads/activity-core/default/llm-connect-provider-secrets", - "duplicate_present_before": true, - "duplicate_versions": 1, - "exit_code": 3, - "granting_policies": { - "ops-mason-survey": [ - "platform/metadata/*" - ], - "platform-readonly": [ - "platform/metadata/*" - ] - }, - "mode": "dry-run", - "outcome": "live_policy_grants_duplicate", - "schema": "platform.kv-duplicate-retire.v1", - "value_reads": 0 - } - }, - "schema": "platform.evidence.rpf-wp-0035-t03-founder-session.v1" -} diff --git a/docs/evidence/2026-10-10-keycape-client-secret-generate-probe.json b/docs/evidence/2026-10-10-keycape-client-secret-generate-probe.json deleted file mode 100644 index 0c4bfa0..0000000 --- a/docs/evidence/2026-10-10-keycape-client-secret-generate-probe.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "actor": "founder", - "ccrs": [ - "CCR-2026-0040", - "CCR-2026-0043" - ], - "date": "2026-10-10", - "note": "Receipts copied verbatim (no values). First attempt failed at grant token creation (limited-use caller token) and wrote nothing; fixed in 6f77484, both CCRs re-applied. Caller token after fix: policies [credential-broker-keycape-client-secret-generate-issuer], num_uses 0. Probe entry metadata-deleted afterwards by platform-admin.", - "receipts": { - "dry_run": { - "case_ref": "probe-secret-generate-20261010@sha256:9cb9b2da16f4a8e5a6dfccad23d1849110485d452e098ff8cbd7a4066447e956", - "case_verified": true, - "client_id": "probe-secret-generate-20261010", - "credential_values_emitted": false, - "exit_code": 0, - "grant": "keycape/client-secret-generate", - "grant_token_revoked": true, - "mode": "dry-run", - "outcome": "dry_run_would_generate", - "path": "platform/workloads/keycape/probe-secret-generate-20261010", - "schema": "platform.keycape-client-secret-generate.v1", - "value_reads": 0 - }, - "first_attempt_1": { - "case_ref": "probe-secret-generate-20261010@sha256:9cb9b2da16f4a8e5a6dfccad23d1849110485d452e098ff8cbd7a4066447e956", - "case_verified": true, - "client_id": "probe-secret-generate-20261010", - "credential_values_emitted": false, - "exit_code": 4, - "grant": "keycape/client-secret-generate", - "mode": "generate", - "outcome": "grant_token_create_failed", - "path": "platform/workloads/keycape/probe-secret-generate-20261010", - "schema": "platform.keycape-client-secret-generate.v1", - "value_reads": 0 - }, - "first_attempt_2": { - "case_ref": "probe-secret-generate-20261010@sha256:9cb9b2da16f4a8e5a6dfccad23d1849110485d452e098ff8cbd7a4066447e956", - "case_verified": true, - "client_id": "probe-secret-generate-20261010", - "credential_values_emitted": false, - "exit_code": 4, - "grant": "keycape/client-secret-generate", - "mode": "generate", - "outcome": "grant_token_create_failed", - "path": "platform/workloads/keycape/probe-secret-generate-20261010", - "schema": "platform.keycape-client-secret-generate.v1", - "value_reads": 0 - }, - "first_attempt_dry_run": { - "case_ref": "probe-secret-generate-20261010@sha256:9cb9b2da16f4a8e5a6dfccad23d1849110485d452e098ff8cbd7a4066447e956", - "case_verified": true, - "client_id": "probe-secret-generate-20261010", - "credential_values_emitted": false, - "exit_code": 4, - "grant": "keycape/client-secret-generate", - "mode": "dry-run", - "outcome": "grant_token_create_failed", - "path": "platform/workloads/keycape/probe-secret-generate-20261010", - "schema": "platform.keycape-client-secret-generate.v1", - "value_reads": 0 - }, - "generate": { - "case_ref": "probe-secret-generate-20261010@sha256:9cb9b2da16f4a8e5a6dfccad23d1849110485d452e098ff8cbd7a4066447e956", - "case_verified": true, - "client_id": "probe-secret-generate-20261010", - "credential_values_emitted": false, - "exit_code": 0, - "grant": "keycape/client-secret-generate", - "grant_token_revoked": true, - "kv_version": 1, - "mode": "generate", - "outcome": "generated", - "path": "platform/workloads/keycape/probe-secret-generate-20261010", - "schema": "platform.keycape-client-secret-generate.v1", - "value_reads": 0 - }, - "second_generate_refused": { - "case_ref": "probe-secret-generate-20261010@sha256:9cb9b2da16f4a8e5a6dfccad23d1849110485d452e098ff8cbd7a4066447e956", - "case_verified": true, - "client_id": "probe-secret-generate-20261010", - "credential_values_emitted": false, - "exit_code": 3, - "grant": "keycape/client-secret-generate", - "grant_token_revoked": true, - "mode": "generate", - "outcome": "already_present", - "path": "platform/workloads/keycape/probe-secret-generate-20261010", - "schema": "platform.keycape-client-secret-generate.v1", - "value_reads": 0 - } - }, - "schema": "platform.evidence.rpf-wp-0054-t05-probe.v1" -} diff --git a/docs/evidence/2026-10-10-operators-nextcloud-retire.json b/docs/evidence/2026-10-10-operators-nextcloud-retire.json deleted file mode 100644 index 818ab3a..0000000 --- a/docs/evidence/2026-10-10-operators-nextcloud-retire.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "actor": "founder", - "date": "2026-10-10", - "decision": "founder 2026-10-10: operators/nextcloud is a deprecated credential", - "note": "Receipts copied verbatim (no values). A repeated dry-run with an existing receipt path was refused before any action.", - "receipts": { - "dry_run": { - "entry": "operators/nextcloud", - "entry_before": { - "current_version": 2, - "versions": 2 - }, - "exit_code": 0, - "mode": "dry-run", - "outcome": "dry_run_would_retire", - "policy_deleted": true, - "schema": "platform.operators-entry-retire.v1", - "sibling": "operators/nextcloud/backup", - "sibling_before": { - "current_version": 2, - "versions": 2 - }, - "token_revoked": true, - "value_reads": 0 - }, - "retire": { - "entry": "operators/nextcloud", - "entry_before": { - "current_version": 2, - "versions": 2 - }, - "entry_deleted": true, - "exit_code": 0, - "mode": "retire", - "outcome": "retired", - "policy_deleted": true, - "schema": "platform.operators-entry-retire.v1", - "sibling": "operators/nextcloud/backup", - "sibling_after": { - "current_version": 2, - "versions": 2 - }, - "sibling_before": { - "current_version": 2, - "versions": 2 - }, - "token_revoked": true, - "value_reads": 0 - } - }, - "schema": "platform.evidence.operators-nextcloud-retire.v1" -} diff --git a/docs/evidence/2026-10-11-activity-promotion-readiness/README.md b/docs/evidence/2026-10-11-activity-promotion-readiness/README.md deleted file mode 100644 index 7baede8..0000000 --- a/docs/evidence/2026-10-11-activity-promotion-readiness/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# Activity-core bounded promotion readiness - -```sh -python3 scripts/activity_promotion_readiness.py --live --observation observation.json --receipt readiness.json -python3 -m unittest discover -s tests -p 'test_activity_promotion_readiness.py' -``` - -The current result is waiting for ACTIVITY-WP-0041-T03 authenticated owner -acceptance. Live scoped adoption, elapsed 24h and Deployment readiness pass; -no automation/identity/principal has been activated. A pair of snapshots or an -old successful-sync timestamp cannot prove the owner's gap-sensitive observer. -Raw subprocess output/errors and credential/Secret objects are never emitted. -The initial strict future-timestamp refusal is retained. The revised collector -records both the remote timestamp and the local observer's receipt time, avoiding -unjustified clock tolerance or backfilled health. - -For later activation, the operator supplies an independent trusted owner public -key/principal and signed native acceptance. The checker never loads a public key -from the submitted receipt, creates an identity, grants authority or changes a -child/root spec. Exact fields and source-derived proof requirements are recorded -in `docs/activity-core-release-admission.md`. Only `status: ready` exits zero; -waiting/unverified cannot activate anything. Generated fixture signatures in the -nine tests are not native acceptance evidence. The receipt and coordination case -accompany executable source changes, not an evidence-only checkpoint. - -Abort owner: railiance-platform. Rollback of preparation: revert this gate source -change. Later bounded dispatcher activation requires its declared owner rollback -(disable admission and recover the prior pinned source through the reviewed -broker); no native activation or credential scope expansion follows from this -preparation. Existing Application remains manually reconciled, prune disabled. diff --git a/docs/evidence/2026-10-11-activity-promotion-readiness/coordination-case.json b/docs/evidence/2026-10-11-activity-promotion-readiness/coordination-case.json deleted file mode 100644 index 3ea727b..0000000 --- a/docs/evidence/2026-10-11-activity-promotion-readiness/coordination-case.json +++ /dev/null @@ -1,31 +0,0 @@ -{ - "schema_version": "cross-owner-wait-receipt/v0.1", - "scenario_type": "cross_owner_wait", - "case_id": "RPF-WP-0048-T02--ACTIVITY-WP-0041-T03", - "state": "waiting", - "reason": "authenticated-owner-activation-acceptance-missing", - "beneficiary_workplan_id": "RPF-WP-0048", - "beneficiary_task_id": "RPF-WP-0048-T02", - "beneficiary_repo": "railiance-platform", - "obligor_workplan_id": "ACTIVITY-WP-0041", - "obligor_task_id": "ACTIVITY-WP-0041-T03", - "obligor_repo": "activity-core", - "owner_observation_refs": [ - "activity-core/docs/release-broker.md", - "activity-core/docs/gitops-release.md", - "activity-core/docs/platform-recovery-scheduling-plan.md" - ], - "needs_human": false, - "automatic_wakeup": false, - "foreign_task_changed": false, - "checkpoint": { - "summary": "Live scoped adoption eligible; authenticated bounded-release owner proof missing. Deterministic fail-closed readiness gate implemented.", - "files_changed": [ - "scripts/activity_promotion_readiness.py", - "tests/test_activity_promotion_readiness.py", - "workplans/RPF-WP-0048-activity-core-gitops-adoption.md" - ], - "next_action": "ACTIVITY-WP-0041-T03 owner supplies native identity/custody/issuers/continuous observation/rollback acceptance, then platform reruns gate before activation.", - "blocked_reason": "ACTIVITY-WP-0041-T03 authenticated owner activation acceptance absent." - } -} diff --git a/docs/evidence/2026-10-11-activity-promotion-readiness/observation-before-local-measurement.json b/docs/evidence/2026-10-11-activity-promotion-readiness/observation-before-local-measurement.json deleted file mode 100644 index 862bdd9..0000000 --- a/docs/evidence/2026-10-11-activity-promotion-readiness/observation-before-local-measurement.json +++ /dev/null @@ -1,124 +0,0 @@ -{ - "application_uid": "cd179999-852d-4d70-9017-29e867475bff", - "automated_present": false, - "captured_at": "2026-10-11T19:44:18.628842+00:00", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "condition_types": [], - "deployments": [ - { - "available": 1, - "generation_observed": true, - "name": "actcore-api", - "ready": 1, - "replicas": 1, - "updated": 1 - }, - { - "available": 1, - "generation_observed": true, - "name": "actcore-worker", - "ready": 1, - "replicas": 1, - "updated": 1 - }, - { - "available": 1, - "generation_observed": true, - "name": "actcore-event-router", - "ready": 1, - "replicas": 1, - "updated": 1 - } - ], - "destination": { - "namespace": "activity-core", - "server": "https://kubernetes.default.svc" - }, - "estate_ip": "92.205.62.239", - "health": "Healthy", - "operation": { - "finishedAt": "2026-10-04T14:13:25Z", - "phase": "Succeeded" - }, - "pending_operation": false, - "project": "activity-core", - "project_scope_matches": true, - "resources": [ - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-external-activity-definitions", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-ops-service-inventory", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-report-schemas", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-runtime-config", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "Service", - "name": "actcore-api", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "Service", - "name": "actcore-worker-metrics", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": "apps", - "kind": "Deployment", - "name": "actcore-api", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": "apps", - "kind": "Deployment", - "name": "actcore-event-router", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": "apps", - "kind": "Deployment", - "name": "actcore-worker", - "namespace": "activity-core", - "status": "Synced" - } - ], - "revision": "a86a14332d76d92a7ed176ea34dca4429902c396", - "source": { - "path": "k8s/gitops", - "repoURL": "https://forgejo.coulomb.social/coulomb/activity-core.git", - "targetRevision": "a86a14332d76d92a7ed176ea34dca4429902c396" - }, - "sync": "Synced", - "sync_options": [ - "CreateNamespace=false", - "ApplyOutOfSyncOnly=true", - "PruneLast=true", - "FailOnSharedResource=true" - ] -} diff --git a/docs/evidence/2026-10-11-activity-promotion-readiness/observation.json b/docs/evidence/2026-10-11-activity-promotion-readiness/observation.json deleted file mode 100644 index 07fb2dc..0000000 --- a/docs/evidence/2026-10-11-activity-promotion-readiness/observation.json +++ /dev/null @@ -1,125 +0,0 @@ -{ - "application_uid": "cd179999-852d-4d70-9017-29e867475bff", - "automated_present": false, - "captured_at": "2026-10-11T19:44:50.828034+00:00", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "condition_types": [], - "deployments": [ - { - "available": 1, - "generation_observed": true, - "name": "actcore-api", - "ready": 1, - "replicas": 1, - "updated": 1 - }, - { - "available": 1, - "generation_observed": true, - "name": "actcore-worker", - "ready": 1, - "replicas": 1, - "updated": 1 - }, - { - "available": 1, - "generation_observed": true, - "name": "actcore-event-router", - "ready": 1, - "replicas": 1, - "updated": 1 - } - ], - "destination": { - "namespace": "activity-core", - "server": "https://kubernetes.default.svc" - }, - "estate_ip": "92.205.62.239", - "health": "Healthy", - "operation": { - "finishedAt": "2026-10-04T14:13:25Z", - "phase": "Succeeded" - }, - "pending_operation": false, - "project": "activity-core", - "project_scope_matches": true, - "resources": [ - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-external-activity-definitions", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-ops-service-inventory", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-report-schemas", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "ConfigMap", - "name": "actcore-runtime-config", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "Service", - "name": "actcore-api", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": null, - "kind": "Service", - "name": "actcore-worker-metrics", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": "apps", - "kind": "Deployment", - "name": "actcore-api", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": "apps", - "kind": "Deployment", - "name": "actcore-event-router", - "namespace": "activity-core", - "status": "Synced" - }, - { - "group": "apps", - "kind": "Deployment", - "name": "actcore-worker", - "namespace": "activity-core", - "status": "Synced" - } - ], - "revision": "a86a14332d76d92a7ed176ea34dca4429902c396", - "source": { - "path": "k8s/gitops", - "repoURL": "https://forgejo.coulomb.social/coulomb/activity-core.git", - "targetRevision": "a86a14332d76d92a7ed176ea34dca4429902c396" - }, - "sync": "Synced", - "sync_options": [ - "CreateNamespace=false", - "ApplyOutOfSyncOnly=true", - "PruneLast=true", - "FailOnSharedResource=true" - ], - "remote_captured_at": "2026-10-11T19:44:52.074977+00:00" -} diff --git a/docs/evidence/2026-10-11-activity-promotion-readiness/readiness-clock-refusal.json b/docs/evidence/2026-10-11-activity-promotion-readiness/readiness-clock-refusal.json deleted file mode 100644 index 5644229..0000000 --- a/docs/evidence/2026-10-11-activity-promotion-readiness/readiness-clock-refusal.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "schema_version": "activity-promotion-readiness/v1", - "task": "RPF-WP-0048-T02", - "owner_dependency": "ACTIVITY-WP-0041-T03", - "native_mutation": false, - "automatic_sync_candidate": false, - "observed_at": "2026-10-11T19:44:17.229169+00:00", - "checker_sha256": "ed159e8128c0fbb2741fcff17e5b2eebcee279057da3c1c6bce285e48b233882", - "status": "unverified", - "reason": "adoption_metadata_unverified" -} diff --git a/docs/evidence/2026-10-11-activity-promotion-readiness/readiness.json b/docs/evidence/2026-10-11-activity-promotion-readiness/readiness.json deleted file mode 100644 index 0b26f11..0000000 --- a/docs/evidence/2026-10-11-activity-promotion-readiness/readiness.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "schema_version": "activity-promotion-readiness/v1", - "task": "RPF-WP-0048-T02", - "owner_dependency": "ACTIVITY-WP-0041-T03", - "native_mutation": false, - "automatic_sync_candidate": false, - "observed_at": "2026-10-11T19:44:50.828539+00:00", - "checker_sha256": "93986567cc6ea01b646cebd2e47eb4a5f19ad43b54ecfe420079a45b60ce30f3", - "adoption_24h_eligible": true, - "adoption_snapshot_is_not_continuous_observation": true, - "observation_sha256": "a66b0d128292e24f7964ca90602166ff8b0d88799f31f605dd9fdf88f0b53c25", - "status": "waiting", - "reason": "authenticated_owner_activation_acceptance_missing", - "depends_on": [ - "ACTIVITY-WP-0041-T03" - ], - "needs_human": false -} diff --git a/docs/evidence/2026-10-11-activity-promotion-readiness/validation.json b/docs/evidence/2026-10-11-activity-promotion-readiness/validation.json deleted file mode 100644 index ab35f75..0000000 --- a/docs/evidence/2026-10-11-activity-promotion-readiness/validation.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "schema_version": "activity-promotion-validation/v1", - "task": "RPF-WP-0048-T02", - "task_closed": false, - "task_status": "wait", - "depends_on": [ - "ACTIVITY-WP-0041-T03" - ], - "needs_human": false, - "native_mutation": false, - "child_source_changed": false, - "tests": { - "passed": 9, - "failed": 0 - }, - "compilation": "passed", - "live_checks": "adoption_24h_eligible", - "activation": "waiting-for-authenticated-owner-proof", - "readiness_ref": "readiness.json", - "coordination_case_ref": "coordination-case.json", - "checker_sha256": "93986567cc6ea01b646cebd2e47eb4a5f19ad43b54ecfe420079a45b60ce30f3", - "source_contracts": { - "activity-core/docs/release-broker.md": "bbc917f9b2a533b65d41deb79324b6bd43f8ad14e83587387aa9078fff238fb1", - "activity-core/docs/gitops-release.md": "b1ce725b881a4d506e1129ad330451b75d65d81a3db2a63351867710bbd89da7", - "activity-core/docs/platform-recovery-scheduling-plan.md": "3b86a1bddae65abd56697360303ae9f56a969c98ae98d757f1ed689aaa241698" - }, - "new_attended_items": [], - "new_red_batch_items": [], - "failed_clock_observation_retained": "readiness-clock-refusal.json", - "timestamp_method": "local observer measured receipt after SSH return; remote timestamp retained separately, never used to backfill continuous health", - "validated_at": "2026-10-11T19:47:19.802659+00:00" -} diff --git a/docs/evidence/2026-10-11-coulombcore-argocd-frozen.md b/docs/evidence/2026-10-11-coulombcore-argocd-frozen.md deleted file mode 100644 index 4fd4fdd..0000000 --- a/docs/evidence/2026-10-11-coulombcore-argocd-frozen.md +++ /dev/null @@ -1,20 +0,0 @@ -# CoulombCore ArgoCD: frozen by expired k3s certificates — 2026-10-11 - -Task: RPF-WP-0044-T08. Collected read-only from the host by claude-code (ssh `coulombcore`, -`sudo -n`), without the Kubernetes API. - -- `/var/lib/rancher/k3s/server/tls/client-admin.crt` and `serving-kube-apiserver.crt`: - `notAfter=Aug 31 16:50:46 2026 GMT`. **Expired.** k3s has been active since 2026-05-24 15:24 UTC with - no restart, so no automatic rotation happened. This explains the "Unauthorized" seen on 2026-09-27 - and 2026-10-11 for every client, root's own kubeconfig included. -- Old ArgoCD (phase A install, undeclared) is still running as containers: application-controller, - applicationset-controller, repo-server, server, dex, redis. notifications-controller is on attempt 53. -- With an expired API server certificate, the old controller cannot reconcile or prune. The legacy - `argocd/applications/` tree is therefore not at risk from it while the certificates stay expired. - -## Disposition (founder, 2026-10-11) - -CoulombCore is not needed for anything. It will be retired and the machine reused. **Do not rotate -certificates or restart k3s on coulombcore.** That would revive the old controller with working -credentials. Retirement proceeds under NK-WP-0022-T08: inventory from the host, then stop and wipe. -RPF-WP-0044 treats the old controller as frozen and no longer waits on this check. diff --git a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/conditional-review.json b/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/conditional-review.json deleted file mode 100644 index def01ea..0000000 --- a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/conditional-review.json +++ /dev/null @@ -1,47 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-FORGEJO-ESO-R1", - "work_record": "RPF-IN-0006", - "reviewer_role": "railiance-platform technical owner only", - "status": "changes-required", - "exact_manifest_sha256": "271afd04512b3dac642b3385013cac03d28afde22ff97bcca8d0e262ab3d9279", - "reviewed_paths": 17, - "all_source_hashes_match": true, - "primary_eso_source_sha256": "e53f43c0748e43f408b5a7c5323c12c0fb5fb4299a33718181f6d70945112071", - "primary_eso_source_url": "https://raw.githubusercontent.com/external-secrets/external-secrets/v0.16.1/pkg/provider/vault/auth_kubernetes.go", - "platform_ccr_validation": "passes with expected unconfirmed-bound-claim apply-block warning", - "sound_preparation": [ - "Dedicated forgejo namespace reader SA and audience openbao, exact role and one data path plus existing token self lifecycle; no default policy or parent/sibling/list permission.", - "Installed CRD supports referenced SA/audiences; retained native RBAC confirms common controller TokenRequest/Secret authority. Independently fetched installed-version provider source confirms600second TokenRequest, namespace-local SA and legacy-token fallback.", - "Proposal explicitly discloses common-controller trust, HTTP custody and no cryptographic isolation; new SA legacy token refs must be absent, with no static fallback or manual secret seeding.", - "Original acknowledged1.1/acceptedcase/history unchanged; proposed1.2 adds sixth object and requires new exact acknowledgment and native provenance before structural/materialized claims.", - "Manual proposed CCR42 and owner design correctly require created-only rollback, consumer reference reversal, issued-reader-token revocation or independently verified expiry before declaring deactivation." - ], - "required_changes": [ - { - "id": "RPF-FORGEJO-ESO-01", - "source": "ops-mason/src/ops_mason/case_build.py::_ccr", - "finding": "Generated consumer CCR target.workload is keycape, while dedicated consumer role is Forgejo and proposed CCR42 target.workload is forgejo.", - "required": "Derive consumer workload from consuming_service for consumer-reader-policy, preserving KeyCape ESO reader target; regression must distinguish both generated CCR targets." - }, - { - "id": "RPF-FORGEJO-ESO-02", - "source": "ops-mason/src/ops_mason/case_build.py::_ccr", - "finding": "Generated lifecycle.deactivate only says delete role then policy; deletion does not revoke issued OpenBao tokens and omits created/preexisting distinction.", - "required": "Generated successor consumer CCR must retain owner-native created-only/custody rollback and revoke issued reader tokens or verify expiry before deactivation; no new login or remint allowed during recovery. Preserve existing historical source and add meaningful output regression." - } - ], - "native_preconditions": [ - "Fresh exact ESO runtime identity/CRD/RBAC and dedicated SA absence of legacy token refs. Tag observation is compatibility preparation, not cryptographic image identity proof.", - "Exact founder/type/case1.2 acknowledgment plus platform CCR approval/bound claims confirmed and authenticated per-owner structural/custody receipts before native apply or credential mint.", - "Future consumer Helm secret consumption/group enforcement/break-glass/browser/MFA handover remains separately incomplete.", - "Ready status/metadata never substitute for native authorization, issued-token revocation, key rotation or cross-owner completion." - ], - "limits": { - "native_changes": false, - "credential_mint": false, - "secret_read": false, - "founder_acknowledgment": false, - "other_owner_role_review": false, - "first_case_completion": false - } -} diff --git a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/final-review.json b/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/final-review.json deleted file mode 100644 index b82fb37..0000000 --- a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/final-review.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-FORGEJO-ESO-R1-FINAL", - "work_record": "RPF-IN-0006", - "reviewer_role": "railiance-platform technical owner only", - "status": "technical-agreement", - "exact_manifest_sha256": "5dbd04cf96d73a2604cbf113073d618c03434fc87508ba614cbe3820a1a0e431", - "reviewed_paths": 19, - "candidate_builder_sha256": "c2b140f380407779ce5478ca57c97eb0e6375d6a9cad4591b0b29ddd177db686", - "initial_conditional_manifest_sha256": "9e4967bfff57694ceb12883f522edc405ef3fa316e46b88c9bdad47205a180b4", - "initial_proposal_manifest_sha256": "271afd04512b3dac642b3385013cac03d28afde22ff97bcca8d0e262ab3d9279", - "all_manifest_hashes_match": true, - "independently_rerun_focused_tests": 44, - "resolved": [ - "Generated consumer-reader target.workload derives consuming_service=forgejo, KeyCape ESO reader remains keycape, generated request identities remain distinct.", - "Generated role-backed consumer deactivation includes authenticated created-only custody, preserving shared/preexisting resources, issued-token revocation or independently verified expiry, no-new-login/no-remint recovery, and refusal on changed references/UID/ownership." - ], - "independent_generated_source_probe": "refined-generated-ccr-probe.json", - "retained_preconditions": [ - "Fresh owner/founder exact type/case1.2 acknowledgment required for sixth structural role/runtime delivery; accepted1.1 does not transfer.", - "CCR42 reconciliation retains prior body/history; actual bound claims unconfirmed, source-only role/policy/delivery objects not applied.", - "Future exact ESO runtime/CRD/RBAC and no-legacy-token SA admission, authenticated created/preexisting ownership/capability receipts and approved contained owner changes remain required; common controller trust and existing HTTP custody are disclosed.", - "No deletion-alone revocation claim; revoke issued OpenBao tokens or independently verify expiry before deactivation, with old consumer references reverted and unknown outcomes retained.", - "Consumer Helm secret consumption/group enforcement, break-glass/MFA/browser checks and Warden descriptor/native per-owner session proof remain distinct unfinished gates." - ], - "scope": "Technical agreement on source proposal and platform CCR semantics only. No other owner role acceptance or native activation.", - "limits": { - "founder_acknowledgment": false, - "native_apply": false, - "credential_mint": false, - "secret_read": false, - "structural_materialization": false, - "first_case_complete": false - } -} diff --git a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/generated-ccr-probe.json b/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/generated-ccr-probe.json deleted file mode 100644 index bd6a447..0000000 --- a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/generated-ccr-probe.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "synthetic_source_only": true, - "native_action": false, - "generated_target_workload": "keycape", - "manual_ccr_target_workload": "forgejo", - "generated_deactivate": "delete the role, then policy workload-kv-read-keycape-forgejo-forgejo (case rollback)", - "generated_bound_claims_confirmed": false -} diff --git a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/refined-generated-ccr-probe.json b/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/refined-generated-ccr-probe.json deleted file mode 100644 index 4767d07..0000000 --- a/docs/evidence/2026-10-11-forgejo-consumer-eso-owner-review/refined-generated-ccr-probe.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "synthetic_source_only": true, - "native_action": false, - "consumer_target": "forgejo", - "keycape_reader_target": "keycape", - "generated_ids_distinct": true, - "bound_claims_remain_false": true, - "lifecycle_terms": { - "case-created": true, - "preexisting-matched": true, - "shared": true, - "revoke issued reader tokens": true, - "independently verified expiry": true, - "deletion alone does not revoke": true, - "no new login": true, - "no remint": true, - "stop on changed": true - } -} diff --git a/docs/evidence/2026-10-11-forgejo-first-case/coordination-receipt.json b/docs/evidence/2026-10-11-forgejo-first-case/coordination-receipt.json deleted file mode 100644 index 3dd0596..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/coordination-receipt.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "scenario_type": "project_driver", - "case_id": "forgejo-netkingdom-oidc-owner-follow-through", - "driver_workplan_id": "ORGREF-WP-0002", - "driver_repo": "prj-forgejo-org-refactor", - "beneficiary_workplan_id": "NK-WP-0048", - "obligor_workplan_id": "MASON-WP-0010", - "flavor": "implementation", - "state": "waiting", - "reason": "Mason structural receipts and CCR42 consumer delivery binding/application absent; Warden owner-native machine/session integration not installed.", - "foreign_commitment_refs": [ - "MASON-WP-0010-T03", - "WARDEN-WP-0044-T03", - "RAPPS-WP-0016-T02", - "CCR-2026-0042" - ], - "human_gate_refs": [ - "RAPPS-WP-0016-T01 real break-glass login", - "exact metadata apply grant if owner-approved binding changes native auth", - "contained first native case session/client restart and verification" - ], - "leased_workers": false, - "native_service_wakeup_enabled": false, - "central_projection_posted": false, - "checkpoint": { - "summary": "Existing Forgejo project driver selected; source structural request validated and fixed native workload metadata observed. No copied task list or new workplan.", - "files_changed": [ - "CCR42 blocking-reason clarification and evidence proposal only" - ], - "next_action": "Consumer owner chooses a supported dedicated identity/delivery path; platform reviews/reconciles CCR42 and supplies exact independent structural observations. Native adapters/type descriptors and contained execution follow only after owner acceptance.", - "blocked_reason": "owner binding and authenticated native receipts; independent human sessions are separate stops" - } -} diff --git a/docs/evidence/2026-10-11-forgejo-first-case/hub-observation.json b/docs/evidence/2026-10-11-forgejo-first-case/hub-observation.json deleted file mode 100644 index 5f15a5f..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/hub-observation.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "central_observed": true, - "records": [ - { - "id": "e31f3f9d-0748-5137-b0a3-7b3ca46a66eb", - "backing_filename": "NK-WP-0048-security-case-types.md", - "backing_relative_path": "workplans/NK-WP-0048-security-case-types.md", - "status": "active", - "flavor": null, - "repo_id": "4589488d-625b-4ffe-b8f0-48744d7b1498" - }, - { - "id": "f7e81013-6868-5d99-8528-ba75a442eca9", - "backing_filename": "WARDEN-WP-0044-security-case-follow-through.md", - "backing_relative_path": "workplans/WARDEN-WP-0044-security-case-follow-through.md", - "status": "active", - "flavor": null, - "repo_id": "74df727e-a756-42de-b776-11928b03bd1f" - }, - { - "id": "eabf2c39-94bd-5a11-8f2e-76d7a95d1583", - "backing_filename": "MASON-WP-0010-security-case-materialization.md", - "backing_relative_path": "workplans/MASON-WP-0010-security-case-materialization.md", - "status": "blocked", - "flavor": "implementation", - "repo_id": "b9066ab1-c6fe-409b-9145-64ba374654db" - }, - { - "id": "f873c10f-1408-5bf2-a834-fed88387804b", - "backing_filename": "ORGREF-WP-0002-forge-closure-netkingdom-access.md", - "backing_relative_path": "workplans/ORGREF-WP-0002-forge-closure-netkingdom-access.md", - "status": "blocked", - "flavor": "implementation", - "repo_id": "01631c7a-f612-4799-892e-a96970d06063" - } - ] -} diff --git a/docs/evidence/2026-10-11-forgejo-first-case/native-forgejo-metadata.json b/docs/evidence/2026-10-11-forgejo-first-case/native-forgejo-metadata.json deleted file mode 100644 index 5555675..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/native-forgejo-metadata.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "observed_at": "2026-10-11T00:50:33.119567+00:00", - "secret_objects_read": false, - "raw_output_suppressed": true, - "workloads": [ - { - "kind": "Deployment", - "name": "forgejo-gitea", - "uid": "e8eff0aa-64bb-4b44-9a80-a66612986355", - "resourceVersion": "67848734", - "generation": 11, - "readyReplicas": 1, - "serviceAccountName": "default", - "automountServiceAccountToken": null, - "containers": [ - { - "name": "gitea", - "image": "code.forgejo.org/forgejo/forgejo:11.0.3-rootless", - "env_names": [ - "SSH_LISTEN_PORT", - "SSH_PORT", - "GITEA_APP_INI", - "GITEA_CUSTOM", - "GITEA_WORK_DIR", - "GITEA_TEMP", - "TMPDIR", - "HOME" - ], - "env_secret_refs": [] - } - ] - }, - { - "kind": "Deployment", - "name": "forgejo-runner", - "uid": "89c8a056-bcae-4940-acdd-2eb2768e108a", - "resourceVersion": "68161637", - "generation": 3, - "readyReplicas": 1, - "serviceAccountName": "default", - "automountServiceAccountToken": null, - "containers": [ - { - "name": "dind", - "image": "docker:27-dind", - "env_names": [ - "DOCKER_TLS_CERTDIR" - ], - "env_secret_refs": [] - }, - { - "name": "runner", - "image": "code.forgejo.org/forgejo/runner:6.3.1", - "env_names": [ - "DOCKER_HOST" - ], - "env_secret_refs": [] - } - ] - } - ], - "delivery": [ - { - "name": "forgejo-mailer", - "uid": "cbbea71e-70a8-4010-aca9-9136920f2f58", - "store": { - "kind": "ClusterSecretStore", - "name": "openbao-forgejo" - }, - "target_name": "forgejo-mailer", - "ready": true, - "remote_paths": [ - "workloads/forgejo/forgejo-mailer" - ] - } - ], - "deployments_read": true, - "statefulsets_read": true, - "externalsecrets_read": true -} diff --git a/docs/evidence/2026-10-11-forgejo-first-case/observe-forgejo-metadata.py b/docs/evidence/2026-10-11-forgejo-first-case/observe-forgejo-metadata.py deleted file mode 100644 index d1521d3..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/observe-forgejo-metadata.py +++ /dev/null @@ -1,21 +0,0 @@ -import subprocess,json,datetime -remote=r''' -import json,subprocess,datetime -out={'observed_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'secret_objects_read':False,'raw_output_suppressed':True,'workloads':[],'delivery':[]} -for resource in ['deployments','statefulsets']: - r=subprocess.run(['kubectl','get',resource,'-n','forgejo','-o','json'],capture_output=True,text=True) - if r.returncode:out[resource+'_read']=False;continue - out[resource+'_read']=True - for d in json.loads(r.stdout)['items']: - s=d['spec']['template']['spec'];out['workloads'].append({'kind':d['kind'],'name':d['metadata']['name'],'uid':d['metadata']['uid'],'resourceVersion':d['metadata']['resourceVersion'],'generation':d['metadata'].get('generation'),'readyReplicas':d.get('status',{}).get('readyReplicas',0),'serviceAccountName':s.get('serviceAccountName','default'),'automountServiceAccountToken':s.get('automountServiceAccountToken'),'containers':[{'name':c['name'],'image':c['image'],'env_names':[e['name'] for e in c.get('env',[])],'env_secret_refs':[{'env':e['name'],'secret_name':e['valueFrom']['secretKeyRef']['name'],'key':e['valueFrom']['secretKeyRef']['key']} for e in c.get('env',[]) if 'secretKeyRef' in e.get('valueFrom',{})]} for c in s['containers']]}) -r=subprocess.run(['kubectl','get','externalsecrets','-n','forgejo','-o','json'],capture_output=True,text=True) -out['externalsecrets_read']=r.returncode==0 -if not r.returncode: - for d in json.loads(r.stdout)['items']: - out['delivery'].append({'name':d['metadata']['name'],'uid':d['metadata']['uid'],'store':d['spec'].get('secretStoreRef'),'target_name':d['spec'].get('target',{}).get('name'),'ready':any(c['type']=='Ready' and c['status']=='True' for c in d.get('status',{}).get('conditions',[])),'remote_paths':[x.get('remoteRef',{}).get('key') for x in d['spec'].get('data',[])]}) -print(json.dumps(out)) -''' -r=subprocess.run(['ssh','railiance01','python3 -'],input=remote,capture_output=True,text=True) -try:out=json.loads(r.stdout) -except Exception:out={'native_metadata_read':False,'raw_output_suppressed':True} -open('/tmp/security-case-forgejo-native-metadata.json','w').write(json.dumps(out,indent=2)+'\n');print(json.dumps(out)) diff --git a/docs/evidence/2026-10-11-forgejo-first-case/owner-return.md b/docs/evidence/2026-10-11-forgejo-first-case/owner-return.md deleted file mode 100644 index d93bd13..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/owner-return.md +++ /dev/null @@ -1,27 +0,0 @@ -# First Forgejo SecurityCase owner return — source/native metadata review - -Disposition: source structural preparation is usable, native first-case completion is not ready. Existing driver ORGREF-WP-0002 already coordinates Forgejo closure/sign-in and names NK-WP0048-T06; attach this observed/waiting case there. No new plan or copied foreign task list is needed. Hub records were observed read-only; this receipt is not centrally projected or a worker wake/lease. - -## Exact source/type boundary - -The actual accepted case remains accepted, oidc-browser-relying-party@1.1, raw SHA256 bcc2c6d5bae0d28242b3cdb71e74d8ca297c2afe04766b55d53597f86893351d. Current exact type SHA256 f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c produces full render e64b56e4590b57b858303fcda1a474693e3dd821bc83df26646fe78fc345498a. Prior reviewed full render af1f4f81ef313f5e086e6e3de17197d9112dd40f00b4c20b52b5ca73c7a6d18a is retained, not inherited as current execution approval. Actual read-only Mason structure-request passes and pins structural projection523a9020822a406f4f4cfb36608abbddf01eecf126aae5f2d170100fc4fdd5c0, requestf2ffc19676b257d4ecbbb79b653da19cc097e3a8d5f7cdfe6fd7e69481b4b49f, five exact objects. Source changes are register-client --check-config/startup guard; structural footprint matches, full execution hash remains changed. Current source pins are attached. - -Mason f52c94c structural seam and Warden1b82619 durable kernel are implementation evidence. Human architecture approval e46b149b accepted the exact source design, not a new execution session or the local warden.case.execution.v1 wire schema. The acknowledged descriptive type1.1 has no execution_descriptors map. Machine descriptor/adapter revisions must get specific native-owner review and type acknowledgment/versioned contract before activation; do not label a different machine contract as already acknowledged1.1. A stricter --check-config guard can receive exact technical owner rereview without inventing a fresh founder ratification of unchanged doctrine, but its current source must be pinned in the actual run sheet. New grants, identity choices or broadened effects require their own disposition. - -## Actual CCR42 binding defect - -At00:50:33Z native Forgejo Deployment forgejo-gitea UIDe8eff0aa-64bb-4b44-9a80-a66612986355 is Ready1 and uses forgejo/default. Forgejo-runner also uses forgejo/default. Binding CCR42 to that observed namespace/SA would cover the runner too and is not a dedicated consumer identity. No Secret object/value was read. Existing Forgejo delivery shows only ready forgejo-mailer via ClusterSecretStore/openbao-forgejo; no OIDC ExternalSecret was observed. - -RAPPS0016 explicitly intends an ESO-delivered OIDC credential. CCR42 is still in_flight with no openbao.auth and historical1.0 render/provenance. Current type1.1 materialization includes a KeyCape ESO role but only a consumer policy, not a consumer role. Therefore filling an arbitrary role/SA and claiming the five-object structural footprint unchanged is incorrect. Existing policy family, exact KV read-only path and self-lifecycle clauses are reusable; historical CCR41 applied approval and CCR42 original request provenance must stay intact. - -Concrete owner proposal: railiance-apps must first select its consumer delivery/authentication implementation. If using its declared ESO path, propose a namespace-scoped SecretStore in forgejo and a dedicated reviewed consumer-reader role targeting the actual ESO controller identity, with the exact CCR42 policy only, audienceopenbao, 15m TTL, no default policy, target secret metadata/config references only, and no broad policy merge into the existing mailer store. That role is additional structural scope not currently templated and needs explicit type/Mason owner refinement; the actual trusted ESO identity and cross-namespace restrictions must be confirmed. If instead using a dedicated Forgejo ServiceAccount, propose that new SA and actual OpenBao-read integration plus projected audience/path; current Forgejo source has neither. Do not grant forgejo/default or reuse an unrelated runtime token to finish a checkbox. These are alternatives for owner review, not an approved auth binding or runnable native adapter. - -Before application: reconcile CCR42's historical1.0 evidence to exact reviewed/current1.1 structure while retaining originals, validate the complete request and policy/role bodies using platform's own validator, independently review identity isolation and scoped capabilities, prepare exact UID/resourceVersion/dry-run/diff/rollback and obtain the specific native grant. No such grant is present here. - -## Remaining authenticated receipts and real human stops - -Platform must return request-bound native ESO/consumer policy and role metadata plus fixed layout identity; Warden returns pointer-source observation. Classify created versus preexisting-matched from independent owner evidence, not planned rollback.yaml. Mint/delivery/login are follow-through, not prerequisites to mark structural presence. Only the verified structural binding permits an accepted→materialized status-only candidate; do not write that label before receipts exist. - -Owner-native adapters need fixed descriptor/source pins and authenticated per-attempt result/reconciliation/created-object evidence. Broker mint must require exact materialized case1.1, independent broker-grant and parent-session revocation receipts and CAS already-present reconciliation without regeneration. Delivery must report fixed readiness/target metadata without values. Registration/groups/env changes remain one contained attended session with exact accepted memberships, registry CAS and current startup validation, owned by NetKingdom/KeyCape. Consumer handover must prove group refusal through the actual Forgejo configuration, preserve local sign-in/break-glass and use exact reversible Helm ownership. Current kernel has no installed dispatch/adapter provenance or ordered global verification; a fake verifier is not an owner return. - -The real break-glass login, founder MFA/sign-in/group mapping and attended credential generation/client restart are separate human/native gates. No new login or live policy/workload/credential change was attempted. NK-WP0048-T06, MASON-WP0010-T03 and WARDEN-WP0044-T03 cannot finish from source preparation or this receipt. Existing4-phase type review and native first-run acceptance remain in force; no placeholder framework was added. diff --git a/docs/evidence/2026-10-11-forgejo-first-case/source-pins.json b/docs/evidence/2026-10-11-forgejo-first-case/source-pins.json deleted file mode 100644 index 6fadb08..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/source-pins.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "net-kingdom": { - "head": "42aaf6006228004a82dc7175bb0202120c68d3df", - "files": { - "security-cases/forgejo-netkingdom-oidc.yaml": "bcc2c6d5bae0d28242b3cdb71e74d8ca297c2afe04766b55d53597f86893351d", - "security-case-types/oidc-browser-relying-party/type.yaml": "f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c" - } - }, - "ops-mason": { - "head": "f52c94cd4e36276d58cd23c34be6d22f02682ba4", - "files": { - "src/ops_mason/case_structure.py": "71032a5ea613f5226f552f796656599c1a7447a38428f890595d8f252831ce80", - "docs/forgejo-structural-materialization-source-v1.md": "60c9a3aa3d4d12da4b18dbf4e34e0ef5ef7f6eec1343980a8096d6d04cc844cb" - } - }, - "ops-warden": { - "head": "eaabb4216186f4fee2d830376a381740b140087c", - "files": { - "src/warden/case_run.py": "3636d089bfa9dd3fe66971529305354ba1e7ed46572eeceaabeb3c9a064b76a8", - "docs/security-case-execution-kernel-v1.md": "ed5b6432ada71d1065fc837e3638e248cd4803241b6430d6ded07c25c0094c8d" - } - }, - "railiance-platform": { - "head": "8c152d012a44664f51969f2cb3456e28a3a0e5ff", - "files": { - "credential-change-requests/CCR-2026-0042-forgejo-netkingdom-oidc-consumer-reader.yaml": "3d894cee60c9623780431c8a008bea22c61cecce9aac7bd152399a7a26d64789" - } - }, - "railiance-apps": { - "head": "b6be18b7274964b9da73c4612c707be65fef02a1", - "files": { - "workplans/RAPPS-WP-0016-forgejo-netkingdom-login.md": "a74f9fbc3300739bc0d8e31f1de88c0b7226ff2f6990e7cbb3ae433ac84fdb5e", - "helm/forgejo-values.yaml": "dab8376997e4db458fb28f751b802db2463a1e628b2510d5a4207c6fca06d41a" - } - }, - "prj-forgejo-org-refactor": { - "head": "a03597ff3cbd1f8415b361838136607f8976f8b7", - "files": { - "workplans/ORGREF-WP-0002-forge-closure-netkingdom-access.md": "c749ad68f7eec9544b6f4f775906b5bc034c44a3f3506e634a9ee03bd221aa4d" - } - } -} diff --git a/docs/evidence/2026-10-11-forgejo-first-case/structural-request.json b/docs/evidence/2026-10-11-forgejo-first-case/structural-request.json deleted file mode 100644 index fc181de..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/structural-request.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "source_only": true, - "request_digest": "sha256:f2ffc19676b257d4ecbbb79b653da19cc097e3a8d5f7cdfe6fd7e69481b4b49f", - "request": { - "schema": "ops-mason.case-structure-request.v1", - "case": "forgejo-netkingdom-oidc", - "case_digest": "sha256:bcc2c6d5bae0d28242b3cdb71e74d8ca297c2afe04766b55d53597f86893351d", - "type_ref": "oidc-browser-relying-party@1.1", - "type_digest": "sha256:f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c", - "render_digest": "sha256:e64b56e4590b57b858303fcda1a474693e3dd821bc83df26646fe78fc345498a", - "plan_digest": "sha256:21c51b1046e1622c2aa7a9295437e5ef51cb5cd271bf5f4a4b2da5307f5ed2ca", - "bundle_digest": "sha256:8a41bfa829386a8ea5f01baa7407f9f31ab6be384f51ca9a4b07027f6ee955e0", - "structural_digest": "sha256:523a9020822a406f4f4cfb36608abbddf01eecf126aae5f2d170100fc4fdd5c0", - "reviewed_render_digest": "sha256:af1f4f81ef313f5e086e6e3de17197d9112dd40f00b4c20b52b5ca73c7a6d18a", - "run_source_changed": true, - "objects": [ - { - "id": "client-secret-path", - "kind": "kv_path", - "target": "platform/workloads/keycape/forgejo", - "expected_digest": "sha256:04bef1f10b77b1b2fde4e5eae1b90a41a2e9c5a3b11cb1d4869c1fbdb5d63807", - "evidence_owner": "railiance-platform", - "proof": "layout-only" - }, - { - "id": "keycape-reader-policy", - "kind": "openbao_policy", - "target": "workload-kv-read-keycape-forgejo-eso", - "expected_digest": "sha256:cdf9bbd0050daef327b3e135a4c9a357f274111a1c65e640b9ff50ac22ce3a23", - "evidence_owner": "railiance-platform", - "proof": "native-structure" - }, - { - "id": "consumer-reader-policy", - "kind": "openbao_policy", - "target": "workload-kv-read-keycape-forgejo-forgejo", - "expected_digest": "sha256:861d80ffcc0289a4623badd9dbb248264899b990f1c8acf72078b86ddbe77335", - "evidence_owner": "railiance-platform", - "proof": "native-structure" - }, - { - "id": "eso-role", - "kind": "openbao_role", - "target": "external-secrets-keycape-forgejo", - "expected_digest": "sha256:7035f10ca01fc0db0ddaf3b04828b5c3e92e2e747065fce8b408bdc1c5aeb8f4", - "evidence_owner": "railiance-platform", - "proof": "native-structure" - }, - { - "id": "catalog-entry", - "kind": "catalog_entry", - "target": "forgejo-keycape-client", - "expected_digest": "sha256:87feb0bc280ea5de555bfee1b2de169331c9739a11c955d2dabdad1a2ec87a7e", - "evidence_owner": "ops-warden", - "proof": "pointer-source" - } - ] - } -} diff --git a/docs/evidence/2026-10-11-forgejo-first-case/validation.json b/docs/evidence/2026-10-11-forgejo-first-case/validation.json deleted file mode 100644 index 077e3b6..0000000 --- a/docs/evidence/2026-10-11-forgejo-first-case/validation.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "platform_native_ccr_validator_passed": true, - "ccr_status": "in_flight", - "auth_binding_added": false, - "historical_security_case_provenance_unchanged": true, - "source_structure_request_passed": true, - "native_materialization_claimed": false, - "human_session_performed": false -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/combined-packet-ack.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/combined-packet-ack.json deleted file mode 100644 index 2427d14..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/combined-packet-ack.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-COMBINED-R1-ACK", - "work_record": "RPF-IN-0007", - "existing_completed_owner_record_commit": "4493360", - "role": "railiance-platform technical owner only", - "status": "technical-packet-acknowledgment", - "exact_combined_manifest_sha256": "ffa2679a94a24e69a75d1248a808825498a80e711ceda98f3639a4143c40ec1b", - "paths": 112, - "memo_sha256": "506d3956beb7c62ab2bbdfda783c05905d183452a1f67a1817f3fb8c2e522937", - "bound_document_count": 20, - "bound_document_bytes": 171910, - "executor_bundle_sha256": "27d6d6d3133c963da1dde92d498d636617387c16e850a17c598148abc35c775d", - "executor_plan_sha256": "f5e5fa906ff19d3a45d10dcdb2810373ae21f9cf44c0472ff2bd47d3bc3328d6", - "reviewed_helper_manifest_sha256": "f2fad6c251fbb4420e07c1329802f521b513bbf66e51f64c5d673ef04f16284c", - "verification": { - "all112_source_hashes_match": true, - "all20_document_digests_match": true, - "packet_links_equal_document_digests": true, - "four_render_pins_equal_reviewed_execution_plan": true, - "all15_indirect_artifact_evidence_hashes_match": true, - "full_raw_scan_sbom_base_preserved": true - }, - "acknowledged_bound_terms": [ - "Exact supervised helper/bundle/plan and all four owner render inputs bound; fixed image763c/runtime6c/schema5/PVC/credentials/audience/policy scope unchanged.", - "Confined new nonce-bound offline volume UID0/CAP_CHOWN initializer; later UID10001 pre/admin/post readonly proof and original snapshot preservation.", - "Unhealthy-candidate-independent same-schema rollback request and distinct recovered Ready/scoped-operation/currentDB integrity proof; no older snapshot overwrite or same-phase retry.", - "Every phase including verification and recovery must begin before deadline2026-10-12T23:59:59Z; beyond it fresh founder decision needed and recovery-delay risk explicitly disclosed.", - "Ten installed checks remain synthetic transport/native_provider=false, including native placement; actual provider/PDP binding evidence remains distinct T04 completion gate.", - "Known inherited fixable MEDIUM zlib remains explicit, raw unfiltered scan/SBOM/base evidence preserved by validated hash links, T05 follow-through dueOct18 remains open." - ], - "remaining_before_post": "Parent applies/commits prepared source, verifies actual final owner revision and all four approved render blobs, then retains that exact revision in proposed/supervised trial receipt. This source packet ACK does not supply a founder disposition.", - "remaining_before_native_action": "Actual exact founder confirmed disposition and fresh authority/sealed custody/admission/capacity checks per phase; real protected backup/actual-data drill/fresh cutover and rollout/native acceptance evidence remain unexecuted.", - "same_trial": "INFD-WP-0009-T04/purpose-overlay-v1 / infd-purpose-20261011-r1; historical conditional/failed preparation evidence retained, no new privilege approval or scored success", - "limits": { - "native_action": false, - "backup_export_restore": false, - "credential_read": false, - "founder_approval": false, - "native_provider_proof": false, - "T04_complete": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/conditional-review.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/conditional-review.json deleted file mode 100644 index fbb1384..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/conditional-review.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-ACTUATOR-R1-CONDITIONAL", - "work_record": "RPF-IN-0007", - "role": "railiance-platform technical owner only", - "status": "changes-required", - "manifest_sha256": "653b68c3891c066f40d104bfc67a35da5e436605cd99afd52fbac4b47a9553fe", - "source_bundle_sha256": "0a79eda40fe0954fd3828bf06fd6ccf149e294ac194bc6fe605e585b878fa8d3", - "independently_rerun_tests": 12, - "required_changes": [ - { - "id": "RPF-INFD-EXEC-01", - "source": "execution/native_program.py", - "finding": "Rollback requires Running/ready candidate Pod and candidate exec before operation; unhealthy startup/readiness failure cannot recover.", - "required": "Separate bounded rollback request from post-rollback verification. Request gates retain sealed Deployment/CM/PVC, exact same-schema source/image pair, node headroom, owner dryrun and Application CAS/scoped sync, without requiring candidate Ready/exec. Verify reverted image/Ready/schema/integrity afterward under an explicit recovery phase; no data snapshot restore or broader mutation." - }, - { - "id": "RPF-INFD-EXEC-02", - "source": "execution/actuator.py::offline_verify", - "finding": "Only pre-open read-only integrity proof exists; admin counters alone do not establish post-open integrity/schema/counts required by protocol.", - "required": "Repeat exact read-only checker under same isolated UID10001 after admin; compare full frozen proof, retain original private snapshot digest unchanged, fail on any mismatch." - }, - { - "id": "RPF-INFD-EXEC-03", - "source": "authority_journal.py / native_program.py / amended founder packet", - "finding": "All phases including recovery are denied after execution deadline, while earlier packet describes expiry for unstarted release only.", - "required": "Make recovery expiry semantics explicit in amended packet. Do not silently broaden authorization: either retain clearly disclosed bounded recovery deadline and stop for fresh recovery decision beyond it, or bind any allowed recovery-after-start semantics to exact persisted release attempt and fresh actual acceptance." - } - ], - "sound_boundaries": [ - "Nested exact founder/current native receipt bookkeeping remains supervised and expressly not authenticated autonomous grant.", - "Four committed render blobs plus source HEAD, exact executor/input hashes and actual UID/CM/PVC seals; automatic:{} and source override refusal.", - "Protected binarycopy byte/hash/size bounds, no raw snapshot output, networknone isolated newly labeled localvolume with confined initializer and UID10001 pre-Store checks.", - "Fsynced RUNNING intent and no same-phase retry after uncertain/crash; Deployment-only manual Argo operation prunefalse via CAS, dispatch distinct from rollout completion." - ], - "limits": { - "native_action": false, - "backup_export_restore": false, - "credential_read": false, - "founder_approval": false, - "native_custody_proof": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/final-review.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/final-review.json deleted file mode 100644 index ba4c1cc..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/final-review.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-ACTUATOR-R1-FINAL", - "work_record": "RPF-IN-0007", - "role": "railiance-platform technical owner only", - "status": "technical-agreement-for-exact-helper-source", - "manifest_sha256": "f2fad6c251fbb4420e07c1329802f521b513bbf66e51f64c5d673ef04f16284c", - "source_bundle_sha256": "27d6d6d3133c963da1dde92d498d636617387c16e850a17c598148abc35c775d", - "source_bundle_bytes": 85747, - "paths": 23, - "all_manifest_hashes_match": true, - "independently_rerun_tests": 16, - "additional_recovery_negative_cases": 5, - "resolved": [ - "Rollback request requires sealed Deployment/CM/PVC/source/image pair/headroom/admission/CAS but no candidate Pod/readiness/database exec; distinct rollback-verify proves old-image Ready1/scoped succeeded operation/current schema5 integrity/counts.", - "Offline UID10001/networknone post-admin checker repeats full readonly integrity/schema/count proof against frozen snapshot and verifies protected original hash unchanged.", - "All phases including verification and recovery have explicit fixed expiry; no silent extension. A fresh founder decision is required beyond the deadline, and the resulting recovery-delay risk is disclosed in helper README." - ], - "independent_probe": "refined-recovery-probes.json", - "scope": "Exact bounded supervised helper source only; no final founder packet acceptance or runtime execution implied. Durable no-retry intent, protected binary copy/new confined offline volume, exact owner hash/source/UID custody gates and Deployment-only Argo CAS/manual prunefalse scope remain.", - "before_first_human_post": [ - "Amend and validate exact hash-bound founder packet to include current helper bundle/plan, all four owner render inputs, confined UID0 initializer, separate rollback verification and explicit all-phase deadline with recovery-delay risk.", - "Retain original78-path/initial11-path/conditional review evidence without backfilling approval." - ], - "native_gates": [ - "Fresh root authenticated fixed-origin exact founder disposition per phase; bookkeeping files are not autonomous grants.", - "Actual protected real-data backup/copy/offline restoration/fresh cutover snapshot remain unexecuted and required.", - "Installed ten-variant checks use synthetic transport even on native placement; native_provider=false. Actual-provider/native binding acceptance remains a distinct T04 completion gate.", - "Image request acknowledgment is not rollout completion; recovery acknowledgment is not recovered Ready/schema proof; uncertain outcomes retain evidence and refuse same-phase retries." - ], - "limits": { - "native_action": false, - "backup_export_restore": false, - "credentials_read": false, - "founder_approval": false, - "native_custody_proof": false, - "native_provider_proof": false, - "other_owner_role": false, - "T04_complete": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/archive-map.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/archive-map.json deleted file mode 100644 index b97c513..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/archive-map.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "original_manifest_sha256": "d59ef787d0173be52a11a2f8bba85e803475f56f9c51d66a676ffab94fe64389", - "kind": "exact-original-source-review-history", - "scope": "Historical original4file snapshot only; current owner anchor and finalactualACK/approvedhelper unchanged. Original manifest paths describe historical staging; this map locates durable byte-identical archive copies.", - "files": [ - { - "original_path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json", - "archive_path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json", - "sha256": "ae641b0251e1fe119a720c4c7efbfbf6999c03660b836103cf4ffb0afe868f72" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py", - "archive_path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py", - "sha256": "2aca5d4a3576ab4601098e2346a2141419c48382fdca7e6655f388e8487d45bf" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json", - "archive_path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json", - "sha256": "5a5423a90152b40f7e905ad60cf24f76dc6777ff70d250dd7600cea419d7c1c1" - }, - { - "original_path": "intakes/RPF-IN-0007-infd-purpose-actuator-review.md", - "archive_path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/intakes/RPF-IN-0007-infd-purpose-actuator-review.md", - "sha256": "fd4df8d55a87fbad9bc0f5dd88019608f562968e1f5a90e301453cdc9ef193fc" - } - ] -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/original-manifest.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/original-manifest.json deleted file mode 100644 index 3a932df..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/original-manifest.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json", - "staged_path": "/tmp/infd-prune-omission-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json", - "original_sha256": null, - "staged_sha256": "ae641b0251e1fe119a720c4c7efbfbf6999c03660b836103cf4ffb0afe868f72" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py", - "staged_path": "/tmp/infd-prune-omission-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py", - "original_sha256": null, - "staged_sha256": "2aca5d4a3576ab4601098e2346a2141419c48382fdca7e6655f388e8487d45bf" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json", - "staged_path": "/tmp/infd-prune-omission-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json", - "original_sha256": null, - "staged_sha256": "5a5423a90152b40f7e905ad60cf24f76dc6777ff70d250dd7600cea419d7c1c1" - }, - { - "repo": "railiance-platform", - "path": "intakes/RPF-IN-0007-infd-purpose-actuator-review.md", - "staged_path": "/tmp/infd-prune-omission-platform-review-stage/railiance-platform/intakes/RPF-IN-0007-infd-purpose-actuator-review.md", - "original_sha256": "0c415b0c92da948b8a3e097ea46fcdfb5ad3aeea9bfd774eefdb3a68fd6ba36c", - "staged_sha256": "fd4df8d55a87fbad9bc0f5dd88019608f562968e1f5a90e301453cdc9ef193fc" - } -] diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json deleted file mode 100644 index 86a711a..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "url": "https://raw.githubusercontent.com/argoproj/argo-cd/master/pkg/apis/application/v1alpha1/types.go", - "source_sha256": "2228837378905e38c54805c66dd7bbbf96bf8fe2b9aaf1feee6a0fb96e837405", - "sync_operation_declaration": "Prune bool `json:\"prune,omitempty\" protobuf:\"bytes,2,opt,name=prune\"`", - "installed_version_correspondence": "pending retained metadata; master alone not proof of installed binary" -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py deleted file mode 100644 index edf7742..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Synthetic analysis predicate; never an executor, authority gate or native verifier.""" -import copy,json -RESOURCE={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'} -def consistent(request, status, revision): - sync=status.get('operation',{}).get('sync',{}) - return (request.get('revision')==revision and request.get('prune') is False - and request.get('resources')==[RESOURCE] and status.get('phase')=='Succeeded' - and sync.get('revision')==revision and sync.get('resources')==[RESOURCE] - and sync.get('prune',False) is False) -request={'revision':'synthetic-exact-revision','prune':False,'resources':[RESOURCE]};status={'phase':'Succeeded','operation':{'sync':{'revision':request['revision'],'resources':[RESOURCE]}}} -assert consistent(request,status,request['revision']) -accepted_false=copy.deepcopy(status);accepted_false['operation']['sync']['prune']=False;assert consistent(request,accepted_false,request['revision']) -for value in (True,None,0,'false',[]): - bad=copy.deepcopy(status);bad['operation']['sync']['prune']=value;assert not consistent(request,bad,request['revision']) -for defect in ('revision','resource','namespace','phase','original_request'): - bad=copy.deepcopy(status);original=copy.deepcopy(request) - if defect=='revision':bad['operation']['sync']['revision']='wrong' - elif defect=='resource':bad['operation']['sync']['resources'].append({'kind':'Secret'}) - elif defect=='namespace':bad['operation']['sync']['resources'][0]['namespace']='wrong' - elif defect=='phase':bad['phase']='Running' - else:original['prune']=True - assert not consistent(original,bad,request['revision']) -print(json.dumps({'synthetic_only':True,'native_commands':False,'omitted_false_agrees_with_explicit_false_request':True,'explicit_false_agrees':True,'five_nonfalse_values_refused':True,'five_scope_request_completion_drift_cases_refused':True})) diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json deleted file mode 100644 index 11e95e9..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-R2-READONLY-RECONCILIATION", - "work_record": "RPF-IN-0007", - "role": "railiance-platform technical owner only", - "status": "agrees-with-bounded-readonly-independent-reconciliation", - "question": "Can independent read-only target evidence verify an approved completed release despite helper verify UNCERTAIN from omitted prune=false serialization?", - "judgement": "Yes. Gather only already-approved verification evidence through existing contained owner/read paths. Preserve original approved helper/source and original UNCERTAIN verify journal; do not rerun that phase, modify its flags or treat it as an unchanged scored success. Independent evidence may refine the trial outcome after actual review, with failure retained. No further privilege/effect is requested by this read-only reconciliation.", - "source_defect": { - "file": "execution/native_program.py", - "line": 179, - "check": "sync.get(prune) is False", - "effect": "Missing serialized false becomes None and refuses even when original explicit prune=false request was correctly applied." - }, - "api_primary_source": { - "url": "https://raw.githubusercontent.com/argoproj/argo-cd/master/pkg/apis/application/v1alpha1/types.go", - "declaration": "Prune bool `json:\"prune,omitempty\" protobuf:\"bytes,2,opt,name=prune\"`", - "installed_version_correspondence": "Current upstream field semantics verified; retained installed-version/API evidence requested from coordinator, not independently read from native estate." - }, - "required_readonly_correlations": [ - "Recorded approved release request explicitly prune=false and exact source revision/single Deployment namespace; correlate succeeded operation revision/resource/initiator/start/finish with that release, not an arbitrary later status.", - "Accept omitted false only as documented serialization coupled to exact original request and succeeded scoped status. Reject present true/null/string/number or changed resource/source/UID.", - "Fresh exact Deployment/CM/PVC/pod ownership/configuration and controller observed-generation/Ready/image pins, actual installed module hash/mode/UID, retained schema5/integrity/counts/current DB and bounded health checks.", - "Retain installed ten checks as synthetic transport/native_provider=false; actual authenticated native service/provider binding evidence remains independently identified.", - "No operation/patch/sync/retry/database restore/cleanup/credential changes. If independent evidence indicates actual target failure or altered effects, stop and use approved recovery or a separately proposed mutation." - ], - "synthetic_analysis": { - "omitted_and_explicit_false_with_matching_request_accepted": true, - "five_wrong_value_cases_refused": true, - "five_request_scope_completion_drift_cases_refused": true, - "native_commands": false - }, - "limits": { - "reviewer_native_commands": false, - "helper_source_or_owner_head_mutation": false, - "journal_rewrite_or_same_phase_retry": false, - "new_privileged_go": false, - "actual_native_target_completion_claimed_here": false, - "T04_complete": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/intakes/RPF-IN-0007-infd-purpose-actuator-review.md b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/intakes/RPF-IN-0007-infd-purpose-actuator-review.md deleted file mode 100644 index 548a0d8..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-d59ef787/owner-snapshot/intakes/RPF-IN-0007-infd-purpose-actuator-review.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -id: RPF-IN-0007 -kind: intake -type: intake -title: "Platform review of INFD protected custody and image release actuator" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -Review exact11-path execution source manifest `653b68c3891c066f40d104bfc67a35da5e436605cd99afd52fbac4b47a9553fe` and bundle `0a79eda40fe0954fd3828bf06fd6ccf149e294ac194bc6fe605e585b878fa8d3`, approval gate, private real-data custody, offline clone isolation, single-writer cutover, uncertain recovery, manual Argo scope and current-database rollback. Record precedes formal technical review. Synthetic verification only; no native backup/export/restore, mutation, credential read or founder approval is authorized. - -## Completed initial source review - -Absorbed into technical review `RPF-REVIEW-20261011-INFD-ACTUATOR-R1-CONDITIONAL`. Changes required: unhealthy-candidate rollback must not depend on candidate readiness/exec; offline restore must repeat integrity/schema/count proof after admin. Deadline/recovery semantics require explicit amended-packet agreement. Initial12tests passed but independent failure probe reproduced zero rollback dispatch for an unready candidate. Native work remains in INFD-WP-0009-T04; no native action/approval occurred. Completed record precedes formal review entry. - -## Final exact helper review - -Final technical agreement `RPF-REVIEW-20261011-INFD-ACTUATOR-R1-FINAL` covers helper manifest `f2fad6c251fbb4420e07c1329802f521b513bbf66e51f64c5d673ef04f16284c`,23paths, execution bundle `27d6d6d3133c963da1dde92d498d636617387c16e850a17c598148abc35c775d`. Sixteen synthetic tests independently pass; additional recovery probes reject wrong operation/source/UID/prune/resource scope and confirm unhealthy-candidate recovery request without Pod/exec. Original conditional evidence remains. Final founder packet must hash-bind this executor/four render inputs and explicitly disclose the all-phase deadline before submission; source agreement is not approval or native custody/provider evidence. - -## Snapshot failure source review work - -Same Platform owner preparation trial, following successful native backup and retained UNCERTAIN drill-copy reported by the coordinator. Completed independent source-mechanism check reproduces WAL0/SHM32768 on a synthetic Store backup after the initial checker. No native database was opened or copied, no retry/cleanup/source HEAD change occurred. The exact recovery-source technical review entry follows this owner work record when maker source is frozen; original failed journal and approval remain evidence, not backfilled success. Fresh exact supervisor disposition on the recovery delta is required before any action. - -## Completed existing-snapshot rescue source review - -Same Platform role final technical agreement `RPF-REVIEW-20261011-INFD-SNAPSHOT-RESCUE-R2` covers exact66-path stage `0867e27a6754dddff3eedbb333ab4ca4faa98ee31d383ebe940b8e054c6cabf7`. Twenty-one executor and five mocked reader tests independently pass; additional ordinary-file/mock and journal crash probes refuse unsafe custody/replay without altering the original. This work note precedes its formal entry. Fresh exact source-version2 founder disposition, newly committed source and authenticated fixed-origin readback are required before any recovery phase. Original COMPLETE backup/UNCERTAIN copy and approval remain historical unchanged; no native recovery, copy or deletion occurred in this review. - -## Independent read-only outcome reconciliation review - -Completed same Platform source judgement after coordinator-reported approved R2 release and original verify UNCERTAIN caused by serialized prune omission. Independent read-only evidence may verify the already-approved target while retaining the original failure/journal unchanged; this does not authorize native mutation or helper retry. Source judgement precedes the formal review entry. Actual collected evidence remains subject to exact owner review and is not claimed complete here. diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-synthetic-sidecar-probe.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-synthetic-sidecar-probe.json deleted file mode 100644 index 6d90092..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/initial-synthetic-sidecar-probe.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "synthetic_only": true, - "native_database_opened": false, - "schema": 5, - "wal_after_read_bytes": 0, - "shm_after_read_bytes": 32768 -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/installed-argocd-controller-version.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/installed-argocd-controller-version.json deleted file mode 100644 index 94d8b70..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/installed-argocd-controller-version.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "namespace": "argocd", - "kind": "StatefulSet", - "name": "argocd-application-controller", - "image": "quay.io/argoproj/argocd:v3.5.3", - "native_metadata_observed": true -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/installed-argocd-primary-source.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/installed-argocd-primary-source.json deleted file mode 100644 index 1b31a84..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/installed-argocd-primary-source.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "url": "https://raw.githubusercontent.com/argoproj/argo-cd/v3.5.3/pkg/apis/application/v1alpha1/types.go", - "source_sha256": "f39d3024cb4bc7e20d0180260a8284422b6a66066870c2a8f368356d8935a3c8", - "source_version": "v3.5.3", - "prune_bool_omitempty_declarations": [ - [ - 1421, - "Prune bool `json:\"prune,omitempty\" protobuf:\"bytes,2,opt,name=prune\"`" - ] - ], - "installed_metadata_receipt_sha256": "62b85702ca73d8c349a52be9c8196e2f85c6e9e0aafb07f17a15d5c988fee82b" -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/live-wal-semantic-probe.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/live-wal-semantic-probe.json deleted file mode 100644 index b9f5c78..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/live-wal-semantic-probe.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "synthetic_only": true, - "native_database_opened": false, - "committed_wal_bytes": 8272, - "wal_aware_count": 1, - "incorrect_immutable_live_count": 0, - "immutable_live_read_loses_committed_record": true -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json deleted file mode 100644 index 86a711a..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-primary-source-provenance.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "url": "https://raw.githubusercontent.com/argoproj/argo-cd/master/pkg/apis/application/v1alpha1/types.go", - "source_sha256": "2228837378905e38c54805c66dd7bbbf96bf8fe2b9aaf1feee6a0fb96e837405", - "sync_operation_declaration": "Prune bool `json:\"prune,omitempty\" protobuf:\"bytes,2,opt,name=prune\"`", - "installed_version_correspondence": "pending retained metadata; master alone not proof of installed binary" -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py deleted file mode 100644 index edf7742..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/prune-semantic-probe.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Synthetic analysis predicate; never an executor, authority gate or native verifier.""" -import copy,json -RESOURCE={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'} -def consistent(request, status, revision): - sync=status.get('operation',{}).get('sync',{}) - return (request.get('revision')==revision and request.get('prune') is False - and request.get('resources')==[RESOURCE] and status.get('phase')=='Succeeded' - and sync.get('revision')==revision and sync.get('resources')==[RESOURCE] - and sync.get('prune',False) is False) -request={'revision':'synthetic-exact-revision','prune':False,'resources':[RESOURCE]};status={'phase':'Succeeded','operation':{'sync':{'revision':request['revision'],'resources':[RESOURCE]}}} -assert consistent(request,status,request['revision']) -accepted_false=copy.deepcopy(status);accepted_false['operation']['sync']['prune']=False;assert consistent(request,accepted_false,request['revision']) -for value in (True,None,0,'false',[]): - bad=copy.deepcopy(status);bad['operation']['sync']['prune']=value;assert not consistent(request,bad,request['revision']) -for defect in ('revision','resource','namespace','phase','original_request'): - bad=copy.deepcopy(status);original=copy.deepcopy(request) - if defect=='revision':bad['operation']['sync']['revision']='wrong' - elif defect=='resource':bad['operation']['sync']['resources'].append({'kind':'Secret'}) - elif defect=='namespace':bad['operation']['sync']['resources'][0]['namespace']='wrong' - elif defect=='phase':bad['phase']='Running' - else:original['prune']=True - assert not consistent(original,bad,request['revision']) -print(json.dumps({'synthetic_only':True,'native_commands':False,'omitted_false_agrees_with_explicit_false_request':True,'explicit_false_agrees':True,'five_nonfalse_values_refused':True,'five_scope_request_completion_drift_cases_refused':True})) diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-actual-target-outcome-ack.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-actual-target-outcome-ack.json deleted file mode 100644 index 5536164..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-actual-target-outcome-ack.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-R2-ACTUAL-TARGET-ACK", - "work_record": "RPF-IN-0007", - "role": "railiance-platform technical owner only", - "status": "approved-target-verified-by-independent-readonly-evidence", - "approved_source_revision": "c96b0c6d881e855d1d158003c6de90ef390262e2", - "candidate_image": "sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "original_verify_state": "UNCERTAIN_preserved", - "initial_source_judgement_manifest_sha256": "d59ef787d0173be52a11a2f8bba85e803475f56f9c51d66a676ffab94fe64389", - "evidence_sha256": { - "independent_target": "c3fc07400204a4821e65aebcbfd228ce571f2b0402a52e0719d29d3704ec0a60", - "post_rollout_native_allow_path": "c534f5fa96d4d3e66b1a8f72cae9be15c62c4d0dd0b35fbefdfe13e3f51e89b7", - "additional_pod_custody": "078fff224931bc3ddaa916b5179511b5dbe1d856e4073ce8176bdcfb23769ae5", - "installed_argocd_version": "62b85702ca73d8c349a52be9c8196e2f85c6e9e0aafb07f17a15d5c988fee82b", - "independent_readonly_command": "5483bcd84f6658960e2339b6c3f29ad75027748af993fa46cd527e12ff912ff8", - "additional_pod_readonly_command": "77962916a5f4cb4d3b110a30b13cba961dd87bbc476261c0c9f5aa29f096e6a6" - }, - "verified_target": [ - "Exact immutable image and imageID763c, existing DeploymentUID a588/currentgeneration25 Ready1/observed generation; exact CMUID90113 and PVCUIDd80b/full custody seals retained.", - "PodUIDc4cb1550-feda-4097-9de4-d891a657b662 Running/exactsinglecontainer Ready, owner chain intact; all sealed template and forbidden optional controls match approved image-only target, canonical controlled digestbb6e6ef2809d15ef27db05fa75cabd40fc4e4bfe43e1f7a3ac750ed69380adaa; actual UID/GID10001.", - "Actual installed policy module9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62/rootowned0644; healthz/readyz200.", - "Read-only WAL-aware schema5/integrityok current database; counts16records56events0pending,336delivered outbox43confirmed submissions. No old snapshot restoration or forced equality with earlier live counts.", - "ActualArgo Succeeded at07:00:30 exactc96 source/single Synced Deployment namespace and custodian-codex initiator/startafterrecordedrelease07:00:23; original request explicitprunefalse, actual false omitted.", - "Installed controller v3.5.3 matches independently fetched primary types.go SHA f39d3024cb4bc7e20d0180260a8284422b6a66066870c2a8f368356d8935a3c8 Prune bool jsonomitempty; omission adjudication confined to recorded exact false request/succeeded scoped operation." - ], - "provider_evidence": "Fresh authenticated canonical lifecycle.outcomes sourceversion2 HTTP success07:01:37 after new-image rollout, together with installed source/configuration seals and LifecycleService.outcomes->_authorize->PolicyClient.check fresh POST/current binding/lifetime checks, supports actual canonical native allow-path preservation. It does not claim provider emitted advisory responses or native denial variants.", - "synthetic_evidence": "Installed10 variants2bindingallows8advisory/malformedrefusals remain synthetic transport/native_providerfalse. No extra provider denial probe claimed.", - "outcome_recording": "Same supervised trial refined by independent actual evidence; original verifier failure/UNCERTAIN and immutable approved helper/source remain retained. No same-phase rerun or journaldone rewrite. Any scoring must preserve both failed verifier and independently verified target outcome.", - "owner_followthrough": [ - "Product owner determines T04 closure using all original/rescue/drill/cleanup/cutover/release receipts plus these actual target/provider proofs.", - "Record prune serialization helper correction as live unpromoted residual before future executor reuse; do not mutate approvedR2helper retrospectively.", - "Known inherited fixable MEDIUM zlib/T05dueOct18 remains a separate live owner risk/remediation gate; no zero-CVE/estate grade inference." - ], - "limits": { - "reviewer_native_commands": false, - "new_privileged_approval": false, - "helper_or_owner_head_mutation": false, - "journal_rewrite_or_phase_replay": false, - "extra_mutation": false, - "native_provider_denial_variants_claimed": false, - "other_owner_role": false, - "blanket_workplan_completion": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json deleted file mode 100644 index 11e95e9..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/r2-readonly-reconciliation-source-review.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-R2-READONLY-RECONCILIATION", - "work_record": "RPF-IN-0007", - "role": "railiance-platform technical owner only", - "status": "agrees-with-bounded-readonly-independent-reconciliation", - "question": "Can independent read-only target evidence verify an approved completed release despite helper verify UNCERTAIN from omitted prune=false serialization?", - "judgement": "Yes. Gather only already-approved verification evidence through existing contained owner/read paths. Preserve original approved helper/source and original UNCERTAIN verify journal; do not rerun that phase, modify its flags or treat it as an unchanged scored success. Independent evidence may refine the trial outcome after actual review, with failure retained. No further privilege/effect is requested by this read-only reconciliation.", - "source_defect": { - "file": "execution/native_program.py", - "line": 179, - "check": "sync.get(prune) is False", - "effect": "Missing serialized false becomes None and refuses even when original explicit prune=false request was correctly applied." - }, - "api_primary_source": { - "url": "https://raw.githubusercontent.com/argoproj/argo-cd/master/pkg/apis/application/v1alpha1/types.go", - "declaration": "Prune bool `json:\"prune,omitempty\" protobuf:\"bytes,2,opt,name=prune\"`", - "installed_version_correspondence": "Current upstream field semantics verified; retained installed-version/API evidence requested from coordinator, not independently read from native estate." - }, - "required_readonly_correlations": [ - "Recorded approved release request explicitly prune=false and exact source revision/single Deployment namespace; correlate succeeded operation revision/resource/initiator/start/finish with that release, not an arbitrary later status.", - "Accept omitted false only as documented serialization coupled to exact original request and succeeded scoped status. Reject present true/null/string/number or changed resource/source/UID.", - "Fresh exact Deployment/CM/PVC/pod ownership/configuration and controller observed-generation/Ready/image pins, actual installed module hash/mode/UID, retained schema5/integrity/counts/current DB and bounded health checks.", - "Retain installed ten checks as synthetic transport/native_provider=false; actual authenticated native service/provider binding evidence remains independently identified.", - "No operation/patch/sync/retry/database restore/cleanup/credential changes. If independent evidence indicates actual target failure or altered effects, stop and use approved recovery or a separately proposed mutation." - ], - "synthetic_analysis": { - "omitted_and_explicit_false_with_matching_request_accepted": true, - "five_wrong_value_cases_refused": true, - "five_request_scope_completion_drift_cases_refused": true, - "native_commands": false - }, - "limits": { - "reviewer_native_commands": false, - "helper_source_or_owner_head_mutation": false, - "journal_rewrite_or_same_phase_retry": false, - "new_privileged_go": false, - "actual_native_target_completion_claimed_here": false, - "T04_complete": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/refined-recovery-probes.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/refined-recovery-probes.json deleted file mode 100644 index 3c736ac..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/refined-recovery-probes.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "synthetic_transport_only": true, - "native_action": false, - "negative_recovery_cases": { - "wrong_operation_revision": { - "refused": true, - "writes": 0 - }, - "prune_true": { - "refused": true, - "writes": 0 - }, - "extra_resource": { - "refused": true, - "writes": 0 - }, - "wrong_source": { - "refused": true, - "writes": 0 - }, - "recreated_application": { - "refused": true, - "writes": 0 - } - }, - "unready_rollback_dispatches_once_without_pod_or_exec": true -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/rescue-independent-probes.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/rescue-independent-probes.json deleted file mode 100644 index bd50c27..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/rescue-independent-probes.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "synthetic_ordinary_files_and_mock_connections_only": true, - "native_action": false, - "extra_refusals": { - "hardlinked_main": { - "rejected_before_sqlite": true - }, - "symlinked_wal": { - "rejected_before_sqlite": true - }, - "unsafe_shm_mode": { - "rejected_before_sqlite": true - } - }, - "crashed_rescue_replay_refused_original_bytes_unchanged": true -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-additional-pod-custody-receipt.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-additional-pod-custody-receipt.json deleted file mode 100644 index 2372284..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-additional-pod-custody-receipt.json +++ /dev/null @@ -1 +0,0 @@ -{"all_checks_pass": true, "additional_readonly_metadata_proof": true, "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662", "exact_one_container": true, "controlled_pod_fields_match_sealed_candidate": true, "controlled_field_names": ["automountServiceAccountToken", "containers", "dnsPolicy", "hostIPC", "hostNetwork", "hostPID", "initContainers", "restartPolicy", "runtimeClassName", "schedulerName", "securityContext", "serviceAccount", "serviceAccountName", "shareProcessNamespace", "terminationGracePeriodSeconds", "volumes"], "canonical_controlled_values_sha256": "bb6e6ef2809d15ef27db05fa75cabd40fc4e4bfe43e1f7a3ac750ed69380adaa", "native_uid": 10001, "native_gid": 10001, "database_opened": false, "original_phase_replayed": false, "mutation_executed": false} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-independent-readonly-target-receipt.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-independent-readonly-target-receipt.json deleted file mode 100644 index c7bf8b8..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-independent-readonly-target-receipt.json +++ /dev/null @@ -1 +0,0 @@ -{"independent_readonly_reconciliation": true, "all_checks_pass": true, "original_verify_journal_state": "UNCERTAIN_preserved", "original_phase_replayed": false, "helper_source_amended": false, "mutation_executed": false, "deployment_uid": "a588e10a-90d2-4883-975e-6dd8b8feca92", "generation": 25, "ready_replicas": 1, "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", "pod_name": "informed-decision-5557446c88-lvm88", "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662", "pod_image_id": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", "configmap_uid": "90113d2d-54d9-4e82-b1d3-90b49ebed2b5", "pvc_uid": "d80b25b1-141a-441b-b205-98fa2fd98b7c", "dependency_seals_match": true, "application_uid": "499868f2-eabc-4df4-ae31-b89ce58d750d", "source_revision": "c96b0c6d881e855d1d158003c6de90ef390262e2", "operation": {"phase": "Succeeded", "started_at": "2026-10-11T07:00:30Z", "finished_at": "2026-10-11T07:00:30Z", "initiated_by": {"username": "custodian-codex"}, "exact_resources": [{"group": "apps", "kind": "Deployment", "name": "informed-decision", "namespace": "informed-decision"}], "prune_field_present": false, "prune_effective": false, "original_release_request_explicit_prune_false": true, "sync_result_resources": [{"group": "apps", "kind": "Deployment", "name": "informed-decision", "namespace": "informed-decision", "status": "Synced", "syncPhase": "Sync"}]}, "native_provider_allow_path_proof": "Separate root-authenticated outcomes readback after new Ready image; installed refusal checks below are synthetic", "live_readonly_schema_integrity_counts": {"schema_version": 5, "lifecycle": {"records": 16, "events": 56, "pending": 0}, "outbox": {"delivered": 336}, "submissions": {"confirmed": 43}, "integrity": "ok", "read_only_pre_open": true, "store_constructed": false}, "module_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62", "module_mode": "0644", "module_uid": 0, "native_uid": 10001, "installed_purpose_checks": {"installed_image_purpose_variants": 10, "binding_forms_allowed": 2, "advisory_or_malformed_refused": 8, "synthetic_only": true, "native_provider": false}, "health_statuses": {"/healthz": 200, "/readyz": 200}, "database_opened_readonly": true, "store_or_admin_constructed": false} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-native-provider-allow-path-receipt.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-native-provider-allow-path-receipt.json deleted file mode 100644 index 57ea59c..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/root-native-provider-allow-path-receipt.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "verification_kind": "post-rollout canonical provider-backed outcomes operation", - "source_memo_id": "infd-20261011-purpose-overlay-r1", - "source_memo_version": 2, - "new_image": "sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662", - "verified_at": "2026-10-11T07:01:37.243411+00:00", - "operation": "lifecycle.outcomes", - "service": "State Hub admitted memo dispatcher using internal mounted credential", - "transport": "fixed internal ORIGIN, no redirects, GET /v1/decisions/infd-20261011-purpose-overlay-r1/outcomes?version=2", - "actual_native_provider_operation": true, - "evidence_basis": "Authenticated native HTTP success after image rollout; installed LifecycleService.outcomes invokes _authorize/PolicyClient.check and current() before returning. Actual installed module hash and configuration seals independently verified.", - "installed_module_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62", - "synthetic_installed_refusal_variants": 8, - "synthetic_variant_check_is_native_provider_response": false, - "extra_provider_denial_probe_executed": false, - "limitation": "Proves actual canonical allow-path preservation; does not claim provider emitted advisory responses or all refusal variants came from native provider." -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/snapshot-rescue-r2-final-packet-ack.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/snapshot-rescue-r2-final-packet-ack.json deleted file mode 100644 index 50de4c7..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/snapshot-rescue-r2-final-packet-ack.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-SNAPSHOT-RESCUE-R2-FINAL-ACK", - "work_record": "RPF-IN-0007", - "role": "railiance-platform technical owner only", - "status": "exact-refined-packet-acknowledgment", - "manifest_sha256": "d05d6e3b15df9acf1d7aef0ba2ec82a5a00d979ab1f324e981025cffe2cc753d", - "paths": 134, - "prior_source_manifest_sha256": "0867e27a6754dddff3eedbb333ab4ca4faa98ee31d383ebe940b8e054c6cabf7", - "prior_platform_review_manifest_sha256": "4687a64f0490b66abefbeb475fb18551a88571d6bb2f5e6a4a5e3688d49c289c", - "memo_sha256": "391330d062ede170a546415886d18b4895ba24d313a0dd50dcc1591c1aa3c2fa", - "bound_document_count": 20, - "bound_document_bytes": 173469, - "unchanged_execution_bundle_sha256": "49c9cb70f36f6ceb7b0d22400b2ec9232a5e1a8a00d17e92b92fe367c98effb5", - "unchanged_execution_plan_sha256": "9ce5728a561e6d38f8497201baa2b840d11444a0af8ad6234dfc37428113f258", - "verification": { - "all134_hashes_match": true, - "archived_original66_exact_bytes": true, - "all20_document_digests_match": true, - "executable_paths_unchanged": true, - "only_changed_existing_paths": [ - "lifecycle-commands.json", - "memo.json", - "proposal.json", - "recovery-intake-rejection-fixture.json", - "revision-command.json", - "validation.json" - ] - }, - "single_semantic_delta": "Historical f2fad helper manifest is labeled original_execution_helper_manifest_sha256 with explicit historical context; current rescue bytes remain bound to unchanged49c9 bundle/9ce5 plan. No executable, authority, snapshot identity, custody, rollback, deadline, image or scanner change.", - "agreement": "Previous exact source technical agreement remains valid for this narrow provenance-label amendment and dependent packet hashes. No new work record, human approval or recovery success inferred.", - "remaining_gates": [ - "Commit exact reviewed owner source and verify final source/four render blobs.", - "Fresh actual source memo2/new reviewID/version1 founder disposition and current fixed-origin source outcome readback before phase; original version1 approval not reused.", - "Original COMPLETE backup/UNCERTAIN copy/journal remain unchanged; new named rescue attempt only after exact approval/fresh custody checks. No same-phase retries/deletions." - ], - "limits": { - "native_action": false, - "native_database_open": false, - "native_copy_retry_cleanup": false, - "owner_head_mutation": false, - "founder_approval": false, - "T04_complete": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/snapshot-rescue-r2-review.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/snapshot-rescue-r2-review.json deleted file mode 100644 index 970ca70..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/snapshot-rescue-r2-review.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-SNAPSHOT-RESCUE-R2", - "work_record": "RPF-IN-0007", - "role": "railiance-platform technical owner only", - "status": "technical-agreement-on-prepared-rescue-source", - "manifest_sha256": "0867e27a6754dddff3eedbb333ab4ca4faa98ee31d383ebe940b8e054c6cabf7", - "paths": 66, - "all_manifest_hashes_match": true, - "source_memo_version": 2, - "memo_sha256": "cac3fede955c6024d2566f443d04d259cefb7a0ef2368e7f05c174fa4b029cf3", - "executor_bundle_sha256": "49c9cb70f36f6ceb7b0d22400b2ec9232a5e1a8a00d17e92b92fe367c98effb5", - "executor_plan_sha256": "9ce5728a561e6d38f8497201baa2b840d11444a0af8ad6234dfc37428113f258", - "bound_document_count": 20, - "bound_document_bytes": 173153, - "bound_digests_valid": true, - "verification": { - "executor_test_methods": 21, - "mocked_reader_test_methods": 5, - "extra_private_file_refusals_before_sqlite": [ - "hardlinked_main", - "symlinked_wal", - "unsafe_shm_mode" - ], - "crashed_rescue_replay_refused_original_bytes_unchanged": true - }, - "original_frozen_identity": { - "journal_sha256": "d187b02d98d6e97429fe20b4fbe1d309a6c6c4b55232b4ae13ac39873039f1c1", - "snapshot_bytes": 19537920, - "snapshot_sha256": "8756cf825df9e7b5b74bb4a0106a5027f5cff2c59ff45d7b48eed8522a7d33f3", - "original_source": "bc6494cd1b290783ef3aef0763bf28d1deabf4b0", - "original_backup_state": "COMPLETE", - "original_copy_state": "UNCERTAIN" - }, - "agreed_boundaries": [ - "Explicit close on success/refusal; live and writable offline pre/post reads remain WAL-aware mode=ro/query_only. Immutable only independently established frozen closed-main with recorded digest/size/private single-link identity, zero/absent WAL and unchanged main/sidecars before/after; stale private SHM retained.", - "Original journal and sanitized original backup receipt exact hashes/identity/phase states/attempts linked before and after each phase; never rewrite COMPLETE/UNCERTAIN history.", - "New drill-copy-rescue-r1 and separate nonce/lock/journal, absent destination/restore volume, original Pod UID/old image/source pin and unchanged Deployment/CM/PVC seals; no new drill backup, old phase retry or sidecar deletion.", - "Offline verification explicitly consumes COMPLETE rescue result; remaining ordered new-journal phases retain protected clone pre/admin/post proof, fresh cutover snapshot and current-DB-preserving Deployment-only release/recovery.", - "Source memo version2 is revised via expected_version1, not registered as new version1; its new review memo independently starts version1, so nested confirmed founder disposition must target that NEW review ID/version1. Root must authenticate source outcome query version2; original version1 approval is not recovery permission.", - "Existing image763c/config6c/schema5/PVC/audience/credentials/policy/sync scope and known fixable MEDIUM/T05 disclosure retained; native_provider=false synthetic checks remain distinct from actual native provider completion." - ], - "remaining_gates": [ - "Root applies/commits exact prepared source then pins final owner revision/four Git blobs; current owner HEAD remains unchanged during review.", - "New exact founder acceptance of source memo2/new reviewID/current bound packet with fresh actual fixed-origin readback before every phase; pending templates/source review are not authority.", - "Fresh actual snapshot/sidecar/custody/source/health checks and durable new RUNNING intent precede any copy; failures preserve partial evidence and forbid same-phase retry.", - "All-phase deadline remainsOct12; beyond expiry fresh decision required. Actual protected copy/offline native-data drill/rollout/provider acceptance remain unexecuted." - ], - "primary_semantics_sources": [ - "https://www.sqlite.org/uri.html", - "https://docs.python.org/3/library/sqlite3.html#how-to-use-the-connection-context-manager" - ], - "limits": { - "native_database_open": false, - "native_copy_backup_restore": false, - "native_retry_or_cleanup": false, - "owner_head_mutation": false, - "founder_approval": false, - "recovered_native_success": false, - "T04_complete": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/unready-rollback-probe.json b/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/unready-rollback-probe.json deleted file mode 100644 index 328ac77..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-actuator-owner-review/unready-rollback-probe.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "synthetic_transport_only": true, - "native_action": false, - "unready_same_schema_candidate_rollback_accepted": false, - "rollback_patches_requested": 0, - "unready_candidate_exec_calls": 0, - "rollback_fail_class": "ValueError" -} diff --git a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/conditional-review.json b/docs/evidence/2026-10-11-infd-purpose-release-owner-review/conditional-review.json deleted file mode 100644 index 8a1dd4d..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/conditional-review.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-PURPOSE-R1-CONDITIONAL", - "work_record": "RPF-IN-0005", - "reviewer_role": "railiance-platform technical owner / GitOps and package binding", - "status": "changes-required", - "reviewed_manifest_sha256": "7c6738b39ea8e54e61c8ed625e94ad0b2755e672e1576efcdf84a1d665ac3300", - "reviewed_paths": 36, - "candidate_image": "sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "published_source": "3e4bcb26f8eeaa8c64345c565477b5ac8ccbb168", - "verified": { - "manifest_all_bytes": true, - "bound_document_digests": 20, - "bound_document_bytes": 249719, - "deployment_candidate_rollback_changed_paths": [ - "/spec/template/spec/containers/0/image" - ], - "guard_test_methods_passed": 5, - "synthetic_main_only_backup_clone_schema5_integrity_counts": true - }, - "required_refinements": [ - { - "id": "RPF-INFD-R1-01", - "issue": "Admin Store initialization migrates schemas1-4; post-open schema5 cannot establish no migration", - "required": "Before any admin/Store open, read-only SQLite schema5 plus integrity and bounded table/state baseline; validate frozen snapshot and offline copy before and after inspection. Exact offline command must override service entrypoint. Wrong pre-open schema stops without migration." - }, - { - "id": "RPF-INFD-R1-02", - "issue": "Assertions disappear under optimization, permitting wrong Argo UID and Ready0", - "required": "Explicit local and remote sys.flags.optimize refusal before assertion-based checks, or explicit non-assert guards. Regression must show optimized execution refuses before remote/native operations." - }, - { - "id": "RPF-INFD-R1-03", - "issue": "Deployment references pin names but not actual ConfigMap/PVC identities", - "required": "Freeze and refresh nonsecret runtime ConfigMap UID/immutable/canonical-data hash and PVC UID/volumeName/bound custody metadata. Reject same-name recreation or data/policy drift without reading Secrets." - }, - { - "id": "RPF-INFD-R1-04", - "issue": "Rollback same counts wording can imply frozen-count equality despite allowed live commits", - "required": "Clarify exact equality applies isolated restored snapshot; live rollback retains and reconciles latest evidence and allows legitimate commits, with no replay or snapshot overwrite." - } - ], - "authority_limits": { - "human_approval": false, - "native_backup_export_restore": false, - "native_workload_change": false, - "native_pdp_proof": false, - "new_role_or_autonomy_grant": false - }, - "risk_assessment": "Published scan has one inherited fixable MEDIUM explicitly requested, not a clean scan or risk grade; T05 remains owner follow-through due2026-10-18. Recreate and image rollback preserve same-schema latest DB and unresolved external effects." -} diff --git a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/final-review.json b/docs/evidence/2026-10-11-infd-purpose-release-owner-review/final-review.json deleted file mode 100644 index b3ed87f..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/final-review.json +++ /dev/null @@ -1,45 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-PURPOSE-R1-FINAL", - "work_record": "RPF-IN-0005", - "reviewer_role": "railiance-platform technical owner / GitOps and package binding", - "status": "technical-agreement", - "reviewed_manifest_sha256": "b1e3e66bc4023dc80c6ea59564c3557c7653fb3c307f46a049997b76198a9033", - "reviewed_paths": 78, - "candidate_image": "sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "published_source": "3e4bcb26f8eeaa8c64345c565477b5ac8ccbb168", - "prior_conditional_review": "RPF-REVIEW-20261011-INFD-PURPOSE-R1-CONDITIONAL", - "original_manifest_sha256": "7c6738b39ea8e54e61c8ed625e94ad0b2755e672e1576efcdf84a1d665ac3300", - "verification": { - "all_78_source_hashes_match": true, - "bound_documents": 20, - "bound_document_bytes": 184912, - "all_bound_digests_match": true, - "maker_test_methods_independently_passed": 11, - "additional_independent_methods_passed": 4, - "additional_dependency_drift_negatives": 8, - "actual_owner_schema4_fixture_refused_without_migration_or_main_file_change": true, - "actual_owner_schema5_fixture_counts_match": true, - "symlink_database_refused": true - }, - "resolved": [ - "Stdlib mode=ro/query_only read transaction requires schema5 and integrity before Store/admin open, both native backup source and isolated candidate restored copy; wrong schemas stop before migration.", - "Both local and remote optimization refusals execute before assertion-based checks or transport/native dispatch.", - "Fresh dependency check seals actual immutable ConfigMap UID/metadata/canonical data checksum and PVC UID/fullspec/volumeName/Bound custody; recreations and drift refuse. No Secret reads.", - "Exact offline entrypoint is python checker then informed-decision-admin inspect, networknone/read-only image/no runtime tokens or config. Frozen counters compare offline only; live writes remain preserved and reconciled through same-schema DB-retaining image rollback." - ], - "agreed_scope": "Prepared exact founder proposal for protected consistent online snapshot/copy/offline restore gates, fresh cutover snapshot, then exact owner-source manual Argo Deployment-only image sync prune=false and same-schema DB-retaining rollback. Candidate image/config/policy/credential/PVC/resource scope remains unchanged.", - "retained_native_gates": [ - "Exact confirmed founder disposition on final bound packet before any native custody/runtime action; config-only prior approval not reused.", - "Fresh sealed source/live/dependency/admission and capacity/custody checks before operation.", - "Real consistent protected backup plus actual-data isolated restore proof before cutover; synthetic tests are not native proof.", - "Fresh cutover snapshot, actual rollout/readiness/image/module and installed purpose/native binding checks; retain uncertain outcomes and private originals.", - "Current known inherited fixable MEDIUM is explicitly requested and retained with T05 owner follow-through due2026-10-18; no clean scan/risk grade/autonomy inference." - ], - "authority_limits": { - "human_approval": false, - "native_backup_export_restore": false, - "native_workload_change": false, - "native_pdp_proof": false, - "T04_complete": false - } -} diff --git a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/optimization-findings.json b/docs/evidence/2026-10-11-infd-purpose-release-owner-review/optimization-findings.json deleted file mode 100644 index aee5ded..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/optimization-findings.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "synthetic_transport_only": true, - "native_action": false, - "optimize_0_wrong_uid_and_unready_passed": false, - "optimize_1_wrong_uid_and_unready_passed": true -} diff --git a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/packet-findings.json b/docs/evidence/2026-10-11-infd-purpose-release-owner-review/packet-findings.json deleted file mode 100644 index 00324ce..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/packet-findings.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "manifest_sha256": "7c6738b39ea8e54e61c8ed625e94ad0b2755e672e1576efcdf84a1d665ac3300", - "manifest_paths": 36, - "all_manifest_paths_match": true, - "candidate_rollback_changed_paths": [ - "/spec/template/spec/containers/0/image" - ], - "bound_documents": 20, - "bound_document_bytes": 249719, - "all_bound_digests_match": true, - "native_actions": false -} diff --git a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/probe_preflight_optimization.py b/docs/evidence/2026-10-11-infd-purpose-release-owner-review/probe_preflight_optimization.py deleted file mode 100644 index 2fd467c..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/probe_preflight_optimization.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Independent isolated probe; fake subprocess transport, no SSH/native calls.""" -import ast,copy,contextlib,io,json,pathlib,subprocess,unittest.mock -root=pathlib.Path('/tmp/infd-purpose-release-stage/informed-decision/docs/evidence/2026-10-11-purpose-image-release') -tree=ast.parse((root/'preflight.py').read_text()) -program=next(node.value.value for node in tree.body if isinstance(node,ast.Assign) and any(isinstance(t,ast.Name) and t.id=='program' for t in node.targets) and isinstance(node.value,ast.Constant)) -sealed=json.loads((root/'sealed-live-deployment.json').read_text());live=copy.deepcopy(sealed);live['status']={'readyReplicas':0,'updatedReplicas':0} -admitted=copy.deepcopy(sealed);admitted['spec']['template']['spec']['containers'][0]['image']='forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a' -argo=json.loads((root/'native-preflight.json').read_text())['argo'];badargo=copy.deepcopy(argo);badargo['uid']='wrong-owner-uid' -app={'metadata':{'uid':badargo['uid']},'spec':{'source':{k:badargo[k] for k in ('repoURL','targetRevision','path')},'destination':badargo['destination']},'status':{'sync':badargo['sync'],'health':badargo['health']}} -for marker,value in [('CANDIDATE_LITERAL',(root/'candidate-deployment.json').read_text()),('SEALED_LITERAL',json.dumps(sealed)),('GUARD_LITERAL',(root/'change_guard.py').read_text()),('ARGO_LITERAL',json.dumps(argo))]:program=program.replace(marker,repr(value)) -def fake(args,**kwargs): - if args[1:3]==['get','deployment']:value=live - elif args[1:3]==['get','application']:value=app - elif args[1:3]==['get','nodes']:value={'items':[{'status':{'allocatable':{'cpu':'4'}}}]} - elif args[1]=='apply':value=admitted - elif args[1]=='describe':return subprocess.CompletedProcess(args,0,'Allocated resources:\n cpu 3460m\nEvents:','') - elif args[1]=='diff':return subprocess.CompletedProcess(args,1,'synthetic-diff','') - else:raise RuntimeError('unrecognized fake call') - return subprocess.CompletedProcess(args,0,json.dumps(value),'') -result={'synthetic_transport_only':True,'native_action':False} -for optimize in (0,1): - out=io.StringIO() - try: - with unittest.mock.patch('subprocess.run',fake),contextlib.redirect_stdout(out):exec(compile(program,'sealed-preflight-synthetic','exec',optimize=optimize),{}) - except AssertionError:passed=False - else:passed=json.loads(out.getvalue())['all_checks_pass'] is True - result['optimize_'+str(optimize)+'_wrong_uid_and_unready_passed']=passed -pathlib.Path('/tmp/infd-platform-owner-review-stage/optimization-findings.json').write_text(json.dumps(result,indent=2)+'\n');print(json.dumps(result)) diff --git a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/refined_independent_probes.py b/docs/evidence/2026-10-11-infd-purpose-release-owner-review/refined_independent_probes.py deleted file mode 100644 index 834a954..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/refined_independent_probes.py +++ /dev/null @@ -1,31 +0,0 @@ -"""Independent platform review regressions; synthetic databases and objects only.""" -import copy,hashlib,importlib.util,json,pathlib,sqlite3,sys,tempfile,unittest -ROOT=pathlib.Path('/tmp/infd-purpose-release-stage/informed-decision/docs/evidence/2026-10-11-purpose-image-release') -def load(name): - spec=importlib.util.spec_from_file_location(name,ROOT/(name+'.py'));mod=importlib.util.module_from_spec(spec);spec.loader.exec_module(mod);return mod -G=load('change_guard');Q=load('sqlite_readonly') -class Independent(unittest.TestCase): - def test_additional_dependency_drift(self): - sealed=json.loads((ROOT/'sealed-native-dependencies.json').read_text());cm=copy.deepcopy(sealed['configmap']);cm.pop('canonical_data_sha256');service=json.loads((ROOT.parents[2]/'deploy/lifecycle/service.candidate.json').read_text());cm['data']=next(x for x in service['items'] if x['kind']=='ConfigMap')['data'];pvc=copy.deepcopy(sealed['pvc']);pvc['status']={'phase':'Bound'};G.check_dependencies(cm,pvc,sealed) - changes=[lambda c,p:c['metadata'].update(namespace='wrong'),lambda c,p:c['metadata'].update(ownerReferences=[{'uid':'foreign'}]),lambda c,p:c['data'].update(extra='new'),lambda c,p:p['metadata'].update(namespace='wrong'),lambda c,p:p['metadata'].update(ownerReferences=[{'uid':'foreign'}]),lambda c,p:p['spec'].update(storageClassName='foreign'),lambda c,p:p['spec'].update(accessModes=['ReadWriteMany']),lambda c,p:p['spec']['resources']['requests'].update(storage='100Gi')] - for change in changes: - c,p=copy.deepcopy(cm),copy.deepcopy(pvc);change(c,p) - with self.assertRaises(ValueError):G.check_dependencies(c,p,sealed) - def test_real_schema4_refuses_without_migration(self): - sys.path.insert(0,'/home/worsch/informed-decision');from informed_decision.store import Store - with tempfile.TemporaryDirectory() as d: - p=pathlib.Path(d)/'fixture.sqlite';Store(p) - db=sqlite3.connect(p);db.execute('DROP INDEX lifecycle_review_snapshot');db.execute('DROP INDEX lifecycle_review_revision');db.execute('PRAGMA user_version=4');db.commit();db.close() - before=hashlib.sha256(p.read_bytes()).hexdigest() - with self.assertRaises(ValueError):Q.inspect_readonly(p) - self.assertEqual(hashlib.sha256(p.read_bytes()).hexdigest(),before) - db=sqlite3.connect(p);self.assertEqual(db.execute('PRAGMA user_version').fetchone()[0],4);db.close() - def test_real_schema5_counts_match_owner(self): - sys.path.insert(0,'/home/worsch/informed-decision');from informed_decision.store import Store - with tempfile.TemporaryDirectory() as d: - p=pathlib.Path(d)/'fixture.sqlite';s=Store(p);proof=Q.inspect_readonly(p);self.assertEqual(proof['lifecycle'],s.lifecycle_counts());self.assertEqual(proof['schema_version'],5) - def test_link_database_refused(self): - with tempfile.TemporaryDirectory() as d: - p=pathlib.Path(d)/'fixture.sqlite';p.touch();q=pathlib.Path(d)/'link.sqlite';q.symlink_to(p) - with self.assertRaises(ValueError):Q.inspect_readonly(q) -if __name__=='__main__':unittest.main() diff --git a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/synthetic-findings.json b/docs/evidence/2026-10-11-infd-purpose-release-owner-review/synthetic-findings.json deleted file mode 100644 index be3102b..0000000 --- a/docs/evidence/2026-10-11-infd-purpose-release-owner-review/synthetic-findings.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "synthetic_only": true, - "native_data_used": false, - "main_only_clone_integrity_ok": true, - "main_only_clone_documents": 1, - "main_only_clone_schema": 5, - "backup_wal_exists_after_close": false, - "schema4_admin_store_open_became_schema5": true, - "schema4_bytes_changed": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/mapping.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/mapping.json deleted file mode 100644 index 61a920b..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/mapping.json +++ /dev/null @@ -1,82 +0,0 @@ -[ - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-probes.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-probes.json", - "sha256": "72c2f3d87a45d8a3b77fe0a06dc6c793003e2ee351574b42829a546450f70783" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-review.json", - "sha256": "30c685422c4608b045c7027c795033ecfda12ecfab9df7a09f6e7f4f5bb0d898" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_overlay_probes.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_overlay_probes.py", - "sha256": "ed5f303cb063230ba20806b2be3b99d6249e6f62bfca6af0780894fd7b697154" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/mapping.json", - "sha256": "b8d8b9abe82ec5c19023fb9e7af289bbd2b591e079b55ae5a785aca7c5e3c337" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/original-manifest.json", - "sha256": "380c6172adc36b5907ba5384d14d799e49eda410732dfce1878fbe6db184ff0c" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "sha256": "8597be8f8a861705f3dd4d054506d160b1b16b9b16a687a63505eaf067ebeee5" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "sha256": "1bcff9cf3f073b6053a5ae20d09e9c70dc4da30e0d9ec1520d426a116c8e1a34" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "sha256": "1b95bd1d10d0775b73ba71e10b1ad6396f8c1720a5b52a26111f1db6f4e9ee28" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "sha256": "76c1d2f2b060da9ff06d3627a33b2b7007a7818f0c1472f50d26778fcafd7547" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/artifact-comparison.json", - "sha256": "689fa7cbd322cb31b215fd2e86e0be267c1398f9b71e5615446d5f7b82dcce89" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/audit-refinement.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/audit-refinement.json", - "sha256": "020514b7514999699512fb338d89630e7eaf51a739e68bf77342eeefe8ad507f" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/source-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/source-manifest.json", - "sha256": "64509029b3de6d33e3675587663ccdae027d9ea18aba83750892480766dd03b8" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/test_artifact_audit.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/test_artifact_audit.py", - "sha256": "e9c5a628e40acc3ae241bb3f3bf9c2846c064601761a11aecce81ba4803c8f71" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/verify_local_artifacts.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/verify_local_artifacts.py", - "sha256": "53778a2c3c89aee4c1256ed7e5896db033d960c6345d490620e4f06d03268794" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029-whiteout-probe.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029-whiteout-probe.json", - "sha256": "f450cd2b29b818782d432d010dc0221564d500b61df15413add203c09cdaa19e" - }, - { - "original_path": "intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "sha256": "7b3f5340c833e6e6641741044369b0251dfe31bb5bf6c1719d07821226a51376" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/original-manifest.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/original-manifest.json deleted file mode 100644 index 1b51ad9..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/original-manifest.json +++ /dev/null @@ -1,114 +0,0 @@ -[ - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-probes.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-probes.json", - "original_sha256": null, - "staged_sha256": "72c2f3d87a45d8a3b77fe0a06dc6c793003e2ee351574b42829a546450f70783" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-review.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-review.json", - "original_sha256": null, - "staged_sha256": "30c685422c4608b045c7027c795033ecfda12ecfab9df7a09f6e7f4f5bb0d898" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_overlay_probes.py", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_overlay_probes.py", - "original_sha256": null, - "staged_sha256": "ed5f303cb063230ba20806b2be3b99d6249e6f62bfca6af0780894fd7b697154" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/mapping.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/mapping.json", - "original_sha256": null, - "staged_sha256": "b8d8b9abe82ec5c19023fb9e7af289bbd2b591e079b55ae5a785aca7c5e3c337" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/original-manifest.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/original-manifest.json", - "original_sha256": null, - "staged_sha256": "380c6172adc36b5907ba5384d14d799e49eda410732dfce1878fbe6db184ff0c" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "original_sha256": null, - "staged_sha256": "8597be8f8a861705f3dd4d054506d160b1b16b9b16a687a63505eaf067ebeee5" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "original_sha256": null, - "staged_sha256": "1bcff9cf3f073b6053a5ae20d09e9c70dc4da30e0d9ec1520d426a116c8e1a34" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "original_sha256": null, - "staged_sha256": "1b95bd1d10d0775b73ba71e10b1ad6396f8c1720a5b52a26111f1db6f4e9ee28" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "original_sha256": null, - "staged_sha256": "76c1d2f2b060da9ff06d3627a33b2b7007a7818f0c1472f50d26778fcafd7547" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "689fa7cbd322cb31b215fd2e86e0be267c1398f9b71e5615446d5f7b82dcce89" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/audit-refinement.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/audit-refinement.json", - "original_sha256": null, - "staged_sha256": "020514b7514999699512fb338d89630e7eaf51a739e68bf77342eeefe8ad507f" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/source-manifest.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/source-manifest.json", - "original_sha256": null, - "staged_sha256": "64509029b3de6d33e3675587663ccdae027d9ea18aba83750892480766dd03b8" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/test_artifact_audit.py", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/test_artifact_audit.py", - "original_sha256": null, - "staged_sha256": "e9c5a628e40acc3ae241bb3f3bf9c2846c064601761a11aecce81ba4803c8f71" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/verify_local_artifacts.py", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/verify_local_artifacts.py", - "original_sha256": null, - "staged_sha256": "53778a2c3c89aee4c1256ed7e5896db033d960c6345d490620e4f06d03268794" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029-whiteout-probe.json", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029-whiteout-probe.json", - "original_sha256": null, - "staged_sha256": "f450cd2b29b818782d432d010dc0221564d500b61df15413add203c09cdaa19e" - }, - { - "repo": "railiance-platform", - "path": "intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "staged_path": "/tmp/infd-zlib-platform-final-review-stage/railiance-platform/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "original_sha256": null, - "staged_sha256": "7b3f5340c833e6e6641741044369b0251dfe31bb5bf6c1719d07821226a51376" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-probes.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-probes.json deleted file mode 100644 index ab1716f..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-probes.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "source_manifest_sha256": "3e41ab391cda26f09a556fecef8a17c2759ef2899e43eb136b25ef4b3fde23a0", - "maker_tests_independently_passed": 8, - "independent_synthetic_probes": { - "OO_refusal_before_read": true, - "root_directory_removed_refused": true, - "root_opaque_refused": true, - "nested_opaque_parent_preserved_descendant_refused": true, - "nested_removed_directory_refused": true - }, - "all66_hashes_match": true, - "initial27_exact_archive_verified": true, - "actual_candidate_changed_by_refinement": false, - "native_actions": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-review.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-review.json deleted file mode 100644 index f60b462..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-source-review.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-ZLIB-CANDIDATE-R2", - "work_record": "RPF-IN-0008", - "role": "railiance-platform technical owner only", - "status": "technical-agreement-source-candidate-preparation-only", - "source_manifest_sha256": "3e41ab391cda26f09a556fecef8a17c2759ef2899e43eb136b25ef4b3fde23a0", - "source_paths": 66, - "initial_conditional_review_manifest_sha256": "380c6172adc36b5907ba5384d14d799e49eda410732dfce1878fbe6db184ff0c", - "intermediate_source_manifest_sha256": "64509029b3de6d33e3675587663ccdae027d9ea18aba83750892480766dd03b8", - "history": "Exact initial four owner files plus original manifest/map archived durably at initial-380c6172; intermediate645 checker/test/proof/source-manifest and independent failed reason retained. Same completed IN8 anchor precedes review.", - "refinements_verified": [ - "Optimization refused before artifact reads/output under -O and independently -OO.", - "Directory mode/UID/GID included; drift negatives pass.", - "Leading dot whiteout names preserved; root/nested deletion and opaque boundaries refuse fixture drift for correct effective paths.", - "Existing source/package provenance, exact763c base, four-file context and bounded existing owner publication/credential routes unchanged." - ], - "actual_local_candidate": { - "daemon_image_id": "sha256:78bfdcc00a62367ae6190f6569a02b18abf9a943f760e4ac8fb946703348af31", - "registry_digest": null, - "context_sha256": "c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d", - "effective_changed_files": [ - "lib/apk/db/installed", - "usr/lib/libz.so.1.3.2" - ], - "directories": 313, - "directory_metadata_delta": {}, - "nine_base_layers_preserved": true, - "runtime_policy_module_unchanged": true, - "fullscan_same_snapshot_baseline_findings": 1, - "fullscan_same_snapshot_local_candidate_findings": 0, - "raw_runtime_config_bytes_identical": false, - "closed_default_normalization_semantics_unchanged": true, - "initial_or_intermediate_candidate_failed": false - }, - "validation": { - "source_manifest_sha256": "3e41ab391cda26f09a556fecef8a17c2759ef2899e43eb136b25ef4b3fde23a0", - "maker_tests_independently_passed": 8, - "independent_synthetic_probes": { - "OO_refusal_before_read": true, - "root_directory_removed_refused": true, - "root_opaque_refused": true, - "nested_opaque_parent_preserved_descendant_refused": true, - "nested_removed_directory_refused": true - }, - "all66_hashes_match": true, - "initial27_exact_archive_verified": true, - "actual_candidate_changed_by_refinement": false, - "native_actions": false - }, - "remaining_gates": [ - "Actual owner CI source-pinned publication and immutable registry descriptor/layer/config/source readback.", - "Actual published full unsuppressed pinned scan/SBOM/runtime/purpose proof; local artifact zero does not extend to a different artifact.", - "Concrete separately reviewed native target/data-retaining protocol and fresh founder disposition; old purpose R2 approval does not authorize zlib release." - ], - "scope_limits": { - "owner_source_write": false, - "registry_publish": false, - "native_workload_changed": false, - "database_access": false, - "credentials_read": false, - "human_native_approval": false, - "T05_complete": false, - "other_owner_role": false - }, - "checker_scope": "Agreement concerns this exact bounded candidate and challenged proof boundary; does not certify a general-purpose OCI extraction or signature verifier." -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_overlay_probes.py b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_overlay_probes.py deleted file mode 100644 index 78c0c7e..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_overlay_probes.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Synthetic public-artifact boundary probes; no native/provider/database use.""" -import importlib.util,json,subprocess,sys,tempfile -from pathlib import Path -root=Path(sys.argv[1]) -spec=importlib.util.spec_from_file_location("fixtures",root/"test_artifact_audit.py") -f=importlib.util.module_from_spec(spec);spec.loader.exec_module(f) -def layer(entries): - b=f.io.BytesIO() - with f.tarfile.open(fileobj=b,mode="w") as t: - for name,data in entries:f.add(t,name,data) - return b.getvalue() -results={} -with tempfile.TemporaryDirectory() as tmp: - r=subprocess.run([sys.executable,"-OO",str(f.CHECKER),str(Path(tmp)/"absent")],capture_output=True,text=True) - results["OO_refusal_before_read"]=r.returncode!=0 and "REFUSED: optimized Python" in r.stderr and "FileNotFoundError" not in r.stderr -for name,base,overlay,expected in [ - ("removed_directory",[("usr",None)],[(".wh.usr",b"")],"usr"), - ("root_opaque",[("usr",None)],[(".wh..wh..opq",b"")],"usr"), - ("nested_opaque",[("usr",None),("usr/inner",None)],[("usr/.wh..wh..opq",b"")],"usr/inner"), - ("nested_removed_directory",[("usr",None),("usr/inner",None)],[("usr/.wh.inner",b"")],"usr/inner"), -]: - with tempfile.TemporaryDirectory() as tmp: - p=Path(tmp);f.fixture(p);b=layer(base) - f.archive(p/"base-image.tar",[b],["base"]) - f.archive(p/"image.tar",[b,layer(overlay),layer([])],["base","overlay","cleanup"]) - r=subprocess.run([sys.executable,str(f.CHECKER),str(p)],capture_output=True,text=True) - results[name]=r.returncode!=0 and repr({expected}) in r.stderr and not (p/"artifact-comparison.json").exists() -print(json.dumps(results,sort_keys=True)) -if not all(results.values()):raise SystemExit(1) diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/mapping.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/mapping.json deleted file mode 100644 index 3acb21e..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/mapping.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "sha256": "8597be8f8a861705f3dd4d054506d160b1b16b9b16a687a63505eaf067ebeee5" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "sha256": "1bcff9cf3f073b6053a5ae20d09e9c70dc4da30e0d9ec1520d426a116c8e1a34" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "sha256": "1b95bd1d10d0775b73ba71e10b1ad6396f8c1720a5b52a26111f1db6f4e9ee28" - }, - { - "original_path": "intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "sha256": "76c1d2f2b060da9ff06d3627a33b2b7007a7818f0c1472f50d26778fcafd7547" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/original-manifest.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/original-manifest.json deleted file mode 100644 index 576b7c8..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/original-manifest.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "staged_path": "/tmp/infd-zlib-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json", - "original_sha256": null, - "staged_sha256": "8597be8f8a861705f3dd4d054506d160b1b16b9b16a687a63505eaf067ebeee5" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "staged_path": "/tmp/infd-zlib-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json", - "original_sha256": null, - "staged_sha256": "1bcff9cf3f073b6053a5ae20d09e9c70dc4da30e0d9ec1520d426a116c8e1a34" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "staged_path": "/tmp/infd-zlib-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json", - "original_sha256": null, - "staged_sha256": "1b95bd1d10d0775b73ba71e10b1ad6396f8c1720a5b52a26111f1db6f4e9ee28" - }, - { - "repo": "railiance-platform", - "path": "intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "staged_path": "/tmp/infd-zlib-platform-review-stage/railiance-platform/intakes/RPF-IN-0008-infd-zlib-candidate-review.md", - "original_sha256": null, - "staged_sha256": "76c1d2f2b060da9ff06d3627a33b2b7007a7818f0c1472f50d26778fcafd7547" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json deleted file mode 100644 index 497b529..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/conditional-source-review.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-ZLIB-CANDIDATE-R1", - "work_record": "RPF-IN-0008", - "role": "railiance-platform technical owner only", - "status": "narrow-proof-helper-refinement-requested", - "manifest_sha256": "8d345bb2bde3efce4d0cc99dfc2f883a64f79c42d2cd4abb288229a995e9ad66", - "paths": 27, - "all_manifest_hashes_match": true, - "all13_local_evidence_hashes_match": true, - "source_test_methods_independently_passed": 4, - "actual_local_scan_base": "one MEDIUM CVE-2026-85091", - "actual_local_scan_candidate": "zero findings all severities stated DB snapshot", - "independent_directory_metadata_delta": {}, - "sound_boundaries": [ - "Exact763c current purpose base; signed official package/index hashes verified in offline named package upgrade against base keys, no allow-untrusted, no broader APK/Python/base refresh.", - "Current local final regular files differ only libz and APK installed DB; independent local export analysis confirms no directory metadata delta. Nine base layers/app36/policy9d/runtime/keys/world/Python/OpenSSL retained, closed serialization default caveat visible.", - "Workflow fixed owner/exactGITHUB_SHA+publicsourcearchive/fourcontext hash checks, existing admitted registry capability passwordstdin/private Dockerconfig cleanup; no borrowedcredential/newgrant/no nativeKubernetes or database action.", - "Workflow publishes candidate only; post-CI actual immutable registry descriptor/layer/config/source proof and fullscan/SBOM remain mandatory before separate nativefounderproposal. LocalimageID78bfdcc/config625d notregistrydigest and localzero doesnotextendactualCIartifact.", - "Current native763c remains legitimate inheritedMEDIUM/manualT05dueOct18, purposeR2approval/currentDB/journals immutable and not reused for remediation." - ], - "required_refinement": { - "source": "docs/evidence/2026-10-11-zlib-remediation-candidate/verify_local_artifacts.py", - "issue": "Seven assertion gates disappear with optimization; generated effective runtime / scan-zero claims can remain unconditional.", - "required": "Explicit sys.flags.optimize refusal before artifact archive/JSON/output IO (or non-assert guards), with regression proving no evidence output under -O. Include directory metadata in reusable full filesystem proof or retain explicit independent supplemental proof before actual published artifact approval." - }, - "publication_scan_boundary": "CI does not itself perform pinned fullscan/SBOM before push. This is adequately disclosed for source-only candidate publication; no clean published artifact/native release is acknowledged by this review. Source guard and actual artifact proof must be valid before any native proposal.", - "scope_limits": { - "candidate_publication_executed": false, - "native_changes": false, - "database_access": false, - "credentials_read": false, - "owner_source_write": false, - "human_native_release_approval": false, - "T05_complete": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json deleted file mode 100644 index d21a1d7..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/directory-metadata-probe.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "public_local_exports_only": true, - "native_action": false, - "directory_metadata_changes": {} -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json deleted file mode 100644 index 2f8b67b..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/source-review-probes.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "manifest_sha256": "8d345bb2bde3efce4d0cc99dfc2f883a64f79c42d2cd4abb288229a995e9ad66", - "paths": 27, - "all_manifest_hashes_match": true, - "all13_local_evidence_hashes_match": true, - "actual_raw_scan_findings": { - "base-trivy-full.json": [ - [ - "CVE-2026-85091", - "MEDIUM" - ] - ], - "trivy-full.json": [] - }, - "artifact_helper_assert_count": 7, - "artifact_helper_optimized_code_keeps_assertion_errors": false, - "local_candidate_registry_digest": null, - "publication_or_native_action": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md deleted file mode 100644 index bf92fa2..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/initial-380c6172/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: RPF-IN-0008 -kind: intake -type: intake -title: "Platform source review of separate INFD zlib candidate publication" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009-T05 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -Bounded new Platform review of source candidate manifest `8d345bb2bde3efce4d0cc99dfc2f883a64f79c42d2cd4abb288229a995e9ad66`: official zlib package/index provenance, exact current763c base and minimal layer/runtime changes, local scan/SBOM proof, source-pinned ownerCI publication/credential boundaries. Distinct from completed purpose-actuator RPF-IN-0007 and its approvedR2 trial. No native rollout/data action, owner source write, registry publish or carried-forward human approval. Native763c still has disclosed inheritedMEDIUM/T05 dueOct18; source candidate is unpublished. This owner work record precedes formal review. - -## Completed initial source review - -Initial technical review `RPF-REVIEW-20261011-INFD-ZLIB-CANDIDATE-R1` validates exact27-path8d345 source/localproofs and publication owner boundary. Source proof helper must refuse optimized Python before artifact IO; directory coverage verified independently for current artifacts and required on actual published readback. Four source methods pass; raw scans confirm base1MED/candidate0 on stated DB. Current local candidate remains unpublished; no native authorization transferred. Review refinement remains in promoted INFD-WP-0009-T05, separate from unpromoted verifier residual and completed IN7. Completed owner work record precedes formal entry. diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029-whiteout-probe.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029-whiteout-probe.json deleted file mode 100644 index b828d2b..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029-whiteout-probe.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "candidate_manifest_sha256": "64509029b3de6d33e3675587663ccdae027d9ea18aba83750892480766dd03b8", - "source_only": true, - "synthetic_fixture_only": true, - "native_actions": false, - "probe": "directory whiteout normalization", - "refused": true, - "correct_effective_removed_directory_reason": false, - "incorrect_dot_stripped_whiteout_reason": true, - "finding": "lstrip dot normalization prevents declared whiteout branch from interpreting rootfs deletion", - "current_local_candidate_failed": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/artifact-comparison.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/artifact-comparison.json deleted file mode 100644 index c99f1fb..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/artifact-comparison.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "base_image_config_id": "sha256:f34de21379c4f50ea0361ca1d8296c8e72f989499ad288016b81204997ae4779", - "candidate_local_image_config_id": "sha256:625d066416e901eaa2db5e18b96c37559b25dd91bdc3afb3d1ff5e916c7a8a3f", - "candidate_registry_digest": null, - "base_layers_preserved": 9, - "new_layers": 2, - "effective_runtime_config_identical": true, - "raw_config_bytes_identical": false, - "serialization_note": "Legacy base explicitly serialized empty/false/null defaults; candidate Docker-save config omits equivalent known defaults. Exact closed default normalization only; runtime fields unchanged.", - "labels_before": { - "org.opencontainers.image.base.digest": "sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173", - "org.opencontainers.image.revision": "3e4bcb26f8eeaa8c64345c565477b5ac8ccbb168", - "social.coulomb.release.scope": "INFD-WP-0009-T04/purpose-overlay-v1", - "social.coulomb.source.policy.sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62" - }, - "labels_after": { - "org.opencontainers.image.base.digest": "sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "org.opencontainers.image.revision": "uncommitted-T05-local-preparation", - "social.coulomb.release.scope": "INFD-WP-0009-T05/zlib-85091-r1", - "social.coulomb.source.context.sha256": "c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d", - "social.coulomb.source.policy.sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62" - }, - "final_changed_files": { - "lib/apk/db/installed": { - "before": [ - "file", - 420, - 0, - 0, - "6383ea3806247e8dbf9ebee8cb30eb8eca63a8ea59662a793127d6a61b988d70" - ], - "after": [ - "file", - 420, - 0, - 0, - "f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001" - ] - }, - "usr/lib/libz.so.1.3.2": { - "before": [ - "file", - 493, - 0, - 0, - "a1e2c03dcdedba98a06a00cadfb67a0e0b1cd93986860ec0c5ce66e789c44e57" - ], - "after": [ - "file", - 493, - 0, - 0, - "ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2" - ] - } - }, - "directory_metadata_compared": true, - "directory_count_base": 313, - "directory_count_candidate": 313, - "directory_metadata_delta": {}, - "sbom_package_count_base": 44, - "sbom_package_count_candidate": 44, - "sbom_only_delta": { - "zlib": [ - "1.3.2-r0", - "1.3.2-r1" - ] - }, - "full_scan_baseline_findings": 1, - "full_scan_candidate_allseverity_findings": 0, - "db_snapshot_same": true, - "native_workload_changed": false, - "candidate_docker_daemon_image_id": "sha256:78bfdcc00a62367ae6190f6569a02b18abf9a943f760e4ac8fb946703348af31", - "candidate_local_image_config_id_kind": "Docker-save OCI export config SHA; distinct from legacy daemon imageID; closed default serialization differs." -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/audit-refinement.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/audit-refinement.json deleted file mode 100644 index 948fb25..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/audit-refinement.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "proposal_id": "INFD-WP-0009-T05/zlib-85091-r1", - "same_trial": true, - "original_manifest_sha256": "8d345bb2bde3efce4d0cc99dfc2f883a64f79c42d2cd4abb288229a995e9ad66", - "original_local_verification_sha256": "c82fa8aab33426fdd6f7730b627d226d3469b9d31c525bca7c28a52ab4847fc1", - "reason": "Platform source-review guard gaps; original actual candidate/build/scan did not fail", - "changes": [ - "Explicit optimized Python refusal before any archive/JSON read", - "Filesystem audit includes final directory mode/UID/GID metadata", - "Three synthetic tests: optimized malformed artifact refuses before read; unchanged directory passes; mode/UID/GID mutation refuses" - ], - "actual_existing_artifact_reaudit": "Passed; no directory metadata differences, same two changed files", - "candidate_image_unchanged": "sha256:78bfdcc00a62367ae6190f6569a02b18abf9a943f760e4ac8fb946703348af31", - "four_file_build_context_unchanged": "c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d", - "registry_publication": false, - "native_changes": false, - "checker_sha256": "53778a2c3c89aee4c1256ed7e5896db033d960c6345d490620e4f06d03268794", - "tests_sha256": "e9c5a628e40acc3ae241bb3f3bf9c2846c064601761a11aecce81ba4803c8f71", - "comparison_sha256": "689fa7cbd322cb31b215fd2e86e0be267c1398f9b71e5615446d5f7b82dcce89", - "verification_sha256": "9ed9bfebae3618936f6b916819949adfb23668fedeb350ae63b43cd2aa6a7d92" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/source-manifest.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/source-manifest.json deleted file mode 100644 index 5c9ebd2..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/source-manifest.json +++ /dev/null @@ -1,408 +0,0 @@ -[ - { - "repo": "informed-decision", - "path": ".forgejo/workflows/zlib-overlay-image.yaml", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/.forgejo/workflows/zlib-overlay-image.yaml", - "original_sha256": null, - "staged_sha256": "789ef77c30ee55a2e4ef2b7edbe781bb2e92056ac11f5f3b9328ce0dfec38e72" - }, - { - "repo": "informed-decision", - "path": "deploy/zlib-overlay/.dockerignore", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/deploy/zlib-overlay/.dockerignore", - "original_sha256": null, - "staged_sha256": "ce3fa53cfe2c963c7fec4f2333a8af84a40740f144605a5aea795bcd6ac9eb8a" - }, - { - "repo": "informed-decision", - "path": "deploy/zlib-overlay/APKINDEX.tar.gz", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/deploy/zlib-overlay/APKINDEX.tar.gz", - "original_sha256": null, - "staged_sha256": "24fc2ccb08f3125429a38b31bd6c76a7b394c6236df0975760dc8259e50dfbd5" - }, - { - "repo": "informed-decision", - "path": "deploy/zlib-overlay/Containerfile", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/deploy/zlib-overlay/Containerfile", - "original_sha256": null, - "staged_sha256": "5e4d9b39e37fed95d729503deb0e9c414ae63d2779b02306beaadc94675d4f15" - }, - { - "repo": "informed-decision", - "path": "deploy/zlib-overlay/pins.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/deploy/zlib-overlay/pins.json", - "original_sha256": null, - "staged_sha256": "0bd2c79633030056a89ae7f055020e6398aa79d481587d4b41f0974b29dc77b5" - }, - { - "repo": "informed-decision", - "path": "deploy/zlib-overlay/zlib-1.3.2-r1.apk", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/deploy/zlib-overlay/zlib-1.3.2-r1.apk", - "original_sha256": null, - "staged_sha256": "63aeea03c15a2f9018f81805cfc8aa926bdf5cd68921f22149c2fbb5d0ee9f47" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/README.md", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/README.md", - "original_sha256": null, - "staged_sha256": "4d0b0ed0b41f2098adfa2e626c641360855cd0e717bbd2d7a6c155cee2ae91ec" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "689fa7cbd322cb31b215fd2e86e0be267c1398f9b71e5615446d5f7b82dcce89" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/audit-refinement.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/audit-refinement.json", - "original_sha256": null, - "staged_sha256": "020514b7514999699512fb338d89630e7eaf51a739e68bf77342eeefe8ad507f" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/base-runtime.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/base-runtime.json", - "original_sha256": null, - "staged_sha256": "20aabb9f4846ab4fa6e28e0763d153188da151ee32293366a51b0fdf872a4d24" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/base-sbom.cdx.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/base-sbom.cdx.json", - "original_sha256": null, - "staged_sha256": "bb40bcd500ec91ad16affe116e74357ab4090827dd930e2186dc99988403bc50" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/base-trivy-full.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/base-trivy-full.json", - "original_sha256": null, - "staged_sha256": "1b683afb558fedcc20b921599eca536551296afecbc81ce062c359f1b26cfac6" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-image-inspect.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-image-inspect.json", - "original_sha256": null, - "staged_sha256": "56466225f35cdf23c5540c0cc37c77424755ca42794e81f2c99ccf13840c34de" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/context-source.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/context-source.json", - "original_sha256": null, - "staged_sha256": "cccf41cf766a037f31f9ab29ae57f6b34c00e0237c94a8a0ce61c9c78fd06828" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/download-receipt.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/download-receipt.json", - "original_sha256": null, - "staged_sha256": "847fce8466807e88a54ea5e374c2ac49ffa25e208697e71e94d83a5cc185b40d" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/archive-map.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/archive-map.json", - "original_sha256": null, - "staged_sha256": "91384b270c0462223af1ff66e3ca75db5fce692c56c6bde6df435e6a2a079c5e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/original-manifest.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/original-manifest.json", - "original_sha256": null, - "staged_sha256": "8d345bb2bde3efce4d0cc99dfc2f883a64f79c42d2cd4abb288229a995e9ad66" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/.forgejo/workflows/zlib-overlay-image.yaml", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/.forgejo/workflows/zlib-overlay-image.yaml", - "original_sha256": null, - "staged_sha256": "789ef77c30ee55a2e4ef2b7edbe781bb2e92056ac11f5f3b9328ce0dfec38e72" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/.dockerignore", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/.dockerignore", - "original_sha256": null, - "staged_sha256": "ce3fa53cfe2c963c7fec4f2333a8af84a40740f144605a5aea795bcd6ac9eb8a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/APKINDEX.tar.gz", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/APKINDEX.tar.gz", - "original_sha256": null, - "staged_sha256": "24fc2ccb08f3125429a38b31bd6c76a7b394c6236df0975760dc8259e50dfbd5" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/Containerfile", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/Containerfile", - "original_sha256": null, - "staged_sha256": "5e4d9b39e37fed95d729503deb0e9c414ae63d2779b02306beaadc94675d4f15" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/pins.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/pins.json", - "original_sha256": null, - "staged_sha256": "0bd2c79633030056a89ae7f055020e6398aa79d481587d4b41f0974b29dc77b5" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/zlib-1.3.2-r1.apk", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/deploy/zlib-overlay/zlib-1.3.2-r1.apk", - "original_sha256": null, - "staged_sha256": "63aeea03c15a2f9018f81805cfc8aa926bdf5cd68921f22149c2fbb5d0ee9f47" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/README.md", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/README.md", - "original_sha256": null, - "staged_sha256": "cc53d16b5dc8375be0d5f63b1144f0a422477e3a5638c397a214275d7ea1d0cb" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "8d84bfb20cc6c64c85b4b6fcb76d7e1113ebbfcadcd4d5c54ffb4121d73e4dfa" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/base-runtime.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/base-runtime.json", - "original_sha256": null, - "staged_sha256": "20aabb9f4846ab4fa6e28e0763d153188da151ee32293366a51b0fdf872a4d24" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/base-sbom.cdx.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/base-sbom.cdx.json", - "original_sha256": null, - "staged_sha256": "bb40bcd500ec91ad16affe116e74357ab4090827dd930e2186dc99988403bc50" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/base-trivy-full.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/base-trivy-full.json", - "original_sha256": null, - "staged_sha256": "1b683afb558fedcc20b921599eca536551296afecbc81ce062c359f1b26cfac6" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-image-inspect.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-image-inspect.json", - "original_sha256": null, - "staged_sha256": "56466225f35cdf23c5540c0cc37c77424755ca42794e81f2c99ccf13840c34de" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/context-source.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/context-source.json", - "original_sha256": null, - "staged_sha256": "cccf41cf766a037f31f9ab29ae57f6b34c00e0237c94a8a0ce61c9c78fd06828" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/download-receipt.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/download-receipt.json", - "original_sha256": null, - "staged_sha256": "847fce8466807e88a54ea5e374c2ac49ffa25e208697e71e94d83a5cc185b40d" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/local-candidate-verification.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/local-candidate-verification.json", - "original_sha256": null, - "staged_sha256": "c82fa8aab33426fdd6f7730b627d226d3469b9d31c525bca7c28a52ab4847fc1" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-index-entry.txt", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-index-entry.txt", - "original_sha256": null, - "staged_sha256": "63b6831755f33c4fe7c8d7dd49cf2a60251edfcd45e4555f2731941939cb8d0a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-package-info.txt", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-package-info.txt", - "original_sha256": null, - "staged_sha256": "395e0ee97996853c6cf03f17dfe25b57d109b0432c6faabb585d78e6ac6f534a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/proposal.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/proposal.json", - "original_sha256": null, - "staged_sha256": "a12877b149427713c12e67f2597bf330d32dd0b30464008c0f79fc593f05d894" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/sbom.cdx.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/sbom.cdx.json", - "original_sha256": null, - "staged_sha256": "287a61e1aaf78b58c014726e9bcc1ddc2c46949ac0ee74d53fccae3007eac1bb" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/trivy-full.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/trivy-full.json", - "original_sha256": null, - "staged_sha256": "fd37bc2d502a9f017f673486f0f960433fc0fc12dcd745e5ed8074e9c3b600d9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/verify_local_artifacts.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/docs/evidence/2026-10-11-zlib-remediation-candidate/verify_local_artifacts.py", - "original_sha256": null, - "staged_sha256": "6706f62937a742a9c5cc00c42d6261d67d4f1e675b868e67db70dc0b5be03afa" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/check_build_inputs.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/check_build_inputs.py", - "original_sha256": null, - "staged_sha256": "1bf0219765dbcf5f7e26495ac4d49f240affa21deeb9b06c65e275afd10d2e06" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/check_runtime.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/check_runtime.py", - "original_sha256": null, - "staged_sha256": "eb1fca9a43d3394d652f748cde2e8d28cbe646ad61c40f990c0b8878c522a3be" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/compare_runtime.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/compare_runtime.py", - "original_sha256": null, - "staged_sha256": "545f04844f90d028f5d8bb65fb16f3805ee5735ac687ec28303eb9dbb4195b0d" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/test_candidate.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/tools/zlib_overlay/test_candidate.py", - "original_sha256": null, - "staged_sha256": "68677b91c34101776381abcabffcd983bf2452076f983f32599ab1f7ad177bb9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/initial-8d345bb2/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "original_sha256": null, - "staged_sha256": "af785afbf188184b72563c10813663d44117df7cb7394ecb1b9a25cbdfea67a4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/local-candidate-verification.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/local-candidate-verification.json", - "original_sha256": null, - "staged_sha256": "9ed9bfebae3618936f6b916819949adfb23668fedeb350ae63b43cd2aa6a7d92" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-index-entry.txt", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-index-entry.txt", - "original_sha256": null, - "staged_sha256": "63b6831755f33c4fe7c8d7dd49cf2a60251edfcd45e4555f2731941939cb8d0a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-package-info.txt", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/official-zlib-package-info.txt", - "original_sha256": null, - "staged_sha256": "395e0ee97996853c6cf03f17dfe25b57d109b0432c6faabb585d78e6ac6f534a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/proposal.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/proposal.json", - "original_sha256": null, - "staged_sha256": "a12877b149427713c12e67f2597bf330d32dd0b30464008c0f79fc593f05d894" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/sbom.cdx.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/sbom.cdx.json", - "original_sha256": null, - "staged_sha256": "287a61e1aaf78b58c014726e9bcc1ddc2c46949ac0ee74d53fccae3007eac1bb" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/test_artifact_audit.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/test_artifact_audit.py", - "original_sha256": null, - "staged_sha256": "e9c5a628e40acc3ae241bb3f3bf9c2846c064601761a11aecce81ba4803c8f71" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/trivy-full.json", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/trivy-full.json", - "original_sha256": null, - "staged_sha256": "fd37bc2d502a9f017f673486f0f960433fc0fc12dcd745e5ed8074e9c3b600d9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-remediation-candidate/verify_local_artifacts.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/docs/evidence/2026-10-11-zlib-remediation-candidate/verify_local_artifacts.py", - "original_sha256": null, - "staged_sha256": "53778a2c3c89aee4c1256ed7e5896db033d960c6345d490620e4f06d03268794" - }, - { - "repo": "informed-decision", - "path": "tools/zlib_overlay/check_build_inputs.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/tools/zlib_overlay/check_build_inputs.py", - "original_sha256": null, - "staged_sha256": "1bf0219765dbcf5f7e26495ac4d49f240affa21deeb9b06c65e275afd10d2e06" - }, - { - "repo": "informed-decision", - "path": "tools/zlib_overlay/check_runtime.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/tools/zlib_overlay/check_runtime.py", - "original_sha256": null, - "staged_sha256": "eb1fca9a43d3394d652f748cde2e8d28cbe646ad61c40f990c0b8878c522a3be" - }, - { - "repo": "informed-decision", - "path": "tools/zlib_overlay/compare_runtime.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/tools/zlib_overlay/compare_runtime.py", - "original_sha256": null, - "staged_sha256": "545f04844f90d028f5d8bb65fb16f3805ee5735ac687ec28303eb9dbb4195b0d" - }, - { - "repo": "informed-decision", - "path": "tools/zlib_overlay/test_candidate.py", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/tools/zlib_overlay/test_candidate.py", - "original_sha256": null, - "staged_sha256": "68677b91c34101776381abcabffcd983bf2452076f983f32599ab1f7ad177bb9" - }, - { - "repo": "informed-decision", - "path": "workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "staged_path": "/tmp/infd-zlib-candidate-stage/informed-decision/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "original_sha256": "73fe52f0a188067d3e9c9d78584117b9cfa40ce417bb31530cbc2e9bbe858223", - "staged_sha256": "af785afbf188184b72563c10813663d44117df7cb7394ecb1b9a25cbdfea67a4" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/test_artifact_audit.py b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/test_artifact_audit.py deleted file mode 100644 index 332e2e8..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/test_artifact_audit.py +++ /dev/null @@ -1,47 +0,0 @@ -"""Synthetic artifact boundaries; never access owner/native databases.""" -from pathlib import Path -import io,json,subprocess,sys,tarfile,tempfile,unittest -CHECKER=Path(__file__).with_name('verify_local_artifacts.py') -def add(t,name,data=None,metadata=(0o755,0,0)): - m=tarfile.TarInfo(name);m.mode,m.uid,m.gid=metadata - if data is None:m.type=tarfile.DIRTYPE;t.addfile(m) - else:m.size=len(data);t.addfile(m,io.BytesIO(data)) -def archive(path,layers,ids): - with tarfile.open(path,'w') as t: - add(t,'manifest.json',json.dumps([{'Config':'config.json','Layers':[f'{i}.tar' for i in range(len(layers))]}]).encode()) - add(t,'config.json',json.dumps({'rootfs':{'diff_ids':ids},'config':{}}).encode()) - for i,data in enumerate(layers):add(t,f'{i}.tar',data) -def layer(metadata): - b=io.BytesIO() - with tarfile.open(fileobj=b,mode='w') as t:add(t,'usr',metadata=metadata) - return b.getvalue() -def fixture(root,metadata=(0o755,0,0)): - base=layer((0o755,0,0));candidate=layer(metadata) - archive(root/'base-image.tar',[base],['base']) - archive(root/'image.tar',[base,candidate,layer(metadata)],['base','overlay','cleanup']) - for name,version in [('base-sbom.cdx.json','1.3.2-r0'),('sbom.cdx.json','1.3.2-r1')]: - (root/name).write_text(json.dumps({'components':[{'name':'zlib','version':version,'purl':'pkg:apk/zlib'}]})) - for name,findings in [('base-trivy-full.json',[{'VulnerabilityID':'CVE-2026-85091','Severity':'MEDIUM'}]),('trivy-full.json',[])]: - (root/name).write_text(json.dumps({'Results':[{'Vulnerabilities':findings}]})) -class ArtifactAuditTests(unittest.TestCase): - def execute(self,root,optimize=False): - return subprocess.run([sys.executable,*(['-O'] if optimize else []),str(CHECKER),str(root)],capture_output=True,text=True) - def test_optimized_tampered_artifacts_refused_before_read(self): - with tempfile.TemporaryDirectory() as tmp: - root=Path(tmp);(root/'base-image.tar').write_bytes(b'tampered not an archive') - r=self.execute(root,True) - self.assertNotEqual(r.returncode,0);self.assertIn('REFUSED: optimized Python',r.stderr) - self.assertNotIn('ReadError',r.stderr);self.assertFalse((root/'artifact-comparison.json').exists()) - def test_matching_directory_metadata_passes(self): - with tempfile.TemporaryDirectory() as tmp: - root=Path(tmp);fixture(root);r=self.execute(root) - self.assertEqual(r.returncode,0,r.stderr) - proof=json.loads((root/'artifact-comparison.json').read_text()) - self.assertTrue(proof['directory_metadata_compared']);self.assertEqual(proof['directory_metadata_delta'],{}) - def test_directory_mode_uid_gid_drift_refuses(self): - for metadata in [(0o777,0,0),(0o755,10001,0),(0o755,0,10001)]: - with self.subTest(metadata=metadata),tempfile.TemporaryDirectory() as tmp: - root=Path(tmp);fixture(root,metadata);r=self.execute(root) - self.assertNotEqual(r.returncode,0);self.assertIn("{'usr'}",r.stderr) - self.assertFalse((root/'artifact-comparison.json').exists()) -if __name__=='__main__':unittest.main() diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/verify_local_artifacts.py b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/verify_local_artifacts.py deleted file mode 100644 index 81654b4..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/intermediate-64509029/verify_local_artifacts.py +++ /dev/null @@ -1,49 +0,0 @@ -from pathlib import Path -import hashlib,io,json,tarfile,sys -if sys.flags.optimize: - raise SystemExit("REFUSED: optimized Python disables required artifact validation") -root=Path(sys.argv[1]) if len(sys.argv)==2 else Path('/tmp/infd-zlib-remediation-build') -def image(path): - with tarfile.open(path) as archive: - manifest=json.load(archive.extractfile('manifest.json'));assert len(manifest)==1 - configbytes=archive.extractfile(manifest[0]['Config']).read();config=json.loads(configbytes) - files={} - for name in manifest[0]['Layers']: - with tarfile.open(fileobj=archive.extractfile(name),mode='r|*') as layer: - for member in layer: - path=member.name.lstrip('./');p=Path(path) - if p.name=='.wh..wh..opq': - folder=p.parent.as_posix();files={key:value for key,value in files.items() if not key.startswith(folder+'/')};continue - if p.name.startswith('.wh.'): - removed=(p.parent/p.name[4:]).as_posix();files={key:value for key,value in files.items() if key!=removed and not key.startswith(removed+'/')};continue - if member.isfile(): - data=layer.extractfile(member).read();value=('file',member.mode,member.uid,member.gid,hashlib.sha256(data).hexdigest()) - elif member.isdir():value=('directory',member.mode,member.uid,member.gid,None) - elif member.issym() or member.islnk():value=('link',member.mode,member.uid,member.gid,member.linkname) - else:continue - files[path]=value - return config,hashlib.sha256(configbytes).hexdigest(),files -base,baseid,bfiles=image(root/'base-image.tar');candidate,candidateid,cfiles=image(root/'image.tar') -assert candidate['rootfs']['diff_ids'][:-2]==base['rootfs']['diff_ids'] -changed={name:{'before':bfiles.get(name),'after':cfiles.get(name)} for name in sorted(set(bfiles)|set(cfiles)) if bfiles.get(name)!=cfiles.get(name)} -expected={'usr/lib/libz.so.1.3.2','lib/apk/db/installed'} -assert set(changed)<=expected,set(changed) -a,b=base['config'].copy(),candidate['config'].copy() -labels_a=a.pop('Labels',{});labels_b=b.pop('Labels',{}) -a.pop('Image',None);b.pop('Image',None) -defaults={'Hostname':'','Domainname':'','AttachStdin':False,'AttachStdout':False,'AttachStderr':False,'Tty':False,'OpenStdin':False,'StdinOnce':False,'Cmd':None,'Volumes':None,'OnBuild':None} -for key,value in defaults.items(): - a.setdefault(key,value);b.setdefault(key,value) -assert a==b,(a,b) -components=[] -for name in ['base-sbom.cdx.json','sbom.cdx.json']: - data=json.loads((root/name).read_text());components.append({c['name']:c['version'] for c in data['components'] if c.get('purl')}) -sbom_delta={name:[components[0].get(name),components[1].get(name)] for name in set(components[0])|set(components[1]) if components[0].get(name)!=components[1].get(name)} -assert sbom_delta=={'zlib':['1.3.2-r0','1.3.2-r1']},sbom_delta -vulns=[] -for name in ['base-trivy-full.json','trivy-full.json']: - data=json.loads((root/name).read_text());vulns.append([v for r in data.get('Results',[]) for v in r.get('Vulnerabilities',[])]) -assert len(vulns[0])==1 and vulns[0][0]['VulnerabilityID']=='CVE-2026-85091' and vulns[0][0]['Severity']=='MEDIUM' -assert not vulns[1] -proof={'base_image_config_id':'sha256:'+baseid,'candidate_local_image_config_id':'sha256:'+candidateid,'candidate_registry_digest':None,'base_layers_preserved':len(base['rootfs']['diff_ids']),'new_layers':2,'effective_runtime_config_identical':True,'raw_config_bytes_identical':False,'serialization_note':'Legacy base explicitly serialized empty/false/null defaults; candidate Docker-save config omits equivalent known defaults. Exact closed default normalization only; runtime fields unchanged.','labels_before':labels_a,'labels_after':labels_b,'final_changed_files':changed,'directory_metadata_compared':True,'directory_count_base':sum(v[0]=='directory' for v in bfiles.values()),'directory_count_candidate':sum(v[0]=='directory' for v in cfiles.values()),'directory_metadata_delta':{k:v for k,v in changed.items() if (v['before'] and v['before'][0]=='directory') or (v['after'] and v['after'][0]=='directory')},'sbom_package_count_base':len(components[0]),'sbom_package_count_candidate':len(components[1]),'sbom_only_delta':sbom_delta,'full_scan_baseline_findings':1,'full_scan_candidate_allseverity_findings':0,'db_snapshot_same':True,'native_workload_changed':False} -(root/'artifact-comparison.json').write_text(json.dumps(proof,indent=2)+'\n');print(json.dumps({key:proof[key] for key in ['candidate_local_image_config_id','base_layers_preserved','effective_runtime_config_identical','sbom_only_delta','full_scan_candidate_allseverity_findings']}));print(json.dumps(list(changed))) diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md deleted file mode 100644 index 29593ea..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/accepted-66-6af44c10/owner-snapshot/intakes/RPF-IN-0008-infd-zlib-candidate-review.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -id: RPF-IN-0008 -kind: intake -type: intake -title: "Platform source review of separate INFD zlib candidate publication" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009-T05 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -Bounded new Platform review of source candidate manifest `8d345bb2bde3efce4d0cc99dfc2f883a64f79c42d2cd4abb288229a995e9ad66`: official zlib package/index provenance, exact current763c base and minimal layer/runtime changes, local scan/SBOM proof, source-pinned ownerCI publication/credential boundaries. Distinct from completed purpose-actuator RPF-IN-0007 and its approvedR2 trial. No native rollout/data action, owner source write, registry publish or carried-forward human approval. Native763c still has disclosed inheritedMEDIUM/T05 dueOct18; source candidate is unpublished. This owner work record precedes formal review. - -## Completed initial source review - -Initial technical review `RPF-REVIEW-20261011-INFD-ZLIB-CANDIDATE-R1` validates exact27-path8d345 source/localproofs and publication owner boundary. Source proof helper must refuse optimized Python before artifact IO; directory coverage verified independently for current artifacts and required on actual published readback. Four source methods pass; raw scans confirm base1MED/candidate0 on stated DB. Current local candidate remains unpublished; no native authorization transferred. Review refinement remains in promoted INFD-WP-0009-T05, separate from unpromoted verifier residual and completed IN7. Completed owner work record precedes formal entry. - -## Completed amended-source review preparation - -Same bounded review completed against the amended source. Exact initial conditional source review and four-file workrecord/probe snapshot are archived under `initial-380c6172`; initial failed optimized-proof boundary remains historical. Seven maker tests and independent optimized/deletion boundary probes reviewed. This completed workrecord is staged before the final formal review. Published-image readback and separate native authorization remain outside this source review. - -## Completed final exact-source review - -Final frozen66-path source `3e41ab391cda26f09a556fecef8a17c2759ef2899e43eb136b25ef4b3fde23a0` completed after optimization, directory metadata and whiteout-boundary refinements. Eight maker tests and five independent synthetic boundary probes pass. All66 hashes match. Actual existing local candidate remains unpublished with unchanged two-file delta,313 directory records and stated scan snapshot. This workrecord update precedes the formal narrow-source agreement; original380c conditional and645 failed proof-boundary evidence remain archived. No native result, approval, publication or T05 completion is recorded. diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-order-source-probes.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-order-source-probes.json deleted file mode 100644 index 385978b..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-order-source-probes.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "source_manifest_sha256": "724aa4777eb1c8976d868e5f504473f4b6d58f13d750d87aefa5c8d5ca0747ec", - "all74_hashes_match": true, - "maker_test_methods_independently_passed": 9, - "independent_cli_order_cases_passed": 8, - "independent_prior_negative_cases_passed": 5, - "order_case_assertion": "Full CLI success plus emitted libz replacement file hash equals current-layer synthetic bytes for root/nested regular/opaque whiteouts before and after additions.", - "fixtures_only": true, - "native_actions": false, - "actual_candidate_image_unchanged": true -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-order-source-review.json b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-order-source-review.json deleted file mode 100644 index 848d12d..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/final-order-source-review.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-ZLIB-CANDIDATE-R3", - "work_record": "RPF-IN-0008", - "role": "railiance-platform technical owner only", - "status": "technical-agreement-source-candidate-preparation-only", - "source_manifest_sha256": "724aa4777eb1c8976d868e5f504473f4b6d58f13d750d87aefa5c8d5ca0747ec", - "source_paths": 74, - "initial_conditional_review_manifest_sha256": "380c6172adc36b5907ba5384d14d799e49eda410732dfce1878fbe6db184ff0c", - "intermediate_source_manifest_sha256": "64509029b3de6d33e3675587663ccdae027d9ea18aba83750892480766dd03b8", - "history": "Exact previous16 review files plus original manifest/map archived at accepted-66-6af44c10. This includes original380c four-file snapshot and645 failed boundary evidence. Prior agreement preserved as intermediate, superseded for source-whiteout semantics. Same completed IN8 trial anchor updated before entry; no new privilege or success.", - "refinements_verified": [ - "Optimization refused before artifact reads/output under -O and independently -OO.", - "Directory mode/UID/GID included; drift negatives pass.", - "Leading dot whiteout names preserved; root/nested deletion and opaque boundaries refuse fixture drift for correct effective paths.", - "Existing source/package provenance, exact763c base, four-file context and bounded existing owner publication/credential routes unchanged.", - "Markers apply only to previous-layer state; current-layer additions merged after masking. Eight independent full CLI cases verify replacement hashes survive root/nested regular/opaque markers in both stream orders." - ], - "actual_local_candidate": { - "daemon_image_id": "sha256:78bfdcc00a62367ae6190f6569a02b18abf9a943f760e4ac8fb946703348af31", - "registry_digest": null, - "context_sha256": "c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d", - "effective_changed_files": [ - "lib/apk/db/installed", - "usr/lib/libz.so.1.3.2" - ], - "directories": 313, - "directory_metadata_delta": {}, - "nine_base_layers_preserved": true, - "runtime_policy_module_unchanged": true, - "fullscan_same_snapshot_baseline_findings": 1, - "fullscan_same_snapshot_local_candidate_findings": 0, - "raw_runtime_config_bytes_identical": false, - "closed_default_normalization_semantics_unchanged": true, - "initial_or_intermediate_candidate_failed": false - }, - "validation": { - "source_manifest_sha256": "724aa4777eb1c8976d868e5f504473f4b6d58f13d750d87aefa5c8d5ca0747ec", - "all74_hashes_match": true, - "maker_test_methods_independently_passed": 9, - "independent_cli_order_cases_passed": 8, - "independent_prior_negative_cases_passed": 5, - "order_case_assertion": "Full CLI success plus emitted libz replacement file hash equals current-layer synthetic bytes for root/nested regular/opaque whiteouts before and after additions.", - "fixtures_only": true, - "native_actions": false, - "actual_candidate_image_unchanged": true - }, - "remaining_gates": [ - "Actual owner CI source-pinned publication and immutable registry descriptor/layer/config/source readback.", - "Actual published full unsuppressed pinned scan/SBOM/runtime/purpose proof; local artifact zero does not extend to a different artifact.", - "Concrete separately reviewed native target/data-retaining protocol and fresh founder disposition; old purpose R2 approval does not authorize zlib release." - ], - "scope_limits": { - "owner_source_write": false, - "registry_publish": false, - "native_workload_changed": false, - "database_access": false, - "credentials_read": false, - "human_native_approval": false, - "T05_complete": false, - "other_owner_role": false - }, - "checker_scope": "Agreement concerns this exact bounded candidate and challenged proof boundary; does not certify a general-purpose OCI extraction or signature verifier.", - "previous_agreement_manifest_sha256": "6af44c102084f7cdf09268264c5fd7bb0b0b354723edb10b7da2c1800ab726d4", - "previous_source_manifest_sha256": "3e41ab391cda26f09a556fecef8a17c2759ef2899e43eb136b25ef4b3fde23a0" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_order_probes.py b/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_order_probes.py deleted file mode 100644 index e51fcd6..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-candidate-owner-review/independent_order_probes.py +++ /dev/null @@ -1,22 +0,0 @@ -"""CLI proof regression using temporary synthetic public image archives only.""" -import importlib.util,subprocess,tempfile,json,hashlib,sys -from pathlib import Path -root=Path(sys.argv[1]);spec=importlib.util.spec_from_file_location("fixture",root/"test_artifact_audit.py");f=importlib.util.module_from_spec(spec);spec.loader.exec_module(f) -def layer(entries): - b=f.io.BytesIO() - with f.tarfile.open(fileobj=b,mode="w") as t: - for name,data in entries:f.add(t,name,data) - return b.getvalue() -results={};target="usr/lib/libz.so.1.3.2";new=b"synthetic-new" -for marker in [".wh..wh..opq","usr/lib/.wh..wh..opq",".wh.usr","usr/lib/.wh.libz.so.1.3.2"]: - for first in [True,False]: - with tempfile.TemporaryDirectory() as tmp: - p=Path(tmp);f.fixture(p);base=layer([(target,b"synthetic-old")]);entries=[(marker,b""),(target,new)] - if not first:entries.reverse() - f.archive(p/"base-image.tar",[base],["base"]);f.archive(p/"image.tar",[base,layer(entries),layer([])],["base","overlay","cleanup"]) - r=subprocess.run([sys.executable,str(f.CHECKER),str(p)],capture_output=True,text=True) - proof=json.loads((p/"artifact-comparison.json").read_text()) if r.returncode==0 else {} - after=proof.get("final_changed_files",{}).get(target,{}).get("after") - results[marker+":"+str(first)]=r.returncode==0 and after is not None and after[0]=="file" and after[-1]==hashlib.sha256(new).hexdigest() -print(json.dumps(results,sort_keys=True)) -if not all(results.values()):raise SystemExit(1) diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/acceptance-validation.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/acceptance-validation.json deleted file mode 100644 index 50a8089..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/acceptance-validation.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "original_root_manifest_sha256": "0e4799f709e097312c18ef0124afc153003efef0627902591939a218dbd66d4d", - "original20_hashes_verified_before_and_after": true, - "all20_safemetadata_source_history_bytes_preserved_exact": true, - "approved_git_source": "312ab97e876e17341bf16f9e95397e14c2ac2720", - "approved_git_memo_sha256": "5dea0d404e4d13683fbf71d56273c06db7a6a916ab68759a90d7394005eee206", - "approved_git_plan_sha256": "d871081999215bd3850ab1173064ed6cb6d7c23b26d9c3d5b3c5940e698d4c61", - "committed_executor_and_four_render_blobs_match": true, - "provider_command_git_hash_matches": true, - "eight_phase_receipts_all_checks_pass": true, - "snapshot_backup_copy_metadata_exact_for_both_drill_and_cutover": true, - "offline_pre_post_exact_snapshot_counts_and_original_hash_unchanged": true, - "only_nonce_owned_clone_removed": true, - "freshscan_report_hash_exact": true, - "actual_fullscan_contract_passed_at_provider_observation_time": true, - "package_count": 44, - "allseverity_findings": 0, - "freshscan_completed_at": "2026-10-11T10:48:46.790940+00:00", - "freshscan_age_expiry": "2026-10-11T14:48:46.790940+00:00", - "db_next_update": "2026-10-11T18:59:50.889010572Z", - "same_pod_generation_image_source_before_after_provider_check": true, - "reviewer_native_requests_or_private_data_access": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/native-technical-acceptance.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/native-technical-acceptance.json deleted file mode 100644 index b082522..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/native-technical-acceptance.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "review_id": "RPF-ACCEPT-20261011-INFD-ZLIB-NATIVE-R1", - "work_record": "RPF-IN-0010", - "role": "railiance-platform technical owner only", - "status": "native-technical-acceptance-exact-observed-release", - "basis": "Independent owner assessment of immutable retained root native execution/verification evidence and exact approved Git source; reviewer issued no native commands and did not read private custody/journal/database/credentials. Root completed-phase journal attestation is corroborated by each safe successful phase receipt.", - "root_evidence_manifest_sha256": "0e4799f709e097312c18ef0124afc153003efef0627902591939a218dbd66d4d", - "validation": { - "original_root_manifest_sha256": "0e4799f709e097312c18ef0124afc153003efef0627902591939a218dbd66d4d", - "original20_hashes_verified_before_and_after": true, - "all20_safemetadata_source_history_bytes_preserved_exact": true, - "approved_git_source": "312ab97e876e17341bf16f9e95397e14c2ac2720", - "approved_git_memo_sha256": "5dea0d404e4d13683fbf71d56273c06db7a6a916ab68759a90d7394005eee206", - "approved_git_plan_sha256": "d871081999215bd3850ab1173064ed6cb6d7c23b26d9c3d5b3c5940e698d4c61", - "committed_executor_and_four_render_blobs_match": true, - "provider_command_git_hash_matches": true, - "eight_phase_receipts_all_checks_pass": true, - "snapshot_backup_copy_metadata_exact_for_both_drill_and_cutover": true, - "offline_pre_post_exact_snapshot_counts_and_original_hash_unchanged": true, - "only_nonce_owned_clone_removed": true, - "freshscan_report_hash_exact": true, - "actual_fullscan_contract_passed_at_provider_observation_time": true, - "package_count": 44, - "allseverity_findings": 0, - "freshscan_completed_at": "2026-10-11T10:48:46.790940+00:00", - "freshscan_age_expiry": "2026-10-11T14:48:46.790940+00:00", - "db_next_update": "2026-10-11T18:59:50.889010572Z", - "same_pod_generation_image_source_before_after_provider_check": true, - "reviewer_native_requests_or_private_data_access": false - }, - "confirmed_founder_go": { - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "source_memo": "infd-20261011-zlib-remediation-r1", - "source_memo_version": 1, - "memo_sha256": "5dea0d404e4d13683fbf71d56273c06db7a6a916ab68759a90d7394005eee206", - "source_revision": "312ab97e876e17341bf16f9e95397e14c2ac2720", - "authority": "root-confirmed native founder readback, not inferred from provider preservation proof or a new grant" - }, - "accepted_actual_outcome": { - "candidate_image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "deployment_uid": "a588e10a-90d2-4883-975e-6dd8b8feca92", - "generation": 26, - "ready_replicas": 1, - "pod_uid": "1153f806-1e05-4596-baf3-a4fa8adf08be", - "eight_phases": [ - "drill-backup", - "drill-copy", - "offline-verify", - "cleanup-offline", - "cutover-backup", - "cutover-copy", - "release", - "verify" - ], - "scoped_argo": "Exact source312ab97, one apps/Deployment informed-decision in its namespace, explicitfalse request and exact completion with guarded false omission; no broad sync/prune.", - "latest_database_retained": true, - "schema": 5, - "integrity": "ok", - "installed_zlib": "1.3.2-r1", - "installed_policy_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62", - "healthz": 200, - "readyz": 200, - "installed_purpose_checks": "10 synthetic only:2allow/8refuse; not native provider/advisory evidence", - "native_provider_preservation": "Canonical fixed authenticated source1 outcomes HTTP200 at2026-10-11T11:20:25.876927+00:00, same exact ReadyPod/source/image before11:20:14 andafter11:20:27; minimal shape/count proof, no founder approval inference.", - "fullscan": "Exact actual44package immutableartifact zeroallseverity at10:48:46, valid through actual verification/provider observation; freshness expires14:48:46 rather than a perpetual zero claim." - }, - "failed_trial_and_refinement_history": { - "prephase_refusals": 3, - "custody_or_durable_phase_native_effects_in_refusals": "none per root diagnosis; immutable original failure metadata retained", - "actual_cause": "Root copied remote native observed_at into root verified_at; remote clock ahead produced negativeage -1.1806s.", - "correction": "Root bookkeeping ONLY now records workstation local time after authenticated readback as verified_at and preserves remote native_observed_at separately. Exact one-line helper diff retained.", - "approved_source_runtime_render_helper_plan_changed": false, - "sandbox_hypothesis": "Earlier provisional sandbox write-denial hypothesis disproven; not recorded as final cause.", - "failed_records_backfilled_to_success": false, - "same_trial_not_three_or_four_successes": true, - "outage_recovery_used": false, - "rollback_used": false, - "standing_or_customer_recovery_authority_created": false - }, - "closure_boundaries": { - "supports_product_specific_T05_risk_and_WP9_closure": true, - "product_records_edited_by_reviewer": false, - "C07_alias_diagnosis": "unsettled; no alias adjudication or tombstone deletion", - "unpromoted_residuals": "remain unpromoted; no generic verifier or recovery mechanism implementation", - "estate_grade_or_broad_security_standard_claim": false, - "customer_ready_claim": false, - "new_privileged_approval": false, - "owner_source_write_or_push": false, - "reviewer_native_or_private_database_secret_access": false - }, - "preservation": "Full original20 safe metadata/sourcefiles plus exactoriginalmanifest and path/hashmap retained under root-evidence. New completed IN10 precedes formal acceptance; IN9source-review scope not stretched." -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-local-image-readiness.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-local-image-readiness.json deleted file mode 100644 index 03cd313..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-local-image-readiness.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "exact_image_available": true, - "pulled": true, - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "image_config_id": "sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "exact_registry_digest_bound": true, - "source_labels_match": true, - "container_execution": false, - "credentials_used": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-native-preflight.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-native-preflight.json deleted file mode 100644 index 4908ef8..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-native-preflight.json +++ /dev/null @@ -1,672 +0,0 @@ -{ - "all_checks_pass": true, - "observed_at": "2026-10-11T10:44:51.809259+00:00", - "native_changed": false, - "protected_database_read": false, - "secret_read": false, - "deployment_generation": 25, - "ready_replicas": 1, - "sealed_live_deployment": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:apps/Deployment:informed-decision/informed-decision", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision", - "namespace": "informed-decision", - "uid": "a588e10a-90d2-4883-975e-6dd8b8feca92" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app.kubernetes.io/name": "informed-decision" - } - }, - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "annotations": { - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "labels": { - "app.kubernetes.io/component": "review", - "app.kubernetes.io/name": "informed-decision" - } - }, - "spec": { - "automountServiceAccountToken": false, - "containers": [ - { - "env": [ - { - "name": "INFD_KEYCAPE_ISSUER", - "value": "https://kc.coulomb.social" - }, - { - "name": "INFD_CONTAINER_CONFIG", - "value": "/configuration/runtime.json" - } - ], - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "name": "informed-decision", - "ports": [ - { - "containerPort": 8080, - "name": "http", - "protocol": "TCP" - }, - { - "containerPort": 8081, - "name": "workload", - "protocol": "TCP" - } - ], - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "resources": { - "limits": { - "cpu": "500m", - "memory": "256Mi" - }, - "requests": { - "cpu": "5m", - "memory": "64Mi" - } - }, - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": [ - "ALL" - ] - }, - "readOnlyRootFilesystem": true - }, - "startupProbe": { - "failureThreshold": 12, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/data", - "name": "data" - }, - { - "mountPath": "/run/informed-decision", - "name": "runtime" - }, - { - "mountPath": "/tmp", - "name": "tmp" - }, - { - "mountPath": "/configuration", - "name": "configuration", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/policy", - "name": "caller", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/audit", - "name": "audit", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/kubernetes", - "name": "kubernetes-api", - "readOnly": true - }, - { - "mountPath": "/workload-registry", - "name": "workload-registry", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/requester", - "name": "sitting-requester", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 10001, - "fsGroupChangePolicy": "OnRootMismatch", - "runAsGroup": 10001, - "runAsNonRoot": true, - "runAsUser": 10001, - "seccompProfile": { - "type": "RuntimeDefault" - } - }, - "serviceAccount": "review", - "serviceAccountName": "review", - "terminationGracePeriodSeconds": 45, - "volumes": [ - { - "name": "data", - "persistentVolumeClaim": { - "claimName": "informed-decision-data" - } - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "runtime" - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "tmp" - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-runtime-6c221878d051" - }, - "name": "configuration" - }, - { - "name": "caller", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "audience": "flex-auth", - "expirationSeconds": 3600, - "path": "token" - } - } - ] - } - }, - { - "name": "audit", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "token", - "path": "token" - } - ], - "secretName": "informed-decision-audit" - } - }, - { - "name": "kubernetes-api", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 600, - "path": "token" - } - }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } - } - ] - } - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-workload-registry" - }, - "name": "workload-registry" - }, - { - "name": "sitting-requester", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "client-secret", - "path": "client-secret" - } - ], - "optional": false, - "secretName": "informed-decision-sitting-requester" - } - } - ] - } - } - } - }, - "candidate_deployment": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:apps/Deployment:informed-decision/informed-decision", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision", - "namespace": "informed-decision", - "uid": "a588e10a-90d2-4883-975e-6dd8b8feca92" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app.kubernetes.io/name": "informed-decision" - } - }, - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "annotations": { - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "labels": { - "app.kubernetes.io/component": "review", - "app.kubernetes.io/name": "informed-decision" - } - }, - "spec": { - "automountServiceAccountToken": false, - "containers": [ - { - "env": [ - { - "name": "INFD_KEYCAPE_ISSUER", - "value": "https://kc.coulomb.social" - }, - { - "name": "INFD_CONTAINER_CONFIG", - "value": "/configuration/runtime.json" - } - ], - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "name": "informed-decision", - "ports": [ - { - "containerPort": 8080, - "name": "http", - "protocol": "TCP" - }, - { - "containerPort": 8081, - "name": "workload", - "protocol": "TCP" - } - ], - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "resources": { - "limits": { - "cpu": "500m", - "memory": "256Mi" - }, - "requests": { - "cpu": "5m", - "memory": "64Mi" - } - }, - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": [ - "ALL" - ] - }, - "readOnlyRootFilesystem": true - }, - "startupProbe": { - "failureThreshold": 12, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/data", - "name": "data" - }, - { - "mountPath": "/run/informed-decision", - "name": "runtime" - }, - { - "mountPath": "/tmp", - "name": "tmp" - }, - { - "mountPath": "/configuration", - "name": "configuration", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/policy", - "name": "caller", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/audit", - "name": "audit", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/kubernetes", - "name": "kubernetes-api", - "readOnly": true - }, - { - "mountPath": "/workload-registry", - "name": "workload-registry", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/requester", - "name": "sitting-requester", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 10001, - "fsGroupChangePolicy": "OnRootMismatch", - "runAsGroup": 10001, - "runAsNonRoot": true, - "runAsUser": 10001, - "seccompProfile": { - "type": "RuntimeDefault" - } - }, - "serviceAccount": "review", - "serviceAccountName": "review", - "terminationGracePeriodSeconds": 45, - "volumes": [ - { - "name": "data", - "persistentVolumeClaim": { - "claimName": "informed-decision-data" - } - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "runtime" - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "tmp" - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-runtime-6c221878d051" - }, - "name": "configuration" - }, - { - "name": "caller", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "audience": "flex-auth", - "expirationSeconds": 3600, - "path": "token" - } - } - ] - } - }, - { - "name": "audit", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "token", - "path": "token" - } - ], - "secretName": "informed-decision-audit" - } - }, - { - "name": "kubernetes-api", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 600, - "path": "token" - } - }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } - } - ] - } - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-workload-registry" - }, - "name": "workload-registry" - }, - { - "name": "sitting-requester", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "client-secret", - "path": "client-secret" - } - ], - "optional": false, - "secretName": "informed-decision-sitting-requester" - } - } - ] - } - } - } - }, - "sealed_dependencies": { - "configmap": { - "apiVersion": "v1", - "immutable": true, - "kind": "ConfigMap", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:/ConfigMap:informed-decision/informed-decision-runtime-6c221878d051", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision-runtime-6c221878d051", - "namespace": "informed-decision", - "uid": "90113d2d-54d9-4e82-b1d3-90b49ebed2b5" - }, - "canonical_data_sha256": "8ec462d12347548f506eeebf1e3c7cc47648019c8a783804d09bcc79b0f22af6" - }, - "pvc": { - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:/PersistentVolumeClaim:informed-decision/informed-decision-data", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover", - "pv.kubernetes.io/bind-completed": "yes", - "pv.kubernetes.io/bound-by-controller": "yes", - "volume.beta.kubernetes.io/storage-provisioner": "rancher.io/local-path", - "volume.kubernetes.io/selected-node": "239.62.205.92.host.secureserver.net", - "volume.kubernetes.io/storage-provisioner": "rancher.io/local-path" - }, - "finalizers": [ - "kubernetes.io/pvc-protection" - ], - "name": "informed-decision-data", - "namespace": "informed-decision", - "uid": "d80b25b1-141a-441b-b205-98fa2fd98b7c" - }, - "spec": { - "accessModes": [ - "ReadWriteOnce" - ], - "resources": { - "requests": { - "storage": "1Gi" - } - }, - "storageClassName": "local-path", - "volumeMode": "Filesystem", - "volumeName": "pvc-d80b25b1-141a-441b-b205-98fa2fd98b7c" - } - }, - "pvc_phase": "Bound" - }, - "argo": { - "uid": "499868f2-eabc-4df4-ae31-b89ce58d750d", - "source": { - "path": "deploy/lifecycle", - "repoURL": "https://forgejo.coulomb.social/coulomb/informed-decision.git", - "targetRevision": "c96b0c6d881e855d1d158003c6de90ef390262e2" - }, - "destination": { - "namespace": "informed-decision", - "server": "https://kubernetes.default.svc" - }, - "automated": false, - "health": { - "lastTransitionTime": "2026-10-11T07:00:42Z", - "status": "Healthy" - }, - "sync": { - "comparedTo": { - "destination": { - "namespace": "informed-decision", - "server": "https://kubernetes.default.svc" - }, - "source": { - "path": "deploy/lifecycle", - "repoURL": "https://forgejo.coulomb.social/coulomb/informed-decision.git", - "targetRevision": "c96b0c6d881e855d1d158003c6de90ef390262e2" - } - }, - "revision": "c96b0c6d881e855d1d158003c6de90ef390262e2", - "status": "OutOfSync" - } - }, - "server_dry_run_passed": true, - "admission_only_image_delta": true, - "client_diff_exit": 1, - "client_diff_sha256": "ee53537b71df69ea1ce4a67498a47524d1aef87aa5ad1e9499c3f699f142762e", - "allocatable_cpu_m": 4000, - "requested_cpu_m": 3460, - "strategy": "Recreate" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/archive-map.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/archive-map.json deleted file mode 100644 index aa41d9f..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/archive-map.json +++ /dev/null @@ -1,102 +0,0 @@ -[ - { - "original_path": "drill-backup.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-backup.json", - "sha256": "53ea89341b3ed9d62b6db11070c19a3f1a97aa705ca5e69f75e5badb6d0f085e" - }, - { - "original_path": "drill-copy.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-copy.json", - "sha256": "06011d148a022d7249f85e794455f297bf8193e19480762b667c8a42afa30e4e" - }, - { - "original_path": "offline-verify.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/offline-verify.json", - "sha256": "4cf2df2c91877c87be8eebb279640473bb3627fe93ed4467467058e8f7baa7ab" - }, - { - "original_path": "cleanup-offline.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cleanup-offline.json", - "sha256": "ee89cddb506e65959c96b8da453443cd77e3a5cce927e0102acf56f28c38408c" - }, - { - "original_path": "cutover-backup.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-backup.json", - "sha256": "3d7a79ddb6f29368284a2d87ffbf350e27a0548750e29e8a532ad9736b3e2cc7" - }, - { - "original_path": "cutover-copy.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-copy.json", - "sha256": "14f008e0b8a7b1447847c3c02f1a07ffa13285832f91a6c5a795200f1c68c12f" - }, - { - "original_path": "release.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/release.json", - "sha256": "05cb8b520c9414b5f3a39b2fa5f9b6bf07e93145390fd252c7ddf8cd618687d7" - }, - { - "original_path": "verify.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/verify.json", - "sha256": "efe87612f116839340a8e4c777ee7a66f806c8b8ded7f4a13451ec858c838f9a" - }, - { - "original_path": "canonical-provider-verification.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/canonical-provider-verification.json", - "sha256": "bee994ec4a965f2dccdd252feeb4b527f523fad172db8999849e7c9bc2b12794" - }, - { - "original_path": "fresh-scan-receipt.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-receipt.json", - "sha256": "0336de3a48d9e1af3e0d74ef7c4a4c01c3a0ac4418a1d84856e87c424dda6db1" - }, - { - "original_path": "fresh-scan-full.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-full.json", - "sha256": "42473695abfb99cb32ae29e607120996d339683da7987e9a91117aab9a7e5c00" - }, - { - "original_path": "confirmed-root-authority.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/confirmed-root-authority.json", - "sha256": "0d64760ba43581a19ec74489e655e89af150083aa59cea2414c12faa76912e88" - }, - { - "original_path": "approved-native-preflight.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-native-preflight.json", - "sha256": "90a8222c400c8e21e4850111fd91c22be9d33c3de9e3351da2bb49c69c3d40eb" - }, - { - "original_path": "root-clock-diagnosis.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-clock-diagnosis.json", - "sha256": "6e3cf5fee7471a58016652b7aa8a11756b40e0c8c75c5b74292fad8e11ec6ffe" - }, - { - "original_path": "prephase-refusal-first.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-first.json", - "sha256": "ea63e675da65771a2971030e2b9f55b5c466f70209718be3d0bad8e0b314843d" - }, - { - "original_path": "prephase-refusal-second.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-second.json", - "sha256": "ea63e675da65771a2971030e2b9f55b5c466f70209718be3d0bad8e0b314843d" - }, - { - "original_path": "prephase-refusal-traced.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-traced.json", - "sha256": "2932b60db10ce8295b376e6b5964ce695a489699782dd03eb02d9cff9c25bfb6" - }, - { - "original_path": "root-authority-helper-before-clock-correction.py", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-before-clock-correction.py", - "sha256": "64afd31c9776bd38c84f82972800fe59cbfb34b5315e9cb56aa25f5eb0e4c96d" - }, - { - "original_path": "root-authority-helper-final.py", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-final.py", - "sha256": "0e4fde2db800d0055c5ef94bd4efcb4f29207dfdb7fbab5df370bac0c65aa42a" - }, - { - "original_path": "approved-local-image-readiness.json", - "archived_owner_path": "docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/approved-local-image-readiness.json", - "sha256": "285bd23bc05a41fe9942e399527594da896f9a2160af757c9b7ffa9e006fbf37" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/canonical-provider-verification.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/canonical-provider-verification.json deleted file mode 100644 index 3b57f95..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/canonical-provider-verification.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "before": { - "all_checks_pass": true, - "observed_at": "2026-10-11T11:20:14.889361+00:00", - "deployment_uid": "a588e10a-90d2-4883-975e-6dd8b8feca92", - "generation": 26, - "ready_replicas": 1, - "pod_name": "informed-decision-68d5f488d8-c576r", - "pod_uid": "1153f806-1e05-4596-baf3-a4fa8adf08be", - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "image_id": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "application_revision": "312ab97e876e17341bf16f9e95397e14c2ac2720", - "application_health": "Healthy", - "native_data_change": false - }, - "native_provider": { - "request_memo_id": "infd-20261011-zlib-remediation-r1", - "request_memo_version": 1, - "request_identity_source": "fixed-authenticated-route", - "has_review": true, - "disposition_count": 1, - "dispositions_interpreted": false, - "approval_inferred": false, - "current_approval_claimed": false, - "http_status": 200, - "observed_at": "2026-10-11T11:20:25.876927+00:00", - "native_authenticated_canonical_lifecycle_outcomes": true, - "native_provider_path_returned": true, - "credential_material_output": false - }, - "after": { - "all_checks_pass": true, - "observed_at": "2026-10-11T11:20:27.541827+00:00", - "deployment_uid": "a588e10a-90d2-4883-975e-6dd8b8feca92", - "generation": 26, - "ready_replicas": 1, - "pod_name": "informed-decision-68d5f488d8-c576r", - "pod_uid": "1153f806-1e05-4596-baf3-a4fa8adf08be", - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "image_id": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "application_revision": "312ab97e876e17341bf16f9e95397e14c2ac2720", - "application_health": "Healthy", - "native_data_change": false - }, - "provider_command_sha256": "ddfe2619f4847238fe79ec779a15b0d8df5d178cbed989ac2d73c1480b57a706", - "founder_approval_inferred": false, - "protected_database_contents_exported": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cleanup-offline.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cleanup-offline.json deleted file mode 100644 index 113769a..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cleanup-offline.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "all_checks_pass": true, - "only_disposable_offline_volume_removed": "infd-zlib-r1-restore-20261011", - "native_and_offcluster_original_snapshots_retained": true -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/confirmed-root-authority.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/confirmed-root-authority.json deleted file mode 100644 index f88d074..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/confirmed-root-authority.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "proposal_id": "INFD-WP-0009-T05/zlib85091-r1", - "memo_id": "infd-20261011-zlib-remediation-r1", - "memo_version": 1, - "memo_sha256": "5dea0d404e4d13683fbf71d56273c06db7a6a916ab68759a90d7394005eee206", - "source_revision": "312ab97e876e17341bf16f9e95397e14c2ac2720", - "executor_plan_sha256": "d871081999215bd3850ab1173064ed6cb6d7c23b26d9c3d5b3c5940e698d4c61", - "native_outcome_sha256": "7cd00372d1a7c4fb4dc92e2c550d8d4c586f96cda3aecec8016a883370893c86", - "activation": "APPROVED", - "authority_mode": "ADMINISTER", - "target": "realm:kubernetes/railiance01/informed-decision", - "verification_method": "root-fixed-origin-native-readback", - "runtime_authenticated_grant": false, - "outcome_query": "/lifecycle/memos/infd-20261011-zlib-remediation-r1/outcomes?version=1", - "verified_at": "2026-10-11T11:19:17.592644+00:00", - "review_memo_id": "review-2cf690a6-8c46-4faa-826c-a24a88d53dea", - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "bounded_outage_rollback_pre_authorized": true, - "note": "Template is not evidence/approval. Root sets scopedpreauthorizationtrue only after actual confirmed founder acceptance of this exact new packet/terms, retains original currentreadback before release.", - "native_observed_at": "2026-10-11T11:19:18.476232+00:00" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-backup.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-backup.json deleted file mode 100644 index 56f1eb0..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-backup.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "phase": "cutover-backup", - "all_checks_pass": true, - "raw_output_withheld": true, - "native_approval_verified_by_root": { - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "verified_at": "2026-10-11T11:17:33.972874+00:00" - }, - "native_authenticated_grant": false, - "source_readonly_baseline": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "snapshot_readonly": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "snapshot_sha256": "e0bac513f7445bf702c504ec7c15a1eb277ed7a48529099437d0dd63cf0679bb", - "snapshot_bytes": 23347200, - "snapshot_file": "cutover.sqlite", - "native_parent_private": true, - "native_uid": 10001, - "pod_name": "informed-decision-5557446c88-lvm88", - "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-copy.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-copy.json deleted file mode 100644 index 59a6882..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/cutover-copy.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "phase": "cutover-copy", - "all_checks_pass": true, - "raw_output_withheld": true, - "native_approval_verified_by_root": { - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "verified_at": "2026-10-11T11:18:05.531924+00:00" - }, - "native_authenticated_grant": false, - "snapshot_file": "cutover.sqlite", - "snapshot_bytes": 23347200, - "snapshot_sha256": "e0bac513f7445bf702c504ec7c15a1eb277ed7a48529099437d0dd63cf0679bb", - "snapshot_readonly": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "pod_name": "informed-decision-5557446c88-lvm88", - "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662", - "off_cluster_private_copy_verified": true, - "snapshot_contents_output": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-backup.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-backup.json deleted file mode 100644 index 5370dca..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-backup.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "phase": "drill-backup", - "all_checks_pass": true, - "raw_output_withheld": true, - "native_approval_verified_by_root": { - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "verified_at": "2026-10-11T11:11:32.769166+00:00" - }, - "native_authenticated_grant": false, - "source_readonly_baseline": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "snapshot_readonly": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "snapshot_sha256": "97d7dfb8fce4f720456468ff169f5e2b2bcb1edc328687b1a6c1225b66f2b0ba", - "snapshot_bytes": 23330816, - "snapshot_file": "drill.sqlite", - "native_parent_private": true, - "native_uid": 10001, - "pod_name": "informed-decision-5557446c88-lvm88", - "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-copy.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-copy.json deleted file mode 100644 index caa20f2..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/drill-copy.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "phase": "drill-copy", - "all_checks_pass": true, - "raw_output_withheld": true, - "native_approval_verified_by_root": { - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "verified_at": "2026-10-11T11:12:17.399753+00:00" - }, - "native_authenticated_grant": false, - "snapshot_file": "drill.sqlite", - "snapshot_bytes": 23330816, - "snapshot_sha256": "97d7dfb8fce4f720456468ff169f5e2b2bcb1edc328687b1a6c1225b66f2b0ba", - "snapshot_readonly": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "pod_name": "informed-decision-5557446c88-lvm88", - "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662", - "off_cluster_private_copy_verified": true, - "snapshot_contents_output": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-full.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-full.json deleted file mode 100644 index 8c02213..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-full.json +++ /dev/null @@ -1,2338 +0,0 @@ -{ - "SchemaVersion": 2, - "Trivy": { - "Version": "0.74.0" - }, - "ReportID": "01a12a94-4912-7435-81dc-e54820845251", - "CreatedAt": "2026-10-11T10:48:43.538280566Z", - "ArtifactID": "sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b", - "ArtifactName": "/artifact/image.tar", - "ArtifactType": "container_image", - "Metadata": { - "Size": 89032704, - "OS": { - "Family": "alpine", - "Name": "3.24.1" - }, - "ImageID": "sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b", - "DiffIDs": [ - "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c", - "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd", - "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366", - "sha256:4098ad033525263f92a8a9c51865596025d47018e078c0b1f7d9cd5663fc1ffe", - "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1", - "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c", - "sha256:361430a1113969743b99e237b20f023d5497493bcf96fb91af99ac0a0dbc4a59", - "sha256:cfc5939674d67f60b4411c24dfcde5622b712eeb7376cd0941a2751da80a6b02", - "sha256:21ff9188cb29ca95fe9be11355f70ab5586e8499e33b52a253d953fb3b75aea3", - "sha256:115811e30e0bde3e5a482191a32028a4323bacde928f17e808760ad2899407d6", - "sha256:08f4420173dc9f73d520ab9f1a08adeb802ab64d1e1a2f419af11ba2302b05ea" - ], - "ImageConfig": { - "architecture": "amd64", - "container": "21ba693e742e213525b526f83be1ea03cfbd875a87b6bce473e01fb8c38a5b91", - "created": "2026-10-11T09:37:06.27375084Z", - "docker_version": "27.5.1", - "history": [ - { - "created": "2026-06-16T00:01:29.967161902Z", - "created_by": "ADD alpine-minirootfs-3.24.1-x86_64.tar.gz / # buildkit", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-06-16T00:01:29.967161902Z", - "created_by": "CMD [\"/bin/sh\"]", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-01T00:05:24.224544843Z", - "created_by": "ENV PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-01T00:05:24.224544843Z", - "created_by": "ENV LANG=C.UTF-8", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-01T00:05:24.224544843Z", - "created_by": "RUN /bin/sh -c set -eux; \tapk add --no-cache \t\tca-certificates \t\ttzdata \t; # buildkit", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-09-01T00:05:24.224544843Z", - "created_by": "ENV GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-01T00:05:24.224544843Z", - "created_by": "ENV PYTHON_VERSION=3.12.14", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-01T00:05:24.224544843Z", - "created_by": "ENV PYTHON_SHA256=5c8462af5790baf43a321a1559dbe0db06d1be4300fb85fb53c40060668e548a", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-01T00:10:30.672225972Z", - "created_by": "RUN /bin/sh -c set -eux; \t\tapk add --no-cache --virtual .build-deps \t\tbluez-dev \t\tbzip2-dev \t\tdpkg-dev dpkg \t\tfindutils \t\tg++ \t\tgcc \t\tgdbm-dev \t\tgnupg \t\tlibc-dev \t\tlibffi-dev \t\tlibnsl-dev \t\tlibtirpc-dev \t\tlinux-headers \t\tmake \t\tncurses-dev \t\topenssl-dev \t\tpax-utils \t\treadline-dev \t\tsqlite-dev \t\ttar \t\ttcl-dev \t\ttk \t\ttk-dev \t\tutil-linux-dev \t\txz \t\txz-dev \t\tzlib-dev \t; \t\twget -O python.tar.xz \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz\"; \techo \"$PYTHON_SHA256 *python.tar.xz\" | sha256sum -c -; \twget -O python.tar.xz.asc \"https://www.python.org/ftp/python/${PYTHON_VERSION%%[a-z]*}/Python-$PYTHON_VERSION.tar.xz.asc\"; \tGNUPGHOME=\"$(mktemp -d)\"; export GNUPGHOME; \tgpg --batch --keyserver hkps://keys.openpgp.org --recv-keys \"$GPG_KEY\"; \tgpg --batch --verify python.tar.xz.asc python.tar.xz; \tgpgconf --kill all; \trm -rf \"$GNUPGHOME\" python.tar.xz.asc; \tmkdir -p /usr/src/python; \ttar --extract --directory /usr/src/python --strip-components=1 --file python.tar.xz; \trm python.tar.xz; \t\tcd /usr/src/python; \tgnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\"; \t./configure \t\t--build=\"$gnuArch\" \t\t--enable-loadable-sqlite-extensions \t\t--enable-option-checking=fatal \t\t--enable-shared \t\t$(test \"${gnuArch%%-*}\" != 'riscv64' \u0026\u0026 echo '--with-lto') \t\t--with-ensurepip \t; \tnproc=\"$(nproc)\"; \tEXTRA_CFLAGS=\"-DTHREAD_STACK_SIZE=0x100000\"; \tLDFLAGS=\"${LDFLAGS:-} -Wl,--strip-all\"; \tarch=\"$(apk --print-arch)\"; \tcase \"$arch\" in \t\tx86_64|aarch64) \t\t\tEXTRA_CFLAGS=\"${EXTRA_CFLAGS:-} -fno-omit-frame-pointer -mno-omit-leaf-frame-pointer\"; \t\t\t;; \t\tx86) \t\t\t;; \t\t*) \t\t\tEXTRA_CFLAGS=\"${EXTRA_CFLAGS:-} -fno-omit-frame-pointer\"; \t\t\t;; \tesac; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:-}\" \t; \trm python; \tmake -j \"$nproc\" \t\t\"EXTRA_CFLAGS=${EXTRA_CFLAGS:-}\" \t\t\"LDFLAGS=${LDFLAGS:-} -Wl,-rpath='\\$\\$ORIGIN/../lib'\" \t\tpython \t; \tmake install; \t\tcd /; \trm -rf /usr/src/python; \t\tfind /usr/local -depth \t\t\\( \t\t\t\\( -type d -a \\( -name test -o -name tests -o -name idle_test \\) \\) \t\t\t-o \\( -type f -a \\( -name '*.pyc' -o -name '*.pyo' -o -name 'libpython*.a' \\) \\) \t\t\\) -exec rm -rf '{}' + \t; \t\tfind /usr/local -type f -executable -not \\( -name '*tkinter*' \\) -exec scanelf --needed --nobanner --format '%n#p' '{}' ';' \t\t| tr ',' '\\n' \t\t| sort -u \t\t| awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' \t\t| xargs -rt apk add --no-network --virtual .python-rundeps \t; \tapk del --no-network .build-deps; \t\texport PYTHONDONTWRITEBYTECODE=1; \tpython3 --version; \tpip3 --version # buildkit", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-09-01T00:10:30.782726003Z", - "created_by": "RUN /bin/sh -c set -eux; \tfor src in idle3 pip3 pydoc3 python3 python3-config; do \t\tdst=\"$(echo \"$src\" | tr -d 3)\"; \t\t[ -s \"/usr/local/bin/$src\" ]; \t\t[ ! -e \"/usr/local/bin/$dst\" ]; \t\tln -svT \"$src\" \"/usr/local/bin/$dst\"; \tdone # buildkit", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-09-01T00:10:30.782726003Z", - "created_by": "CMD [\"python3\"]", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-11T00:49:47.962156876+02:00", - "created_by": "ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-09-11T00:49:47.962156876+02:00", - "created_by": "RUN /bin/sh -c apk add --no-cache 'libuuid\u003e=2.42.3-r1' \u0026\u0026 addgroup -S -g 10001 informed \u0026\u0026 adduser -S -u 10001 -G informed -H informed # buildkit", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-10-05T01:03:55.351944978+02:00", - "created_by": "COPY /opt/venv /opt/venv # buildkit", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-10-05T01:03:56.462092072+02:00", - "created_by": "RUN /bin/sh -c rm -rf /usr/local/bin/pip* /usr/local/lib/python3.12/site-packages/pip* /usr/local/lib/python3.12/site-packages/setuptools* /usr/local/lib/python3.12/site-packages/pkg_resources /opt/venv/bin/pip* /opt/venv/lib/python3.12/site-packages/pip* /opt/venv/lib/python3.12/site-packages/setuptools* /opt/venv/lib/python3.12/site-packages/pkg_resources # buildkit", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-10-05T01:03:56.675216821+02:00", - "created_by": "WORKDIR /app", - "comment": "buildkit.dockerfile.v0" - }, - { - "created": "2026-10-05T01:03:56.675216821+02:00", - "created_by": "USER 10001:10001", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-10-05T01:03:56.675216821+02:00", - "created_by": "EXPOSE [8080/tcp 8081/tcp]", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-10-05T01:03:56.675216821+02:00", - "created_by": "ENTRYPOINT [\"informed-decision-container\"]", - "comment": "buildkit.dockerfile.v0", - "empty_layer": true - }, - { - "created": "2026-10-11T01:15:11.991443676Z", - "created_by": "/bin/sh -c #(nop) ARG SOURCE_REVISION", - "empty_layer": true - }, - { - "created": "2026-10-11T01:21:04.716126392Z", - "created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.revision=3e4bcb26f8eeaa8c64345c565477b5ac8ccbb168 org.opencontainers.image.base.digest=sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173 social.coulomb.source.policy.sha256=9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62 social.coulomb.release.scope=INFD-WP-0009-T04/purpose-overlay-v1", - "empty_layer": true - }, - { - "created": "2026-10-11T01:21:04.787527697Z", - "created_by": "/bin/sh -c #(nop) COPY file:2be9cb36e7253e15733673359cda8caa22986b6888822a008c1b360d909994e0 in /opt/venv/lib/python3.12/site-packages/informed_decision/policy.py " - }, - { - "created": "2026-10-11T09:37:05.094824861Z", - "created_by": "/bin/sh -c #(nop) ARG SOURCE_REVISION=uncommitted-T05-local-preparation", - "empty_layer": true - }, - { - "created": "2026-10-11T09:37:05.210154454Z", - "created_by": "/bin/sh -c #(nop) ARG SOURCE_CONTEXT_SHA256", - "empty_layer": true - }, - { - "created": "2026-10-11T09:37:05.349758525Z", - "created_by": "/bin/sh -c #(nop) USER 0:0", - "empty_layer": true - }, - { - "created": "2026-10-11T09:37:05.460271111Z", - "created_by": "/bin/sh -c #(nop) COPY multi:e4d183fdf93ea6320911bb7398e4860a1b9b96fe0768cfd3d0260955517dd272 in /tmp/infd-zlib/x86_64/ " - }, - { - "created": "2026-10-11T09:37:06.14850806Z", - "created_by": "|2 SOURCE_CONTEXT_SHA256=c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d SOURCE_REVISION=9b4bb25d325698ae543e7e8a8a3c3592db1f58fc /bin/sh -c echo '63aeea03c15a2f9018f81805cfc8aa926bdf5cd68921f22149c2fbb5d0ee9f47 /tmp/infd-zlib/x86_64/zlib-1.3.2-r1.apk' | sha256sum -c - \u0026\u0026 echo '24fc2ccb08f3125429a38b31bd6c76a7b394c6236df0975760dc8259e50dfbd5 /tmp/infd-zlib/x86_64/APKINDEX.tar.gz' | sha256sum -c - \u0026\u0026 apk verify /tmp/infd-zlib/x86_64/zlib-1.3.2-r1.apk /tmp/infd-zlib/x86_64/APKINDEX.tar.gz \u0026\u0026 apk upgrade --no-logfile --no-network --no-cache --no-preupgrade --repositories-file /dev/null --repository /tmp/infd-zlib zlib \u0026\u0026 apk info --exists 'zlib=1.3.2-r1' \u0026\u0026 rm -rf /tmp/infd-zlib" - }, - { - "created": "2026-10-11T09:37:06.218269088Z", - "created_by": "/bin/sh -c #(nop) USER 10001:10001", - "empty_layer": true - }, - { - "created": "2026-10-11T09:37:06.27375084Z", - "created_by": "/bin/sh -c #(nop) LABEL org.opencontainers.image.revision=9b4bb25d325698ae543e7e8a8a3c3592db1f58fc org.opencontainers.image.base.digest=sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a social.coulomb.source.context.sha256=c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d social.coulomb.source.policy.sha256=9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62 social.coulomb.release.scope=INFD-WP-0009-T05/zlib-85091-r1", - "empty_layer": true - } - ], - "os": "linux", - "rootfs": { - "type": "layers", - "diff_ids": [ - "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c", - "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd", - "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366", - "sha256:4098ad033525263f92a8a9c51865596025d47018e078c0b1f7d9cd5663fc1ffe", - "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1", - "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c", - "sha256:361430a1113969743b99e237b20f023d5497493bcf96fb91af99ac0a0dbc4a59", - "sha256:cfc5939674d67f60b4411c24dfcde5622b712eeb7376cd0941a2751da80a6b02", - "sha256:21ff9188cb29ca95fe9be11355f70ab5586e8499e33b52a253d953fb3b75aea3", - "sha256:115811e30e0bde3e5a482191a32028a4323bacde928f17e808760ad2899407d6", - "sha256:08f4420173dc9f73d520ab9f1a08adeb802ab64d1e1a2f419af11ba2302b05ea" - ] - }, - "config": { - "Entrypoint": [ - "informed-decision-container" - ], - "Env": [ - "PATH=/opt/venv/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "LANG=C.UTF-8", - "GPG_KEY=7169605F62C751356D054A26A821E680E5FA6305", - "PYTHON_VERSION=3.12.14", - "PYTHON_SHA256=5c8462af5790baf43a321a1559dbe0db06d1be4300fb85fb53c40060668e548a", - "PYTHONDONTWRITEBYTECODE=1", - "PYTHONUNBUFFERED=1" - ], - "Image": "sha256:b0f10723d8eb4962d7eb1a0cc1d1b24c3113d1f67576916d85080f2f2d46c2e7", - "Labels": { - "org.opencontainers.image.base.digest": "sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "org.opencontainers.image.revision": "9b4bb25d325698ae543e7e8a8a3c3592db1f58fc", - "social.coulomb.release.scope": "INFD-WP-0009-T05/zlib-85091-r1", - "social.coulomb.source.context.sha256": "c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d", - "social.coulomb.source.policy.sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62" - }, - "User": "10001:10001", - "WorkingDir": "/app", - "ExposedPorts": { - "8080/tcp": {}, - "8081/tcp": {} - } - } - }, - "Layers": [ - { - "Size": 8697856, - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - { - "Size": 1538048, - "DiffID": "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd" - }, - { - "Size": 47150592, - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - { - "Size": 5120, - "DiffID": "sha256:4098ad033525263f92a8a9c51865596025d47018e078c0b1f7d9cd5663fc1ffe" - }, - { - "Size": 131584, - "DiffID": "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1" - }, - { - "Size": 30705664, - "DiffID": "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c" - }, - { - "Size": 12288, - "DiffID": "sha256:361430a1113969743b99e237b20f023d5497493bcf96fb91af99ac0a0dbc4a59" - }, - { - "Size": 1536, - "DiffID": "sha256:cfc5939674d67f60b4411c24dfcde5622b712eeb7376cd0941a2751da80a6b02" - }, - { - "Size": 15360, - "DiffID": "sha256:21ff9188cb29ca95fe9be11355f70ab5586e8499e33b52a253d953fb3b75aea3" - }, - { - "Size": 588288, - "DiffID": "sha256:115811e30e0bde3e5a482191a32028a4323bacde928f17e808760ad2899407d6" - }, - { - "Size": 186368, - "DiffID": "sha256:08f4420173dc9f73d520ab9f1a08adeb802ab64d1e1a2f419af11ba2302b05ea" - } - ] - }, - "Results": [ - { - "Target": "/artifact/image.tar (alpine 3.24.1)", - "Class": "os-pkgs", - "Type": "alpine", - "Packages": [ - { - "ID": ".python-rundeps@20260901.001029", - "Name": ".python-rundeps", - "Identifier": { - "PURL": "pkg:apk/alpine/.python-rundeps@20260901.001029?arch=noarch\u0026distro=3.24.1", - "UID": "62fa84f88695c077" - }, - "Version": "20260901.001029", - "Arch": "noarch", - "DependsOn": [ - "gdbm@1.26-r0", - "libbz2@1.0.8-r6", - "libcrypto3@3.5.8-r0", - "libffi@3.5.2-r1", - "libncursesw@6.6_p20260516-r0", - "libnsl@2.0.1-r2", - "libpanelw@6.6_p20260516-r0", - "libssl3@3.5.8-r0", - "libtirpc@1.3.5-r1", - "libuuid@2.42.3-r1", - "musl@1.2.6-r2", - "readline@8.3.3-r1", - "sqlite-libs@3.53.4-r0", - "xz-libs@5.8.3-r0", - "zlib@1.3.2-r1" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:51d0c1e7d88a414345799f0309185c625013ef32", - "AnalyzedBy": "apk" - }, - { - "ID": "alpine-baselayout@3.7.2-r1", - "Name": "alpine-baselayout", - "Identifier": { - "PURL": "pkg:apk/alpine/alpine-baselayout@3.7.2-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "1b9c543300f8073e" - }, - "Version": "3.7.2-r1", - "Arch": "x86_64", - "SrcName": "alpine-baselayout", - "SrcVersion": "3.7.2-r1", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "alpine-baselayout-data@3.7.2-r1", - "busybox-binsh@1.37.0-r31" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:f0fcfdc2f4da058af6473bc45c4eab62916225b2", - "InstalledFiles": [ - "etc/motd", - "etc/crontabs/root", - "etc/modprobe.d/aliases.conf", - "etc/modprobe.d/blacklist.conf", - "etc/modprobe.d/i386.conf", - "etc/profile.d/20locale.sh", - "etc/profile.d/README", - "etc/profile.d/color_prompt.sh.disabled", - "usr/lib/sysctl.d/00-alpine.conf", - "var/lock", - "var/run", - "var/spool/mail", - "var/spool/cron/crontabs" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "alpine-baselayout-data@3.7.2-r1", - "Name": "alpine-baselayout-data", - "Identifier": { - "PURL": "pkg:apk/alpine/alpine-baselayout-data@3.7.2-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "fcc3cd90122d8aa7" - }, - "Version": "3.7.2-r1", - "Arch": "x86_64", - "SrcName": "alpine-baselayout", - "SrcVersion": "3.7.2-r1", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:c6f9bded17d844b6f1b0bb5766d2c35c0d5c8508", - "InstalledFiles": [ - "etc/fstab", - "etc/group", - "etc/hostname", - "etc/hosts", - "etc/inittab", - "etc/modules", - "etc/mtab", - "etc/nsswitch.conf", - "etc/passwd", - "etc/profile", - "etc/protocols", - "etc/services", - "etc/shadow", - "etc/shells", - "etc/sysctl.conf" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "alpine-keys@2.6-r0", - "Name": "alpine-keys", - "Identifier": { - "PURL": "pkg:apk/alpine/alpine-keys@2.6-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "cb1f6c8fa74713e0" - }, - "Version": "2.6-r0", - "Arch": "x86_64", - "SrcName": "alpine-keys", - "SrcVersion": "2.6-r0", - "Licenses": [ - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:d8d6b80e53c059a77e4915da78ba964f3ffea240", - "InstalledFiles": [ - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub", - "usr/share/apk/keys/alpine-devel@lists.alpinelinux.org-66ba20fe.rsa.pub", - "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-58199dcc.rsa.pub", - "usr/share/apk/keys/aarch64/alpine-devel@lists.alpinelinux.org-616ae350.rsa.pub", - "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", - "usr/share/apk/keys/armhf/alpine-devel@lists.alpinelinux.org-616a9724.rsa.pub", - "usr/share/apk/keys/armv7/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", - "usr/share/apk/keys/armv7/alpine-devel@lists.alpinelinux.org-616adfeb.rsa.pub", - "usr/share/apk/keys/loongarch64/alpine-devel@lists.alpinelinux.org-66ba20fe.rsa.pub", - "usr/share/apk/keys/mips64/alpine-devel@lists.alpinelinux.org-5e69ca50.rsa.pub", - "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-58cbb476.rsa.pub", - "usr/share/apk/keys/ppc64le/alpine-devel@lists.alpinelinux.org-616abc23.rsa.pub", - "usr/share/apk/keys/riscv64/alpine-devel@lists.alpinelinux.org-60ac2099.rsa.pub", - "usr/share/apk/keys/riscv64/alpine-devel@lists.alpinelinux.org-616db30d.rsa.pub", - "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-58e4f17d.rsa.pub", - "usr/share/apk/keys/s390x/alpine-devel@lists.alpinelinux.org-616ac3bc.rsa.pub", - "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", - "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", - "usr/share/apk/keys/x86/alpine-devel@lists.alpinelinux.org-61666e3f.rsa.pub", - "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", - "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", - "usr/share/apk/keys/x86_64/alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "alpine-release@3.24.1-r0", - "Name": "alpine-release", - "Identifier": { - "PURL": "pkg:apk/alpine/alpine-release@3.24.1-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "6cfad6f36e1f31aa" - }, - "Version": "3.24.1-r0", - "Arch": "x86_64", - "SrcName": "alpine-base", - "SrcVersion": "3.24.1-r0", - "Licenses": [ - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "alpine-keys@2.6-r0" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:bc6912a25cdc7733e0035ed509eceaa4026946e3", - "InstalledFiles": [ - "etc/alpine-release", - "etc/issue", - "etc/os-release", - "etc/secfixes.d/alpine", - "usr/lib/os-release" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "apk-tools@3.0.6-r0", - "Name": "apk-tools", - "Identifier": { - "PURL": "pkg:apk/alpine/apk-tools@3.0.6-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "b37730591f24ae44" - }, - "Version": "3.0.6-r0", - "Arch": "x86_64", - "SrcName": "apk-tools", - "SrcVersion": "3.0.6-r0", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "ca-certificates-bundle@20260611-r0", - "libapk@3.0.6-r0", - "libcrypto3@3.5.8-r0", - "musl@1.2.6-r2", - "zlib@1.3.2-r1" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:043333589798a1f52e09ae63f026c6c8fa676d34", - "InstalledFiles": [ - "sbin/apk" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "busybox@1.37.0-r31", - "Name": "busybox", - "Identifier": { - "PURL": "pkg:apk/alpine/busybox@1.37.0-r31?arch=x86_64\u0026distro=3.24.1", - "UID": "771020c43c8440bf" - }, - "Version": "1.37.0-r31", - "Arch": "x86_64", - "SrcName": "busybox", - "SrcVersion": "1.37.0-r31", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Sören Tempel \u003csoeren+alpine@soeren-tempel.net\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:b5405ebc02f7dd8be95b6265c54b243d5456ab8f", - "InstalledFiles": [ - "bin/busybox", - "etc/securetty", - "etc/busybox-paths.d/busybox", - "etc/logrotate.d/acpid", - "etc/network/if-up.d/dad", - "etc/udhcpc/udhcpc.conf", - "usr/share/udhcpc/default.script" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "busybox-binsh@1.37.0-r31", - "Name": "busybox-binsh", - "Identifier": { - "PURL": "pkg:apk/alpine/busybox-binsh@1.37.0-r31?arch=x86_64\u0026distro=3.24.1", - "UID": "1ec8a3d5d2b63297" - }, - "Version": "1.37.0-r31", - "Arch": "x86_64", - "SrcName": "busybox", - "SrcVersion": "1.37.0-r31", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Sören Tempel \u003csoeren+alpine@soeren-tempel.net\u003e", - "DependsOn": [ - "busybox@1.37.0-r31" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:5cbd14acc6f1804c5bac6c77dc2c492f010b0fc7", - "InstalledFiles": [ - "bin/sh" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "ca-certificates@20260611-r0", - "Name": "ca-certificates", - "Identifier": { - "PURL": "pkg:apk/alpine/ca-certificates@20260611-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "2a3c316ee83c6198" - }, - "Version": "20260611-r0", - "Arch": "x86_64", - "SrcName": "ca-certificates", - "SrcVersion": "20260611-r0", - "Licenses": [ - "MPL-2.0", - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "busybox-binsh@1.37.0-r31", - "libcrypto3@3.5.8-r0", - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd" - }, - "Digest": "sha1:8f2a04abeb54b331e2970a0a0dffb7bd6bdde567", - "InstalledFiles": [ - "etc/ca-certificates.conf", - "etc/apk/protected_paths.d/ca-certificates.list", - "etc/ca-certificates/update.d/certhash", - "usr/bin/c_rehash", - "usr/sbin/update-ca-certificates", - "usr/share/ca-certificates/mozilla/ACCVRAIZ1.crt", - "usr/share/ca-certificates/mozilla/AC_RAIZ_FNMT-RCM.crt", - "usr/share/ca-certificates/mozilla/AC_RAIZ_FNMT-RCM_SERVIDORES_SEGUROS.crt", - "usr/share/ca-certificates/mozilla/ANF_Secure_Server_Root_CA.crt", - "usr/share/ca-certificates/mozilla/Actalis_Authentication_Root_CA.crt", - "usr/share/ca-certificates/mozilla/Amazon_Root_CA_1.crt", - "usr/share/ca-certificates/mozilla/Amazon_Root_CA_2.crt", - "usr/share/ca-certificates/mozilla/Amazon_Root_CA_3.crt", - "usr/share/ca-certificates/mozilla/Amazon_Root_CA_4.crt", - "usr/share/ca-certificates/mozilla/Atos_TrustedRoot_2011.crt", - "usr/share/ca-certificates/mozilla/Atos_TrustedRoot_Root_CA_ECC_TLS_2021.crt", - "usr/share/ca-certificates/mozilla/Atos_TrustedRoot_Root_CA_RSA_TLS_2021.crt", - "usr/share/ca-certificates/mozilla/Autoridad_de_Certificacion_Firmaprofesional_CIF_A62634068.crt", - "usr/share/ca-certificates/mozilla/BJCA_Global_Root_CA1.crt", - "usr/share/ca-certificates/mozilla/BJCA_Global_Root_CA2.crt", - "usr/share/ca-certificates/mozilla/Buypass_Class_2_Root_CA.crt", - "usr/share/ca-certificates/mozilla/Buypass_Class_3_Root_CA.crt", - "usr/share/ca-certificates/mozilla/CA_Disig_Root_R2.crt", - "usr/share/ca-certificates/mozilla/CFCA_EV_ROOT.crt", - "usr/share/ca-certificates/mozilla/COMODO_ECC_Certification_Authority.crt", - "usr/share/ca-certificates/mozilla/COMODO_RSA_Certification_Authority.crt", - "usr/share/ca-certificates/mozilla/Certainly_Root_E1.crt", - "usr/share/ca-certificates/mozilla/Certainly_Root_R1.crt", - "usr/share/ca-certificates/mozilla/Certigna_Root_CA.crt", - "usr/share/ca-certificates/mozilla/Certum_EC-384_CA.crt", - "usr/share/ca-certificates/mozilla/Certum_Trusted_Network_CA.crt", - "usr/share/ca-certificates/mozilla/Certum_Trusted_Network_CA_2.crt", - "usr/share/ca-certificates/mozilla/Certum_Trusted_Root_CA.crt", - "usr/share/ca-certificates/mozilla/D-TRUST_BR_Root_CA_1_2020.crt", - "usr/share/ca-certificates/mozilla/D-TRUST_BR_Root_CA_2_2023.crt", - "usr/share/ca-certificates/mozilla/D-TRUST_EV_Root_CA_1_2020.crt", - "usr/share/ca-certificates/mozilla/D-TRUST_EV_Root_CA_2_2023.crt", - "usr/share/ca-certificates/mozilla/D-TRUST_Root_Class_3_CA_2_2009.crt", - "usr/share/ca-certificates/mozilla/D-TRUST_Root_Class_3_CA_2_EV_2009.crt", - "usr/share/ca-certificates/mozilla/DigiCert_Assured_ID_Root_G2.crt", - "usr/share/ca-certificates/mozilla/DigiCert_Assured_ID_Root_G3.crt", - "usr/share/ca-certificates/mozilla/DigiCert_Global_Root_G2.crt", - "usr/share/ca-certificates/mozilla/DigiCert_Global_Root_G3.crt", - "usr/share/ca-certificates/mozilla/DigiCert_TLS_ECC_P384_Root_G5.crt", - "usr/share/ca-certificates/mozilla/DigiCert_TLS_RSA4096_Root_G5.crt", - "usr/share/ca-certificates/mozilla/DigiCert_Trusted_Root_G4.crt", - "usr/share/ca-certificates/mozilla/GDCA_TrustAUTH_R5_ROOT.crt", - "usr/share/ca-certificates/mozilla/GTS_Root_R1.crt", - "usr/share/ca-certificates/mozilla/GTS_Root_R3.crt", - "usr/share/ca-certificates/mozilla/GTS_Root_R4.crt", - "usr/share/ca-certificates/mozilla/GlobalSign_ECC_Root_CA_-_R4.crt", - "usr/share/ca-certificates/mozilla/GlobalSign_ECC_Root_CA_-_R5.crt", - "usr/share/ca-certificates/mozilla/GlobalSign_Root_CA_-_R3.crt", - "usr/share/ca-certificates/mozilla/GlobalSign_Root_CA_-_R6.crt", - "usr/share/ca-certificates/mozilla/GlobalSign_Root_E46.crt", - "usr/share/ca-certificates/mozilla/GlobalSign_Root_R46.crt", - "usr/share/ca-certificates/mozilla/Go_Daddy_Root_Certificate_Authority_-_G2.crt", - "usr/share/ca-certificates/mozilla/HARICA_TLS_ECC_Root_CA_2021.crt", - "usr/share/ca-certificates/mozilla/HARICA_TLS_RSA_Root_CA_2021.crt", - "usr/share/ca-certificates/mozilla/Hellenic_Academic_and_Research_Institutions_ECC_RootCA_2015.crt", - "usr/share/ca-certificates/mozilla/Hellenic_Academic_and_Research_Institutions_RootCA_2015.crt", - "usr/share/ca-certificates/mozilla/HiPKI_Root_CA_-_G1.crt", - "usr/share/ca-certificates/mozilla/Hongkong_Post_Root_CA_3.crt", - "usr/share/ca-certificates/mozilla/ISRG_Root_X1.crt", - "usr/share/ca-certificates/mozilla/ISRG_Root_X2.crt", - "usr/share/ca-certificates/mozilla/IdenTrust_Commercial_Root_CA_1.crt", - "usr/share/ca-certificates/mozilla/IdenTrust_Public_Sector_Root_CA_1.crt", - "usr/share/ca-certificates/mozilla/Izenpe.com.crt", - "usr/share/ca-certificates/mozilla/Microsec_e-Szigno_Root_CA_2009.crt", - "usr/share/ca-certificates/mozilla/Microsoft_ECC_Root_Certificate_Authority_2017.crt", - "usr/share/ca-certificates/mozilla/Microsoft_RSA_Root_Certificate_Authority_2017.crt", - "usr/share/ca-certificates/mozilla/NAVER_Global_Root_Certification_Authority.crt", - "usr/share/ca-certificates/mozilla/NetLock_Arany_=Class_Gold=_Főtanúsítvány.crt", - "usr/share/ca-certificates/mozilla/OISTE_Server_Root_ECC_G1.crt", - "usr/share/ca-certificates/mozilla/OISTE_Server_Root_RSA_G1.crt", - "usr/share/ca-certificates/mozilla/OISTE_WISeKey_Global_Root_GB_CA.crt", - "usr/share/ca-certificates/mozilla/OISTE_WISeKey_Global_Root_GC_CA.crt", - "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_1_G3.crt", - "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_2_G3.crt", - "usr/share/ca-certificates/mozilla/QuoVadis_Root_CA_3_G3.crt", - "usr/share/ca-certificates/mozilla/SSL.com_EV_Root_Certification_Authority_ECC.crt", - "usr/share/ca-certificates/mozilla/SSL.com_EV_Root_Certification_Authority_RSA_R2.crt", - "usr/share/ca-certificates/mozilla/SSL.com_Root_Certification_Authority_ECC.crt", - "usr/share/ca-certificates/mozilla/SSL.com_Root_Certification_Authority_RSA.crt", - "usr/share/ca-certificates/mozilla/SSL.com_TLS_ECC_Root_CA_2022.crt", - "usr/share/ca-certificates/mozilla/SSL.com_TLS_RSA_Root_CA_2022.crt", - "usr/share/ca-certificates/mozilla/SZAFIR_ROOT_CA2.crt", - "usr/share/ca-certificates/mozilla/Sectigo_Public_Server_Authentication_Root_E46.crt", - "usr/share/ca-certificates/mozilla/Sectigo_Public_Server_Authentication_Root_R46.crt", - "usr/share/ca-certificates/mozilla/SecureSign_Root_CA14.crt", - "usr/share/ca-certificates/mozilla/SecureSign_Root_CA15.crt", - "usr/share/ca-certificates/mozilla/Security_Communication_ECC_RootCA1.crt", - "usr/share/ca-certificates/mozilla/Security_Communication_RootCA2.crt", - "usr/share/ca-certificates/mozilla/Starfield_Root_Certificate_Authority_-_G2.crt", - "usr/share/ca-certificates/mozilla/Starfield_Services_Root_Certificate_Authority_-_G2.crt", - "usr/share/ca-certificates/mozilla/SwissSign_RSA_TLS_Root_CA_2022_-_1.crt", - "usr/share/ca-certificates/mozilla/T-TeleSec_GlobalRoot_Class_2.crt", - "usr/share/ca-certificates/mozilla/T-TeleSec_GlobalRoot_Class_3.crt", - "usr/share/ca-certificates/mozilla/TUBITAK_Kamu_SM_SSL_Kok_Sertifikasi_-_Surum_1.crt", - "usr/share/ca-certificates/mozilla/TWCA_CYBER_Root_CA.crt", - "usr/share/ca-certificates/mozilla/TWCA_Global_Root_CA.crt", - "usr/share/ca-certificates/mozilla/TWCA_Root_Certification_Authority.crt", - "usr/share/ca-certificates/mozilla/Telekom_Security_TLS_ECC_Root_2020.crt", - "usr/share/ca-certificates/mozilla/Telekom_Security_TLS_RSA_Root_2023.crt", - "usr/share/ca-certificates/mozilla/Telia_Root_CA_v2.crt", - "usr/share/ca-certificates/mozilla/TrustAsia_Global_Root_CA_G3.crt", - "usr/share/ca-certificates/mozilla/TrustAsia_Global_Root_CA_G4.crt", - "usr/share/ca-certificates/mozilla/TrustAsia_TLS_ECC_Root_CA.crt", - "usr/share/ca-certificates/mozilla/TrustAsia_TLS_RSA_Root_CA.crt", - "usr/share/ca-certificates/mozilla/TunTrust_Root_CA.crt", - "usr/share/ca-certificates/mozilla/UCA_Extended_Validation_Root.crt", - "usr/share/ca-certificates/mozilla/UCA_Global_G2_Root.crt", - "usr/share/ca-certificates/mozilla/USERTrust_ECC_Certification_Authority.crt", - "usr/share/ca-certificates/mozilla/USERTrust_RSA_Certification_Authority.crt", - "usr/share/ca-certificates/mozilla/certSIGN_Root_CA_G2.crt", - "usr/share/ca-certificates/mozilla/e-Szigno_Root_CA_2017.crt", - "usr/share/ca-certificates/mozilla/e-Szigno_TLS_Root_CA_2023.crt", - "usr/share/ca-certificates/mozilla/ePKI_Root_Certification_Authority.crt", - "usr/share/ca-certificates/mozilla/emSign_ECC_Root_CA_-_C3.crt", - "usr/share/ca-certificates/mozilla/emSign_ECC_Root_CA_-_G3.crt", - "usr/share/ca-certificates/mozilla/emSign_Root_CA_-_C1.crt", - "usr/share/ca-certificates/mozilla/emSign_Root_CA_-_G1.crt", - "usr/share/ca-certificates/mozilla/vTrus_ECC_Root_CA.crt", - "usr/share/ca-certificates/mozilla/vTrus_Root_CA.crt" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "ca-certificates-bundle@20260611-r0", - "Name": "ca-certificates-bundle", - "Identifier": { - "PURL": "pkg:apk/alpine/ca-certificates-bundle@20260611-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "20337c2ea28bef28" - }, - "Version": "20260611-r0", - "Arch": "x86_64", - "SrcName": "ca-certificates", - "SrcVersion": "20260611-r0", - "Licenses": [ - "MPL-2.0", - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:c9534a03750bdfae341f10969016090fc11febbd", - "InstalledFiles": [ - "etc/ssl/cert.pem", - "etc/ssl/certs/ca-certificates.crt", - "etc/ssl1.1/cert.pem", - "etc/ssl1.1/certs" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "gdbm@1.26-r0", - "Name": "gdbm", - "Identifier": { - "PURL": "pkg:apk/alpine/gdbm@1.26-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "5eb1a3f86262ca77" - }, - "Version": "1.26-r0", - "Arch": "x86_64", - "SrcName": "gdbm", - "SrcVersion": "1.26-r0", - "Licenses": [ - "GPL-3.0-or-later" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:619493d8124fe49a8ee1d8f7a1372cf7e3977337", - "InstalledFiles": [ - "usr/lib/libgdbm.so.6", - "usr/lib/libgdbm.so.6.0.0", - "usr/lib/libgdbm_compat.so.4", - "usr/lib/libgdbm_compat.so.4.0.0" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "keyutils-libs@1.6.3-r4", - "Name": "keyutils-libs", - "Identifier": { - "PURL": "pkg:apk/alpine/keyutils-libs@1.6.3-r4?arch=x86_64\u0026distro=3.24.1", - "UID": "a1a29120eb342032" - }, - "Version": "1.6.3-r4", - "Arch": "x86_64", - "SrcName": "keyutils", - "SrcVersion": "1.6.3-r4", - "Licenses": [ - "GPL-2.0-or-later", - "LGPL-2.0-or-later" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:b7331c96f077fc3522ee4361a441d1097204cd90", - "InstalledFiles": [ - "usr/lib/libkeyutils.so.1", - "usr/lib/libkeyutils.so.1.10" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "krb5-conf@1.0-r2", - "Name": "krb5-conf", - "Identifier": { - "PURL": "pkg:apk/alpine/krb5-conf@1.0-r2?arch=x86_64\u0026distro=3.24.1", - "UID": "e7cf94ba8b6dbc33" - }, - "Version": "1.0-r2", - "Arch": "x86_64", - "SrcName": "krb5-conf", - "SrcVersion": "1.0-r2", - "Licenses": [ - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:40a11e0690a59e110367b62a80661024e9942a2d", - "InstalledFiles": [ - "etc/krb5.conf" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "krb5-libs@1.22.2-r1", - "Name": "krb5-libs", - "Identifier": { - "PURL": "pkg:apk/alpine/krb5-libs@1.22.2-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "958f8b4a4f087dac" - }, - "Version": "1.22.2-r1", - "Arch": "x86_64", - "SrcName": "krb5", - "SrcVersion": "1.22.2-r1", - "Licenses": [ - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "keyutils-libs@1.6.3-r4", - "krb5-conf@1.0-r2", - "libcom_err@1.47.4-r0", - "libcrypto3@3.5.8-r0", - "libssl3@3.5.8-r0", - "libverto@0.3.2-r2", - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:ef02346e21b817ff379e4601e5b1fc9760e5e31a", - "InstalledFiles": [ - "usr/lib/libgssapi_krb5.so.2", - "usr/lib/libgssapi_krb5.so.2.2", - "usr/lib/libgssrpc.so.4", - "usr/lib/libgssrpc.so.4.2", - "usr/lib/libk5crypto.so.3", - "usr/lib/libk5crypto.so.3.1", - "usr/lib/libkadm5clnt_mit.so.12", - "usr/lib/libkadm5clnt_mit.so.12.0", - "usr/lib/libkadm5srv_mit.so.12", - "usr/lib/libkadm5srv_mit.so.12.0", - "usr/lib/libkdb5.so.10", - "usr/lib/libkdb5.so.10.0", - "usr/lib/libkrad.so.0", - "usr/lib/libkrad.so.0.0", - "usr/lib/libkrb5.so.3", - "usr/lib/libkrb5.so.3.3", - "usr/lib/libkrb5support.so.0", - "usr/lib/libkrb5support.so.0.1", - "usr/lib/krb5/plugins/kdb/db2.so", - "usr/lib/krb5/plugins/preauth/otp.so", - "usr/lib/krb5/plugins/preauth/spake.so", - "usr/lib/krb5/plugins/preauth/test.so", - "usr/lib/krb5/plugins/tls/k5tls.so" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libapk@3.0.6-r0", - "Name": "libapk", - "Identifier": { - "PURL": "pkg:apk/alpine/libapk@3.0.6-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "779641db16cf6477" - }, - "Version": "3.0.6-r0", - "Arch": "x86_64", - "SrcName": "apk-tools", - "SrcVersion": "3.0.6-r0", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "libcrypto3@3.5.8-r0", - "libssl3@3.5.8-r0", - "musl@1.2.6-r2", - "zlib@1.3.2-r1" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:523a8f58104589f74f743d501c81bf6517155378", - "InstalledFiles": [ - "usr/lib/libapk.so.3.0.0" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libbz2@1.0.8-r6", - "Name": "libbz2", - "Identifier": { - "PURL": "pkg:apk/alpine/libbz2@1.0.8-r6?arch=x86_64\u0026distro=3.24.1", - "UID": "48e17539404e2d56" - }, - "Version": "1.0.8-r6", - "Arch": "x86_64", - "SrcName": "bzip2", - "SrcVersion": "1.0.8-r6", - "Licenses": [ - "bzip-2-1.0.6" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:08566ba5c7af97c25474be8b24e8b48f0116b67d", - "InstalledFiles": [ - "usr/lib/libbz2.so.1", - "usr/lib/libbz2.so.1.0.8" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libcom_err@1.47.4-r0", - "Name": "libcom_err", - "Identifier": { - "PURL": "pkg:apk/alpine/libcom_err@1.47.4-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "3fc35a3f66d192af" - }, - "Version": "1.47.4-r0", - "Arch": "x86_64", - "SrcName": "e2fsprogs", - "SrcVersion": "1.47.4-r0", - "Licenses": [ - "GPL-2.0-or-later", - "LGPL-2.0-or-later", - "BSD-3-Clause", - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:4b9fb5899ea0b1c0cfbb2a2c5952704437d4ecef", - "InstalledFiles": [ - "usr/lib/libcom_err.so.2", - "usr/lib/libcom_err.so.2.1" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libcrypto3@3.5.8-r0", - "Name": "libcrypto3", - "Identifier": { - "PURL": "pkg:apk/alpine/libcrypto3@3.5.8-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "4299bba2ba9c96aa" - }, - "Version": "3.5.8-r0", - "Arch": "x86_64", - "SrcName": "openssl", - "SrcVersion": "3.5.8-r0", - "Licenses": [ - "Apache-2.0" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:1b3b9fc0c136f4936932bcb2ec21499f0ae5dfd3", - "InstalledFiles": [ - "etc/ssl/ct_log_list.cnf", - "etc/ssl/ct_log_list.cnf.dist", - "etc/ssl/openssl.cnf", - "etc/ssl/openssl.cnf.dist", - "usr/lib/libcrypto.so.3", - "usr/lib/engines-3/afalg.so", - "usr/lib/engines-3/capi.so", - "usr/lib/engines-3/loader_attic.so", - "usr/lib/engines-3/padlock.so", - "usr/lib/ossl-modules/legacy.so" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libffi@3.5.2-r1", - "Name": "libffi", - "Identifier": { - "PURL": "pkg:apk/alpine/libffi@3.5.2-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "d3eaa921287b1ccd" - }, - "Version": "3.5.2-r1", - "Arch": "x86_64", - "SrcName": "libffi", - "SrcVersion": "3.5.2-r1", - "Licenses": [ - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:2f7b261f7fcecc4eebd8b330e7f6fb80713bfe3a", - "InstalledFiles": [ - "usr/lib/libffi.so.8", - "usr/lib/libffi.so.8.2.0" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libintl@1.0-r0", - "Name": "libintl", - "Identifier": { - "PURL": "pkg:apk/alpine/libintl@1.0-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "25c18808d8fc87e0" - }, - "Version": "1.0-r0", - "Arch": "x86_64", - "SrcName": "gettext", - "SrcVersion": "1.0-r0", - "Licenses": [ - "LGPL-2.1-or-later" - ], - "Maintainer": "Carlo Landmeter \u003cclandmeter@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:282d6642ccac87237e43a864159d73cf5abe7550", - "InstalledFiles": [ - "usr/lib/libintl.so.8", - "usr/lib/libintl.so.8.4.6" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libncursesw@6.6_p20260516-r0", - "Name": "libncursesw", - "Identifier": { - "PURL": "pkg:apk/alpine/libncursesw@6.6_p20260516-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "e8939f45e2191bcc" - }, - "Version": "6.6_p20260516-r0", - "Arch": "x86_64", - "SrcName": "ncurses", - "SrcVersion": "6.6_p20260516-r0", - "Licenses": [ - "X-11" - ], - "Maintainer": "Milan P. Stanić \u003cmps@arvanta.net\u003e", - "DependsOn": [ - "musl@1.2.6-r2", - "ncurses-terminfo-base@6.6_p20260516-r0" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:9f318e6e324d6827274829194cf6ac6afbaded12", - "InstalledFiles": [ - "usr/lib/libncursesw.so.6", - "usr/lib/libncursesw.so.6.6" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libnsl@2.0.1-r2", - "Name": "libnsl", - "Identifier": { - "PURL": "pkg:apk/alpine/libnsl@2.0.1-r2?arch=x86_64\u0026distro=3.24.1", - "UID": "3d893c60d41da67b" - }, - "Version": "2.0.1-r2", - "Arch": "x86_64", - "SrcName": "libnsl", - "SrcVersion": "2.0.1-r2", - "Licenses": [ - "LGPL-2.0-or-later" - ], - "DependsOn": [ - "libintl@1.0-r0", - "libtirpc@1.3.5-r1", - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:5194adbf26560b52be318481a7af294ab05fd795", - "InstalledFiles": [ - "usr/lib/libnsl.so.3", - "usr/lib/libnsl.so.3.0.0" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libpanelw@6.6_p20260516-r0", - "Name": "libpanelw", - "Identifier": { - "PURL": "pkg:apk/alpine/libpanelw@6.6_p20260516-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "ef1ad9bb256b373" - }, - "Version": "6.6_p20260516-r0", - "Arch": "x86_64", - "SrcName": "ncurses", - "SrcVersion": "6.6_p20260516-r0", - "Licenses": [ - "X-11" - ], - "Maintainer": "Milan P. Stanić \u003cmps@arvanta.net\u003e", - "DependsOn": [ - "libncursesw@6.6_p20260516-r0", - "musl@1.2.6-r2", - "ncurses-terminfo-base@6.6_p20260516-r0" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:149bdc434232ed11fc5bb975ba519a07154a6f56", - "InstalledFiles": [ - "usr/lib/libpanelw.so.6", - "usr/lib/libpanelw.so.6.6" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libssl3@3.5.8-r0", - "Name": "libssl3", - "Identifier": { - "PURL": "pkg:apk/alpine/libssl3@3.5.8-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "f290ab59b88b1c4a" - }, - "Version": "3.5.8-r0", - "Arch": "x86_64", - "SrcName": "openssl", - "SrcVersion": "3.5.8-r0", - "Licenses": [ - "Apache-2.0" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "libcrypto3@3.5.8-r0", - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:5f9b1e55d821953d2e2b1db322dad70a3b5041ea", - "InstalledFiles": [ - "usr/lib/libssl.so.3" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libtirpc@1.3.5-r1", - "Name": "libtirpc", - "Identifier": { - "PURL": "pkg:apk/alpine/libtirpc@1.3.5-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "471c8d955ca03be5" - }, - "Version": "1.3.5-r1", - "Arch": "x86_64", - "SrcName": "libtirpc", - "SrcVersion": "1.3.5-r1", - "Licenses": [ - "BSD-3-Clause" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "krb5-libs@1.22.2-r1", - "libtirpc-conf@1.3.5-r1", - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:c32865bd86a8b8829d152279cabbc5ac6d430eb2", - "InstalledFiles": [ - "usr/lib/libtirpc.so.3", - "usr/lib/libtirpc.so.3.0.0" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libtirpc-conf@1.3.5-r1", - "Name": "libtirpc-conf", - "Identifier": { - "PURL": "pkg:apk/alpine/libtirpc-conf@1.3.5-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "3437392c14aad33f" - }, - "Version": "1.3.5-r1", - "Arch": "x86_64", - "SrcName": "libtirpc", - "SrcVersion": "1.3.5-r1", - "Licenses": [ - "BSD-3-Clause" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:0c17d9988dc986c90e22f79778df047eca9973ff", - "InstalledFiles": [ - "etc/bindresvport.blacklist", - "etc/netconfig" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libuuid@2.42.3-r1", - "Name": "libuuid", - "Identifier": { - "PURL": "pkg:apk/alpine/libuuid@2.42.3-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "c444e9cdd1bdda40" - }, - "Version": "2.42.3-r1", - "Arch": "x86_64", - "SrcName": "util-linux", - "SrcVersion": "2.42.3-r1", - "Licenses": [ - "BSD-3-Clause" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:f9b4a8a412a0c07a489e46fb5db41f9d0dc84d4ec38a4e2c6ab9531f44e0d9f1" - }, - "Digest": "sha1:8f041ef637ad75f381cc0c034f2afbbb0303264e", - "InstalledFiles": [ - "usr/lib/libuuid.so.1", - "usr/lib/libuuid.so.1.3.0" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "libverto@0.3.2-r2", - "Name": "libverto", - "Identifier": { - "PURL": "pkg:apk/alpine/libverto@0.3.2-r2?arch=x86_64\u0026distro=3.24.1", - "UID": "9df4321a283a6cf6" - }, - "Version": "0.3.2-r2", - "Arch": "x86_64", - "SrcName": "libverto", - "SrcVersion": "0.3.2-r2", - "Licenses": [ - "MIT" - ], - "Maintainer": "Francesco Colista \u003cfcolista@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:73233059338142e4ac6d8fb8bb0074e93da75dc1", - "InstalledFiles": [ - "usr/lib/libverto.so.1", - "usr/lib/libverto.so.1.0.0" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "musl@1.2.6-r2", - "Name": "musl", - "Identifier": { - "PURL": "pkg:apk/alpine/musl@1.2.6-r2?arch=x86_64\u0026distro=3.24.1", - "UID": "1f8cb44befe503d4" - }, - "Version": "1.2.6-r2", - "Arch": "x86_64", - "SrcName": "musl", - "SrcVersion": "1.2.6-r2", - "Licenses": [ - "MIT" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:0f7e5827e4e1a73631beda27b78431a8ba240e05", - "InstalledFiles": [ - "lib/ld-musl-x86_64.so.1", - "lib/libc.musl-x86_64.so.1" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "musl-utils@1.2.6-r2", - "Name": "musl-utils", - "Identifier": { - "PURL": "pkg:apk/alpine/musl-utils@1.2.6-r2?arch=x86_64\u0026distro=3.24.1", - "UID": "4497482ec1d943e1" - }, - "Version": "1.2.6-r2", - "Arch": "x86_64", - "SrcName": "musl", - "SrcVersion": "1.2.6-r2", - "Licenses": [ - "MIT", - "BSD-2-Clause", - "GPL-2.0-or-later" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2", - "scanelf@1.3.9-r1" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:75ada3c48d63ec5d87c42fdf934a3fdd11298dc5", - "InstalledFiles": [ - "sbin/ldconfig", - "usr/bin/getconf", - "usr/bin/getent", - "usr/bin/iconv", - "usr/bin/ldd" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "ncurses-terminfo-base@6.6_p20260516-r0", - "Name": "ncurses-terminfo-base", - "Identifier": { - "PURL": "pkg:apk/alpine/ncurses-terminfo-base@6.6_p20260516-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "e7fac5c6a6e9ae8" - }, - "Version": "6.6_p20260516-r0", - "Arch": "x86_64", - "SrcName": "ncurses", - "SrcVersion": "6.6_p20260516-r0", - "Licenses": [ - "X-11" - ], - "Maintainer": "Milan P. Stanić \u003cmps@arvanta.net\u003e", - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:9d551d828e9c8ee52c5801c1d6046828ebf15752", - "InstalledFiles": [ - "etc/terminfo/a/alacritty", - "etc/terminfo/a/ansi", - "etc/terminfo/d/dumb", - "etc/terminfo/g/gnome", - "etc/terminfo/g/gnome-256color", - "etc/terminfo/k/konsole", - "etc/terminfo/k/konsole-256color", - "etc/terminfo/k/konsole-linux", - "etc/terminfo/l/linux", - "etc/terminfo/p/putty", - "etc/terminfo/p/putty-256color", - "etc/terminfo/r/rxvt", - "etc/terminfo/r/rxvt-256color", - "etc/terminfo/s/screen", - "etc/terminfo/s/screen-256color", - "etc/terminfo/s/st-0.6", - "etc/terminfo/s/st-0.7", - "etc/terminfo/s/st-0.8", - "etc/terminfo/s/st-0.8.5", - "etc/terminfo/s/st-16color", - "etc/terminfo/s/st-256color", - "etc/terminfo/s/st-direct", - "etc/terminfo/s/sun", - "etc/terminfo/t/terminator", - "etc/terminfo/t/terminology", - "etc/terminfo/t/terminology-0.6.1", - "etc/terminfo/t/terminology-1.0.0", - "etc/terminfo/t/terminology-1.8.1", - "etc/terminfo/t/tmux", - "etc/terminfo/t/tmux-256color", - "etc/terminfo/v/vt100", - "etc/terminfo/v/vt102", - "etc/terminfo/v/vt200", - "etc/terminfo/v/vt220", - "etc/terminfo/v/vt52", - "etc/terminfo/v/vte", - "etc/terminfo/v/vte-256color", - "etc/terminfo/x/xterm", - "etc/terminfo/x/xterm-256color", - "etc/terminfo/x/xterm-color", - "etc/terminfo/x/xterm-xfree86" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "readline@8.3.3-r1", - "Name": "readline", - "Identifier": { - "PURL": "pkg:apk/alpine/readline@8.3.3-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "f50058781c93e1f2" - }, - "Version": "8.3.3-r1", - "Arch": "x86_64", - "SrcName": "readline", - "SrcVersion": "8.3.3-r1", - "Licenses": [ - "GPL-3.0-or-later" - ], - "Maintainer": "qaqland \u003cqaq@qaq.land\u003e", - "DependsOn": [ - "libncursesw@6.6_p20260516-r0", - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:945b16e378bc00e44d89d4de8124bba7647fdf4d", - "InstalledFiles": [ - "etc/inputrc", - "usr/lib/libreadline.so.8", - "usr/lib/libreadline.so.8.3" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "scanelf@1.3.9-r1", - "Name": "scanelf", - "Identifier": { - "PURL": "pkg:apk/alpine/scanelf@1.3.9-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "936394657a1626fb" - }, - "Version": "1.3.9-r1", - "Arch": "x86_64", - "SrcName": "pax-utils", - "SrcVersion": "1.3.9-r1", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:013f903d0ba219673aebbdd04f74bb5ed82b01f0", - "InstalledFiles": [ - "usr/bin/scanelf" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "sqlite-libs@3.53.4-r0", - "Name": "sqlite-libs", - "Identifier": { - "PURL": "pkg:apk/alpine/sqlite-libs@3.53.4-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "8b10aee2ccc192de" - }, - "Version": "3.53.4-r0", - "Arch": "x86_64", - "SrcName": "sqlite", - "SrcVersion": "3.53.4-r0", - "Licenses": [ - "blessing" - ], - "Maintainer": "Jingyun Hua \u003chuajingyun@loongson.cn\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:f7630ceb37c232b6609861aeab6cdb9cd120e6fa", - "InstalledFiles": [ - "usr/lib/libsqlite3.so.0", - "usr/lib/libsqlite3.so.3.53.4" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "ssl_client@1.37.0-r31", - "Name": "ssl_client", - "Identifier": { - "PURL": "pkg:apk/alpine/ssl_client@1.37.0-r31?arch=x86_64\u0026distro=3.24.1", - "UID": "d42e31f15a4ce47a" - }, - "Version": "1.37.0-r31", - "Arch": "x86_64", - "SrcName": "busybox", - "SrcVersion": "1.37.0-r31", - "Licenses": [ - "GPL-2.0-only" - ], - "Maintainer": "Sören Tempel \u003csoeren+alpine@soeren-tempel.net\u003e", - "DependsOn": [ - "libcrypto3@3.5.8-r0", - "libssl3@3.5.8-r0", - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:34884abbe92863fce933ed7c39c0e045631af0ed86d5cc0dfbdf9fdca426ce3c" - }, - "Digest": "sha1:c220f4a5125a313af733e98def94132bb1e9d40d", - "InstalledFiles": [ - "usr/bin/ssl_client" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "tzdata@2026c-r0", - "Name": "tzdata", - "Identifier": { - "PURL": "pkg:apk/alpine/tzdata@2026c-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "52c6b50eff629f9d" - }, - "Version": "2026c-r0", - "Arch": "x86_64", - "SrcName": "tzdata", - "SrcVersion": "2026c-r0", - "Licenses": [ - "Public-Domain" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "Layer": { - "DiffID": "sha256:3ca4be2c6e9ac0223715355253725381000486c2b45bfcb22cf571b6a85a12dd" - }, - "Digest": "sha1:61f7544ecd8dd63eac23fe3e645702fda92ef02a", - "InstalledFiles": [ - "usr/share/zoneinfo/CET", - "usr/share/zoneinfo/CST6CDT", - "usr/share/zoneinfo/Cuba", - "usr/share/zoneinfo/EET", - "usr/share/zoneinfo/EST", - "usr/share/zoneinfo/EST5EDT", - "usr/share/zoneinfo/Egypt", - "usr/share/zoneinfo/Eire", - "usr/share/zoneinfo/Factory", - "usr/share/zoneinfo/GB", - "usr/share/zoneinfo/GB-Eire", - "usr/share/zoneinfo/GMT", - "usr/share/zoneinfo/GMT+0", - "usr/share/zoneinfo/GMT-0", - "usr/share/zoneinfo/GMT0", - "usr/share/zoneinfo/Greenwich", - "usr/share/zoneinfo/HST", - "usr/share/zoneinfo/Hongkong", - "usr/share/zoneinfo/Iceland", - "usr/share/zoneinfo/Iran", - "usr/share/zoneinfo/Israel", - "usr/share/zoneinfo/Jamaica", - "usr/share/zoneinfo/Japan", - "usr/share/zoneinfo/Kwajalein", - "usr/share/zoneinfo/Libya", - "usr/share/zoneinfo/MET", - "usr/share/zoneinfo/MST", - "usr/share/zoneinfo/MST7MDT", - "usr/share/zoneinfo/NZ", - "usr/share/zoneinfo/NZ-CHAT", - "usr/share/zoneinfo/Navajo", - "usr/share/zoneinfo/PRC", - "usr/share/zoneinfo/PST8PDT", - "usr/share/zoneinfo/Poland", - "usr/share/zoneinfo/Portugal", - "usr/share/zoneinfo/ROC", - "usr/share/zoneinfo/ROK", - "usr/share/zoneinfo/Singapore", - "usr/share/zoneinfo/Turkey", - "usr/share/zoneinfo/UCT", - "usr/share/zoneinfo/UTC", - "usr/share/zoneinfo/Universal", - "usr/share/zoneinfo/W-SU", - "usr/share/zoneinfo/WET", - "usr/share/zoneinfo/Zulu", - "usr/share/zoneinfo/iso3166.tab", - "usr/share/zoneinfo/leap-seconds.list", - "usr/share/zoneinfo/posixrules", - "usr/share/zoneinfo/zone.tab", - "usr/share/zoneinfo/zone1970.tab", - "usr/share/zoneinfo/Africa/Abidjan", - "usr/share/zoneinfo/Africa/Accra", - "usr/share/zoneinfo/Africa/Addis_Ababa", - "usr/share/zoneinfo/Africa/Algiers", - "usr/share/zoneinfo/Africa/Asmara", - "usr/share/zoneinfo/Africa/Asmera", - "usr/share/zoneinfo/Africa/Bamako", - "usr/share/zoneinfo/Africa/Bangui", - "usr/share/zoneinfo/Africa/Banjul", - "usr/share/zoneinfo/Africa/Bissau", - "usr/share/zoneinfo/Africa/Blantyre", - "usr/share/zoneinfo/Africa/Brazzaville", - "usr/share/zoneinfo/Africa/Bujumbura", - "usr/share/zoneinfo/Africa/Cairo", - "usr/share/zoneinfo/Africa/Casablanca", - "usr/share/zoneinfo/Africa/Ceuta", - "usr/share/zoneinfo/Africa/Conakry", - "usr/share/zoneinfo/Africa/Dakar", - "usr/share/zoneinfo/Africa/Dar_es_Salaam", - "usr/share/zoneinfo/Africa/Djibouti", - "usr/share/zoneinfo/Africa/Douala", - "usr/share/zoneinfo/Africa/El_Aaiun", - "usr/share/zoneinfo/Africa/Freetown", - "usr/share/zoneinfo/Africa/Gaborone", - "usr/share/zoneinfo/Africa/Harare", - "usr/share/zoneinfo/Africa/Johannesburg", - "usr/share/zoneinfo/Africa/Juba", - "usr/share/zoneinfo/Africa/Kampala", - "usr/share/zoneinfo/Africa/Khartoum", - "usr/share/zoneinfo/Africa/Kigali", - "usr/share/zoneinfo/Africa/Kinshasa", - "usr/share/zoneinfo/Africa/Lagos", - "usr/share/zoneinfo/Africa/Libreville", - "usr/share/zoneinfo/Africa/Lome", - "usr/share/zoneinfo/Africa/Luanda", - "usr/share/zoneinfo/Africa/Lubumbashi", - "usr/share/zoneinfo/Africa/Lusaka", - "usr/share/zoneinfo/Africa/Malabo", - "usr/share/zoneinfo/Africa/Maputo", - "usr/share/zoneinfo/Africa/Maseru", - "usr/share/zoneinfo/Africa/Mbabane", - "usr/share/zoneinfo/Africa/Mogadishu", - "usr/share/zoneinfo/Africa/Monrovia", - "usr/share/zoneinfo/Africa/Nairobi", - "usr/share/zoneinfo/Africa/Ndjamena", - "usr/share/zoneinfo/Africa/Niamey", - "usr/share/zoneinfo/Africa/Nouakchott", - "usr/share/zoneinfo/Africa/Ouagadougou", - "usr/share/zoneinfo/Africa/Porto-Novo", - "usr/share/zoneinfo/Africa/Sao_Tome", - "usr/share/zoneinfo/Africa/Timbuktu", - "usr/share/zoneinfo/Africa/Tripoli", - "usr/share/zoneinfo/Africa/Tunis", - "usr/share/zoneinfo/Africa/Windhoek", - "usr/share/zoneinfo/America/Adak", - "usr/share/zoneinfo/America/Anchorage", - "usr/share/zoneinfo/America/Anguilla", - "usr/share/zoneinfo/America/Antigua", - "usr/share/zoneinfo/America/Araguaina", - "usr/share/zoneinfo/America/Aruba", - "usr/share/zoneinfo/America/Asuncion", - "usr/share/zoneinfo/America/Atikokan", - "usr/share/zoneinfo/America/Atka", - "usr/share/zoneinfo/America/Bahia", - "usr/share/zoneinfo/America/Bahia_Banderas", - "usr/share/zoneinfo/America/Barbados", - "usr/share/zoneinfo/America/Belem", - "usr/share/zoneinfo/America/Belize", - "usr/share/zoneinfo/America/Blanc-Sablon", - "usr/share/zoneinfo/America/Boa_Vista", - "usr/share/zoneinfo/America/Bogota", - "usr/share/zoneinfo/America/Boise", - "usr/share/zoneinfo/America/Buenos_Aires", - "usr/share/zoneinfo/America/Cambridge_Bay", - "usr/share/zoneinfo/America/Campo_Grande", - "usr/share/zoneinfo/America/Cancun", - "usr/share/zoneinfo/America/Caracas", - "usr/share/zoneinfo/America/Catamarca", - "usr/share/zoneinfo/America/Cayenne", - "usr/share/zoneinfo/America/Cayman", - "usr/share/zoneinfo/America/Chicago", - "usr/share/zoneinfo/America/Chihuahua", - "usr/share/zoneinfo/America/Ciudad_Juarez", - "usr/share/zoneinfo/America/Coral_Harbour", - "usr/share/zoneinfo/America/Cordoba", - "usr/share/zoneinfo/America/Costa_Rica", - "usr/share/zoneinfo/America/Coyhaique", - "usr/share/zoneinfo/America/Creston", - "usr/share/zoneinfo/America/Cuiaba", - "usr/share/zoneinfo/America/Curacao", - "usr/share/zoneinfo/America/Danmarkshavn", - "usr/share/zoneinfo/America/Dawson", - "usr/share/zoneinfo/America/Dawson_Creek", - "usr/share/zoneinfo/America/Denver", - "usr/share/zoneinfo/America/Detroit", - "usr/share/zoneinfo/America/Dominica", - "usr/share/zoneinfo/America/Edmonton", - "usr/share/zoneinfo/America/Eirunepe", - "usr/share/zoneinfo/America/El_Salvador", - "usr/share/zoneinfo/America/Ensenada", - "usr/share/zoneinfo/America/Fort_Nelson", - "usr/share/zoneinfo/America/Fort_Wayne", - "usr/share/zoneinfo/America/Fortaleza", - "usr/share/zoneinfo/America/Glace_Bay", - "usr/share/zoneinfo/America/Godthab", - "usr/share/zoneinfo/America/Goose_Bay", - "usr/share/zoneinfo/America/Grand_Turk", - "usr/share/zoneinfo/America/Grenada", - "usr/share/zoneinfo/America/Guadeloupe", - "usr/share/zoneinfo/America/Guatemala", - "usr/share/zoneinfo/America/Guayaquil", - "usr/share/zoneinfo/America/Guyana", - "usr/share/zoneinfo/America/Halifax", - "usr/share/zoneinfo/America/Havana", - "usr/share/zoneinfo/America/Hermosillo", - "usr/share/zoneinfo/America/Indianapolis", - "usr/share/zoneinfo/America/Inuvik", - "usr/share/zoneinfo/America/Iqaluit", - "usr/share/zoneinfo/America/Jamaica", - "usr/share/zoneinfo/America/Jujuy", - "usr/share/zoneinfo/America/Juneau", - "usr/share/zoneinfo/America/Knox_IN", - "usr/share/zoneinfo/America/Kralendijk", - "usr/share/zoneinfo/America/La_Paz", - "usr/share/zoneinfo/America/Lima", - "usr/share/zoneinfo/America/Los_Angeles", - "usr/share/zoneinfo/America/Louisville", - "usr/share/zoneinfo/America/Lower_Princes", - "usr/share/zoneinfo/America/Maceio", - "usr/share/zoneinfo/America/Managua", - "usr/share/zoneinfo/America/Manaus", - "usr/share/zoneinfo/America/Marigot", - "usr/share/zoneinfo/America/Martinique", - "usr/share/zoneinfo/America/Matamoros", - "usr/share/zoneinfo/America/Mazatlan", - "usr/share/zoneinfo/America/Mendoza", - "usr/share/zoneinfo/America/Menominee", - "usr/share/zoneinfo/America/Merida", - "usr/share/zoneinfo/America/Metlakatla", - "usr/share/zoneinfo/America/Mexico_City", - "usr/share/zoneinfo/America/Miquelon", - "usr/share/zoneinfo/America/Moncton", - "usr/share/zoneinfo/America/Monterrey", - "usr/share/zoneinfo/America/Montevideo", - "usr/share/zoneinfo/America/Montreal", - "usr/share/zoneinfo/America/Montserrat", - "usr/share/zoneinfo/America/Nassau", - "usr/share/zoneinfo/America/New_York", - "usr/share/zoneinfo/America/Nipigon", - "usr/share/zoneinfo/America/Nome", - "usr/share/zoneinfo/America/Noronha", - "usr/share/zoneinfo/America/Nuuk", - "usr/share/zoneinfo/America/Ojinaga", - "usr/share/zoneinfo/America/Panama", - "usr/share/zoneinfo/America/Pangnirtung", - "usr/share/zoneinfo/America/Paramaribo", - "usr/share/zoneinfo/America/Phoenix", - "usr/share/zoneinfo/America/Port-au-Prince", - "usr/share/zoneinfo/America/Port_of_Spain", - "usr/share/zoneinfo/America/Porto_Acre", - "usr/share/zoneinfo/America/Porto_Velho", - "usr/share/zoneinfo/America/Puerto_Rico", - "usr/share/zoneinfo/America/Punta_Arenas", - "usr/share/zoneinfo/America/Rainy_River", - "usr/share/zoneinfo/America/Rankin_Inlet", - "usr/share/zoneinfo/America/Recife", - "usr/share/zoneinfo/America/Regina", - "usr/share/zoneinfo/America/Resolute", - "usr/share/zoneinfo/America/Rio_Branco", - "usr/share/zoneinfo/America/Rosario", - "usr/share/zoneinfo/America/Santa_Isabel", - "usr/share/zoneinfo/America/Santarem", - "usr/share/zoneinfo/America/Santiago", - "usr/share/zoneinfo/America/Santo_Domingo", - "usr/share/zoneinfo/America/Sao_Paulo", - "usr/share/zoneinfo/America/Scoresbysund", - "usr/share/zoneinfo/America/Shiprock", - "usr/share/zoneinfo/America/Sitka", - "usr/share/zoneinfo/America/St_Barthelemy", - "usr/share/zoneinfo/America/St_Johns", - "usr/share/zoneinfo/America/St_Kitts", - "usr/share/zoneinfo/America/St_Lucia", - "usr/share/zoneinfo/America/St_Thomas", - "usr/share/zoneinfo/America/St_Vincent", - "usr/share/zoneinfo/America/Swift_Current", - "usr/share/zoneinfo/America/Tegucigalpa", - "usr/share/zoneinfo/America/Thule", - "usr/share/zoneinfo/America/Thunder_Bay", - "usr/share/zoneinfo/America/Tijuana", - "usr/share/zoneinfo/America/Toronto", - "usr/share/zoneinfo/America/Tortola", - "usr/share/zoneinfo/America/Vancouver", - "usr/share/zoneinfo/America/Virgin", - "usr/share/zoneinfo/America/Whitehorse", - "usr/share/zoneinfo/America/Winnipeg", - "usr/share/zoneinfo/America/Yakutat", - "usr/share/zoneinfo/America/Yellowknife", - "usr/share/zoneinfo/America/Argentina/Buenos_Aires", - "usr/share/zoneinfo/America/Argentina/Catamarca", - "usr/share/zoneinfo/America/Argentina/ComodRivadavia", - "usr/share/zoneinfo/America/Argentina/Cordoba", - "usr/share/zoneinfo/America/Argentina/Jujuy", - "usr/share/zoneinfo/America/Argentina/La_Rioja", - "usr/share/zoneinfo/America/Argentina/Mendoza", - "usr/share/zoneinfo/America/Argentina/Rio_Gallegos", - "usr/share/zoneinfo/America/Argentina/Salta", - "usr/share/zoneinfo/America/Argentina/San_Juan", - "usr/share/zoneinfo/America/Argentina/San_Luis", - "usr/share/zoneinfo/America/Argentina/Tucuman", - "usr/share/zoneinfo/America/Argentina/Ushuaia", - "usr/share/zoneinfo/America/Indiana/Indianapolis", - "usr/share/zoneinfo/America/Indiana/Knox", - "usr/share/zoneinfo/America/Indiana/Marengo", - "usr/share/zoneinfo/America/Indiana/Petersburg", - "usr/share/zoneinfo/America/Indiana/Tell_City", - "usr/share/zoneinfo/America/Indiana/Vevay", - "usr/share/zoneinfo/America/Indiana/Vincennes", - "usr/share/zoneinfo/America/Indiana/Winamac", - "usr/share/zoneinfo/America/Kentucky/Louisville", - "usr/share/zoneinfo/America/Kentucky/Monticello", - "usr/share/zoneinfo/America/North_Dakota/Beulah", - "usr/share/zoneinfo/America/North_Dakota/Center", - "usr/share/zoneinfo/America/North_Dakota/New_Salem", - "usr/share/zoneinfo/Antarctica/Casey", - "usr/share/zoneinfo/Antarctica/Davis", - "usr/share/zoneinfo/Antarctica/DumontDUrville", - "usr/share/zoneinfo/Antarctica/Macquarie", - "usr/share/zoneinfo/Antarctica/Mawson", - "usr/share/zoneinfo/Antarctica/McMurdo", - "usr/share/zoneinfo/Antarctica/Palmer", - "usr/share/zoneinfo/Antarctica/Rothera", - "usr/share/zoneinfo/Antarctica/South_Pole", - "usr/share/zoneinfo/Antarctica/Syowa", - "usr/share/zoneinfo/Antarctica/Troll", - "usr/share/zoneinfo/Antarctica/Vostok", - "usr/share/zoneinfo/Arctic/Longyearbyen", - "usr/share/zoneinfo/Asia/Aden", - "usr/share/zoneinfo/Asia/Almaty", - "usr/share/zoneinfo/Asia/Amman", - "usr/share/zoneinfo/Asia/Anadyr", - "usr/share/zoneinfo/Asia/Aqtau", - "usr/share/zoneinfo/Asia/Aqtobe", - "usr/share/zoneinfo/Asia/Ashgabat", - "usr/share/zoneinfo/Asia/Ashkhabad", - "usr/share/zoneinfo/Asia/Atyrau", - "usr/share/zoneinfo/Asia/Baghdad", - "usr/share/zoneinfo/Asia/Bahrain", - "usr/share/zoneinfo/Asia/Baku", - "usr/share/zoneinfo/Asia/Bangkok", - "usr/share/zoneinfo/Asia/Barnaul", - "usr/share/zoneinfo/Asia/Beirut", - "usr/share/zoneinfo/Asia/Bishkek", - "usr/share/zoneinfo/Asia/Brunei", - "usr/share/zoneinfo/Asia/Calcutta", - "usr/share/zoneinfo/Asia/Chita", - "usr/share/zoneinfo/Asia/Choibalsan", - "usr/share/zoneinfo/Asia/Chongqing", - "usr/share/zoneinfo/Asia/Chungking", - "usr/share/zoneinfo/Asia/Colombo", - "usr/share/zoneinfo/Asia/Dacca", - "usr/share/zoneinfo/Asia/Damascus", - "usr/share/zoneinfo/Asia/Dhaka", - "usr/share/zoneinfo/Asia/Dili", - "usr/share/zoneinfo/Asia/Dubai", - "usr/share/zoneinfo/Asia/Dushanbe", - "usr/share/zoneinfo/Asia/Famagusta", - "usr/share/zoneinfo/Asia/Gaza", - "usr/share/zoneinfo/Asia/Harbin", - "usr/share/zoneinfo/Asia/Hebron", - "usr/share/zoneinfo/Asia/Ho_Chi_Minh", - "usr/share/zoneinfo/Asia/Hong_Kong", - "usr/share/zoneinfo/Asia/Hovd", - "usr/share/zoneinfo/Asia/Irkutsk", - "usr/share/zoneinfo/Asia/Istanbul", - "usr/share/zoneinfo/Asia/Jakarta", - "usr/share/zoneinfo/Asia/Jayapura", - "usr/share/zoneinfo/Asia/Jerusalem", - "usr/share/zoneinfo/Asia/Kabul", - "usr/share/zoneinfo/Asia/Kamchatka", - "usr/share/zoneinfo/Asia/Karachi", - "usr/share/zoneinfo/Asia/Kashgar", - "usr/share/zoneinfo/Asia/Kathmandu", - "usr/share/zoneinfo/Asia/Katmandu", - "usr/share/zoneinfo/Asia/Khandyga", - "usr/share/zoneinfo/Asia/Kolkata", - "usr/share/zoneinfo/Asia/Krasnoyarsk", - "usr/share/zoneinfo/Asia/Kuala_Lumpur", - "usr/share/zoneinfo/Asia/Kuching", - "usr/share/zoneinfo/Asia/Kuwait", - "usr/share/zoneinfo/Asia/Macao", - "usr/share/zoneinfo/Asia/Macau", - "usr/share/zoneinfo/Asia/Magadan", - "usr/share/zoneinfo/Asia/Makassar", - "usr/share/zoneinfo/Asia/Manila", - "usr/share/zoneinfo/Asia/Muscat", - "usr/share/zoneinfo/Asia/Nicosia", - "usr/share/zoneinfo/Asia/Novokuznetsk", - "usr/share/zoneinfo/Asia/Novosibirsk", - "usr/share/zoneinfo/Asia/Omsk", - "usr/share/zoneinfo/Asia/Oral", - "usr/share/zoneinfo/Asia/Phnom_Penh", - "usr/share/zoneinfo/Asia/Pontianak", - "usr/share/zoneinfo/Asia/Pyongyang", - "usr/share/zoneinfo/Asia/Qatar", - "usr/share/zoneinfo/Asia/Qostanay", - "usr/share/zoneinfo/Asia/Qyzylorda", - "usr/share/zoneinfo/Asia/Rangoon", - "usr/share/zoneinfo/Asia/Riyadh", - "usr/share/zoneinfo/Asia/Saigon", - "usr/share/zoneinfo/Asia/Sakhalin", - "usr/share/zoneinfo/Asia/Samarkand", - "usr/share/zoneinfo/Asia/Seoul", - "usr/share/zoneinfo/Asia/Shanghai", - "usr/share/zoneinfo/Asia/Singapore", - "usr/share/zoneinfo/Asia/Srednekolymsk", - "usr/share/zoneinfo/Asia/Taipei", - "usr/share/zoneinfo/Asia/Tashkent", - "usr/share/zoneinfo/Asia/Tbilisi", - "usr/share/zoneinfo/Asia/Tehran", - "usr/share/zoneinfo/Asia/Tel_Aviv", - "usr/share/zoneinfo/Asia/Thimbu", - "usr/share/zoneinfo/Asia/Thimphu", - "usr/share/zoneinfo/Asia/Tokyo", - "usr/share/zoneinfo/Asia/Tomsk", - "usr/share/zoneinfo/Asia/Ujung_Pandang", - "usr/share/zoneinfo/Asia/Ulaanbaatar", - "usr/share/zoneinfo/Asia/Ulan_Bator", - "usr/share/zoneinfo/Asia/Urumqi", - "usr/share/zoneinfo/Asia/Ust-Nera", - "usr/share/zoneinfo/Asia/Vientiane", - "usr/share/zoneinfo/Asia/Vladivostok", - "usr/share/zoneinfo/Asia/Yakutsk", - "usr/share/zoneinfo/Asia/Yangon", - "usr/share/zoneinfo/Asia/Yekaterinburg", - "usr/share/zoneinfo/Asia/Yerevan", - "usr/share/zoneinfo/Atlantic/Azores", - "usr/share/zoneinfo/Atlantic/Bermuda", - "usr/share/zoneinfo/Atlantic/Canary", - "usr/share/zoneinfo/Atlantic/Cape_Verde", - "usr/share/zoneinfo/Atlantic/Faeroe", - "usr/share/zoneinfo/Atlantic/Faroe", - "usr/share/zoneinfo/Atlantic/Jan_Mayen", - "usr/share/zoneinfo/Atlantic/Madeira", - "usr/share/zoneinfo/Atlantic/Reykjavik", - "usr/share/zoneinfo/Atlantic/South_Georgia", - "usr/share/zoneinfo/Atlantic/St_Helena", - "usr/share/zoneinfo/Atlantic/Stanley", - "usr/share/zoneinfo/Australia/ACT", - "usr/share/zoneinfo/Australia/Adelaide", - "usr/share/zoneinfo/Australia/Brisbane", - "usr/share/zoneinfo/Australia/Broken_Hill", - "usr/share/zoneinfo/Australia/Canberra", - "usr/share/zoneinfo/Australia/Currie", - "usr/share/zoneinfo/Australia/Darwin", - "usr/share/zoneinfo/Australia/Eucla", - "usr/share/zoneinfo/Australia/Hobart", - "usr/share/zoneinfo/Australia/LHI", - "usr/share/zoneinfo/Australia/Lindeman", - "usr/share/zoneinfo/Australia/Lord_Howe", - "usr/share/zoneinfo/Australia/Melbourne", - "usr/share/zoneinfo/Australia/NSW", - "usr/share/zoneinfo/Australia/North", - "usr/share/zoneinfo/Australia/Perth", - "usr/share/zoneinfo/Australia/Queensland", - "usr/share/zoneinfo/Australia/South", - "usr/share/zoneinfo/Australia/Sydney", - "usr/share/zoneinfo/Australia/Tasmania", - "usr/share/zoneinfo/Australia/Victoria", - "usr/share/zoneinfo/Australia/West", - "usr/share/zoneinfo/Australia/Yancowinna", - "usr/share/zoneinfo/Brazil/Acre", - "usr/share/zoneinfo/Brazil/DeNoronha", - "usr/share/zoneinfo/Brazil/East", - "usr/share/zoneinfo/Brazil/West", - "usr/share/zoneinfo/Canada/Atlantic", - "usr/share/zoneinfo/Canada/Central", - "usr/share/zoneinfo/Canada/Eastern", - "usr/share/zoneinfo/Canada/Mountain", - "usr/share/zoneinfo/Canada/Newfoundland", - "usr/share/zoneinfo/Canada/Pacific", - "usr/share/zoneinfo/Canada/Saskatchewan", - "usr/share/zoneinfo/Canada/Yukon", - "usr/share/zoneinfo/Chile/Continental", - "usr/share/zoneinfo/Chile/EasterIsland", - "usr/share/zoneinfo/Etc/GMT", - "usr/share/zoneinfo/Etc/GMT+0", - "usr/share/zoneinfo/Etc/GMT+1", - "usr/share/zoneinfo/Etc/GMT+10", - "usr/share/zoneinfo/Etc/GMT+11", - "usr/share/zoneinfo/Etc/GMT+12", - "usr/share/zoneinfo/Etc/GMT+2", - "usr/share/zoneinfo/Etc/GMT+3", - "usr/share/zoneinfo/Etc/GMT+4", - "usr/share/zoneinfo/Etc/GMT+5", - "usr/share/zoneinfo/Etc/GMT+6", - "usr/share/zoneinfo/Etc/GMT+7", - "usr/share/zoneinfo/Etc/GMT+8", - "usr/share/zoneinfo/Etc/GMT+9", - "usr/share/zoneinfo/Etc/GMT-0", - "usr/share/zoneinfo/Etc/GMT-1", - "usr/share/zoneinfo/Etc/GMT-10", - "usr/share/zoneinfo/Etc/GMT-11", - "usr/share/zoneinfo/Etc/GMT-12", - "usr/share/zoneinfo/Etc/GMT-13", - "usr/share/zoneinfo/Etc/GMT-14", - "usr/share/zoneinfo/Etc/GMT-2", - "usr/share/zoneinfo/Etc/GMT-3", - "usr/share/zoneinfo/Etc/GMT-4", - "usr/share/zoneinfo/Etc/GMT-5", - "usr/share/zoneinfo/Etc/GMT-6", - "usr/share/zoneinfo/Etc/GMT-7", - "usr/share/zoneinfo/Etc/GMT-8", - "usr/share/zoneinfo/Etc/GMT-9", - "usr/share/zoneinfo/Etc/GMT0", - "usr/share/zoneinfo/Etc/Greenwich", - "usr/share/zoneinfo/Etc/UCT", - "usr/share/zoneinfo/Etc/UTC", - "usr/share/zoneinfo/Etc/Universal", - "usr/share/zoneinfo/Etc/Zulu", - "usr/share/zoneinfo/Europe/Amsterdam", - "usr/share/zoneinfo/Europe/Andorra", - "usr/share/zoneinfo/Europe/Astrakhan", - "usr/share/zoneinfo/Europe/Athens", - "usr/share/zoneinfo/Europe/Belfast", - "usr/share/zoneinfo/Europe/Belgrade", - "usr/share/zoneinfo/Europe/Berlin", - "usr/share/zoneinfo/Europe/Bratislava", - "usr/share/zoneinfo/Europe/Brussels", - "usr/share/zoneinfo/Europe/Bucharest", - "usr/share/zoneinfo/Europe/Budapest", - "usr/share/zoneinfo/Europe/Busingen", - "usr/share/zoneinfo/Europe/Chisinau", - "usr/share/zoneinfo/Europe/Copenhagen", - "usr/share/zoneinfo/Europe/Dublin", - "usr/share/zoneinfo/Europe/Gibraltar", - "usr/share/zoneinfo/Europe/Guernsey", - "usr/share/zoneinfo/Europe/Helsinki", - "usr/share/zoneinfo/Europe/Isle_of_Man", - "usr/share/zoneinfo/Europe/Istanbul", - "usr/share/zoneinfo/Europe/Jersey", - "usr/share/zoneinfo/Europe/Kaliningrad", - "usr/share/zoneinfo/Europe/Kiev", - "usr/share/zoneinfo/Europe/Kirov", - "usr/share/zoneinfo/Europe/Kyiv", - "usr/share/zoneinfo/Europe/Lisbon", - "usr/share/zoneinfo/Europe/Ljubljana", - "usr/share/zoneinfo/Europe/London", - "usr/share/zoneinfo/Europe/Luxembourg", - "usr/share/zoneinfo/Europe/Madrid", - "usr/share/zoneinfo/Europe/Malta", - "usr/share/zoneinfo/Europe/Mariehamn", - "usr/share/zoneinfo/Europe/Minsk", - "usr/share/zoneinfo/Europe/Monaco", - "usr/share/zoneinfo/Europe/Moscow", - "usr/share/zoneinfo/Europe/Nicosia", - "usr/share/zoneinfo/Europe/Oslo", - "usr/share/zoneinfo/Europe/Paris", - "usr/share/zoneinfo/Europe/Podgorica", - "usr/share/zoneinfo/Europe/Prague", - "usr/share/zoneinfo/Europe/Riga", - "usr/share/zoneinfo/Europe/Rome", - "usr/share/zoneinfo/Europe/Samara", - "usr/share/zoneinfo/Europe/San_Marino", - "usr/share/zoneinfo/Europe/Sarajevo", - "usr/share/zoneinfo/Europe/Saratov", - "usr/share/zoneinfo/Europe/Simferopol", - "usr/share/zoneinfo/Europe/Skopje", - "usr/share/zoneinfo/Europe/Sofia", - "usr/share/zoneinfo/Europe/Stockholm", - "usr/share/zoneinfo/Europe/Tallinn", - "usr/share/zoneinfo/Europe/Tirane", - "usr/share/zoneinfo/Europe/Tiraspol", - "usr/share/zoneinfo/Europe/Ulyanovsk", - "usr/share/zoneinfo/Europe/Uzhgorod", - "usr/share/zoneinfo/Europe/Vaduz", - "usr/share/zoneinfo/Europe/Vatican", - "usr/share/zoneinfo/Europe/Vienna", - "usr/share/zoneinfo/Europe/Vilnius", - "usr/share/zoneinfo/Europe/Volgograd", - "usr/share/zoneinfo/Europe/Warsaw", - "usr/share/zoneinfo/Europe/Zagreb", - "usr/share/zoneinfo/Europe/Zaporozhye", - "usr/share/zoneinfo/Europe/Zurich", - "usr/share/zoneinfo/Indian/Antananarivo", - "usr/share/zoneinfo/Indian/Chagos", - "usr/share/zoneinfo/Indian/Christmas", - "usr/share/zoneinfo/Indian/Cocos", - "usr/share/zoneinfo/Indian/Comoro", - "usr/share/zoneinfo/Indian/Kerguelen", - "usr/share/zoneinfo/Indian/Mahe", - "usr/share/zoneinfo/Indian/Maldives", - "usr/share/zoneinfo/Indian/Mauritius", - "usr/share/zoneinfo/Indian/Mayotte", - "usr/share/zoneinfo/Indian/Reunion", - "usr/share/zoneinfo/Mexico/BajaNorte", - "usr/share/zoneinfo/Mexico/BajaSur", - "usr/share/zoneinfo/Mexico/General", - "usr/share/zoneinfo/Pacific/Apia", - "usr/share/zoneinfo/Pacific/Auckland", - "usr/share/zoneinfo/Pacific/Bougainville", - "usr/share/zoneinfo/Pacific/Chatham", - "usr/share/zoneinfo/Pacific/Chuuk", - "usr/share/zoneinfo/Pacific/Easter", - "usr/share/zoneinfo/Pacific/Efate", - "usr/share/zoneinfo/Pacific/Enderbury", - "usr/share/zoneinfo/Pacific/Fakaofo", - "usr/share/zoneinfo/Pacific/Fiji", - "usr/share/zoneinfo/Pacific/Funafuti", - "usr/share/zoneinfo/Pacific/Galapagos", - "usr/share/zoneinfo/Pacific/Gambier", - "usr/share/zoneinfo/Pacific/Guadalcanal", - "usr/share/zoneinfo/Pacific/Guam", - "usr/share/zoneinfo/Pacific/Honolulu", - "usr/share/zoneinfo/Pacific/Johnston", - "usr/share/zoneinfo/Pacific/Kanton", - "usr/share/zoneinfo/Pacific/Kiritimati", - "usr/share/zoneinfo/Pacific/Kosrae", - "usr/share/zoneinfo/Pacific/Kwajalein", - "usr/share/zoneinfo/Pacific/Majuro", - "usr/share/zoneinfo/Pacific/Marquesas", - "usr/share/zoneinfo/Pacific/Midway", - "usr/share/zoneinfo/Pacific/Nauru", - "usr/share/zoneinfo/Pacific/Niue", - "usr/share/zoneinfo/Pacific/Norfolk", - "usr/share/zoneinfo/Pacific/Noumea", - "usr/share/zoneinfo/Pacific/Pago_Pago", - "usr/share/zoneinfo/Pacific/Palau", - "usr/share/zoneinfo/Pacific/Pitcairn", - "usr/share/zoneinfo/Pacific/Pohnpei", - "usr/share/zoneinfo/Pacific/Ponape", - "usr/share/zoneinfo/Pacific/Port_Moresby", - "usr/share/zoneinfo/Pacific/Rarotonga", - "usr/share/zoneinfo/Pacific/Saipan", - "usr/share/zoneinfo/Pacific/Samoa", - "usr/share/zoneinfo/Pacific/Tahiti", - "usr/share/zoneinfo/Pacific/Tarawa", - "usr/share/zoneinfo/Pacific/Tongatapu", - "usr/share/zoneinfo/Pacific/Truk", - "usr/share/zoneinfo/Pacific/Wake", - "usr/share/zoneinfo/Pacific/Wallis", - "usr/share/zoneinfo/Pacific/Yap", - "usr/share/zoneinfo/US/Alaska", - "usr/share/zoneinfo/US/Aleutian", - "usr/share/zoneinfo/US/Arizona", - "usr/share/zoneinfo/US/Central", - "usr/share/zoneinfo/US/East-Indiana", - "usr/share/zoneinfo/US/Eastern", - "usr/share/zoneinfo/US/Hawaii", - "usr/share/zoneinfo/US/Indiana-Starke", - "usr/share/zoneinfo/US/Michigan", - "usr/share/zoneinfo/US/Mountain", - "usr/share/zoneinfo/US/Pacific", - "usr/share/zoneinfo/US/Samoa" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "xz-libs@5.8.3-r0", - "Name": "xz-libs", - "Identifier": { - "PURL": "pkg:apk/alpine/xz-libs@5.8.3-r0?arch=x86_64\u0026distro=3.24.1", - "UID": "1ae658a5f132a091" - }, - "Version": "5.8.3-r0", - "Arch": "x86_64", - "SrcName": "xz", - "SrcVersion": "5.8.3-r0", - "Licenses": [ - "GPL-2.0-or-later", - "0BSD", - "Public-Domain", - "LGPL-2.1-or-later" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:e5ccda0fbf6b54d343e024156d876553ba57d3926d7196584b3a1ce4df016366" - }, - "Digest": "sha1:4c92c5be0c5bef404e93c880eba9037a9b147347", - "InstalledFiles": [ - "usr/lib/liblzma.so.5", - "usr/lib/liblzma.so.5.8.3" - ], - "AnalyzedBy": "apk" - }, - { - "ID": "zlib@1.3.2-r1", - "Name": "zlib", - "Identifier": { - "PURL": "pkg:apk/alpine/zlib@1.3.2-r1?arch=x86_64\u0026distro=3.24.1", - "UID": "1c01476a88d60a66" - }, - "Version": "1.3.2-r1", - "Arch": "x86_64", - "SrcName": "zlib", - "SrcVersion": "1.3.2-r1", - "Licenses": [ - "Zlib" - ], - "Maintainer": "Natanael Copa \u003cncopa@alpinelinux.org\u003e", - "DependsOn": [ - "musl@1.2.6-r2" - ], - "Layer": { - "DiffID": "sha256:08f4420173dc9f73d520ab9f1a08adeb802ab64d1e1a2f419af11ba2302b05ea" - }, - "Digest": "sha1:4f525c8cc204f84b8b4ca40db3be389958f056c6", - "InstalledFiles": [ - "usr/lib/libz.so.1", - "usr/lib/libz.so.1.3.2" - ], - "AnalyzedBy": "apk" - } - ] - }, - { - "Target": "Python", - "Class": "lang-pkgs", - "Type": "python-pkg", - "Packages": [ - { - "Name": "PyJWT", - "Identifier": { - "PURL": "pkg:pypi/pyjwt@2.15.1", - "UID": "d3f5f9a421b8abc9" - }, - "Version": "2.15.1", - "Licenses": [ - "MIT" - ], - "Layer": { - "DiffID": "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c" - }, - "FilePath": "opt/venv/lib/python3.12/site-packages/pyjwt-2.15.1.dist-info/METADATA", - "AnalyzedBy": "python-pkg" - }, - { - "Name": "cffi", - "Identifier": { - "PURL": "pkg:pypi/cffi@2.1.1", - "UID": "21042e2f37e33c05" - }, - "Version": "2.1.1", - "Licenses": [ - "MIT-0" - ], - "Layer": { - "DiffID": "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c" - }, - "FilePath": "opt/venv/lib/python3.12/site-packages/cffi-2.1.1.dist-info/METADATA", - "AnalyzedBy": "python-pkg" - }, - { - "Name": "cryptography", - "Identifier": { - "PURL": "pkg:pypi/cryptography@50.0.1", - "UID": "dcb3df6383fe2a60" - }, - "Version": "50.0.1", - "Licenses": [ - "Apache-2.0 OR BSD-3-Clause" - ], - "Layer": { - "DiffID": "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c" - }, - "FilePath": "opt/venv/lib/python3.12/site-packages/cryptography-50.0.1.dist-info/METADATA", - "AnalyzedBy": "python-pkg" - }, - { - "Name": "informed-decision", - "Identifier": { - "PURL": "pkg:pypi/informed-decision@0.2.0", - "UID": "7989f2afae8ff568" - }, - "Version": "0.2.0", - "Layer": { - "DiffID": "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c" - }, - "FilePath": "opt/venv/lib/python3.12/site-packages/informed_decision-0.2.0.dist-info/METADATA", - "AnalyzedBy": "python-pkg" - }, - { - "Name": "pycparser", - "Identifier": { - "PURL": "pkg:pypi/pycparser@3.0", - "UID": "e5a4d03f15a695ff" - }, - "Version": "3.0", - "Licenses": [ - "BSD-3-Clause" - ], - "Layer": { - "DiffID": "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c" - }, - "FilePath": "opt/venv/lib/python3.12/site-packages/pycparser-3.0.dist-info/METADATA", - "AnalyzedBy": "python-pkg" - }, - { - "Name": "waitress", - "Identifier": { - "PURL": "pkg:pypi/waitress@3.0.2", - "UID": "806e218e1c1e85f" - }, - "Version": "3.0.2", - "Licenses": [ - "ZPL-2.1" - ], - "Layer": { - "DiffID": "sha256:648451a0dda84ad04681e32b5ecc7cd5d3a4f0ad84f783e9598d72bc69620d5c" - }, - "FilePath": "opt/venv/lib/python3.12/site-packages/waitress-3.0.2.dist-info/METADATA", - "AnalyzedBy": "python-pkg" - } - ] - } - ] -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-receipt.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-receipt.json deleted file mode 100644 index e2cd599..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/fresh-scan-receipt.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "artifact_source_revision": "9b4bb25d325698ae543e7e8a8a3c3592db1f58fc", - "scanner": "aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969", - "full_allseverity_scan": true, - "no_suppression": true, - "exit_code": 0, - "completed_at": "2026-10-11T10:48:46.790940+00:00", - "db_metadata": { - "Version": 2, - "NextUpdate": "2026-10-11T18:59:50.889010572Z", - "UpdatedAt": "2026-10-10T18:59:50.889010923Z", - "DownloadedAt": "2026-10-10T23:32:32.380033131Z" - }, - "db_sha256": "03cacb40452ceb74afdfca072cb82f79ee884dfac006ef24a4dfdad133b0f917", - "full_report_sha256": "42473695abfb99cb32ae29e607120996d339683da7987e9a91117aab9a7e5c00", - "public_archive_sha256": "d24993af69028c6ac43640ee17dcccdc3e962cda4bc28dc8000b4ac77728bc2d", - "archive_config_and_11_layers_verified": true, - "started_at": "2026-10-11T10:48:21.420853+00:00", - "command": [ - "docker", - "--config", - "/tmp/infd-zlib-fresh-rescan-20261011T104821420853Z/empty-docker-config", - "--host", - "unix:///var/run/docker.sock", - "run", - "--rm", - "--user", - "1000:1000", - "--network", - "none", - "--cpus", - "1", - "--memory", - "1g", - "--read-only", - "--tmpfs", - "/tmp:rw,nosuid,nodev,noexec,size=268435456", - "--cap-drop", - "ALL", - "--security-opt", - "no-new-privileges", - "-v", - "/tmp/infd-zlib-published-9b4bb25d/image.tar:/artifact/image.tar:ro", - "-v", - "/tmp/infd-zlib-fresh-rescan-20261011T104821420853Z/scanner-cache:/cache", - "-v", - "/tmp/infd-zlib-fresh-rescan-20261011T104821420853Z:/output", - "aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969", - "image", - "--input", - "/artifact/image.tar", - "--cache-dir", - "/cache", - "--skip-db-update", - "--skip-java-db-update", - "--scanners", - "vuln", - "--format", - "json", - "--output", - "/output/trivy-full.json" - ], - "raw_output_withheld": true, - "native_actions": false, - "native_database_read": false, - "credential_read": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/offline-verify.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/offline-verify.json deleted file mode 100644 index d49cec1..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/offline-verify.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "all_checks_pass": true, - "offline_volume": "infd-zlib-r1-restore-20261011", - "offline_readonly": { - "integrity": "ok", - "lifecycle": { - "events": 59, - "pending": 0, - "records": 17 - }, - "outbox": { - "delivered": 344 - }, - "read_only_pre_open": true, - "schema_version": 5, - "store_constructed": false, - "submissions": { - "confirmed": 44 - } - }, - "offline_post_admin_readonly": { - "integrity": "ok", - "lifecycle": { - "events": 59, - "pending": 0, - "records": 17 - }, - "outbox": { - "delivered": 344 - }, - "read_only_pre_open": true, - "schema_version": 5, - "store_constructed": false, - "submissions": { - "confirmed": 44 - } - }, - "original_snapshot_hash_unchanged": true, - "created_volume_identity": { - "Name": "infd-zlib-r1-restore-20261011", - "Labels": { - "informed-decision.attempt": "643de582-9501-45dd-bac1-754b8524f5b9", - "informed-decision.case": "infd-zlib-20261011-r1", - "informed-decision.use": "offline-restore-only" - }, - "Driver": "local", - "Scope": "local", - "CreatedAt": "2026-10-11T13:16:36+02:00" - }, - "admin_counts": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - } - }, - "network": "none", - "runtime_tokens_or_config_mounted": false, - "service_entrypoint_started": false, - "original_snapshot_retained": true -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/original-manifest.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/original-manifest.json deleted file mode 100644 index b06c6a5..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/original-manifest.json +++ /dev/null @@ -1,82 +0,0 @@ -[ - { - "path": "drill-backup.json", - "sha256": "53ea89341b3ed9d62b6db11070c19a3f1a97aa705ca5e69f75e5badb6d0f085e" - }, - { - "path": "drill-copy.json", - "sha256": "06011d148a022d7249f85e794455f297bf8193e19480762b667c8a42afa30e4e" - }, - { - "path": "offline-verify.json", - "sha256": "4cf2df2c91877c87be8eebb279640473bb3627fe93ed4467467058e8f7baa7ab" - }, - { - "path": "cleanup-offline.json", - "sha256": "ee89cddb506e65959c96b8da453443cd77e3a5cce927e0102acf56f28c38408c" - }, - { - "path": "cutover-backup.json", - "sha256": "3d7a79ddb6f29368284a2d87ffbf350e27a0548750e29e8a532ad9736b3e2cc7" - }, - { - "path": "cutover-copy.json", - "sha256": "14f008e0b8a7b1447847c3c02f1a07ffa13285832f91a6c5a795200f1c68c12f" - }, - { - "path": "release.json", - "sha256": "05cb8b520c9414b5f3a39b2fa5f9b6bf07e93145390fd252c7ddf8cd618687d7" - }, - { - "path": "verify.json", - "sha256": "efe87612f116839340a8e4c777ee7a66f806c8b8ded7f4a13451ec858c838f9a" - }, - { - "path": "canonical-provider-verification.json", - "sha256": "bee994ec4a965f2dccdd252feeb4b527f523fad172db8999849e7c9bc2b12794" - }, - { - "path": "fresh-scan-receipt.json", - "sha256": "0336de3a48d9e1af3e0d74ef7c4a4c01c3a0ac4418a1d84856e87c424dda6db1" - }, - { - "path": "fresh-scan-full.json", - "sha256": "42473695abfb99cb32ae29e607120996d339683da7987e9a91117aab9a7e5c00" - }, - { - "path": "confirmed-root-authority.json", - "sha256": "0d64760ba43581a19ec74489e655e89af150083aa59cea2414c12faa76912e88" - }, - { - "path": "approved-native-preflight.json", - "sha256": "90a8222c400c8e21e4850111fd91c22be9d33c3de9e3351da2bb49c69c3d40eb" - }, - { - "path": "root-clock-diagnosis.json", - "sha256": "6e3cf5fee7471a58016652b7aa8a11756b40e0c8c75c5b74292fad8e11ec6ffe" - }, - { - "path": "prephase-refusal-first.json", - "sha256": "ea63e675da65771a2971030e2b9f55b5c466f70209718be3d0bad8e0b314843d" - }, - { - "path": "prephase-refusal-second.json", - "sha256": "ea63e675da65771a2971030e2b9f55b5c466f70209718be3d0bad8e0b314843d" - }, - { - "path": "prephase-refusal-traced.json", - "sha256": "2932b60db10ce8295b376e6b5964ce695a489699782dd03eb02d9cff9c25bfb6" - }, - { - "path": "root-authority-helper-before-clock-correction.py", - "sha256": "64afd31c9776bd38c84f82972800fe59cbfb34b5315e9cb56aa25f5eb0e4c96d" - }, - { - "path": "root-authority-helper-final.py", - "sha256": "0e4fde2db800d0055c5ef94bd4efcb4f29207dfdb7fbab5df370bac0c65aa42a" - }, - { - "path": "approved-local-image-readiness.json", - "sha256": "285bd23bc05a41fe9942e399527594da896f9a2160af757c9b7ffa9e006fbf37" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-first.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-first.json deleted file mode 100644 index 962502d..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-first.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "all_checks_pass": false, - "phase": "drill-backup", - "status": "phase_receipt_unverified", - "raw_output_withheld": true -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-second.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-second.json deleted file mode 100644 index 962502d..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-second.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "all_checks_pass": false, - "phase": "drill-backup", - "status": "phase_receipt_unverified", - "raw_output_withheld": true -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-traced.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-traced.json deleted file mode 100644 index 89da973..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/prephase-refusal-traced.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "all_checks_pass": false, - "phase": "drill-backup", - "status": "prephase_receipt_unverified", - "exception_locations": [ - { - "file": "actuator.py", - "line": 321, - "exception_type": "ValueError" - }, - { - "file": "actuator.py", - "line": 23, - "exception_type": "ValueError" - }, - { - "file": "actuator.py", - "line": 325, - "exception_type": "ValueError" - }, - { - "file": "actuator.py", - "line": 397, - "exception_type": "ValueError" - }, - { - "file": "actuator.py", - "line": 399, - "exception_type": "SystemExit" - } - ], - "raw_output_withheld": true -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/release.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/release.json deleted file mode 100644 index c7fa511..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/release.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "phase": "release", - "all_checks_pass": true, - "raw_output_withheld": true, - "native_approval_verified_by_root": { - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "verified_at": "2026-10-11T11:18:37.429056+00:00" - }, - "native_authenticated_grant": false, - "live_readonly_schema_integrity_counts": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "live_counts_may_include_concurrent_commits": true, - "pod_name": "informed-decision-5557446c88-lvm88", - "pod_uid": "c4cb1550-feda-4097-9de4-d891a657b662", - "operation_requested": true, - "source_revision": "312ab97e876e17341bf16f9e95397e14c2ac2720", - "resources": [ - { - "group": "apps", - "kind": "Deployment", - "name": "informed-decision", - "namespace": "informed-decision" - } - ], - "prune": false, - "rollout_verified": false, - "latest_live_database_retained": true, - "client_diff_sha256": "d61acc1da118dbd1dee8a94080c8b48a2e12f4b144704b2cb49f3443c49a3587" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-before-clock-correction.py b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-before-clock-correction.py deleted file mode 100644 index c05efd0..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-before-clock-correction.py +++ /dev/null @@ -1,42 +0,0 @@ -import datetime,hashlib,json,os,stat,subprocess -from pathlib import Path -base=Path('/home/worsch/informed-decision/docs/evidence/2026-10-11-zlib-image-release') -revision='312ab97e876e17341bf16f9e95397e14c2ac2720' -program='''import json,re,urllib.request -from datetime import datetime,timezone -from api.services.memo_dispatch import ORIGIN,NoRedirect,token_file -ident='infd-20261011-zlib-remediation-r1';expected='review-2cf690a6-8c46-4faa-826c-a24a88d53dea' -try: - request=urllib.request.Request(ORIGIN+'/v1/decisions/'+ident+'/outcomes?version=1',headers={'Authorization':'Bearer '+token_file('/var/run/secrets/state-hub/informed-decision/lifecycle-token')}) - opener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect()) - with opener.open(request,timeout=20) as response: - raw=response.read(1024*1024+1) - if response.code!=200 or len(raw)>1024*1024:raise ValueError('response_refused') - value=json.loads(raw);review=value['review'];rows=value['dispositions'] - if not isinstance(review,dict) or review.get('memo_id')!=expected or review.get('stale') is not False or review.get('intake_state')!='ready' or not isinstance(rows,list):raise ValueError('review_refused') - if len(rows)!=1:print(json.dumps({'status':'approval_pending','disposition_count':len(rows)}));raise SystemExit(3) - row=rows[0];d=row['disposition'];founder={'kind':'person','sub':'uid=platform-root,ou=people,dc=netkingdom,dc=local'} - if row.get('submission_state')!='confirmed' or d.get('verb')!='accept':print(json.dumps({'status':'approval_not_confirmed_accept'}));raise SystemExit(3) - if d.get('actor')!=founder or d.get('memo_version')!=1 or d.get('memo_id')!=expected or not re.fullmatch('[a-f0-9-]{36}',d.get('approval_id','')) or not re.fullmatch('disp-[a-f0-9-]{36}',d.get('id','')):raise ValueError('founder_refused') - accepted=datetime.fromisoformat(d['at'].replace('Z','+00:00'));now=datetime.now(timezone.utc) - if accepted.tzinfo is None or accepted>now:raise ValueError('time_refused') - normalized={'memo_id':ident,'request_identity_origin':'fixed_authenticated_request_route','review':{'memo_id':expected,'intake_state':'ready','stale':False},'dispositions':[{'submission_state':'confirmed','disposition':{k:d[k] for k in ['id','at','memo_id','memo_version','verb','approval_id','actor']}}],'observed_at':now.isoformat()} - print(json.dumps({'status':'confirmed_founder_acceptance','normalized_outcome':normalized}),flush=True) -except SystemExit:raise -except Exception:print(json.dumps({'status':'native_readback_unverified'}),flush=True);raise SystemExit(1) from None -''' -run=subprocess.run(['ssh','railiance01','kubectl -n state-hub exec -i deployment/state-hub-memo-dispatch -c dispatcher -- python -'],input=program,text=True,capture_output=True,timeout=60) -try:result=json.loads(run.stdout) -except Exception:raise SystemExit('Native readback unverified; raw transport suppressed') -if run.returncode or result.get('status')!='confirmed_founder_acceptance':print(json.dumps({'status':result.get('status','native_readback_unverified'),'native_action':False}));raise SystemExit(3) -outcome=result['normalized_outcome'];d=outcome['dispositions'][0]['disposition'] -raw=(json.dumps(outcome,sort_keys=True,indent=2)+'\n').encode() -receipt=json.loads((base/'root-authority-receipt-template.json').read_text()) -receipt.update(memo_sha256=hashlib.sha256((base/'memo.json').read_bytes()).hexdigest(),source_revision=revision,executor_plan_sha256=hashlib.sha256((base/'execution/execution-plan.json').read_bytes()).hexdigest(),native_outcome_sha256=hashlib.sha256(raw).hexdigest(),activation='APPROVED',verified_at=outcome['observed_at'],review_memo_id=outcome['review']['memo_id'],approval_id=d['approval_id'],disposition_id=d['id'],accepted_at=d['at'],bounded_outage_rollback_pre_authorized=True) -for path,data in [(Path('/tmp/security-infd-zlib-native-outcome-latest.json'),raw),(Path('/tmp/security-infd-zlib-root-authority.json'),(json.dumps(receipt,indent=2)+'\n').encode())]: - if path.exists(): - info=path.lstat() - if not stat.S_ISREG(info.st_mode) or info.st_uid!=os.getuid() or info.st_nlink!=1:raise SystemExit('Authority custody refused') - fd=os.open(path,os.O_CREAT|os.O_TRUNC|os.O_WRONLY|os.O_NOFOLLOW,0o600);os.fchmod(fd,0o600) - with os.fdopen(fd,'wb') as f:f.write(data);f.flush();os.fsync(f.fileno()) -print(json.dumps({'status':'confirmed_founder_acceptance','approval_id':d['approval_id'],'disposition_id':d['id'],'accepted_at':d['at'],'source_revision':revision,'verified_at':receipt['verified_at'],'native_action':False})) diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-final.py b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-final.py deleted file mode 100644 index 6a34432..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-authority-helper-final.py +++ /dev/null @@ -1,42 +0,0 @@ -import datetime,hashlib,json,os,stat,subprocess -from pathlib import Path -base=Path('/home/worsch/informed-decision/docs/evidence/2026-10-11-zlib-image-release') -revision='312ab97e876e17341bf16f9e95397e14c2ac2720' -program='''import json,re,urllib.request -from datetime import datetime,timezone -from api.services.memo_dispatch import ORIGIN,NoRedirect,token_file -ident='infd-20261011-zlib-remediation-r1';expected='review-2cf690a6-8c46-4faa-826c-a24a88d53dea' -try: - request=urllib.request.Request(ORIGIN+'/v1/decisions/'+ident+'/outcomes?version=1',headers={'Authorization':'Bearer '+token_file('/var/run/secrets/state-hub/informed-decision/lifecycle-token')}) - opener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect()) - with opener.open(request,timeout=20) as response: - raw=response.read(1024*1024+1) - if response.code!=200 or len(raw)>1024*1024:raise ValueError('response_refused') - value=json.loads(raw);review=value['review'];rows=value['dispositions'] - if not isinstance(review,dict) or review.get('memo_id')!=expected or review.get('stale') is not False or review.get('intake_state')!='ready' or not isinstance(rows,list):raise ValueError('review_refused') - if len(rows)!=1:print(json.dumps({'status':'approval_pending','disposition_count':len(rows)}));raise SystemExit(3) - row=rows[0];d=row['disposition'];founder={'kind':'person','sub':'uid=platform-root,ou=people,dc=netkingdom,dc=local'} - if row.get('submission_state')!='confirmed' or d.get('verb')!='accept':print(json.dumps({'status':'approval_not_confirmed_accept'}));raise SystemExit(3) - if d.get('actor')!=founder or d.get('memo_version')!=1 or d.get('memo_id')!=expected or not re.fullmatch('[a-f0-9-]{36}',d.get('approval_id','')) or not re.fullmatch('disp-[a-f0-9-]{36}',d.get('id','')):raise ValueError('founder_refused') - accepted=datetime.fromisoformat(d['at'].replace('Z','+00:00'));now=datetime.now(timezone.utc) - if accepted.tzinfo is None or accepted>now:raise ValueError('time_refused') - normalized={'memo_id':ident,'request_identity_origin':'fixed_authenticated_request_route','review':{'memo_id':expected,'intake_state':'ready','stale':False},'dispositions':[{'submission_state':'confirmed','disposition':{k:d[k] for k in ['id','at','memo_id','memo_version','verb','approval_id','actor']}}],'observed_at':now.isoformat()} - print(json.dumps({'status':'confirmed_founder_acceptance','normalized_outcome':normalized}),flush=True) -except SystemExit:raise -except Exception:print(json.dumps({'status':'native_readback_unverified'}),flush=True);raise SystemExit(1) from None -''' -run=subprocess.run(['ssh','railiance01','kubectl -n state-hub exec -i deployment/state-hub-memo-dispatch -c dispatcher -- python -'],input=program,text=True,capture_output=True,timeout=60) -try:result=json.loads(run.stdout) -except Exception:raise SystemExit('Native readback unverified; raw transport suppressed') -if run.returncode or result.get('status')!='confirmed_founder_acceptance':print(json.dumps({'status':result.get('status','native_readback_unverified'),'native_action':False}));raise SystemExit(3) -outcome=result['normalized_outcome'];d=outcome['dispositions'][0]['disposition'] -raw=(json.dumps(outcome,sort_keys=True,indent=2)+'\n').encode() -receipt=json.loads((base/'root-authority-receipt-template.json').read_text()) -receipt.update(memo_sha256=hashlib.sha256((base/'memo.json').read_bytes()).hexdigest(),source_revision=revision,executor_plan_sha256=hashlib.sha256((base/'execution/execution-plan.json').read_bytes()).hexdigest(),native_outcome_sha256=hashlib.sha256(raw).hexdigest(),activation='APPROVED',verified_at=datetime.datetime.now(datetime.timezone.utc).isoformat(),native_observed_at=outcome['observed_at'],review_memo_id=outcome['review']['memo_id'],approval_id=d['approval_id'],disposition_id=d['id'],accepted_at=d['at'],bounded_outage_rollback_pre_authorized=True) -for path,data in [(Path('/tmp/security-infd-zlib-native-outcome-latest.json'),raw),(Path('/tmp/security-infd-zlib-root-authority.json'),(json.dumps(receipt,indent=2)+'\n').encode())]: - if path.exists(): - info=path.lstat() - if not stat.S_ISREG(info.st_mode) or info.st_uid!=os.getuid() or info.st_nlink!=1:raise SystemExit('Authority custody refused') - fd=os.open(path,os.O_CREAT|os.O_TRUNC|os.O_WRONLY|os.O_NOFOLLOW,0o600);os.fchmod(fd,0o600) - with os.fdopen(fd,'wb') as f:f.write(data);f.flush();os.fsync(f.fileno()) -print(json.dumps({'status':'confirmed_founder_acceptance','approval_id':d['approval_id'],'disposition_id':d['id'],'accepted_at':d['at'],'source_revision':revision,'verified_at':receipt['verified_at'],'native_action':False})) diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-clock-diagnosis.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-clock-diagnosis.json deleted file mode 100644 index 4404ce9..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/root-clock-diagnosis.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "immediate_authority_pass": false, - "delta_seconds": -1.1806, - "static_reason": "fresh root native readback required before every phase" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/verify.json b/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/verify.json deleted file mode 100644 index a23b7d1..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-acceptance/root-evidence/verify.json +++ /dev/null @@ -1,55 +0,0 @@ -{ - "phase": "verify", - "all_checks_pass": true, - "raw_output_withheld": true, - "native_approval_verified_by_root": { - "approval_id": "b5ce337c-5d7d-44fa-864d-706b54039f7c", - "disposition_id": "disp-cf61c2cf-4a7d-4672-8bc2-de2e4641916f", - "accepted_at": "2026-10-11T10:40:55Z", - "verified_at": "2026-10-11T11:19:17.592644+00:00" - }, - "native_authenticated_grant": false, - "live_readonly_schema_integrity_counts": { - "schema_version": 5, - "lifecycle": { - "records": 17, - "events": 59, - "pending": 0 - }, - "outbox": { - "delivered": 344 - }, - "submissions": { - "confirmed": 44 - }, - "integrity": "ok", - "read_only_pre_open": true, - "store_constructed": false - }, - "live_counts_may_include_concurrent_commits": true, - "installed_zlib_files": { - "lib/apk/db/installed": "f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001", - "usr/lib/libz.so.1.3.2": "ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2" - }, - "zlib_package": "1.3.2-r1", - "installed_purpose_checks": { - "installed_image_purpose_variants": 10, - "binding_forms_allowed": 2, - "advisory_or_malformed_refused": 8, - "synthetic_only": true, - "native_provider": false - }, - "policy_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62", - "policy_mode": "0644", - "policy_uid": 0, - "health_statuses": { - "/healthz": 200, - "/readyz": 200 - }, - "native_provider_probe": false, - "pod_name": "informed-decision-68d5f488d8-c576r", - "pod_uid": "1153f806-1e05-4596-baf3-a4fa8adf08be", - "native_rollout_verified": true, - "current_schema5_database_retained": true, - "recovery_verification": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/archive-map.json b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/archive-map.json deleted file mode 100644 index f82343c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/archive-map.json +++ /dev/null @@ -1,655 +0,0 @@ -{ - "scheme": "exact content-addressed dedup; each130 originalpath maps losslessly to its unchanged originalbytes", - "files": [ - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e1aaea000c7de1be6cde62499d9fe2705c1e31b3fb8904fafa5333537978e785", - "sha256": "e1aaea000c7de1be6cde62499d9fe2705c1e31b3fb8904fafa5333537978e785" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/aa941510645a655190c967adb4a04f89dfae9c625981a27552dea8a27216df7f", - "sha256": "aa941510645a655190c967adb4a04f89dfae9c625981a27552dea8a27216df7f" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/796dbbff5c7ca61af2c84c2f2ad3de23b56f67210a30bded23a1ff5db806d26f", - "sha256": "796dbbff5c7ca61af2c84c2f2ad3de23b56f67210a30bded23a1ff5db806d26f" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/60507a1000a216623a3d53b21e64c6f4569b1aa064781d3ac672a9fa130e813c", - "sha256": "60507a1000a216623a3d53b21e64c6f4569b1aa064781d3ac672a9fa130e813c" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349", - "sha256": "e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e", - "sha256": "dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47", - "sha256": "81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a", - "sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80", - "sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108", - "sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c", - "sha256": "750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac", - "sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9", - "sha256": "09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd", - "sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca", - "sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07", - "sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3", - "sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4", - "sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0", - "sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361", - "sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225", - "sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98", - "sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35", - "sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70", - "sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a", - "sha256": "8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294", - "sha256": "196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f", - "sha256": "15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4", - "sha256": "0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa", - "sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9", - "sha256": "a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5", - "sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9", - "sha256": "4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0", - "sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_initial_dispatch.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a", - "sha256": "609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/published-evidence-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9", - "sha256": "6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a187eae16bd6eb8b05fdbffc860a7e87b62e726379fde92915de15d6d8bd4290", - "sha256": "a187eae16bd6eb8b05fdbffc860a7e87b62e726379fde92915de15d6d8bd4290" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_refined_boundaries.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/5ab431b060c2417df920c6268a9f39181b0164fc887b5dc3359c4c1d70270ebd", - "sha256": "5ab431b060c2417df920c6268a9f39181b0164fc887b5dc3359c4c1d70270ebd" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/refined-changes-requested-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/354db3948f878ad14f790b02658739d0b620acfdebe0bcfd492667846e457fdf", - "sha256": "354db3948f878ad14f790b02658739d0b620acfdebe0bcfd492667846e457fdf" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/4f6633dbbfd522465da60156b811a1dc29ebad645752d4b1fd72b5f49afaefae", - "sha256": "4f6633dbbfd522465da60156b811a1dc29ebad645752d4b1fd72b5f49afaefae" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29", - "sha256": "f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a", - "sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461", - "sha256": "eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788", - "sha256": "9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/fd38385e3687ec6f9886d5025b394463c252b8198b0426c56f95740139eb7e9b", - "sha256": "fd38385e3687ec6f9886d5025b394463c252b8198b0426c56f95740139eb7e9b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/a1d29ea6822be12817b2d60109cec44b061d167f147b10f9aa352529e1a4c475", - "sha256": "a1d29ea6822be12817b2d60109cec44b061d167f147b10f9aa352529e1a4c475" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b", - "sha256": "2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-contract.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/b13806a7e1464454e3817e1e62b56b0fa07fe2fddbd94d7fdc530e2c3dd9e3d2", - "sha256": "b13806a7e1464454e3817e1e62b56b0fa07fe2fddbd94d7fdc530e2c3dd9e3d2" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd", - "sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-report-fixture.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/30607043a4ca44e42024b63659ae10993c14c7a04e7c5685255ccafe4d3fb55c", - "sha256": "30607043a4ca44e42024b63659ae10993c14c7a04e7c5685255ccafe4d3fb55c" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844", - "sha256": "6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/508f9bf9f11a35b8fc494e1d66e8f11aa0338df4aa9d7115a80e2dabdae81b55", - "sha256": "508f9bf9f11a35b8fc494e1d66e8f11aa0338df4aa9d7115a80e2dabdae81b55" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d", - "sha256": "ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/3c903dd82c13273cb61f590b258b059c32e0fd515d7c176ce4fec3e68a77bc7b", - "sha256": "3c903dd82c13273cb61f590b258b059c32e0fd515d7c176ce4fec3e68a77bc7b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight-full.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/3b6b67a0b4d51ef791e775b528b64aa5a6f14eff629737e67baee7e2f6f60762", - "sha256": "3b6b67a0b4d51ef791e775b528b64aa5a6f14eff629737e67baee7e2f6f60762" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa", - "sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/preparation-failure-history.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/51b9983d2a806240f562dcb951955fc8360c181e6d08a8dfdcba95361c6dd596", - "sha256": "51b9983d2a806240f562dcb951955fc8360c181e6d08a8dfdcba95361c6dd596" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453", - "sha256": "29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight-full.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5", - "sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/4c3c3166205808e81326ccdae1b56afef757b0ceb4e6c96a17bf384317aeee4a", - "sha256": "4c3c3166205808e81326ccdae1b56afef757b0ceb4e6c96a17bf384317aeee4a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ecddcb00127774a17d5cf553417c60d1b6fb753b6eb91a49dabb7fc52678595e", - "sha256": "ecddcb00127774a17d5cf553417c60d1b6fb753b6eb91a49dabb7fc52678595e" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0", - "sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d162fda35ac46502f14cea91d7d614ce5041e15eae0d4705ede4bf9efc18b36a", - "sha256": "d162fda35ac46502f14cea91d7d614ce5041e15eae0d4705ede4bf9efc18b36a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/final-agreement-probes.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d23f10a3eca61ee3fb9c20213621122a394679a3f97c1abd8e91edff089803aa", - "sha256": "d23f10a3eca61ee3fb9c20213621122a394679a3f97c1abd8e91edff089803aa" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/final481-changes-requested-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/faeb2b4a9f57a3db029197d4488b528d0a93b6464bf60ade9b0ee189f5a9a95a", - "sha256": "faeb2b4a9f57a3db029197d4488b528d0a93b6464bf60ade9b0ee189f5a9a95a" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/independent_final_probes.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/c29eba9ff32bd9d8ed9e825a4acf8ba692e19a03087fd7e7a55aa0d9e1fc2d43", - "sha256": "c29eba9ff32bd9d8ed9e825a4acf8ba692e19a03087fd7e7a55aa0d9e1fc2d43" - }, - { - "original_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/unissued-final-technical-draft.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/be936ee74899f9a2c2bee2e12560fd5e7aaeaa6fb6e38e717ef9dabbdf73055f", - "sha256": "be936ee74899f9a2c2bee2e12560fd5e7aaeaa6fb6e38e717ef9dabbdf73055f" - }, - { - "original_path": "intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8261e4f08dc73410a1c507d562e0b3fae8629ee1f8c39ed16b3c926744a816f6", - "sha256": "8261e4f08dc73410a1c507d562e0b3fae8629ee1f8c39ed16b3c926744a816f6" - } - ] -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5 deleted file mode 100644 index ff52810..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5 +++ /dev/null @@ -1,672 +0,0 @@ -{ - "all_checks_pass": true, - "observed_at": "2026-10-11T09:43:42.851086+00:00", - "native_changed": false, - "protected_database_read": false, - "secret_read": false, - "deployment_generation": 25, - "ready_replicas": 1, - "sealed_live_deployment": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:apps/Deployment:informed-decision/informed-decision", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision", - "namespace": "informed-decision", - "uid": "a588e10a-90d2-4883-975e-6dd8b8feca92" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app.kubernetes.io/name": "informed-decision" - } - }, - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "annotations": { - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "labels": { - "app.kubernetes.io/component": "review", - "app.kubernetes.io/name": "informed-decision" - } - }, - "spec": { - "automountServiceAccountToken": false, - "containers": [ - { - "env": [ - { - "name": "INFD_KEYCAPE_ISSUER", - "value": "https://kc.coulomb.social" - }, - { - "name": "INFD_CONTAINER_CONFIG", - "value": "/configuration/runtime.json" - } - ], - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "name": "informed-decision", - "ports": [ - { - "containerPort": 8080, - "name": "http", - "protocol": "TCP" - }, - { - "containerPort": 8081, - "name": "workload", - "protocol": "TCP" - } - ], - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "resources": { - "limits": { - "cpu": "500m", - "memory": "256Mi" - }, - "requests": { - "cpu": "5m", - "memory": "64Mi" - } - }, - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": [ - "ALL" - ] - }, - "readOnlyRootFilesystem": true - }, - "startupProbe": { - "failureThreshold": 12, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/data", - "name": "data" - }, - { - "mountPath": "/run/informed-decision", - "name": "runtime" - }, - { - "mountPath": "/tmp", - "name": "tmp" - }, - { - "mountPath": "/configuration", - "name": "configuration", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/policy", - "name": "caller", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/audit", - "name": "audit", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/kubernetes", - "name": "kubernetes-api", - "readOnly": true - }, - { - "mountPath": "/workload-registry", - "name": "workload-registry", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/requester", - "name": "sitting-requester", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 10001, - "fsGroupChangePolicy": "OnRootMismatch", - "runAsGroup": 10001, - "runAsNonRoot": true, - "runAsUser": 10001, - "seccompProfile": { - "type": "RuntimeDefault" - } - }, - "serviceAccount": "review", - "serviceAccountName": "review", - "terminationGracePeriodSeconds": 45, - "volumes": [ - { - "name": "data", - "persistentVolumeClaim": { - "claimName": "informed-decision-data" - } - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "runtime" - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "tmp" - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-runtime-6c221878d051" - }, - "name": "configuration" - }, - { - "name": "caller", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "audience": "flex-auth", - "expirationSeconds": 3600, - "path": "token" - } - } - ] - } - }, - { - "name": "audit", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "token", - "path": "token" - } - ], - "secretName": "informed-decision-audit" - } - }, - { - "name": "kubernetes-api", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 600, - "path": "token" - } - }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } - } - ] - } - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-workload-registry" - }, - "name": "workload-registry" - }, - { - "name": "sitting-requester", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "client-secret", - "path": "client-secret" - } - ], - "optional": false, - "secretName": "informed-decision-sitting-requester" - } - } - ] - } - } - } - }, - "candidate_deployment": { - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:apps/Deployment:informed-decision/informed-decision", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision", - "namespace": "informed-decision", - "uid": "a588e10a-90d2-4883-975e-6dd8b8feca92" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app.kubernetes.io/name": "informed-decision" - } - }, - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "annotations": { - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "labels": { - "app.kubernetes.io/component": "review", - "app.kubernetes.io/name": "informed-decision" - } - }, - "spec": { - "automountServiceAccountToken": false, - "containers": [ - { - "env": [ - { - "name": "INFD_KEYCAPE_ISSUER", - "value": "https://kc.coulomb.social" - }, - { - "name": "INFD_CONTAINER_CONFIG", - "value": "/configuration/runtime.json" - } - ], - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "name": "informed-decision", - "ports": [ - { - "containerPort": 8080, - "name": "http", - "protocol": "TCP" - }, - { - "containerPort": 8081, - "name": "workload", - "protocol": "TCP" - } - ], - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "resources": { - "limits": { - "cpu": "500m", - "memory": "256Mi" - }, - "requests": { - "cpu": "5m", - "memory": "64Mi" - } - }, - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": [ - "ALL" - ] - }, - "readOnlyRootFilesystem": true - }, - "startupProbe": { - "failureThreshold": 12, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/data", - "name": "data" - }, - { - "mountPath": "/run/informed-decision", - "name": "runtime" - }, - { - "mountPath": "/tmp", - "name": "tmp" - }, - { - "mountPath": "/configuration", - "name": "configuration", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/policy", - "name": "caller", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/audit", - "name": "audit", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/kubernetes", - "name": "kubernetes-api", - "readOnly": true - }, - { - "mountPath": "/workload-registry", - "name": "workload-registry", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/requester", - "name": "sitting-requester", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 10001, - "fsGroupChangePolicy": "OnRootMismatch", - "runAsGroup": 10001, - "runAsNonRoot": true, - "runAsUser": 10001, - "seccompProfile": { - "type": "RuntimeDefault" - } - }, - "serviceAccount": "review", - "serviceAccountName": "review", - "terminationGracePeriodSeconds": 45, - "volumes": [ - { - "name": "data", - "persistentVolumeClaim": { - "claimName": "informed-decision-data" - } - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "runtime" - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "tmp" - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-runtime-6c221878d051" - }, - "name": "configuration" - }, - { - "name": "caller", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "audience": "flex-auth", - "expirationSeconds": 3600, - "path": "token" - } - } - ] - } - }, - { - "name": "audit", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "token", - "path": "token" - } - ], - "secretName": "informed-decision-audit" - } - }, - { - "name": "kubernetes-api", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 600, - "path": "token" - } - }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } - } - ] - } - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-workload-registry" - }, - "name": "workload-registry" - }, - { - "name": "sitting-requester", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "client-secret", - "path": "client-secret" - } - ], - "optional": false, - "secretName": "informed-decision-sitting-requester" - } - } - ] - } - } - } - }, - "sealed_dependencies": { - "configmap": { - "apiVersion": "v1", - "immutable": true, - "kind": "ConfigMap", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:/ConfigMap:informed-decision/informed-decision-runtime-6c221878d051", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision-runtime-6c221878d051", - "namespace": "informed-decision", - "uid": "90113d2d-54d9-4e82-b1d3-90b49ebed2b5" - }, - "canonical_data_sha256": "8ec462d12347548f506eeebf1e3c7cc47648019c8a783804d09bcc79b0f22af6" - }, - "pvc": { - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:/PersistentVolumeClaim:informed-decision/informed-decision-data", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover", - "pv.kubernetes.io/bind-completed": "yes", - "pv.kubernetes.io/bound-by-controller": "yes", - "volume.beta.kubernetes.io/storage-provisioner": "rancher.io/local-path", - "volume.kubernetes.io/selected-node": "239.62.205.92.host.secureserver.net", - "volume.kubernetes.io/storage-provisioner": "rancher.io/local-path" - }, - "finalizers": [ - "kubernetes.io/pvc-protection" - ], - "name": "informed-decision-data", - "namespace": "informed-decision", - "uid": "d80b25b1-141a-441b-b205-98fa2fd98b7c" - }, - "spec": { - "accessModes": [ - "ReadWriteOnce" - ], - "resources": { - "requests": { - "storage": "1Gi" - } - }, - "storageClassName": "local-path", - "volumeMode": "Filesystem", - "volumeName": "pvc-d80b25b1-141a-441b-b205-98fa2fd98b7c" - } - }, - "pvc_phase": "Bound" - }, - "argo": { - "uid": "499868f2-eabc-4df4-ae31-b89ce58d750d", - "source": { - "path": "deploy/lifecycle", - "repoURL": "https://forgejo.coulomb.social/coulomb/informed-decision.git", - "targetRevision": "c96b0c6d881e855d1d158003c6de90ef390262e2" - }, - "destination": { - "namespace": "informed-decision", - "server": "https://kubernetes.default.svc" - }, - "automated": false, - "health": { - "lastTransitionTime": "2026-10-11T07:00:42Z", - "status": "Healthy" - }, - "sync": { - "comparedTo": { - "destination": { - "namespace": "informed-decision", - "server": "https://kubernetes.default.svc" - }, - "source": { - "path": "deploy/lifecycle", - "repoURL": "https://forgejo.coulomb.social/coulomb/informed-decision.git", - "targetRevision": "c96b0c6d881e855d1d158003c6de90ef390262e2" - } - }, - "revision": "c96b0c6d881e855d1d158003c6de90ef390262e2", - "status": "OutOfSync" - } - }, - "server_dry_run_passed": true, - "admission_only_image_delta": true, - "client_diff_exit": 0, - "client_diff_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", - "allocatable_cpu_m": 4000, - "requested_cpu_m": 3460, - "strategy": "Recreate" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0 deleted file mode 100644 index 2a0b2c8..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0 +++ /dev/null @@ -1,150 +0,0 @@ -"""Supervised receipt bookkeeping, not an authenticated approval provider.""" -import datetime -import fcntl -import hashlib -import json -import os -import re -from pathlib import Path -import stat -import uuid - -FOUNDER = {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'} -MEMO_ID = 'infd-20261011-zlib-remediation-r1' -PROPOSAL = 'INFD-WP-0009-T05/zlib85091-r1' -PHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline', - 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify') - - -def require(value, reason): - if not value: - raise ValueError(reason) - - -def sha(path): - digest = hashlib.sha256() - with Path(path).open('rb') as stream: - for block in iter(lambda: stream.read(1024**2), b''): - digest.update(block) - return digest.hexdigest() - - -def private(path, directory=False): - info = Path(path).lstat() - require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode)) - and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600) - and (directory or info.st_nlink == 1), 'private owned custody required') - - -def authority(receipt, outcome, memo_sha, source_revision, phase, now=None): - """Root must freshly verify native outcome at fixed ORIGIN outside this helper. - - Receipt files/flags are not signed grants. This function checks the exact - supervised receipt relation; existing harness/founder authority remains the - actual permission boundary. It never authenticates a caller as human. - """ - now = now or datetime.datetime.now(datetime.timezone.utc) - require(phase in PHASES, 'fixed phase required') - require(receipt.get('proposal_id') == PROPOSAL and receipt.get('memo_id') == MEMO_ID - and receipt.get('memo_version') == 1 and receipt.get('memo_sha256') == memo_sha - and receipt.get('source_revision') == source_revision - and receipt.get('activation') == 'APPROVED' - and receipt.get('authority_mode') == 'ADMINISTER' - and receipt.get('target') == 'realm:kubernetes/railiance01/informed-decision' - and receipt.get('verification_method') == 'root-fixed-origin-native-readback' - and receipt.get('runtime_authenticated_grant') is False - and receipt.get('outcome_query') == '/lifecycle/memos/' + MEMO_ID + '/outcomes?version=1', - 'exact externally verified supervised receipt required') - verified = datetime.datetime.fromisoformat(receipt['verified_at'].replace('Z', '+00:00')) - require(verified.tzinfo is not None and 0 <= (now - verified).total_seconds() <= 120, - 'fresh root native readback required before every phase') - deadline = datetime.datetime.fromisoformat('2026-10-12T23:59:59+00:00') - require(now < deadline, 'approved execution window expired') - if phase == 'release': - require((deadline-now).total_seconds() >= 1800, 'reserve thirty minutes for verification and recovery') - require(outcome.get('memo_id') == MEMO_ID and outcome.get('review', {}).get('stale') is False - and outcome['review'].get('intake_state') == 'ready', 'native review is not ready/current') - review_id = outcome['review']['memo_id'] - require(review_id == receipt.get('review_memo_id'), 'native review identity changed') - accepted = [x for x in outcome.get('dispositions', []) - if x.get('submission_state') == 'confirmed' - and x.get('disposition', {}).get('verb') == 'accept' - and x['disposition'].get('memo_version') == 1] - require(len(accepted) == 1, 'one confirmed founder acceptance required') - accepted = accepted[0]['disposition'] - require(accepted.get('actor') == FOUNDER and accepted.get('memo_id') == review_id - and accepted.get('approval_id') == receipt.get('approval_id') - and accepted.get('id') == receipt.get('disposition_id') - and accepted.get('at') == receipt.get('accepted_at'), 'exact founder disposition mismatch') - require(re.fullmatch(r'[a-f0-9-]{36}', accepted['approval_id']) is not None - and re.fullmatch(r'disp-[a-f0-9-]{36}', accepted['id']) is not None - and re.fullmatch(r'review-[a-f0-9-]{36}', review_id) is not None, - 'native disposition identifiers are malformed') - accepted_at = datetime.datetime.fromisoformat(accepted['at'].replace('Z', '+00:00')) - require(accepted_at.tzinfo is not None and accepted_at <= verified, 'acceptance time not bounded') - return {key: receipt[key] for key in ('approval_id', 'disposition_id', 'accepted_at', 'verified_at')} - - -class Journal: - def __init__(self, parent, identity): - self.parent = Path(parent) - private(self.parent, directory=True) - self.path = self.parent / 'execution-journal.json' - self.lock = os.open(self.parent / 'execution.lock', os.O_CREAT | os.O_NOFOLLOW | os.O_RDWR, 0o600) - fcntl.flock(self.lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - private(self.parent / 'execution.lock') - if self.path.exists(): - private(self.path) - self.data = json.loads(self.path.read_text()) - require(self.data['identity'] == identity, 'journal case/source identity changed') - else: - self.data = {'identity': identity, 'phases': {}} - self.save() - - def save(self): - temporary = self.parent / 'execution-journal.new' - fd = os.open(temporary, os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW | os.O_WRONLY, 0o600) - try: - with os.fdopen(fd, 'w') as stream: - json.dump(self.data, stream, indent=2) - stream.write('\n') - stream.flush() - os.fsync(stream.fileno()) - os.replace(temporary, self.path) - directory = os.open(self.parent, os.O_DIRECTORY | os.O_RDONLY) - try: - os.fsync(directory) - finally: - os.close(directory) - except BaseException: - # Retain evidence rather than overwriting an uncertain durable intent. - raise - - def start(self, phase, approval): - require(phase not in self.data['phases'], 'already attempted phase; no retry') - predecessors = dict(zip(PHASES[1:8], PHASES[:7])) - predecessors['rollback-verify'] = 'rollback' - if phase in predecessors: - require(self.data['phases'].get(predecessors[phase], {}).get('state') == 'COMPLETE', - 'preceding phase is not verified complete') - if phase == 'rollback': - require('release' in self.data['phases'], 'rollback requires a recorded release attempt') - self.data['phases'][phase] = {'state': 'RUNNING', 'approval': approval, - 'attempt_id': str(uuid.uuid4()), - 'started_at': datetime.datetime.now(datetime.timezone.utc).isoformat()} - self.save() - - def note(self, phase, key, value): - require(self.data['phases'][phase]['state'] == 'RUNNING', 'phase intent missing') - self.data['phases'][phase][key] = value - self.save() - - def finish(self, phase, result): - entry = self.data['phases'][phase] - require(entry['state'] == 'RUNNING', 'phase intent missing') - entry['result'] = result - entry['state'] = 'COMPLETE' if result.get('all_checks_pass') is True else 'UNCERTAIN' - self.save() - - def close(self): - os.close(self.lock) diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369 deleted file mode 100644 index 8490089..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369 +++ /dev/null @@ -1,52 +0,0 @@ -{ - "configmap": { - "apiVersion": "v1", - "immutable": true, - "kind": "ConfigMap", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:/ConfigMap:informed-decision/informed-decision-runtime-6c221878d051", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision-runtime-6c221878d051", - "namespace": "informed-decision", - "uid": "90113d2d-54d9-4e82-b1d3-90b49ebed2b5" - }, - "canonical_data_sha256": "8ec462d12347548f506eeebf1e3c7cc47648019c8a783804d09bcc79b0f22af6" - }, - "pvc": { - "apiVersion": "v1", - "kind": "PersistentVolumeClaim", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:/PersistentVolumeClaim:informed-decision/informed-decision-data", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover", - "pv.kubernetes.io/bind-completed": "yes", - "pv.kubernetes.io/bound-by-controller": "yes", - "volume.beta.kubernetes.io/storage-provisioner": "rancher.io/local-path", - "volume.kubernetes.io/selected-node": "239.62.205.92.host.secureserver.net", - "volume.kubernetes.io/storage-provisioner": "rancher.io/local-path" - }, - "finalizers": [ - "kubernetes.io/pvc-protection" - ], - "name": "informed-decision-data", - "namespace": "informed-decision", - "uid": "d80b25b1-141a-441b-b205-98fa2fd98b7c" - }, - "spec": { - "accessModes": [ - "ReadWriteOnce" - ], - "resources": { - "requests": { - "storage": "1Gi" - } - }, - "storageClassName": "local-path", - "volumeMode": "Filesystem", - "volumeName": "pvc-d80b25b1-141a-441b-b205-98fa2fd98b7c" - } - }, - "pvc_phase": "Bound" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4 deleted file mode 100644 index aca7f5c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4 +++ /dev/null @@ -1,23 +0,0 @@ -# T05 zlib85091 release — preliminary, unapproved - -This is a distinct proposal/trial for existing INFD-WP-0009-T05. The actual T04 source2 acceptance does not authorize it. Previous R1 copy and R2 verify UNCERTAIN journal states remain untouched. INFD-IN-0010 remains an unpromoted general verifier residual; this new task-specific verifier is a prerequisite of active T05, not an amendment of the old approved helper. - -Exact intended image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23 was built by admitted owner CI11534/run4 from 9b4bb25d325698ae543e7e8a8a3c3592db1f58fc. Publication is not installed acceptance. Final packet requires independent published-image scan/runtime/filesystem/source proof and fresh read-only native projection/one-object dry-run evidence. Until all final inputs and independent owner acknowledgments are frozen, execution-plan state remains preliminary and the actuator refuses before any authority, journal or native action. - -After a NEW exact confirmed founder acceptance, root freshly authenticates fixed-origin native source-version1 outcomes before each phase. Receipt files are supervised bookkeeping, not signed grants or autonomy. Wrong task, source, memo/version, stale/pending/agent disposition, old T04 acceptance or changed inputs refuse. Root retains exact approved source revision/digests and action results in supervision. Approval does not promote any agent. - -Fixed phase order: drill-backup → drill-copy → offline-verify → cleanup-offline → cutover-backup → cutover-copy → release → verify. New private native /data/private/zlib85091-r1, root .private-backups/20261011-infd-zlib-r1 and local volume infd-zlib-r1-restore-20261011 are distinct from all T04 custody. New private journal intent is fsynced RUNNING before dispatch; no attempted phase replay. Fixed output schema contains only counts, hashes, object identity and boolean results; raw errors and private content are withheld. Existing native snapshots/database/sidecars are never removed or restored over current data. - -Fresh backup inspection uses copied explicit-close SQLite helpers: live and offline clone readers are mode=ro/query_only and WAL-aware, before any Store/admin open. Immutable reader is used only for freshly closed hash-bound private snapshot with absent/zero WAL frames and stable identity/metadata/digest before/after read/copy. Main and sidecars remain retained. Schema must equal5, integrity ok, and exact snapshot/isolated clone counts match before/admin/after. The detached local clone has network none, readonly root, no service/runtime config/credential mounts; only narrowly scoped UID0 CAP_CHOWN initializer precedes UID10001 reads/admin. Original private copy is hash-stable. Cleanup removes only the disposable volume whose nonce/labels/creation metadata match this trial, before cutover. - -Each snapshot is bounded512MiB. At most two new snapshots occupy each native/offcluster custody (1GiB payload each); the disposable offcluster clone adds512MiB while present, plus16MiB sidecar/metadata reserve. New native drill phase checks free space for both; local custody checks two snapshot bounds plus reserve. Existing T04 retention consumes additional pre-existing space. EUR0 recurring infrastructure estimate; human/compute time unpriced. - -Cutover takes a fresh native backup and exact offcluster copy. Release then requires latest schema5 readonly proof, sealed Pod/Deployment/config/PVC custody and same baseline image763c/current Application sourcec96, a healthy single target92.205.62.239 node and fresh requested CPU ≤ allocatable capacity. Recreate and resources remain unchanged. Server dry-run/client diff must show only the image. Application UID/resourceVersion/source CAS dispatches manual exact final revision, initiatedBy custodian-codex, explicit prune=false and only apps/Deployment informed-decision in its namespace. No whole-Application sync, namespace/config/credential/resource change or pruning. - -Verification reads imageID/readiness/owner chain/exact configured Pod fields, schema5/integrity/metadata counts, installed policy hash and synthetic purpose refusals, health200, installed fixed zlib and exact image proof. After exact candidate Ready and verified rollout, root executes attached post-ready-native-outcomes-command.py inside the existing admitted StateHub Pod. It calls canonical lifecycle.outcomes for infd-20261011-zlib-remediation-r1 SOURCEversion1 at dispatcher fixed ORIGIN with NoRedirect/proxy disabled; existing internal token_file stays inside that Pod and no token/body/private content is exported. Root retains bounded HTTP-success metadata/observationtime and correlates verified candidate PodUID/image/source, then rechecks those identities. This actual provider/PDP-gated preservation proof is separate from installed synthetic2allow8refuse; both are required before task closure. It does not assert provider advisory emissions or new doctrine. Argo3.5.3 omits false prune in operation status: missing or exactfalse is accepted only together with this journal's COMPLETE explicitfalse request, exact source/resources/initiation, operation start after our durable attempt, bounded finish and one Synced Deployment result. True/null/string/prior/foreign/extra-resource results refuse. - -Rollback → rollback-verify is separate and permitted only after a recorded release attempt. It switches only Application source back to c96 and Deployment image763c; it never restores an old snapshot over the latest database. Recovery dispatch works even if candidate Pod is absent/unready and never depends on candidate DB inspection. Recovery verification separately requires Ready1/old image/current schema5/data-custody and exact recorded scoped completion. All phases must begin before2026-10-12T23:59:59Z; release must begin at least30minutes earlier. Beyond expiry stop and obtain a new founder decision; no automatic extension or emergency grant. A failed/uncertain attempt requires informed recovery rather than retry. - -T05 remains progress until actual installed acceptance and native rescan prove the finding absent. No Risk Engine grade is assigned. Only then may owner close T05/risk and consider finishing WP9, with existing residuals live first. - -Actual release and installed verification also require fixed private root scan receipt and retained fullreport at /tmp/security-infd-zlib-fresh-scan-{receipt,full}.json, supervised metadata binding to exact immutablef558/artifactsource9b4/scannerdigest62b1. Before journal start, fullreporthash must match, no suppression/allseverity flags true, exit0, nonempty valid fullresults contain zero findings, scan completed within4hours and now precedes DB NextUpdate. The published DB expires2026-10-11T18:59:50Z; it is not valid until release deadline. Root refreshes DB and rescans the same immutable image as routine read-only verification when needed, retaining all raw publicscan artifacts. Any new finding refuses release/closure and is retained for informed reassessment/new decision as needed, never filtered or ignored. Existing risk stays open until actual installed proof/native rescan. diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9 deleted file mode 100644 index 5aae51c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9 +++ /dev/null @@ -1,54 +0,0 @@ -{ - "kind": "informed-decision.supervised-execution-source-bundle", - "version": 1, - "proposal_id": "INFD-WP-0009-T05/zlib85091-r1", - "files": { - "execution/actuator.py": { - "sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80", - "content": "\"\"\"One fixed supervised phase; never run this during source preparation.\"\"\"\nimport argparse\nimport hashlib\nimport json\nimport os\nfrom pathlib import Path\nimport re\nimport selectors\nimport stat\nimport subprocess\nimport sys\nimport time\n\nif sys.flags.optimize:\n raise SystemExit('optimized interpreter refused')\n\nPHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline',\n 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify')\n\n\ndef require(value, reason):\n if not value:\n raise ValueError(reason)\n\n\ndef sha(path):\n digest = hashlib.sha256()\n with Path(path).open('rb') as stream:\n for block in iter(lambda: stream.read(1024**2), b''):\n digest.update(block)\n return digest.hexdigest()\n\n\ndef private(path, directory=False):\n info = Path(path).lstat()\n require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode))\n and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600)\n and (directory or info.st_nlink == 1), 'private owned custody required')\n\nROOT = Path(__file__).resolve().parent\nCASE = ROOT.parent\nOWNER = Path('/home/worsch/informed-decision')\nCUSTODIAN = Path('/home/worsch/the-custodian')\nCUSTODY = CUSTODIAN / '.private-backups/20261011-infd-zlib-r1'\nSOCKET = 'unix:///var/run/docker.sock'\nIMAGE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23'\nVOLUME = 'infd-zlib-r1-restore-20261011'\nMAX_BYTES = 512 * 1024**2\n\n\ndef run(argv, body=None, timeout=90):\n return subprocess.run(argv, input=body, text=True, capture_output=True, timeout=timeout)\n\n\ndef json_result(result):\n try:\n value = json.loads(result.stdout)\n except (ValueError, TypeError):\n raise ValueError('safe phase receipt unavailable; outcome uncertain') from None\n require(result.returncode == 0 and isinstance(value, dict) and value.get('all_checks_pass') is True,\n 'phase did not verify complete; raw output withheld; no retry')\n permitted = {'phase', 'all_checks_pass', 'raw_output_withheld', 'native_approval_verified_by_root',\n 'native_authenticated_grant', 'source_readonly_baseline', 'snapshot_readonly',\n 'snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'native_parent_private',\n 'native_uid', 'pod_name', 'pod_uid', 'live_readonly_schema_integrity_counts',\n 'live_counts_may_include_concurrent_commits', 'operation_requested', 'source_revision',\n 'resources', 'prune', 'rollout_verified', 'latest_live_database_retained',\n 'client_diff_sha256', 'native_rollout_verified', 'current_schema5_database_retained',\n 'private_copy_verified', 'installed_purpose_checks', 'policy_sha256',\n 'policy_mode', 'policy_uid', 'installed_zlib_files', 'zlib_package', 'health_statuses', 'native_provider_probe', 'recovery_verification'}\n require(set(value) <= permitted, 'unexpected output fields withheld')\n from receipt_safety import counts\n for name in ('source_readonly_baseline', 'snapshot_readonly', 'live_readonly_schema_integrity_counts'):\n if name in value:\n counts(value[name])\n for name in ('snapshot_sha256', 'client_diff_sha256'):\n if name in value:\n require(re.fullmatch(r'[a-f0-9]{64}', value[name]) is not None, 'invalid output digest withheld')\n if 'snapshot_bytes' in value:\n require(type(value['snapshot_bytes']) is int and 0 < value['snapshot_bytes'] <= MAX_BYTES,\n 'invalid output size withheld')\n if 'snapshot_file' in value:\n require(value['snapshot_file'] in ('drill.sqlite', 'cutover.sqlite'), 'invalid output file withheld')\n return value\n\n\ndef docker(argv, body=None, timeout=120):\n return run(['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET] + argv,\n body=body, timeout=timeout)\n\n\ndef remote(phase, revision, approved):\n # Fixed module imported only after main verifies every executor source hash.\n from native_program import PROGRAM\n payload = {'phase': phase, 'source_revision': revision, 'approval': approved,\n 'sealed': json.loads((CASE / 'sealed-live-deployment.json').read_text()),\n 'dependencies': json.loads((CASE / 'sealed-native-dependencies.json').read_text()),\n 'candidate': json.loads((CASE / 'candidate-deployment.json').read_text()),\n 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()),\n 'receipt_safety': (ROOT / 'receipt_safety.py').read_text(),\n 'purpose_checks': (CASE / 'check_installed.py').read_text(),\n 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'readonly': (CASE / 'sqlite_readonly.py').read_text()}\n script = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace(\n '__GUARD__', repr((CASE / 'change_guard.py').read_text()))\n return json_result(run(['ssh', 'railiance01', 'python3 -'], script, timeout=200))\n\n\ndef copy_snapshot(phase, revision, approved, journal):\n metadata = remote(phase, revision, approved)\n backup_phase = 'drill-backup' if phase == 'drill-copy' else 'cutover-backup'\n prior = journal.data['phases'][backup_phase]['result']\n for key in ('snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'snapshot_readonly'):\n require(metadata[key] == prior[key], 'closed frozen snapshot changed')\n name = metadata['snapshot_file']\n require(name in ('drill.sqlite', 'cutover.sqlite') and 0 < metadata['snapshot_bytes'] <= MAX_BYTES,\n 'fixed bounded snapshot required')\n pod = metadata['pod_name']\n require(re.fullmatch(r'[a-z0-9]([-a-z0-9]*[a-z0-9])?', pod) is not None, 'fixed owner pod name required')\n local = CUSTODY / name\n fd = os.open(local, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)\n # Binary snapshot stdout goes only to a private file, never tool/chat/log output.\n # Snapshot schema/count metadata was validated before opening this destination.\n with os.fdopen(fd, 'wb') as destination:\n command = ('kubectl exec -n informed-decision ' + pod +\n ' -c informed-decision -- cat /data/private/zlib85091-r1/' + name)\n transfer = subprocess.Popen(['ssh', 'railiance01', command], stdout=subprocess.PIPE,\n stderr=subprocess.DEVNULL)\n selector = selectors.DefaultSelector()\n selector.register(transfer.stdout, selectors.EVENT_READ)\n deadline = time.monotonic() + 150\n received = 0\n try:\n while True:\n remaining = deadline - time.monotonic()\n require(remaining > 0 and selector.select(remaining), 'private transfer timed out')\n block = os.read(transfer.stdout.fileno(), 1024**2)\n if not block:\n break\n received += len(block)\n require(received <= metadata['snapshot_bytes'] and received <= MAX_BYTES,\n 'private copy exceeds frozen snapshot size')\n destination.write(block)\n transfer.wait(timeout=max(1, deadline - time.monotonic()))\n finally:\n selector.close()\n transfer.stdout.close()\n if transfer.poll() is None:\n transfer.kill()\n transfer.wait()\n destination.flush()\n os.fsync(destination.fileno())\n private(local)\n require(transfer.returncode == 0 and local.stat().st_size == metadata['snapshot_bytes']\n and sha(local) == metadata['snapshot_sha256'], 'private copy incomplete/uncertain; retain, no retry')\n # Re-read identity/hash after transfer. This is verification, not a write retry.\n after = remote(phase, revision, approved)\n require(after['pod_uid'] == metadata['pod_uid'] and after['snapshot_sha256'] == metadata['snapshot_sha256'],\n 'source identity/snapshot changed during transfer')\n return {**metadata, 'off_cluster_private_copy_verified': True, 'snapshot_contents_output': False}\n\n\ndef offline_verify(journal):\n from receipt_safety import counts\n snapshot = CUSTODY / 'drill.sqlite'\n private(snapshot)\n previous = journal.data['phases']['drill-copy']['result']\n require(sha(snapshot) == previous['snapshot_sha256'], 'protected snapshot bytes changed')\n identity = docker(['info', '--format', '{{json .Name}}'])\n require(identity.returncode == 0 and json.loads(identity.stdout) == 'bnt-lap001',\n 'local offline Docker engine identity changed')\n listed = docker(['volume', 'ls', '--format', '{{.Name}}'])\n require(listed.returncode == 0 and VOLUME not in listed.stdout.splitlines(),\n 'disposable volume absence not verified')\n nonce = journal.data['phases']['offline-verify']['attempt_id']\n labels = {'informed-decision.case': 'infd-zlib-20261011-r1',\n 'informed-decision.use': 'offline-restore-only', 'informed-decision.attempt': nonce}\n created = docker(['volume', 'create', '--driver', 'local', '--label', 'informed-decision.case=infd-zlib-20261011-r1',\n '--label', 'informed-decision.attempt=' + nonce,\n '--label', 'informed-decision.use=offline-restore-only', VOLUME])\n require(created.returncode == 0 and created.stdout.strip() == VOLUME, 'volume creation uncertain; no retry')\n inspected = docker(['volume', 'inspect', VOLUME])\n require(inspected.returncode == 0, 'new volume identity unavailable')\n volume = json.loads(inspected.stdout)\n require(len(volume) == 1 and volume[0]['Name'] == VOLUME and volume[0].get('Labels') == labels\n and volume[0].get('Driver') == 'local' and not volume[0].get('Options')\n and volume[0].get('Scope') == 'local', 'new isolated volume identity changed')\n identity = {key: volume[0][key] for key in ('Name', 'Labels', 'Driver', 'Scope', 'CreatedAt')}\n journal.note('offline-verify', 'created_volume_identity', identity)\n # One-time UID0/CAP_CHOWN initialization is isolated to the NEW named volume.\n # No host/live PVC/config/token mount exists; this detail needs packet review.\n initializer = \"\"\"import hashlib,os,stat,sys\nif sys.flags.optimize:raise RuntimeError('optimized interpreter refused')\nfolder='/restore';expected=EXPECTED\nif os.listdir(folder):raise ValueError('new empty private volume required')\nos.chmod(folder,0o700)\nfd=os.open(folder+'/review.sqlite',os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_WRONLY,0o600)\ndigest=hashlib.sha256();size=0\nwith os.fdopen(fd,'wb') as target:\n while True:\n block=sys.stdin.buffer.read(1024**2)\n if not block:break\n size+=len(block)\n if size>512*1024**2:raise ValueError('private restore size exceeds bound')\n target.write(block);digest.update(block)\n target.flush();os.fsync(target.fileno())\nif digest.hexdigest()!=expected:raise ValueError('private copied snapshot differs')\nos.chown(folder+'/review.sqlite',10001,10001);os.chown(folder,10001,10001)\nprint('{\"all_checks_pass\":true,\"private_copy_verified\":true}')\n\"\"\".replace('EXPECTED', repr(previous['snapshot_sha256']))\n with snapshot.open('rb') as source:\n argv = ['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET,\n 'run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m',\n '--user', '0:0', '--cap-drop', 'ALL', '--cap-add', 'CHOWN',\n '--security-opt', 'no-new-privileges', '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore',\n '--entrypoint', 'python', IMAGE, '-c', initializer]\n copied = subprocess.run(argv, stdin=source, text=True, capture_output=True, timeout=150)\n json_result(copied)\n options = ['run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m',\n '--user', '10001:10001', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges',\n '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore']\n readonly = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'],\n (CASE / 'sqlite_readonly.py').read_text())\n require(readonly.returncode == 0, 'offline pre-open schema/integrity gate refused; never open Store')\n checked = counts(json.loads(readonly.stdout))\n require(checked == previous['snapshot_readonly'], 'frozen snapshot/clone readonly counts differ')\n admin = docker(options + ['--entrypoint', 'informed-decision-admin', IMAGE,\n 'inspect', '--db', '/restore/review.sqlite'])\n require(admin.returncode == 0, 'offline admin result uncertain')\n admin_counts = json.loads(admin.stdout)\n require(admin_counts == {key: checked[key] for key in ('schema_version', 'lifecycle', 'outbox', 'submissions')},\n 'offline admin changed frozen snapshot counters')\n # Admin Store construction must not silently alter schema/integrity/counters.\n post = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'],\n (CASE / 'sqlite_readonly.py').read_text())\n require(post.returncode == 0, 'offline post-admin readonly integrity gate refused')\n post_checked = counts(json.loads(post.stdout))\n require(post_checked == checked == previous['snapshot_readonly'],\n 'offline admin altered frozen snapshot proof')\n private(snapshot)\n require(sha(snapshot) == previous['snapshot_sha256'], 'original protected snapshot changed during drill')\n return {'all_checks_pass': True, 'offline_volume': VOLUME, 'offline_readonly': checked,\n 'offline_post_admin_readonly': post_checked, 'original_snapshot_hash_unchanged': True,\n 'created_volume_identity': identity,\n 'admin_counts': admin_counts, 'network': 'none', 'runtime_tokens_or_config_mounted': False,\n 'service_entrypoint_started': False, 'original_snapshot_retained': True}\n\n\ndef cleanup_offline(journal):\n inspected = docker(['volume', 'inspect', VOLUME])\n require(inspected.returncode == 0, 'owned disposable volume identity unverified')\n value = json.loads(inspected.stdout)\n expected = journal.data['phases']['offline-verify']['result']['created_volume_identity']\n require(len(value) == 1 and {key: value[0].get(key) for key in expected} == expected\n and not value[0].get('Options'), 'volume ownership/recreation changed')\n removed = docker(['volume', 'rm', VOLUME])\n require(removed.returncode == 0 and removed.stdout.strip() == VOLUME\n and docker(['volume', 'inspect', VOLUME]).returncode != 0,\n 'cleanup outcome ambiguous; do not retry')\n return {'all_checks_pass': True, 'only_disposable_offline_volume_removed': VOLUME,\n 'native_and_offcluster_original_snapshots_retained': True}\n\n\ndef main():\n parser = argparse.ArgumentParser(description=__doc__)\n parser.add_argument('--phase', choices=PHASES, required=True)\n parser.add_argument('--root-authority-receipt', type=Path, required=True)\n parser.add_argument('--root-native-outcome', type=Path, required=True)\n parser.add_argument('--source-revision', required=True)\n parser.add_argument('--root-fresh-scan-receipt', type=Path, required=True)\n args = parser.parse_args()\n require(args.root_authority_receipt == Path('/tmp/security-infd-zlib-root-authority.json')\n and args.root_native_outcome == Path('/tmp/security-infd-zlib-native-outcome-latest.json'),\n 'fixed supervised receipt paths required')\n require(re.fullmatch(r'[0-9a-f]{40}', args.source_revision) is not None, 'exact source commit required')\n # Verify all prepared executor/input bytes before reading authority or dispatching.\n plan = json.loads((ROOT / 'execution-plan.json').read_text())\n require(plan.get('state') == 'prepared-unapproved' and plan.get('published_artifact_verified') is True, 'unfrozen proposal cannot execute')\n require(set(plan['executor_files']) == {'actuator.py', 'authority_journal.py', 'native_program.py', 'receipt_safety.py'},\n 'fixed executor sources required')\n require(set(plan['owner_source_files']) == {'deploy/lifecycle/admission.candidate.json',\n 'deploy/lifecycle/service.candidate.json',\n 'deploy/lifecycle/kustomization.yaml',\n 'deploy/lifecycle/registries.candidate.json'},\n 'fixed owner image sources required')\n for relative, digest in plan['executor_files'].items():\n target = ROOT / relative\n require(not target.is_symlink() and sha(target) == digest, 'executor source bytes changed')\n for relative, digest in plan['case_files'].items():\n require(not Path(relative).is_absolute() and '..' not in Path(relative).parts,\n 'case source path escaped')\n target = CASE / relative\n require(not target.is_symlink() and sha(target) == digest, 'case source bytes changed')\n for relative, digest in plan['owner_source_files'].items():\n target = OWNER / relative\n require(not target.is_symlink() and sha(target) == digest, 'owner image-only source changed')\n committed = subprocess.run(['git', '-C', str(OWNER), 'show', args.source_revision + ':' + relative],\n capture_output=True)\n require(committed.returncode == 0 and hashlib.sha256(committed.stdout).hexdigest() == digest,\n 'committed owner render source differs from approved inputs')\n head = run(['git', '-C', str(OWNER), 'rev-parse', 'HEAD'])\n require(head.returncode == 0 and head.stdout.strip() == args.source_revision, 'owner source HEAD differs')\n from authority_journal import Journal, authority, fresh_scan\n private(args.root_authority_receipt)\n private(args.root_native_outcome)\n receipt = json.loads(args.root_authority_receipt.read_text())\n require(receipt.get('native_outcome_sha256') == sha(args.root_native_outcome), 'native outcome bytes changed')\n require(receipt.get('executor_plan_sha256') == sha(ROOT / 'execution-plan.json'),\n 'root verified executor plan differs')\n approved = authority(receipt, json.loads(args.root_native_outcome.read_text()), sha(CASE / 'memo.json'),\n args.source_revision, args.phase)\n if args.phase in ('release', 'verify'):\n require(args.root_fresh_scan_receipt == Path('/tmp/security-infd-zlib-fresh-scan-receipt.json'), 'fixed fresh scan receipt path required')\n private(args.root_fresh_scan_receipt)\n scan_receipt=json.loads(args.root_fresh_scan_receipt.read_text())\n report_path=Path('/tmp/security-infd-zlib-fresh-scan-full.json');private(report_path)\n require(sha(report_path)==scan_receipt.get('full_report_sha256'), 'fresh scan report bytes changed')\n fresh_scan(scan_receipt,json.loads(report_path.read_text()))\n parent = CUSTODY.parent\n private(parent, directory=True)\n ignored = run(['git', '-C', str(CUSTODIAN), 'check-ignore', '--no-index', str(CUSTODY / 'drill.sqlite')])\n require(ignored.returncode == 0, 'offcluster custody is not ignored')\n if not CUSTODY.exists():\n require(args.phase == 'drill-backup', 'first approved phase must create unique private custody')\n os.mkdir(CUSTODY, 0o700)\n private(CUSTODY, directory=True)\n capacity = os.statvfs(CUSTODY)\n require(capacity.f_bavail * capacity.f_frsize >= 2 * MAX_BYTES + 16 * 1024**2,\n 'independent protected custody headroom insufficient')\n os.makedirs(CUSTODY / 'empty-docker-config', mode=0o700, exist_ok=True)\n private(CUSTODY / 'empty-docker-config', directory=True)\n require(not list((CUSTODY / 'empty-docker-config').iterdir()), 'public Docker read config must stay empty')\n journal = Journal(CUSTODY, {'proposal': plan['proposal_id'], 'source_revision': args.source_revision,\n 'memo_sha256': sha(CASE / 'memo.json'), 'plan_sha256': sha(ROOT / 'execution-plan.json')})\n try:\n journal.start(args.phase, approved) # fsynced RUNNING before any phase effect.\n if args.phase in ('drill-copy', 'cutover-copy'):\n result = copy_snapshot(args.phase, args.source_revision, approved, journal)\n elif args.phase == 'offline-verify':\n result = offline_verify(journal)\n elif args.phase == 'cleanup-offline':\n result = cleanup_offline(journal)\n else:\n result = remote(args.phase, args.source_revision, approved, operation_intent=journal.data['phases'].get('rollback' if args.phase == 'rollback-verify' else 'release') if args.phase in ('verify', 'rollback-verify') else None)\n journal.finish(args.phase, result)\n print(json.dumps(result)) # Only count/hash/identity receipts, never snapshot bytes.\n except BaseException as error:\n safe = {'all_checks_pass': False, 'phase': args.phase, 'failure_class': type(error).__name__,\n 'failure': 'fixed phase outcome uncertain; original evidence retained; no retry',\n 'raw_output_withheld': True}\n if journal.data['phases'].get(args.phase, {}).get('state') == 'RUNNING':\n journal.finish(args.phase, safe)\n print(json.dumps(safe))\n raise SystemExit(1) from None\n finally:\n journal.close()\n\n\nif __name__ == '__main__':\n try:\n main()\n except Exception:\n raise SystemExit('supervised execution refused before phase; raw output withheld') from None\n" - }, - "execution/authority_journal.py": { - "sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108", - "content": "\"\"\"Supervised receipt bookkeeping, not an authenticated approval provider.\"\"\"\nimport datetime\nimport fcntl\nimport hashlib\nimport json\nimport os\nimport re\nfrom pathlib import Path\nimport stat\nimport uuid\n\nFOUNDER = {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'}\nMEMO_ID = 'infd-20261011-zlib-remediation-r1'\nPROPOSAL = 'INFD-WP-0009-T05/zlib85091-r1'\nPHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline',\n 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify')\n\n\ndef require(value, reason):\n if not value:\n raise ValueError(reason)\n\n\ndef sha(path):\n digest = hashlib.sha256()\n with Path(path).open('rb') as stream:\n for block in iter(lambda: stream.read(1024**2), b''):\n digest.update(block)\n return digest.hexdigest()\n\n\ndef private(path, directory=False):\n info = Path(path).lstat()\n require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode))\n and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600)\n and (directory or info.st_nlink == 1), 'private owned custody required')\n\n\ndef authority(receipt, outcome, memo_sha, source_revision, phase, now=None):\n \"\"\"Root must freshly verify native outcome at fixed ORIGIN outside this helper.\n\n Receipt files/flags are not signed grants. This function checks the exact\n supervised receipt relation; existing harness/founder authority remains the\n actual permission boundary. It never authenticates a caller as human.\n \"\"\"\n now = now or datetime.datetime.now(datetime.timezone.utc)\n require(phase in PHASES, 'fixed phase required')\n require(receipt.get('proposal_id') == PROPOSAL and receipt.get('memo_id') == MEMO_ID\n and receipt.get('memo_version') == 1 and receipt.get('memo_sha256') == memo_sha\n and receipt.get('source_revision') == source_revision\n and receipt.get('activation') == 'APPROVED'\n and receipt.get('authority_mode') == 'ADMINISTER'\n and receipt.get('target') == 'realm:kubernetes/railiance01/informed-decision'\n and receipt.get('verification_method') == 'root-fixed-origin-native-readback'\n and receipt.get('runtime_authenticated_grant') is False\n and receipt.get('outcome_query') == '/lifecycle/memos/' + MEMO_ID + '/outcomes?version=1',\n 'exact externally verified supervised receipt required')\n verified = datetime.datetime.fromisoformat(receipt['verified_at'].replace('Z', '+00:00'))\n require(verified.tzinfo is not None and 0 <= (now - verified).total_seconds() <= 120,\n 'fresh root native readback required before every phase')\n deadline = datetime.datetime.fromisoformat('2026-10-12T23:59:59+00:00')\n require(now < deadline, 'approved execution window expired')\n if phase == 'release':\n require((deadline-now).total_seconds() >= 1800, 'reserve thirty minutes for verification and recovery')\n require(outcome.get('memo_id') == MEMO_ID and outcome.get('review', {}).get('stale') is False\n and outcome['review'].get('intake_state') == 'ready', 'native review is not ready/current')\n review_id = outcome['review']['memo_id']\n require(review_id == receipt.get('review_memo_id'), 'native review identity changed')\n accepted = [x for x in outcome.get('dispositions', [])\n if x.get('submission_state') == 'confirmed'\n and x.get('disposition', {}).get('verb') == 'accept'\n and x['disposition'].get('memo_version') == 1]\n require(len(accepted) == 1, 'one confirmed founder acceptance required')\n accepted = accepted[0]['disposition']\n require(accepted.get('actor') == FOUNDER and accepted.get('memo_id') == review_id\n and accepted.get('approval_id') == receipt.get('approval_id')\n and accepted.get('id') == receipt.get('disposition_id')\n and accepted.get('at') == receipt.get('accepted_at'), 'exact founder disposition mismatch')\n require(re.fullmatch(r'[a-f0-9-]{36}', accepted['approval_id']) is not None\n and re.fullmatch(r'disp-[a-f0-9-]{36}', accepted['id']) is not None\n and re.fullmatch(r'review-[a-f0-9-]{36}', review_id) is not None,\n 'native disposition identifiers are malformed')\n accepted_at = datetime.datetime.fromisoformat(accepted['at'].replace('Z', '+00:00'))\n require(accepted_at.tzinfo is not None and accepted_at <= verified, 'acceptance time not bounded')\n return {key: receipt[key] for key in ('approval_id', 'disposition_id', 'accepted_at', 'verified_at')}\n\n\ndef fresh_scan(receipt, report, now=None):\n \"\"\"Root supervised scan bookkeeping; actual scanner proof stays retained.\"\"\"\n now=now or datetime.datetime.now(datetime.timezone.utc)\n require(receipt.get('image')=='forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23'\n and receipt.get('artifact_source_revision')=='9b4bb25d325698ae543e7e8a8a3c3592db1f58fc'\n and receipt.get('scanner')=='aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969'\n and receipt.get('no_suppression') is True and receipt.get('full_allseverity_scan') is True\n and receipt.get('exit_code')==0,'exact full immutable artifact scan required')\n scanned=datetime.datetime.fromisoformat(receipt['completed_at'].replace('Z','+00:00'))\n metadata=receipt['db_metadata'];updated=datetime.datetime.fromisoformat(metadata['UpdatedAt'].replace('Z','+00:00'));next_update=datetime.datetime.fromisoformat(metadata['NextUpdate'].replace('Z','+00:00'))\n require(all(t.tzinfo is not None for t in (scanned,updated,next_update))\n and updated<=scanned<=now0 and size<=512*1024**2 and free>=size*(2 if action==\\'drill-backup\\' else 1)+16*1024**2,\\'bounded native snapshot capacity required\\')\\n if action==\\'drill-backup\\':\\n require(not os.path.lexists(folder),\\'native case directory must be unique\\')\\n os.mkdir(folder,0o700);os.chmod(folder,0o700)\\n private(folder,True)\\n filename=\\'drill.sqlite\\' if action==\\'drill-backup\\' else \\'cutover.sqlite\\';output=folder+\\'/\\'+filename\\n require(not os.path.lexists(output),\\'snapshot output must be unique\\')\\n admin=subprocess.run([\\'informed-decision-admin\\',\\'backup\\',\\'--db\\',db,\\'--output\\',output],capture_output=True,text=True,timeout=120)\\n require(admin.returncode==0 and json.loads(admin.stdout)=={\\'status\\':\\'backed_up\\',\\'consistent_snapshot\\':True},\\'backup completion not verified; do not retry\\')\\n private(output)\\n for suffix in (\\'-wal\\',):\\n require(not os.path.exists(output+suffix) or os.stat(output+suffix).st_size==0,\\'snapshot has WAL frames; immutable read refused\\')\\n digest=hashlib.sha256()\\n with open(output,\\'rb\\') as stream:\\n for block in iter(lambda:stream.read(1024**2),b\\'\\'):digest.update(block)\\n frozen_digest=digest.hexdigest()\\n snapshot=counts(inspect_closed_snapshot(output,frozen_digest))\\n size=os.stat(output).st_size;require(0 10**12\n for number in value[group].values()):\n raise ValueError('count-only receipt values refused')\n return value\n" - }, - "execution/test_execution.py": { - "sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac", - "content": "\"\"\"Synthetic custody/transport tests. No native data, Docker or cluster action.\"\"\"\nimport copy\nimport datetime\nimport io\nimport json\nimport os\nfrom pathlib import Path\nimport subprocess\nimport sys\nimport tempfile\nimport unittest\nfrom unittest.mock import patch\nfrom contextlib import redirect_stdout\n\nimport actuator\nfrom authority_journal import Journal, authority, fresh_scan\nfrom native_program import PROGRAM\nfrom receipt_safety import counts\n\nCASE = Path(__file__).resolve().parent.parent\nNOW = datetime.datetime(2026, 10, 11, 2, 0, tzinfo=datetime.timezone.utc)\nREVISION = 'e' * 40\nCOUNT = {'schema_version': 5, 'integrity': 'ok', 'read_only_pre_open': True, 'store_constructed': False,\n 'lifecycle': {'records': 3, 'events': 8, 'pending': 1},\n 'outbox': {'pending': 2, 'delivered': 10}, 'submissions': {'confirmed': 1, 'unresolved': 1}}\n\n\ndef accepted():\n approval = '11111111-1111-4111-8111-111111111111'\n review = 'review-22222222-2222-4222-8222-222222222222'\n disposition = 'disp-33333333-3333-4333-8333-333333333333'\n receipt = {'proposal_id': 'INFD-WP-0009-T05/zlib85091-r1',\n 'memo_id': 'infd-20261011-zlib-remediation-r1', 'memo_version': 1,\n 'memo_sha256': 'a' * 64, 'source_revision': REVISION, 'activation': 'APPROVED',\n 'authority_mode': 'ADMINISTER', 'target': 'realm:kubernetes/railiance01/informed-decision',\n 'verification_method': 'root-fixed-origin-native-readback', 'runtime_authenticated_grant': False, 'outcome_query': '/lifecycle/memos/infd-20261011-zlib-remediation-r1/outcomes?version=1',\n 'verified_at': '2026-10-11T02:00:00Z', 'accepted_at': '2026-10-11T01:59:00Z',\n 'review_memo_id': review, 'approval_id': approval, 'disposition_id': disposition}\n outcome = {'memo_id': receipt['memo_id'], 'review': {'memo_id': review, 'stale': False, 'intake_state': 'ready'},\n 'dispositions': [{'approval_id': None, 'submission_state': 'confirmed',\n 'disposition': {'verb': 'accept', 'memo_version': 1, 'memo_id': review,\n 'approval_id': approval, 'id': disposition,\n 'actor': {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'},\n 'at': receipt['accepted_at']}}]}\n return receipt, outcome\n\n\nclass AuthorityAndCustody(unittest.TestCase):\n def test_exact_nested_founder_receipt_not_top_level_approval(self):\n receipt, outcome = accepted()\n result = authority(receipt, outcome, 'a' * 64, REVISION, 'drill-copy', NOW)\n self.assertEqual(result['approval_id'], receipt['approval_id'])\n\n def test_pending_stale_wrong_actor_version_and_binding_refuse(self):\n mutations = [lambda r, o: r.update(activation='PENDING'),\n lambda r, o: r.update(memo_sha256='changed'),\n lambda r, o: r.update(source_revision='changed'),\n lambda r, o: r.update(target='other'),\n lambda r, o: r.update(verified_at='2026-10-11T01:00:00Z'),\n lambda r, o: r.update(verification_method='caller-asserted'),\n lambda r, o: r.update(runtime_authenticated_grant=True),\n lambda r, o: o['review'].update(stale=True),\n lambda r, o: o['review'].update(intake_state='pending'),\n lambda r, o: o['dispositions'][0].update(submission_state='unresolved'),\n lambda r, o: o['dispositions'][0]['disposition'].update(memo_version=3),\n lambda r, o: o['dispositions'][0]['disposition'].update(actor={'kind': 'agent', 'sub': 'platform-root'}),\n lambda r, o: o['dispositions'].append(copy.deepcopy(o['dispositions'][0]))]\n for mutate in mutations:\n with self.subTest(mutate=mutate):\n r, o = accepted()\n mutate(r, o)\n with self.assertRaises(ValueError):\n authority(r, o, 'a' * 64, REVISION, 'release', NOW)\n\n def test_expired_window_and_unknown_phase_refuse(self):\n r, o = accepted()\n with self.assertRaises(ValueError):\n authority(r, o, 'a' * 64, REVISION, 'arbitrary', NOW)\n late = datetime.datetime(2026, 10, 13, tzinfo=datetime.timezone.utc)\n r['verified_at'] = late.isoformat()\n with self.assertRaises(ValueError):\n authority(r, o, 'a' * 64, REVISION, 'rollback', late)\n\n def test_durable_intent_precedes_dispatch_and_never_retries(self):\n with tempfile.TemporaryDirectory() as directory:\n parent = Path(directory)\n os.chmod(parent, 0o700)\n journal = Journal(parent, {'case': 'fixture'})\n journal.start('drill-backup', {'approval_id': 'fixture'})\n self.assertEqual(json.loads(journal.path.read_text())['phases']['drill-backup']['state'], 'RUNNING')\n journal.close() # simulated crash before result\n recovered = Journal(parent, {'case': 'fixture'})\n with self.assertRaises(ValueError):\n recovered.start('drill-backup', {})\n with self.assertRaises(ValueError):\n recovered.start('drill-backup', {})\n recovered.close()\n\n def test_phase_order_and_uncertain_release_allows_only_distinct_recovery(self):\n with tempfile.TemporaryDirectory() as directory:\n os.chmod(directory, 0o700)\n j = Journal(directory, {'case': 'fixture'})\n with self.assertRaises(ValueError):\n j.start('release', {})\n for phase in actuator.PHASES[:6]:\n j.start(phase, {})\n j.finish(phase, {'all_checks_pass': True})\n j.start('release', {})\n j.finish('release', {'all_checks_pass': False})\n with self.assertRaises(ValueError):\n j.start('release', {})\n with self.assertRaises(ValueError):\n j.start('verify', {})\n j.start('rollback', {})\n j.finish('rollback', {'all_checks_pass': True})\n j.close()\n\n def test_secret_or_unknown_child_output_is_withheld(self):\n for body in ('not-json', '{\"all_checks_pass\":true,\"token\":\"private-value\"}',\n '{\"all_checks_pass\":true,\"snapshot_readonly\":{\"schema_version\":5}}'):\n with self.assertRaises(ValueError):\n actuator.json_result(subprocess.CompletedProcess([], 0, body, ''))\n wrong = copy.deepcopy(COUNT)\n wrong['outbox'] = {'private-row-value': 1}\n with self.assertRaises(ValueError):\n counts(wrong)\n counts(COUNT) # blocked/unresolved counts are not required to be zero.\n\n def test_binary_copy_is_bounded_private_and_never_echoed(self):\n blob = b'synthetic private fixture bytes'\n digest = __import__('hashlib').sha256(blob).hexdigest()\n metadata = {'all_checks_pass': True, 'snapshot_file': 'drill.sqlite', 'snapshot_bytes': len(blob),\n 'snapshot_sha256': digest, 'snapshot_readonly': COUNT,\n 'pod_name': 'fixture-pod', 'pod_uid': 'fixture-uid'}\n journal = type('FixtureJournal', (), {'data': {'phases': {'drill-backup': {'result': metadata}}}})()\n real_popen = subprocess.Popen\n for extra in (b'', b'oversized'):\n with self.subTest(extra=extra), tempfile.TemporaryDirectory() as folder:\n os.chmod(folder, 0o700)\n output = io.StringIO()\n\n def child(*args, **kwargs):\n return real_popen([sys.executable, '-c', 'import sys;sys.stdout.buffer.write(' + repr(blob + extra) + ')'],\n stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)\n\n with patch.object(actuator, 'CUSTODY', Path(folder)), patch.object(actuator, 'remote', return_value=metadata), \\\n patch('subprocess.Popen', side_effect=child), redirect_stdout(output):\n if extra:\n with self.assertRaises(ValueError):\n actuator.copy_snapshot('drill-copy', REVISION, {}, journal)\n else:\n result = actuator.copy_snapshot('drill-copy', REVISION, {}, journal)\n self.assertTrue(result['off_cluster_private_copy_verified'])\n self.assertEqual((Path(folder) / 'drill.sqlite').read_bytes(), blob)\n self.assertEqual(output.getvalue(), '')\n self.assertEqual((Path(folder) / 'drill.sqlite').stat().st_mode & 0o777, 0o600)\n\n def test_recreated_offline_volume_cannot_be_deleted(self):\n identity = {'Name': actuator.VOLUME, 'Labels': {'nonce': 'old'}, 'CreatedAt': 'old'}\n journal = type('FixtureJournal', (), {'data': {'phases': {\n 'offline-verify': {'result': {'created_volume_identity': identity}}}}})()\n inspected = [{**identity, 'CreatedAt': 'recreated'}]\n with patch.object(actuator, 'docker', return_value=subprocess.CompletedProcess([], 0, json.dumps(inspected), '')) as mocked:\n with self.assertRaises(ValueError):\n actuator.cleanup_offline(journal)\n self.assertEqual(mocked.call_count, 1)\n\n def test_offline_post_admin_integrity_and_original_hash_are_rechecked(self):\n for defect in ('none', 'post-integrity', 'original-hash'):\n with self.subTest(defect=defect), tempfile.TemporaryDirectory() as directory:\n folder=Path(directory);os.chmod(folder,0o700)\n snapshot=folder/'drill.sqlite';snapshot.write_bytes(b'private synthetic snapshot');os.chmod(snapshot,0o600)\n digest=actuator.sha(snapshot)\n prior={'snapshot_sha256':digest,'snapshot_readonly':COUNT}\n journal=type('FixtureJournal',(),{'data':{'phases':{'drill-copy':{'result':prior},'offline-verify':{'attempt_id':'nonce'}}},'note':lambda *args:None})()\n reads=[]\n volume={'Name':actuator.VOLUME,'Labels':{'informed-decision.case':'infd-zlib-20261011-r1','informed-decision.use':'offline-restore-only','informed-decision.attempt':'nonce'},'Driver':'local','Scope':'local','CreatedAt':'fixture','Options':None}\n def docker(argv, body=None, **kwargs):\n if argv[0]=='info':body=json.dumps('bnt-lap001')\n elif argv[:2]==['volume','ls']:body=''\n elif argv[:2]==['volume','create']:body=actuator.VOLUME\n elif argv[:2]==['volume','inspect']:body=json.dumps([volume])\n elif 'informed-decision-admin' in argv:\n body=json.dumps({key:COUNT[key] for key in ('schema_version','lifecycle','outbox','submissions')})\n if defect=='original-hash':snapshot.write_bytes(b'changed synthetic original')\n elif '--entrypoint' in argv and 'python' in argv:\n reads.append(argv);proof=copy.deepcopy(COUNT)\n if len(reads)==2 and defect=='post-integrity':proof['integrity']='corrupt'\n body=json.dumps(proof)\n else:raise AssertionError('unexpected offline command')\n return subprocess.CompletedProcess(argv,0,body,'')\n with patch.object(actuator,'CUSTODY',folder),patch.object(actuator,'CASE',CASE),patch.object(actuator,'docker',side_effect=docker),patch('subprocess.run',return_value=subprocess.CompletedProcess([],0,'{\"all_checks_pass\":true,\"private_copy_verified\":true}','')):\n if defect=='none':\n result=actuator.offline_verify(journal)\n self.assertEqual(result['offline_post_admin_readonly'],COUNT)\n self.assertTrue(result['original_snapshot_hash_unchanged'])\n else:\n with self.assertRaises(ValueError):actuator.offline_verify(journal)\n self.assertEqual(len(reads),2)\n\n def test_fresh_full_scan_db_expiry_and_new_findings_refuse(self):\n receipt={'image':actuator.IMAGE,'artifact_source_revision':'9b4bb25d325698ae543e7e8a8a3c3592db1f58fc','scanner':'aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969','no_suppression':True,'full_allseverity_scan':True,'exit_code':0,'completed_at':'2026-10-11T01:59:00Z','db_metadata':{'UpdatedAt':'2026-10-10T19:00:00Z','NextUpdate':'2026-10-11T19:00:00Z','Version':2},'db_sha256':'a'*64,'full_report_sha256':'b'*64}\n report={'Results':[{'Vulnerabilities':None}]}\n self.assertEqual(fresh_scan(receipt,report,NOW)['allseverity_findings'],0)\n mutations=[lambda r,p:r.update(no_suppression=False),lambda r,p:r.update(image='foreign'),lambda r,p:r.update(scanner='other'),lambda r,p:r.update(completed_at='2026-10-10T21:00:00Z'),lambda r,p:r['db_metadata'].update(NextUpdate='2026-10-11T01:59:00Z'),lambda r,p:p['Results'][0].update(Vulnerabilities=[{'Severity':'LOW'}]),lambda r,p:p.update(Results=[])]\n for mutate in mutations:\n r,p=copy.deepcopy(receipt),copy.deepcopy(report);mutate(r,p)\n with self.assertRaises(ValueError):fresh_scan(r,p,NOW)\n\n\nclass FixedNativeProgram(unittest.TestCase):\n def fixture(self):\n sealed = json.loads((CASE / 'sealed-live-deployment.json').read_text())\n live = copy.deepcopy(sealed)\n live['metadata'].update(generation=42)\n live['status'] = {'readyReplicas': 1, 'updatedReplicas': 1, 'observedGeneration': 42}\n dependencies = json.loads((CASE / 'sealed-native-dependencies.json').read_text())\n cm = copy.deepcopy(dependencies['configmap'])\n cm.pop('canonical_data_sha256')\n service = json.loads((CASE / 'candidate-deployment.json').read_text())\n source_service = json.loads((Path('/home/worsch/informed-decision/deploy/lifecycle/service.candidate.json')).read_text())\n cm['data'] = next(x for x in source_service['items'] if x['kind'] == 'ConfigMap')['data']\n pvc = copy.deepcopy(dependencies['pvc'])\n pvc['status'] = {'phase': 'Bound'}\n app = {'metadata': {'uid': '499868f2-eabc-4df4-ae31-b89ce58d750d', 'resourceVersion': '17'},\n 'spec': {'source': {'repoURL': 'https://forgejo.coulomb.social/coulomb/informed-decision.git',\n 'path': 'deploy/lifecycle', 'targetRevision': 'c96b0c6d881e855d1d158003c6de90ef390262e2'},\n 'destination': {'namespace': 'informed-decision', 'server': 'https://kubernetes.default.svc'}}}\n objects = {'deployment': live, 'configmap': cm, 'pvc': pvc, 'application': app}\n payload = {'phase': 'release', 'source_revision': REVISION, 'approval': {}, 'sealed': sealed,\n 'dependencies': dependencies, 'candidate': service,\n 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()),\n 'readonly': (CASE / 'sqlite_readonly.py').read_text(),'purpose_checks':(CASE/'check_installed.py').read_text(),\n 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'receipt_safety': (Path(__file__).parent / 'receipt_safety.py').read_text()}\n return objects, payload\n\n def execute_fake(self, objects, payload, ambiguous=False, schema_refused=False):\n writes = []\n calls = []\n\n def fake(argv, input=None, **kwargs):\n calls.append(argv)\n if argv[:2] == ['kubectl', 'get']:\n kind = argv[2]\n if kind in objects:\n body = objects[kind]\n elif kind == 'pods':\n body = {'items': [{'metadata': {'name': 'fixture-pod', 'uid': 'c4cb1550-feda-4097-9de4-d891a657b662',\n 'ownerReferences': [{'kind': 'ReplicaSet', 'name': 'fixture-rs', 'uid': 'rs-uid'}]},\n 'spec': objects['deployment']['spec']['template']['spec'],\n 'status': {'phase': 'Running', 'containerStatuses': [{'name':'informed-decision','ready': True,'imageID':objects['deployment']['spec']['template']['spec']['containers'][0]['image']}] }}]}\n elif kind == 'replicaset':\n body = {'metadata': {'uid': 'rs-uid', 'ownerReferences': [\n {'kind': 'Deployment', 'uid': payload['sealed']['metadata']['uid']}]}}\n elif kind == 'nodes':\n body = {'items': [{'metadata': {'name': 'fixture-node'}, 'status': {\n 'addresses': [{'address': '92.205.62.239'}], 'allocatable': {'cpu': '4'},\n 'conditions': [{'type': key, 'status': 'True' if key == 'Ready' else 'False'}\n for key in ('Ready', 'MemoryPressure', 'DiskPressure', 'PIDPressure')]}}]}\n else:\n raise AssertionError('unexpected fixed metadata route')\n return subprocess.CompletedProcess(argv, 0, json.dumps(body), '')\n if argv[:2] == ['kubectl', 'exec']:\n if schema_refused:\n return subprocess.CompletedProcess(argv, 1, '', 'withheld')\n return subprocess.CompletedProcess(argv, 0, json.dumps({\n 'all_checks_pass': True, 'live_readonly_schema_integrity_counts': COUNT}), '')\n if argv[:2] == ['kubectl', 'apply']:\n admitted = copy.deepcopy(payload['sealed'])\n admitted['spec']['template']['spec']['containers'][0]['image'] = (actuator.IMAGE if payload['phase']=='release' else payload['sealed']['spec']['template']['spec']['containers'][0]['image'])\n return subprocess.CompletedProcess(argv, 0, json.dumps(admitted), '')\n if argv[:2] == ['kubectl', 'describe']:\n return subprocess.CompletedProcess(argv, 0, 'Allocated resources:\\n cpu 3460m (86%)\\n', '')\n if argv[:2] == ['kubectl', 'diff']:\n return subprocess.CompletedProcess(argv, 1, '-image: base\\n+image: candidate\\n', '')\n if argv[:2] == ['kubectl', 'patch']:\n if '--dry-run=server' not in argv:\n writes.append(json.loads(input))\n if ambiguous:\n return subprocess.CompletedProcess(argv, 1, '', 'withheld')\n return subprocess.CompletedProcess(argv, 0, 'application.argoproj.io/informed-decision\\n', '')\n raise AssertionError('unexpected command')\n\n source = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace(\n '__GUARD__', repr((CASE / 'change_guard.py').read_text()))\n output = io.StringIO()\n with patch('subprocess.run', side_effect=fake), redirect_stdout(output):\n try:\n exec(compile(source, 'fixed-native-fixture', 'exec'), {'__name__': '__fixture__'})\n except (ValueError, SystemExit):\n pass\n receipt = json.loads(output.getvalue()) if output.getvalue() else {'all_checks_pass': False}\n return receipt, writes, calls\n\n def test_only_existing_application_operation_and_deployment_are_requested(self):\n objects, payload = self.fixture()\n receipt, writes, calls = self.execute_fake(objects, payload)\n self.assertTrue(receipt['all_checks_pass'])\n self.assertFalse(receipt['rollout_verified'])\n self.assertEqual(len(writes), 1)\n operation = writes[0][-1]['value']\n self.assertFalse(operation['sync']['prune'])\n self.assertEqual(operation['sync']['resources'], [\n {'group': 'apps', 'kind': 'Deployment', 'name': 'informed-decision', 'namespace': 'informed-decision'}])\n self.assertTrue(any('--dry-run=server' in argv and argv[:2] == ['kubectl', 'patch'] for argv in calls))\n self.assertEqual(writes[0][0]['path'], '/metadata/uid')\n\n def test_source_overrides_automation_and_custody_drift_stop_before_write(self):\n mutations = [lambda o: o['application']['metadata'].update(uid='recreated'),\n lambda o: o['application']['spec'].update(syncPolicy={'automated': {}}),\n lambda o: o['application']['spec']['source'].update(kustomize={'images': ['other']}),\n lambda o: o['application']['spec'].update(sources=[{}]),\n lambda o: o['configmap']['metadata'].update(uid='recreated'),\n lambda o: o['pvc']['spec'].update(volumeName='replaced'),\n lambda o: o['deployment']['status'].update(readyReplicas=0)]\n for mutate in mutations:\n with self.subTest(mutate=mutate):\n objects, payload = self.fixture()\n mutate(objects)\n receipt, writes, calls = self.execute_fake(objects, payload)\n self.assertFalse(receipt['all_checks_pass'])\n self.assertEqual(writes, [])\n\n def test_preopen_schema_refusal_prevents_image_operation(self):\n objects, payload = self.fixture()\n receipt, writes, calls = self.execute_fake(objects, payload, schema_refused=True)\n self.assertFalse(receipt['all_checks_pass'])\n self.assertEqual(writes, [])\n\n\n def test_rollback_unready_candidate_never_reads_pod_or_database(self):\n objects, payload = self.fixture()\n payload['phase'] = 'rollback'\n objects['deployment']['status'].update(readyReplicas=0, updatedReplicas=0)\n objects['deployment']['spec']['template']['spec']['containers'][0]['image'] = actuator.IMAGE\n objects['application']['spec']['source']['targetRevision'] = REVISION\n receipt, writes, calls = self.execute_fake(objects, payload, schema_refused=True)\n self.assertTrue(receipt['all_checks_pass'])\n self.assertFalse(receipt['rollout_verified'])\n self.assertEqual(len(writes), 1)\n self.assertFalse(any(argv[:2] == ['kubectl', 'exec'] or argv[:3] == ['kubectl', 'get', 'pods'] for argv in calls))\n self.assertEqual(writes[0][-1]['value']['sync']['revision'], 'c96b0c6d881e855d1d158003c6de90ef390262e2')\n\n def test_rollback_still_refuses_custody_or_nonpair_image_drift(self):\n for defect in ('cm', 'pvc', 'image'):\n with self.subTest(defect=defect):\n objects, payload = self.fixture();payload['phase'] = 'rollback'\n objects['deployment']['status']['readyReplicas'] = 0\n if defect == 'cm':objects['configmap']['metadata']['uid'] = 'recreated'\n elif defect == 'pvc':objects['pvc']['spec']['volumeName'] = 'replaced'\n else:objects['deployment']['spec']['template']['spec']['containers'][0]['image'] = 'foreign-image'\n receipt, writes, calls = self.execute_fake(objects, payload)\n self.assertFalse(receipt['all_checks_pass']);self.assertEqual(writes, [])\n\n def test_recovery_verification_is_separate_ready_old_image_schema_proof(self):\n objects, payload = self.fixture();payload['phase'] = 'rollback-verify'\n resource={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'}\n target='c96b0c6d881e855d1d158003c6de90ef390262e2'\n payload['operation_intent']={'state':'COMPLETE','started_at':'2026-10-11T01:00:00Z','result':{'operation_requested':True,'source_revision':target,'prune':False,'resources':[resource]}}\n objects['application']['status']={'operationState':{'phase':'Succeeded','startedAt':'2026-10-11T01:00:01Z','finishedAt':'2026-10-11T01:01:00Z','operation':{'initiatedBy':{'username':'custodian-codex'},'sync':{'revision':target,'resources':[resource]}},'syncResult':{'revision':target,'resources':[{**resource,'status':'Synced'}]}}}\n receipt, writes, calls = self.execute_fake(objects, payload)\n self.assertTrue(receipt['all_checks_pass']);self.assertTrue(receipt['recovery_verification'])\n self.assertEqual(writes, [])\n self.assertTrue(any(argv[:2]==['kubectl','exec'] for argv in calls))\n objects['deployment']['status']['readyReplicas'] = 0\n receipt, writes, calls = self.execute_fake(objects,payload)\n self.assertFalse(receipt['all_checks_pass'])\n\n def test_ambiguous_patch_is_never_retried(self):\n objects, payload = self.fixture()\n receipt, writes, calls = self.execute_fake(objects, payload, ambiguous=True)\n self.assertFalse(receipt['all_checks_pass'])\n self.assertEqual(len(writes), 1)\n\n def test_false_omitempty_needs_bound_current_request_and_exact_completion(self):\n objects,payload=self.fixture();payload['phase']='rollback-verify'\n resource={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'}\n target='c96b0c6d881e855d1d158003c6de90ef390262e2'\n payload['operation_intent']={'state':'COMPLETE','started_at':'2026-10-11T01:00:00Z','result':{'operation_requested':True,'source_revision':target,'prune':False,'resources':[resource]}}\n objects['application']['status']={'operationState':{'phase':'Succeeded','startedAt':'2026-10-11T01:00:01Z','finishedAt':'2026-10-11T01:01:00Z','operation':{'initiatedBy':{'username':'custodian-codex'},'sync':{'revision':target,'resources':[resource]}},'syncResult':{'revision':target,'resources':[{**resource,'status':'Synced'}]}}}\n self.assertTrue(self.execute_fake(objects,payload)[0]['all_checks_pass'])\n mutations=[lambda o,p:o['application']['status']['operationState']['operation']['sync'].update(prune=True),\n lambda o,p:o['application']['status']['operationState']['operation']['sync'].update(prune=None),\n lambda o,p:o['application']['status']['operationState'].update(startedAt='2026-10-11T00:59:00Z'),\n lambda o,p:o['application']['status']['operationState']['operation'].update(initiatedBy={'username':'other'}),\n lambda o,p:o['application']['status']['operationState']['syncResult'].update(revision='foreign'),\n lambda o,p:o['application']['status']['operationState']['syncResult']['resources'][0].update(status='Pruned'),\n lambda o,p:p['operation_intent']['result'].update(prune=True),\n lambda o,p:p.update(operation_intent=None)]\n for mutate in mutations:\n o,p=copy.deepcopy(objects),copy.deepcopy(payload);mutate(o,p)\n receipt,writes,calls=self.execute_fake(o,p)\n self.assertFalse(receipt['all_checks_pass']);self.assertEqual(writes,[])\n\n def test_optimized_executor_refuses_before_any_input(self):\n result=subprocess.run([sys.executable,'-O',str(Path(__file__).parent/'actuator.py'),'--phase','release','--root-authority-receipt','/missing','--root-native-outcome','/missing','--source-revision',REVISION],capture_output=True,text=True)\n self.assertNotEqual(result.returncode,0)\n self.assertIn('optimized interpreter refused',result.stderr)\n\n\nif __name__ == '__main__':\n unittest.main()\n" - } - }, - "native_execution": false, - "case_evidence": { - "sqlite_readonly.py": { - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509", - "content": "\"\"\"Pre-open schema/integrity/count proof; stdlib only, never constructs Store.\"\"\"\nimport argparse\nfrom contextlib import closing\nimport hashlib\nimport os\nimport re\nimport stat\nimport json\nfrom pathlib import Path\nimport sqlite3\n\n\ndef inspect_readonly(path, *, _closed_snapshot=False):\n path = Path(path)\n if not path.is_file() or path.is_symlink():\n raise ValueError('existing nonsymlink private database required')\n uri = path.resolve().as_uri() + '?mode=ro' + ('&immutable=1' if _closed_snapshot else '')\n # sqlite3.Connection context management ends transactions; it does not close.\n with closing(sqlite3.connect(uri, uri=True, timeout=5)) as db:\n db.execute('PRAGMA query_only=ON')\n db.execute('BEGIN')\n version = db.execute('PRAGMA user_version').fetchone()[0]\n if version != 5:\n raise ValueError('pre-open database is not schema5; refuse migration')\n if db.execute('PRAGMA integrity_check').fetchall() != [('ok',)]:\n raise ValueError('read-only integrity check refused')\n lifecycle = {name: db.execute('SELECT COUNT(*) FROM ' + table).fetchone()[0]\n for name, table in (('records', 'lifecycle_records'),\n ('events', 'lifecycle_events'),\n ('pending', 'lifecycle_pending'))}\n result = {'schema_version': version, 'lifecycle': lifecycle,\n 'outbox': dict(db.execute('SELECT state,COUNT(*) FROM outbox GROUP BY state')),\n 'submissions': dict(db.execute('SELECT state,COUNT(*) FROM submissions GROUP BY state')),\n 'integrity': 'ok', 'read_only_pre_open': True, 'store_constructed': False}\n db.rollback()\n return result\n\n\ndef inspect_closed_snapshot(path, expected_sha256):\n \"\"\"Only a privately retained, frozen main-file snapshot with no WAL frames.\n\n Caller must establish exclusive snapshot creation/admin exit/no writer, and\n supply the recorded frozen main digest. Never use for the live database.\n Immutable SQLite reads ignore stale SHM and never create/change sidecars.\n Existing sidecars remain custody evidence; nothing is deleted/checkpointed.\n \"\"\"\n path = Path(path)\n if re.fullmatch(r'[0-9a-f]{64}', expected_sha256) is None:\n raise ValueError('exact frozen snapshot digest required')\n def metadata(target):\n info = target.lstat()\n if not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode) != 0o600 or info.st_nlink != 1:\n raise ValueError('private single-link snapshot custody required')\n return info\n main = metadata(path)\n if not 0 < main.st_size <= 512 * 1024**2:\n raise ValueError('bounded frozen snapshot required')\n def sidecars():\n identities = {}\n for suffix in ('-wal', '-shm'):\n target = Path(str(path) + suffix)\n if os.path.lexists(target):\n info = metadata(target)\n if info.st_uid != main.st_uid or info.st_gid != main.st_gid:\n raise ValueError('snapshot sidecar custody changed')\n if suffix == '-wal' and info.st_size != 0:\n raise ValueError('snapshot has WAL frames; immutable read refused')\n identities[suffix] = (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns)\n return identities\n before_sidecars = sidecars()\n def digest():\n value = hashlib.sha256()\n with path.open('rb') as stream:\n for block in iter(lambda: stream.read(1024**2), b''):\n value.update(block)\n return value.hexdigest()\n if digest() != expected_sha256:\n raise ValueError('frozen main-file digest changed before inspection')\n proof = inspect_readonly(path, _closed_snapshot=True)\n after = metadata(path)\n if (after.st_dev, after.st_ino, after.st_uid, after.st_gid, after.st_size) != (main.st_dev, main.st_ino, main.st_uid, main.st_gid, main.st_size):\n raise ValueError('frozen main-file identity changed')\n if digest() != expected_sha256 or sidecars() != before_sidecars:\n raise ValueError('frozen snapshot changed during inspection')\n return proof\n\n\nif __name__ == '__main__':\n parser = argparse.ArgumentParser(description=__doc__)\n parser.add_argument('--db', type=Path, required=True)\n args = parser.parse_args()\n try:\n print(json.dumps(inspect_readonly(args.db), sort_keys=True))\n except Exception:\n raise SystemExit('read-only schema5/integrity/count gate refused; no Store/admin open') from None\n" - }, - "change_guard.py": { - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201", - "content": "\"\"\"Read-only exact Deployment and admission boundaries; no mutation authority.\"\"\"\nimport copy\nimport hashlib\nimport json\n\nBASE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a'\nCANDIDATE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23'\n\n\ndef dependency_projection(configmap, pvc):\n if (configmap.get('kind') != 'ConfigMap' or configmap.get('immutable') is not True\n or configmap['metadata']['namespace'] != 'informed-decision'\n or configmap['metadata']['name'] != 'informed-decision-runtime-6c221878d051'\n or set(configmap.get('data', {})) != {'runtime.json'}\n or configmap.get('binaryData')):\n raise ValueError('unexpected immutable runtime config identity')\n if (pvc.get('kind') != 'PersistentVolumeClaim'\n or pvc['metadata']['namespace'] != 'informed-decision'\n or pvc['metadata']['name'] != 'informed-decision-data'\n or pvc.get('status', {}).get('phase') != 'Bound'\n or not pvc.get('spec', {}).get('volumeName')):\n raise ValueError('unexpected bound PVC custody')\n config = projection(configmap)\n config.pop('data')\n config['canonical_data_sha256'] = hashlib.sha256(json.dumps(\n configmap['data'], sort_keys=True, separators=(',', ':')).encode()).hexdigest()\n return {'configmap': config, 'pvc': projection(pvc), 'pvc_phase': 'Bound'}\n\n\ndef check_dependencies(configmap, pvc, sealed):\n if dependency_projection(configmap, pvc) != sealed:\n raise ValueError('runtime config or PVC identity/custody changed')\n\n\ndef projection(value):\n value = copy.deepcopy(value)\n value.pop('status', None)\n for key in ('resourceVersion', 'generation', 'creationTimestamp', 'managedFields'):\n value['metadata'].pop(key, None)\n for key in ('kubectl.kubernetes.io/last-applied-configuration', 'deployment.kubernetes.io/revision'):\n value['metadata'].get('annotations', {}).pop(key, None)\n return value\n\n\ndef check_current(current, sealed):\n if projection(current) != sealed:\n raise ValueError('sealed live UID or configuration changed')\n\n\ndef check_admitted(admitted, sealed):\n expected = copy.deepcopy(sealed)\n containers = expected['spec']['template']['spec']['containers']\n if len(containers) != 1 or containers[0]['name'] != 'informed-decision' or containers[0]['image'] != BASE:\n raise ValueError('unexpected baseline container identity')\n containers[0]['image'] = CANDIDATE\n if projection(admitted) != expected:\n raise ValueError('admission changed configuration beyond the sole image pin')\n\n\ndef transport_result(exit_code, output):\n import json\n try:\n value = json.loads(output)\n except (ValueError, TypeError):\n return {'all_checks_pass': False, 'reason': 'invalid transport result'}\n if exit_code != 0 or not isinstance(value, dict) or value.get('all_checks_pass') is not True:\n return {'all_checks_pass': False, 'reason': 'read-only preflight refused'}\n return value\n" - }, - "check_installed.py": { - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad", - "content": "import copy,json,time\nfrom datetime import datetime,timezone\nfrom informed_decision.policy import PolicyClient,CONTRACT,submitted_digest\npin='sha256:'+'b'*64\nreq={'id':'image-purpose-fixture','tenant':'tenant:platform','subject':{'id':'synthetic','type':'service','tenant':'tenant:platform'},'resource':{'id':'memo:fixture','type':'memo','system':'informed-decision','tenant':'tenant:platform'},'action':'register','context':{}}\nclass Transport:\n def request(self,*args,**kwargs):\n now=time.time(); stamp=lambda t:datetime.fromtimestamp(t,timezone.utc).isoformat()\n b={**copy.deepcopy({k:req[k] for k in ('tenant','subject','action','resource','context')}),'request_digest':'sha256:'+'e'*64,'submitted_request_digest':submitted_digest(req)}\n if self.purpose!='ABSENT':b['purpose']=self.purpose\n d={'id':'synthetic-decision','contract_version':CONTRACT,'request_id':req['id'],'effect':'allow','matched_policy_version':'v1','subject':req['subject'],'resource':req['resource'],'binding':b,'lifetime':{'kind':'ttl','not_before':stamp(now-1),'expires_at':stamp(now+60)},'provenance':{'policy_package':'fixture','policy_version':'v1','policy_package_digest':pin,'registry_snapshot_digest':'sha256:'+'c'*64,'evaluator':'flex-auth/fixture','decision_time':stamp(now)}}\n return 200,d\nchecks=[]\nfor purpose in ('ABSENT','','readback','enforcement','unknown',None,False,0,[],{}):\n t=Transport();t.purpose=purpose\n c=PolicyClient('https://fixture.invalid',lambda:'synthetic-only',package='fixture',version='v1',package_digest=pin,transport=t)\n o=c.check(req)\n if purpose in ('ABSENT',''):assert o.outcome=='allow';o.require_current(time.time())\n else:assert o.outcome=='invalid_decision' and o.decision_json is None\n checks.append(str(purpose))\nprint(json.dumps({'installed_image_purpose_variants':len(checks),'binding_forms_allowed':2,'advisory_or_malformed_refused':8,'synthetic_only':True,'native_provider':False}))\n" - }, - "post-ready-native-outcomes-command.py": { - "sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa", - "content": "\"\"\"Run only inside existing admitted StateHub Pod AFTER verified candidate Ready.\n\nThe fixed existing dispatcher loads its internal credential; root never reads\nit. HTTP success proves canonical lifecycle.outcomes PDP gate returned, not an\nadvisory verdict emission or a substitute for separate evidence obligations.\nRoot records this bounded metadata receipt with verified PodUID/image/source.\nThis preparation only writes source; no request is made here.\n\"\"\"\nimport datetime,json,urllib.request,urllib.error\nfrom api.services.memo_dispatch import ORIGIN,NoRedirect,token_file\nid='infd-20261011-zlib-remediation-r1'\nreq=urllib.request.Request(ORIGIN+'/v1/decisions/'+id+'/outcomes?version=1',headers={'Authorization':'Bearer '+token_file('/var/run/secrets/state-hub/informed-decision/lifecycle-token')})\nopener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect())\ntry:\n with opener.open(req,timeout=15) as response:\n raw=response.read(1048577)\n if len(raw)>1048576:raise ValueError('oversize')\n r=json.loads(raw)\n print(json.dumps({'memo_id':id,'source_memo_version':1,'observed_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'native_authenticated_canonical_lifecycle_outcomes':True,'native_provider_path_returned':True,'credential_material_output':False,'review':r.get('review'),'disposition_count':len(r.get('dispositions',[])),'dispositions':[{k:d.get(k) for k in ('id','approval_id','submission_state','disposition','approved_at','decided_at')} for d in r.get('dispositions',[])]}))\nexcept Exception as e:print(json.dumps({'memo_id':id,'status':'unverified','error_class':type(e).__name__}));raise SystemExit(1)\n" - }, - "installed-zlib-proof.json": { - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68", - "content": "{\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"zlib_package\": \"1.3.2-r1\",\n \"files\": {\n \"lib/apk/db/installed\": [\n \"file\",\n 420,\n 0,\n 0,\n \"f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001\"\n ],\n \"usr/lib/libz.so.1.3.2\": [\n \"file\",\n 493,\n 0,\n 0,\n \"ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2\"\n ]\n }\n}\n" - } - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0 deleted file mode 100644 index 249eb1c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0 +++ /dev/null @@ -1,191 +0,0 @@ ---- -id: INFD-WP-0009 -type: workplan -title: "Repair lifecycle consumer pin after Access Engine v2 policy release" -domain: infotech -repo: informed-decision -status: active -owner: codex -topic_slug: netkingdom -created: "2026-10-10" -updated: "2026-10-11" -flavor: implementation -depends_on: [] -related: - - FLEX-WP-0035 - - INFD-WP-0007 -state_hub_workstream_id: "77eaeabf-0644-595f-b3d5-1168e6a294c4" ---- - -## Diagnose and prepare exact config repair - -```task -id: INFD-WP-0009-T01 -status: done -priority: high -state_hub_task_id: "5c30c308-8fe2-5956-b0c1-66c80bcb07e9" -``` - -Native State Hub lifecycle submission returns HTTP 503 `invalid_decision`. -The service remains Ready but its configured lifecycle package is v1/cee6a79; -Access Engine lifecycle provider now runs v2/da01e045 after FLEX-WP-0035. -The envelope contract remains `flex-auth.decision-record.v1`; this is a policy -pin mismatch, not an envelope compatibility migration. Keep exact package, -version and digest enforcement. Source preparation and regression evidence are -in `docs/evidence/2026-10-10-lifecycle-consumer-pin-repair.md`. - -## Release and verify the config-only repair - -```task -id: INFD-WP-0009-T02 -status: done -priority: high -needs_human: false -state_hub_task_id: "1e6684d7-7676-5c70-b701-9dad124b4f07" -``` - -Apply only the reviewed immutable runtime ConfigMap and Deployment reference -through the existing owner GitOps path after exact scoped supervisor approval. -Image, schema, credentials, audience and review policy remain pinned. Retain -server dry-run/diff, readiness and native State Hub register/publish receipts; -retry with stable write keys, do not infer success from readiness. Root agent -owns live evidence and approval recording. This is a repair of the shipped -initial lifecycle, separate from INFD-WP-0008 expansion. - -**Verified 2026-10-11:** founder approved exact config-only revision -`7db8485ffecdebfabd77a9daec085553c74bf0bf`. Manual Argo Application operation -synced only `informed-decision-runtime-6c221878d051` and Deployment, prune false. -Application Succeeded/Healthy; generation24 observed24 Ready1; `/readyz`200; -consumer v2/da01e045 and unchanged image7f5c6cb verified. Native lifecycle -registration and publication succeeded (events28/29). A later review request -rejected packet validation; this separate caller error does not undo verified -authoring. Custodian evidence records original approval, execution and rollback: -`docs/evidence/2026-10-10-security-consolidation/infd-pin-repair-preflight.json`. - -## Explicitly reject advisory purpose at consumer boundary - -```task -id: INFD-WP-0009-T03 -status: done -priority: medium -state_hub_task_id: "ee798d7d-5e60-5247-9c9b-285a7f65cf11" -``` - -PolicyClient now validates binding purpose: absent or exact empty string only; -readback and malformed purposes are refused. Nine purpose variants and a real -provider response contamination test preserve the binding, pin, expiry and -obligations gates. This task covers source implementation and regression tests. -At source implementation time the runtime image lacked this patch. Service build/native release belongs -to T04, separate from the config-only T02 repair. - - -## Release the advisory-purpose source correction - -```task -id: INFD-WP-0009-T04 -status: done -priority: medium -state_hub_task_id: "0e1bb868-7bc2-5d4d-953c-e0edaf660ad0" -``` - -Build and scan a new service image including T03, retain provenance and backup -compatibility evidence, prepare a separate scoped image proposal, and verify -native binding/refusal after the owner's approved release. Do not fold an image -change into the config-only T02 proposal. The pre-release runtime baseline was exact7f5c6cb. - -**Historical local preparation 2026-10-11:** narrow single-module image overlay on immutable running -7f5c6cb; exact source policy SHA-256 9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62. -Local image/index b8b58944786015a5375304241c149d1dab885eb539d6d8945f56b01a3c2bc821 -is built and retained, unpublished and undeployed. Base layer prefix and runtime -configuration are preserved; installed-image 10 purpose variants pass; 13 -container checks include restart and synthetic consistent backup/restore. A -fresh Trivy database scan has zero HIGH/CRITICAL findings, with its Alpine EOL -warning retained. See `docs/evidence/2026-10-11-purpose-hardening/README.md`. -Remaining gates: source-bound registry publication and exact digest verification, -fresh native snapshot/restore compatibility, owner GitOps server dry-run/diff, -separate scoped supervisor approval and native post-release binding/refusal. -No release approval or actual production-data restore is inferred from fixtures. - -**Publication and release preparation 2026-10-11:** actual owner CI -task11484/run3 succeeds at source3e4bcb26f8eeaa8c64345c565477b5ac8ccbb168, -publishing immutable image763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a. -The failed run1/f66 and run2/ace are retained as refinements in the same trial. -Anonymous registry readback proves all8base compressed blobs/rootfs unchanged -plus only policy.py; legacy media-type/default-config serialization differences -are explicit. Pulled published artifact UID10001/GID10001, installed mode0644 -and exactpolicy hash, all10purpose forms pass. Full pinned scanner/DB covers -44packages: 0HIGH/CRITICAL, 1fixableMEDIUM CVE-2026-85091 in zlib1.3.2-r0, -identical actual current-base scan. This is not a zero-CVE release or estate -risk grade. T05 and INFD-RISK-20261011-ZLIB-85091 are live owner follow-through. - -The exact separate20document informed-decision packet is prepared and -offline typed/intake validated, with no submission or founder disposition -claimed. Source candidates differ solely in Deploymentimage and retain -ConfigMap6c221878d051/schema5/PVC/identity/policy pins. Fresh read-only -serverdryrun/clientdiff pass with sealedUID/fullconfiguration and actualCM/PVC -UID/contentchecksum/boundvolume custody; optimizedinterpreters refused. -Independent review refinement requires stdlib mode=ro schema5/integrity/count -checks before any native/offline admin/Store open (native gate notexecuted); existingmanual -Argo remains Healthy/OutOfSync at7db8485, not whole-app reconciled. -Remaining gates: independent owner review, actual founder native approval, -fresh protected native consistent snapshot/off-cluster custody/offline restore, -refresh preflight, exact owner-source pin/Deployment-only manual sync and -verified native installed purpose/refusal/lifecycle behavior. Synthetic native -placement checks are not new provider authorization evidence. Runtime remains7f5c. - -Evidence: `docs/evidence/2026-10-11-purpose-image-release/`. - -**Actual approved release and independent verification, 2026-10-11:** source -c96b0c6d881e855d1d158003c6de90ef390262e2, fresh version2 founder acceptance, -existing-snapshot rescue, isolated actual-data compatibility and disposable -cleanup, fresh cutover backup/private exact copy, and Deployment-only manual -image763c release are evidenced. Independent read-only reconciliation confirms -Ready generation25, exact image/module/config/PVC/controlled Pod, UID/GID10001, -schema5/integrity and preserved live counts16records/56events/0pending, -336delivered/43confirmed; healthz/readyz200. Authenticated actual lifecycle.outcomes -readback at07:01:37 after Ready07:00:42 traverses installed canonical PDP gates. -Installed ten purpose fixtures (two canonical allows/eight refusals) are synthetic, -not provider advisory responses. The separate denial probe remains unexecuted. -This satisfies the bounded T04 release/consumer verification, without declaring -full emission guarantee, all human MFA or a zero-CVE image. Earlier preparation -gates above are historical. Original failed drill-copy and final verifier -UNCERTAIN remain immutable; no failed phase replay/helper amendment or scored -new trial. Argo omitted false prune is independently reconciled to exact approved -non-pruning operation; future verifier defect is live unpromoted INFD-IN-0010. -T05 remains todo/due2026-10-18, so WP0009 remains active. -Review/evidence: docs/reviews/2026-10-11-purpose-native-outcome-product-owner-acceptance.md -and docs/evidence/2026-10-11-purpose-native-acceptance/. - -## Resolve inherited published-image zlib finding - -```task -id: INFD-WP-0009-T05 -status: progress -priority: medium -state_hub_task_id: "1743596f-8558-5a6b-afef-4dd5c1e9c0ae" -``` - -Owner follow-through `INFD-RISK-20261011-ZLIB-85091` tracks actual published -base and purpose-overlay scan finding CVE-2026-85091: zlib 1.3.2-r0, fixed -1.3.2-r1, scanner MEDIUM; this is not an estate risk grade. Prepare and verify a -separate source-bound base/dependency refresh, then obtain distinct native -release approval before changing the workload. Review due 2026-10-18; the date -is a manual owner follow-up, with no automatic expiry/rollback controller. -T04 cannot be treated as a zero-CVE release or silently include the base fix. -Preserve the original detection, pending founder disposition and remediation -evidence. Close only after actual installed-package proof and full rescan. - -Evidence: `docs/evidence/2026-10-11-purpose-image-release/inherited-vulnerability-follow-through.json`. - -2026-10-11 — T05 bounded zlib-only source candidate preparation: -Exactcurrent763base andsignedofficialr1/packageindex buildoffline. Actual44package -fullscan sameDB baseline1MEDCVE85091→localcandidate0allseverity. Finalfilesystem -deltaonlylibz+APKdb;36appfiles/policy9d/world/keys/Python/runtime unchanged. -Localcandidate is unpublished/unapproved;4sourcenegative tests+actualbackend -and10purpose checks pass. Source/evidence:docs/evidence/2026-10-11-zlib-remediation-candidate/. -T05progress/planactive:actualownerCIpublishedprovenance/fullscan/SBOM,distinctnative -founderreleaseapproval andactualinstalledr1/runtime/fullrescan still required. -NoT04/nativec96/helper/journal/DB/ConfigMap/PVC/identity/policy changes. - - -Prepared distinct source-only T05 zlib85091 native release packet in `docs/evidence/2026-10-11-zlib-image-release/`: actual published image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23, full scan zero44packages, fresh image-only candidate and unchanged-baseline rollback server dry-runs. New private drill/cutover custody, no-network schema5 clone, immutable prior T04 helpers/journals, explicitfalse request plus Argo bool-omitempty completion guards and separate post-Ready authenticated canonical lifecycle/PDP proof. Product/Platform exact acknowledgment and NEW founder informed decision still required before protected database/workload action; old T04 approval is excluded. T05 remains progress and risk stays open until actual installed acceptance/native rescan. INFD-IN-0010 remains unpromoted. diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f deleted file mode 100644 index 9074439..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f +++ /dev/null @@ -1,106 +0,0 @@ -{ - "id": "infd-20261011-zlib-remediation-r1", - "version": 1, - "question": "Approve the exact zlib85091 fixed image-only release, new protected backup/restore compatibility drill and database-preserving rollback?", - "requested_act": "approve", - "binding_level": "organizational", - "brief": "Published owner CI image f558 has zero full-scan findings across44packages; only zlib1.3.2-r0 to r1 changes. Fresh schema5 private backup/isolated clone drill and cutover snapshot precede one Deployment manual prune=false Recreate sync. Separate founder go required; old T04 acceptance/history unchanged. Rollback image763c/Applicationc96 retains latestDB. Release reserves30minutes before Oct12 deadline.", - "binding": { - "principal": { - "id": "pending-human-session", - "kind": "person", - "display_name": "Founder review" - }, - "target": { - "kind": "environment", - "id": "realm:kubernetes/railiance01/informed-decision", - "label": "Informed Decision zlib85091 image-only remediation on railiance01", - "environment": "production" - }, - "terms": "Approve only INFD-WP-0009-T05/zlib85091-r1 trialinfd-zlib-20261011-r1 and exact attached digests: new private drillbackup/copy, isolatedschema5pre/admin/postintegrityandcounts, exactcreatedclonecleanup, freshcutoverbackup/copy, manualfinalsourceDeployment-onlyimagef558/prune=false. ImmutableCM/PVC/config/credentials/audience/resources sealed. Freshrootnativeauthenticated confirmed founderACCEPTsource1 beforeeveryphase; receiptsarebookkeeping/noautonomousgrant. Rollbackonlyimage763c/Applicationc96 preserveslatestDB, separateverification. AllphasesbeginbeforeOct12 23:59:59Z; releasereserves30minutes; newdecisionbeyond. ExistingT04UNCERTAINEvidenceunaltered, nooldhelperamendment/phase retry, noresidualpromotion ordoctrinechange." - }, - "step_kind": "approve", - "packet": [ - { - "item_id": "decision-packet-md", - "label": "decision-packet.md", - "hash": "sha256:c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "item_id": "proposal-json", - "label": "proposal.json", - "hash": "sha256:a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "item_id": "native-backup-restore-protocol-md", - "label": "native-backup-restore-protocol.md", - "hash": "sha256:0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "item_id": "native-preflight-json", - "label": "native-preflight.json", - "hash": "sha256:56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "item_id": "rollback-preflight-json", - "label": "rollback-preflight.json", - "hash": "sha256:0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "item_id": "published-verification-json", - "label": "published-verification.json", - "hash": "sha256:dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "item_id": "artifact-comparison-json", - "label": "artifact-comparison.json", - "hash": "sha256:a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "item_id": "installed-zlib-proof-json", - "label": "installed-zlib-proof.json", - "hash": "sha256:6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "item_id": "sealed-live-deployment-json", - "label": "sealed-live-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "sealed-native-dependencies-json", - "label": "sealed-native-dependencies.json", - "hash": "sha256:042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "item_id": "candidate-deployment-json", - "label": "candidate-deployment.json", - "hash": "sha256:487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "item_id": "rollback-deployment-json", - "label": "rollback-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "execution-bundle-json", - "label": "execution-bundle.json", - "hash": "sha256:09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - } - ], - "highlights": [ - { - "id": "protected-drill-and-recreate", - "item_id": "decision-packet-md", - "note": "This new T05 decision authorizes protected real-data backup/copy and no-network same-schema restore drill plus brief Recreate image release. Scan0 is actual published44package result, not installed acceptance. Rollback preserves latestDB; priorfailedT04evidence retained. No oldapprovalreuse. Allphase starts expireOct12; release reserves30minutesforrecovery; noautomaticextension.", - "required_ack": true, - "severity": "warning", - "locator": { - "kind": "section", - "heading": "Requested decision" - } - } - ], - "sealed": false, - "approval_id": null, - "approval_binding_digest": null -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294 deleted file mode 100644 index 9806488..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294 +++ /dev/null @@ -1,207 +0,0 @@ -[ - { - "id": "infd-20261011-zlib-remediation-r1", - "register": { - "memo": { - "id": "infd-20261011-zlib-remediation-r1", - "version": 1, - "question": "Approve the exact zlib85091 fixed image-only release, new protected backup/restore compatibility drill and database-preserving rollback?", - "requested_act": "approve", - "binding_level": "organizational", - "brief": "Published owner CI image f558 has zero full-scan findings across44packages; only zlib1.3.2-r0 to r1 changes. Fresh schema5 private backup/isolated clone drill and cutover snapshot precede one Deployment manual prune=false Recreate sync. Separate founder go required; old T04 acceptance/history unchanged. Rollback image763c/Applicationc96 retains latestDB. Release reserves30minutes before Oct12 deadline.", - "binding": { - "principal": { - "id": "pending-human-session", - "kind": "person", - "display_name": "Founder review" - }, - "target": { - "kind": "environment", - "id": "realm:kubernetes/railiance01/informed-decision", - "label": "Informed Decision zlib85091 image-only remediation on railiance01", - "environment": "production" - }, - "terms": "Approve only INFD-WP-0009-T05/zlib85091-r1 trialinfd-zlib-20261011-r1 and exact attached digests: new private drillbackup/copy, isolatedschema5pre/admin/postintegrityandcounts, exactcreatedclonecleanup, freshcutoverbackup/copy, manualfinalsourceDeployment-onlyimagef558/prune=false. ImmutableCM/PVC/config/credentials/audience/resources sealed. Freshrootnativeauthenticated confirmed founderACCEPTsource1 beforeeveryphase; receiptsarebookkeeping/noautonomousgrant. Rollbackonlyimage763c/Applicationc96 preserveslatestDB, separateverification. AllphasesbeginbeforeOct12 23:59:59Z; releasereserves30minutes; newdecisionbeyond. ExistingT04UNCERTAINEvidenceunaltered, nooldhelperamendment/phase retry, noresidualpromotion ordoctrinechange." - }, - "step_kind": "approve", - "packet": [ - { - "item_id": "decision-packet-md", - "label": "decision-packet.md", - "hash": "sha256:c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "item_id": "proposal-json", - "label": "proposal.json", - "hash": "sha256:a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "item_id": "native-backup-restore-protocol-md", - "label": "native-backup-restore-protocol.md", - "hash": "sha256:0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "item_id": "native-preflight-json", - "label": "native-preflight.json", - "hash": "sha256:56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "item_id": "rollback-preflight-json", - "label": "rollback-preflight.json", - "hash": "sha256:0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "item_id": "published-verification-json", - "label": "published-verification.json", - "hash": "sha256:dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "item_id": "artifact-comparison-json", - "label": "artifact-comparison.json", - "hash": "sha256:a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "item_id": "installed-zlib-proof-json", - "label": "installed-zlib-proof.json", - "hash": "sha256:6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "item_id": "sealed-live-deployment-json", - "label": "sealed-live-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "sealed-native-dependencies-json", - "label": "sealed-native-dependencies.json", - "hash": "sha256:042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "item_id": "candidate-deployment-json", - "label": "candidate-deployment.json", - "hash": "sha256:487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "item_id": "rollback-deployment-json", - "label": "rollback-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "execution-bundle-json", - "label": "execution-bundle.json", - "hash": "sha256:09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - } - ], - "highlights": [ - { - "id": "protected-drill-and-recreate", - "item_id": "decision-packet-md", - "note": "This new T05 decision authorizes protected real-data backup/copy and no-network same-schema restore drill plus brief Recreate image release. Scan0 is actual published44package result, not installed acceptance. Rollback preserves latestDB; priorfailedT04evidence retained. No oldapprovalreuse. Allphase starts expireOct12; release reserves30minutesforrecovery; noautomaticextension.", - "required_ack": true, - "severity": "warning", - "locator": { - "kind": "section", - "heading": "Requested decision" - } - } - ], - "sealed": false, - "approval_id": null, - "approval_binding_digest": null - }, - "documents": [ - { - "digest": "sha256:c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a", - "media_type": "text/markdown", - "content_base64": "IyBSZXF1ZXN0ZWQgZGVjaXNpb24KCkFwcHJvdmUgdGhlIGV4YWN0IG5ldyBUMDUgemxpYi1vbmx5IGltYWdlIHJlbGVhc2UgYW5kIHByb3RlY3RlZCBzYW1lLXNjaGVtYSBjb21wYXRpYmlsaXR5IGRyaWxsLCBhZnRlciBmaW5hbCBpbmRlcGVuZGVudCBQcm9kdWN0IGFuZCBQbGF0Zm9ybSBhY2tub3dsZWRnbWVudHMuIFRoaXMgcGFja2V0IGlzIHByZXBhcmVkIGFuZCB1bmFwcHJvdmVkOyBmcmVzaCByb2xsYmFjayBzZXJ2ZXIgZHJ5LXJ1biBwYXNzZWQgd2l0aCBleGFjdGx5IHRoZSBiYXNlbGluZSBpbWFnZSBhbmQgZW1wdHkgZGlmZi4gUm9vdCByZXRhaW5zIGV4YWN0IGZpbmFsIG93bmVyIHNvdXJjZSByZXZpc2lvbiBhbmQgaW5kZXBlbmRlbnQgYWNrbm93bGVkZ21lbnRzIGJlZm9yZSBhc2tpbmcgZm9yIG5hdGl2ZSBmb3VuZGVyIGRpc3Bvc2l0aW9uLiBUaGUgcHJpb3IgVDA0IGFwcHJvdmFsIGlzIGV4Y2x1ZGVkLgoKVGhlIGFkbWl0dGVkIG93bmVyIENJIHB1Ymxpc2hlZCBpbW11dGFibGUgaW1hZ2UgZjU1ODhmNDk2YjFmMjY3ZDA3Y2RjOWY1MjA4ZTZmMmYyNDFmNzRiNzM5YzIzOTI0YjAzNzAyZGVjMTNhNWMyMyBmcm9tIHNvdXJjZTliNGJiMjVkMzI1Njk4YWU1NDNlN2U4YThhM2MzNTkyZGIxZjU4ZmMuIEl0cyBhY3R1YWwgZnVsbCBzY2FuIHJlcG9ydHMgemVybyBmaW5kaW5ncyBhY3Jvc3M0NHBhY2thZ2VzLiBUaGUgc2FtZSBzY2FuIERCIGZpbmRzIG9uZU1FRElVTSBvbiBjdXJyZW50aW1hZ2U3NjNjLiBTaWduZWQgb2ZmaWNpYWwgQWxwaW5lIHpsaWIxLjMuMi1yMSBpcyB0aGUgc29sZSBwYWNrYWdlIGNoYW5nZTsgYWN0dWFsIHB1Ymxpc2hlZCBmaWxlc3lzdGVtIHByb29mIHNob3dzIG9ubHlsaWJ6IGFuZEFQS2luc3RhbGxlZGRhdGFiYXNlIGNoYW5nZXMsIHplcm8zMTNkaXJlY3RvcnltZXRhZGF0YSBjaGFuZ2VzLCByZXRhaW5lZG5pbmViYXNlbGF5ZXJzIGFuZCB1bmNoYW5nZWQzNmFwcGxpY2F0aW9uZmlsZXMvcHVycG9zZXBvbGljeS9jb25maWcvY3JlZGVudGlhbHMvYXVkaWVuY2UvcmVzb3VyY2VzLiBSZWdpc3RyeS10by1kYWVtb24gY29uZmlnIHNlcmlhbGl6YXRpb24gZGlmZmVycyB1bmRlciBjbG9zZWQgdmVyaWZpZWQgbm9ybWFsaXphdGlvbiwgd2l0aG91dCBydW50aW1lIGNoYW5nZS4KClRoZSBleGFjdCBvcmRlcmVkIHByb3RlY3RlZCBkcmlsbCBhbmQgcmVsZWFzZSBhcmUgc3BlY2lmaWVkIGluIG5hdGl2ZS1iYWNrdXAtcmVzdG9yZS1wcm90b2NvbC5tZCBhbmQgaGFzaC1ib3VuZCBleGVjdXRpb24gYnVuZGxlLiBOZXcgYmFja3VwIHBhdGhzLCBpc29sYXRlZCBjbG9uZSBhbmQgam91cm5hbCBhcmUgZGlzdGluY3QgZnJvbSBvbGQgZXZpZGVuY2UuIFJlYWwgcHJpdmF0ZSBkYXRhIHdpbGwgZW50ZXIgb25seSBhcHByb3ZlZCBwcml2YXRlIGN1c3RvZHkgYW5kIHRoZSBpc29sYXRlZCBuby1uZXR3b3JrIGNsb25lOyBjb250ZW50IG5ldmVyIGVudGVycyBwdWJsaWMgZXZpZGVuY2UuIEZhaWxlZC91bmNlcnRhaW4gcGhhc2Ugc3RhdGVzIHJlbWFpbiBkdXJhYmxlIGFuZCBjYW5ub3QgYmUgcmV0cmllZC4gUHJpb3IgVDA0IGZhaWxlZCBjb3B5L3ZlcmlmeSBhcmUgcHJlc2VydmVkLgoKQWZ0ZXIgc2NoZW1hNS9pbnRlZ3JpdHkvZXhhY3RjbG9uZWNvdW50cyBwYXNzIGFuZCBjbG9uZSBjbGVhbnVwIHN1Y2NlZWRzLCBmcmVzaCBjdXRvdmVyIGJhY2t1cC9jb3B5IHByZWNlZGVzIHRoZSBleGFjdCBtYW51YWwgaW1hZ2Utb25seSBEZXBsb3ltZW50IHN5bmMuIEN1cnJlbnQgYmFzZWxpbmUgaXMgaGVhbHRoeWdlbmVyYXRpb24yNS9pbWFnZTc2M2MvQXBwbGljYXRpb25jOTYuIEltbXV0YWJsZUNNNmMvUFZDL1BvZGNvbmZpZ3VyYXRpb24vUERQIG9yaWdpbnMgcmVtYWluIHNlYWxlZC4gRnJlc2hub2RlQ1BVMzQ2MG9mNDAwMG0gc3VwcG9ydHMgdW5jaGFuZ2VkUmVjcmVhdGUsIHdpdGggYW5vdGhlciBjaGVjayBiZWZvcmUgZWZmZWN0LiBCcmllZiBhdmFpbGFiaWxpdHkgaW50ZXJydXB0aW9uIGV4cGVjdGVkLgoKUm9sbGJhY2sgcmVzdG9yZXMgRGVwbG95bWVudGltYWdlNzYzYyBhbmRBcHBsaWNhdGlvbmM5NiB3aGlsZSByZXRhaW5pbmcgbGF0ZXN0c2NoZW1hNWRhdGFiYXNlOyBubyBvbGQgc25hcHNob3QgcmVzdG9yYXRpb24uIFNlcGFyYXRlIGluc3RhbGxlZC9uYXRpdmUgaGVhbHRoL2N1c3RvZHkvUERQLWdhdGVkIHJlYWRiYWNrIGFuZCBhY3R1YWwgbmF0aXZlIGltYWdlIHJlc2NhbiBwcmVjZWRlIFQwNSBjbG9zdXJlLiBBbGxwaGFzZSBzdGFydHMgZXhwaXJlT2N0MTIgMjM6NTk6NTlaOyByZWxlYXNlIHJlc2VydmVzMzBtaW51dGVzZm9ydmVyaWZpY2F0aW9uYW5kcmVjb3ZlcnkuIEFmdGVyZXhwaXJ5IGEgbmV3Zm91bmRlcmRlY2lzaW9uIGlzIHJlcXVpcmVkLiBFVVIwbmV3cmVjdXJyaW5naW5mcmFzdHJ1Y3R1cmUsIGJvdW5kZWRwcml2YXRlIHN0b3JhZ2UgYW5kdW5wcmljZWRhdHRlbmRlZHRpbWUuIE5vIG5ld2RvY3RyaW5lL2F1dG9waWxvdC9jcmVkZW50aWFsZ3JhbnQgaXMgYXV0aG9yaXplZC4K" - }, - { - "digest": "sha256:a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9", - "media_type": "application/json", - "content_base64": "ewogICJraW5kIjogImluZm9ybWVkLWRlY2lzaW9uLnN1cGVydmlzZWQtcmVsZWFzZS1wcm9wb3NhbCIsCiAgInZlcnNpb24iOiAxLAogICJwcm9wb3NhbF9pZCI6ICJJTkZELVdQLTAwMDktVDA1L3psaWI4NTA5MS1yMSIsCiAgInRyaWFsX2lkIjogImluZmQtemxpYi0yMDI2MTAxMS1yMSIsCiAgInN0YXRlIjogInByZXBhcmVkLXVuYXBwcm92ZWQiLAogICJtZW1vX2lkIjogImluZmQtMjAyNjEwMTEtemxpYi1yZW1lZGlhdGlvbi1yMSIsCiAgIm1lbW9fdmVyc2lvbiI6IDEsCiAgInRhc2siOiAiSU5GRC1XUC0wMDA5LVQwNSIsCiAgImNhbmRpZGF0ZV9pbWFnZSI6ICJmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2OmY1NTg4ZjQ5NmIxZjI2N2QwN2NkYzlmNTIwOGU2ZjJmMjQxZjc0YjczOWMyMzkyNGIwMzcwMmRlYzEzYTVjMjMiLAogICJhcnRpZmFjdF9zb3VyY2VfcmV2aXNpb24iOiAiOWI0YmIyNWQzMjU2OThhZTU0M2U3ZThhOGEzYzM1OTJkYjFmNThmYyIsCiAgInByaW9yX2ltYWdlIjogImZvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbkBzaGEyNTY6NzYzYzM2NDAyYzhiZTdjYzRjMWJkMWE2MTYyM2QxYWZmOTExNmQ1MjI0MTg3NzQyNWI0NWE3MDE4Y2MwODgzYSIsCiAgInByaW9yX2FwcGxpY2F0aW9uX3JldmlzaW9uIjogImM5NmIwYzZkODgxZTg1NWQxZDE1ODAwM2M2ZGU5MGVmMzkwMjYyZTIiLAogICJzb3VyY2VfcmV2aXNpb24iOiBudWxsLAogICJwdWJsaXNoZWRfYXJ0aWZhY3RfdmVyaWZpZWQiOiB0cnVlLAogICJuYXRpdmVfYWN0aW9uc19leGVjdXRlZCI6IGZhbHNlLAogICJhcHByb3ZhbF9pZCI6IG51bGwsCiAgImF1dGhvcml0eV9tb2RlIjogIkFETUlOSVNURVIiLAogICJ0YXJnZXQiOiAicmVhbG06a3ViZXJuZXRlcy9yYWlsaWFuY2UwMS9pbmZvcm1lZC1kZWNpc2lvbiIsCiAgImNvc3RfZXN0aW1hdGVfZXVyIjogMCwKICAicmlzayI6ICJQcml2YXRlIHNhbWUtc2NoZW1hIGJhY2t1cCBjb3BpZXMgYW5kIGJyaWVmIFJlY3JlYXRlIGF2YWlsYWJpbGl0eSBnYXAuIEZhaWxlZCBvciBhbWJpZ3VvdXMgcGhhc2VzIHJldGFpbmVkOyBubyByZXBsYXkuIFJvbGxiYWNrIGNoYW5nZXMgRGVwbG95bWVudCBpbWFnZS9zb3VyY2Ugb25seSwgcmV0YWlucyBsYXRlc3QgZGF0YWJhc2UuIiwKICAiZXhlY3V0ZV9ieSI6ICIyMDI2LTEwLTEyVDIzOjU5OjU5WiIsCiAgInJlbGVhc2VfbXVzdF9zdGFydF9ieSI6ICIyMDI2LTEwLTEyVDIzOjI5OjU5WiIsCiAgIm1heF9zbmFwc2hvdF9ieXRlcyI6IDUzNjg3MDkxMiwKICAibWF4aW11bV9uZXdfc25hcHNob3RfcGF5bG9hZF9ieXRlc19lYWNoX3NpdGUiOiAxMDczNzQxODI0LAogICJsb2NhbF9leHRyYV9kaXNwb3NhYmxlX2Nsb25lX21heF9ieXRlcyI6IDUzNjg3MDkxMiwKICAicHJpdmF0ZV9tZXRhZGF0YV9hbmRfc3FsaXRlX3NpZGVjYXJzX2FkZGl0aW9uYWxfaGVhZHJvb21fYnl0ZXMiOiAxNjc3NzIxNiwKICAidW5ib3VuZF9pbnB1dHMiOiBbCiAgICAiZXhhY3QgY29tbWl0dGVkIG93bmVyIHJlbmRlcmluZyByZXZpc2lvbiByZXRhaW5lZCBpbiByb290IHN1cGVydmlzZWQgcmVjZWlwdCIsCiAgICAiUHJvZHVjdCBhbmQgUGxhdGZvcm0gZmluYWwgYWNrbm93bGVkZ21lbnRzIiwKICAgICJjb25maXJtZWQgZm91bmRlciBleGFjdCBuZXcgZGlzcG9zaXRpb24iCiAgXSwKICAicHVibGlzaGVkX3ZlcmlmaWNhdGlvbl9zaGEyNTYiOiAiZGQ0ZWVjZjBjMTAzZGNhNzkyMjA5NGE4YmVlYjk5Yjc3YWU5NmZkMWQ1ZmU1MzJlMzdiZDgzM2RhYTAyNjE0ZSIsCiAgIm5hdGl2ZV9yZWFkb25seV9wcmVmbGlnaHRfc2hhMjU2IjogIjU2NjM3OTk0ZGZkYWU0NmE2ZjlhYjQ0MmMyZmY5YjI1OTIxMjY1MGIzNWViOThkODk2YzVlODM3ZjAxY2UyZjkiLAogICJleGVjdXRvcl9wbGFuX3NoYTI1NiI6ICI3NTBhNDM4NjI3N2RjNzljMzIxM2U2YWVkMTdhZTU2ODYzMzJiZmQ1ODdhNTVlNWU4N2ViZjM3NzZhNjNhYjBjIiwKICAiZXhlY3V0aW9uX2J1bmRsZV9zaGEyNTYiOiAiMDliMGE4ZWQ2YzNjN2QxZTk0ZjVmNzAzYTJkNTgzNGNhMjZmYzE2NmNmYzhmNGY5YjQ1MTFhYjcwZWMxNmFjOSIsCiAgInJvbGxiYWNrX3ByZWZsaWdodF9zaGEyNTYiOiAiMDE4ODc5OWQzNTJlNGQ0OWQ1Y2EyOWQ2ZTE5Nzc0MmQwMmU5NmE0YjBiMzk5ZGYxZjQ3OGU0MWIyNTI5MmViNSIsCiAgInBvc3RfcmVhZHlfbmF0aXZlX3Byb3ZpZGVyX3Byb2JlIjogImNhbm9uaWNhbCBsaWZlY3ljbGUub3V0Y29tZXMgU09VUkNFMSB0aHJvdWdoIGV4aXN0aW5nIGFkbWl0dGVkIFN0YXRlSHViIGRpc3BhdGNoZXI7IGNyZWRlbnRpYWxzIHN0YXkgaW5zaWRlIFBvZDsgYm91bmRlZCBtZXRhZGF0YSBjb3JyZWxhdGVkIHRvIHZlcmlmaWVkIGNhbmRpZGF0ZSBpbWFnZS9Qb2Qvc291cmNlIgp9Cg==" - }, - { - "digest": "sha256:0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4", - "media_type": "text/markdown", - "content_base64": "IyBUMDUgemxpYjg1MDkxIHJlbGVhc2Ug4oCUIHByZWxpbWluYXJ5LCB1bmFwcHJvdmVkCgpUaGlzIGlzIGEgZGlzdGluY3QgcHJvcG9zYWwvdHJpYWwgZm9yIGV4aXN0aW5nIElORkQtV1AtMDAwOS1UMDUuIFRoZSBhY3R1YWwgVDA0IHNvdXJjZTIgYWNjZXB0YW5jZSBkb2VzIG5vdCBhdXRob3JpemUgaXQuIFByZXZpb3VzIFIxIGNvcHkgYW5kIFIyIHZlcmlmeSBVTkNFUlRBSU4gam91cm5hbCBzdGF0ZXMgcmVtYWluIHVudG91Y2hlZC4gSU5GRC1JTi0wMDEwIHJlbWFpbnMgYW4gdW5wcm9tb3RlZCBnZW5lcmFsIHZlcmlmaWVyIHJlc2lkdWFsOyB0aGlzIG5ldyB0YXNrLXNwZWNpZmljIHZlcmlmaWVyIGlzIGEgcHJlcmVxdWlzaXRlIG9mIGFjdGl2ZSBUMDUsIG5vdCBhbiBhbWVuZG1lbnQgb2YgdGhlIG9sZCBhcHByb3ZlZCBoZWxwZXIuCgpFeGFjdCBpbnRlbmRlZCBpbWFnZSBmNTU4OGY0OTZiMWYyNjdkMDdjZGM5ZjUyMDhlNmYyZjI0MWY3NGI3MzljMjM5MjRiMDM3MDJkZWMxM2E1YzIzIHdhcyBidWlsdCBieSBhZG1pdHRlZCBvd25lciBDSTExNTM0L3J1bjQgZnJvbSA5YjRiYjI1ZDMyNTY5OGFlNTQzZTdlOGE4YTNjMzU5MmRiMWY1OGZjLiBQdWJsaWNhdGlvbiBpcyBub3QgaW5zdGFsbGVkIGFjY2VwdGFuY2UuIEZpbmFsIHBhY2tldCByZXF1aXJlcyBpbmRlcGVuZGVudCBwdWJsaXNoZWQtaW1hZ2Ugc2Nhbi9ydW50aW1lL2ZpbGVzeXN0ZW0vc291cmNlIHByb29mIGFuZCBmcmVzaCByZWFkLW9ubHkgbmF0aXZlIHByb2plY3Rpb24vb25lLW9iamVjdCBkcnktcnVuIGV2aWRlbmNlLiBVbnRpbCBhbGwgZmluYWwgaW5wdXRzIGFuZCBpbmRlcGVuZGVudCBvd25lciBhY2tub3dsZWRnbWVudHMgYXJlIGZyb3plbiwgZXhlY3V0aW9uLXBsYW4gc3RhdGUgcmVtYWlucyBwcmVsaW1pbmFyeSBhbmQgdGhlIGFjdHVhdG9yIHJlZnVzZXMgYmVmb3JlIGFueSBhdXRob3JpdHksIGpvdXJuYWwgb3IgbmF0aXZlIGFjdGlvbi4KCkFmdGVyIGEgTkVXIGV4YWN0IGNvbmZpcm1lZCBmb3VuZGVyIGFjY2VwdGFuY2UsIHJvb3QgZnJlc2hseSBhdXRoZW50aWNhdGVzIGZpeGVkLW9yaWdpbiBuYXRpdmUgc291cmNlLXZlcnNpb24xIG91dGNvbWVzIGJlZm9yZSBlYWNoIHBoYXNlLiBSZWNlaXB0IGZpbGVzIGFyZSBzdXBlcnZpc2VkIGJvb2trZWVwaW5nLCBub3Qgc2lnbmVkIGdyYW50cyBvciBhdXRvbm9teS4gV3JvbmcgdGFzaywgc291cmNlLCBtZW1vL3ZlcnNpb24sIHN0YWxlL3BlbmRpbmcvYWdlbnQgZGlzcG9zaXRpb24sIG9sZCBUMDQgYWNjZXB0YW5jZSBvciBjaGFuZ2VkIGlucHV0cyByZWZ1c2UuIFJvb3QgcmV0YWlucyBleGFjdCBhcHByb3ZlZCBzb3VyY2UgcmV2aXNpb24vZGlnZXN0cyBhbmQgYWN0aW9uIHJlc3VsdHMgaW4gc3VwZXJ2aXNpb24uIEFwcHJvdmFsIGRvZXMgbm90IHByb21vdGUgYW55IGFnZW50LgoKRml4ZWQgcGhhc2Ugb3JkZXI6IGRyaWxsLWJhY2t1cCDihpIgZHJpbGwtY29weSDihpIgb2ZmbGluZS12ZXJpZnkg4oaSIGNsZWFudXAtb2ZmbGluZSDihpIgY3V0b3Zlci1iYWNrdXAg4oaSIGN1dG92ZXItY29weSDihpIgcmVsZWFzZSDihpIgdmVyaWZ5LiBOZXcgcHJpdmF0ZSBuYXRpdmUgL2RhdGEvcHJpdmF0ZS96bGliODUwOTEtcjEsIHJvb3QgLnByaXZhdGUtYmFja3Vwcy8yMDI2MTAxMS1pbmZkLXpsaWItcjEgYW5kIGxvY2FsIHZvbHVtZSBpbmZkLXpsaWItcjEtcmVzdG9yZS0yMDI2MTAxMSBhcmUgZGlzdGluY3QgZnJvbSBhbGwgVDA0IGN1c3RvZHkuIE5ldyBwcml2YXRlIGpvdXJuYWwgaW50ZW50IGlzIGZzeW5jZWQgUlVOTklORyBiZWZvcmUgZGlzcGF0Y2g7IG5vIGF0dGVtcHRlZCBwaGFzZSByZXBsYXkuIEZpeGVkIG91dHB1dCBzY2hlbWEgY29udGFpbnMgb25seSBjb3VudHMsIGhhc2hlcywgb2JqZWN0IGlkZW50aXR5IGFuZCBib29sZWFuIHJlc3VsdHM7IHJhdyBlcnJvcnMgYW5kIHByaXZhdGUgY29udGVudCBhcmUgd2l0aGhlbGQuIEV4aXN0aW5nIG5hdGl2ZSBzbmFwc2hvdHMvZGF0YWJhc2Uvc2lkZWNhcnMgYXJlIG5ldmVyIHJlbW92ZWQgb3IgcmVzdG9yZWQgb3ZlciBjdXJyZW50IGRhdGEuCgpGcmVzaCBiYWNrdXAgaW5zcGVjdGlvbiB1c2VzIGNvcGllZCBleHBsaWNpdC1jbG9zZSBTUUxpdGUgaGVscGVyczogbGl2ZSBhbmQgb2ZmbGluZSBjbG9uZSByZWFkZXJzIGFyZSBtb2RlPXJvL3F1ZXJ5X29ubHkgYW5kIFdBTC1hd2FyZSwgYmVmb3JlIGFueSBTdG9yZS9hZG1pbiBvcGVuLiBJbW11dGFibGUgcmVhZGVyIGlzIHVzZWQgb25seSBmb3IgZnJlc2hseSBjbG9zZWQgaGFzaC1ib3VuZCBwcml2YXRlIHNuYXBzaG90IHdpdGggYWJzZW50L3plcm8gV0FMIGZyYW1lcyBhbmQgc3RhYmxlIGlkZW50aXR5L21ldGFkYXRhL2RpZ2VzdCBiZWZvcmUvYWZ0ZXIgcmVhZC9jb3B5LiBNYWluIGFuZCBzaWRlY2FycyByZW1haW4gcmV0YWluZWQuIFNjaGVtYSBtdXN0IGVxdWFsNSwgaW50ZWdyaXR5IG9rLCBhbmQgZXhhY3Qgc25hcHNob3QvaXNvbGF0ZWQgY2xvbmUgY291bnRzIG1hdGNoIGJlZm9yZS9hZG1pbi9hZnRlci4gVGhlIGRldGFjaGVkIGxvY2FsIGNsb25lIGhhcyBuZXR3b3JrIG5vbmUsIHJlYWRvbmx5IHJvb3QsIG5vIHNlcnZpY2UvcnVudGltZSBjb25maWcvY3JlZGVudGlhbCBtb3VudHM7IG9ubHkgbmFycm93bHkgc2NvcGVkIFVJRDAgQ0FQX0NIT1dOIGluaXRpYWxpemVyIHByZWNlZGVzIFVJRDEwMDAxIHJlYWRzL2FkbWluLiBPcmlnaW5hbCBwcml2YXRlIGNvcHkgaXMgaGFzaC1zdGFibGUuIENsZWFudXAgcmVtb3ZlcyBvbmx5IHRoZSBkaXNwb3NhYmxlIHZvbHVtZSB3aG9zZSBub25jZS9sYWJlbHMvY3JlYXRpb24gbWV0YWRhdGEgbWF0Y2ggdGhpcyB0cmlhbCwgYmVmb3JlIGN1dG92ZXIuCgpFYWNoIHNuYXBzaG90IGlzIGJvdW5kZWQ1MTJNaUIuIEF0IG1vc3QgdHdvIG5ldyBzbmFwc2hvdHMgb2NjdXB5IGVhY2ggbmF0aXZlL29mZmNsdXN0ZXIgY3VzdG9keSAoMUdpQiBwYXlsb2FkIGVhY2gpOyB0aGUgZGlzcG9zYWJsZSBvZmZjbHVzdGVyIGNsb25lIGFkZHM1MTJNaUIgd2hpbGUgcHJlc2VudCwgcGx1czE2TWlCIHNpZGVjYXIvbWV0YWRhdGEgcmVzZXJ2ZS4gTmV3IG5hdGl2ZSBkcmlsbCBwaGFzZSBjaGVja3MgZnJlZSBzcGFjZSBmb3IgYm90aDsgbG9jYWwgY3VzdG9keSBjaGVja3MgdHdvIHNuYXBzaG90IGJvdW5kcyBwbHVzIHJlc2VydmUuIEV4aXN0aW5nIFQwNCByZXRlbnRpb24gY29uc3VtZXMgYWRkaXRpb25hbCBwcmUtZXhpc3Rpbmcgc3BhY2UuIEVVUjAgcmVjdXJyaW5nIGluZnJhc3RydWN0dXJlIGVzdGltYXRlOyBodW1hbi9jb21wdXRlIHRpbWUgdW5wcmljZWQuCgpDdXRvdmVyIHRha2VzIGEgZnJlc2ggbmF0aXZlIGJhY2t1cCBhbmQgZXhhY3Qgb2ZmY2x1c3RlciBjb3B5LiBSZWxlYXNlIHRoZW4gcmVxdWlyZXMgbGF0ZXN0IHNjaGVtYTUgcmVhZG9ubHkgcHJvb2YsIHNlYWxlZCBQb2QvRGVwbG95bWVudC9jb25maWcvUFZDIGN1c3RvZHkgYW5kIHNhbWUgYmFzZWxpbmUgaW1hZ2U3NjNjL2N1cnJlbnQgQXBwbGljYXRpb24gc291cmNlYzk2LCBhIGhlYWx0aHkgc2luZ2xlIHRhcmdldDkyLjIwNS42Mi4yMzkgbm9kZSBhbmQgZnJlc2ggcmVxdWVzdGVkIENQVSDiiaQgYWxsb2NhdGFibGUgY2FwYWNpdHkuIFJlY3JlYXRlIGFuZCByZXNvdXJjZXMgcmVtYWluIHVuY2hhbmdlZC4gU2VydmVyIGRyeS1ydW4vY2xpZW50IGRpZmYgbXVzdCBzaG93IG9ubHkgdGhlIGltYWdlLiBBcHBsaWNhdGlvbiBVSUQvcmVzb3VyY2VWZXJzaW9uL3NvdXJjZSBDQVMgZGlzcGF0Y2hlcyBtYW51YWwgZXhhY3QgZmluYWwgcmV2aXNpb24sIGluaXRpYXRlZEJ5IGN1c3RvZGlhbi1jb2RleCwgZXhwbGljaXQgcHJ1bmU9ZmFsc2UgYW5kIG9ubHkgYXBwcy9EZXBsb3ltZW50IGluZm9ybWVkLWRlY2lzaW9uIGluIGl0cyBuYW1lc3BhY2UuIE5vIHdob2xlLUFwcGxpY2F0aW9uIHN5bmMsIG5hbWVzcGFjZS9jb25maWcvY3JlZGVudGlhbC9yZXNvdXJjZSBjaGFuZ2Ugb3IgcHJ1bmluZy4KClZlcmlmaWNhdGlvbiByZWFkcyBpbWFnZUlEL3JlYWRpbmVzcy9vd25lciBjaGFpbi9leGFjdCBjb25maWd1cmVkIFBvZCBmaWVsZHMsIHNjaGVtYTUvaW50ZWdyaXR5L21ldGFkYXRhIGNvdW50cywgaW5zdGFsbGVkIHBvbGljeSBoYXNoIGFuZCBzeW50aGV0aWMgcHVycG9zZSByZWZ1c2FscywgaGVhbHRoMjAwLCBpbnN0YWxsZWQgZml4ZWQgemxpYiBhbmQgZXhhY3QgaW1hZ2UgcHJvb2YuIEFmdGVyIGV4YWN0IGNhbmRpZGF0ZSBSZWFkeSBhbmQgdmVyaWZpZWQgcm9sbG91dCwgcm9vdCBleGVjdXRlcyBhdHRhY2hlZCBwb3N0LXJlYWR5LW5hdGl2ZS1vdXRjb21lcy1jb21tYW5kLnB5IGluc2lkZSB0aGUgZXhpc3RpbmcgYWRtaXR0ZWQgU3RhdGVIdWIgUG9kLiBJdCBjYWxscyBjYW5vbmljYWwgbGlmZWN5Y2xlLm91dGNvbWVzIGZvciBpbmZkLTIwMjYxMDExLXpsaWItcmVtZWRpYXRpb24tcjEgU09VUkNFdmVyc2lvbjEgYXQgZGlzcGF0Y2hlciBmaXhlZCBPUklHSU4gd2l0aCBOb1JlZGlyZWN0L3Byb3h5IGRpc2FibGVkOyBleGlzdGluZyBpbnRlcm5hbCB0b2tlbl9maWxlIHN0YXlzIGluc2lkZSB0aGF0IFBvZCBhbmQgbm8gdG9rZW4vYm9keS9wcml2YXRlIGNvbnRlbnQgaXMgZXhwb3J0ZWQuIFJvb3QgcmV0YWlucyBib3VuZGVkIEhUVFAtc3VjY2VzcyBtZXRhZGF0YS9vYnNlcnZhdGlvbnRpbWUgYW5kIGNvcnJlbGF0ZXMgdmVyaWZpZWQgY2FuZGlkYXRlIFBvZFVJRC9pbWFnZS9zb3VyY2UsIHRoZW4gcmVjaGVja3MgdGhvc2UgaWRlbnRpdGllcy4gVGhpcyBhY3R1YWwgcHJvdmlkZXIvUERQLWdhdGVkIHByZXNlcnZhdGlvbiBwcm9vZiBpcyBzZXBhcmF0ZSBmcm9tIGluc3RhbGxlZCBzeW50aGV0aWMyYWxsb3c4cmVmdXNlOyBib3RoIGFyZSByZXF1aXJlZCBiZWZvcmUgdGFzayBjbG9zdXJlLiBJdCBkb2VzIG5vdCBhc3NlcnQgcHJvdmlkZXIgYWR2aXNvcnkgZW1pc3Npb25zIG9yIG5ldyBkb2N0cmluZS4gQXJnbzMuNS4zIG9taXRzIGZhbHNlIHBydW5lIGluIG9wZXJhdGlvbiBzdGF0dXM6IG1pc3Npbmcgb3IgZXhhY3RmYWxzZSBpcyBhY2NlcHRlZCBvbmx5IHRvZ2V0aGVyIHdpdGggdGhpcyBqb3VybmFsJ3MgQ09NUExFVEUgZXhwbGljaXRmYWxzZSByZXF1ZXN0LCBleGFjdCBzb3VyY2UvcmVzb3VyY2VzL2luaXRpYXRpb24sIG9wZXJhdGlvbiBzdGFydCBhZnRlciBvdXIgZHVyYWJsZSBhdHRlbXB0LCBib3VuZGVkIGZpbmlzaCBhbmQgb25lIFN5bmNlZCBEZXBsb3ltZW50IHJlc3VsdC4gVHJ1ZS9udWxsL3N0cmluZy9wcmlvci9mb3JlaWduL2V4dHJhLXJlc291cmNlIHJlc3VsdHMgcmVmdXNlLgoKUm9sbGJhY2sg4oaSIHJvbGxiYWNrLXZlcmlmeSBpcyBzZXBhcmF0ZSBhbmQgcGVybWl0dGVkIG9ubHkgYWZ0ZXIgYSByZWNvcmRlZCByZWxlYXNlIGF0dGVtcHQuIEl0IHN3aXRjaGVzIG9ubHkgQXBwbGljYXRpb24gc291cmNlIGJhY2sgdG8gYzk2IGFuZCBEZXBsb3ltZW50IGltYWdlNzYzYzsgaXQgbmV2ZXIgcmVzdG9yZXMgYW4gb2xkIHNuYXBzaG90IG92ZXIgdGhlIGxhdGVzdCBkYXRhYmFzZS4gUmVjb3ZlcnkgZGlzcGF0Y2ggd29ya3MgZXZlbiBpZiBjYW5kaWRhdGUgUG9kIGlzIGFic2VudC91bnJlYWR5IGFuZCBuZXZlciBkZXBlbmRzIG9uIGNhbmRpZGF0ZSBEQiBpbnNwZWN0aW9uLiBSZWNvdmVyeSB2ZXJpZmljYXRpb24gc2VwYXJhdGVseSByZXF1aXJlcyBSZWFkeTEvb2xkIGltYWdlL2N1cnJlbnQgc2NoZW1hNS9kYXRhLWN1c3RvZHkgYW5kIGV4YWN0IHJlY29yZGVkIHNjb3BlZCBjb21wbGV0aW9uLiBBbGwgcGhhc2VzIG11c3QgYmVnaW4gYmVmb3JlMjAyNi0xMC0xMlQyMzo1OTo1OVo7IHJlbGVhc2UgbXVzdCBiZWdpbiBhdCBsZWFzdDMwbWludXRlcyBlYXJsaWVyLiBCZXlvbmQgZXhwaXJ5IHN0b3AgYW5kIG9idGFpbiBhIG5ldyBmb3VuZGVyIGRlY2lzaW9uOyBubyBhdXRvbWF0aWMgZXh0ZW5zaW9uIG9yIGVtZXJnZW5jeSBncmFudC4gQSBmYWlsZWQvdW5jZXJ0YWluIGF0dGVtcHQgcmVxdWlyZXMgaW5mb3JtZWQgcmVjb3ZlcnkgcmF0aGVyIHRoYW4gcmV0cnkuCgpUMDUgcmVtYWlucyBwcm9ncmVzcyB1bnRpbCBhY3R1YWwgaW5zdGFsbGVkIGFjY2VwdGFuY2UgYW5kIG5hdGl2ZSByZXNjYW4gcHJvdmUgdGhlIGZpbmRpbmcgYWJzZW50LiBObyBSaXNrIEVuZ2luZSBncmFkZSBpcyBhc3NpZ25lZC4gT25seSB0aGVuIG1heSBvd25lciBjbG9zZSBUMDUvcmlzayBhbmQgY29uc2lkZXIgZmluaXNoaW5nIFdQOSwgd2l0aCBleGlzdGluZyByZXNpZHVhbHMgbGl2ZSBmaXJzdC4KCkFjdHVhbCByZWxlYXNlIGFuZCBpbnN0YWxsZWQgdmVyaWZpY2F0aW9uIGFsc28gcmVxdWlyZSBmaXhlZCBwcml2YXRlIHJvb3Qgc2NhbiByZWNlaXB0IGFuZCByZXRhaW5lZCBmdWxscmVwb3J0IGF0IC90bXAvc2VjdXJpdHktaW5mZC16bGliLWZyZXNoLXNjYW4te3JlY2VpcHQsZnVsbH0uanNvbiwgc3VwZXJ2aXNlZCBtZXRhZGF0YSBiaW5kaW5nIHRvIGV4YWN0IGltbXV0YWJsZWY1NTgvYXJ0aWZhY3Rzb3VyY2U5YjQvc2Nhbm5lcmRpZ2VzdDYyYjEuIEJlZm9yZSBqb3VybmFsIHN0YXJ0LCBmdWxscmVwb3J0aGFzaCBtdXN0IG1hdGNoLCBubyBzdXBwcmVzc2lvbi9hbGxzZXZlcml0eSBmbGFncyB0cnVlLCBleGl0MCwgbm9uZW1wdHkgdmFsaWQgZnVsbHJlc3VsdHMgY29udGFpbiB6ZXJvIGZpbmRpbmdzLCBzY2FuIGNvbXBsZXRlZCB3aXRoaW40aG91cnMgYW5kIG5vdyBwcmVjZWRlcyBEQiBOZXh0VXBkYXRlLiBUaGUgcHVibGlzaGVkIERCIGV4cGlyZXMyMDI2LTEwLTExVDE4OjU5OjUwWjsgaXQgaXMgbm90IHZhbGlkIHVudGlsIHJlbGVhc2UgZGVhZGxpbmUuIFJvb3QgcmVmcmVzaGVzIERCIGFuZCByZXNjYW5zIHRoZSBzYW1lIGltbXV0YWJsZSBpbWFnZSBhcyByb3V0aW5lIHJlYWQtb25seSB2ZXJpZmljYXRpb24gd2hlbiBuZWVkZWQsIHJldGFpbmluZyBhbGwgcmF3IHB1YmxpY3NjYW4gYXJ0aWZhY3RzLiBBbnkgbmV3IGZpbmRpbmcgcmVmdXNlcyByZWxlYXNlL2Nsb3N1cmUgYW5kIGlzIHJldGFpbmVkIGZvciBpbmZvcm1lZCByZWFzc2Vzc21lbnQvbmV3IGRlY2lzaW9uIGFzIG5lZWRlZCwgbmV2ZXIgZmlsdGVyZWQgb3IgaWdub3JlZC4gRXhpc3RpbmcgcmlzayBzdGF5cyBvcGVuIHVudGlsIGFjdHVhbCBpbnN0YWxsZWQgcHJvb2YvbmF0aXZlIHJlc2Nhbi4K" - }, - { - "digest": "sha256:56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9", - "media_type": "application/json", - "content_base64": "ewogICJhbGxfY2hlY2tzX3Bhc3MiOiB0cnVlLAogICJvYnNlcnZlZF9hdCI6ICIyMDI2LTEwLTExVDA5OjQwOjE1LjU0MDg3NCswMDowMCIsCiAgIm5hdGl2ZV9jaGFuZ2VkIjogZmFsc2UsCiAgInByb3RlY3RlZF9kYXRhYmFzZV9yZWFkIjogZmFsc2UsCiAgInNlY3JldF9yZWFkIjogZmFsc2UsCiAgImRlcGxveW1lbnRfZ2VuZXJhdGlvbiI6IDI1LAogICJyZWFkeV9yZXBsaWNhcyI6IDEsCiAgInNlYWxlZF9saXZlX2RlcGxveW1lbnQiOiB7CiAgICAiYXBpVmVyc2lvbiI6ICJhcHBzL3YxIiwKICAgICJraW5kIjogIkRlcGxveW1lbnQiLAogICAgIm1ldGFkYXRhIjogewogICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgImFyZ29jZC5hcmdvcHJvai5pby90cmFja2luZy1pZCI6ICJpbmZvcm1lZC1kZWNpc2lvbjphcHBzL0RlcGxveW1lbnQ6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICJpbmZvcm1lZC1kZWNpc2lvbi5jb3Vsb21iLnNvY2lhbC9hZG1pc3Npb24iOiAiYXBwcm92ZWQ7IElORkQtV1AtMDAwNyBjdXRvdmVyIgogICAgICB9LAogICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICJuYW1lc3BhY2UiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAidWlkIjogImE1ODhlMTBhLTkwZDItNDg4My05NzVlLTZkZDhiOGZlY2E5MiIKICAgIH0sCiAgICAic3BlYyI6IHsKICAgICAgInByb2dyZXNzRGVhZGxpbmVTZWNvbmRzIjogNjAwLAogICAgICAicmVwbGljYXMiOiAxLAogICAgICAicmV2aXNpb25IaXN0b3J5TGltaXQiOiAxMCwKICAgICAgInNlbGVjdG9yIjogewogICAgICAgICJtYXRjaExhYmVscyI6IHsKICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgIH0KICAgICAgfSwKICAgICAgInN0cmF0ZWd5IjogewogICAgICAgICJ0eXBlIjogIlJlY3JlYXRlIgogICAgICB9LAogICAgICAidGVtcGxhdGUiOiB7CiAgICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIKICAgICAgICAgIH0sCiAgICAgICAgICAibGFiZWxzIjogewogICAgICAgICAgICAiYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50IjogInJldmlldyIsCiAgICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInNwZWMiOiB7CiAgICAgICAgICAiYXV0b21vdW50U2VydmljZUFjY291bnRUb2tlbiI6IGZhbHNlLAogICAgICAgICAgImNvbnRhaW5lcnMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW52IjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0tFWUNBUEVfSVNTVUVSIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8va2MuY291bG9tYi5zb2NpYWwiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0NPTlRBSU5FUl9DT05GSUciLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2NvbmZpZ3VyYXRpb24vcnVudGltZS5qc29uIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgImltYWdlIjogImZvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbkBzaGEyNTY6NzYzYzM2NDAyYzhiZTdjYzRjMWJkMWE2MTYyM2QxYWZmOTExNmQ1MjI0MTg3NzQyNWI0NWE3MDE4Y2MwODgzYSIsCiAgICAgICAgICAgICAgImltYWdlUHVsbFBvbGljeSI6ICJJZk5vdFByZXNlbnQiLAogICAgICAgICAgICAgICJsaXZlbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgImNvbnRhaW5lclBvcnQiOiA4MDgwLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJodHRwIiwKICAgICAgICAgICAgICAgICAgInByb3RvY29sIjogIlRDUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJjb250YWluZXJQb3J0IjogODA4MSwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQiLAogICAgICAgICAgICAgICAgICAicHJvdG9jb2wiOiAiVENQIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgInJlYWRpbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInJlc291cmNlcyI6IHsKICAgICAgICAgICAgICAgICJsaW1pdHMiOiB7CiAgICAgICAgICAgICAgICAgICJjcHUiOiAiNTAwbSIsCiAgICAgICAgICAgICAgICAgICJtZW1vcnkiOiAiMjU2TWkiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInJlcXVlc3RzIjogewogICAgICAgICAgICAgICAgICAiY3B1IjogIjVtIiwKICAgICAgICAgICAgICAgICAgIm1lbW9yeSI6ICI2NE1pIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgICAgICJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOiBmYWxzZSwKICAgICAgICAgICAgICAgICJjYXBhYmlsaXRpZXMiOiB7CiAgICAgICAgICAgICAgICAgICJkcm9wIjogWwogICAgICAgICAgICAgICAgICAgICJBTEwiCiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicmVhZE9ubHlSb290RmlsZXN5c3RlbSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJzdGFydHVwUHJvYmUiOiB7CiAgICAgICAgICAgICAgICAiZmFpbHVyZVRocmVzaG9sZCI6IDEyLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInRlcm1pbmF0aW9uTWVzc2FnZVBhdGgiOiAiL2Rldi90ZXJtaW5hdGlvbi1sb2ciLAogICAgICAgICAgICAgICJ0ZXJtaW5hdGlvbk1lc3NhZ2VQb2xpY3kiOiAiRmlsZSIsCiAgICAgICAgICAgICAgInZvbHVtZU1vdW50cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvZGF0YSIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImRhdGEiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi9ydW4vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJydW50aW1lIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdG1wIiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvY29uZmlndXJhdGlvbiIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImNvbmZpZ3VyYXRpb24iLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24vcG9saWN5IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdmFyL3J1bi9zZWNyZXRzL2luZm9ybWVkLWRlY2lzaW9uL2F1ZGl0IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiYXVkaXQiLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24va3ViZXJuZXRlcyIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmVybmV0ZXMtYXBpIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvd29ya2xvYWQtcmVnaXN0cnkiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9yZXF1ZXN0ZXIiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICAgIF0sCiAgICAgICAgICAiZG5zUG9saWN5IjogIkNsdXN0ZXJGaXJzdCIsCiAgICAgICAgICAicmVzdGFydFBvbGljeSI6ICJBbHdheXMiLAogICAgICAgICAgInNjaGVkdWxlck5hbWUiOiAiZGVmYXVsdC1zY2hlZHVsZXIiLAogICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgImZzR3JvdXAiOiAxMDAwMSwKICAgICAgICAgICAgImZzR3JvdXBDaGFuZ2VQb2xpY3kiOiAiT25Sb290TWlzbWF0Y2giLAogICAgICAgICAgICAicnVuQXNHcm91cCI6IDEwMDAxLAogICAgICAgICAgICAicnVuQXNOb25Sb290IjogdHJ1ZSwKICAgICAgICAgICAgInJ1bkFzVXNlciI6IDEwMDAxLAogICAgICAgICAgICAic2VjY29tcFByb2ZpbGUiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiUnVudGltZURlZmF1bHQiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICAic2VydmljZUFjY291bnQiOiAicmV2aWV3IiwKICAgICAgICAgICJzZXJ2aWNlQWNjb3VudE5hbWUiOiAicmV2aWV3IiwKICAgICAgICAgICJ0ZXJtaW5hdGlvbkdyYWNlUGVyaW9kU2Vjb25kcyI6IDQ1LAogICAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJkYXRhIiwKICAgICAgICAgICAgICAicGVyc2lzdGVudFZvbHVtZUNsYWltIjogewogICAgICAgICAgICAgICAgImNsYWltTmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1kYXRhIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJlbXB0eURpciI6IHsKICAgICAgICAgICAgICAgICJtZWRpdW0iOiAiTWVtb3J5IiwKICAgICAgICAgICAgICAgICJzaXplTGltaXQiOiAiMTZNaSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW1wdHlEaXIiOiB7CiAgICAgICAgICAgICAgICAibWVkaXVtIjogIk1lbW9yeSIsCiAgICAgICAgICAgICAgICAic2l6ZUxpbWl0IjogIjE2TWkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJ0bXAiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjkyLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJjb25maWd1cmF0aW9uIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAicHJvamVjdGVkIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgInNvdXJjZXMiOiBbCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAic2VydmljZUFjY291bnRUb2tlbiI6IHsKICAgICAgICAgICAgICAgICAgICAgICJhdWRpZW5jZSI6ICJmbGV4LWF1dGgiLAogICAgICAgICAgICAgICAgICAgICAgImV4cGlyYXRpb25TZWNvbmRzIjogMzYwMCwKICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogInRva2VuIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJuYW1lIjogImF1ZGl0IiwKICAgICAgICAgICAgICAic2VjcmV0IjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImtleSI6ICJ0b2tlbiIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAidG9rZW4iCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAic2VjcmV0TmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1hdWRpdCIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICAgInByb2plY3RlZCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJzb3VyY2VzIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInNlcnZpY2VBY2NvdW50VG9rZW4iOiB7CiAgICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvblNlY29uZHMiOiA2MDAsCiAgICAgICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgImtleSI6ICJjYS5jcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogImNhLmNydCIKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmUtcm9vdC1jYS5jcnQiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgImNvbmZpZ01hcCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI5MiwKICAgICAgICAgICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLXdvcmtsb2FkLXJlZ2lzdHJ5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQtcmVnaXN0cnkiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJpdGVtcyI6IFsKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJrZXkiOiAiY2xpZW50LXNlY3JldCIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAiY2xpZW50LXNlY3JldCIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJvcHRpb25hbCI6IGZhbHNlLAogICAgICAgICAgICAgICAgInNlY3JldE5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tc2l0dGluZy1yZXF1ZXN0ZXIiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfQogICAgICB9CiAgICB9CiAgfSwKICAiY2FuZGlkYXRlX2RlcGxveW1lbnQiOiB7CiAgICAiYXBpVmVyc2lvbiI6ICJhcHBzL3YxIiwKICAgICJraW5kIjogIkRlcGxveW1lbnQiLAogICAgIm1ldGFkYXRhIjogewogICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgImFyZ29jZC5hcmdvcHJvai5pby90cmFja2luZy1pZCI6ICJpbmZvcm1lZC1kZWNpc2lvbjphcHBzL0RlcGxveW1lbnQ6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICJpbmZvcm1lZC1kZWNpc2lvbi5jb3Vsb21iLnNvY2lhbC9hZG1pc3Npb24iOiAiYXBwcm92ZWQ7IElORkQtV1AtMDAwNyBjdXRvdmVyIgogICAgICB9LAogICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICJuYW1lc3BhY2UiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAidWlkIjogImE1ODhlMTBhLTkwZDItNDg4My05NzVlLTZkZDhiOGZlY2E5MiIKICAgIH0sCiAgICAic3BlYyI6IHsKICAgICAgInByb2dyZXNzRGVhZGxpbmVTZWNvbmRzIjogNjAwLAogICAgICAicmVwbGljYXMiOiAxLAogICAgICAicmV2aXNpb25IaXN0b3J5TGltaXQiOiAxMCwKICAgICAgInNlbGVjdG9yIjogewogICAgICAgICJtYXRjaExhYmVscyI6IHsKICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgIH0KICAgICAgfSwKICAgICAgInN0cmF0ZWd5IjogewogICAgICAgICJ0eXBlIjogIlJlY3JlYXRlIgogICAgICB9LAogICAgICAidGVtcGxhdGUiOiB7CiAgICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIKICAgICAgICAgIH0sCiAgICAgICAgICAibGFiZWxzIjogewogICAgICAgICAgICAiYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50IjogInJldmlldyIsCiAgICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInNwZWMiOiB7CiAgICAgICAgICAiYXV0b21vdW50U2VydmljZUFjY291bnRUb2tlbiI6IGZhbHNlLAogICAgICAgICAgImNvbnRhaW5lcnMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW52IjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0tFWUNBUEVfSVNTVUVSIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8va2MuY291bG9tYi5zb2NpYWwiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0NPTlRBSU5FUl9DT05GSUciLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2NvbmZpZ3VyYXRpb24vcnVudGltZS5qc29uIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgImltYWdlIjogImZvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbkBzaGEyNTY6ZjU1ODhmNDk2YjFmMjY3ZDA3Y2RjOWY1MjA4ZTZmMmYyNDFmNzRiNzM5YzIzOTI0YjAzNzAyZGVjMTNhNWMyMyIsCiAgICAgICAgICAgICAgImltYWdlUHVsbFBvbGljeSI6ICJJZk5vdFByZXNlbnQiLAogICAgICAgICAgICAgICJsaXZlbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgImNvbnRhaW5lclBvcnQiOiA4MDgwLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJodHRwIiwKICAgICAgICAgICAgICAgICAgInByb3RvY29sIjogIlRDUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJjb250YWluZXJQb3J0IjogODA4MSwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQiLAogICAgICAgICAgICAgICAgICAicHJvdG9jb2wiOiAiVENQIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgInJlYWRpbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInJlc291cmNlcyI6IHsKICAgICAgICAgICAgICAgICJsaW1pdHMiOiB7CiAgICAgICAgICAgICAgICAgICJjcHUiOiAiNTAwbSIsCiAgICAgICAgICAgICAgICAgICJtZW1vcnkiOiAiMjU2TWkiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInJlcXVlc3RzIjogewogICAgICAgICAgICAgICAgICAiY3B1IjogIjVtIiwKICAgICAgICAgICAgICAgICAgIm1lbW9yeSI6ICI2NE1pIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgICAgICJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOiBmYWxzZSwKICAgICAgICAgICAgICAgICJjYXBhYmlsaXRpZXMiOiB7CiAgICAgICAgICAgICAgICAgICJkcm9wIjogWwogICAgICAgICAgICAgICAgICAgICJBTEwiCiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicmVhZE9ubHlSb290RmlsZXN5c3RlbSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJzdGFydHVwUHJvYmUiOiB7CiAgICAgICAgICAgICAgICAiZmFpbHVyZVRocmVzaG9sZCI6IDEyLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInRlcm1pbmF0aW9uTWVzc2FnZVBhdGgiOiAiL2Rldi90ZXJtaW5hdGlvbi1sb2ciLAogICAgICAgICAgICAgICJ0ZXJtaW5hdGlvbk1lc3NhZ2VQb2xpY3kiOiAiRmlsZSIsCiAgICAgICAgICAgICAgInZvbHVtZU1vdW50cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvZGF0YSIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImRhdGEiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi9ydW4vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJydW50aW1lIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdG1wIiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvY29uZmlndXJhdGlvbiIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImNvbmZpZ3VyYXRpb24iLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24vcG9saWN5IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdmFyL3J1bi9zZWNyZXRzL2luZm9ybWVkLWRlY2lzaW9uL2F1ZGl0IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiYXVkaXQiLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24va3ViZXJuZXRlcyIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmVybmV0ZXMtYXBpIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvd29ya2xvYWQtcmVnaXN0cnkiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9yZXF1ZXN0ZXIiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICAgIF0sCiAgICAgICAgICAiZG5zUG9saWN5IjogIkNsdXN0ZXJGaXJzdCIsCiAgICAgICAgICAicmVzdGFydFBvbGljeSI6ICJBbHdheXMiLAogICAgICAgICAgInNjaGVkdWxlck5hbWUiOiAiZGVmYXVsdC1zY2hlZHVsZXIiLAogICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgImZzR3JvdXAiOiAxMDAwMSwKICAgICAgICAgICAgImZzR3JvdXBDaGFuZ2VQb2xpY3kiOiAiT25Sb290TWlzbWF0Y2giLAogICAgICAgICAgICAicnVuQXNHcm91cCI6IDEwMDAxLAogICAgICAgICAgICAicnVuQXNOb25Sb290IjogdHJ1ZSwKICAgICAgICAgICAgInJ1bkFzVXNlciI6IDEwMDAxLAogICAgICAgICAgICAic2VjY29tcFByb2ZpbGUiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiUnVudGltZURlZmF1bHQiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICAic2VydmljZUFjY291bnQiOiAicmV2aWV3IiwKICAgICAgICAgICJzZXJ2aWNlQWNjb3VudE5hbWUiOiAicmV2aWV3IiwKICAgICAgICAgICJ0ZXJtaW5hdGlvbkdyYWNlUGVyaW9kU2Vjb25kcyI6IDQ1LAogICAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJkYXRhIiwKICAgICAgICAgICAgICAicGVyc2lzdGVudFZvbHVtZUNsYWltIjogewogICAgICAgICAgICAgICAgImNsYWltTmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1kYXRhIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJlbXB0eURpciI6IHsKICAgICAgICAgICAgICAgICJtZWRpdW0iOiAiTWVtb3J5IiwKICAgICAgICAgICAgICAgICJzaXplTGltaXQiOiAiMTZNaSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW1wdHlEaXIiOiB7CiAgICAgICAgICAgICAgICAibWVkaXVtIjogIk1lbW9yeSIsCiAgICAgICAgICAgICAgICAic2l6ZUxpbWl0IjogIjE2TWkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJ0bXAiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjkyLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJjb25maWd1cmF0aW9uIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAicHJvamVjdGVkIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgInNvdXJjZXMiOiBbCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAic2VydmljZUFjY291bnRUb2tlbiI6IHsKICAgICAgICAgICAgICAgICAgICAgICJhdWRpZW5jZSI6ICJmbGV4LWF1dGgiLAogICAgICAgICAgICAgICAgICAgICAgImV4cGlyYXRpb25TZWNvbmRzIjogMzYwMCwKICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogInRva2VuIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJuYW1lIjogImF1ZGl0IiwKICAgICAgICAgICAgICAic2VjcmV0IjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImtleSI6ICJ0b2tlbiIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAidG9rZW4iCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAic2VjcmV0TmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1hdWRpdCIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICAgInByb2plY3RlZCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJzb3VyY2VzIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInNlcnZpY2VBY2NvdW50VG9rZW4iOiB7CiAgICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvblNlY29uZHMiOiA2MDAsCiAgICAgICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgImtleSI6ICJjYS5jcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogImNhLmNydCIKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmUtcm9vdC1jYS5jcnQiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgImNvbmZpZ01hcCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI5MiwKICAgICAgICAgICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLXdvcmtsb2FkLXJlZ2lzdHJ5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQtcmVnaXN0cnkiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJpdGVtcyI6IFsKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJrZXkiOiAiY2xpZW50LXNlY3JldCIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAiY2xpZW50LXNlY3JldCIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJvcHRpb25hbCI6IGZhbHNlLAogICAgICAgICAgICAgICAgInNlY3JldE5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tc2l0dGluZy1yZXF1ZXN0ZXIiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfQogICAgICB9CiAgICB9CiAgfSwKICAic2VhbGVkX2RlcGVuZGVuY2llcyI6IHsKICAgICJjb25maWdtYXAiOiB7CiAgICAgICJhcGlWZXJzaW9uIjogInYxIiwKICAgICAgImltbXV0YWJsZSI6IHRydWUsCiAgICAgICJraW5kIjogIkNvbmZpZ01hcCIsCiAgICAgICJtZXRhZGF0YSI6IHsKICAgICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgICAiYXJnb2NkLmFyZ29wcm9qLmlvL3RyYWNraW5nLWlkIjogImluZm9ybWVkLWRlY2lzaW9uOi9Db25maWdNYXA6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiLAogICAgICAgICAgImluZm9ybWVkLWRlY2lzaW9uLmNvdWxvbWIuc29jaWFsL2FkbWlzc2lvbiI6ICJhcHByb3ZlZDsgSU5GRC1XUC0wMDA3IGN1dG92ZXIiCiAgICAgICAgfSwKICAgICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1ydW50aW1lLTZjMjIxODc4ZDA1MSIsCiAgICAgICAgIm5hbWVzcGFjZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICAgInVpZCI6ICI5MDExM2QyZC01NGQ5LTRlODItYjFkMy05MGI0OWViZWQyYjUiCiAgICAgIH0sCiAgICAgICJjYW5vbmljYWxfZGF0YV9zaGEyNTYiOiAiOGVjNDYyZDEyMzQ3NTQ4ZjUwNmVlZWJmMWUzYzdjYzQ3NjQ4MDE5YzhhNzgzODA0ZDA5YmNjNzliMGYyMmFmNiIKICAgIH0sCiAgICAicHZjIjogewogICAgICAiYXBpVmVyc2lvbiI6ICJ2MSIsCiAgICAgICJraW5kIjogIlBlcnNpc3RlbnRWb2x1bWVDbGFpbSIsCiAgICAgICJtZXRhZGF0YSI6IHsKICAgICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgICAiYXJnb2NkLmFyZ29wcm9qLmlvL3RyYWNraW5nLWlkIjogImluZm9ybWVkLWRlY2lzaW9uOi9QZXJzaXN0ZW50Vm9sdW1lQ2xhaW06aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24tZGF0YSIsCiAgICAgICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIsCiAgICAgICAgICAicHYua3ViZXJuZXRlcy5pby9iaW5kLWNvbXBsZXRlZCI6ICJ5ZXMiLAogICAgICAgICAgInB2Lmt1YmVybmV0ZXMuaW8vYm91bmQtYnktY29udHJvbGxlciI6ICJ5ZXMiLAogICAgICAgICAgInZvbHVtZS5iZXRhLmt1YmVybmV0ZXMuaW8vc3RvcmFnZS1wcm92aXNpb25lciI6ICJyYW5jaGVyLmlvL2xvY2FsLXBhdGgiLAogICAgICAgICAgInZvbHVtZS5rdWJlcm5ldGVzLmlvL3NlbGVjdGVkLW5vZGUiOiAiMjM5LjYyLjIwNS45Mi5ob3N0LnNlY3VyZXNlcnZlci5uZXQiLAogICAgICAgICAgInZvbHVtZS5rdWJlcm5ldGVzLmlvL3N0b3JhZ2UtcHJvdmlzaW9uZXIiOiAicmFuY2hlci5pby9sb2NhbC1wYXRoIgogICAgICAgIH0sCiAgICAgICAgImZpbmFsaXplcnMiOiBbCiAgICAgICAgICAia3ViZXJuZXRlcy5pby9wdmMtcHJvdGVjdGlvbiIKICAgICAgICBdLAogICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLWRhdGEiLAogICAgICAgICJuYW1lc3BhY2UiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICJ1aWQiOiAiZDgwYjI1YjEtMTQxYS00NDFiLWIyMDUtOThmYTJmZDk4YjdjIgogICAgICB9LAogICAgICAic3BlYyI6IHsKICAgICAgICAiYWNjZXNzTW9kZXMiOiBbCiAgICAgICAgICAiUmVhZFdyaXRlT25jZSIKICAgICAgICBdLAogICAgICAgICJyZXNvdXJjZXMiOiB7CiAgICAgICAgICAicmVxdWVzdHMiOiB7CiAgICAgICAgICAgICJzdG9yYWdlIjogIjFHaSIKICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgICJzdG9yYWdlQ2xhc3NOYW1lIjogImxvY2FsLXBhdGgiLAogICAgICAgICJ2b2x1bWVNb2RlIjogIkZpbGVzeXN0ZW0iLAogICAgICAgICJ2b2x1bWVOYW1lIjogInB2Yy1kODBiMjViMS0xNDFhLTQ0MWItYjIwNS05OGZhMmZkOThiN2MiCiAgICAgIH0KICAgIH0sCiAgICAicHZjX3BoYXNlIjogIkJvdW5kIgogIH0sCiAgImFyZ28iOiB7CiAgICAidWlkIjogIjQ5OTg2OGYyLWVhYmMtNGRmNC1hZTMxLWI4OWNlNThkNzUwZCIsCiAgICAic291cmNlIjogewogICAgICAicGF0aCI6ICJkZXBsb3kvbGlmZWN5Y2xlIiwKICAgICAgInJlcG9VUkwiOiAiaHR0cHM6Ly9mb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb24uZ2l0IiwKICAgICAgInRhcmdldFJldmlzaW9uIjogImM5NmIwYzZkODgxZTg1NWQxZDE1ODAwM2M2ZGU5MGVmMzkwMjYyZTIiCiAgICB9LAogICAgImRlc3RpbmF0aW9uIjogewogICAgICAibmFtZXNwYWNlIjogImluZm9ybWVkLWRlY2lzaW9uIiwKICAgICAgInNlcnZlciI6ICJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMiCiAgICB9LAogICAgImF1dG9tYXRlZCI6IGZhbHNlLAogICAgImhlYWx0aCI6IHsKICAgICAgImxhc3RUcmFuc2l0aW9uVGltZSI6ICIyMDI2LTEwLTExVDA3OjAwOjQyWiIsCiAgICAgICJzdGF0dXMiOiAiSGVhbHRoeSIKICAgIH0sCiAgICAic3luYyI6IHsKICAgICAgImNvbXBhcmVkVG8iOiB7CiAgICAgICAgImRlc3RpbmF0aW9uIjogewogICAgICAgICAgIm5hbWVzcGFjZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICAgICAic2VydmVyIjogImh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2YyIKICAgICAgICB9LAogICAgICAgICJzb3VyY2UiOiB7CiAgICAgICAgICAicGF0aCI6ICJkZXBsb3kvbGlmZWN5Y2xlIiwKICAgICAgICAgICJyZXBvVVJMIjogImh0dHBzOi8vZm9yZ2Vqby5jb3Vsb21iLnNvY2lhbC9jb3Vsb21iL2luZm9ybWVkLWRlY2lzaW9uLmdpdCIsCiAgICAgICAgICAidGFyZ2V0UmV2aXNpb24iOiAiYzk2YjBjNmQ4ODFlODU1ZDFkMTU4MDAzYzZkZTkwZWYzOTAyNjJlMiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJyZXZpc2lvbiI6ICJjOTZiMGM2ZDg4MWU4NTVkMWQxNTgwMDNjNmRlOTBlZjM5MDI2MmUyIiwKICAgICAgInN0YXR1cyI6ICJPdXRPZlN5bmMiCiAgICB9CiAgfSwKICAic2VydmVyX2RyeV9ydW5fcGFzc2VkIjogdHJ1ZSwKICAiYWRtaXNzaW9uX29ubHlfaW1hZ2VfZGVsdGEiOiB0cnVlLAogICJjbGllbnRfZGlmZl9leGl0IjogMSwKICAiY2xpZW50X2RpZmZfc2hhMjU2IjogImNjZmY3YzFlY2ZkOWViNTlmYzBjZTU2NmMxMDEzYTJhZjVkZjUxNGFmMDQ4YzY4NDc0YTI3Mjk5NzAyNGVmN2IiLAogICJhbGxvY2F0YWJsZV9jcHVfbSI6IDQwMDAsCiAgInJlcXVlc3RlZF9jcHVfbSI6IDM0NjAsCiAgInN0cmF0ZWd5IjogIlJlY3JlYXRlIgp9Cg==" - }, - { - "digest": "sha256:0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5", - "media_type": "application/json", - "content_base64": "ewogICJhbGxfY2hlY2tzX3Bhc3MiOiB0cnVlLAogICJvYnNlcnZlZF9hdCI6ICIyMDI2LTEwLTExVDA5OjQzOjQyLjg1MTA4NiswMDowMCIsCiAgIm5hdGl2ZV9jaGFuZ2VkIjogZmFsc2UsCiAgInByb3RlY3RlZF9kYXRhYmFzZV9yZWFkIjogZmFsc2UsCiAgInNlY3JldF9yZWFkIjogZmFsc2UsCiAgImRlcGxveW1lbnRfZ2VuZXJhdGlvbiI6IDI1LAogICJyZWFkeV9yZXBsaWNhcyI6IDEsCiAgInNlYWxlZF9saXZlX2RlcGxveW1lbnQiOiB7CiAgICAiYXBpVmVyc2lvbiI6ICJhcHBzL3YxIiwKICAgICJraW5kIjogIkRlcGxveW1lbnQiLAogICAgIm1ldGFkYXRhIjogewogICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgImFyZ29jZC5hcmdvcHJvai5pby90cmFja2luZy1pZCI6ICJpbmZvcm1lZC1kZWNpc2lvbjphcHBzL0RlcGxveW1lbnQ6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICJpbmZvcm1lZC1kZWNpc2lvbi5jb3Vsb21iLnNvY2lhbC9hZG1pc3Npb24iOiAiYXBwcm92ZWQ7IElORkQtV1AtMDAwNyBjdXRvdmVyIgogICAgICB9LAogICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICJuYW1lc3BhY2UiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAidWlkIjogImE1ODhlMTBhLTkwZDItNDg4My05NzVlLTZkZDhiOGZlY2E5MiIKICAgIH0sCiAgICAic3BlYyI6IHsKICAgICAgInByb2dyZXNzRGVhZGxpbmVTZWNvbmRzIjogNjAwLAogICAgICAicmVwbGljYXMiOiAxLAogICAgICAicmV2aXNpb25IaXN0b3J5TGltaXQiOiAxMCwKICAgICAgInNlbGVjdG9yIjogewogICAgICAgICJtYXRjaExhYmVscyI6IHsKICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgIH0KICAgICAgfSwKICAgICAgInN0cmF0ZWd5IjogewogICAgICAgICJ0eXBlIjogIlJlY3JlYXRlIgogICAgICB9LAogICAgICAidGVtcGxhdGUiOiB7CiAgICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIKICAgICAgICAgIH0sCiAgICAgICAgICAibGFiZWxzIjogewogICAgICAgICAgICAiYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50IjogInJldmlldyIsCiAgICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInNwZWMiOiB7CiAgICAgICAgICAiYXV0b21vdW50U2VydmljZUFjY291bnRUb2tlbiI6IGZhbHNlLAogICAgICAgICAgImNvbnRhaW5lcnMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW52IjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0tFWUNBUEVfSVNTVUVSIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8va2MuY291bG9tYi5zb2NpYWwiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0NPTlRBSU5FUl9DT05GSUciLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2NvbmZpZ3VyYXRpb24vcnVudGltZS5qc29uIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgImltYWdlIjogImZvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbkBzaGEyNTY6NzYzYzM2NDAyYzhiZTdjYzRjMWJkMWE2MTYyM2QxYWZmOTExNmQ1MjI0MTg3NzQyNWI0NWE3MDE4Y2MwODgzYSIsCiAgICAgICAgICAgICAgImltYWdlUHVsbFBvbGljeSI6ICJJZk5vdFByZXNlbnQiLAogICAgICAgICAgICAgICJsaXZlbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgImNvbnRhaW5lclBvcnQiOiA4MDgwLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJodHRwIiwKICAgICAgICAgICAgICAgICAgInByb3RvY29sIjogIlRDUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJjb250YWluZXJQb3J0IjogODA4MSwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQiLAogICAgICAgICAgICAgICAgICAicHJvdG9jb2wiOiAiVENQIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgInJlYWRpbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInJlc291cmNlcyI6IHsKICAgICAgICAgICAgICAgICJsaW1pdHMiOiB7CiAgICAgICAgICAgICAgICAgICJjcHUiOiAiNTAwbSIsCiAgICAgICAgICAgICAgICAgICJtZW1vcnkiOiAiMjU2TWkiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInJlcXVlc3RzIjogewogICAgICAgICAgICAgICAgICAiY3B1IjogIjVtIiwKICAgICAgICAgICAgICAgICAgIm1lbW9yeSI6ICI2NE1pIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgICAgICJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOiBmYWxzZSwKICAgICAgICAgICAgICAgICJjYXBhYmlsaXRpZXMiOiB7CiAgICAgICAgICAgICAgICAgICJkcm9wIjogWwogICAgICAgICAgICAgICAgICAgICJBTEwiCiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicmVhZE9ubHlSb290RmlsZXN5c3RlbSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJzdGFydHVwUHJvYmUiOiB7CiAgICAgICAgICAgICAgICAiZmFpbHVyZVRocmVzaG9sZCI6IDEyLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInRlcm1pbmF0aW9uTWVzc2FnZVBhdGgiOiAiL2Rldi90ZXJtaW5hdGlvbi1sb2ciLAogICAgICAgICAgICAgICJ0ZXJtaW5hdGlvbk1lc3NhZ2VQb2xpY3kiOiAiRmlsZSIsCiAgICAgICAgICAgICAgInZvbHVtZU1vdW50cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvZGF0YSIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImRhdGEiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi9ydW4vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJydW50aW1lIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdG1wIiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvY29uZmlndXJhdGlvbiIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImNvbmZpZ3VyYXRpb24iLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24vcG9saWN5IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdmFyL3J1bi9zZWNyZXRzL2luZm9ybWVkLWRlY2lzaW9uL2F1ZGl0IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiYXVkaXQiLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24va3ViZXJuZXRlcyIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmVybmV0ZXMtYXBpIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvd29ya2xvYWQtcmVnaXN0cnkiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9yZXF1ZXN0ZXIiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICAgIF0sCiAgICAgICAgICAiZG5zUG9saWN5IjogIkNsdXN0ZXJGaXJzdCIsCiAgICAgICAgICAicmVzdGFydFBvbGljeSI6ICJBbHdheXMiLAogICAgICAgICAgInNjaGVkdWxlck5hbWUiOiAiZGVmYXVsdC1zY2hlZHVsZXIiLAogICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgImZzR3JvdXAiOiAxMDAwMSwKICAgICAgICAgICAgImZzR3JvdXBDaGFuZ2VQb2xpY3kiOiAiT25Sb290TWlzbWF0Y2giLAogICAgICAgICAgICAicnVuQXNHcm91cCI6IDEwMDAxLAogICAgICAgICAgICAicnVuQXNOb25Sb290IjogdHJ1ZSwKICAgICAgICAgICAgInJ1bkFzVXNlciI6IDEwMDAxLAogICAgICAgICAgICAic2VjY29tcFByb2ZpbGUiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiUnVudGltZURlZmF1bHQiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICAic2VydmljZUFjY291bnQiOiAicmV2aWV3IiwKICAgICAgICAgICJzZXJ2aWNlQWNjb3VudE5hbWUiOiAicmV2aWV3IiwKICAgICAgICAgICJ0ZXJtaW5hdGlvbkdyYWNlUGVyaW9kU2Vjb25kcyI6IDQ1LAogICAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJkYXRhIiwKICAgICAgICAgICAgICAicGVyc2lzdGVudFZvbHVtZUNsYWltIjogewogICAgICAgICAgICAgICAgImNsYWltTmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1kYXRhIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJlbXB0eURpciI6IHsKICAgICAgICAgICAgICAgICJtZWRpdW0iOiAiTWVtb3J5IiwKICAgICAgICAgICAgICAgICJzaXplTGltaXQiOiAiMTZNaSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW1wdHlEaXIiOiB7CiAgICAgICAgICAgICAgICAibWVkaXVtIjogIk1lbW9yeSIsCiAgICAgICAgICAgICAgICAic2l6ZUxpbWl0IjogIjE2TWkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJ0bXAiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjkyLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJjb25maWd1cmF0aW9uIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAicHJvamVjdGVkIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgInNvdXJjZXMiOiBbCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAic2VydmljZUFjY291bnRUb2tlbiI6IHsKICAgICAgICAgICAgICAgICAgICAgICJhdWRpZW5jZSI6ICJmbGV4LWF1dGgiLAogICAgICAgICAgICAgICAgICAgICAgImV4cGlyYXRpb25TZWNvbmRzIjogMzYwMCwKICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogInRva2VuIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJuYW1lIjogImF1ZGl0IiwKICAgICAgICAgICAgICAic2VjcmV0IjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImtleSI6ICJ0b2tlbiIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAidG9rZW4iCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAic2VjcmV0TmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1hdWRpdCIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICAgInByb2plY3RlZCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJzb3VyY2VzIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInNlcnZpY2VBY2NvdW50VG9rZW4iOiB7CiAgICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvblNlY29uZHMiOiA2MDAsCiAgICAgICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgImtleSI6ICJjYS5jcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogImNhLmNydCIKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmUtcm9vdC1jYS5jcnQiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgImNvbmZpZ01hcCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI5MiwKICAgICAgICAgICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLXdvcmtsb2FkLXJlZ2lzdHJ5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQtcmVnaXN0cnkiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJpdGVtcyI6IFsKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJrZXkiOiAiY2xpZW50LXNlY3JldCIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAiY2xpZW50LXNlY3JldCIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJvcHRpb25hbCI6IGZhbHNlLAogICAgICAgICAgICAgICAgInNlY3JldE5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tc2l0dGluZy1yZXF1ZXN0ZXIiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfQogICAgICB9CiAgICB9CiAgfSwKICAiY2FuZGlkYXRlX2RlcGxveW1lbnQiOiB7CiAgICAiYXBpVmVyc2lvbiI6ICJhcHBzL3YxIiwKICAgICJraW5kIjogIkRlcGxveW1lbnQiLAogICAgIm1ldGFkYXRhIjogewogICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgImFyZ29jZC5hcmdvcHJvai5pby90cmFja2luZy1pZCI6ICJpbmZvcm1lZC1kZWNpc2lvbjphcHBzL0RlcGxveW1lbnQ6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICJpbmZvcm1lZC1kZWNpc2lvbi5jb3Vsb21iLnNvY2lhbC9hZG1pc3Npb24iOiAiYXBwcm92ZWQ7IElORkQtV1AtMDAwNyBjdXRvdmVyIgogICAgICB9LAogICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICJuYW1lc3BhY2UiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAidWlkIjogImE1ODhlMTBhLTkwZDItNDg4My05NzVlLTZkZDhiOGZlY2E5MiIKICAgIH0sCiAgICAic3BlYyI6IHsKICAgICAgInByb2dyZXNzRGVhZGxpbmVTZWNvbmRzIjogNjAwLAogICAgICAicmVwbGljYXMiOiAxLAogICAgICAicmV2aXNpb25IaXN0b3J5TGltaXQiOiAxMCwKICAgICAgInNlbGVjdG9yIjogewogICAgICAgICJtYXRjaExhYmVscyI6IHsKICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgIH0KICAgICAgfSwKICAgICAgInN0cmF0ZWd5IjogewogICAgICAgICJ0eXBlIjogIlJlY3JlYXRlIgogICAgICB9LAogICAgICAidGVtcGxhdGUiOiB7CiAgICAgICAgIm1ldGFkYXRhIjogewogICAgICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIKICAgICAgICAgIH0sCiAgICAgICAgICAibGFiZWxzIjogewogICAgICAgICAgICAiYXBwLmt1YmVybmV0ZXMuaW8vY29tcG9uZW50IjogInJldmlldyIsCiAgICAgICAgICAgICJhcHAua3ViZXJuZXRlcy5pby9uYW1lIjogImluZm9ybWVkLWRlY2lzaW9uIgogICAgICAgICAgfQogICAgICAgIH0sCiAgICAgICAgInNwZWMiOiB7CiAgICAgICAgICAiYXV0b21vdW50U2VydmljZUFjY291bnRUb2tlbiI6IGZhbHNlLAogICAgICAgICAgImNvbnRhaW5lcnMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW52IjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0tFWUNBUEVfSVNTVUVSIiwKICAgICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8va2MuY291bG9tYi5zb2NpYWwiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0NPTlRBSU5FUl9DT05GSUciLAogICAgICAgICAgICAgICAgICAidmFsdWUiOiAiL2NvbmZpZ3VyYXRpb24vcnVudGltZS5qc29uIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgImltYWdlIjogImZvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbkBzaGEyNTY6NzYzYzM2NDAyYzhiZTdjYzRjMWJkMWE2MTYyM2QxYWZmOTExNmQ1MjI0MTg3NzQyNWI0NWE3MDE4Y2MwODgzYSIsCiAgICAgICAgICAgICAgImltYWdlUHVsbFBvbGljeSI6ICJJZk5vdFByZXNlbnQiLAogICAgICAgICAgICAgICJsaXZlbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgImNvbnRhaW5lclBvcnQiOiA4MDgwLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJodHRwIiwKICAgICAgICAgICAgICAgICAgInByb3RvY29sIjogIlRDUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJjb250YWluZXJQb3J0IjogODA4MSwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQiLAogICAgICAgICAgICAgICAgICAicHJvdG9jb2wiOiAiVENQIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgInJlYWRpbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAzLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInJlc291cmNlcyI6IHsKICAgICAgICAgICAgICAgICJsaW1pdHMiOiB7CiAgICAgICAgICAgICAgICAgICJjcHUiOiAiNTAwbSIsCiAgICAgICAgICAgICAgICAgICJtZW1vcnkiOiAiMjU2TWkiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgInJlcXVlc3RzIjogewogICAgICAgICAgICAgICAgICAiY3B1IjogIjVtIiwKICAgICAgICAgICAgICAgICAgIm1lbW9yeSI6ICI2NE1pIgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgICAgICJhbGxvd1ByaXZpbGVnZUVzY2FsYXRpb24iOiBmYWxzZSwKICAgICAgICAgICAgICAgICJjYXBhYmlsaXRpZXMiOiB7CiAgICAgICAgICAgICAgICAgICJkcm9wIjogWwogICAgICAgICAgICAgICAgICAgICJBTEwiCiAgICAgICAgICAgICAgICAgIF0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicmVhZE9ubHlSb290RmlsZXN5c3RlbSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJzdGFydHVwUHJvYmUiOiB7CiAgICAgICAgICAgICAgICAiZmFpbHVyZVRocmVzaG9sZCI6IDEyLAogICAgICAgICAgICAgICAgImh0dHBHZXQiOiB7CiAgICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICAgInBvcnQiOiAiaHR0cCIsCiAgICAgICAgICAgICAgICAgICJzY2hlbWUiOiAiSFRUUCIKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAicGVyaW9kU2Vjb25kcyI6IDEwLAogICAgICAgICAgICAgICAgInN1Y2Nlc3NUaHJlc2hvbGQiOiAxLAogICAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInRlcm1pbmF0aW9uTWVzc2FnZVBhdGgiOiAiL2Rldi90ZXJtaW5hdGlvbi1sb2ciLAogICAgICAgICAgICAgICJ0ZXJtaW5hdGlvbk1lc3NhZ2VQb2xpY3kiOiAiRmlsZSIsCiAgICAgICAgICAgICAgInZvbHVtZU1vdW50cyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvZGF0YSIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImRhdGEiCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi9ydW4vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJydW50aW1lIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdG1wIiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvY29uZmlndXJhdGlvbiIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImNvbmZpZ3VyYXRpb24iLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24vcG9saWN5IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvdmFyL3J1bi9zZWNyZXRzL2luZm9ybWVkLWRlY2lzaW9uL2F1ZGl0IiwKICAgICAgICAgICAgICAgICAgIm5hbWUiOiAiYXVkaXQiLAogICAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24va3ViZXJuZXRlcyIsCiAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmVybmV0ZXMtYXBpIiwKICAgICAgICAgICAgICAgICAgInJlYWRPbmx5IjogdHJ1ZQogICAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvd29ya2xvYWQtcmVnaXN0cnkiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9yZXF1ZXN0ZXIiLAogICAgICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICAgIF0sCiAgICAgICAgICAiZG5zUG9saWN5IjogIkNsdXN0ZXJGaXJzdCIsCiAgICAgICAgICAicmVzdGFydFBvbGljeSI6ICJBbHdheXMiLAogICAgICAgICAgInNjaGVkdWxlck5hbWUiOiAiZGVmYXVsdC1zY2hlZHVsZXIiLAogICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgImZzR3JvdXAiOiAxMDAwMSwKICAgICAgICAgICAgImZzR3JvdXBDaGFuZ2VQb2xpY3kiOiAiT25Sb290TWlzbWF0Y2giLAogICAgICAgICAgICAicnVuQXNHcm91cCI6IDEwMDAxLAogICAgICAgICAgICAicnVuQXNOb25Sb290IjogdHJ1ZSwKICAgICAgICAgICAgInJ1bkFzVXNlciI6IDEwMDAxLAogICAgICAgICAgICAic2VjY29tcFByb2ZpbGUiOiB7CiAgICAgICAgICAgICAgInR5cGUiOiAiUnVudGltZURlZmF1bHQiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICAic2VydmljZUFjY291bnQiOiAicmV2aWV3IiwKICAgICAgICAgICJzZXJ2aWNlQWNjb3VudE5hbWUiOiAicmV2aWV3IiwKICAgICAgICAgICJ0ZXJtaW5hdGlvbkdyYWNlUGVyaW9kU2Vjb25kcyI6IDQ1LAogICAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJkYXRhIiwKICAgICAgICAgICAgICAicGVyc2lzdGVudFZvbHVtZUNsYWltIjogewogICAgICAgICAgICAgICAgImNsYWltTmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1kYXRhIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJlbXB0eURpciI6IHsKICAgICAgICAgICAgICAgICJtZWRpdW0iOiAiTWVtb3J5IiwKICAgICAgICAgICAgICAgICJzaXplTGltaXQiOiAiMTZNaSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiZW1wdHlEaXIiOiB7CiAgICAgICAgICAgICAgICAibWVkaXVtIjogIk1lbW9yeSIsCiAgICAgICAgICAgICAgICAic2l6ZUxpbWl0IjogIjE2TWkiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJ0bXAiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjkyLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICAibmFtZSI6ICJjb25maWd1cmF0aW9uIgogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAicHJvamVjdGVkIjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgInNvdXJjZXMiOiBbCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAic2VydmljZUFjY291bnRUb2tlbiI6IHsKICAgICAgICAgICAgICAgICAgICAgICJhdWRpZW5jZSI6ICJmbGV4LWF1dGgiLAogICAgICAgICAgICAgICAgICAgICAgImV4cGlyYXRpb25TZWNvbmRzIjogMzYwMCwKICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogInRva2VuIgogICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgewogICAgICAgICAgICAgICJuYW1lIjogImF1ZGl0IiwKICAgICAgICAgICAgICAic2VjcmV0IjogewogICAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgImtleSI6ICJ0b2tlbiIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAidG9rZW4iCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgICAic2VjcmV0TmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1hdWRpdCIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICAgInByb2plY3RlZCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJzb3VyY2VzIjogWwogICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgInNlcnZpY2VBY2NvdW50VG9rZW4iOiB7CiAgICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvblNlY29uZHMiOiA2MDAsCiAgICAgICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgICAiY29uZmlnTWFwIjogewogICAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICAgICAgICAgImtleSI6ICJjYS5jcnQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogImNhLmNydCIKICAgICAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmUtcm9vdC1jYS5jcnQiCiAgICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgICB9CiAgICAgICAgICAgICAgICBdCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9LAogICAgICAgICAgICB7CiAgICAgICAgICAgICAgImNvbmZpZ01hcCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI5MiwKICAgICAgICAgICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLXdvcmtsb2FkLXJlZ2lzdHJ5IgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgIm5hbWUiOiAid29ya2xvYWQtcmVnaXN0cnkiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgIHsKICAgICAgICAgICAgICAibmFtZSI6ICJzaXR0aW5nLXJlcXVlc3RlciIsCiAgICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAgICJpdGVtcyI6IFsKICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICJrZXkiOiAiY2xpZW50LXNlY3JldCIsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAiY2xpZW50LXNlY3JldCIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgXSwKICAgICAgICAgICAgICAgICJvcHRpb25hbCI6IGZhbHNlLAogICAgICAgICAgICAgICAgInNlY3JldE5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tc2l0dGluZy1yZXF1ZXN0ZXIiCiAgICAgICAgICAgICAgfQogICAgICAgICAgICB9CiAgICAgICAgICBdCiAgICAgICAgfQogICAgICB9CiAgICB9CiAgfSwKICAic2VhbGVkX2RlcGVuZGVuY2llcyI6IHsKICAgICJjb25maWdtYXAiOiB7CiAgICAgICJhcGlWZXJzaW9uIjogInYxIiwKICAgICAgImltbXV0YWJsZSI6IHRydWUsCiAgICAgICJraW5kIjogIkNvbmZpZ01hcCIsCiAgICAgICJtZXRhZGF0YSI6IHsKICAgICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgICAiYXJnb2NkLmFyZ29wcm9qLmlvL3RyYWNraW5nLWlkIjogImluZm9ybWVkLWRlY2lzaW9uOi9Db25maWdNYXA6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiLAogICAgICAgICAgImluZm9ybWVkLWRlY2lzaW9uLmNvdWxvbWIuc29jaWFsL2FkbWlzc2lvbiI6ICJhcHByb3ZlZDsgSU5GRC1XUC0wMDA3IGN1dG92ZXIiCiAgICAgICAgfSwKICAgICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1ydW50aW1lLTZjMjIxODc4ZDA1MSIsCiAgICAgICAgIm5hbWVzcGFjZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICAgInVpZCI6ICI5MDExM2QyZC01NGQ5LTRlODItYjFkMy05MGI0OWViZWQyYjUiCiAgICAgIH0sCiAgICAgICJjYW5vbmljYWxfZGF0YV9zaGEyNTYiOiAiOGVjNDYyZDEyMzQ3NTQ4ZjUwNmVlZWJmMWUzYzdjYzQ3NjQ4MDE5YzhhNzgzODA0ZDA5YmNjNzliMGYyMmFmNiIKICAgIH0sCiAgICAicHZjIjogewogICAgICAiYXBpVmVyc2lvbiI6ICJ2MSIsCiAgICAgICJraW5kIjogIlBlcnNpc3RlbnRWb2x1bWVDbGFpbSIsCiAgICAgICJtZXRhZGF0YSI6IHsKICAgICAgICAiYW5ub3RhdGlvbnMiOiB7CiAgICAgICAgICAiYXJnb2NkLmFyZ29wcm9qLmlvL3RyYWNraW5nLWlkIjogImluZm9ybWVkLWRlY2lzaW9uOi9QZXJzaXN0ZW50Vm9sdW1lQ2xhaW06aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24tZGF0YSIsCiAgICAgICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIsCiAgICAgICAgICAicHYua3ViZXJuZXRlcy5pby9iaW5kLWNvbXBsZXRlZCI6ICJ5ZXMiLAogICAgICAgICAgInB2Lmt1YmVybmV0ZXMuaW8vYm91bmQtYnktY29udHJvbGxlciI6ICJ5ZXMiLAogICAgICAgICAgInZvbHVtZS5iZXRhLmt1YmVybmV0ZXMuaW8vc3RvcmFnZS1wcm92aXNpb25lciI6ICJyYW5jaGVyLmlvL2xvY2FsLXBhdGgiLAogICAgICAgICAgInZvbHVtZS5rdWJlcm5ldGVzLmlvL3NlbGVjdGVkLW5vZGUiOiAiMjM5LjYyLjIwNS45Mi5ob3N0LnNlY3VyZXNlcnZlci5uZXQiLAogICAgICAgICAgInZvbHVtZS5rdWJlcm5ldGVzLmlvL3N0b3JhZ2UtcHJvdmlzaW9uZXIiOiAicmFuY2hlci5pby9sb2NhbC1wYXRoIgogICAgICAgIH0sCiAgICAgICAgImZpbmFsaXplcnMiOiBbCiAgICAgICAgICAia3ViZXJuZXRlcy5pby9wdmMtcHJvdGVjdGlvbiIKICAgICAgICBdLAogICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLWRhdGEiLAogICAgICAgICJuYW1lc3BhY2UiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAgICJ1aWQiOiAiZDgwYjI1YjEtMTQxYS00NDFiLWIyMDUtOThmYTJmZDk4YjdjIgogICAgICB9LAogICAgICAic3BlYyI6IHsKICAgICAgICAiYWNjZXNzTW9kZXMiOiBbCiAgICAgICAgICAiUmVhZFdyaXRlT25jZSIKICAgICAgICBdLAogICAgICAgICJyZXNvdXJjZXMiOiB7CiAgICAgICAgICAicmVxdWVzdHMiOiB7CiAgICAgICAgICAgICJzdG9yYWdlIjogIjFHaSIKICAgICAgICAgIH0KICAgICAgICB9LAogICAgICAgICJzdG9yYWdlQ2xhc3NOYW1lIjogImxvY2FsLXBhdGgiLAogICAgICAgICJ2b2x1bWVNb2RlIjogIkZpbGVzeXN0ZW0iLAogICAgICAgICJ2b2x1bWVOYW1lIjogInB2Yy1kODBiMjViMS0xNDFhLTQ0MWItYjIwNS05OGZhMmZkOThiN2MiCiAgICAgIH0KICAgIH0sCiAgICAicHZjX3BoYXNlIjogIkJvdW5kIgogIH0sCiAgImFyZ28iOiB7CiAgICAidWlkIjogIjQ5OTg2OGYyLWVhYmMtNGRmNC1hZTMxLWI4OWNlNThkNzUwZCIsCiAgICAic291cmNlIjogewogICAgICAicGF0aCI6ICJkZXBsb3kvbGlmZWN5Y2xlIiwKICAgICAgInJlcG9VUkwiOiAiaHR0cHM6Ly9mb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb24uZ2l0IiwKICAgICAgInRhcmdldFJldmlzaW9uIjogImM5NmIwYzZkODgxZTg1NWQxZDE1ODAwM2M2ZGU5MGVmMzkwMjYyZTIiCiAgICB9LAogICAgImRlc3RpbmF0aW9uIjogewogICAgICAibmFtZXNwYWNlIjogImluZm9ybWVkLWRlY2lzaW9uIiwKICAgICAgInNlcnZlciI6ICJodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMiCiAgICB9LAogICAgImF1dG9tYXRlZCI6IGZhbHNlLAogICAgImhlYWx0aCI6IHsKICAgICAgImxhc3RUcmFuc2l0aW9uVGltZSI6ICIyMDI2LTEwLTExVDA3OjAwOjQyWiIsCiAgICAgICJzdGF0dXMiOiAiSGVhbHRoeSIKICAgIH0sCiAgICAic3luYyI6IHsKICAgICAgImNvbXBhcmVkVG8iOiB7CiAgICAgICAgImRlc3RpbmF0aW9uIjogewogICAgICAgICAgIm5hbWVzcGFjZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICAgICAic2VydmVyIjogImh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2YyIKICAgICAgICB9LAogICAgICAgICJzb3VyY2UiOiB7CiAgICAgICAgICAicGF0aCI6ICJkZXBsb3kvbGlmZWN5Y2xlIiwKICAgICAgICAgICJyZXBvVVJMIjogImh0dHBzOi8vZm9yZ2Vqby5jb3Vsb21iLnNvY2lhbC9jb3Vsb21iL2luZm9ybWVkLWRlY2lzaW9uLmdpdCIsCiAgICAgICAgICAidGFyZ2V0UmV2aXNpb24iOiAiYzk2YjBjNmQ4ODFlODU1ZDFkMTU4MDAzYzZkZTkwZWYzOTAyNjJlMiIKICAgICAgICB9CiAgICAgIH0sCiAgICAgICJyZXZpc2lvbiI6ICJjOTZiMGM2ZDg4MWU4NTVkMWQxNTgwMDNjNmRlOTBlZjM5MDI2MmUyIiwKICAgICAgInN0YXR1cyI6ICJPdXRPZlN5bmMiCiAgICB9CiAgfSwKICAic2VydmVyX2RyeV9ydW5fcGFzc2VkIjogdHJ1ZSwKICAiYWRtaXNzaW9uX29ubHlfaW1hZ2VfZGVsdGEiOiB0cnVlLAogICJjbGllbnRfZGlmZl9leGl0IjogMCwKICAiY2xpZW50X2RpZmZfc2hhMjU2IjogImUzYjBjNDQyOThmYzFjMTQ5YWZiZjRjODk5NmZiOTI0MjdhZTQxZTQ2NDliOTM0Y2E0OTU5OTFiNzg1MmI4NTUiLAogICJhbGxvY2F0YWJsZV9jcHVfbSI6IDQwMDAsCiAgInJlcXVlc3RlZF9jcHVfbSI6IDM0NjAsCiAgInN0cmF0ZWd5IjogIlJlY3JlYXRlIgp9Cg==" - }, - { - "digest": "sha256:dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e", - "media_type": "application/json", - "content_base64": "ewogICJwcm9wb3NhbF9pZCI6ICJJTkZELVdQLTAwMDktVDA1L3psaWItODUwOTEtcjEiLAogICJ0YXNrX2lkIjogIklORkQtV1AtMDAwOS1UMDUiLAogICJyaXNrX3JlY29yZCI6ICJJTkZELVJJU0stMjAyNjEwMTEtWkxJQi04NTA5MSIsCiAgInN0YXRlIjogImFjdHVhbC1vd25lci1DSS1wdWJsaXNoZWQtaW5kZXBlbmRlbnQtdmVyaWZpY2F0aW9uLXBhc3NlZC1uYXRpdmUtdW5hcHByb3ZlZCIsCiAgInZlcmlmaWVkX3V0YyI6ICIyMDI2LTEwLTExVDA5OjQwOjI0LjQ2OTY0NiswMDowMCIsCiAgInNvdXJjZV9yZXZpc2lvbiI6ICI5YjRiYjI1ZDMyNTY5OGFlNTQzZTdlOGE4YTNjMzU5MmRiMWY1OGZjIiwKICAiY2lfdGFzayI6IDExNTM0LAogICJjaV9ydW4iOiA0LAogICJjaV9zdGF0dXMiOiAic3VjY2VzcyIsCiAgInJldmlld2VkX3NvdXJjZV9tYW5pZmVzdF9zaGEyNTYiOiAiNzI0YWE0Nzc3ZWIxYzg5NzZkODY4ZTVmNTA0NDczZjRiNmQ1OGYxM2Q3NTBkODdhZWZhNWM4ZDVjYTA3NDdlYyIsCiAgImFsbDc0X3NvdXJjZV9oYXNoZXNfbWF0Y2hfYWN0dWFsX2NvbW1pdCI6IHRydWUsCiAgInB1Ymxpc2hlZF9pbWFnZV9kaWdlc3QiOiAic2hhMjU2OmY1NTg4ZjQ5NmIxZjI2N2QwN2NkYzlmNTIwOGU2ZjJmMjQxZjc0YjczOWMyMzkyNGIwMzcwMmRlYzEzYTVjMjMiLAogICJiYXNlX2ltYWdlX2RpZ2VzdCI6ICJzaGEyNTY6NzYzYzM2NDAyYzhiZTdjYzRjMWJkMWE2MTYyM2QxYWZmOTExNmQ1MjI0MTg3NzQyNWI0NWE3MDE4Y2MwODgzYSIsCiAgInJlZ2lzdHJ5X2NvbmZpZ19kaWdlc3QiOiAic2hhMjU2OjYyMmJlY2MzZWJkOTdjYjY2YjZlN2MzNmYzNWJmOTMxYTdkZWNkOTQ2NGI2NTg1MTEyM2MxY2M4Yzk5OGQ3NWIiLAogICJleGVjdXRlZF9sb2NhbF9kYWVtb25faW1hZ2VfaWQiOiAic2hhMjU2OmYyZTA3ZTNkZDgzOWI1NWFmZDVkNTE3MjQzYTc2NWEyNjk2NTMyNWU0ZTY3NTBmNTA4NzcxYWQ1NTZiZDZkMDEiLAogICJzZXJpYWxpemF0aW9uX2NhdmVhdCI6ICJEYWVtb24gbG9hZCByZXNlcmlhbGl6ZXMgY29uZmlnOyBhbGwgZGlmZklEcy9wbGF0Zm9ybS9lZmZlY3RpdmUgY29uZmlnK2xhYmVscyBpbmRlcGVuZGVudGx5IGVxdWFsIHRvIHJlZ2lzdHJ5OyBuZWl0aGVyIGNvbmZpZyBub3IgZGFlbW9uIElEIHN1YnN0aXR1dGVzIGZvciByZWdpc3RyeSBpbWFnZSBkaWdlc3QiLAogICJhY3R1YWxfcmVnaXN0cnlfZGVzY3JpcHRvcnNfYW5kX2xheWVyc192ZXJpZmllZCI6IHRydWUsCiAgImJhc2U5bGF5ZXJzX3VuY2hhbmdlZCI6IHRydWUsCiAgIm92ZXJsYXlfbGF5ZXJzIjogMiwKICAiZnVsbHNjYW5fYmFzZWxpbmUiOiB7CiAgICAiTUVESVVNIjogMSwKICAgICJpZCI6ICJDVkUtMjAyNi04NTA5MSIKICB9LAogICJmdWxsc2Nhbl9jYW5kaWRhdGVfYWxsc2V2ZXJpdHlfZmluZGluZ3MiOiAwLAogICJmdWxsc2Nhbl9ub19zdXBwcmVzc2lvbnMiOiB0cnVlLAogICJzYW1lX2ZyZXNoX0RCX2JvdGhfaW1hZ2VzIjogdHJ1ZSwKICAic2Nhbm5lcl9kYl9zaGEyNTYiOiAiMDNjYWNiNDA0NTJjZWI3NGFmZGZjYTA3MmNiODJmNzllZTg4NGRmYWMwMDZlZjI0YTRkZmRhZDEzM2IwZjkxNyIsCiAgInNjYW5uZXJfZGJfbWV0YWRhdGEiOiB7CiAgICAiVmVyc2lvbiI6IDIsCiAgICAiTmV4dFVwZGF0ZSI6ICIyMDI2LTEwLTExVDE4OjU5OjUwLjg4OTAxMDU3MloiLAogICAgIlVwZGF0ZWRBdCI6ICIyMDI2LTEwLTEwVDE4OjU5OjUwLjg4OTAxMDkyM1oiLAogICAgIkRvd25sb2FkZWRBdCI6ICIyMDI2LTEwLTEwVDIzOjMyOjMyLjM4MDAzMzEzMVoiCiAgfSwKICAic2Nhbm5lcl9jYXZlYXQiOiAiQWxwaW5lMy4yNCBhYnNlbnQgRU9MIGxpc3Qgd2FybmluZzsgYWN0dWFsMzhPUys2UHl0aG9uIGRldGVjdG9ycyByYW4uIFplcm8gbWVhbnMgdGhpcyB2ZXJpZmllZCBEQiBzbmFwc2hvdCwgbm90IHVua25vd24tZnV0dXJlIGFzc3VyYW5jZSBvciBlc3RhdGUgcmlzayBncmFkZS4iLAogICJzYm9tX2NvdW50IjogNDQsCiAgInNib21fb25seV9kZWx0YSI6IHsKICAgICJ6bGliIjogWwogICAgICAiMS4zLjItcjAiLAogICAgICAiMS4zLjItcjEiCiAgICBdCiAgfSwKICAiZmluYWxfY2hhbmdlZF9maWxlcyI6IFsKICAgICJsaWIvYXBrL2RiL2luc3RhbGxlZCIsCiAgICAidXNyL2xpYi9saWJ6LnNvLjEuMy4yIgogIF0sCiAgImRpcmVjdG9yeV9jb3VudCI6IDMxMywKICAiZGlyZWN0b3J5X21ldGFkYXRhX2RlbHRhIjoge30sCiAgImFwcF9maWxlc19pZGVudGljYWwiOiAzNiwKICAiaW5zdGFsbGVkX1VJRCI6IDEwMDAxLAogICJpbnN0YWxsZWRfR0lEIjogMTAwMDEsCiAgInBvbGljeV9zaGEyNTYiOiAiOWQ3YzFiZTRjYjYxOTlkMTRlZjBhYWVhZDVmODU5NTFmNDViMGNjYmQ3YzgwMGE3ZmRlOTIyYTI1MzU2NmI2MiIsCiAgInBvbGljeV9tb2RlIjogIjBvNjQ0IiwKICAicG9saWN5X1VJRCI6IDAsCiAgImNhbmRpZGF0ZV9pbWFnZV9leGVjdXRlZCI6IHRydWUsCiAgInB1YmxpY19yZWdpc3RyeV9sYXllcl9yZWFkYmFjayI6IHRydWUsCiAgIm5hdGl2ZV9wcm92aWRlcl90ZXN0IjogZmFsc2UsCiAgImluc3RhbGxlZF9wdXJwb3NlX3ZhcmlhbnRzIjogMTAsCiAgInB1cnBvc2VfZm9ybXNfYWxsb3ciOiAyLAogICJwdXJwb3NlX2Zvcm1zX3JlZnVzZSI6IDgsCiAgInJ1bnRpbWVfcmVhZG9ubHlfbmV0d29ya25vbmUiOiB0cnVlLAogICJuYXRpdmVfd29ya2xvYWRfb3JfZGF0YWJhc2Vfb3Jfc25hcHNob3Rfb3JfY3JlZGVudGlhbHNfYWNjZXNzZWQiOiBmYWxzZSwKICAibmF0aXZlX3JvbGxvdXQiOiBmYWxzZSwKICAiZm91bmRlcl9yZWxlYXNlX2FwcHJvdmFsIjogZmFsc2UsCiAgInJpc2tfZm9sbG93dGhyb3VnaCI6ICJUMDUgcmVtYWlucyBhY3RpdmU7IG1hbnVhbCByZXZpZXcgZHVlMjAyNi0xMC0xOCwgbm8gY29udHJvbGxlciBvciBlc3RhdGUgcmlzayBncmFkZS4gQWN0dWFsIG5hdGl2ZSBpbnN0YWxsYXRpb24vZnJlc2ggc2NvcGVkIGZvdW5kZXIgZGVjaXNpb24gc3RpbGwgcmVxdWlyZWQgYmVmb3JlIGNsb3N1cmUuIiwKICAidmVyaWZpY2F0aW9uX3JlZmluZW1lbnQiOiAiRmlyc3QgcnVuIHVzZWQgcmVnaXN0cnkgY29uZmlnIGFzIGRhZW1vbiBoYW5kbGUgYW5kIHJlZnVzZWQgYmVmb3JlIGNhbmRpZGF0ZSBleGVjdXRpb24xMjUuIENvcnJlY3RlZCBieSBpbmRlcGVuZGVudGx5IHByb3ZpbmcgbG9hZGVkIGRhZW1vbiBJRCBzZW1hbnRpY3MrZGlmZklEcyBleGFjdCwgdGhlbiBleGVjdXRlZC4gTm90IGFjdHVhbCBpbWFnZSBmYWlsdXJlLiIsCiAgImV2aWRlbmNlX3NoYTI1NiI6IHsKICAgICJhcnRpZmFjdC1jb21wYXJpc29uLmpzb24iOiAiYTFjNWQ5ZTI0OWRjMDA2MDA0NDk5OTgzNjBmYWY2ZThkYTAyMDZiNTY4Yzc2ZDE1NTYwMmM0NjQ1ZmE1ZmNlNCIsCiAgICAiYmFzZS1pbWFnZS1jb25maWcuanNvbiI6ICI0YWQ3MjQ5Yzc1ZmQ5ZmMwMzljZWIxODY3NzI0MTllMWNiYTVmZDk3NTljNDA0NTU2NDU5YzgwYTJkMjg1ZDEwIiwKICAgICJiYXNlLWltYWdlLW1hbmlmZXN0Lmpzb24iOiAiOTc4YzU3NWQzYzdiZjBkZTM5ZGJlNGU5NTY3YzM5Y2QzNmY3MGI3ZTU1YmMxNzM1N2JkYjAzZmIzYjk2YjBmYiIsCiAgICAiYmFzZS1ydW50aW1lLmpzb24iOiAiMjBhYWJiOWY0ODQ2YWI0ZmE2ZTI4ZTA3NjNkMTUzMTg4ZGExNTFlZTMyMjkzMzY2YTUxYjBmZGY4NzJhNGQyNCIsCiAgICAiYmFzZS1zYm9tLmNkeC5qc29uIjogImRkN2Q2NGM5NTcxMzkxYWNkNGQxZjg2YzYwMjZhMWIwOTRhYmFiNGVlNTZlNWE5Zjc5OWVjMmZkM2M3ZDkxNWEiLAogICAgImJhc2Utc2JvbS5jZHguanNvbi5sb2ciOiAiOGFiNDQ3M2E1Zjc0YjM2ZTgyYTQwOTVlOWM4ZDZlNjZkODBhZWM2NGJkMWNlYmQ2YzlmNjczNDUzMTg2OWZjYSIsCiAgICAiYmFzZS10cml2eS1mdWxsLmpzb24iOiAiM2U5MWEzMDAxYWRiMDA4Y2VjMzdkOWFlM2MwODM0MmY1ZDQzYzhiYTAxMTFlZWU3NWM2M2E2YTc5MTg4MDhhOSIsCiAgICAiYmFzZS10cml2eS1mdWxsLmpzb24ubG9nIjogIjk2YTQ4ZGMzOWY3ZDY3NGI0OTU0ZDVmYTk4YTc2NjQ1NTI4ZWMwZmU3MDU3ODJmZTExNGUzYjRiOWQ3NzY4NjIiLAogICAgImNhbmRpZGF0ZS1ydW50aW1lLmpzb24iOiAiZDU4ZTYwYmRjNjk0NThmZTQyYWY4MjQ4ZTAyYWNkOTI0YjNiNjA1YzgyODg3NzM1MmE3ZDlhMzk1M2U5MDEyNCIsCiAgICAiY2ktcHVibGljYXRpb24tb2JzZXJ2YXRpb24uanNvbiI6ICJmZmU2ZjI1MDhiYzliZWJiZGM4NWFjNjNlYWU1NzY1NTYyZWZiYjhlOTIwY2M4NWI4ZGEzMGExMmVlMWU0YjE3IiwKICAgICJpbWFnZS1jb25maWcuanNvbiI6ICIyNTNhYjM1OTAxM2FlYjM0NTdkMmFiZTA4NTJiYWNkMzM2OTdmOTczZGQzMmMxYjU3Mzg4MDBlNWRjNjMzZjQzIiwKICAgICJpbWFnZS1tYW5pZmVzdC5qc29uIjogIjljYTJhYmVjNmEyNmQ5YTlmODAyZGZlNzVlZmE4YmZmZGMyODQ4NWU2YmNhZjUyNWNjOWIwNzVkZjM3MDAxOTQiLAogICAgImluc3RhbGxlZC1wdXJwb3NlLmpzb24iOiAiMzEyMzEyYzZmMGJkMjc1MTQwMWIzYmM1YmNkMzBlZTZhZjAxZWQ3Y2IzODBmODViODYzZDAzYjBjN2U5ZDFhZCIsCiAgICAibG9hZGVkLWRhZW1vbi1pbWFnZS1wcm9vZi5qc29uIjogIjJlNWMwNDNhMTAxZDc1ZTRjNTY5YWRiNmJjMmYwOTAyMDY3OWIyOGVkMmFkODM2Yzk1NGY3ZDI3MTAxY2U3MTkiLAogICAgInB1YmxpY2F0aW9uLWRpZ2VzdC5qc29uIjogImNhMmExZGVjMzhkYjhjNWZiYmMwMGVmNGYyN2FhODY1ZWRiYmQ0YTdjYTlkYzIxYjJiYTAyMzY2NGM1MDdmMzgiLAogICAgInJlYWRfcHVibGljX2FydGlmYWN0LnB5IjogImVhYTMxZGExZWJiMjA5NWVhNWUxYmM3YzU3MDMxMWNjNDc0N2YwZDdmNzE4MDM0ZGFkOTM0MzUxZDE5MjBkYWYiLAogICAgInJlZ2lzdHJ5LXByb29mLmpzb24iOiAiM2VmNTJkOWU0ODAwZmM5YmQyMzgwNzIyNDFkYzE1ZjEzYTAyNjBlZTNkZjFlODJlNmY2M2U3MjkwMjc0OWQ5MiIsCiAgICAicnVuX3B1Ymxpc2hlZF9ydW50aW1lLnB5IjogImQ5YmQzM2ZlY2QyYTk4YzIzODFiNGJhMmRjZTEyYjVjM2Q5YTY5ZmFhNWUzNTAyOTE5MjRmYzg4NWY1ZTc3NWYiLAogICAgInJ1bl9wdWJsaXNoZWRfc2Nhbi5weSI6ICIyNTgzYWI5NjM1MDQxMmZiM2YyMGI1YjA1ZGVkMDk0OWViM2VlOGU1YzBlODkyM2E5YzJjNDJkNDE5ZDM2NjM5IiwKICAgICJydW50aW1lLWNvbXBhcmlzb24uanNvbiI6ICI5OTNiODBiOTI4NWUyMjdhMGJkZmRlNDkyN2NkMmYwNTNiY2M0YTU4ZGQ5YmQxYjYzM2FjN2E0NjM3ZDY3MTNjIiwKICAgICJydW50aW1lLXJlY2VpcHQuanNvbiI6ICI2NDQyNjc2ZGNmZDk0MWYwYTA1YTRjYjc5YTAyZGVhODM5ZmNkMzMyZDgyYjIxNTRjMTVjZjZkYjY4ZGU1ODg5IiwKICAgICJzYm9tLmNkeC5qc29uIjogIjQyMDIyNjMwNTk3ODBlYWUwNDQ4MzI4MDQ0YzFjZjY4YjU5NzU4ZjMyOWFlNmJjOTgxZDkxNjYxMDUyN2VhZTMiLAogICAgInNib20uY2R4Lmpzb24ubG9nIjogIjUxODczYjM3YTA4NDZlZTFhNjE4ZWE3NTkxZDc5ODA5ZGMwZjg2MjRmNzdhMDNmYWU3YWY5NTNiY2JkNzI4MmYiLAogICAgInNjYW4tcmVjZWlwdC5qc29uIjogImFhYWE5MTA2MWJiMDM2ZTZkMjI4YjFjMWJkNDMzMzU3OGRjOTQ4Y2IyYmI1YWM4YmEyNjk0MjkxMjFlNzMyMWEiLAogICAgInNvdXJjZS1jb21taXQtcHJvb2YuanNvbiI6ICJiMDgxMWRhMmQzYmQ1OTM4NzMzZjAwNzhlNWZlYzhlMTlhNmQ5NzUyYzQyNzdiODBiZmU5ODM3ZWQ3MmM2YTNiIiwKICAgICJ0cml2eS1mdWxsLmpzb24iOiAiMmZmNjhlNzY1YjljNjUyOTI2MTFkNWE5NjkyMDJhNzY1NDYzNzE2OGVkZWZjZWRhYzVkMmM3NjM0MDA5ZWRhZSIsCiAgICAidHJpdnktZnVsbC5qc29uLmxvZyI6ICJiMzQxOWFmODYwZDI2NDQ0OWY2MTk4NmY1NjI0OTFlZGFlYWQyN2RmZDM2MzhjZWFiZTk0YjRmZTE3N2Y0YmYxIgogIH0KfQo=" - }, - { - "digest": "sha256:a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4", - "media_type": "application/json", - "content_base64": "ewogICJiYXNlX2ltYWdlX2NvbmZpZ19pZCI6ICJzaGEyNTY6ZjM0ZGUyMTM3OWM0ZjUwZWEwMzYxY2ExZDgyOTZjOGU3MmY5ODk0OTlhZDI4ODAxNmI4MTIwNDk5N2FlNDc3OSIsCiAgImNhbmRpZGF0ZV9sb2NhbF9pbWFnZV9jb25maWdfaWQiOiAic2hhMjU2OjYyMmJlY2MzZWJkOTdjYjY2YjZlN2MzNmYzNWJmOTMxYTdkZWNkOTQ2NGI2NTg1MTEyM2MxY2M4Yzk5OGQ3NWIiLAogICJjYW5kaWRhdGVfcmVnaXN0cnlfZGlnZXN0IjogbnVsbCwKICAiYmFzZV9sYXllcnNfcHJlc2VydmVkIjogOSwKICAibmV3X2xheWVycyI6IDIsCiAgImVmZmVjdGl2ZV9ydW50aW1lX2NvbmZpZ19pZGVudGljYWwiOiB0cnVlLAogICJyYXdfY29uZmlnX2J5dGVzX2lkZW50aWNhbCI6IGZhbHNlLAogICJzZXJpYWxpemF0aW9uX25vdGUiOiAiTGVnYWN5IGJhc2UgZXhwbGljaXRseSBzZXJpYWxpemVkIGVtcHR5L2ZhbHNlL251bGwgZGVmYXVsdHM7IGNhbmRpZGF0ZSBEb2NrZXItc2F2ZSBjb25maWcgb21pdHMgZXF1aXZhbGVudCBrbm93biBkZWZhdWx0cy4gRXhhY3QgY2xvc2VkIGRlZmF1bHQgbm9ybWFsaXphdGlvbiBvbmx5OyBydW50aW1lIGZpZWxkcyB1bmNoYW5nZWQuIiwKICAibGFiZWxzX2JlZm9yZSI6IHsKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYmFzZS5kaWdlc3QiOiAic2hhMjU2OjdmNWM2Y2IyOWM5ZjcwYTAyNmJmZTMwZTcyOTM0Mjc0ZGRjNjkyNjE2NzZjZjZkNDc2ZjgxZGQzMjgxMmQxNzMiLAogICAgIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5yZXZpc2lvbiI6ICIzZTRiY2IyNmY4ZWVhYThjNjQzNDVjNTY1NDc3YjVhYzhjY2JiMTY4IiwKICAgICJzb2NpYWwuY291bG9tYi5yZWxlYXNlLnNjb3BlIjogIklORkQtV1AtMDAwOS1UMDQvcHVycG9zZS1vdmVybGF5LXYxIiwKICAgICJzb2NpYWwuY291bG9tYi5zb3VyY2UucG9saWN5LnNoYTI1NiI6ICI5ZDdjMWJlNGNiNjE5OWQxNGVmMGFhZWFkNWY4NTk1MWY0NWIwY2NiZDdjODAwYTdmZGU5MjJhMjUzNTY2YjYyIgogIH0sCiAgImxhYmVsc19hZnRlciI6IHsKICAgICJvcmcub3BlbmNvbnRhaW5lcnMuaW1hZ2UuYmFzZS5kaWdlc3QiOiAic2hhMjU2Ojc2M2MzNjQwMmM4YmU3Y2M0YzFiZDFhNjE2MjNkMWFmZjkxMTZkNTIyNDE4Nzc0MjViNDVhNzAxOGNjMDg4M2EiLAogICAgIm9yZy5vcGVuY29udGFpbmVycy5pbWFnZS5yZXZpc2lvbiI6ICI5YjRiYjI1ZDMyNTY5OGFlNTQzZTdlOGE4YTNjMzU5MmRiMWY1OGZjIiwKICAgICJzb2NpYWwuY291bG9tYi5yZWxlYXNlLnNjb3BlIjogIklORkQtV1AtMDAwOS1UMDUvemxpYi04NTA5MS1yMSIsCiAgICAic29jaWFsLmNvdWxvbWIuc291cmNlLmNvbnRleHQuc2hhMjU2IjogImM1NTRlNjJhODRhZjM0MGNiYTE1ZDQ2OWE0NTY4ZjZkMGM0MDkyM2U1ODBiMDhlZDc1NjVlMjI3MDJiMTIwN2QiLAogICAgInNvY2lhbC5jb3Vsb21iLnNvdXJjZS5wb2xpY3kuc2hhMjU2IjogIjlkN2MxYmU0Y2I2MTk5ZDE0ZWYwYWFlYWQ1Zjg1OTUxZjQ1YjBjY2JkN2M4MDBhN2ZkZTkyMmEyNTM1NjZiNjIiCiAgfSwKICAiZmluYWxfY2hhbmdlZF9maWxlcyI6IHsKICAgICJsaWIvYXBrL2RiL2luc3RhbGxlZCI6IHsKICAgICAgImJlZm9yZSI6IFsKICAgICAgICAiZmlsZSIsCiAgICAgICAgNDIwLAogICAgICAgIDAsCiAgICAgICAgMCwKICAgICAgICAiNjM4M2VhMzgwNjI0N2U4ZGJmOWViZWU4Y2IzMGViOGVjYTYzYThlYTU5NjYyYTc5MzEyN2Q2YTYxYjk4OGQ3MCIKICAgICAgXSwKICAgICAgImFmdGVyIjogWwogICAgICAgICJmaWxlIiwKICAgICAgICA0MjAsCiAgICAgICAgMCwKICAgICAgICAwLAogICAgICAgICJmNTkwNzYyMTk2NzEwZTI1YzBlOWY3MmRhMDY0MmNhZDhhNWRjYmEwYWE5ZTU4YTBjYmFhNjU2ODRkMTVjMDAxIgogICAgICBdCiAgICB9LAogICAgInVzci9saWIvbGliei5zby4xLjMuMiI6IHsKICAgICAgImJlZm9yZSI6IFsKICAgICAgICAiZmlsZSIsCiAgICAgICAgNDkzLAogICAgICAgIDAsCiAgICAgICAgMCwKICAgICAgICAiYTFlMmMwM2RjZGVkYmE5OGEwNmEwMGNhZGZiNjdhMGUwYjFjZDkzOTg2ODYwZWMwYzVjZTY2ZTc4OWM0NGU1NyIKICAgICAgXSwKICAgICAgImFmdGVyIjogWwogICAgICAgICJmaWxlIiwKICAgICAgICA0OTMsCiAgICAgICAgMCwKICAgICAgICAwLAogICAgICAgICJlY2M4YjlkZmM0NWViN2ZhMjliNDEwZmZhY2E2MjU3ODczODkwZGU1M2I4MGZlOTE3MzU3MzdiNDkwNjdjNWYyIgogICAgICBdCiAgICB9CiAgfSwKICAiZGlyZWN0b3J5X21ldGFkYXRhX2NvbXBhcmVkIjogdHJ1ZSwKICAiZGlyZWN0b3J5X2NvdW50X2Jhc2UiOiAzMTMsCiAgImRpcmVjdG9yeV9jb3VudF9jYW5kaWRhdGUiOiAzMTMsCiAgImRpcmVjdG9yeV9tZXRhZGF0YV9kZWx0YSI6IHt9LAogICJzYm9tX3BhY2thZ2VfY291bnRfYmFzZSI6IDQ0LAogICJzYm9tX3BhY2thZ2VfY291bnRfY2FuZGlkYXRlIjogNDQsCiAgInNib21fb25seV9kZWx0YSI6IHsKICAgICJ6bGliIjogWwogICAgICAiMS4zLjItcjAiLAogICAgICAiMS4zLjItcjEiCiAgICBdCiAgfSwKICAiZnVsbF9zY2FuX2Jhc2VsaW5lX2ZpbmRpbmdzIjogMSwKICAiZnVsbF9zY2FuX2NhbmRpZGF0ZV9hbGxzZXZlcml0eV9maW5kaW5ncyI6IDAsCiAgImRiX3NuYXBzaG90X3NhbWUiOiB0cnVlLAogICJuYXRpdmVfd29ya2xvYWRfY2hhbmdlZCI6IGZhbHNlCn0K" - }, - { - "digest": "sha256:6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68", - "media_type": "application/json", - "content_base64": "ewogICJpbWFnZSI6ICJmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2OmY1NTg4ZjQ5NmIxZjI2N2QwN2NkYzlmNTIwOGU2ZjJmMjQxZjc0YjczOWMyMzkyNGIwMzcwMmRlYzEzYTVjMjMiLAogICJ6bGliX3BhY2thZ2UiOiAiMS4zLjItcjEiLAogICJmaWxlcyI6IHsKICAgICJsaWIvYXBrL2RiL2luc3RhbGxlZCI6IFsKICAgICAgImZpbGUiLAogICAgICA0MjAsCiAgICAgIDAsCiAgICAgIDAsCiAgICAgICJmNTkwNzYyMTk2NzEwZTI1YzBlOWY3MmRhMDY0MmNhZDhhNWRjYmEwYWE5ZTU4YTBjYmFhNjU2ODRkMTVjMDAxIgogICAgXSwKICAgICJ1c3IvbGliL2xpYnouc28uMS4zLjIiOiBbCiAgICAgICJmaWxlIiwKICAgICAgNDkzLAogICAgICAwLAogICAgICAwLAogICAgICAiZWNjOGI5ZGZjNDVlYjdmYTI5YjQxMGZmYWNhNjI1Nzg3Mzg5MGRlNTNiODBmZTkxNzM1NzM3YjQ5MDY3YzVmMiIKICAgIF0KICB9Cn0K" - }, - { - "digest": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "media_type": "application/json", - "content_base64": "ewogICJhcGlWZXJzaW9uIjogImFwcHMvdjEiLAogICJraW5kIjogIkRlcGxveW1lbnQiLAogICJtZXRhZGF0YSI6IHsKICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgImFyZ29jZC5hcmdvcHJvai5pby90cmFja2luZy1pZCI6ICJpbmZvcm1lZC1kZWNpc2lvbjphcHBzL0RlcGxveW1lbnQ6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIKICAgIH0sCiAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAibmFtZXNwYWNlIjogImluZm9ybWVkLWRlY2lzaW9uIiwKICAgICJ1aWQiOiAiYTU4OGUxMGEtOTBkMi00ODgzLTk3NWUtNmRkOGI4ZmVjYTkyIgogIH0sCiAgInNwZWMiOiB7CiAgICAicHJvZ3Jlc3NEZWFkbGluZVNlY29uZHMiOiA2MDAsCiAgICAicmVwbGljYXMiOiAxLAogICAgInJldmlzaW9uSGlzdG9yeUxpbWl0IjogMTAsCiAgICAic2VsZWN0b3IiOiB7CiAgICAgICJtYXRjaExhYmVscyI6IHsKICAgICAgICAiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIKICAgICAgfQogICAgfSwKICAgICJzdHJhdGVneSI6IHsKICAgICAgInR5cGUiOiAiUmVjcmVhdGUiCiAgICB9LAogICAgInRlbXBsYXRlIjogewogICAgICAibWV0YWRhdGEiOiB7CiAgICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICAgImluZm9ybWVkLWRlY2lzaW9uLmNvdWxvbWIuc29jaWFsL2FkbWlzc2lvbiI6ICJhcHByb3ZlZDsgSU5GRC1XUC0wMDA3IGN1dG92ZXIiCiAgICAgICAgfSwKICAgICAgICAibGFiZWxzIjogewogICAgICAgICAgImFwcC5rdWJlcm5ldGVzLmlvL2NvbXBvbmVudCI6ICJyZXZpZXciLAogICAgICAgICAgImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24iCiAgICAgICAgfQogICAgICB9LAogICAgICAic3BlYyI6IHsKICAgICAgICAiYXV0b21vdW50U2VydmljZUFjY291bnRUb2tlbiI6IGZhbHNlLAogICAgICAgICJjb250YWluZXJzIjogWwogICAgICAgICAgewogICAgICAgICAgICAiZW52IjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJuYW1lIjogIklORkRfS0VZQ0FQRV9JU1NVRVIiLAogICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8va2MuY291bG9tYi5zb2NpYWwiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0NPTlRBSU5FUl9DT05GSUciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIi9jb25maWd1cmF0aW9uL3J1bnRpbWUuanNvbiIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJpbWFnZSI6ICJmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2Ojc2M2MzNjQwMmM4YmU3Y2M0YzFiZDFhNjE2MjNkMWFmZjkxMTZkNTIyNDE4Nzc0MjViNDVhNzAxOGNjMDg4M2EiLAogICAgICAgICAgICAiaW1hZ2VQdWxsUG9saWN5IjogIklmTm90UHJlc2VudCIsCiAgICAgICAgICAgICJsaXZlbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICJmYWlsdXJlVGhyZXNob2xkIjogMywKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiY29udGFpbmVyUG9ydCI6IDgwODAsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJodHRwIiwKICAgICAgICAgICAgICAgICJwcm90b2NvbCI6ICJUQ1AiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiY29udGFpbmVyUG9ydCI6IDgwODEsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZCIsCiAgICAgICAgICAgICAgICAicHJvdG9jb2wiOiAiVENQIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgInJlYWRpbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICJmYWlsdXJlVGhyZXNob2xkIjogMywKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAicmVzb3VyY2VzIjogewogICAgICAgICAgICAgICJsaW1pdHMiOiB7CiAgICAgICAgICAgICAgICAiY3B1IjogIjUwMG0iLAogICAgICAgICAgICAgICAgIm1lbW9yeSI6ICIyNTZNaSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJyZXF1ZXN0cyI6IHsKICAgICAgICAgICAgICAgICJjcHUiOiAiNW0iLAogICAgICAgICAgICAgICAgIm1lbW9yeSI6ICI2NE1pIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgICAiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjogZmFsc2UsCiAgICAgICAgICAgICAgImNhcGFiaWxpdGllcyI6IHsKICAgICAgICAgICAgICAgICJkcm9wIjogWwogICAgICAgICAgICAgICAgICAiQUxMIgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInJlYWRPbmx5Um9vdEZpbGVzeXN0ZW0iOiB0cnVlCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJzdGFydHVwUHJvYmUiOiB7CiAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAxMiwKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAidGVybWluYXRpb25NZXNzYWdlUGF0aCI6ICIvZGV2L3Rlcm1pbmF0aW9uLWxvZyIsCiAgICAgICAgICAgICJ0ZXJtaW5hdGlvbk1lc3NhZ2VQb2xpY3kiOiAiRmlsZSIsCiAgICAgICAgICAgICJ2b2x1bWVNb3VudHMiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvZGF0YSIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJkYXRhIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvcnVuL2luZm9ybWVkLWRlY2lzaW9uIiwKICAgICAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi90bXAiLAogICAgICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvY29uZmlndXJhdGlvbiIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJjb25maWd1cmF0aW9uIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9wb2xpY3kiLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9hdWRpdCIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJhdWRpdCIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24va3ViZXJuZXRlcyIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi93b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24vcmVxdWVzdGVyIiwKICAgICAgICAgICAgICAgICJuYW1lIjogInNpdHRpbmctcmVxdWVzdGVyIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0KICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJkbnNQb2xpY3kiOiAiQ2x1c3RlckZpcnN0IiwKICAgICAgICAicmVzdGFydFBvbGljeSI6ICJBbHdheXMiLAogICAgICAgICJzY2hlZHVsZXJOYW1lIjogImRlZmF1bHQtc2NoZWR1bGVyIiwKICAgICAgICAic2VjdXJpdHlDb250ZXh0IjogewogICAgICAgICAgImZzR3JvdXAiOiAxMDAwMSwKICAgICAgICAgICJmc0dyb3VwQ2hhbmdlUG9saWN5IjogIk9uUm9vdE1pc21hdGNoIiwKICAgICAgICAgICJydW5Bc0dyb3VwIjogMTAwMDEsCiAgICAgICAgICAicnVuQXNOb25Sb290IjogdHJ1ZSwKICAgICAgICAgICJydW5Bc1VzZXIiOiAxMDAwMSwKICAgICAgICAgICJzZWNjb21wUHJvZmlsZSI6IHsKICAgICAgICAgICAgInR5cGUiOiAiUnVudGltZURlZmF1bHQiCiAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAic2VydmljZUFjY291bnQiOiAicmV2aWV3IiwKICAgICAgICAic2VydmljZUFjY291bnROYW1lIjogInJldmlldyIsCiAgICAgICAgInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjogNDUsCiAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJuYW1lIjogImRhdGEiLAogICAgICAgICAgICAicGVyc2lzdGVudFZvbHVtZUNsYWltIjogewogICAgICAgICAgICAgICJjbGFpbU5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tZGF0YSIKICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImVtcHR5RGlyIjogewogICAgICAgICAgICAgICJtZWRpdW0iOiAiTWVtb3J5IiwKICAgICAgICAgICAgICAic2l6ZUxpbWl0IjogIjE2TWkiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiZW1wdHlEaXIiOiB7CiAgICAgICAgICAgICAgIm1lZGl1bSI6ICJNZW1vcnkiLAogICAgICAgICAgICAgICJzaXplTGltaXQiOiAiMTZNaSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImNvbmZpZ01hcCI6IHsKICAgICAgICAgICAgICAiZGVmYXVsdE1vZGUiOiAyOTIsCiAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJuYW1lIjogImNvbmZpZ3VyYXRpb24iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibmFtZSI6ICJjYWxsZXIiLAogICAgICAgICAgICAicHJvamVjdGVkIjogewogICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAic291cmNlcyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInNlcnZpY2VBY2NvdW50VG9rZW4iOiB7CiAgICAgICAgICAgICAgICAgICAgImF1ZGllbmNlIjogImZsZXgtYXV0aCIsCiAgICAgICAgICAgICAgICAgICAgImV4cGlyYXRpb25TZWNvbmRzIjogMzYwMCwKICAgICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgIm5hbWUiOiAiYXVkaXQiLAogICAgICAgICAgICAic2VjcmV0IjogewogICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAiaXRlbXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJrZXkiOiAidG9rZW4iLAogICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICJzZWNyZXROYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLWF1ZGl0IgogICAgICAgICAgICB9CiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICJwcm9qZWN0ZWQiOiB7CiAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICJzb3VyY2VzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAic2VydmljZUFjY291bnRUb2tlbiI6IHsKICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvblNlY29uZHMiOiA2MDAsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAidG9rZW4iCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJjb25maWdNYXAiOiB7CiAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAia2V5IjogImNhLmNydCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogImNhLmNydCIKICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmUtcm9vdC1jYS5jcnQiCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJjb25maWdNYXAiOiB7CiAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjkyLAogICAgICAgICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLXdvcmtsb2FkLXJlZ2lzdHJ5IgogICAgICAgICAgICB9LAogICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJuYW1lIjogInNpdHRpbmctcmVxdWVzdGVyIiwKICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAiZGVmYXVsdE1vZGUiOiAyODgsCiAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAia2V5IjogImNsaWVudC1zZWNyZXQiLAogICAgICAgICAgICAgICAgICAicGF0aCI6ICJjbGllbnQtc2VjcmV0IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgIm9wdGlvbmFsIjogZmFsc2UsCiAgICAgICAgICAgICAgInNlY3JldE5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tc2l0dGluZy1yZXF1ZXN0ZXIiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICBdCiAgICAgIH0KICAgIH0KICB9Cn0K" - }, - { - "digest": "sha256:042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369", - "media_type": "application/json", - "content_base64": "ewogICJjb25maWdtYXAiOiB7CiAgICAiYXBpVmVyc2lvbiI6ICJ2MSIsCiAgICAiaW1tdXRhYmxlIjogdHJ1ZSwKICAgICJraW5kIjogIkNvbmZpZ01hcCIsCiAgICAibWV0YWRhdGEiOiB7CiAgICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgICAiYXJnb2NkLmFyZ29wcm9qLmlvL3RyYWNraW5nLWlkIjogImluZm9ybWVkLWRlY2lzaW9uOi9Db25maWdNYXA6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiLAogICAgICAgICJpbmZvcm1lZC1kZWNpc2lvbi5jb3Vsb21iLnNvY2lhbC9hZG1pc3Npb24iOiAiYXBwcm92ZWQ7IElORkQtV1AtMDAwNyBjdXRvdmVyIgogICAgICB9LAogICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1ydW50aW1lLTZjMjIxODc4ZDA1MSIsCiAgICAgICJuYW1lc3BhY2UiOiAiaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAidWlkIjogIjkwMTEzZDJkLTU0ZDktNGU4Mi1iMWQzLTkwYjQ5ZWJlZDJiNSIKICAgIH0sCiAgICAiY2Fub25pY2FsX2RhdGFfc2hhMjU2IjogIjhlYzQ2MmQxMjM0NzU0OGY1MDZlZWViZjFlM2M3Y2M0NzY0ODAxOWM4YTc4MzgwNGQwOWJjYzc5YjBmMjJhZjYiCiAgfSwKICAicHZjIjogewogICAgImFwaVZlcnNpb24iOiAidjEiLAogICAgImtpbmQiOiAiUGVyc2lzdGVudFZvbHVtZUNsYWltIiwKICAgICJtZXRhZGF0YSI6IHsKICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICJhcmdvY2QuYXJnb3Byb2ouaW8vdHJhY2tpbmctaWQiOiAiaW5mb3JtZWQtZGVjaXNpb246L1BlcnNpc3RlbnRWb2x1bWVDbGFpbTppbmZvcm1lZC1kZWNpc2lvbi9pbmZvcm1lZC1kZWNpc2lvbi1kYXRhIiwKICAgICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIsCiAgICAgICAgInB2Lmt1YmVybmV0ZXMuaW8vYmluZC1jb21wbGV0ZWQiOiAieWVzIiwKICAgICAgICAicHYua3ViZXJuZXRlcy5pby9ib3VuZC1ieS1jb250cm9sbGVyIjogInllcyIsCiAgICAgICAgInZvbHVtZS5iZXRhLmt1YmVybmV0ZXMuaW8vc3RvcmFnZS1wcm92aXNpb25lciI6ICJyYW5jaGVyLmlvL2xvY2FsLXBhdGgiLAogICAgICAgICJ2b2x1bWUua3ViZXJuZXRlcy5pby9zZWxlY3RlZC1ub2RlIjogIjIzOS42Mi4yMDUuOTIuaG9zdC5zZWN1cmVzZXJ2ZXIubmV0IiwKICAgICAgICAidm9sdW1lLmt1YmVybmV0ZXMuaW8vc3RvcmFnZS1wcm92aXNpb25lciI6ICJyYW5jaGVyLmlvL2xvY2FsLXBhdGgiCiAgICAgIH0sCiAgICAgICJmaW5hbGl6ZXJzIjogWwogICAgICAgICJrdWJlcm5ldGVzLmlvL3B2Yy1wcm90ZWN0aW9uIgogICAgICBdLAogICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbi1kYXRhIiwKICAgICAgIm5hbWVzcGFjZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICJ1aWQiOiAiZDgwYjI1YjEtMTQxYS00NDFiLWIyMDUtOThmYTJmZDk4YjdjIgogICAgfSwKICAgICJzcGVjIjogewogICAgICAiYWNjZXNzTW9kZXMiOiBbCiAgICAgICAgIlJlYWRXcml0ZU9uY2UiCiAgICAgIF0sCiAgICAgICJyZXNvdXJjZXMiOiB7CiAgICAgICAgInJlcXVlc3RzIjogewogICAgICAgICAgInN0b3JhZ2UiOiAiMUdpIgogICAgICAgIH0KICAgICAgfSwKICAgICAgInN0b3JhZ2VDbGFzc05hbWUiOiAibG9jYWwtcGF0aCIsCiAgICAgICJ2b2x1bWVNb2RlIjogIkZpbGVzeXN0ZW0iLAogICAgICAidm9sdW1lTmFtZSI6ICJwdmMtZDgwYjI1YjEtMTQxYS00NDFiLWIyMDUtOThmYTJmZDk4YjdjIgogICAgfQogIH0sCiAgInB2Y19waGFzZSI6ICJCb3VuZCIKfQo=" - }, - { - "digest": "sha256:487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95", - "media_type": "application/json", - "content_base64": "ewogICJhcGlWZXJzaW9uIjogImFwcHMvdjEiLAogICJraW5kIjogIkRlcGxveW1lbnQiLAogICJtZXRhZGF0YSI6IHsKICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgImFyZ29jZC5hcmdvcHJvai5pby90cmFja2luZy1pZCI6ICJpbmZvcm1lZC1kZWNpc2lvbjphcHBzL0RlcGxveW1lbnQ6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIKICAgIH0sCiAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAibmFtZXNwYWNlIjogImluZm9ybWVkLWRlY2lzaW9uIiwKICAgICJ1aWQiOiAiYTU4OGUxMGEtOTBkMi00ODgzLTk3NWUtNmRkOGI4ZmVjYTkyIgogIH0sCiAgInNwZWMiOiB7CiAgICAicHJvZ3Jlc3NEZWFkbGluZVNlY29uZHMiOiA2MDAsCiAgICAicmVwbGljYXMiOiAxLAogICAgInJldmlzaW9uSGlzdG9yeUxpbWl0IjogMTAsCiAgICAic2VsZWN0b3IiOiB7CiAgICAgICJtYXRjaExhYmVscyI6IHsKICAgICAgICAiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIKICAgICAgfQogICAgfSwKICAgICJzdHJhdGVneSI6IHsKICAgICAgInR5cGUiOiAiUmVjcmVhdGUiCiAgICB9LAogICAgInRlbXBsYXRlIjogewogICAgICAibWV0YWRhdGEiOiB7CiAgICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICAgImluZm9ybWVkLWRlY2lzaW9uLmNvdWxvbWIuc29jaWFsL2FkbWlzc2lvbiI6ICJhcHByb3ZlZDsgSU5GRC1XUC0wMDA3IGN1dG92ZXIiCiAgICAgICAgfSwKICAgICAgICAibGFiZWxzIjogewogICAgICAgICAgImFwcC5rdWJlcm5ldGVzLmlvL2NvbXBvbmVudCI6ICJyZXZpZXciLAogICAgICAgICAgImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24iCiAgICAgICAgfQogICAgICB9LAogICAgICAic3BlYyI6IHsKICAgICAgICAiYXV0b21vdW50U2VydmljZUFjY291bnRUb2tlbiI6IGZhbHNlLAogICAgICAgICJjb250YWluZXJzIjogWwogICAgICAgICAgewogICAgICAgICAgICAiZW52IjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJuYW1lIjogIklORkRfS0VZQ0FQRV9JU1NVRVIiLAogICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8va2MuY291bG9tYi5zb2NpYWwiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0NPTlRBSU5FUl9DT05GSUciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIi9jb25maWd1cmF0aW9uL3J1bnRpbWUuanNvbiIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJpbWFnZSI6ICJmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2OmY1NTg4ZjQ5NmIxZjI2N2QwN2NkYzlmNTIwOGU2ZjJmMjQxZjc0YjczOWMyMzkyNGIwMzcwMmRlYzEzYTVjMjMiLAogICAgICAgICAgICAiaW1hZ2VQdWxsUG9saWN5IjogIklmTm90UHJlc2VudCIsCiAgICAgICAgICAgICJsaXZlbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICJmYWlsdXJlVGhyZXNob2xkIjogMywKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiY29udGFpbmVyUG9ydCI6IDgwODAsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJodHRwIiwKICAgICAgICAgICAgICAgICJwcm90b2NvbCI6ICJUQ1AiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiY29udGFpbmVyUG9ydCI6IDgwODEsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZCIsCiAgICAgICAgICAgICAgICAicHJvdG9jb2wiOiAiVENQIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgInJlYWRpbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICJmYWlsdXJlVGhyZXNob2xkIjogMywKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAicmVzb3VyY2VzIjogewogICAgICAgICAgICAgICJsaW1pdHMiOiB7CiAgICAgICAgICAgICAgICAiY3B1IjogIjUwMG0iLAogICAgICAgICAgICAgICAgIm1lbW9yeSI6ICIyNTZNaSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJyZXF1ZXN0cyI6IHsKICAgICAgICAgICAgICAgICJjcHUiOiAiNW0iLAogICAgICAgICAgICAgICAgIm1lbW9yeSI6ICI2NE1pIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgICAiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjogZmFsc2UsCiAgICAgICAgICAgICAgImNhcGFiaWxpdGllcyI6IHsKICAgICAgICAgICAgICAgICJkcm9wIjogWwogICAgICAgICAgICAgICAgICAiQUxMIgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInJlYWRPbmx5Um9vdEZpbGVzeXN0ZW0iOiB0cnVlCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJzdGFydHVwUHJvYmUiOiB7CiAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAxMiwKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAidGVybWluYXRpb25NZXNzYWdlUGF0aCI6ICIvZGV2L3Rlcm1pbmF0aW9uLWxvZyIsCiAgICAgICAgICAgICJ0ZXJtaW5hdGlvbk1lc3NhZ2VQb2xpY3kiOiAiRmlsZSIsCiAgICAgICAgICAgICJ2b2x1bWVNb3VudHMiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvZGF0YSIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJkYXRhIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvcnVuL2luZm9ybWVkLWRlY2lzaW9uIiwKICAgICAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi90bXAiLAogICAgICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvY29uZmlndXJhdGlvbiIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJjb25maWd1cmF0aW9uIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9wb2xpY3kiLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9hdWRpdCIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJhdWRpdCIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24va3ViZXJuZXRlcyIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi93b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24vcmVxdWVzdGVyIiwKICAgICAgICAgICAgICAgICJuYW1lIjogInNpdHRpbmctcmVxdWVzdGVyIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0KICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJkbnNQb2xpY3kiOiAiQ2x1c3RlckZpcnN0IiwKICAgICAgICAicmVzdGFydFBvbGljeSI6ICJBbHdheXMiLAogICAgICAgICJzY2hlZHVsZXJOYW1lIjogImRlZmF1bHQtc2NoZWR1bGVyIiwKICAgICAgICAic2VjdXJpdHlDb250ZXh0IjogewogICAgICAgICAgImZzR3JvdXAiOiAxMDAwMSwKICAgICAgICAgICJmc0dyb3VwQ2hhbmdlUG9saWN5IjogIk9uUm9vdE1pc21hdGNoIiwKICAgICAgICAgICJydW5Bc0dyb3VwIjogMTAwMDEsCiAgICAgICAgICAicnVuQXNOb25Sb290IjogdHJ1ZSwKICAgICAgICAgICJydW5Bc1VzZXIiOiAxMDAwMSwKICAgICAgICAgICJzZWNjb21wUHJvZmlsZSI6IHsKICAgICAgICAgICAgInR5cGUiOiAiUnVudGltZURlZmF1bHQiCiAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAic2VydmljZUFjY291bnQiOiAicmV2aWV3IiwKICAgICAgICAic2VydmljZUFjY291bnROYW1lIjogInJldmlldyIsCiAgICAgICAgInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjogNDUsCiAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJuYW1lIjogImRhdGEiLAogICAgICAgICAgICAicGVyc2lzdGVudFZvbHVtZUNsYWltIjogewogICAgICAgICAgICAgICJjbGFpbU5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tZGF0YSIKICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImVtcHR5RGlyIjogewogICAgICAgICAgICAgICJtZWRpdW0iOiAiTWVtb3J5IiwKICAgICAgICAgICAgICAic2l6ZUxpbWl0IjogIjE2TWkiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiZW1wdHlEaXIiOiB7CiAgICAgICAgICAgICAgIm1lZGl1bSI6ICJNZW1vcnkiLAogICAgICAgICAgICAgICJzaXplTGltaXQiOiAiMTZNaSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImNvbmZpZ01hcCI6IHsKICAgICAgICAgICAgICAiZGVmYXVsdE1vZGUiOiAyOTIsCiAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJuYW1lIjogImNvbmZpZ3VyYXRpb24iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibmFtZSI6ICJjYWxsZXIiLAogICAgICAgICAgICAicHJvamVjdGVkIjogewogICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAic291cmNlcyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInNlcnZpY2VBY2NvdW50VG9rZW4iOiB7CiAgICAgICAgICAgICAgICAgICAgImF1ZGllbmNlIjogImZsZXgtYXV0aCIsCiAgICAgICAgICAgICAgICAgICAgImV4cGlyYXRpb25TZWNvbmRzIjogMzYwMCwKICAgICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgIm5hbWUiOiAiYXVkaXQiLAogICAgICAgICAgICAic2VjcmV0IjogewogICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAiaXRlbXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJrZXkiOiAidG9rZW4iLAogICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICJzZWNyZXROYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLWF1ZGl0IgogICAgICAgICAgICB9CiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICJwcm9qZWN0ZWQiOiB7CiAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICJzb3VyY2VzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAic2VydmljZUFjY291bnRUb2tlbiI6IHsKICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvblNlY29uZHMiOiA2MDAsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAidG9rZW4iCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJjb25maWdNYXAiOiB7CiAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAia2V5IjogImNhLmNydCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogImNhLmNydCIKICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmUtcm9vdC1jYS5jcnQiCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJjb25maWdNYXAiOiB7CiAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjkyLAogICAgICAgICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLXdvcmtsb2FkLXJlZ2lzdHJ5IgogICAgICAgICAgICB9LAogICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJuYW1lIjogInNpdHRpbmctcmVxdWVzdGVyIiwKICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAiZGVmYXVsdE1vZGUiOiAyODgsCiAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAia2V5IjogImNsaWVudC1zZWNyZXQiLAogICAgICAgICAgICAgICAgICAicGF0aCI6ICJjbGllbnQtc2VjcmV0IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgIm9wdGlvbmFsIjogZmFsc2UsCiAgICAgICAgICAgICAgInNlY3JldE5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tc2l0dGluZy1yZXF1ZXN0ZXIiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICBdCiAgICAgIH0KICAgIH0KICB9Cn0K" - }, - { - "digest": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "media_type": "application/json", - "content_base64": "ewogICJhcGlWZXJzaW9uIjogImFwcHMvdjEiLAogICJraW5kIjogIkRlcGxveW1lbnQiLAogICJtZXRhZGF0YSI6IHsKICAgICJhbm5vdGF0aW9ucyI6IHsKICAgICAgImFyZ29jZC5hcmdvcHJvai5pby90cmFja2luZy1pZCI6ICJpbmZvcm1lZC1kZWNpc2lvbjphcHBzL0RlcGxveW1lbnQ6aW5mb3JtZWQtZGVjaXNpb24vaW5mb3JtZWQtZGVjaXNpb24iLAogICAgICAiaW5mb3JtZWQtZGVjaXNpb24uY291bG9tYi5zb2NpYWwvYWRtaXNzaW9uIjogImFwcHJvdmVkOyBJTkZELVdQLTAwMDcgY3V0b3ZlciIKICAgIH0sCiAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAibmFtZXNwYWNlIjogImluZm9ybWVkLWRlY2lzaW9uIiwKICAgICJ1aWQiOiAiYTU4OGUxMGEtOTBkMi00ODgzLTk3NWUtNmRkOGI4ZmVjYTkyIgogIH0sCiAgInNwZWMiOiB7CiAgICAicHJvZ3Jlc3NEZWFkbGluZVNlY29uZHMiOiA2MDAsCiAgICAicmVwbGljYXMiOiAxLAogICAgInJldmlzaW9uSGlzdG9yeUxpbWl0IjogMTAsCiAgICAic2VsZWN0b3IiOiB7CiAgICAgICJtYXRjaExhYmVscyI6IHsKICAgICAgICAiYXBwLmt1YmVybmV0ZXMuaW8vbmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIKICAgICAgfQogICAgfSwKICAgICJzdHJhdGVneSI6IHsKICAgICAgInR5cGUiOiAiUmVjcmVhdGUiCiAgICB9LAogICAgInRlbXBsYXRlIjogewogICAgICAibWV0YWRhdGEiOiB7CiAgICAgICAgImFubm90YXRpb25zIjogewogICAgICAgICAgImluZm9ybWVkLWRlY2lzaW9uLmNvdWxvbWIuc29jaWFsL2FkbWlzc2lvbiI6ICJhcHByb3ZlZDsgSU5GRC1XUC0wMDA3IGN1dG92ZXIiCiAgICAgICAgfSwKICAgICAgICAibGFiZWxzIjogewogICAgICAgICAgImFwcC5rdWJlcm5ldGVzLmlvL2NvbXBvbmVudCI6ICJyZXZpZXciLAogICAgICAgICAgImFwcC5rdWJlcm5ldGVzLmlvL25hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24iCiAgICAgICAgfQogICAgICB9LAogICAgICAic3BlYyI6IHsKICAgICAgICAiYXV0b21vdW50U2VydmljZUFjY291bnRUb2tlbiI6IGZhbHNlLAogICAgICAgICJjb250YWluZXJzIjogWwogICAgICAgICAgewogICAgICAgICAgICAiZW52IjogWwogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJuYW1lIjogIklORkRfS0VZQ0FQRV9JU1NVRVIiLAogICAgICAgICAgICAgICAgInZhbHVlIjogImh0dHBzOi8va2MuY291bG9tYi5zb2NpYWwiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibmFtZSI6ICJJTkZEX0NPTlRBSU5FUl9DT05GSUciLAogICAgICAgICAgICAgICAgInZhbHVlIjogIi9jb25maWd1cmF0aW9uL3J1bnRpbWUuanNvbiIKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0sCiAgICAgICAgICAgICJpbWFnZSI6ICJmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2Ojc2M2MzNjQwMmM4YmU3Y2M0YzFiZDFhNjE2MjNkMWFmZjkxMTZkNTIyNDE4Nzc0MjViNDVhNzAxOGNjMDg4M2EiLAogICAgICAgICAgICAiaW1hZ2VQdWxsUG9saWN5IjogIklmTm90UHJlc2VudCIsCiAgICAgICAgICAgICJsaXZlbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICJmYWlsdXJlVGhyZXNob2xkIjogMywKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAibmFtZSI6ICJpbmZvcm1lZC1kZWNpc2lvbiIsCiAgICAgICAgICAgICJwb3J0cyI6IFsKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiY29udGFpbmVyUG9ydCI6IDgwODAsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJodHRwIiwKICAgICAgICAgICAgICAgICJwcm90b2NvbCI6ICJUQ1AiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAiY29udGFpbmVyUG9ydCI6IDgwODEsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZCIsCiAgICAgICAgICAgICAgICAicHJvdG9jb2wiOiAiVENQIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgXSwKICAgICAgICAgICAgInJlYWRpbmVzc1Byb2JlIjogewogICAgICAgICAgICAgICJmYWlsdXJlVGhyZXNob2xkIjogMywKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAicmVzb3VyY2VzIjogewogICAgICAgICAgICAgICJsaW1pdHMiOiB7CiAgICAgICAgICAgICAgICAiY3B1IjogIjUwMG0iLAogICAgICAgICAgICAgICAgIm1lbW9yeSI6ICIyNTZNaSIKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgICJyZXF1ZXN0cyI6IHsKICAgICAgICAgICAgICAgICJjcHUiOiAiNW0iLAogICAgICAgICAgICAgICAgIm1lbW9yeSI6ICI2NE1pIgogICAgICAgICAgICAgIH0KICAgICAgICAgICAgfSwKICAgICAgICAgICAgInNlY3VyaXR5Q29udGV4dCI6IHsKICAgICAgICAgICAgICAiYWxsb3dQcml2aWxlZ2VFc2NhbGF0aW9uIjogZmFsc2UsCiAgICAgICAgICAgICAgImNhcGFiaWxpdGllcyI6IHsKICAgICAgICAgICAgICAgICJkcm9wIjogWwogICAgICAgICAgICAgICAgICAiQUxMIgogICAgICAgICAgICAgICAgXQogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInJlYWRPbmx5Um9vdEZpbGVzeXN0ZW0iOiB0cnVlCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJzdGFydHVwUHJvYmUiOiB7CiAgICAgICAgICAgICAgImZhaWx1cmVUaHJlc2hvbGQiOiAxMiwKICAgICAgICAgICAgICAiaHR0cEdldCI6IHsKICAgICAgICAgICAgICAgICJwYXRoIjogIi9oZWFsdGh6IiwKICAgICAgICAgICAgICAgICJwb3J0IjogImh0dHAiLAogICAgICAgICAgICAgICAgInNjaGVtZSI6ICJIVFRQIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgInBlcmlvZFNlY29uZHMiOiAxMCwKICAgICAgICAgICAgICAic3VjY2Vzc1RocmVzaG9sZCI6IDEsCiAgICAgICAgICAgICAgInRpbWVvdXRTZWNvbmRzIjogMgogICAgICAgICAgICB9LAogICAgICAgICAgICAidGVybWluYXRpb25NZXNzYWdlUGF0aCI6ICIvZGV2L3Rlcm1pbmF0aW9uLWxvZyIsCiAgICAgICAgICAgICJ0ZXJtaW5hdGlvbk1lc3NhZ2VQb2xpY3kiOiAiRmlsZSIsCiAgICAgICAgICAgICJ2b2x1bWVNb3VudHMiOiBbCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvZGF0YSIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJkYXRhIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvcnVuL2luZm9ybWVkLWRlY2lzaW9uIiwKICAgICAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi90bXAiLAogICAgICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgIm1vdW50UGF0aCI6ICIvY29uZmlndXJhdGlvbiIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJjb25maWd1cmF0aW9uIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9wb2xpY3kiLAogICAgICAgICAgICAgICAgIm5hbWUiOiAiY2FsbGVyIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9LAogICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICJtb3VudFBhdGgiOiAiL3Zhci9ydW4vc2VjcmV0cy9pbmZvcm1lZC1kZWNpc2lvbi9hdWRpdCIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJhdWRpdCIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24va3ViZXJuZXRlcyIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi93b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIsCiAgICAgICAgICAgICAgICAicmVhZE9ubHkiOiB0cnVlCiAgICAgICAgICAgICAgfSwKICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAibW91bnRQYXRoIjogIi92YXIvcnVuL3NlY3JldHMvaW5mb3JtZWQtZGVjaXNpb24vcmVxdWVzdGVyIiwKICAgICAgICAgICAgICAgICJuYW1lIjogInNpdHRpbmctcmVxdWVzdGVyIiwKICAgICAgICAgICAgICAgICJyZWFkT25seSI6IHRydWUKICAgICAgICAgICAgICB9CiAgICAgICAgICAgIF0KICAgICAgICAgIH0KICAgICAgICBdLAogICAgICAgICJkbnNQb2xpY3kiOiAiQ2x1c3RlckZpcnN0IiwKICAgICAgICAicmVzdGFydFBvbGljeSI6ICJBbHdheXMiLAogICAgICAgICJzY2hlZHVsZXJOYW1lIjogImRlZmF1bHQtc2NoZWR1bGVyIiwKICAgICAgICAic2VjdXJpdHlDb250ZXh0IjogewogICAgICAgICAgImZzR3JvdXAiOiAxMDAwMSwKICAgICAgICAgICJmc0dyb3VwQ2hhbmdlUG9saWN5IjogIk9uUm9vdE1pc21hdGNoIiwKICAgICAgICAgICJydW5Bc0dyb3VwIjogMTAwMDEsCiAgICAgICAgICAicnVuQXNOb25Sb290IjogdHJ1ZSwKICAgICAgICAgICJydW5Bc1VzZXIiOiAxMDAwMSwKICAgICAgICAgICJzZWNjb21wUHJvZmlsZSI6IHsKICAgICAgICAgICAgInR5cGUiOiAiUnVudGltZURlZmF1bHQiCiAgICAgICAgICB9CiAgICAgICAgfSwKICAgICAgICAic2VydmljZUFjY291bnQiOiAicmV2aWV3IiwKICAgICAgICAic2VydmljZUFjY291bnROYW1lIjogInJldmlldyIsCiAgICAgICAgInRlcm1pbmF0aW9uR3JhY2VQZXJpb2RTZWNvbmRzIjogNDUsCiAgICAgICAgInZvbHVtZXMiOiBbCiAgICAgICAgICB7CiAgICAgICAgICAgICJuYW1lIjogImRhdGEiLAogICAgICAgICAgICAicGVyc2lzdGVudFZvbHVtZUNsYWltIjogewogICAgICAgICAgICAgICJjbGFpbU5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tZGF0YSIKICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImVtcHR5RGlyIjogewogICAgICAgICAgICAgICJtZWRpdW0iOiAiTWVtb3J5IiwKICAgICAgICAgICAgICAic2l6ZUxpbWl0IjogIjE2TWkiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJuYW1lIjogInJ1bnRpbWUiCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAiZW1wdHlEaXIiOiB7CiAgICAgICAgICAgICAgIm1lZGl1bSI6ICJNZW1vcnkiLAogICAgICAgICAgICAgICJzaXplTGltaXQiOiAiMTZNaSIKICAgICAgICAgICAgfSwKICAgICAgICAgICAgIm5hbWUiOiAidG1wIgogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgImNvbmZpZ01hcCI6IHsKICAgICAgICAgICAgICAiZGVmYXVsdE1vZGUiOiAyOTIsCiAgICAgICAgICAgICAgIm5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tcnVudGltZS02YzIyMTg3OGQwNTEiCiAgICAgICAgICAgIH0sCiAgICAgICAgICAgICJuYW1lIjogImNvbmZpZ3VyYXRpb24iCiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibmFtZSI6ICJjYWxsZXIiLAogICAgICAgICAgICAicHJvamVjdGVkIjogewogICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAic291cmNlcyI6IFsKICAgICAgICAgICAgICAgIHsKICAgICAgICAgICAgICAgICAgInNlcnZpY2VBY2NvdW50VG9rZW4iOiB7CiAgICAgICAgICAgICAgICAgICAgImF1ZGllbmNlIjogImZsZXgtYXV0aCIsCiAgICAgICAgICAgICAgICAgICAgImV4cGlyYXRpb25TZWNvbmRzIjogMzYwMCwKICAgICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0KICAgICAgICAgICAgfQogICAgICAgICAgfSwKICAgICAgICAgIHsKICAgICAgICAgICAgIm5hbWUiOiAiYXVkaXQiLAogICAgICAgICAgICAic2VjcmV0IjogewogICAgICAgICAgICAgICJkZWZhdWx0TW9kZSI6IDI4OCwKICAgICAgICAgICAgICAiaXRlbXMiOiBbCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJrZXkiOiAidG9rZW4iLAogICAgICAgICAgICAgICAgICAicGF0aCI6ICJ0b2tlbiIKICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICJzZWNyZXROYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLWF1ZGl0IgogICAgICAgICAgICB9CiAgICAgICAgICB9LAogICAgICAgICAgewogICAgICAgICAgICAibmFtZSI6ICJrdWJlcm5ldGVzLWFwaSIsCiAgICAgICAgICAgICJwcm9qZWN0ZWQiOiB7CiAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjg4LAogICAgICAgICAgICAgICJzb3VyY2VzIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAic2VydmljZUFjY291bnRUb2tlbiI6IHsKICAgICAgICAgICAgICAgICAgICAiZXhwaXJhdGlvblNlY29uZHMiOiA2MDAsCiAgICAgICAgICAgICAgICAgICAgInBhdGgiOiAidG9rZW4iCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0sCiAgICAgICAgICAgICAgICB7CiAgICAgICAgICAgICAgICAgICJjb25maWdNYXAiOiB7CiAgICAgICAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAgICAgICAia2V5IjogImNhLmNydCIsCiAgICAgICAgICAgICAgICAgICAgICAgICJwYXRoIjogImNhLmNydCIKICAgICAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgICAgICBdLAogICAgICAgICAgICAgICAgICAgICJuYW1lIjogImt1YmUtcm9vdC1jYS5jcnQiCiAgICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICAgIH0KICAgICAgICAgICAgICBdCiAgICAgICAgICAgIH0KICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJjb25maWdNYXAiOiB7CiAgICAgICAgICAgICAgImRlZmF1bHRNb2RlIjogMjkyLAogICAgICAgICAgICAgICJuYW1lIjogImluZm9ybWVkLWRlY2lzaW9uLXdvcmtsb2FkLXJlZ2lzdHJ5IgogICAgICAgICAgICB9LAogICAgICAgICAgICAibmFtZSI6ICJ3b3JrbG9hZC1yZWdpc3RyeSIKICAgICAgICAgIH0sCiAgICAgICAgICB7CiAgICAgICAgICAgICJuYW1lIjogInNpdHRpbmctcmVxdWVzdGVyIiwKICAgICAgICAgICAgInNlY3JldCI6IHsKICAgICAgICAgICAgICAiZGVmYXVsdE1vZGUiOiAyODgsCiAgICAgICAgICAgICAgIml0ZW1zIjogWwogICAgICAgICAgICAgICAgewogICAgICAgICAgICAgICAgICAia2V5IjogImNsaWVudC1zZWNyZXQiLAogICAgICAgICAgICAgICAgICAicGF0aCI6ICJjbGllbnQtc2VjcmV0IgogICAgICAgICAgICAgICAgfQogICAgICAgICAgICAgIF0sCiAgICAgICAgICAgICAgIm9wdGlvbmFsIjogZmFsc2UsCiAgICAgICAgICAgICAgInNlY3JldE5hbWUiOiAiaW5mb3JtZWQtZGVjaXNpb24tc2l0dGluZy1yZXF1ZXN0ZXIiCiAgICAgICAgICAgIH0KICAgICAgICAgIH0KICAgICAgICBdCiAgICAgIH0KICAgIH0KICB9Cn0K" - }, - { - "digest": "sha256:09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9", - "media_type": "application/json", - "content_base64": "ewogICJraW5kIjogImluZm9ybWVkLWRlY2lzaW9uLnN1cGVydmlzZWQtZXhlY3V0aW9uLXNvdXJjZS1idW5kbGUiLAogICJ2ZXJzaW9uIjogMSwKICAicHJvcG9zYWxfaWQiOiAiSU5GRC1XUC0wMDA5LVQwNS96bGliODUwOTEtcjEiLAogICJmaWxlcyI6IHsKICAgICJleGVjdXRpb24vYWN0dWF0b3IucHkiOiB7CiAgICAgICJzaGEyNTYiOiAiZGFiZWRhMzNmZmU1Mjk4OTMzN2UyMTRkYzZlNmQzZWY1ZjA0Nzk3NTY5MTQ1ZmFmMmZmNjkwMjRiMTAxZWM4MCIsCiAgICAgICJjb250ZW50IjogIlwiXCJcIk9uZSBmaXhlZCBzdXBlcnZpc2VkIHBoYXNlOyBuZXZlciBydW4gdGhpcyBkdXJpbmcgc291cmNlIHByZXBhcmF0aW9uLlwiXCJcIlxuaW1wb3J0IGFyZ3BhcnNlXG5pbXBvcnQgaGFzaGxpYlxuaW1wb3J0IGpzb25cbmltcG9ydCBvc1xuZnJvbSBwYXRobGliIGltcG9ydCBQYXRoXG5pbXBvcnQgcmVcbmltcG9ydCBzZWxlY3RvcnNcbmltcG9ydCBzdGF0XG5pbXBvcnQgc3VicHJvY2Vzc1xuaW1wb3J0IHN5c1xuaW1wb3J0IHRpbWVcblxuaWYgc3lzLmZsYWdzLm9wdGltaXplOlxuICAgIHJhaXNlIFN5c3RlbUV4aXQoJ29wdGltaXplZCBpbnRlcnByZXRlciByZWZ1c2VkJylcblxuUEhBU0VTID0gKCdkcmlsbC1iYWNrdXAnLCAnZHJpbGwtY29weScsICdvZmZsaW5lLXZlcmlmeScsICdjbGVhbnVwLW9mZmxpbmUnLFxuICAgICAgICAgICdjdXRvdmVyLWJhY2t1cCcsICdjdXRvdmVyLWNvcHknLCAncmVsZWFzZScsICd2ZXJpZnknLCAncm9sbGJhY2snLCAncm9sbGJhY2stdmVyaWZ5JylcblxuXG5kZWYgcmVxdWlyZSh2YWx1ZSwgcmVhc29uKTpcbiAgICBpZiBub3QgdmFsdWU6XG4gICAgICAgIHJhaXNlIFZhbHVlRXJyb3IocmVhc29uKVxuXG5cbmRlZiBzaGEocGF0aCk6XG4gICAgZGlnZXN0ID0gaGFzaGxpYi5zaGEyNTYoKVxuICAgIHdpdGggUGF0aChwYXRoKS5vcGVuKCdyYicpIGFzIHN0cmVhbTpcbiAgICAgICAgZm9yIGJsb2NrIGluIGl0ZXIobGFtYmRhOiBzdHJlYW0ucmVhZCgxMDI0KioyKSwgYicnKTpcbiAgICAgICAgICAgIGRpZ2VzdC51cGRhdGUoYmxvY2spXG4gICAgcmV0dXJuIGRpZ2VzdC5oZXhkaWdlc3QoKVxuXG5cbmRlZiBwcml2YXRlKHBhdGgsIGRpcmVjdG9yeT1GYWxzZSk6XG4gICAgaW5mbyA9IFBhdGgocGF0aCkubHN0YXQoKVxuICAgIHJlcXVpcmUoKHN0YXQuU19JU0RJUihpbmZvLnN0X21vZGUpIGlmIGRpcmVjdG9yeSBlbHNlIHN0YXQuU19JU1JFRyhpbmZvLnN0X21vZGUpKVxuICAgICAgICAgICAgYW5kIGluZm8uc3RfdWlkID09IG9zLmdldHVpZCgpIGFuZCBzdGF0LlNfSU1PREUoaW5mby5zdF9tb2RlKSA9PSAoMG83MDAgaWYgZGlyZWN0b3J5IGVsc2UgMG82MDApXG4gICAgICAgICAgICBhbmQgKGRpcmVjdG9yeSBvciBpbmZvLnN0X25saW5rID09IDEpLCAncHJpdmF0ZSBvd25lZCBjdXN0b2R5IHJlcXVpcmVkJylcblxuUk9PVCA9IFBhdGgoX19maWxlX18pLnJlc29sdmUoKS5wYXJlbnRcbkNBU0UgPSBST09ULnBhcmVudFxuT1dORVIgPSBQYXRoKCcvaG9tZS93b3JzY2gvaW5mb3JtZWQtZGVjaXNpb24nKVxuQ1VTVE9ESUFOID0gUGF0aCgnL2hvbWUvd29yc2NoL3RoZS1jdXN0b2RpYW4nKVxuQ1VTVE9EWSA9IENVU1RPRElBTiAvICcucHJpdmF0ZS1iYWNrdXBzLzIwMjYxMDExLWluZmQtemxpYi1yMSdcblNPQ0tFVCA9ICd1bml4Oi8vL3Zhci9ydW4vZG9ja2VyLnNvY2snXG5JTUFHRSA9ICdmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2OmY1NTg4ZjQ5NmIxZjI2N2QwN2NkYzlmNTIwOGU2ZjJmMjQxZjc0YjczOWMyMzkyNGIwMzcwMmRlYzEzYTVjMjMnXG5WT0xVTUUgPSAnaW5mZC16bGliLXIxLXJlc3RvcmUtMjAyNjEwMTEnXG5NQVhfQllURVMgPSA1MTIgKiAxMDI0KioyXG5cblxuZGVmIHJ1bihhcmd2LCBib2R5PU5vbmUsIHRpbWVvdXQ9OTApOlxuICAgIHJldHVybiBzdWJwcm9jZXNzLnJ1bihhcmd2LCBpbnB1dD1ib2R5LCB0ZXh0PVRydWUsIGNhcHR1cmVfb3V0cHV0PVRydWUsIHRpbWVvdXQ9dGltZW91dClcblxuXG5kZWYganNvbl9yZXN1bHQocmVzdWx0KTpcbiAgICB0cnk6XG4gICAgICAgIHZhbHVlID0ganNvbi5sb2FkcyhyZXN1bHQuc3Rkb3V0KVxuICAgIGV4Y2VwdCAoVmFsdWVFcnJvciwgVHlwZUVycm9yKTpcbiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcignc2FmZSBwaGFzZSByZWNlaXB0IHVuYXZhaWxhYmxlOyBvdXRjb21lIHVuY2VydGFpbicpIGZyb20gTm9uZVxuICAgIHJlcXVpcmUocmVzdWx0LnJldHVybmNvZGUgPT0gMCBhbmQgaXNpbnN0YW5jZSh2YWx1ZSwgZGljdCkgYW5kIHZhbHVlLmdldCgnYWxsX2NoZWNrc19wYXNzJykgaXMgVHJ1ZSxcbiAgICAgICAgICAgICdwaGFzZSBkaWQgbm90IHZlcmlmeSBjb21wbGV0ZTsgcmF3IG91dHB1dCB3aXRoaGVsZDsgbm8gcmV0cnknKVxuICAgIHBlcm1pdHRlZCA9IHsncGhhc2UnLCAnYWxsX2NoZWNrc19wYXNzJywgJ3Jhd19vdXRwdXRfd2l0aGhlbGQnLCAnbmF0aXZlX2FwcHJvdmFsX3ZlcmlmaWVkX2J5X3Jvb3QnLFxuICAgICAgICAgICAgICAgICAnbmF0aXZlX2F1dGhlbnRpY2F0ZWRfZ3JhbnQnLCAnc291cmNlX3JlYWRvbmx5X2Jhc2VsaW5lJywgJ3NuYXBzaG90X3JlYWRvbmx5JyxcbiAgICAgICAgICAgICAgICAgJ3NuYXBzaG90X3NoYTI1NicsICdzbmFwc2hvdF9ieXRlcycsICdzbmFwc2hvdF9maWxlJywgJ25hdGl2ZV9wYXJlbnRfcHJpdmF0ZScsXG4gICAgICAgICAgICAgICAgICduYXRpdmVfdWlkJywgJ3BvZF9uYW1lJywgJ3BvZF91aWQnLCAnbGl2ZV9yZWFkb25seV9zY2hlbWFfaW50ZWdyaXR5X2NvdW50cycsXG4gICAgICAgICAgICAgICAgICdsaXZlX2NvdW50c19tYXlfaW5jbHVkZV9jb25jdXJyZW50X2NvbW1pdHMnLCAnb3BlcmF0aW9uX3JlcXVlc3RlZCcsICdzb3VyY2VfcmV2aXNpb24nLFxuICAgICAgICAgICAgICAgICAncmVzb3VyY2VzJywgJ3BydW5lJywgJ3JvbGxvdXRfdmVyaWZpZWQnLCAnbGF0ZXN0X2xpdmVfZGF0YWJhc2VfcmV0YWluZWQnLFxuICAgICAgICAgICAgICAgICAnY2xpZW50X2RpZmZfc2hhMjU2JywgJ25hdGl2ZV9yb2xsb3V0X3ZlcmlmaWVkJywgJ2N1cnJlbnRfc2NoZW1hNV9kYXRhYmFzZV9yZXRhaW5lZCcsXG4gICAgICAgICAgICAgICAgICdwcml2YXRlX2NvcHlfdmVyaWZpZWQnLCAnaW5zdGFsbGVkX3B1cnBvc2VfY2hlY2tzJywgJ3BvbGljeV9zaGEyNTYnLFxuICAgICAgICAgICAgICAgICAncG9saWN5X21vZGUnLCAncG9saWN5X3VpZCcsICdpbnN0YWxsZWRfemxpYl9maWxlcycsICd6bGliX3BhY2thZ2UnLCAnaGVhbHRoX3N0YXR1c2VzJywgJ25hdGl2ZV9wcm92aWRlcl9wcm9iZScsICdyZWNvdmVyeV92ZXJpZmljYXRpb24nfVxuICAgIHJlcXVpcmUoc2V0KHZhbHVlKSA8PSBwZXJtaXR0ZWQsICd1bmV4cGVjdGVkIG91dHB1dCBmaWVsZHMgd2l0aGhlbGQnKVxuICAgIGZyb20gcmVjZWlwdF9zYWZldHkgaW1wb3J0IGNvdW50c1xuICAgIGZvciBuYW1lIGluICgnc291cmNlX3JlYWRvbmx5X2Jhc2VsaW5lJywgJ3NuYXBzaG90X3JlYWRvbmx5JywgJ2xpdmVfcmVhZG9ubHlfc2NoZW1hX2ludGVncml0eV9jb3VudHMnKTpcbiAgICAgICAgaWYgbmFtZSBpbiB2YWx1ZTpcbiAgICAgICAgICAgIGNvdW50cyh2YWx1ZVtuYW1lXSlcbiAgICBmb3IgbmFtZSBpbiAoJ3NuYXBzaG90X3NoYTI1NicsICdjbGllbnRfZGlmZl9zaGEyNTYnKTpcbiAgICAgICAgaWYgbmFtZSBpbiB2YWx1ZTpcbiAgICAgICAgICAgIHJlcXVpcmUocmUuZnVsbG1hdGNoKHInW2EtZjAtOV17NjR9JywgdmFsdWVbbmFtZV0pIGlzIG5vdCBOb25lLCAnaW52YWxpZCBvdXRwdXQgZGlnZXN0IHdpdGhoZWxkJylcbiAgICBpZiAnc25hcHNob3RfYnl0ZXMnIGluIHZhbHVlOlxuICAgICAgICByZXF1aXJlKHR5cGUodmFsdWVbJ3NuYXBzaG90X2J5dGVzJ10pIGlzIGludCBhbmQgMCA8IHZhbHVlWydzbmFwc2hvdF9ieXRlcyddIDw9IE1BWF9CWVRFUyxcbiAgICAgICAgICAgICAgICAnaW52YWxpZCBvdXRwdXQgc2l6ZSB3aXRoaGVsZCcpXG4gICAgaWYgJ3NuYXBzaG90X2ZpbGUnIGluIHZhbHVlOlxuICAgICAgICByZXF1aXJlKHZhbHVlWydzbmFwc2hvdF9maWxlJ10gaW4gKCdkcmlsbC5zcWxpdGUnLCAnY3V0b3Zlci5zcWxpdGUnKSwgJ2ludmFsaWQgb3V0cHV0IGZpbGUgd2l0aGhlbGQnKVxuICAgIHJldHVybiB2YWx1ZVxuXG5cbmRlZiBkb2NrZXIoYXJndiwgYm9keT1Ob25lLCB0aW1lb3V0PTEyMCk6XG4gICAgcmV0dXJuIHJ1bihbJ2RvY2tlcicsICctLWNvbmZpZycsIHN0cihDVVNUT0RZIC8gJ2VtcHR5LWRvY2tlci1jb25maWcnKSwgJy0taG9zdCcsIFNPQ0tFVF0gKyBhcmd2LFxuICAgICAgICAgICAgICAgYm9keT1ib2R5LCB0aW1lb3V0PXRpbWVvdXQpXG5cblxuZGVmIHJlbW90ZShwaGFzZSwgcmV2aXNpb24sIGFwcHJvdmVkKTpcbiAgICAjIEZpeGVkIG1vZHVsZSBpbXBvcnRlZCBvbmx5IGFmdGVyIG1haW4gdmVyaWZpZXMgZXZlcnkgZXhlY3V0b3Igc291cmNlIGhhc2guXG4gICAgZnJvbSBuYXRpdmVfcHJvZ3JhbSBpbXBvcnQgUFJPR1JBTVxuICAgIHBheWxvYWQgPSB7J3BoYXNlJzogcGhhc2UsICdzb3VyY2VfcmV2aXNpb24nOiByZXZpc2lvbiwgJ2FwcHJvdmFsJzogYXBwcm92ZWQsXG4gICAgICAgICAgICAgICAnc2VhbGVkJzoganNvbi5sb2FkcygoQ0FTRSAvICdzZWFsZWQtbGl2ZS1kZXBsb3ltZW50Lmpzb24nKS5yZWFkX3RleHQoKSksXG4gICAgICAgICAgICAgICAnZGVwZW5kZW5jaWVzJzoganNvbi5sb2FkcygoQ0FTRSAvICdzZWFsZWQtbmF0aXZlLWRlcGVuZGVuY2llcy5qc29uJykucmVhZF90ZXh0KCkpLFxuICAgICAgICAgICAgICAgJ2NhbmRpZGF0ZSc6IGpzb24ubG9hZHMoKENBU0UgLyAnY2FuZGlkYXRlLWRlcGxveW1lbnQuanNvbicpLnJlYWRfdGV4dCgpKSxcbiAgICAgICAgICAgICAgICdyb2xsYmFjayc6IGpzb24ubG9hZHMoKENBU0UgLyAncm9sbGJhY2stZGVwbG95bWVudC5qc29uJykucmVhZF90ZXh0KCkpLFxuICAgICAgICAgICAgICAgJ3JlY2VpcHRfc2FmZXR5JzogKFJPT1QgLyAncmVjZWlwdF9zYWZldHkucHknKS5yZWFkX3RleHQoKSxcbiAgICAgICAgICAgICAgICdwdXJwb3NlX2NoZWNrcyc6IChDQVNFIC8gJ2NoZWNrX2luc3RhbGxlZC5weScpLnJlYWRfdGV4dCgpLFxuICAgICAgICAgICAgICAgJ2luc3RhbGxlZF96bGliJzoganNvbi5sb2FkcygoQ0FTRSAvICdpbnN0YWxsZWQtemxpYi1wcm9vZi5qc29uJykucmVhZF90ZXh0KCkpLCAncmVhZG9ubHknOiAoQ0FTRSAvICdzcWxpdGVfcmVhZG9ubHkucHknKS5yZWFkX3RleHQoKX1cbiAgICBzY3JpcHQgPSBQUk9HUkFNLnJlcGxhY2UoJ19fUEFZTE9BRF9fJywgcmVwcihqc29uLmR1bXBzKHBheWxvYWQpKSkucmVwbGFjZShcbiAgICAgICAgJ19fR1VBUkRfXycsIHJlcHIoKENBU0UgLyAnY2hhbmdlX2d1YXJkLnB5JykucmVhZF90ZXh0KCkpKVxuICAgIHJldHVybiBqc29uX3Jlc3VsdChydW4oWydzc2gnLCAncmFpbGlhbmNlMDEnLCAncHl0aG9uMyAtJ10sIHNjcmlwdCwgdGltZW91dD0yMDApKVxuXG5cbmRlZiBjb3B5X3NuYXBzaG90KHBoYXNlLCByZXZpc2lvbiwgYXBwcm92ZWQsIGpvdXJuYWwpOlxuICAgIG1ldGFkYXRhID0gcmVtb3RlKHBoYXNlLCByZXZpc2lvbiwgYXBwcm92ZWQpXG4gICAgYmFja3VwX3BoYXNlID0gJ2RyaWxsLWJhY2t1cCcgaWYgcGhhc2UgPT0gJ2RyaWxsLWNvcHknIGVsc2UgJ2N1dG92ZXItYmFja3VwJ1xuICAgIHByaW9yID0gam91cm5hbC5kYXRhWydwaGFzZXMnXVtiYWNrdXBfcGhhc2VdWydyZXN1bHQnXVxuICAgIGZvciBrZXkgaW4gKCdzbmFwc2hvdF9zaGEyNTYnLCAnc25hcHNob3RfYnl0ZXMnLCAnc25hcHNob3RfZmlsZScsICdzbmFwc2hvdF9yZWFkb25seScpOlxuICAgICAgICByZXF1aXJlKG1ldGFkYXRhW2tleV0gPT0gcHJpb3Jba2V5XSwgJ2Nsb3NlZCBmcm96ZW4gc25hcHNob3QgY2hhbmdlZCcpXG4gICAgbmFtZSA9IG1ldGFkYXRhWydzbmFwc2hvdF9maWxlJ11cbiAgICByZXF1aXJlKG5hbWUgaW4gKCdkcmlsbC5zcWxpdGUnLCAnY3V0b3Zlci5zcWxpdGUnKSBhbmQgMCA8IG1ldGFkYXRhWydzbmFwc2hvdF9ieXRlcyddIDw9IE1BWF9CWVRFUyxcbiAgICAgICAgICAgICdmaXhlZCBib3VuZGVkIHNuYXBzaG90IHJlcXVpcmVkJylcbiAgICBwb2QgPSBtZXRhZGF0YVsncG9kX25hbWUnXVxuICAgIHJlcXVpcmUocmUuZnVsbG1hdGNoKHInW2EtejAtOV0oWy1hLXowLTldKlthLXowLTldKT8nLCBwb2QpIGlzIG5vdCBOb25lLCAnZml4ZWQgb3duZXIgcG9kIG5hbWUgcmVxdWlyZWQnKVxuICAgIGxvY2FsID0gQ1VTVE9EWSAvIG5hbWVcbiAgICBmZCA9IG9zLm9wZW4obG9jYWwsIG9zLk9fV1JPTkxZIHwgb3MuT19DUkVBVCB8IG9zLk9fRVhDTCB8IG9zLk9fTk9GT0xMT1csIDBvNjAwKVxuICAgICMgQmluYXJ5IHNuYXBzaG90IHN0ZG91dCBnb2VzIG9ubHkgdG8gYSBwcml2YXRlIGZpbGUsIG5ldmVyIHRvb2wvY2hhdC9sb2cgb3V0cHV0LlxuICAgICMgU25hcHNob3Qgc2NoZW1hL2NvdW50IG1ldGFkYXRhIHdhcyB2YWxpZGF0ZWQgYmVmb3JlIG9wZW5pbmcgdGhpcyBkZXN0aW5hdGlvbi5cbiAgICB3aXRoIG9zLmZkb3BlbihmZCwgJ3diJykgYXMgZGVzdGluYXRpb246XG4gICAgICAgIGNvbW1hbmQgPSAoJ2t1YmVjdGwgZXhlYyAtbiBpbmZvcm1lZC1kZWNpc2lvbiAnICsgcG9kICtcbiAgICAgICAgICAgICAgICAgICAnIC1jIGluZm9ybWVkLWRlY2lzaW9uIC0tIGNhdCAvZGF0YS9wcml2YXRlL3psaWI4NTA5MS1yMS8nICsgbmFtZSlcbiAgICAgICAgdHJhbnNmZXIgPSBzdWJwcm9jZXNzLlBvcGVuKFsnc3NoJywgJ3JhaWxpYW5jZTAxJywgY29tbWFuZF0sIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdGRlcnI9c3VicHJvY2Vzcy5ERVZOVUxMKVxuICAgICAgICBzZWxlY3RvciA9IHNlbGVjdG9ycy5EZWZhdWx0U2VsZWN0b3IoKVxuICAgICAgICBzZWxlY3Rvci5yZWdpc3Rlcih0cmFuc2Zlci5zdGRvdXQsIHNlbGVjdG9ycy5FVkVOVF9SRUFEKVxuICAgICAgICBkZWFkbGluZSA9IHRpbWUubW9ub3RvbmljKCkgKyAxNTBcbiAgICAgICAgcmVjZWl2ZWQgPSAwXG4gICAgICAgIHRyeTpcbiAgICAgICAgICAgIHdoaWxlIFRydWU6XG4gICAgICAgICAgICAgICAgcmVtYWluaW5nID0gZGVhZGxpbmUgLSB0aW1lLm1vbm90b25pYygpXG4gICAgICAgICAgICAgICAgcmVxdWlyZShyZW1haW5pbmcgPiAwIGFuZCBzZWxlY3Rvci5zZWxlY3QocmVtYWluaW5nKSwgJ3ByaXZhdGUgdHJhbnNmZXIgdGltZWQgb3V0JylcbiAgICAgICAgICAgICAgICBibG9jayA9IG9zLnJlYWQodHJhbnNmZXIuc3Rkb3V0LmZpbGVubygpLCAxMDI0KioyKVxuICAgICAgICAgICAgICAgIGlmIG5vdCBibG9jazpcbiAgICAgICAgICAgICAgICAgICAgYnJlYWtcbiAgICAgICAgICAgICAgICByZWNlaXZlZCArPSBsZW4oYmxvY2spXG4gICAgICAgICAgICAgICAgcmVxdWlyZShyZWNlaXZlZCA8PSBtZXRhZGF0YVsnc25hcHNob3RfYnl0ZXMnXSBhbmQgcmVjZWl2ZWQgPD0gTUFYX0JZVEVTLFxuICAgICAgICAgICAgICAgICAgICAgICAgJ3ByaXZhdGUgY29weSBleGNlZWRzIGZyb3plbiBzbmFwc2hvdCBzaXplJylcbiAgICAgICAgICAgICAgICBkZXN0aW5hdGlvbi53cml0ZShibG9jaylcbiAgICAgICAgICAgIHRyYW5zZmVyLndhaXQodGltZW91dD1tYXgoMSwgZGVhZGxpbmUgLSB0aW1lLm1vbm90b25pYygpKSlcbiAgICAgICAgZmluYWxseTpcbiAgICAgICAgICAgIHNlbGVjdG9yLmNsb3NlKClcbiAgICAgICAgICAgIHRyYW5zZmVyLnN0ZG91dC5jbG9zZSgpXG4gICAgICAgICAgICBpZiB0cmFuc2Zlci5wb2xsKCkgaXMgTm9uZTpcbiAgICAgICAgICAgICAgICB0cmFuc2Zlci5raWxsKClcbiAgICAgICAgICAgICAgICB0cmFuc2Zlci53YWl0KClcbiAgICAgICAgZGVzdGluYXRpb24uZmx1c2goKVxuICAgICAgICBvcy5mc3luYyhkZXN0aW5hdGlvbi5maWxlbm8oKSlcbiAgICBwcml2YXRlKGxvY2FsKVxuICAgIHJlcXVpcmUodHJhbnNmZXIucmV0dXJuY29kZSA9PSAwIGFuZCBsb2NhbC5zdGF0KCkuc3Rfc2l6ZSA9PSBtZXRhZGF0YVsnc25hcHNob3RfYnl0ZXMnXVxuICAgICAgICAgICAgYW5kIHNoYShsb2NhbCkgPT0gbWV0YWRhdGFbJ3NuYXBzaG90X3NoYTI1NiddLCAncHJpdmF0ZSBjb3B5IGluY29tcGxldGUvdW5jZXJ0YWluOyByZXRhaW4sIG5vIHJldHJ5JylcbiAgICAjIFJlLXJlYWQgaWRlbnRpdHkvaGFzaCBhZnRlciB0cmFuc2Zlci4gVGhpcyBpcyB2ZXJpZmljYXRpb24sIG5vdCBhIHdyaXRlIHJldHJ5LlxuICAgIGFmdGVyID0gcmVtb3RlKHBoYXNlLCByZXZpc2lvbiwgYXBwcm92ZWQpXG4gICAgcmVxdWlyZShhZnRlclsncG9kX3VpZCddID09IG1ldGFkYXRhWydwb2RfdWlkJ10gYW5kIGFmdGVyWydzbmFwc2hvdF9zaGEyNTYnXSA9PSBtZXRhZGF0YVsnc25hcHNob3Rfc2hhMjU2J10sXG4gICAgICAgICAgICAnc291cmNlIGlkZW50aXR5L3NuYXBzaG90IGNoYW5nZWQgZHVyaW5nIHRyYW5zZmVyJylcbiAgICByZXR1cm4geyoqbWV0YWRhdGEsICdvZmZfY2x1c3Rlcl9wcml2YXRlX2NvcHlfdmVyaWZpZWQnOiBUcnVlLCAnc25hcHNob3RfY29udGVudHNfb3V0cHV0JzogRmFsc2V9XG5cblxuZGVmIG9mZmxpbmVfdmVyaWZ5KGpvdXJuYWwpOlxuICAgIGZyb20gcmVjZWlwdF9zYWZldHkgaW1wb3J0IGNvdW50c1xuICAgIHNuYXBzaG90ID0gQ1VTVE9EWSAvICdkcmlsbC5zcWxpdGUnXG4gICAgcHJpdmF0ZShzbmFwc2hvdClcbiAgICBwcmV2aW91cyA9IGpvdXJuYWwuZGF0YVsncGhhc2VzJ11bJ2RyaWxsLWNvcHknXVsncmVzdWx0J11cbiAgICByZXF1aXJlKHNoYShzbmFwc2hvdCkgPT0gcHJldmlvdXNbJ3NuYXBzaG90X3NoYTI1NiddLCAncHJvdGVjdGVkIHNuYXBzaG90IGJ5dGVzIGNoYW5nZWQnKVxuICAgIGlkZW50aXR5ID0gZG9ja2VyKFsnaW5mbycsICctLWZvcm1hdCcsICd7e2pzb24gLk5hbWV9fSddKVxuICAgIHJlcXVpcmUoaWRlbnRpdHkucmV0dXJuY29kZSA9PSAwIGFuZCBqc29uLmxvYWRzKGlkZW50aXR5LnN0ZG91dCkgPT0gJ2JudC1sYXAwMDEnLFxuICAgICAgICAgICAgJ2xvY2FsIG9mZmxpbmUgRG9ja2VyIGVuZ2luZSBpZGVudGl0eSBjaGFuZ2VkJylcbiAgICBsaXN0ZWQgPSBkb2NrZXIoWyd2b2x1bWUnLCAnbHMnLCAnLS1mb3JtYXQnLCAne3suTmFtZX19J10pXG4gICAgcmVxdWlyZShsaXN0ZWQucmV0dXJuY29kZSA9PSAwIGFuZCBWT0xVTUUgbm90IGluIGxpc3RlZC5zdGRvdXQuc3BsaXRsaW5lcygpLFxuICAgICAgICAgICAgJ2Rpc3Bvc2FibGUgdm9sdW1lIGFic2VuY2Ugbm90IHZlcmlmaWVkJylcbiAgICBub25jZSA9IGpvdXJuYWwuZGF0YVsncGhhc2VzJ11bJ29mZmxpbmUtdmVyaWZ5J11bJ2F0dGVtcHRfaWQnXVxuICAgIGxhYmVscyA9IHsnaW5mb3JtZWQtZGVjaXNpb24uY2FzZSc6ICdpbmZkLXpsaWItMjAyNjEwMTEtcjEnLFxuICAgICAgICAgICAgICAnaW5mb3JtZWQtZGVjaXNpb24udXNlJzogJ29mZmxpbmUtcmVzdG9yZS1vbmx5JywgJ2luZm9ybWVkLWRlY2lzaW9uLmF0dGVtcHQnOiBub25jZX1cbiAgICBjcmVhdGVkID0gZG9ja2VyKFsndm9sdW1lJywgJ2NyZWF0ZScsICctLWRyaXZlcicsICdsb2NhbCcsICctLWxhYmVsJywgJ2luZm9ybWVkLWRlY2lzaW9uLmNhc2U9aW5mZC16bGliLTIwMjYxMDExLXIxJyxcbiAgICAgICAgICAgICAgICAgICAgICAnLS1sYWJlbCcsICdpbmZvcm1lZC1kZWNpc2lvbi5hdHRlbXB0PScgKyBub25jZSxcbiAgICAgICAgICAgICAgICAgICAgICAnLS1sYWJlbCcsICdpbmZvcm1lZC1kZWNpc2lvbi51c2U9b2ZmbGluZS1yZXN0b3JlLW9ubHknLCBWT0xVTUVdKVxuICAgIHJlcXVpcmUoY3JlYXRlZC5yZXR1cm5jb2RlID09IDAgYW5kIGNyZWF0ZWQuc3Rkb3V0LnN0cmlwKCkgPT0gVk9MVU1FLCAndm9sdW1lIGNyZWF0aW9uIHVuY2VydGFpbjsgbm8gcmV0cnknKVxuICAgIGluc3BlY3RlZCA9IGRvY2tlcihbJ3ZvbHVtZScsICdpbnNwZWN0JywgVk9MVU1FXSlcbiAgICByZXF1aXJlKGluc3BlY3RlZC5yZXR1cm5jb2RlID09IDAsICduZXcgdm9sdW1lIGlkZW50aXR5IHVuYXZhaWxhYmxlJylcbiAgICB2b2x1bWUgPSBqc29uLmxvYWRzKGluc3BlY3RlZC5zdGRvdXQpXG4gICAgcmVxdWlyZShsZW4odm9sdW1lKSA9PSAxIGFuZCB2b2x1bWVbMF1bJ05hbWUnXSA9PSBWT0xVTUUgYW5kIHZvbHVtZVswXS5nZXQoJ0xhYmVscycpID09IGxhYmVsc1xuICAgICAgICAgICAgYW5kIHZvbHVtZVswXS5nZXQoJ0RyaXZlcicpID09ICdsb2NhbCcgYW5kIG5vdCB2b2x1bWVbMF0uZ2V0KCdPcHRpb25zJylcbiAgICAgICAgICAgIGFuZCB2b2x1bWVbMF0uZ2V0KCdTY29wZScpID09ICdsb2NhbCcsICduZXcgaXNvbGF0ZWQgdm9sdW1lIGlkZW50aXR5IGNoYW5nZWQnKVxuICAgIGlkZW50aXR5ID0ge2tleTogdm9sdW1lWzBdW2tleV0gZm9yIGtleSBpbiAoJ05hbWUnLCAnTGFiZWxzJywgJ0RyaXZlcicsICdTY29wZScsICdDcmVhdGVkQXQnKX1cbiAgICBqb3VybmFsLm5vdGUoJ29mZmxpbmUtdmVyaWZ5JywgJ2NyZWF0ZWRfdm9sdW1lX2lkZW50aXR5JywgaWRlbnRpdHkpXG4gICAgIyBPbmUtdGltZSBVSUQwL0NBUF9DSE9XTiBpbml0aWFsaXphdGlvbiBpcyBpc29sYXRlZCB0byB0aGUgTkVXIG5hbWVkIHZvbHVtZS5cbiAgICAjIE5vIGhvc3QvbGl2ZSBQVkMvY29uZmlnL3Rva2VuIG1vdW50IGV4aXN0czsgdGhpcyBkZXRhaWwgbmVlZHMgcGFja2V0IHJldmlldy5cbiAgICBpbml0aWFsaXplciA9IFwiXCJcImltcG9ydCBoYXNobGliLG9zLHN0YXQsc3lzXG5pZiBzeXMuZmxhZ3Mub3B0aW1pemU6cmFpc2UgUnVudGltZUVycm9yKCdvcHRpbWl6ZWQgaW50ZXJwcmV0ZXIgcmVmdXNlZCcpXG5mb2xkZXI9Jy9yZXN0b3JlJztleHBlY3RlZD1FWFBFQ1RFRFxuaWYgb3MubGlzdGRpcihmb2xkZXIpOnJhaXNlIFZhbHVlRXJyb3IoJ25ldyBlbXB0eSBwcml2YXRlIHZvbHVtZSByZXF1aXJlZCcpXG5vcy5jaG1vZChmb2xkZXIsMG83MDApXG5mZD1vcy5vcGVuKGZvbGRlcisnL3Jldmlldy5zcWxpdGUnLG9zLk9fQ1JFQVR8b3MuT19FWENMfG9zLk9fTk9GT0xMT1d8b3MuT19XUk9OTFksMG82MDApXG5kaWdlc3Q9aGFzaGxpYi5zaGEyNTYoKTtzaXplPTBcbndpdGggb3MuZmRvcGVuKGZkLCd3YicpIGFzIHRhcmdldDpcbiB3aGlsZSBUcnVlOlxuICBibG9jaz1zeXMuc3RkaW4uYnVmZmVyLnJlYWQoMTAyNCoqMilcbiAgaWYgbm90IGJsb2NrOmJyZWFrXG4gIHNpemUrPWxlbihibG9jaylcbiAgaWYgc2l6ZT41MTIqMTAyNCoqMjpyYWlzZSBWYWx1ZUVycm9yKCdwcml2YXRlIHJlc3RvcmUgc2l6ZSBleGNlZWRzIGJvdW5kJylcbiAgdGFyZ2V0LndyaXRlKGJsb2NrKTtkaWdlc3QudXBkYXRlKGJsb2NrKVxuIHRhcmdldC5mbHVzaCgpO29zLmZzeW5jKHRhcmdldC5maWxlbm8oKSlcbmlmIGRpZ2VzdC5oZXhkaWdlc3QoKSE9ZXhwZWN0ZWQ6cmFpc2UgVmFsdWVFcnJvcigncHJpdmF0ZSBjb3BpZWQgc25hcHNob3QgZGlmZmVycycpXG5vcy5jaG93bihmb2xkZXIrJy9yZXZpZXcuc3FsaXRlJywxMDAwMSwxMDAwMSk7b3MuY2hvd24oZm9sZGVyLDEwMDAxLDEwMDAxKVxucHJpbnQoJ3tcImFsbF9jaGVja3NfcGFzc1wiOnRydWUsXCJwcml2YXRlX2NvcHlfdmVyaWZpZWRcIjp0cnVlfScpXG5cIlwiXCIucmVwbGFjZSgnRVhQRUNURUQnLCByZXByKHByZXZpb3VzWydzbmFwc2hvdF9zaGEyNTYnXSkpXG4gICAgd2l0aCBzbmFwc2hvdC5vcGVuKCdyYicpIGFzIHNvdXJjZTpcbiAgICAgICAgYXJndiA9IFsnZG9ja2VyJywgJy0tY29uZmlnJywgc3RyKENVU1RPRFkgLyAnZW1wdHktZG9ja2VyLWNvbmZpZycpLCAnLS1ob3N0JywgU09DS0VULFxuICAgICAgICAgICAgICAgICdydW4nLCAnLS1ybScsICctaScsICctLW5ldHdvcmsnLCAnbm9uZScsICctLXJlYWQtb25seScsICctLWNwdXMnLCAnMScsICctLW1lbW9yeScsICcxMjhtJyxcbiAgICAgICAgICAgICAgICAnLS11c2VyJywgJzA6MCcsICctLWNhcC1kcm9wJywgJ0FMTCcsICctLWNhcC1hZGQnLCAnQ0hPV04nLFxuICAgICAgICAgICAgICAgICctLXNlY3VyaXR5LW9wdCcsICduby1uZXctcHJpdmlsZWdlcycsICctLW1vdW50JywgJ3R5cGU9dm9sdW1lLHNyYz0nICsgVk9MVU1FICsgJyxkc3Q9L3Jlc3RvcmUnLFxuICAgICAgICAgICAgICAgICctLWVudHJ5cG9pbnQnLCAncHl0aG9uJywgSU1BR0UsICctYycsIGluaXRpYWxpemVyXVxuICAgICAgICBjb3BpZWQgPSBzdWJwcm9jZXNzLnJ1bihhcmd2LCBzdGRpbj1zb3VyY2UsIHRleHQ9VHJ1ZSwgY2FwdHVyZV9vdXRwdXQ9VHJ1ZSwgdGltZW91dD0xNTApXG4gICAganNvbl9yZXN1bHQoY29waWVkKVxuICAgIG9wdGlvbnMgPSBbJ3J1bicsICctLXJtJywgJy1pJywgJy0tbmV0d29yaycsICdub25lJywgJy0tcmVhZC1vbmx5JywgJy0tY3B1cycsICcxJywgJy0tbWVtb3J5JywgJzEyOG0nLFxuICAgICAgICAgICAgICAgJy0tdXNlcicsICcxMDAwMToxMDAwMScsICctLWNhcC1kcm9wJywgJ0FMTCcsICctLXNlY3VyaXR5LW9wdCcsICduby1uZXctcHJpdmlsZWdlcycsXG4gICAgICAgICAgICAgICAnLS1tb3VudCcsICd0eXBlPXZvbHVtZSxzcmM9JyArIFZPTFVNRSArICcsZHN0PS9yZXN0b3JlJ11cbiAgICByZWFkb25seSA9IGRvY2tlcihvcHRpb25zICsgWyctLWVudHJ5cG9pbnQnLCAncHl0aG9uJywgSU1BR0UsICctJywgJy0tZGInLCAnL3Jlc3RvcmUvcmV2aWV3LnNxbGl0ZSddLFxuICAgICAgICAgICAgICAgICAgICAgIChDQVNFIC8gJ3NxbGl0ZV9yZWFkb25seS5weScpLnJlYWRfdGV4dCgpKVxuICAgIHJlcXVpcmUocmVhZG9ubHkucmV0dXJuY29kZSA9PSAwLCAnb2ZmbGluZSBwcmUtb3BlbiBzY2hlbWEvaW50ZWdyaXR5IGdhdGUgcmVmdXNlZDsgbmV2ZXIgb3BlbiBTdG9yZScpXG4gICAgY2hlY2tlZCA9IGNvdW50cyhqc29uLmxvYWRzKHJlYWRvbmx5LnN0ZG91dCkpXG4gICAgcmVxdWlyZShjaGVja2VkID09IHByZXZpb3VzWydzbmFwc2hvdF9yZWFkb25seSddLCAnZnJvemVuIHNuYXBzaG90L2Nsb25lIHJlYWRvbmx5IGNvdW50cyBkaWZmZXInKVxuICAgIGFkbWluID0gZG9ja2VyKG9wdGlvbnMgKyBbJy0tZW50cnlwb2ludCcsICdpbmZvcm1lZC1kZWNpc2lvbi1hZG1pbicsIElNQUdFLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2luc3BlY3QnLCAnLS1kYicsICcvcmVzdG9yZS9yZXZpZXcuc3FsaXRlJ10pXG4gICAgcmVxdWlyZShhZG1pbi5yZXR1cm5jb2RlID09IDAsICdvZmZsaW5lIGFkbWluIHJlc3VsdCB1bmNlcnRhaW4nKVxuICAgIGFkbWluX2NvdW50cyA9IGpzb24ubG9hZHMoYWRtaW4uc3Rkb3V0KVxuICAgIHJlcXVpcmUoYWRtaW5fY291bnRzID09IHtrZXk6IGNoZWNrZWRba2V5XSBmb3Iga2V5IGluICgnc2NoZW1hX3ZlcnNpb24nLCAnbGlmZWN5Y2xlJywgJ291dGJveCcsICdzdWJtaXNzaW9ucycpfSxcbiAgICAgICAgICAgICdvZmZsaW5lIGFkbWluIGNoYW5nZWQgZnJvemVuIHNuYXBzaG90IGNvdW50ZXJzJylcbiAgICAjIEFkbWluIFN0b3JlIGNvbnN0cnVjdGlvbiBtdXN0IG5vdCBzaWxlbnRseSBhbHRlciBzY2hlbWEvaW50ZWdyaXR5L2NvdW50ZXJzLlxuICAgIHBvc3QgPSBkb2NrZXIob3B0aW9ucyArIFsnLS1lbnRyeXBvaW50JywgJ3B5dGhvbicsIElNQUdFLCAnLScsICctLWRiJywgJy9yZXN0b3JlL3Jldmlldy5zcWxpdGUnXSxcbiAgICAgICAgICAgICAgICAgIChDQVNFIC8gJ3NxbGl0ZV9yZWFkb25seS5weScpLnJlYWRfdGV4dCgpKVxuICAgIHJlcXVpcmUocG9zdC5yZXR1cm5jb2RlID09IDAsICdvZmZsaW5lIHBvc3QtYWRtaW4gcmVhZG9ubHkgaW50ZWdyaXR5IGdhdGUgcmVmdXNlZCcpXG4gICAgcG9zdF9jaGVja2VkID0gY291bnRzKGpzb24ubG9hZHMocG9zdC5zdGRvdXQpKVxuICAgIHJlcXVpcmUocG9zdF9jaGVja2VkID09IGNoZWNrZWQgPT0gcHJldmlvdXNbJ3NuYXBzaG90X3JlYWRvbmx5J10sXG4gICAgICAgICAgICAnb2ZmbGluZSBhZG1pbiBhbHRlcmVkIGZyb3plbiBzbmFwc2hvdCBwcm9vZicpXG4gICAgcHJpdmF0ZShzbmFwc2hvdClcbiAgICByZXF1aXJlKHNoYShzbmFwc2hvdCkgPT0gcHJldmlvdXNbJ3NuYXBzaG90X3NoYTI1NiddLCAnb3JpZ2luYWwgcHJvdGVjdGVkIHNuYXBzaG90IGNoYW5nZWQgZHVyaW5nIGRyaWxsJylcbiAgICByZXR1cm4geydhbGxfY2hlY2tzX3Bhc3MnOiBUcnVlLCAnb2ZmbGluZV92b2x1bWUnOiBWT0xVTUUsICdvZmZsaW5lX3JlYWRvbmx5JzogY2hlY2tlZCxcbiAgICAgICAgICAgICdvZmZsaW5lX3Bvc3RfYWRtaW5fcmVhZG9ubHknOiBwb3N0X2NoZWNrZWQsICdvcmlnaW5hbF9zbmFwc2hvdF9oYXNoX3VuY2hhbmdlZCc6IFRydWUsXG4gICAgICAgICAgICAnY3JlYXRlZF92b2x1bWVfaWRlbnRpdHknOiBpZGVudGl0eSxcbiAgICAgICAgICAgICdhZG1pbl9jb3VudHMnOiBhZG1pbl9jb3VudHMsICduZXR3b3JrJzogJ25vbmUnLCAncnVudGltZV90b2tlbnNfb3JfY29uZmlnX21vdW50ZWQnOiBGYWxzZSxcbiAgICAgICAgICAgICdzZXJ2aWNlX2VudHJ5cG9pbnRfc3RhcnRlZCc6IEZhbHNlLCAnb3JpZ2luYWxfc25hcHNob3RfcmV0YWluZWQnOiBUcnVlfVxuXG5cbmRlZiBjbGVhbnVwX29mZmxpbmUoam91cm5hbCk6XG4gICAgaW5zcGVjdGVkID0gZG9ja2VyKFsndm9sdW1lJywgJ2luc3BlY3QnLCBWT0xVTUVdKVxuICAgIHJlcXVpcmUoaW5zcGVjdGVkLnJldHVybmNvZGUgPT0gMCwgJ293bmVkIGRpc3Bvc2FibGUgdm9sdW1lIGlkZW50aXR5IHVudmVyaWZpZWQnKVxuICAgIHZhbHVlID0ganNvbi5sb2FkcyhpbnNwZWN0ZWQuc3Rkb3V0KVxuICAgIGV4cGVjdGVkID0gam91cm5hbC5kYXRhWydwaGFzZXMnXVsnb2ZmbGluZS12ZXJpZnknXVsncmVzdWx0J11bJ2NyZWF0ZWRfdm9sdW1lX2lkZW50aXR5J11cbiAgICByZXF1aXJlKGxlbih2YWx1ZSkgPT0gMSBhbmQge2tleTogdmFsdWVbMF0uZ2V0KGtleSkgZm9yIGtleSBpbiBleHBlY3RlZH0gPT0gZXhwZWN0ZWRcbiAgICAgICAgICAgIGFuZCBub3QgdmFsdWVbMF0uZ2V0KCdPcHRpb25zJyksICd2b2x1bWUgb3duZXJzaGlwL3JlY3JlYXRpb24gY2hhbmdlZCcpXG4gICAgcmVtb3ZlZCA9IGRvY2tlcihbJ3ZvbHVtZScsICdybScsIFZPTFVNRV0pXG4gICAgcmVxdWlyZShyZW1vdmVkLnJldHVybmNvZGUgPT0gMCBhbmQgcmVtb3ZlZC5zdGRvdXQuc3RyaXAoKSA9PSBWT0xVTUVcbiAgICAgICAgICAgIGFuZCBkb2NrZXIoWyd2b2x1bWUnLCAnaW5zcGVjdCcsIFZPTFVNRV0pLnJldHVybmNvZGUgIT0gMCxcbiAgICAgICAgICAgICdjbGVhbnVwIG91dGNvbWUgYW1iaWd1b3VzOyBkbyBub3QgcmV0cnknKVxuICAgIHJldHVybiB7J2FsbF9jaGVja3NfcGFzcyc6IFRydWUsICdvbmx5X2Rpc3Bvc2FibGVfb2ZmbGluZV92b2x1bWVfcmVtb3ZlZCc6IFZPTFVNRSxcbiAgICAgICAgICAgICduYXRpdmVfYW5kX29mZmNsdXN0ZXJfb3JpZ2luYWxfc25hcHNob3RzX3JldGFpbmVkJzogVHJ1ZX1cblxuXG5kZWYgbWFpbigpOlxuICAgIHBhcnNlciA9IGFyZ3BhcnNlLkFyZ3VtZW50UGFyc2VyKGRlc2NyaXB0aW9uPV9fZG9jX18pXG4gICAgcGFyc2VyLmFkZF9hcmd1bWVudCgnLS1waGFzZScsIGNob2ljZXM9UEhBU0VTLCByZXF1aXJlZD1UcnVlKVxuICAgIHBhcnNlci5hZGRfYXJndW1lbnQoJy0tcm9vdC1hdXRob3JpdHktcmVjZWlwdCcsIHR5cGU9UGF0aCwgcmVxdWlyZWQ9VHJ1ZSlcbiAgICBwYXJzZXIuYWRkX2FyZ3VtZW50KCctLXJvb3QtbmF0aXZlLW91dGNvbWUnLCB0eXBlPVBhdGgsIHJlcXVpcmVkPVRydWUpXG4gICAgcGFyc2VyLmFkZF9hcmd1bWVudCgnLS1zb3VyY2UtcmV2aXNpb24nLCByZXF1aXJlZD1UcnVlKVxuICAgIHBhcnNlci5hZGRfYXJndW1lbnQoJy0tcm9vdC1mcmVzaC1zY2FuLXJlY2VpcHQnLCB0eXBlPVBhdGgsIHJlcXVpcmVkPVRydWUpXG4gICAgYXJncyA9IHBhcnNlci5wYXJzZV9hcmdzKClcbiAgICByZXF1aXJlKGFyZ3Mucm9vdF9hdXRob3JpdHlfcmVjZWlwdCA9PSBQYXRoKCcvdG1wL3NlY3VyaXR5LWluZmQtemxpYi1yb290LWF1dGhvcml0eS5qc29uJylcbiAgICAgICAgICAgIGFuZCBhcmdzLnJvb3RfbmF0aXZlX291dGNvbWUgPT0gUGF0aCgnL3RtcC9zZWN1cml0eS1pbmZkLXpsaWItbmF0aXZlLW91dGNvbWUtbGF0ZXN0Lmpzb24nKSxcbiAgICAgICAgICAgICdmaXhlZCBzdXBlcnZpc2VkIHJlY2VpcHQgcGF0aHMgcmVxdWlyZWQnKVxuICAgIHJlcXVpcmUocmUuZnVsbG1hdGNoKHInWzAtOWEtZl17NDB9JywgYXJncy5zb3VyY2VfcmV2aXNpb24pIGlzIG5vdCBOb25lLCAnZXhhY3Qgc291cmNlIGNvbW1pdCByZXF1aXJlZCcpXG4gICAgIyBWZXJpZnkgYWxsIHByZXBhcmVkIGV4ZWN1dG9yL2lucHV0IGJ5dGVzIGJlZm9yZSByZWFkaW5nIGF1dGhvcml0eSBvciBkaXNwYXRjaGluZy5cbiAgICBwbGFuID0ganNvbi5sb2FkcygoUk9PVCAvICdleGVjdXRpb24tcGxhbi5qc29uJykucmVhZF90ZXh0KCkpXG4gICAgcmVxdWlyZShwbGFuLmdldCgnc3RhdGUnKSA9PSAncHJlcGFyZWQtdW5hcHByb3ZlZCcgYW5kIHBsYW4uZ2V0KCdwdWJsaXNoZWRfYXJ0aWZhY3RfdmVyaWZpZWQnKSBpcyBUcnVlLCAndW5mcm96ZW4gcHJvcG9zYWwgY2Fubm90IGV4ZWN1dGUnKVxuICAgIHJlcXVpcmUoc2V0KHBsYW5bJ2V4ZWN1dG9yX2ZpbGVzJ10pID09IHsnYWN0dWF0b3IucHknLCAnYXV0aG9yaXR5X2pvdXJuYWwucHknLCAnbmF0aXZlX3Byb2dyYW0ucHknLCAncmVjZWlwdF9zYWZldHkucHknfSxcbiAgICAgICAgICAgICdmaXhlZCBleGVjdXRvciBzb3VyY2VzIHJlcXVpcmVkJylcbiAgICByZXF1aXJlKHNldChwbGFuWydvd25lcl9zb3VyY2VfZmlsZXMnXSkgPT0geydkZXBsb3kvbGlmZWN5Y2xlL2FkbWlzc2lvbi5jYW5kaWRhdGUuanNvbicsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2RlcGxveS9saWZlY3ljbGUvc2VydmljZS5jYW5kaWRhdGUuanNvbicsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2RlcGxveS9saWZlY3ljbGUva3VzdG9taXphdGlvbi55YW1sJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnZGVwbG95L2xpZmVjeWNsZS9yZWdpc3RyaWVzLmNhbmRpZGF0ZS5qc29uJ30sXG4gICAgICAgICAgICAnZml4ZWQgb3duZXIgaW1hZ2Ugc291cmNlcyByZXF1aXJlZCcpXG4gICAgZm9yIHJlbGF0aXZlLCBkaWdlc3QgaW4gcGxhblsnZXhlY3V0b3JfZmlsZXMnXS5pdGVtcygpOlxuICAgICAgICB0YXJnZXQgPSBST09UIC8gcmVsYXRpdmVcbiAgICAgICAgcmVxdWlyZShub3QgdGFyZ2V0LmlzX3N5bWxpbmsoKSBhbmQgc2hhKHRhcmdldCkgPT0gZGlnZXN0LCAnZXhlY3V0b3Igc291cmNlIGJ5dGVzIGNoYW5nZWQnKVxuICAgIGZvciByZWxhdGl2ZSwgZGlnZXN0IGluIHBsYW5bJ2Nhc2VfZmlsZXMnXS5pdGVtcygpOlxuICAgICAgICByZXF1aXJlKG5vdCBQYXRoKHJlbGF0aXZlKS5pc19hYnNvbHV0ZSgpIGFuZCAnLi4nIG5vdCBpbiBQYXRoKHJlbGF0aXZlKS5wYXJ0cyxcbiAgICAgICAgICAgICAgICAnY2FzZSBzb3VyY2UgcGF0aCBlc2NhcGVkJylcbiAgICAgICAgdGFyZ2V0ID0gQ0FTRSAvIHJlbGF0aXZlXG4gICAgICAgIHJlcXVpcmUobm90IHRhcmdldC5pc19zeW1saW5rKCkgYW5kIHNoYSh0YXJnZXQpID09IGRpZ2VzdCwgJ2Nhc2Ugc291cmNlIGJ5dGVzIGNoYW5nZWQnKVxuICAgIGZvciByZWxhdGl2ZSwgZGlnZXN0IGluIHBsYW5bJ293bmVyX3NvdXJjZV9maWxlcyddLml0ZW1zKCk6XG4gICAgICAgIHRhcmdldCA9IE9XTkVSIC8gcmVsYXRpdmVcbiAgICAgICAgcmVxdWlyZShub3QgdGFyZ2V0LmlzX3N5bWxpbmsoKSBhbmQgc2hhKHRhcmdldCkgPT0gZGlnZXN0LCAnb3duZXIgaW1hZ2Utb25seSBzb3VyY2UgY2hhbmdlZCcpXG4gICAgICAgIGNvbW1pdHRlZCA9IHN1YnByb2Nlc3MucnVuKFsnZ2l0JywgJy1DJywgc3RyKE9XTkVSKSwgJ3Nob3cnLCBhcmdzLnNvdXJjZV9yZXZpc2lvbiArICc6JyArIHJlbGF0aXZlXSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2FwdHVyZV9vdXRwdXQ9VHJ1ZSlcbiAgICAgICAgcmVxdWlyZShjb21taXR0ZWQucmV0dXJuY29kZSA9PSAwIGFuZCBoYXNobGliLnNoYTI1Nihjb21taXR0ZWQuc3Rkb3V0KS5oZXhkaWdlc3QoKSA9PSBkaWdlc3QsXG4gICAgICAgICAgICAgICAgJ2NvbW1pdHRlZCBvd25lciByZW5kZXIgc291cmNlIGRpZmZlcnMgZnJvbSBhcHByb3ZlZCBpbnB1dHMnKVxuICAgIGhlYWQgPSBydW4oWydnaXQnLCAnLUMnLCBzdHIoT1dORVIpLCAncmV2LXBhcnNlJywgJ0hFQUQnXSlcbiAgICByZXF1aXJlKGhlYWQucmV0dXJuY29kZSA9PSAwIGFuZCBoZWFkLnN0ZG91dC5zdHJpcCgpID09IGFyZ3Muc291cmNlX3JldmlzaW9uLCAnb3duZXIgc291cmNlIEhFQUQgZGlmZmVycycpXG4gICAgZnJvbSBhdXRob3JpdHlfam91cm5hbCBpbXBvcnQgSm91cm5hbCwgYXV0aG9yaXR5LCBmcmVzaF9zY2FuXG4gICAgcHJpdmF0ZShhcmdzLnJvb3RfYXV0aG9yaXR5X3JlY2VpcHQpXG4gICAgcHJpdmF0ZShhcmdzLnJvb3RfbmF0aXZlX291dGNvbWUpXG4gICAgcmVjZWlwdCA9IGpzb24ubG9hZHMoYXJncy5yb290X2F1dGhvcml0eV9yZWNlaXB0LnJlYWRfdGV4dCgpKVxuICAgIHJlcXVpcmUocmVjZWlwdC5nZXQoJ25hdGl2ZV9vdXRjb21lX3NoYTI1NicpID09IHNoYShhcmdzLnJvb3RfbmF0aXZlX291dGNvbWUpLCAnbmF0aXZlIG91dGNvbWUgYnl0ZXMgY2hhbmdlZCcpXG4gICAgcmVxdWlyZShyZWNlaXB0LmdldCgnZXhlY3V0b3JfcGxhbl9zaGEyNTYnKSA9PSBzaGEoUk9PVCAvICdleGVjdXRpb24tcGxhbi5qc29uJyksXG4gICAgICAgICAgICAncm9vdCB2ZXJpZmllZCBleGVjdXRvciBwbGFuIGRpZmZlcnMnKVxuICAgIGFwcHJvdmVkID0gYXV0aG9yaXR5KHJlY2VpcHQsIGpzb24ubG9hZHMoYXJncy5yb290X25hdGl2ZV9vdXRjb21lLnJlYWRfdGV4dCgpKSwgc2hhKENBU0UgLyAnbWVtby5qc29uJyksXG4gICAgICAgICAgICAgICAgICAgICAgICAgYXJncy5zb3VyY2VfcmV2aXNpb24sIGFyZ3MucGhhc2UpXG4gICAgaWYgYXJncy5waGFzZSBpbiAoJ3JlbGVhc2UnLCAndmVyaWZ5Jyk6XG4gICAgICAgIHJlcXVpcmUoYXJncy5yb290X2ZyZXNoX3NjYW5fcmVjZWlwdCA9PSBQYXRoKCcvdG1wL3NlY3VyaXR5LWluZmQtemxpYi1mcmVzaC1zY2FuLXJlY2VpcHQuanNvbicpLCAnZml4ZWQgZnJlc2ggc2NhbiByZWNlaXB0IHBhdGggcmVxdWlyZWQnKVxuICAgICAgICBwcml2YXRlKGFyZ3Mucm9vdF9mcmVzaF9zY2FuX3JlY2VpcHQpXG4gICAgICAgIHNjYW5fcmVjZWlwdD1qc29uLmxvYWRzKGFyZ3Mucm9vdF9mcmVzaF9zY2FuX3JlY2VpcHQucmVhZF90ZXh0KCkpXG4gICAgICAgIHJlcG9ydF9wYXRoPVBhdGgoJy90bXAvc2VjdXJpdHktaW5mZC16bGliLWZyZXNoLXNjYW4tZnVsbC5qc29uJyk7cHJpdmF0ZShyZXBvcnRfcGF0aClcbiAgICAgICAgcmVxdWlyZShzaGEocmVwb3J0X3BhdGgpPT1zY2FuX3JlY2VpcHQuZ2V0KCdmdWxsX3JlcG9ydF9zaGEyNTYnKSwgJ2ZyZXNoIHNjYW4gcmVwb3J0IGJ5dGVzIGNoYW5nZWQnKVxuICAgICAgICBmcmVzaF9zY2FuKHNjYW5fcmVjZWlwdCxqc29uLmxvYWRzKHJlcG9ydF9wYXRoLnJlYWRfdGV4dCgpKSlcbiAgICBwYXJlbnQgPSBDVVNUT0RZLnBhcmVudFxuICAgIHByaXZhdGUocGFyZW50LCBkaXJlY3Rvcnk9VHJ1ZSlcbiAgICBpZ25vcmVkID0gcnVuKFsnZ2l0JywgJy1DJywgc3RyKENVU1RPRElBTiksICdjaGVjay1pZ25vcmUnLCAnLS1uby1pbmRleCcsIHN0cihDVVNUT0RZIC8gJ2RyaWxsLnNxbGl0ZScpXSlcbiAgICByZXF1aXJlKGlnbm9yZWQucmV0dXJuY29kZSA9PSAwLCAnb2ZmY2x1c3RlciBjdXN0b2R5IGlzIG5vdCBpZ25vcmVkJylcbiAgICBpZiBub3QgQ1VTVE9EWS5leGlzdHMoKTpcbiAgICAgICAgcmVxdWlyZShhcmdzLnBoYXNlID09ICdkcmlsbC1iYWNrdXAnLCAnZmlyc3QgYXBwcm92ZWQgcGhhc2UgbXVzdCBjcmVhdGUgdW5pcXVlIHByaXZhdGUgY3VzdG9keScpXG4gICAgICAgIG9zLm1rZGlyKENVU1RPRFksIDBvNzAwKVxuICAgIHByaXZhdGUoQ1VTVE9EWSwgZGlyZWN0b3J5PVRydWUpXG4gICAgY2FwYWNpdHkgPSBvcy5zdGF0dmZzKENVU1RPRFkpXG4gICAgcmVxdWlyZShjYXBhY2l0eS5mX2JhdmFpbCAqIGNhcGFjaXR5LmZfZnJzaXplID49IDIgKiBNQVhfQllURVMgKyAxNiAqIDEwMjQqKjIsXG4gICAgICAgICAgICAnaW5kZXBlbmRlbnQgcHJvdGVjdGVkIGN1c3RvZHkgaGVhZHJvb20gaW5zdWZmaWNpZW50JylcbiAgICBvcy5tYWtlZGlycyhDVVNUT0RZIC8gJ2VtcHR5LWRvY2tlci1jb25maWcnLCBtb2RlPTBvNzAwLCBleGlzdF9vaz1UcnVlKVxuICAgIHByaXZhdGUoQ1VTVE9EWSAvICdlbXB0eS1kb2NrZXItY29uZmlnJywgZGlyZWN0b3J5PVRydWUpXG4gICAgcmVxdWlyZShub3QgbGlzdCgoQ1VTVE9EWSAvICdlbXB0eS1kb2NrZXItY29uZmlnJykuaXRlcmRpcigpKSwgJ3B1YmxpYyBEb2NrZXIgcmVhZCBjb25maWcgbXVzdCBzdGF5IGVtcHR5JylcbiAgICBqb3VybmFsID0gSm91cm5hbChDVVNUT0RZLCB7J3Byb3Bvc2FsJzogcGxhblsncHJvcG9zYWxfaWQnXSwgJ3NvdXJjZV9yZXZpc2lvbic6IGFyZ3Muc291cmNlX3JldmlzaW9uLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdtZW1vX3NoYTI1Nic6IHNoYShDQVNFIC8gJ21lbW8uanNvbicpLCAncGxhbl9zaGEyNTYnOiBzaGEoUk9PVCAvICdleGVjdXRpb24tcGxhbi5qc29uJyl9KVxuICAgIHRyeTpcbiAgICAgICAgam91cm5hbC5zdGFydChhcmdzLnBoYXNlLCBhcHByb3ZlZCkgIyBmc3luY2VkIFJVTk5JTkcgYmVmb3JlIGFueSBwaGFzZSBlZmZlY3QuXG4gICAgICAgIGlmIGFyZ3MucGhhc2UgaW4gKCdkcmlsbC1jb3B5JywgJ2N1dG92ZXItY29weScpOlxuICAgICAgICAgICAgcmVzdWx0ID0gY29weV9zbmFwc2hvdChhcmdzLnBoYXNlLCBhcmdzLnNvdXJjZV9yZXZpc2lvbiwgYXBwcm92ZWQsIGpvdXJuYWwpXG4gICAgICAgIGVsaWYgYXJncy5waGFzZSA9PSAnb2ZmbGluZS12ZXJpZnknOlxuICAgICAgICAgICAgcmVzdWx0ID0gb2ZmbGluZV92ZXJpZnkoam91cm5hbClcbiAgICAgICAgZWxpZiBhcmdzLnBoYXNlID09ICdjbGVhbnVwLW9mZmxpbmUnOlxuICAgICAgICAgICAgcmVzdWx0ID0gY2xlYW51cF9vZmZsaW5lKGpvdXJuYWwpXG4gICAgICAgIGVsc2U6XG4gICAgICAgICAgICByZXN1bHQgPSByZW1vdGUoYXJncy5waGFzZSwgYXJncy5zb3VyY2VfcmV2aXNpb24sIGFwcHJvdmVkLCBvcGVyYXRpb25faW50ZW50PWpvdXJuYWwuZGF0YVsncGhhc2VzJ10uZ2V0KCdyb2xsYmFjaycgaWYgYXJncy5waGFzZSA9PSAncm9sbGJhY2stdmVyaWZ5JyBlbHNlICdyZWxlYXNlJykgaWYgYXJncy5waGFzZSBpbiAoJ3ZlcmlmeScsICdyb2xsYmFjay12ZXJpZnknKSBlbHNlIE5vbmUpXG4gICAgICAgIGpvdXJuYWwuZmluaXNoKGFyZ3MucGhhc2UsIHJlc3VsdClcbiAgICAgICAgcHJpbnQoanNvbi5kdW1wcyhyZXN1bHQpKSAjIE9ubHkgY291bnQvaGFzaC9pZGVudGl0eSByZWNlaXB0cywgbmV2ZXIgc25hcHNob3QgYnl0ZXMuXG4gICAgZXhjZXB0IEJhc2VFeGNlcHRpb24gYXMgZXJyb3I6XG4gICAgICAgIHNhZmUgPSB7J2FsbF9jaGVja3NfcGFzcyc6IEZhbHNlLCAncGhhc2UnOiBhcmdzLnBoYXNlLCAnZmFpbHVyZV9jbGFzcyc6IHR5cGUoZXJyb3IpLl9fbmFtZV9fLFxuICAgICAgICAgICAgICAgICdmYWlsdXJlJzogJ2ZpeGVkIHBoYXNlIG91dGNvbWUgdW5jZXJ0YWluOyBvcmlnaW5hbCBldmlkZW5jZSByZXRhaW5lZDsgbm8gcmV0cnknLFxuICAgICAgICAgICAgICAgICdyYXdfb3V0cHV0X3dpdGhoZWxkJzogVHJ1ZX1cbiAgICAgICAgaWYgam91cm5hbC5kYXRhWydwaGFzZXMnXS5nZXQoYXJncy5waGFzZSwge30pLmdldCgnc3RhdGUnKSA9PSAnUlVOTklORyc6XG4gICAgICAgICAgICBqb3VybmFsLmZpbmlzaChhcmdzLnBoYXNlLCBzYWZlKVxuICAgICAgICBwcmludChqc29uLmR1bXBzKHNhZmUpKVxuICAgICAgICByYWlzZSBTeXN0ZW1FeGl0KDEpIGZyb20gTm9uZVxuICAgIGZpbmFsbHk6XG4gICAgICAgIGpvdXJuYWwuY2xvc2UoKVxuXG5cbmlmIF9fbmFtZV9fID09ICdfX21haW5fXyc6XG4gICAgdHJ5OlxuICAgICAgICBtYWluKClcbiAgICBleGNlcHQgRXhjZXB0aW9uOlxuICAgICAgICByYWlzZSBTeXN0ZW1FeGl0KCdzdXBlcnZpc2VkIGV4ZWN1dGlvbiByZWZ1c2VkIGJlZm9yZSBwaGFzZTsgcmF3IG91dHB1dCB3aXRoaGVsZCcpIGZyb20gTm9uZVxuIgogICAgfSwKICAgICJleGVjdXRpb24vYXV0aG9yaXR5X2pvdXJuYWwucHkiOiB7CiAgICAgICJzaGEyNTYiOiAiMmFhOWJhZTQwNzUyOTIyMjY0ZTUzMzgyZDg5OGUzMDgzYjhkMjI5YjE4NTMyOTI2Njk0MmJkZDM5NzAxMzEwOCIsCiAgICAgICJjb250ZW50IjogIlwiXCJcIlN1cGVydmlzZWQgcmVjZWlwdCBib29ra2VlcGluZywgbm90IGFuIGF1dGhlbnRpY2F0ZWQgYXBwcm92YWwgcHJvdmlkZXIuXCJcIlwiXG5pbXBvcnQgZGF0ZXRpbWVcbmltcG9ydCBmY250bFxuaW1wb3J0IGhhc2hsaWJcbmltcG9ydCBqc29uXG5pbXBvcnQgb3NcbmltcG9ydCByZVxuZnJvbSBwYXRobGliIGltcG9ydCBQYXRoXG5pbXBvcnQgc3RhdFxuaW1wb3J0IHV1aWRcblxuRk9VTkRFUiA9IHsna2luZCc6ICdwZXJzb24nLCAnc3ViJzogJ3VpZD1wbGF0Zm9ybS1yb290LG91PXBlb3BsZSxkYz1uZXRraW5nZG9tLGRjPWxvY2FsJ31cbk1FTU9fSUQgPSAnaW5mZC0yMDI2MTAxMS16bGliLXJlbWVkaWF0aW9uLXIxJ1xuUFJPUE9TQUwgPSAnSU5GRC1XUC0wMDA5LVQwNS96bGliODUwOTEtcjEnXG5QSEFTRVMgPSAoJ2RyaWxsLWJhY2t1cCcsICdkcmlsbC1jb3B5JywgJ29mZmxpbmUtdmVyaWZ5JywgJ2NsZWFudXAtb2ZmbGluZScsXG4gICAgICAgICAgJ2N1dG92ZXItYmFja3VwJywgJ2N1dG92ZXItY29weScsICdyZWxlYXNlJywgJ3ZlcmlmeScsICdyb2xsYmFjaycsICdyb2xsYmFjay12ZXJpZnknKVxuXG5cbmRlZiByZXF1aXJlKHZhbHVlLCByZWFzb24pOlxuICAgIGlmIG5vdCB2YWx1ZTpcbiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcihyZWFzb24pXG5cblxuZGVmIHNoYShwYXRoKTpcbiAgICBkaWdlc3QgPSBoYXNobGliLnNoYTI1NigpXG4gICAgd2l0aCBQYXRoKHBhdGgpLm9wZW4oJ3JiJykgYXMgc3RyZWFtOlxuICAgICAgICBmb3IgYmxvY2sgaW4gaXRlcihsYW1iZGE6IHN0cmVhbS5yZWFkKDEwMjQqKjIpLCBiJycpOlxuICAgICAgICAgICAgZGlnZXN0LnVwZGF0ZShibG9jaylcbiAgICByZXR1cm4gZGlnZXN0LmhleGRpZ2VzdCgpXG5cblxuZGVmIHByaXZhdGUocGF0aCwgZGlyZWN0b3J5PUZhbHNlKTpcbiAgICBpbmZvID0gUGF0aChwYXRoKS5sc3RhdCgpXG4gICAgcmVxdWlyZSgoc3RhdC5TX0lTRElSKGluZm8uc3RfbW9kZSkgaWYgZGlyZWN0b3J5IGVsc2Ugc3RhdC5TX0lTUkVHKGluZm8uc3RfbW9kZSkpXG4gICAgICAgICAgICBhbmQgaW5mby5zdF91aWQgPT0gb3MuZ2V0dWlkKCkgYW5kIHN0YXQuU19JTU9ERShpbmZvLnN0X21vZGUpID09ICgwbzcwMCBpZiBkaXJlY3RvcnkgZWxzZSAwbzYwMClcbiAgICAgICAgICAgIGFuZCAoZGlyZWN0b3J5IG9yIGluZm8uc3RfbmxpbmsgPT0gMSksICdwcml2YXRlIG93bmVkIGN1c3RvZHkgcmVxdWlyZWQnKVxuXG5cbmRlZiBhdXRob3JpdHkocmVjZWlwdCwgb3V0Y29tZSwgbWVtb19zaGEsIHNvdXJjZV9yZXZpc2lvbiwgcGhhc2UsIG5vdz1Ob25lKTpcbiAgICBcIlwiXCJSb290IG11c3QgZnJlc2hseSB2ZXJpZnkgbmF0aXZlIG91dGNvbWUgYXQgZml4ZWQgT1JJR0lOIG91dHNpZGUgdGhpcyBoZWxwZXIuXG5cbiAgICBSZWNlaXB0IGZpbGVzL2ZsYWdzIGFyZSBub3Qgc2lnbmVkIGdyYW50cy4gVGhpcyBmdW5jdGlvbiBjaGVja3MgdGhlIGV4YWN0XG4gICAgc3VwZXJ2aXNlZCByZWNlaXB0IHJlbGF0aW9uOyBleGlzdGluZyBoYXJuZXNzL2ZvdW5kZXIgYXV0aG9yaXR5IHJlbWFpbnMgdGhlXG4gICAgYWN0dWFsIHBlcm1pc3Npb24gYm91bmRhcnkuIEl0IG5ldmVyIGF1dGhlbnRpY2F0ZXMgYSBjYWxsZXIgYXMgaHVtYW4uXG4gICAgXCJcIlwiXG4gICAgbm93ID0gbm93IG9yIGRhdGV0aW1lLmRhdGV0aW1lLm5vdyhkYXRldGltZS50aW1lem9uZS51dGMpXG4gICAgcmVxdWlyZShwaGFzZSBpbiBQSEFTRVMsICdmaXhlZCBwaGFzZSByZXF1aXJlZCcpXG4gICAgcmVxdWlyZShyZWNlaXB0LmdldCgncHJvcG9zYWxfaWQnKSA9PSBQUk9QT1NBTCBhbmQgcmVjZWlwdC5nZXQoJ21lbW9faWQnKSA9PSBNRU1PX0lEXG4gICAgICAgICAgICBhbmQgcmVjZWlwdC5nZXQoJ21lbW9fdmVyc2lvbicpID09IDEgYW5kIHJlY2VpcHQuZ2V0KCdtZW1vX3NoYTI1NicpID09IG1lbW9fc2hhXG4gICAgICAgICAgICBhbmQgcmVjZWlwdC5nZXQoJ3NvdXJjZV9yZXZpc2lvbicpID09IHNvdXJjZV9yZXZpc2lvblxuICAgICAgICAgICAgYW5kIHJlY2VpcHQuZ2V0KCdhY3RpdmF0aW9uJykgPT0gJ0FQUFJPVkVEJ1xuICAgICAgICAgICAgYW5kIHJlY2VpcHQuZ2V0KCdhdXRob3JpdHlfbW9kZScpID09ICdBRE1JTklTVEVSJ1xuICAgICAgICAgICAgYW5kIHJlY2VpcHQuZ2V0KCd0YXJnZXQnKSA9PSAncmVhbG06a3ViZXJuZXRlcy9yYWlsaWFuY2UwMS9pbmZvcm1lZC1kZWNpc2lvbidcbiAgICAgICAgICAgIGFuZCByZWNlaXB0LmdldCgndmVyaWZpY2F0aW9uX21ldGhvZCcpID09ICdyb290LWZpeGVkLW9yaWdpbi1uYXRpdmUtcmVhZGJhY2snXG4gICAgICAgICAgICBhbmQgcmVjZWlwdC5nZXQoJ3J1bnRpbWVfYXV0aGVudGljYXRlZF9ncmFudCcpIGlzIEZhbHNlXG4gICAgICAgICAgICBhbmQgcmVjZWlwdC5nZXQoJ291dGNvbWVfcXVlcnknKSA9PSAnL2xpZmVjeWNsZS9tZW1vcy8nICsgTUVNT19JRCArICcvb3V0Y29tZXM/dmVyc2lvbj0xJyxcbiAgICAgICAgICAgICdleGFjdCBleHRlcm5hbGx5IHZlcmlmaWVkIHN1cGVydmlzZWQgcmVjZWlwdCByZXF1aXJlZCcpXG4gICAgdmVyaWZpZWQgPSBkYXRldGltZS5kYXRldGltZS5mcm9taXNvZm9ybWF0KHJlY2VpcHRbJ3ZlcmlmaWVkX2F0J10ucmVwbGFjZSgnWicsICcrMDA6MDAnKSlcbiAgICByZXF1aXJlKHZlcmlmaWVkLnR6aW5mbyBpcyBub3QgTm9uZSBhbmQgMCA8PSAobm93IC0gdmVyaWZpZWQpLnRvdGFsX3NlY29uZHMoKSA8PSAxMjAsXG4gICAgICAgICAgICAnZnJlc2ggcm9vdCBuYXRpdmUgcmVhZGJhY2sgcmVxdWlyZWQgYmVmb3JlIGV2ZXJ5IHBoYXNlJylcbiAgICBkZWFkbGluZSA9IGRhdGV0aW1lLmRhdGV0aW1lLmZyb21pc29mb3JtYXQoJzIwMjYtMTAtMTJUMjM6NTk6NTkrMDA6MDAnKVxuICAgIHJlcXVpcmUobm93IDwgZGVhZGxpbmUsICdhcHByb3ZlZCBleGVjdXRpb24gd2luZG93IGV4cGlyZWQnKVxuICAgIGlmIHBoYXNlID09ICdyZWxlYXNlJzpcbiAgICAgICAgcmVxdWlyZSgoZGVhZGxpbmUtbm93KS50b3RhbF9zZWNvbmRzKCkgPj0gMTgwMCwgJ3Jlc2VydmUgdGhpcnR5IG1pbnV0ZXMgZm9yIHZlcmlmaWNhdGlvbiBhbmQgcmVjb3ZlcnknKVxuICAgIHJlcXVpcmUob3V0Y29tZS5nZXQoJ21lbW9faWQnKSA9PSBNRU1PX0lEIGFuZCBvdXRjb21lLmdldCgncmV2aWV3Jywge30pLmdldCgnc3RhbGUnKSBpcyBGYWxzZVxuICAgICAgICAgICAgYW5kIG91dGNvbWVbJ3JldmlldyddLmdldCgnaW50YWtlX3N0YXRlJykgPT0gJ3JlYWR5JywgJ25hdGl2ZSByZXZpZXcgaXMgbm90IHJlYWR5L2N1cnJlbnQnKVxuICAgIHJldmlld19pZCA9IG91dGNvbWVbJ3JldmlldyddWydtZW1vX2lkJ11cbiAgICByZXF1aXJlKHJldmlld19pZCA9PSByZWNlaXB0LmdldCgncmV2aWV3X21lbW9faWQnKSwgJ25hdGl2ZSByZXZpZXcgaWRlbnRpdHkgY2hhbmdlZCcpXG4gICAgYWNjZXB0ZWQgPSBbeCBmb3IgeCBpbiBvdXRjb21lLmdldCgnZGlzcG9zaXRpb25zJywgW10pXG4gICAgICAgICAgICAgICAgaWYgeC5nZXQoJ3N1Ym1pc3Npb25fc3RhdGUnKSA9PSAnY29uZmlybWVkJ1xuICAgICAgICAgICAgICAgIGFuZCB4LmdldCgnZGlzcG9zaXRpb24nLCB7fSkuZ2V0KCd2ZXJiJykgPT0gJ2FjY2VwdCdcbiAgICAgICAgICAgICAgICBhbmQgeFsnZGlzcG9zaXRpb24nXS5nZXQoJ21lbW9fdmVyc2lvbicpID09IDFdXG4gICAgcmVxdWlyZShsZW4oYWNjZXB0ZWQpID09IDEsICdvbmUgY29uZmlybWVkIGZvdW5kZXIgYWNjZXB0YW5jZSByZXF1aXJlZCcpXG4gICAgYWNjZXB0ZWQgPSBhY2NlcHRlZFswXVsnZGlzcG9zaXRpb24nXVxuICAgIHJlcXVpcmUoYWNjZXB0ZWQuZ2V0KCdhY3RvcicpID09IEZPVU5ERVIgYW5kIGFjY2VwdGVkLmdldCgnbWVtb19pZCcpID09IHJldmlld19pZFxuICAgICAgICAgICAgYW5kIGFjY2VwdGVkLmdldCgnYXBwcm92YWxfaWQnKSA9PSByZWNlaXB0LmdldCgnYXBwcm92YWxfaWQnKVxuICAgICAgICAgICAgYW5kIGFjY2VwdGVkLmdldCgnaWQnKSA9PSByZWNlaXB0LmdldCgnZGlzcG9zaXRpb25faWQnKVxuICAgICAgICAgICAgYW5kIGFjY2VwdGVkLmdldCgnYXQnKSA9PSByZWNlaXB0LmdldCgnYWNjZXB0ZWRfYXQnKSwgJ2V4YWN0IGZvdW5kZXIgZGlzcG9zaXRpb24gbWlzbWF0Y2gnKVxuICAgIHJlcXVpcmUocmUuZnVsbG1hdGNoKHInW2EtZjAtOS1dezM2fScsIGFjY2VwdGVkWydhcHByb3ZhbF9pZCddKSBpcyBub3QgTm9uZVxuICAgICAgICAgICAgYW5kIHJlLmZ1bGxtYXRjaChyJ2Rpc3AtW2EtZjAtOS1dezM2fScsIGFjY2VwdGVkWydpZCddKSBpcyBub3QgTm9uZVxuICAgICAgICAgICAgYW5kIHJlLmZ1bGxtYXRjaChyJ3Jldmlldy1bYS1mMC05LV17MzZ9JywgcmV2aWV3X2lkKSBpcyBub3QgTm9uZSxcbiAgICAgICAgICAgICduYXRpdmUgZGlzcG9zaXRpb24gaWRlbnRpZmllcnMgYXJlIG1hbGZvcm1lZCcpXG4gICAgYWNjZXB0ZWRfYXQgPSBkYXRldGltZS5kYXRldGltZS5mcm9taXNvZm9ybWF0KGFjY2VwdGVkWydhdCddLnJlcGxhY2UoJ1onLCAnKzAwOjAwJykpXG4gICAgcmVxdWlyZShhY2NlcHRlZF9hdC50emluZm8gaXMgbm90IE5vbmUgYW5kIGFjY2VwdGVkX2F0IDw9IHZlcmlmaWVkLCAnYWNjZXB0YW5jZSB0aW1lIG5vdCBib3VuZGVkJylcbiAgICByZXR1cm4ge2tleTogcmVjZWlwdFtrZXldIGZvciBrZXkgaW4gKCdhcHByb3ZhbF9pZCcsICdkaXNwb3NpdGlvbl9pZCcsICdhY2NlcHRlZF9hdCcsICd2ZXJpZmllZF9hdCcpfVxuXG5cbmRlZiBmcmVzaF9zY2FuKHJlY2VpcHQsIHJlcG9ydCwgbm93PU5vbmUpOlxuICAgIFwiXCJcIlJvb3Qgc3VwZXJ2aXNlZCBzY2FuIGJvb2trZWVwaW5nOyBhY3R1YWwgc2Nhbm5lciBwcm9vZiBzdGF5cyByZXRhaW5lZC5cIlwiXCJcbiAgICBub3c9bm93IG9yIGRhdGV0aW1lLmRhdGV0aW1lLm5vdyhkYXRldGltZS50aW1lem9uZS51dGMpXG4gICAgcmVxdWlyZShyZWNlaXB0LmdldCgnaW1hZ2UnKT09J2Zvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbkBzaGEyNTY6ZjU1ODhmNDk2YjFmMjY3ZDA3Y2RjOWY1MjA4ZTZmMmYyNDFmNzRiNzM5YzIzOTI0YjAzNzAyZGVjMTNhNWMyMydcbiAgICAgICAgICAgIGFuZCByZWNlaXB0LmdldCgnYXJ0aWZhY3Rfc291cmNlX3JldmlzaW9uJyk9PSc5YjRiYjI1ZDMyNTY5OGFlNTQzZTdlOGE4YTNjMzU5MmRiMWY1OGZjJ1xuICAgICAgICAgICAgYW5kIHJlY2VpcHQuZ2V0KCdzY2FubmVyJyk9PSdhcXVhc2VjL3RyaXZ5QHNoYTI1Njo2MmIxZTY1ZTg4NjliYzRiNGM2YWE0ZmEyYjIxNTk1MjU2YzdjMmY2MDE4YTlkOWFkNjFjYWY4NzE4N2MxOTY5J1xuICAgICAgICAgICAgYW5kIHJlY2VpcHQuZ2V0KCdub19zdXBwcmVzc2lvbicpIGlzIFRydWUgYW5kIHJlY2VpcHQuZ2V0KCdmdWxsX2FsbHNldmVyaXR5X3NjYW4nKSBpcyBUcnVlXG4gICAgICAgICAgICBhbmQgcmVjZWlwdC5nZXQoJ2V4aXRfY29kZScpPT0wLCdleGFjdCBmdWxsIGltbXV0YWJsZSBhcnRpZmFjdCBzY2FuIHJlcXVpcmVkJylcbiAgICBzY2FubmVkPWRhdGV0aW1lLmRhdGV0aW1lLmZyb21pc29mb3JtYXQocmVjZWlwdFsnY29tcGxldGVkX2F0J10ucmVwbGFjZSgnWicsJyswMDowMCcpKVxuICAgIG1ldGFkYXRhPXJlY2VpcHRbJ2RiX21ldGFkYXRhJ107dXBkYXRlZD1kYXRldGltZS5kYXRldGltZS5mcm9taXNvZm9ybWF0KG1ldGFkYXRhWydVcGRhdGVkQXQnXS5yZXBsYWNlKCdaJywnKzAwOjAwJykpO25leHRfdXBkYXRlPWRhdGV0aW1lLmRhdGV0aW1lLmZyb21pc29mb3JtYXQobWV0YWRhdGFbJ05leHRVcGRhdGUnXS5yZXBsYWNlKCdaJywnKzAwOjAwJykpXG4gICAgcmVxdWlyZShhbGwodC50emluZm8gaXMgbm90IE5vbmUgZm9yIHQgaW4gKHNjYW5uZWQsdXBkYXRlZCxuZXh0X3VwZGF0ZSkpXG4gICAgICAgICAgICBhbmQgdXBkYXRlZDw9c2Nhbm5lZDw9bm93PG5leHRfdXBkYXRlIGFuZCAobm93LXNjYW5uZWQpLnRvdGFsX3NlY29uZHMoKTw9MTQ0MDBcbiAgICAgICAgICAgIGFuZCBtZXRhZGF0YS5nZXQoJ1ZlcnNpb24nKT09MiBhbmQgcmUuZnVsbG1hdGNoKHInW2EtZjAtOV17NjR9JyxyZWNlaXB0LmdldCgnZGJfc2hhMjU2JywnJykpIGlzIG5vdCBOb25lLCdzY2FuIG9yIHZ1bG5lcmFiaWxpdHkgZGF0YWJhc2UgZXhwaXJlZCcpXG4gICAgcmVxdWlyZShpc2luc3RhbmNlKHJlcG9ydCxkaWN0KSBhbmQgaXNpbnN0YW5jZShyZXBvcnQuZ2V0KCdSZXN1bHRzJyksbGlzdCkgYW5kIHJlcG9ydFsnUmVzdWx0cyddLCdyZXRhaW5lZCBmdWxsIHNjYW4gcmVzdWx0cyByZXF1aXJlZCcpXG4gICAgZm9yIHJlc3VsdCBpbiByZXBvcnRbJ1Jlc3VsdHMnXTpcbiAgICAgICAgcmVxdWlyZShpc2luc3RhbmNlKHJlc3VsdCxkaWN0KSBhbmQgaXNpbnN0YW5jZShyZXN1bHQuZ2V0KCdWdWxuZXJhYmlsaXRpZXMnLFtdKSwobGlzdCx0eXBlKE5vbmUpKSksJ2Z1bGwgc2NhbiByZXN1bHQgaW52YWxpZCcpXG4gICAgICAgIHJlcXVpcmUobm90IHJlc3VsdC5nZXQoJ1Z1bG5lcmFiaWxpdGllcycpLCduZXcgZmluZGluZyByZWZ1c2VzIHJlbGVhc2UgYW5kIGNsb3N1cmUnKVxuICAgIHJldHVybiB7J2Z1bGxfcmVwb3J0X3NoYTI1Nic6cmVjZWlwdFsnZnVsbF9yZXBvcnRfc2hhMjU2J10sJ2RiX3NoYTI1Nic6cmVjZWlwdFsnZGJfc2hhMjU2J10sJ2NvbXBsZXRlZF9hdCc6cmVjZWlwdFsnY29tcGxldGVkX2F0J10sJ25leHRfdXBkYXRlJzptZXRhZGF0YVsnTmV4dFVwZGF0ZSddLCdhbGxzZXZlcml0eV9maW5kaW5ncyc6MH1cblxuXG5jbGFzcyBKb3VybmFsOlxuICAgIGRlZiBfX2luaXRfXyhzZWxmLCBwYXJlbnQsIGlkZW50aXR5KTpcbiAgICAgICAgc2VsZi5wYXJlbnQgPSBQYXRoKHBhcmVudClcbiAgICAgICAgcHJpdmF0ZShzZWxmLnBhcmVudCwgZGlyZWN0b3J5PVRydWUpXG4gICAgICAgIHNlbGYucGF0aCA9IHNlbGYucGFyZW50IC8gJ2V4ZWN1dGlvbi1qb3VybmFsLmpzb24nXG4gICAgICAgIHNlbGYubG9jayA9IG9zLm9wZW4oc2VsZi5wYXJlbnQgLyAnZXhlY3V0aW9uLmxvY2snLCBvcy5PX0NSRUFUIHwgb3MuT19OT0ZPTExPVyB8IG9zLk9fUkRXUiwgMG82MDApXG4gICAgICAgIGZjbnRsLmZsb2NrKHNlbGYubG9jaywgZmNudGwuTE9DS19FWCB8IGZjbnRsLkxPQ0tfTkIpXG4gICAgICAgIHByaXZhdGUoc2VsZi5wYXJlbnQgLyAnZXhlY3V0aW9uLmxvY2snKVxuICAgICAgICBpZiBzZWxmLnBhdGguZXhpc3RzKCk6XG4gICAgICAgICAgICBwcml2YXRlKHNlbGYucGF0aClcbiAgICAgICAgICAgIHNlbGYuZGF0YSA9IGpzb24ubG9hZHMoc2VsZi5wYXRoLnJlYWRfdGV4dCgpKVxuICAgICAgICAgICAgcmVxdWlyZShzZWxmLmRhdGFbJ2lkZW50aXR5J10gPT0gaWRlbnRpdHksICdqb3VybmFsIGNhc2Uvc291cmNlIGlkZW50aXR5IGNoYW5nZWQnKVxuICAgICAgICBlbHNlOlxuICAgICAgICAgICAgc2VsZi5kYXRhID0geydpZGVudGl0eSc6IGlkZW50aXR5LCAncGhhc2VzJzoge319XG4gICAgICAgICAgICBzZWxmLnNhdmUoKVxuXG4gICAgZGVmIHNhdmUoc2VsZik6XG4gICAgICAgIHRlbXBvcmFyeSA9IHNlbGYucGFyZW50IC8gJ2V4ZWN1dGlvbi1qb3VybmFsLm5ldydcbiAgICAgICAgZmQgPSBvcy5vcGVuKHRlbXBvcmFyeSwgb3MuT19DUkVBVCB8IG9zLk9fRVhDTCB8IG9zLk9fTk9GT0xMT1cgfCBvcy5PX1dST05MWSwgMG82MDApXG4gICAgICAgIHRyeTpcbiAgICAgICAgICAgIHdpdGggb3MuZmRvcGVuKGZkLCAndycpIGFzIHN0cmVhbTpcbiAgICAgICAgICAgICAgICBqc29uLmR1bXAoc2VsZi5kYXRhLCBzdHJlYW0sIGluZGVudD0yKVxuICAgICAgICAgICAgICAgIHN0cmVhbS53cml0ZSgnXFxuJylcbiAgICAgICAgICAgICAgICBzdHJlYW0uZmx1c2goKVxuICAgICAgICAgICAgICAgIG9zLmZzeW5jKHN0cmVhbS5maWxlbm8oKSlcbiAgICAgICAgICAgIG9zLnJlcGxhY2UodGVtcG9yYXJ5LCBzZWxmLnBhdGgpXG4gICAgICAgICAgICBkaXJlY3RvcnkgPSBvcy5vcGVuKHNlbGYucGFyZW50LCBvcy5PX0RJUkVDVE9SWSB8IG9zLk9fUkRPTkxZKVxuICAgICAgICAgICAgdHJ5OlxuICAgICAgICAgICAgICAgIG9zLmZzeW5jKGRpcmVjdG9yeSlcbiAgICAgICAgICAgIGZpbmFsbHk6XG4gICAgICAgICAgICAgICAgb3MuY2xvc2UoZGlyZWN0b3J5KVxuICAgICAgICBleGNlcHQgQmFzZUV4Y2VwdGlvbjpcbiAgICAgICAgICAgICMgUmV0YWluIGV2aWRlbmNlIHJhdGhlciB0aGFuIG92ZXJ3cml0aW5nIGFuIHVuY2VydGFpbiBkdXJhYmxlIGludGVudC5cbiAgICAgICAgICAgIHJhaXNlXG5cbiAgICBkZWYgc3RhcnQoc2VsZiwgcGhhc2UsIGFwcHJvdmFsKTpcbiAgICAgICAgcmVxdWlyZShwaGFzZSBub3QgaW4gc2VsZi5kYXRhWydwaGFzZXMnXSwgJ2FscmVhZHkgYXR0ZW1wdGVkIHBoYXNlOyBubyByZXRyeScpXG4gICAgICAgIHByZWRlY2Vzc29ycyA9IGRpY3QoemlwKFBIQVNFU1sxOjhdLCBQSEFTRVNbOjddKSlcbiAgICAgICAgcHJlZGVjZXNzb3JzWydyb2xsYmFjay12ZXJpZnknXSA9ICdyb2xsYmFjaydcbiAgICAgICAgaWYgcGhhc2UgaW4gcHJlZGVjZXNzb3JzOlxuICAgICAgICAgICAgcmVxdWlyZShzZWxmLmRhdGFbJ3BoYXNlcyddLmdldChwcmVkZWNlc3NvcnNbcGhhc2VdLCB7fSkuZ2V0KCdzdGF0ZScpID09ICdDT01QTEVURScsXG4gICAgICAgICAgICAgICAgICAgICdwcmVjZWRpbmcgcGhhc2UgaXMgbm90IHZlcmlmaWVkIGNvbXBsZXRlJylcbiAgICAgICAgaWYgcGhhc2UgPT0gJ3JvbGxiYWNrJzpcbiAgICAgICAgICAgIHJlcXVpcmUoJ3JlbGVhc2UnIGluIHNlbGYuZGF0YVsncGhhc2VzJ10sICdyb2xsYmFjayByZXF1aXJlcyBhIHJlY29yZGVkIHJlbGVhc2UgYXR0ZW1wdCcpXG4gICAgICAgIHNlbGYuZGF0YVsncGhhc2VzJ11bcGhhc2VdID0geydzdGF0ZSc6ICdSVU5OSU5HJywgJ2FwcHJvdmFsJzogYXBwcm92YWwsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdhdHRlbXB0X2lkJzogc3RyKHV1aWQudXVpZDQoKSksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdzdGFydGVkX2F0JzogZGF0ZXRpbWUuZGF0ZXRpbWUubm93KGRhdGV0aW1lLnRpbWV6b25lLnV0YykuaXNvZm9ybWF0KCl9XG4gICAgICAgIHNlbGYuc2F2ZSgpXG5cbiAgICBkZWYgbm90ZShzZWxmLCBwaGFzZSwga2V5LCB2YWx1ZSk6XG4gICAgICAgIHJlcXVpcmUoc2VsZi5kYXRhWydwaGFzZXMnXVtwaGFzZV1bJ3N0YXRlJ10gPT0gJ1JVTk5JTkcnLCAncGhhc2UgaW50ZW50IG1pc3NpbmcnKVxuICAgICAgICBzZWxmLmRhdGFbJ3BoYXNlcyddW3BoYXNlXVtrZXldID0gdmFsdWVcbiAgICAgICAgc2VsZi5zYXZlKClcblxuICAgIGRlZiBmaW5pc2goc2VsZiwgcGhhc2UsIHJlc3VsdCk6XG4gICAgICAgIGVudHJ5ID0gc2VsZi5kYXRhWydwaGFzZXMnXVtwaGFzZV1cbiAgICAgICAgcmVxdWlyZShlbnRyeVsnc3RhdGUnXSA9PSAnUlVOTklORycsICdwaGFzZSBpbnRlbnQgbWlzc2luZycpXG4gICAgICAgIGVudHJ5WydyZXN1bHQnXSA9IHJlc3VsdFxuICAgICAgICBlbnRyeVsnc3RhdGUnXSA9ICdDT01QTEVURScgaWYgcmVzdWx0LmdldCgnYWxsX2NoZWNrc19wYXNzJykgaXMgVHJ1ZSBlbHNlICdVTkNFUlRBSU4nXG4gICAgICAgIHNlbGYuc2F2ZSgpXG5cbiAgICBkZWYgY2xvc2Uoc2VsZik6XG4gICAgICAgIG9zLmNsb3NlKHNlbGYubG9jaylcbiIKICAgIH0sCiAgICAiZXhlY3V0aW9uL2V4ZWN1dGlvbi1wbGFuLmpzb24iOiB7CiAgICAgICJzaGEyNTYiOiAiNzUwYTQzODYyNzdkYzc5YzMyMTNlNmFlZDE3YWU1Njg2MzMyYmZkNTg3YTU1ZTVlODdlYmYzNzc2YTYzYWIwYyIsCiAgICAgICJjb250ZW50IjogIntcbiAgXCJraW5kXCI6IFwiaW5mb3JtZWQtZGVjaXNpb24uc3VwZXJ2aXNlZC1tZWNoYW5pY2FsLXJlbGVhc2UtcGxhblwiLFxuICBcInZlcnNpb25cIjogMSxcbiAgXCJwcm9wb3NhbF9pZFwiOiBcIklORkQtV1AtMDAwOS1UMDUvemxpYjg1MDkxLXIxXCIsXG4gIFwidHJpYWxfaWRcIjogXCJpbmZkLXpsaWItMjAyNjEwMTEtcjFcIixcbiAgXCJzdGF0ZVwiOiBcInByZXBhcmVkLXVuYXBwcm92ZWRcIixcbiAgXCJleGVjdXRvcl9maWxlc1wiOiB7XG4gICAgXCJhY3R1YXRvci5weVwiOiBcImRhYmVkYTMzZmZlNTI5ODkzMzdlMjE0ZGM2ZTZkM2VmNWYwNDc5NzU2OTE0NWZhZjJmZjY5MDI0YjEwMWVjODBcIixcbiAgICBcImF1dGhvcml0eV9qb3VybmFsLnB5XCI6IFwiMmFhOWJhZTQwNzUyOTIyMjY0ZTUzMzgyZDg5OGUzMDgzYjhkMjI5YjE4NTMyOTI2Njk0MmJkZDM5NzAxMzEwOFwiLFxuICAgIFwibmF0aXZlX3Byb2dyYW0ucHlcIjogXCJmY2Y4ZmMzNTkzOTEwYjJlOTlkZDUxZDE1OWEzMGFhOWI4NTNjNDBmZjAwYzU2NmE2MjBkMjBlYWFkYzhhZjIwXCIsXG4gICAgXCJyZWNlaXB0X3NhZmV0eS5weVwiOiBcIjVjNTQzNzNmMmRjNGIwNjQwMDgyMjVjMTg2ZWZiMWQ1YWZkZWVjMzZkMTUzNTBiZGEwYTEyZGVkOTc2MDJkMWJcIlxuICB9LFxuICBcImNhc2VfZmlsZXNcIjoge1xuICAgIFwicG9zdC1yZWFkeS1uYXRpdmUtb3V0Y29tZXMtY29tbWFuZC5weVwiOiBcImNlODhhY2E1ZTJkNmNkNWFiNzUzMzg5OGJjOWIyNDZiZDE1OTQ4MmM0YWNjY2IwYWZlNGM0NTFlZTg4ZjU3ZmFcIixcbiAgICBcInNjYW4tcmVjZWlwdC5qc29uXCI6IFwiYWFhYTkxMDYxYmIwMzZlNmQyMjhiMWMxYmQ0MzMzNTc4ZGM5NDhjYjJiYjVhYzhiYTI2OTQyOTEyMWU3MzIxYVwiLFxuICAgIFwic291cmNlLWNvbW1pdC1wcm9vZi5qc29uXCI6IFwiYjA4MTFkYTJkM2JkNTkzODczM2YwMDc4ZTVmZWM4ZTE5YTZkOTc1MmM0Mjc3YjgwYmZlOTgzN2VkNzJjNmEzYlwiLFxuICAgIFwiY2FuZGlkYXRlLWRlcGxveW1lbnQuanNvblwiOiBcIjQ4N2Y4MzRjYzg2ZGNiYzdhMDRjMjM5MmJkZTA3MmVjYWY4N2RjMTE2YjRiYWExNmMzNWViOWM1OTlhZDVjOTVcIixcbiAgICBcImFydGlmYWN0LWNvbXBhcmlzb24uanNvblwiOiBcImExYzVkOWUyNDlkYzAwNjAwNDQ5OTk4MzYwZmFmNmU4ZGEwMjA2YjU2OGM3NmQxNTU2MDJjNDY0NWZhNWZjZTRcIixcbiAgICBcInJvbGxiYWNrLWRlcGxveW1lbnQuanNvblwiOiBcImY4YTlhMDdhYmMyZWI1MmNkZjZiY2RiYTJkOWY3OWRlYzA3MzE0NWMzMWQwNDQ0YTllMGZlMTE0N2U0MmNjNGVcIixcbiAgICBcInNxbGl0ZV9yZWFkb25seS5weVwiOiBcIjkwOTNjNjRmN2YwZTM5YjI0OWRhOWZlZmRmNWFhZjRlNDI1OGJjMzFlN2EyN2ZkODg4NDc5Y2UzMjlhMjA1MDlcIixcbiAgICBcInB1Ymxpc2hlZC12ZXJpZmljYXRpb24uanNvblwiOiBcImRkNGVlY2YwYzEwM2RjYTc5MjIwOTRhOGJlZWI5OWI3N2FlOTZmZDFkNWZlNTMyZTM3YmQ4MzNkYWEwMjYxNGVcIixcbiAgICBcInNlYWxlZC1uYXRpdmUtZGVwZW5kZW5jaWVzLmpzb25cIjogXCIwNDIzMjdhMDM2ZWQyNTRjZjk1ZGJlYjBjMDU2YWMyNWIwM2E2YTg0NGI5NWEzMzJjOTkyZGYyOTQzNjkxMzY5XCIsXG4gICAgXCJzZWFsZWQtbGl2ZS1kZXBsb3ltZW50Lmpzb25cIjogXCJmOGE5YTA3YWJjMmViNTJjZGY2YmNkYmEyZDlmNzlkZWMwNzMxNDVjMzFkMDQ0NGE5ZTBmZTExNDdlNDJjYzRlXCIsXG4gICAgXCJuYXRpdmUtcHJlZmxpZ2h0Lmpzb25cIjogXCI1NjYzNzk5NGRmZGFlNDZhNmY5YWI0NDJjMmZmOWIyNTkyMTI2NTBiMzVlYjk4ZDg5NmM1ZTgzN2YwMWNlMmY5XCIsXG4gICAgXCJmcmVzaC1zY2FuLXJlY2VpcHQtdGVtcGxhdGUuanNvblwiOiBcIjkyYmJmOGE4OTFjN2VmNzhkZjQ4NDMwOTAxMjE5ZDhkMDcwNzNjYmE3NDExMmEzMDdhNmQ4YmRlYzUwOWU4ZGRcIixcbiAgICBcInJvbGxiYWNrLXByZWZsaWdodC5qc29uXCI6IFwiMDE4ODc5OWQzNTJlNGQ0OWQ1Y2EyOWQ2ZTE5Nzc0MmQwMmU5NmE0YjBiMzk5ZGYxZjQ3OGU0MWIyNTI5MmViNVwiLFxuICAgIFwiaW5zdGFsbGVkLXpsaWItcHJvb2YuanNvblwiOiBcIjZhZjYyZjg2ZjUwYWMxODVmYWI5NDE2M2MyNGFjODE1MGVhZDY5ZTYyZjhkODI1YjBkY2UxZTc1YTVhNTdiNjhcIixcbiAgICBcInJ1bnRpbWUtcmVjZWlwdC5qc29uXCI6IFwiNjQ0MjY3NmRjZmQ5NDFmMGEwNWE0Y2I3OWEwMmRlYTgzOWZjZDMzMmQ4MmIyMTU0YzE1Y2Y2ZGI2OGRlNTg4OVwiLFxuICAgIFwiY2hhbmdlX2d1YXJkLnB5XCI6IFwiYjNlZTJiODlkZTE0ZTIwNGVmZWRkMDE2ZDM5MDBiNmUzMWZjYzlmMGVhMGYzMTI1ZDNhNDc5NGQ5MzhjYTIwMVwiLFxuICAgIFwiY2hlY2tfaW5zdGFsbGVkLnB5XCI6IFwiYTM0YTUxYmU5YzlmMmIxNmEwNmE4ODE3M2I1YzZjNDkyYzk5NTdmZTAyMWNmNDEyYTg2YTE3Mzc3MzA2NDBhZFwiLFxuICAgIFwiY2FuZGlkYXRlLXJ1bnRpbWUuanNvblwiOiBcImQ1OGU2MGJkYzY5NDU4ZmU0MmFmODI0OGUwMmFjZDkyNGIzYjYwNWM4Mjg4NzczNTJhN2Q5YTM5NTNlOTAxMjRcIlxuICB9LFxuICBcIm93bmVyX3NvdXJjZV9maWxlc1wiOiB7XG4gICAgXCJkZXBsb3kvbGlmZWN5Y2xlL2FkbWlzc2lvbi5jYW5kaWRhdGUuanNvblwiOiBcIjZjNzZjZWRkMGFiYzRhNDI2MWNmY2I2OWM2NTk3NjExYzI4MmJkNGZlZDhiNmFhZjc3YWZmMDRmZGY5NDI1MjlcIixcbiAgICBcImRlcGxveS9saWZlY3ljbGUvc2VydmljZS5jYW5kaWRhdGUuanNvblwiOiBcIjM4MzMzMmNkNGZkMjdhOGIzNmU2M2E0ZDc5YWRkYzQzZWI2ODYxNWY3NDIzMjhkNDU3N2JmYzJiNDY3ZTIyNzhcIixcbiAgICBcImRlcGxveS9saWZlY3ljbGUva3VzdG9taXphdGlvbi55YW1sXCI6IFwiMWJiY2U4MWJjNjc1ODQzNDM1NDE1N2Y4NjAxNTM0MzIwYzVkMDI5MDU4NzkwOGZkMTVhNmQyNWUzZmE1N2Y0N1wiLFxuICAgIFwiZGVwbG95L2xpZmVjeWNsZS9yZWdpc3RyaWVzLmNhbmRpZGF0ZS5qc29uXCI6IFwiOWJhZDZkYjVmYmMyMTc4ZDMxYjA0MTIwMDE2YTQxODMwYzM1NTJmYmI5ZTI1MTdiNzE4ZjliYmU4N2NmNjQ5ZVwiXG4gIH0sXG4gIFwiY2FuZGlkYXRlX2ltYWdlXCI6IFwiZm9yZ2Vqby5jb3Vsb21iLnNvY2lhbC9jb3Vsb21iL2luZm9ybWVkLWRlY2lzaW9uQHNoYTI1NjpmNTU4OGY0OTZiMWYyNjdkMDdjZGM5ZjUyMDhlNmYyZjI0MWY3NGI3MzljMjM5MjRiMDM3MDJkZWMxM2E1YzIzXCIsXG4gIFwicHJpb3JfaW1hZ2VcIjogXCJmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2Ojc2M2MzNjQwMmM4YmU3Y2M0YzFiZDFhNjE2MjNkMWFmZjkxMTZkNTIyNDE4Nzc0MjViNDVhNzAxOGNjMDg4M2FcIixcbiAgXCJzb3VyY2VfcmV2aXNpb25cIjogXCJtdXN0LWJlLWV4YWN0LWZpbmFsLW93bmVyLUhFQUQtd2l0aC1hcHByb3ZlZC1mb3VyLUdpdC1ibG9ic1wiLFxuICBcInBoYXNlc1wiOiBbXG4gICAgXCJkcmlsbC1iYWNrdXBcIixcbiAgICBcImRyaWxsLWNvcHlcIixcbiAgICBcIm9mZmxpbmUtdmVyaWZ5XCIsXG4gICAgXCJjbGVhbnVwLW9mZmxpbmVcIixcbiAgICBcImN1dG92ZXItYmFja3VwXCIsXG4gICAgXCJjdXRvdmVyLWNvcHlcIixcbiAgICBcInJlbGVhc2VcIixcbiAgICBcInZlcmlmeVwiLFxuICAgIFwicm9sbGJhY2tcIixcbiAgICBcInJvbGxiYWNrLXZlcmlmeVwiXG4gIF0sXG4gIFwiYXV0aG9yaXphdGlvblwiOiBcImZyZXNoIGV4dGVybmFsbHkgYXV0aGVudGljYXRlZCByb290IGZpeGVkLW9yaWdpbiBuYXRpdmUgcmVhZGJhY2sgYmVmb3JlIGVhY2ggcGhhc2U7IGV4YWN0IGNvbmZpcm1lZCBwbGF0Zm9ybS1yb290IGRpc3Bvc2l0aW9uIGFuZCBjdXJyZW50IHBhY2tldCBTSEEvc291cmNlIGNvbW1pdDsgcmVjZWlwdCBib29ra2VlcGluZyBpcyBub3QgYW4gYXV0b25vbW91cyBzaWduZWQgZ3JhbnRcIixcbiAgXCJleGVjdXRlX2J5XCI6IFwiMjAyNi0xMC0xMlQyMzo1OTo1OVpcIixcbiAgXCJyYXdfb3V0cHV0X3dpdGhoZWxkXCI6IHRydWUsXG4gIFwibmF0aXZlX2RhdGFfb3Jfd29ya2xvYWRfYWN0aW9uX2V4ZWN1dGVkXCI6IGZhbHNlLFxuICBcImRlYWRsaW5lX3NlbWFudGljc1wiOiBcIkFsbCBwaGFzZXMgaW5jbHVkaW5nIHZlcmlmaWNhdGlvbi9yZWNvdmVyeSBtdXN0IGJlZ2luIGJlZm9yZSBleGVjdXRlX2J5LiBObyBleHRlbnNpb24gb3IgYXV0b21hdGljIGVtZXJnZW5jeSBncmFudDsgYmV5b25kIGRlYWRsaW5lIHN0b3AgYW5kIG9idGFpbiBhIGZyZXNoIGZvdW5kZXIgZGVjaXNpb24uIENvbXBsZXRlIHRoZSBib3VuZGVkIGRyaWxsIGFuZCByb2xsb3V0IGVhcmx5IGVub3VnaCB0byBhbGxvdyByZWNvdmVyeSB3aXRoaW4gdGhlIHdpbmRvdy5cIixcbiAgXCJzb3VyY2VfbWVtb192ZXJzaW9uXCI6IDEsXG4gIFwibmF0aXZlX3Jldmlld19tZW1vX3ZlcnNpb25cIjogMSxcbiAgXCJwdWJsaXNoZWRfYXJ0aWZhY3RfdmVyaWZpZWRcIjogdHJ1ZVxufVxuIgogICAgfSwKICAgICJleGVjdXRpb24vbmF0aXZlX3Byb2dyYW0ucHkiOiB7CiAgICAgICJzaGEyNTYiOiAiZmNmOGZjMzU5MzkxMGIyZTk5ZGQ1MWQxNTlhMzBhYTliODUzYzQwZmYwMGM1NjZhNjIwZDIwZWFhZGM4YWYyMCIsCiAgICAgICJjb250ZW50IjogIlwiXCJcIkZpeGVkIFQwNSByZW1vdGUgc2NyaXB0OyBzb3VyY2UgcHJlcGFyYXRpb24gbmV2ZXIgZGlzcGF0Y2hlcyBpdC5cIlwiXCJcblBST0dSQU0gPSAnXFxuaW1wb3J0IGNvcHksZGF0ZXRpbWUsaGFzaGxpYixqc29uLG9zLHJlLHN1YnByb2Nlc3Msc3lzXFxuaWYgc3lzLmZsYWdzLm9wdGltaXplOnJhaXNlIFJ1bnRpbWVFcnJvcihcXCdvcHRpbWl6ZWQgaW50ZXJwcmV0ZXIgcmVmdXNlZFxcJylcXG5wYXlsb2FkPWpzb24ubG9hZHMoX19QQVlMT0FEX18pXFxuZXhlYyhfX0dVQVJEX18pXFxucGhhc2U9cGF5bG9hZFtcXCdwaGFzZVxcJ11cXG5pZiBwaGFzZSBub3QgaW4gKFxcJ2RyaWxsLWJhY2t1cFxcJyxcXCdjdXRvdmVyLWJhY2t1cFxcJyxcXCdkcmlsbC1jb3B5XFwnLFxcJ2N1dG92ZXItY29weVxcJyxcXCdyZWxlYXNlXFwnLFxcJ3ZlcmlmeVxcJyxcXCdyb2xsYmFja1xcJyxcXCdyb2xsYmFjay12ZXJpZnlcXCcpOnJhaXNlIFZhbHVlRXJyb3IoXFwnZml4ZWQgbmF0aXZlIHBoYXNlIHJlcXVpcmVkXFwnKVxcbmRlYWRsaW5lPWRhdGV0aW1lLmRhdGV0aW1lLmZyb21pc29mb3JtYXQoXFwnMjAyNi0xMC0xMlQyMzo1OTo1OSswMDowMFxcJylcXG5kZWYgcmVxdWlyZSh2YWx1ZSxyZWFzb24pOlxcbiBpZiBub3QgdmFsdWU6cmFpc2UgVmFsdWVFcnJvcihyZWFzb24pXFxucmVxdWlyZShkYXRldGltZS5kYXRldGltZS5ub3coZGF0ZXRpbWUudGltZXpvbmUudXRjKTxkZWFkbGluZSxcXCdleGVjdXRpb24gd2luZG93IGV4cGlyZWRcXCcpXFxuZW52PXsqKm9zLmVudmlyb24sXFwnS1VCRUNPTkZJR1xcJzpcXCcvZXRjL3JhbmNoZXIvazNzL2szcy55YW1sXFwnfVxcbmRlZiBydW4oYXJndixib2R5PU5vbmUsdGltZW91dD05MCk6XFxuIHJldHVybiBzdWJwcm9jZXNzLnJ1bihhcmd2LGlucHV0PWJvZHksdGV4dD1UcnVlLGNhcHR1cmVfb3V0cHV0PVRydWUsdGltZW91dD10aW1lb3V0LGVudj1lbnYpXFxuZGVmIHJlYWQoa2luZCxuYW1lLG5hbWVzcGFjZSk6XFxuIHJlc3VsdD1ydW4oW1xcJ2t1YmVjdGxcXCcsXFwnZ2V0XFwnLGtpbmQsbmFtZSxcXCctblxcJyxuYW1lc3BhY2UsXFwnLW9cXCcsXFwnanNvblxcJ10pXFxuIHJlcXVpcmUocmVzdWx0LnJldHVybmNvZGU9PTAsXFwnZml4ZWQgbWV0YWRhdGEgcmVhZCByZWZ1c2VkXFwnKVxcbiByZXR1cm4ganNvbi5sb2FkcyhyZXN1bHQuc3Rkb3V0KVxcbnNlYWxlZD1wYXlsb2FkW1xcJ3NlYWxlZFxcJ107IGRlcGVuZGVuY2llcz1wYXlsb2FkW1xcJ2RlcGVuZGVuY2llc1xcJ11cXG5saXZlPXJlYWQoXFwnZGVwbG95bWVudFxcJyxcXCdpbmZvcm1lZC1kZWNpc2lvblxcJyxcXCdpbmZvcm1lZC1kZWNpc2lvblxcJylcXG5jbT1yZWFkKFxcJ2NvbmZpZ21hcFxcJyxcXCdpbmZvcm1lZC1kZWNpc2lvbi1ydW50aW1lLTZjMjIxODc4ZDA1MVxcJyxcXCdpbmZvcm1lZC1kZWNpc2lvblxcJylcXG5wdmM9cmVhZChcXCdwdmNcXCcsXFwnaW5mb3JtZWQtZGVjaXNpb24tZGF0YVxcJyxcXCdpbmZvcm1lZC1kZWNpc2lvblxcJylcXG5jaGVja19kZXBlbmRlbmNpZXMoY20scHZjLGRlcGVuZGVuY2llcylcXG5hcHA9cmVhZChcXCdhcHBsaWNhdGlvblxcJyxcXCdpbmZvcm1lZC1kZWNpc2lvblxcJyxcXCdhcmdvY2RcXCcpXFxucmVxdWlyZShhcHBbXFwnbWV0YWRhdGFcXCddW1xcJ3VpZFxcJ109PVxcJzQ5OTg2OGYyLWVhYmMtNGRmNC1hZTMxLWI4OWNlNThkNzUwZFxcJyxcXCdBcHBsaWNhdGlvbiBVSUQgY2hhbmdlZFxcJylcXG5yZXF1aXJlKGFwcFtcXCdzcGVjXFwnXVtcXCdzb3VyY2VcXCddW1xcJ3JlcG9VUkxcXCddPT1cXCdodHRwczovL2Zvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbi5naXRcXCdcXG4gYW5kIGFwcFtcXCdzcGVjXFwnXVtcXCdzb3VyY2VcXCddW1xcJ3BhdGhcXCddPT1cXCdkZXBsb3kvbGlmZWN5Y2xlXFwnXFxuIGFuZCBhcHBbXFwnc3BlY1xcJ11bXFwnZGVzdGluYXRpb25cXCddPT17XFwnbmFtZXNwYWNlXFwnOlxcJ2luZm9ybWVkLWRlY2lzaW9uXFwnLFxcJ3NlcnZlclxcJzpcXCdodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmNcXCd9XFxuIGFuZCBhcHBbXFwnc3BlY1xcJ10uZ2V0KFxcJ3N5bmNQb2xpY3lcXCcse30pLmdldChcXCdhdXRvbWF0ZWRcXCcpIGlzIE5vbmVcXG4gYW5kIHNldChhcHBbXFwnc3BlY1xcJ11bXFwnc291cmNlXFwnXSk9PXtcXCdyZXBvVVJMXFwnLFxcJ3BhdGhcXCcsXFwndGFyZ2V0UmV2aXNpb25cXCd9IGFuZCBub3QgYXBwW1xcJ3NwZWNcXCddLmdldChcXCdzb3VyY2VzXFwnKSxcXCdBcHBsaWNhdGlvbiBvd25lci9zY29wZSBjaGFuZ2VkXFwnKVxcbmV4cGVjdGVkPWNvcHkuZGVlcGNvcHkoc2VhbGVkKVxcbmlmIHBoYXNlPT1cXCd2ZXJpZnlcXCc6ZXhwZWN0ZWRbXFwnc3BlY1xcJ11bXFwndGVtcGxhdGVcXCddW1xcJ3NwZWNcXCddW1xcJ2NvbnRhaW5lcnNcXCddWzBdW1xcJ2ltYWdlXFwnXT1DQU5ESURBVEVcXG5pZiBwaGFzZT09XFwncm9sbGJhY2tcXCc6XFxuIGltYWdlPWxpdmVbXFwnc3BlY1xcJ11bXFwndGVtcGxhdGVcXCddW1xcJ3NwZWNcXCddW1xcJ2NvbnRhaW5lcnNcXCddWzBdW1xcJ2ltYWdlXFwnXVxcbiByZXF1aXJlKGltYWdlIGluIChCQVNFLENBTkRJREFURSksXFwncm9sbGJhY2sgaW1hZ2UgaXMgb3V0c2lkZSBzYW1lLXNjaGVtYSBwYWlyXFwnKVxcbiBleHBlY3RlZFtcXCdzcGVjXFwnXVtcXCd0ZW1wbGF0ZVxcJ11bXFwnc3BlY1xcJ11bXFwnY29udGFpbmVyc1xcJ11bMF1bXFwnaW1hZ2VcXCddPWltYWdlXFxuY2hlY2tfY3VycmVudChsaXZlLGV4cGVjdGVkKVxcbmlmIHBoYXNlIT1cXCdyb2xsYmFja1xcJzpcXG4gcmVxdWlyZShsaXZlLmdldChcXCdzdGF0dXNcXCcse30pLmdldChcXCdyZWFkeVJlcGxpY2FzXFwnKT09MVxcbiAgYW5kIGxpdmVbXFwnc3RhdHVzXFwnXS5nZXQoXFwndXBkYXRlZFJlcGxpY2FzXFwnKT09MVxcbiAgYW5kIGxpdmVbXFwnc3RhdHVzXFwnXS5nZXQoXFwnb2JzZXJ2ZWRHZW5lcmF0aW9uXFwnKT09bGl2ZVtcXCdtZXRhZGF0YVxcJ11bXFwnZ2VuZXJhdGlvblxcJ10sXFwnc2VydmljZSBub3QgUmVhZHkxXFwnKVxcbnRhcmdldD1wYXlsb2FkW1xcJ3NvdXJjZV9yZXZpc2lvblxcJ10gaWYgcGhhc2U9PVxcJ3ZlcmlmeVxcJyBlbHNlIFxcJ2M5NmIwYzZkODgxZTg1NWQxZDE1ODAwM2M2ZGU5MGVmMzkwMjYyZTJcXCdcXG5pZiBwaGFzZT09XFwncm9sbGJhY2tcXCc6cmVxdWlyZShhcHBbXFwnc3BlY1xcJ11bXFwnc291cmNlXFwnXVtcXCd0YXJnZXRSZXZpc2lvblxcJ10gaW4gKHRhcmdldCxwYXlsb2FkW1xcJ3NvdXJjZV9yZXZpc2lvblxcJ10pLFxcJ3JvbGxiYWNrIHNvdXJjZSBpcyBvdXRzaWRlIHBhaXJcXCcpXFxuZWxzZTpyZXF1aXJlKGFwcFtcXCdzcGVjXFwnXVtcXCdzb3VyY2VcXCddW1xcJ3RhcmdldFJldmlzaW9uXFwnXT09dGFyZ2V0LFxcJ3VuZXhwZWN0ZWQgc291cmNlIHBpblxcJylcXG5yZXN1bHQ9e1xcJ3BoYXNlXFwnOnBoYXNlLFxcJ2FsbF9jaGVja3NfcGFzc1xcJzpGYWxzZSxcXCdyYXdfb3V0cHV0X3dpdGhoZWxkXFwnOlRydWUsXFxuIFxcJ25hdGl2ZV9hcHByb3ZhbF92ZXJpZmllZF9ieV9yb290XFwnOnBheWxvYWRbXFwnYXBwcm92YWxcXCddLFxcJ25hdGl2ZV9hdXRoZW50aWNhdGVkX2dyYW50XFwnOkZhbHNlfVxcbnRyeTpcXG4gaWYgcGhhc2UgaW4gKFxcJ2RyaWxsLWJhY2t1cFxcJyxcXCdjdXRvdmVyLWJhY2t1cFxcJyxcXCdkcmlsbC1jb3B5XFwnLFxcJ2N1dG92ZXItY29weVxcJyxcXCdyZWxlYXNlXFwnLFxcJ3ZlcmlmeVxcJyxcXCdyb2xsYmFjay12ZXJpZnlcXCcpOlxcbiAgcG9kbGlzdD1ydW4oW1xcJ2t1YmVjdGxcXCcsXFwnZ2V0XFwnLFxcJ3BvZHNcXCcsXFwnLW5cXCcsXFwnaW5mb3JtZWQtZGVjaXNpb25cXCcsXFwnLWxcXCcsXFwnYXBwLmt1YmVybmV0ZXMuaW8vbmFtZT1pbmZvcm1lZC1kZWNpc2lvblxcJyxcXCctb1xcJyxcXCdqc29uXFwnXSlcXG4gIHJlcXVpcmUocG9kbGlzdC5yZXR1cm5jb2RlPT0wLFxcJ3BvZCBtZXRhZGF0YSBxdWVyeSByZWZ1c2VkXFwnKVxcbiAgcG9kcz1bcCBmb3IgcCBpbiBqc29uLmxvYWRzKHBvZGxpc3Quc3Rkb3V0KVtcXCdpdGVtc1xcJ10gaWYgbm90IHBbXFwnbWV0YWRhdGFcXCddLmdldChcXCdkZWxldGlvblRpbWVzdGFtcFxcJyldXFxuICByZXF1aXJlKGxlbihwb2RzKT09MSxcXCdleGFjdCBzaW5nbGUgbGl2ZSBvd25lciBwb2QgcmVxdWlyZWRcXCcpO3BvZD1wb2RzWzBdXFxuICByZXF1aXJlKHBvZFtcXCdzdGF0dXNcXCddLmdldChcXCdwaGFzZVxcJyk9PVxcJ1J1bm5pbmdcXCcgYW5kIGxlbihwb2RbXFwnc3BlY1xcJ11bXFwnY29udGFpbmVyc1xcJ10pPT0xXFxuICAgYW5kIHBvZFtcXCdzcGVjXFwnXVtcXCdjb250YWluZXJzXFwnXVswXVtcXCdpbWFnZVxcJ109PWV4cGVjdGVkW1xcJ3NwZWNcXCddW1xcJ3RlbXBsYXRlXFwnXVtcXCdzcGVjXFwnXVtcXCdjb250YWluZXJzXFwnXVswXVtcXCdpbWFnZVxcJ11cXG4gICBhbmQgbGVuKHBvZFtcXCdzdGF0dXNcXCddLmdldChcXCdjb250YWluZXJTdGF0dXNlc1xcJyxbXSkpPT0xXFxuICAgYW5kIHBvZFtcXCdzdGF0dXNcXCddW1xcJ2NvbnRhaW5lclN0YXR1c2VzXFwnXVswXS5nZXQoXFwnbmFtZVxcJyk9PVxcJ2luZm9ybWVkLWRlY2lzaW9uXFwnXFxuICAgYW5kIHBvZFtcXCdzdGF0dXNcXCddW1xcJ2NvbnRhaW5lclN0YXR1c2VzXFwnXVswXS5nZXQoXFwncmVhZHlcXCcpIGlzIFRydWVcXG4gICBhbmQgcG9kW1xcJ3N0YXR1c1xcJ11bXFwnY29udGFpbmVyU3RhdHVzZXNcXCddWzBdLmdldChcXCdpbWFnZUlEXFwnLFxcJ1xcJykuZW5kc3dpdGgoXFwnQFxcJytleHBlY3RlZFtcXCdzcGVjXFwnXVtcXCd0ZW1wbGF0ZVxcJ11bXFwnc3BlY1xcJ11bXFwnY29udGFpbmVyc1xcJ11bMF1bXFwnaW1hZ2VcXCddLnNwbGl0KFxcJ0BcXCcsMSlbMV0pLFxcJ293bmVyIHBvZCByZWFkaW5lc3MvaW1hZ2UgY2hhbmdlZFxcJylcXG4gIGlmIHBoYXNlIGluIChcXCdkcmlsbC1iYWNrdXBcXCcsXFwnZHJpbGwtY29weVxcJyxcXCdjdXRvdmVyLWJhY2t1cFxcJyxcXCdjdXRvdmVyLWNvcHlcXCcsXFwncmVsZWFzZVxcJyk6cmVxdWlyZShwb2RbXFwnbWV0YWRhdGFcXCddW1xcJ3VpZFxcJ109PVxcJ2M0Y2IxNTUwLWZlZGEtNDA5Ny05ZGU0LWQ4OTFhNjU3YjY2MlxcJyxcXCdvcmlnaW5hbCBiYWNrdXAgUG9kIGlkZW50aXR5IGNoYW5nZWRcXCcpXFxuICB0ZW1wbGF0ZT1leHBlY3RlZFtcXCdzcGVjXFwnXVtcXCd0ZW1wbGF0ZVxcJ11bXFwnc3BlY1xcJ11cXG4gIGNvbnRyb2xsZWQ9c2V0KHRlbXBsYXRlKXx7XFwnaW5pdENvbnRhaW5lcnNcXCcsXFwnaG9zdE5ldHdvcmtcXCcsXFwnaG9zdFBJRFxcJyxcXCdob3N0SVBDXFwnLFxcJ3J1bnRpbWVDbGFzc05hbWVcXCcsXFwnc2hhcmVQcm9jZXNzTmFtZXNwYWNlXFwnfVxcbiAgcmVxdWlyZShhbGwocG9kW1xcJ3NwZWNcXCddLmdldChrZXkpPT10ZW1wbGF0ZS5nZXQoa2V5KSBmb3Iga2V5IGluIGNvbnRyb2xsZWQpLFxcJ2FjdHVhbCBvd25lciBQb2QgY29uZmlndXJhdGlvbiBkcmlmdGVkXFwnKVxcbiAgb3duZXJzPXBvZFtcXCdtZXRhZGF0YVxcJ10uZ2V0KFxcJ293bmVyUmVmZXJlbmNlc1xcJyxbXSk7cmVxdWlyZShsZW4ob3duZXJzKT09MSBhbmQgb3duZXJzWzBdW1xcJ2tpbmRcXCddPT1cXCdSZXBsaWNhU2V0XFwnLFxcJ3BvZCBvd25lciBjaGFpbiBjaGFuZ2VkXFwnKVxcbiAgcnM9cmVhZChcXCdyZXBsaWNhc2V0XFwnLG93bmVyc1swXVtcXCduYW1lXFwnXSxcXCdpbmZvcm1lZC1kZWNpc2lvblxcJylcXG4gIHJlcXVpcmUocnNbXFwnbWV0YWRhdGFcXCddW1xcJ3VpZFxcJ109PW93bmVyc1swXVtcXCd1aWRcXCddIGFuZCBhbnkoby5nZXQoXFwndWlkXFwnKT09c2VhbGVkW1xcJ21ldGFkYXRhXFwnXVtcXCd1aWRcXCddXFxuICAgYW5kIG8uZ2V0KFxcJ2tpbmRcXCcpPT1cXCdEZXBsb3ltZW50XFwnIGZvciBvIGluIHJzW1xcJ21ldGFkYXRhXFwnXS5nZXQoXFwnb3duZXJSZWZlcmVuY2VzXFwnLFtdKSksXFwnUmVwbGljYVNldCBvd25lcnNoaXAgY2hhbmdlZFxcJylcXG4gIGlubmVyPXJcIlwiXCJcXG5pbXBvcnQgaGFzaGxpYixqc29uLG9zLHN0YXQsc3VicHJvY2VzcyxzeXNcXG5pZiBzeXMuZmxhZ3Mub3B0aW1pemU6cmFpc2UgUnVudGltZUVycm9yKFxcJ29wdGltaXplZCBpbnRlcnByZXRlciByZWZ1c2VkXFwnKVxcbmV4ZWMoX19SRUFET05MWV9fKVxcbmV4ZWMoX19TQUZFVFlfXylcXG5hY3Rpb249X19BQ1RJT05fXztkYj1cXCcvZGF0YS9wcml2YXRlL3Jldmlldy5zcWxpdGVcXCc7Zm9sZGVyPVxcJy9kYXRhL3ByaXZhdGUvemxpYjg1MDkxLXIxXFwnXFxuZGVmIHJlcXVpcmUodixyKTpcXG4gaWYgbm90IHY6cmFpc2UgVmFsdWVFcnJvcihyKVxcbnJlcXVpcmUob3MuZ2V0dWlkKCk9PTEwMDAxIGFuZCBvcy5nZXRnaWQoKT09MTAwMDEsXFwnbmF0aXZlIG93bmVyIFVJRC9HSUQgcmVxdWlyZWRcXCcpXFxuZGVmIHByaXZhdGUocCxkaXJlY3Rvcnk9RmFsc2UpOlxcbiBzPW9zLmxzdGF0KHApO3JlcXVpcmUoKHN0YXQuU19JU0RJUihzLnN0X21vZGUpIGlmIGRpcmVjdG9yeSBlbHNlIHN0YXQuU19JU1JFRyhzLnN0X21vZGUpKVxcbiAgYW5kIHMuc3RfdWlkPT0xMDAwMSBhbmQgc3RhdC5TX0lNT0RFKHMuc3RfbW9kZSk9PSgwbzcwMCBpZiBkaXJlY3RvcnkgZWxzZSAwbzYwMClcXG4gIGFuZCAoZGlyZWN0b3J5IG9yIHMuc3Rfbmxpbms9PTEpLFxcJ3ByaXZhdGUgbmF0aXZlIGN1c3RvZHkgcmVxdWlyZWRcXCcpXFxucHJpdmF0ZShcXCcvZGF0YS9wcml2YXRlXFwnLFRydWUpO3ByaXZhdGUoZGIpXFxuYmFzZWxpbmU9Y291bnRzKGluc3BlY3RfcmVhZG9ubHkoZGIpKSAjIE1VU1QgcHJlY2VkZSBhbnkgU3RvcmUvYWRtaW4gb3Blbi5cXG5pZiBhY3Rpb24gaW4gKFxcJ2RyaWxsLWJhY2t1cFxcJyxcXCdjdXRvdmVyLWJhY2t1cFxcJyk6XFxuIHNpemU9b3Muc3RhdChkYikuc3Rfc2l6ZVxcbiBpZiBvcy5wYXRoLmV4aXN0cyhkYitcXCctd2FsXFwnKTpzaXplKz1vcy5zdGF0KGRiK1xcJy13YWxcXCcpLnN0X3NpemVcXG4gdj1vcy5zdGF0dmZzKFxcJy9kYXRhL3ByaXZhdGVcXCcpO2ZyZWU9di5mX2JhdmFpbCp2LmZfZnJzaXplXFxuIHJlcXVpcmUoc2l6ZT4wIGFuZCBzaXplPD01MTIqMTAyNCoqMiBhbmQgZnJlZT49c2l6ZSooMiBpZiBhY3Rpb249PVxcJ2RyaWxsLWJhY2t1cFxcJyBlbHNlIDEpKzE2KjEwMjQqKjIsXFwnYm91bmRlZCBuYXRpdmUgc25hcHNob3QgY2FwYWNpdHkgcmVxdWlyZWRcXCcpXFxuIGlmIGFjdGlvbj09XFwnZHJpbGwtYmFja3VwXFwnOlxcbiAgcmVxdWlyZShub3Qgb3MucGF0aC5sZXhpc3RzKGZvbGRlciksXFwnbmF0aXZlIGNhc2UgZGlyZWN0b3J5IG11c3QgYmUgdW5pcXVlXFwnKVxcbiAgb3MubWtkaXIoZm9sZGVyLDBvNzAwKTtvcy5jaG1vZChmb2xkZXIsMG83MDApXFxuIHByaXZhdGUoZm9sZGVyLFRydWUpXFxuIGZpbGVuYW1lPVxcJ2RyaWxsLnNxbGl0ZVxcJyBpZiBhY3Rpb249PVxcJ2RyaWxsLWJhY2t1cFxcJyBlbHNlIFxcJ2N1dG92ZXIuc3FsaXRlXFwnO291dHB1dD1mb2xkZXIrXFwnL1xcJytmaWxlbmFtZVxcbiByZXF1aXJlKG5vdCBvcy5wYXRoLmxleGlzdHMob3V0cHV0KSxcXCdzbmFwc2hvdCBvdXRwdXQgbXVzdCBiZSB1bmlxdWVcXCcpXFxuIGFkbWluPXN1YnByb2Nlc3MucnVuKFtcXCdpbmZvcm1lZC1kZWNpc2lvbi1hZG1pblxcJyxcXCdiYWNrdXBcXCcsXFwnLS1kYlxcJyxkYixcXCctLW91dHB1dFxcJyxvdXRwdXRdLGNhcHR1cmVfb3V0cHV0PVRydWUsdGV4dD1UcnVlLHRpbWVvdXQ9MTIwKVxcbiByZXF1aXJlKGFkbWluLnJldHVybmNvZGU9PTAgYW5kIGpzb24ubG9hZHMoYWRtaW4uc3Rkb3V0KT09e1xcJ3N0YXR1c1xcJzpcXCdiYWNrZWRfdXBcXCcsXFwnY29uc2lzdGVudF9zbmFwc2hvdFxcJzpUcnVlfSxcXCdiYWNrdXAgY29tcGxldGlvbiBub3QgdmVyaWZpZWQ7IGRvIG5vdCByZXRyeVxcJylcXG4gcHJpdmF0ZShvdXRwdXQpXFxuIGZvciBzdWZmaXggaW4gKFxcJy13YWxcXCcsKTpcXG4gIHJlcXVpcmUobm90IG9zLnBhdGguZXhpc3RzKG91dHB1dCtzdWZmaXgpIG9yIG9zLnN0YXQob3V0cHV0K3N1ZmZpeCkuc3Rfc2l6ZT09MCxcXCdzbmFwc2hvdCBoYXMgV0FMIGZyYW1lczsgaW1tdXRhYmxlIHJlYWQgcmVmdXNlZFxcJylcXG4gZGlnZXN0PWhhc2hsaWIuc2hhMjU2KClcXG4gd2l0aCBvcGVuKG91dHB1dCxcXCdyYlxcJykgYXMgc3RyZWFtOlxcbiAgZm9yIGJsb2NrIGluIGl0ZXIobGFtYmRhOnN0cmVhbS5yZWFkKDEwMjQqKjIpLGJcXCdcXCcpOmRpZ2VzdC51cGRhdGUoYmxvY2spXFxuIGZyb3plbl9kaWdlc3Q9ZGlnZXN0LmhleGRpZ2VzdCgpXFxuIHNuYXBzaG90PWNvdW50cyhpbnNwZWN0X2Nsb3NlZF9zbmFwc2hvdChvdXRwdXQsZnJvemVuX2RpZ2VzdCkpXFxuIHNpemU9b3Muc3RhdChvdXRwdXQpLnN0X3NpemU7cmVxdWlyZSgwPHNpemU8PTUxMioxMDI0KioyLFxcJ3NuYXBzaG90IG91dHNpZGUgYm91bmRlZCBjb3B5IHNpemVcXCcpXFxuIGRpZ2VzdD1oYXNobGliLnNoYTI1NigpXFxuIHdpdGggb3BlbihvdXRwdXQsXFwncmJcXCcpIGFzIHN0cmVhbTpcXG4gIGZvciBibG9jayBpbiBpdGVyKGxhbWJkYTpzdHJlYW0ucmVhZCgxMDI0KioyKSxiXFwnXFwnKTpkaWdlc3QudXBkYXRlKGJsb2NrKVxcbiBwcmludChqc29uLmR1bXBzKHtcXCdhbGxfY2hlY2tzX3Bhc3NcXCc6VHJ1ZSxcXCdzb3VyY2VfcmVhZG9ubHlfYmFzZWxpbmVcXCc6YmFzZWxpbmUsXFwnc25hcHNob3RfcmVhZG9ubHlcXCc6c25hcHNob3QsXFwnc25hcHNob3Rfc2hhMjU2XFwnOmRpZ2VzdC5oZXhkaWdlc3QoKSxcXCdzbmFwc2hvdF9ieXRlc1xcJzpzaXplLFxcJ3NuYXBzaG90X2ZpbGVcXCc6ZmlsZW5hbWUsXFwnbmF0aXZlX3BhcmVudF9wcml2YXRlXFwnOlRydWUsXFwnbmF0aXZlX3VpZFxcJzpvcy5nZXR1aWQoKX0pKVxcbmVsaWYgYWN0aW9uIGluIChcXCd2ZXJpZnlcXCcsXFwncmVsZWFzZVxcJyxcXCdyb2xsYmFjay12ZXJpZnlcXCcpOlxcbiBwcm9vZj17XFwnYWxsX2NoZWNrc19wYXNzXFwnOlRydWUsXFwnbGl2ZV9yZWFkb25seV9zY2hlbWFfaW50ZWdyaXR5X2NvdW50c1xcJzpiYXNlbGluZSxcXCdsaXZlX2NvdW50c19tYXlfaW5jbHVkZV9jb25jdXJyZW50X2NvbW1pdHNcXCc6VHJ1ZX1cXG4gaWYgYWN0aW9uPT1cXCd2ZXJpZnlcXCc6XFxuICBpbXBvcnQgdXJsbGliLnJlcXVlc3RcXG4gIG1vZHVsZT1cXCcvb3B0L3ZlbnYvbGliL3B5dGhvbjMuMTIvc2l0ZS1wYWNrYWdlcy9pbmZvcm1lZF9kZWNpc2lvbi9wb2xpY3kucHlcXCc7aW5mbz1vcy5sc3RhdChtb2R1bGUpXFxuICByZXF1aXJlKHN0YXQuU19JU1JFRyhpbmZvLnN0X21vZGUpIGFuZCBpbmZvLnN0X3VpZD09MCBhbmQgc3RhdC5TX0lNT0RFKGluZm8uc3RfbW9kZSk9PTBvNjQ0LFxcJ2luc3RhbGxlZCBwb2xpY3kgbWV0YWRhdGEgZGlmZmVyc1xcJylcXG4gIHdpdGggb3Blbihtb2R1bGUsXFwncmJcXCcpIGFzIHNvdXJjZTptb2R1bGVfZGlnZXN0PWhhc2hsaWIuc2hhMjU2KHNvdXJjZS5yZWFkKCkpLmhleGRpZ2VzdCgpXFxuICByZXF1aXJlKG1vZHVsZV9kaWdlc3Q9PVxcJzlkN2MxYmU0Y2I2MTk5ZDE0ZWYwYWFlYWQ1Zjg1OTUxZjQ1YjBjY2JkN2M4MDBhN2ZkZTkyMmEyNTM1NjZiNjJcXCcsXFwnaW5zdGFsbGVkIHNvdXJjZSBtb2R1bGUgZGlmZmVyc1xcJylcXG4gIHB1cnBvc2U9c3VicHJvY2Vzcy5ydW4oW3N5cy5leGVjdXRhYmxlLFxcJy1cXCddLGlucHV0PV9fUFVSUE9TRV9fLHRleHQ9VHJ1ZSxjYXB0dXJlX291dHB1dD1UcnVlLHRpbWVvdXQ9MzApXFxuICByZXF1aXJlKHB1cnBvc2UucmV0dXJuY29kZT09MCxcXCdpbnN0YWxsZWQgbmF0aXZlLXBsYWNlbWVudCBwdXJwb3NlIGNoZWNrcyBmYWlsZWRcXCcpXFxuICB0ZXN0ZWQ9anNvbi5sb2FkcyhwdXJwb3NlLnN0ZG91dClcXG4gIHJlcXVpcmUodGVzdGVkPT17XFwnaW5zdGFsbGVkX2ltYWdlX3B1cnBvc2VfdmFyaWFudHNcXCc6MTAsXFwnYmluZGluZ19mb3Jtc19hbGxvd2VkXFwnOjIsXFwnYWR2aXNvcnlfb3JfbWFsZm9ybWVkX3JlZnVzZWRcXCc6OCxcXCdzeW50aGV0aWNfb25seVxcJzpUcnVlLFxcJ25hdGl2ZV9wcm92aWRlclxcJzpGYWxzZX0sXFwncHVycG9zZSByZXN1bHQgZGlmZmVyc1xcJylcXG4gIGNsYXNzIE5vUmVkaXJlY3QodXJsbGliLnJlcXVlc3QuSFRUUFJlZGlyZWN0SGFuZGxlcik6XFxuICAgZGVmIHJlZGlyZWN0X3JlcXVlc3Qoc2VsZiwqYXJncywqKmt3YXJncyk6cmFpc2UgVmFsdWVFcnJvcihcXCdoZWFsdGggcmVkaXJlY3QgcmVmdXNlZFxcJylcXG4gIG9wZW5lcj11cmxsaWIucmVxdWVzdC5idWlsZF9vcGVuZXIoTm9SZWRpcmVjdCgpKTtzdGF0dXNlcz17fVxcbiAgZm9yIHJvdXRlIGluIChcXCcvaGVhbHRoelxcJyxcXCcvcmVhZHl6XFwnKTpcXG4gICB3aXRoIG9wZW5lci5vcGVuKFxcJ2h0dHA6Ly8xMjcuMC4wLjE6ODA4MFxcJytyb3V0ZSx0aW1lb3V0PTEwKSBhcyByZXNwb25zZTpcXG4gICAgcmVxdWlyZShyZXNwb25zZS5zdGF0dXM9PTIwMCBhbmQgbGVuKHJlc3BvbnNlLnJlYWQoODE5MykpPD04MTkyLFxcJ2JvdW5kZWQgaGVhbHRoIGNoZWNrIHJlZnVzZWRcXCcpO3N0YXR1c2VzW3JvdXRlXT0yMDBcXG4gIHpsaWJfZXhwZWN0ZWQ9X19aTElCX187cmVxdWlyZSh6bGliX2V4cGVjdGVkW1xcJ3psaWJfcGFja2FnZVxcJ109PVxcJzEuMy4yLXIxXFwnLFxcJ2V4YWN0IGZpeGVkIHBhY2thZ2UgcHJvb2YgcmVxdWlyZWRcXCcpO2luc3RhbGxlZF96bGliPXt9XFxuICBmb3IgcmVsYXRpdmUsbWV0YSBpbiB6bGliX2V4cGVjdGVkW1xcJ2ZpbGVzXFwnXS5pdGVtcygpOlxcbiAgIHJlcXVpcmUocmVsYXRpdmUgaW4gKFxcJ2xpYi9hcGsvZGIvaW5zdGFsbGVkXFwnLFxcJ3Vzci9saWIvbGliei5zby4xLjMuMlxcJyksXFwnZml4ZWQgemxpYiBzb3VyY2UgcGF0aCByZXF1aXJlZFxcJylcXG4gICBwYXRoPVxcJy9cXCcrcmVsYXRpdmU7aW5mbz1vcy5sc3RhdChwYXRoKTtyZXF1aXJlKG1ldGFbMF09PVxcJ2ZpbGVcXCcgYW5kIHN0YXQuU19JU1JFRyhpbmZvLnN0X21vZGUpIGFuZCBzdGF0LlNfSU1PREUoaW5mby5zdF9tb2RlKT09bWV0YVsxXSBhbmQgaW5mby5zdF91aWQ9PW1ldGFbMl0gYW5kIGluZm8uc3RfZ2lkPT1tZXRhWzNdLFxcJ2luc3RhbGxlZCB6bGliIGZpbGUgbWV0YWRhdGEgZGlmZmVyc1xcJylcXG4gICB3aXRoIG9wZW4ocGF0aCxcXCdyYlxcJykgYXMgaGFuZGxlOmRpZ2VzdD1oYXNobGliLnNoYTI1NihoYW5kbGUucmVhZCgpKS5oZXhkaWdlc3QoKVxcbiAgIHJlcXVpcmUoZGlnZXN0PT1tZXRhWzRdLFxcJ2luc3RhbGxlZCB6bGliIGZpbGUgZGlnZXN0IGRpZmZlcnNcXCcpO2luc3RhbGxlZF96bGliW3JlbGF0aXZlXT1kaWdlc3RcXG4gIHByb29mLnVwZGF0ZShpbnN0YWxsZWRfemxpYl9maWxlcz1pbnN0YWxsZWRfemxpYix6bGliX3BhY2thZ2U9XFwnMS4zLjItcjFcXCcpXFxuICBwcm9vZi51cGRhdGUoaW5zdGFsbGVkX3B1cnBvc2VfY2hlY2tzPXRlc3RlZCxwb2xpY3lfc2hhMjU2PW1vZHVsZV9kaWdlc3QscG9saWN5X21vZGU9XFwnMDY0NFxcJyxwb2xpY3lfdWlkPTAsaGVhbHRoX3N0YXR1c2VzPXN0YXR1c2VzLG5hdGl2ZV9wcm92aWRlcl9wcm9iZT1GYWxzZSlcXG4gcHJpbnQoanNvbi5kdW1wcyhwcm9vZikpXFxuZWxpZiBhY3Rpb24gaW4gKFxcJ2RyaWxsLWNvcHlcXCcsXFwnY3V0b3Zlci1jb3B5XFwnKTpcXG4gcHJpdmF0ZShmb2xkZXIsVHJ1ZSk7ZmlsZW5hbWU9XFwnZHJpbGwuc3FsaXRlXFwnIGlmIGFjdGlvbj09XFwnZHJpbGwtY29weVxcJyBlbHNlIFxcJ2N1dG92ZXIuc3FsaXRlXFwnO291dHB1dD1mb2xkZXIrXFwnL1xcJytmaWxlbmFtZTtwcml2YXRlKG91dHB1dClcXG4gaWRlbnRpdHk9X19TTkFQU0hPVF9fO3JlcXVpcmUoaXNpbnN0YW5jZShpZGVudGl0eSxkaWN0KSBhbmQgaWRlbnRpdHkuZ2V0KFxcJ3NuYXBzaG90X2ZpbGVcXCcpPT1maWxlbmFtZSxcXCdyZWNvcmRlZCBvcmlnaW5hbCBmcm96ZW4gc25hcHNob3QgaWRlbnRpdHkgcmVxdWlyZWRcXCcpXFxuIHNuYXBzaG90PWNvdW50cyhpbnNwZWN0X2Nsb3NlZF9zbmFwc2hvdChvdXRwdXQsaWRlbnRpdHlbXFwnc25hcHNob3Rfc2hhMjU2XFwnXSkpO3NpemU9b3Muc3RhdChvdXRwdXQpLnN0X3NpemU7cmVxdWlyZShzaXplPT1pZGVudGl0eVtcXCdzbmFwc2hvdF9ieXRlc1xcJ10gYW5kIDA8c2l6ZTw9NTEyKjEwMjQqKjIsXFwnc25hcHNob3Qgc2l6ZSBvdXRzaWRlIGZyb3plbiBib3VuZFxcJylcXG4gcmVxdWlyZShzbmFwc2hvdD09aWRlbnRpdHlbXFwnc25hcHNob3RfcmVhZG9ubHlcXCddLFxcJ2Zyb3plbiBzbmFwc2hvdCBwcm9vZiBkaWZmZXJzXFwnKVxcbiBkaWdlc3Q9aGFzaGxpYi5zaGEyNTYoKVxcbiB3aXRoIG9wZW4ob3V0cHV0LFxcJ3JiXFwnKSBhcyBzdHJlYW06XFxuICBmb3IgYmxvY2sgaW4gaXRlcihsYW1iZGE6c3RyZWFtLnJlYWQoMTAyNCoqMiksYlxcJ1xcJyk6ZGlnZXN0LnVwZGF0ZShibG9jaylcXG4gcHJpbnQoanNvbi5kdW1wcyh7XFwnYWxsX2NoZWNrc19wYXNzXFwnOlRydWUsXFwnc25hcHNob3RfZmlsZVxcJzpmaWxlbmFtZSxcXCdzbmFwc2hvdF9ieXRlc1xcJzpzaXplLFxcJ3NuYXBzaG90X3NoYTI1NlxcJzpkaWdlc3QuaGV4ZGlnZXN0KCksXFwnc25hcHNob3RfcmVhZG9ubHlcXCc6c25hcHNob3R9KSlcXG5lbHNlOnJhaXNlIFZhbHVlRXJyb3IoXFwnZml4ZWQgbmF0aXZlIGJhY2t1cC92ZXJpZnkgcGhhc2UgcmVxdWlyZWRcXCcpXFxuXCJcIlwiXFxuICAjIFN0cmlwIHRoZSBDTEkgYnJhbmNoOiBleGVjIG9ubHkgdGhlIHZlcmlmaWVkIHN0ZGxpYiBmdW5jdGlvbiwgbmV2ZXIgU3RvcmUuXFxuICByZWFkb25seT1wYXlsb2FkW1xcJ3JlYWRvbmx5XFwnXS5zcGxpdChcImlmIF9fbmFtZV9fID09IFxcJ19fbWFpbl9fXFwnOlwiLDEpWzBdXFxuICBpbm5lcj1pbm5lci5yZXBsYWNlKFxcJ19fUkVBRE9OTFlfX1xcJyxyZXByKHJlYWRvbmx5KSkucmVwbGFjZShcXCdfX1NBRkVUWV9fXFwnLHJlcHIocGF5bG9hZFtcXCdyZWNlaXB0X3NhZmV0eVxcJ10pKS5yZXBsYWNlKFxcJ19fQUNUSU9OX19cXCcscmVwcihcXCdkcmlsbC1jb3B5XFwnIGlmIHBoYXNlPT1cXCdkcmlsbC1jb3B5XFwnIGVsc2UgcGhhc2UpKS5yZXBsYWNlKFxcJ19fUFVSUE9TRV9fXFwnLHJlcHIocGF5bG9hZFtcXCdwdXJwb3NlX2NoZWNrc1xcJ10pKS5yZXBsYWNlKFxcJ19fU05BUFNIT1RfX1xcJyxyZXByKHBheWxvYWQuZ2V0KFxcJ3NuYXBzaG90X2lkZW50aXR5XFwnKSkpLnJlcGxhY2UoXFwnX19aTElCX19cXCcscmVwcihwYXlsb2FkW1xcJ2luc3RhbGxlZF96bGliXFwnXSkpXFxuICBwPXJ1bihbXFwna3ViZWN0bFxcJyxcXCdleGVjXFwnLFxcJy1pXFwnLFxcJy1uXFwnLFxcJ2luZm9ybWVkLWRlY2lzaW9uXFwnLHBvZFtcXCdtZXRhZGF0YVxcJ11bXFwnbmFtZVxcJ10sXFwnLWNcXCcsXFwnaW5mb3JtZWQtZGVjaXNpb25cXCcsXFwnLS1cXCcsXFwncHl0aG9uXFwnLFxcJy1cXCddLGlubmVyLDE1MClcXG4gIHJlcXVpcmUocC5yZXR1cm5jb2RlPT0wLFxcJ25hdGl2ZSBwaGFzZSBvdXRjb21lIHVudmVyaWZpZWQ7IHJhdyBvdXRwdXQgd2l0aGhlbGQsIGRvIG5vdCByZXRyeVxcJylcXG4gIG91dGNvbWU9anNvbi5sb2FkcyhwLnN0ZG91dCk7cmVxdWlyZShvdXRjb21lLmdldChcXCdhbGxfY2hlY2tzX3Bhc3NcXCcpIGlzIFRydWUsXFwnbmF0aXZlIHBoYXNlIGdhdGUgcmVmdXNlZFxcJylcXG4gIHJlc3VsdC51cGRhdGUob3V0Y29tZSxwb2RfbmFtZT1wb2RbXFwnbWV0YWRhdGFcXCddW1xcJ25hbWVcXCddLHBvZF91aWQ9cG9kW1xcJ21ldGFkYXRhXFwnXVtcXCd1aWRcXCddKVxcbiBpZiBwaGFzZSBpbiAoXFwncmVsZWFzZVxcJyxcXCdyb2xsYmFja1xcJyk6XFxuICBub2RlX3JlYWQ9cnVuKFtcXCdrdWJlY3RsXFwnLFxcJ2dldFxcJyxcXCdub2Rlc1xcJyxcXCctb1xcJyxcXCdqc29uXFwnXSk7cmVxdWlyZShub2RlX3JlYWQucmV0dXJuY29kZT09MCxcXCdub2RlIG1ldGFkYXRhIHVuYXZhaWxhYmxlXFwnKVxcbiAgbm9kZXM9anNvbi5sb2Fkcyhub2RlX3JlYWQuc3Rkb3V0KVtcXCdpdGVtc1xcJ107cmVxdWlyZShsZW4obm9kZXMpPT0xLFxcJ29uZSB0YXJnZXQgbm9kZSByZXF1aXJlZFxcJyk7bm9kZT1ub2Rlc1swXVxcbiAgcmVxdWlyZShhbnkoYS5nZXQoXFwnYWRkcmVzc1xcJyk9PVxcJzkyLjIwNS42Mi4yMzlcXCcgZm9yIGEgaW4gbm9kZVtcXCdzdGF0dXNcXCddW1xcJ2FkZHJlc3Nlc1xcJ10pLFxcJ3RhcmdldCBub2RlIGFkZHJlc3MgY2hhbmdlZFxcJylcXG4gIGNvbmRpdGlvbnM9e2NbXFwndHlwZVxcJ106Y1tcXCdzdGF0dXNcXCddIGZvciBjIGluIG5vZGVbXFwnc3RhdHVzXFwnXVtcXCdjb25kaXRpb25zXFwnXX1cXG4gIHJlcXVpcmUoY29uZGl0aW9ucy5nZXQoXFwnUmVhZHlcXCcpPT1cXCdUcnVlXFwnIGFuZCBhbGwoY29uZGl0aW9ucy5nZXQoayk9PVxcJ0ZhbHNlXFwnIGZvciBrIGluIChcXCdNZW1vcnlQcmVzc3VyZVxcJyxcXCdEaXNrUHJlc3N1cmVcXCcsXFwnUElEUHJlc3N1cmVcXCcpKSxcXCdub2RlIHVuaGVhbHRoeSBvciBwcmVzc3VyZWRcXCcpXFxuICBkZXNjcmlwdGlvbj1ydW4oW1xcJ2t1YmVjdGxcXCcsXFwnZGVzY3JpYmVcXCcsXFwnbm9kZVxcJyxub2RlW1xcJ21ldGFkYXRhXFwnXVtcXCduYW1lXFwnXV0pO3JlcXVpcmUoZGVzY3JpcHRpb24ucmV0dXJuY29kZT09MCxcXCdoZWFkcm9vbSBjYXB0dXJlIHVuYXZhaWxhYmxlXFwnKVxcbiAgdGFibGU9ZGVzY3JpcHRpb24uc3Rkb3V0LnNwbGl0KFxcJ0FsbG9jYXRlZCByZXNvdXJjZXM6XFwnLDEpWzFdO3JlcXVlc3RlZD1pbnQocmUuc2VhcmNoKHJcXCdjcHVcXFxccysoXFxcXGQrKW1cXCcsdGFibGUpLmdyb3VwKDEpKTthbGxvYz1ub2RlW1xcJ3N0YXR1c1xcJ11bXFwnYWxsb2NhdGFibGVcXCddW1xcJ2NwdVxcJ107Y2FwYWNpdHk9aW50KGFsbG9jWzotMV0pIGlmIGFsbG9jLmVuZHN3aXRoKFxcJ21cXCcpIGVsc2UgaW50KGFsbG9jKSoxMDAwXFxuICByZXF1aXJlKHJlcXVlc3RlZDw9Y2FwYWNpdHksXFwnUmVjcmVhdGUgcmVxdWVzdGVkIENQVSBleGNlZWRzIGNhcGFjaXR5XFwnKVxcbiAgcmVxdWlyZShub3QgYXBwLmdldChcXCdvcGVyYXRpb25cXCcpIGFuZCBhcHAuZ2V0KFxcJ3N0YXR1c1xcJyx7fSkuZ2V0KFxcJ29wZXJhdGlvblN0YXRlXFwnLHt9KS5nZXQoXFwncGhhc2VcXCcpIG5vdCBpbiAoXFwnUnVubmluZ1xcJyxcXCdUZXJtaW5hdGluZ1xcJyksXFwnZXhpc3RpbmcgQXJnbyBvcGVyYXRpb24gbXVzdCBiZSBzZXR0bGVkXFwnKVxcbiAgZGVzdGluYXRpb249cGF5bG9hZFtcXCdzb3VyY2VfcmV2aXNpb25cXCddIGlmIHBoYXNlPT1cXCdyZWxlYXNlXFwnIGVsc2UgXFwnYzk2YjBjNmQ4ODFlODU1ZDFkMTU4MDAzYzZkZTkwZWYzOTAyNjJlMlxcJ1xcbiAgYm9keT1jb3B5LmRlZXBjb3B5KHBheWxvYWRbXFwnY2FuZGlkYXRlXFwnXSBpZiBwaGFzZT09XFwncmVsZWFzZVxcJyBlbHNlIHBheWxvYWRbXFwncm9sbGJhY2tcXCddKVxcbiAgZHJ5PXJ1bihbXFwna3ViZWN0bFxcJyxcXCdhcHBseVxcJyxcXCctLWRyeS1ydW49c2VydmVyXFwnLFxcJy1mXFwnLFxcJy1cXCcsXFwnLW9cXCcsXFwnanNvblxcJ10sanNvbi5kdW1wcyhib2R5KSlcXG4gIHJlcXVpcmUoZHJ5LnJldHVybmNvZGU9PTAsXFwnZnJlc2ggb3duZXIgc2VydmVyIGFkbWlzc2lvbiByZWZ1c2VkXFwnKVxcbiAgYWRtaXR0ZWQ9anNvbi5sb2Fkcyhkcnkuc3Rkb3V0KTt3YW50ZWQ9Y29weS5kZWVwY29weShzZWFsZWQpXFxuICB3YW50ZWRbXFwnc3BlY1xcJ11bXFwndGVtcGxhdGVcXCddW1xcJ3NwZWNcXCddW1xcJ2NvbnRhaW5lcnNcXCddWzBdW1xcJ2ltYWdlXFwnXT1DQU5ESURBVEUgaWYgcGhhc2U9PVxcJ3JlbGVhc2VcXCcgZWxzZSBCQVNFXFxuICByZXF1aXJlKHByb2plY3Rpb24oYWRtaXR0ZWQpPT13YW50ZWQsXFwnZHJ5LXJ1biBjaGFuZ2VkIGFueXRoaW5nIGJleW9uZCBleGFjdCBpbWFnZVxcJylcXG4gIGRpZmY9cnVuKFtcXCdrdWJlY3RsXFwnLFxcJ2RpZmZcXCcsXFwnLWZcXCcsXFwnLVxcJ10sanNvbi5kdW1wcyhib2R5KSk7cmVxdWlyZShkaWZmLnJldHVybmNvZGUgaW4gKDAsMSksXFwnY2xpZW50IGRpZmYgdW5hdmFpbGFibGVcXCcpXFxuICBvcGVyYXRpb249e1xcJ2luaXRpYXRlZEJ5XFwnOntcXCd1c2VybmFtZVxcJzpcXCdjdXN0b2RpYW4tY29kZXhcXCd9LFxcJ3N5bmNcXCc6e1xcJ3JldmlzaW9uXFwnOmRlc3RpbmF0aW9uLFxcJ3BydW5lXFwnOkZhbHNlLFxcbiAgIFxcJ3Jlc291cmNlc1xcJzpbe1xcJ2dyb3VwXFwnOlxcJ2FwcHNcXCcsXFwna2luZFxcJzpcXCdEZXBsb3ltZW50XFwnLFxcJ25hbWVcXCc6XFwnaW5mb3JtZWQtZGVjaXNpb25cXCcsXFwnbmFtZXNwYWNlXFwnOlxcJ2luZm9ybWVkLWRlY2lzaW9uXFwnfV19fVxcbiAgcGF0Y2g9W3tcXCdvcFxcJzpcXCd0ZXN0XFwnLFxcJ3BhdGhcXCc6XFwnL21ldGFkYXRhL3VpZFxcJyxcXCd2YWx1ZVxcJzphcHBbXFwnbWV0YWRhdGFcXCddW1xcJ3VpZFxcJ119LFxcbiAgIHtcXCdvcFxcJzpcXCd0ZXN0XFwnLFxcJ3BhdGhcXCc6XFwnL21ldGFkYXRhL3Jlc291cmNlVmVyc2lvblxcJyxcXCd2YWx1ZVxcJzphcHBbXFwnbWV0YWRhdGFcXCddW1xcJ3Jlc291cmNlVmVyc2lvblxcJ119LFxcbiAgIHtcXCdvcFxcJzpcXCd0ZXN0XFwnLFxcJ3BhdGhcXCc6XFwnL3NwZWMvc291cmNlL3RhcmdldFJldmlzaW9uXFwnLFxcJ3ZhbHVlXFwnOmFwcFtcXCdzcGVjXFwnXVtcXCdzb3VyY2VcXCddW1xcJ3RhcmdldFJldmlzaW9uXFwnXX0sXFxuICAge1xcJ29wXFwnOlxcJ3JlcGxhY2VcXCcsXFwncGF0aFxcJzpcXCcvc3BlYy9zb3VyY2UvdGFyZ2V0UmV2aXNpb25cXCcsXFwndmFsdWVcXCc6ZGVzdGluYXRpb259LFxcbiAgIHtcXCdvcFxcJzpcXCdhZGRcXCcsXFwncGF0aFxcJzpcXCcvb3BlcmF0aW9uXFwnLFxcJ3ZhbHVlXFwnOm9wZXJhdGlvbn1dXFxuICBhcmd2PVtcXCdrdWJlY3RsXFwnLFxcJ3BhdGNoXFwnLFxcJ2FwcGxpY2F0aW9uXFwnLFxcJ2luZm9ybWVkLWRlY2lzaW9uXFwnLFxcJy1uXFwnLFxcJ2FyZ29jZFxcJyxcXCctLXR5cGU9anNvblxcJyxcXCctLXBhdGNoLWZpbGU9L2Rldi9zdGRpblxcJyxcXCctb1xcJyxcXCduYW1lXFwnXVxcbiAgcGF0Y2hkcnk9cnVuKGFyZ3YrW1xcJy0tZHJ5LXJ1bj1zZXJ2ZXJcXCddLGpzb24uZHVtcHMocGF0Y2gpKTtyZXF1aXJlKHBhdGNoZHJ5LnJldHVybmNvZGU9PTAsXFwnQXBwbGljYXRpb24gb3BlcmF0aW9uIGRyeS1ydW4gcmVmdXNlZFxcJylcXG4gIGNoYW5nZT1ydW4oYXJndixqc29uLmR1bXBzKHBhdGNoKSlcXG4gIHJlcXVpcmUoY2hhbmdlLnJldHVybmNvZGU9PTAsXFwnQXBwbGljYXRpb24gcGF0Y2ggb3V0Y29tZSBhbWJpZ3VvdXM7IGRvIG5vdCByZXRyeVxcJylcXG4gIHJlc3VsdC51cGRhdGUoYWxsX2NoZWNrc19wYXNzPVRydWUsb3BlcmF0aW9uX3JlcXVlc3RlZD1UcnVlLHNvdXJjZV9yZXZpc2lvbj1kZXN0aW5hdGlvbixcXG4gICByZXNvdXJjZXM9b3BlcmF0aW9uW1xcJ3N5bmNcXCddW1xcJ3Jlc291cmNlc1xcJ10scHJ1bmU9RmFsc2Uscm9sbG91dF92ZXJpZmllZD1GYWxzZSxcXG4gICBsYXRlc3RfbGl2ZV9kYXRhYmFzZV9yZXRhaW5lZD1UcnVlLGNsaWVudF9kaWZmX3NoYTI1Nj1oYXNobGliLnNoYTI1NihkaWZmLnN0ZG91dC5lbmNvZGUoKSkuaGV4ZGlnZXN0KCkpXFxuIGlmIHBoYXNlIGluIChcXCd2ZXJpZnlcXCcsXFwncm9sbGJhY2stdmVyaWZ5XFwnKTpcXG4gIG9wPWFwcC5nZXQoXFwnc3RhdHVzXFwnLHt9KS5nZXQoXFwnb3BlcmF0aW9uU3RhdGVcXCcse30pO3N5bmM9b3AuZ2V0KFxcJ29wZXJhdGlvblxcJyx7fSkuZ2V0KFxcJ3N5bmNcXCcse30pXFxuICBpbnRlbnQ9cGF5bG9hZC5nZXQoXFwnb3BlcmF0aW9uX2ludGVudFxcJyk7cmVxdWlyZShpc2luc3RhbmNlKGludGVudCxkaWN0KSBhbmQgaW50ZW50LmdldChcXCdzdGF0ZVxcJyk9PVxcJ0NPTVBMRVRFXFwnLFxcJ3ZlcmlmaWVkIG1hdGNoaW5nIG9wZXJhdGlvbiBpbnRlbnQgcmVxdWlyZWRcXCcpXFxuICByZXF1ZXN0PWludGVudC5nZXQoXFwncmVzdWx0XFwnLHt9KTtyZXF1aXJlKHJlcXVlc3QuZ2V0KFxcJ29wZXJhdGlvbl9yZXF1ZXN0ZWRcXCcpIGlzIFRydWUgYW5kIHJlcXVlc3QuZ2V0KFxcJ3BydW5lXFwnKSBpcyBGYWxzZSBhbmQgcmVxdWVzdC5nZXQoXFwnc291cmNlX3JldmlzaW9uXFwnKT09dGFyZ2V0LFxcJ3JlY29yZGVkIHJlcXVlc3QgbXVzdCBleHBsaWNpdGx5IGRpc2FibGUgcHJ1bmVcXCcpXFxuICBzdGFydGVkPWRhdGV0aW1lLmRhdGV0aW1lLmZyb21pc29mb3JtYXQoaW50ZW50W1xcJ3N0YXJ0ZWRfYXRcXCddKTtmaW5pc2hlZD1kYXRldGltZS5kYXRldGltZS5mcm9taXNvZm9ybWF0KG9wLmdldChcXCdmaW5pc2hlZEF0XFwnLFxcJ1xcJykucmVwbGFjZShcXCdaXFwnLFxcJyswMDowMFxcJykpO29ic2VydmVkX3N0YXJ0PWRhdGV0aW1lLmRhdGV0aW1lLmZyb21pc29mb3JtYXQob3AuZ2V0KFxcJ3N0YXJ0ZWRBdFxcJyxcXCdcXCcpLnJlcGxhY2UoXFwnWlxcJyxcXCcrMDA6MDBcXCcpKVxcbiAgcmVxdWlyZShzdGFydGVkLnR6aW5mbyBpcyBub3QgTm9uZSBhbmQgb2JzZXJ2ZWRfc3RhcnQudHppbmZvIGlzIG5vdCBOb25lIGFuZCBmaW5pc2hlZC50emluZm8gaXMgbm90IE5vbmUgYW5kIHN0YXJ0ZWQ8PW9ic2VydmVkX3N0YXJ0PD1maW5pc2hlZCxcXCdvcGVyYXRpb24gY29tcGxldGlvbiBpcyBub3QgZnJvbSB0aGlzIGF0dGVtcHRcXCcpXFxuICByZXF1aXJlKG9wLmdldChcXCdwaGFzZVxcJyk9PVxcJ1N1Y2NlZWRlZFxcJyBhbmQgc3luYy5nZXQoXFwncmV2aXNpb25cXCcpPT10YXJnZXQgYW5kIChcXCdwcnVuZVxcJyBub3QgaW4gc3luYyBvciBzeW5jW1xcJ3BydW5lXFwnXSBpcyBGYWxzZSlcXG4gICBhbmQgb3AuZ2V0KFxcJ29wZXJhdGlvblxcJyx7fSkuZ2V0KFxcJ2luaXRpYXRlZEJ5XFwnKT09e1xcJ3VzZXJuYW1lXFwnOlxcJ2N1c3RvZGlhbi1jb2RleFxcJ31cXG4gICBhbmQgc3luYy5nZXQoXFwncmVzb3VyY2VzXFwnKT09cmVxdWVzdC5nZXQoXFwncmVzb3VyY2VzXFwnKT09W3tcXCdncm91cFxcJzpcXCdhcHBzXFwnLFxcJ2tpbmRcXCc6XFwnRGVwbG95bWVudFxcJyxcXCduYW1lXFwnOlxcJ2luZm9ybWVkLWRlY2lzaW9uXFwnLFxcJ25hbWVzcGFjZVxcJzpcXCdpbmZvcm1lZC1kZWNpc2lvblxcJ31dLFxcJ2V4YWN0IG5hdGl2ZSBBcmdvIG9wZXJhdGlvbiBjb21wbGV0aW9uIHVudmVyaWZpZWRcXCcpXFxuICBzeW5jZWQ9b3AuZ2V0KFxcJ3N5bmNSZXN1bHRcXCcse30pO3JlcXVpcmUoc3luY2VkLmdldChcXCdyZXZpc2lvblxcJyk9PXRhcmdldCBhbmQgbGVuKHN5bmNlZC5nZXQoXFwncmVzb3VyY2VzXFwnLFtdKSk9PTEsXFwnbWF0Y2hpbmcgc2luZ2xlLW9iamVjdCBzeW5jIHJlc3VsdCByZXF1aXJlZFxcJylcXG4gIHJlc291cmNlPXN5bmNlZFtcXCdyZXNvdXJjZXNcXCddWzBdO3JlcXVpcmUoYWxsKHJlc291cmNlLmdldChrKT09diBmb3Igayx2IGluIHJlcXVlc3RbXFwncmVzb3VyY2VzXFwnXVswXS5pdGVtcygpKSBhbmQgcmVzb3VyY2UuZ2V0KFxcJ3N0YXR1c1xcJyk9PVxcJ1N5bmNlZFxcJyxcXCdleGFjdCBEZXBsb3ltZW50IHN5bmMgcmVzdWx0IHJlcXVpcmVkXFwnKVxcbiAgcmVxdWlyZShsaXZlW1xcJ3N0YXR1c1xcJ10uZ2V0KFxcJ29ic2VydmVkR2VuZXJhdGlvblxcJyk9PWxpdmVbXFwnbWV0YWRhdGFcXCddW1xcJ2dlbmVyYXRpb25cXCddLFxcJ2NvbnRyb2xsZXIgZ2VuZXJhdGlvbiBub3Qgc2V0dGxlZFxcJylcXG4gIHJlc3VsdC51cGRhdGUobmF0aXZlX3JvbGxvdXRfdmVyaWZpZWQ9VHJ1ZSxjdXJyZW50X3NjaGVtYTVfZGF0YWJhc2VfcmV0YWluZWQ9VHJ1ZSxyZWNvdmVyeV92ZXJpZmljYXRpb249cGhhc2U9PVxcJ3JvbGxiYWNrLXZlcmlmeVxcJylcXG5leGNlcHQgQmFzZUV4Y2VwdGlvbiBhcyBlcnJvcjpcXG4gcmVzdWx0LnVwZGF0ZShhbGxfY2hlY2tzX3Bhc3M9RmFsc2UsZmFpbHVyZV9jbGFzcz10eXBlKGVycm9yKS5fX25hbWVfXyxcXG4gIGZhaWx1cmU9XFwnZml4ZWQgcGhhc2Ugb3V0Y29tZSByZWZ1c2VkIG9yIGFtYmlndW91czsgcmF3IG91dHB1dCB3aXRoaGVsZDsgbm8gcmV0cnlcXCcpXFxucHJpbnQoanNvbi5kdW1wcyhyZXN1bHQpKTtzeXMuZXhpdCgwIGlmIHJlc3VsdC5nZXQoXFwnYWxsX2NoZWNrc19wYXNzXFwnKSBpcyBUcnVlIGVsc2UgMSlcXG4nXG4iCiAgICB9LAogICAgImV4ZWN1dGlvbi9yZWNlaXB0X3NhZmV0eS5weSI6IHsKICAgICAgInNoYTI1NiI6ICI1YzU0MzczZjJkYzRiMDY0MDA4MjI1YzE4NmVmYjFkNWFmZGVlYzM2ZDE1MzUwYmRhMGExMmRlZDk3NjAyZDFiIiwKICAgICAgImNvbnRlbnQiOiAiXCJcIlwiT25seSBzdHJ1Y3R1cmFsIGNvdW50L2hhc2ggcmVjZWlwdHMgY2FuIGxlYXZlIHRoZSBwcml2YXRlIGV4ZWN1dGlvbiBwaGFzZS5cIlwiXCJcbmRlZiBjb3VudHModmFsdWUpOlxuICAgIGlmIChzZXQodmFsdWUpICE9IHsnc2NoZW1hX3ZlcnNpb24nLCAnbGlmZWN5Y2xlJywgJ291dGJveCcsICdzdWJtaXNzaW9ucycsICdpbnRlZ3JpdHknLFxuICAgICAgICAgICAgICAgICAgICAgICAncmVhZF9vbmx5X3ByZV9vcGVuJywgJ3N0b3JlX2NvbnN0cnVjdGVkJ31cbiAgICAgICAgICAgIG9yIHZhbHVlWydzY2hlbWFfdmVyc2lvbiddICE9IDUgb3IgdmFsdWVbJ2ludGVncml0eSddICE9ICdvaydcbiAgICAgICAgICAgIG9yIHZhbHVlWydyZWFkX29ubHlfcHJlX29wZW4nXSBpcyBub3QgVHJ1ZSBvciB2YWx1ZVsnc3RvcmVfY29uc3RydWN0ZWQnXSBpcyBub3QgRmFsc2VcbiAgICAgICAgICAgIG9yIHNldCh2YWx1ZVsnbGlmZWN5Y2xlJ10pICE9IHsncmVjb3JkcycsICdldmVudHMnLCAncGVuZGluZyd9XG4gICAgICAgICAgICBvciBub3Qgc2V0KHZhbHVlWydvdXRib3gnXSkgPD0geydwZW5kaW5nJywgJ2RlbGl2ZXJlZCcsICdibG9ja2VkJ31cbiAgICAgICAgICAgIG9yIG5vdCBzZXQodmFsdWVbJ3N1Ym1pc3Npb25zJ10pIDw9IHsncHJlcGFyZWQnLCAnaW5fZmxpZ2h0JywgJ2NvbmZpcm1lZCcsICd1bnJlc29sdmVkJ30pOlxuICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdjb3VudC1vbmx5IHJlY2VpcHQgc2hhcGUvc3RhdGUgcmVmdXNlZDsgdmFsdWVzIHdpdGhoZWxkJylcbiAgICBmb3IgZ3JvdXAgaW4gKCdsaWZlY3ljbGUnLCAnb3V0Ym94JywgJ3N1Ym1pc3Npb25zJyk6XG4gICAgICAgIGlmIGFueSh0eXBlKG51bWJlcikgaXMgbm90IGludCBvciBudW1iZXIgPCAwIG9yIG51bWJlciA+IDEwKioxMlxuICAgICAgICAgICAgICAgZm9yIG51bWJlciBpbiB2YWx1ZVtncm91cF0udmFsdWVzKCkpOlxuICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcignY291bnQtb25seSByZWNlaXB0IHZhbHVlcyByZWZ1c2VkJylcbiAgICByZXR1cm4gdmFsdWVcbiIKICAgIH0sCiAgICAiZXhlY3V0aW9uL3Rlc3RfZXhlY3V0aW9uLnB5IjogewogICAgICAic2hhMjU2IjogIjc0NTgyMjIwNDlkOGRmMmYwMWYyY2UzNTliNTg2NGFlNDFjZDczMDU0ZWU1OTY1OWNhOGNkOGFjMTdjZjczYWMiLAogICAgICAiY29udGVudCI6ICJcIlwiXCJTeW50aGV0aWMgY3VzdG9keS90cmFuc3BvcnQgdGVzdHMuIE5vIG5hdGl2ZSBkYXRhLCBEb2NrZXIgb3IgY2x1c3RlciBhY3Rpb24uXCJcIlwiXG5pbXBvcnQgY29weVxuaW1wb3J0IGRhdGV0aW1lXG5pbXBvcnQgaW9cbmltcG9ydCBqc29uXG5pbXBvcnQgb3NcbmZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuaW1wb3J0IHN1YnByb2Nlc3NcbmltcG9ydCBzeXNcbmltcG9ydCB0ZW1wZmlsZVxuaW1wb3J0IHVuaXR0ZXN0XG5mcm9tIHVuaXR0ZXN0Lm1vY2sgaW1wb3J0IHBhdGNoXG5mcm9tIGNvbnRleHRsaWIgaW1wb3J0IHJlZGlyZWN0X3N0ZG91dFxuXG5pbXBvcnQgYWN0dWF0b3JcbmZyb20gYXV0aG9yaXR5X2pvdXJuYWwgaW1wb3J0IEpvdXJuYWwsIGF1dGhvcml0eSwgZnJlc2hfc2NhblxuZnJvbSBuYXRpdmVfcHJvZ3JhbSBpbXBvcnQgUFJPR1JBTVxuZnJvbSByZWNlaXB0X3NhZmV0eSBpbXBvcnQgY291bnRzXG5cbkNBU0UgPSBQYXRoKF9fZmlsZV9fKS5yZXNvbHZlKCkucGFyZW50LnBhcmVudFxuTk9XID0gZGF0ZXRpbWUuZGF0ZXRpbWUoMjAyNiwgMTAsIDExLCAyLCAwLCB0emluZm89ZGF0ZXRpbWUudGltZXpvbmUudXRjKVxuUkVWSVNJT04gPSAnZScgKiA0MFxuQ09VTlQgPSB7J3NjaGVtYV92ZXJzaW9uJzogNSwgJ2ludGVncml0eSc6ICdvaycsICdyZWFkX29ubHlfcHJlX29wZW4nOiBUcnVlLCAnc3RvcmVfY29uc3RydWN0ZWQnOiBGYWxzZSxcbiAgICAgICAgICdsaWZlY3ljbGUnOiB7J3JlY29yZHMnOiAzLCAnZXZlbnRzJzogOCwgJ3BlbmRpbmcnOiAxfSxcbiAgICAgICAgICdvdXRib3gnOiB7J3BlbmRpbmcnOiAyLCAnZGVsaXZlcmVkJzogMTB9LCAnc3VibWlzc2lvbnMnOiB7J2NvbmZpcm1lZCc6IDEsICd1bnJlc29sdmVkJzogMX19XG5cblxuZGVmIGFjY2VwdGVkKCk6XG4gICAgYXBwcm92YWwgPSAnMTExMTExMTEtMTExMS00MTExLTgxMTEtMTExMTExMTExMTExJ1xuICAgIHJldmlldyA9ICdyZXZpZXctMjIyMjIyMjItMjIyMi00MjIyLTgyMjItMjIyMjIyMjIyMjIyJ1xuICAgIGRpc3Bvc2l0aW9uID0gJ2Rpc3AtMzMzMzMzMzMtMzMzMy00MzMzLTgzMzMtMzMzMzMzMzMzMzMzJ1xuICAgIHJlY2VpcHQgPSB7J3Byb3Bvc2FsX2lkJzogJ0lORkQtV1AtMDAwOS1UMDUvemxpYjg1MDkxLXIxJyxcbiAgICAgICAgICAgICAgICdtZW1vX2lkJzogJ2luZmQtMjAyNjEwMTEtemxpYi1yZW1lZGlhdGlvbi1yMScsICdtZW1vX3ZlcnNpb24nOiAxLFxuICAgICAgICAgICAgICAgJ21lbW9fc2hhMjU2JzogJ2EnICogNjQsICdzb3VyY2VfcmV2aXNpb24nOiBSRVZJU0lPTiwgJ2FjdGl2YXRpb24nOiAnQVBQUk9WRUQnLFxuICAgICAgICAgICAgICAgJ2F1dGhvcml0eV9tb2RlJzogJ0FETUlOSVNURVInLCAndGFyZ2V0JzogJ3JlYWxtOmt1YmVybmV0ZXMvcmFpbGlhbmNlMDEvaW5mb3JtZWQtZGVjaXNpb24nLFxuICAgICAgICAgICAgICAgJ3ZlcmlmaWNhdGlvbl9tZXRob2QnOiAncm9vdC1maXhlZC1vcmlnaW4tbmF0aXZlLXJlYWRiYWNrJywgJ3J1bnRpbWVfYXV0aGVudGljYXRlZF9ncmFudCc6IEZhbHNlLCAnb3V0Y29tZV9xdWVyeSc6ICcvbGlmZWN5Y2xlL21lbW9zL2luZmQtMjAyNjEwMTEtemxpYi1yZW1lZGlhdGlvbi1yMS9vdXRjb21lcz92ZXJzaW9uPTEnLFxuICAgICAgICAgICAgICAgJ3ZlcmlmaWVkX2F0JzogJzIwMjYtMTAtMTFUMDI6MDA6MDBaJywgJ2FjY2VwdGVkX2F0JzogJzIwMjYtMTAtMTFUMDE6NTk6MDBaJyxcbiAgICAgICAgICAgICAgICdyZXZpZXdfbWVtb19pZCc6IHJldmlldywgJ2FwcHJvdmFsX2lkJzogYXBwcm92YWwsICdkaXNwb3NpdGlvbl9pZCc6IGRpc3Bvc2l0aW9ufVxuICAgIG91dGNvbWUgPSB7J21lbW9faWQnOiByZWNlaXB0WydtZW1vX2lkJ10sICdyZXZpZXcnOiB7J21lbW9faWQnOiByZXZpZXcsICdzdGFsZSc6IEZhbHNlLCAnaW50YWtlX3N0YXRlJzogJ3JlYWR5J30sXG4gICAgICAgICAgICAgICAnZGlzcG9zaXRpb25zJzogW3snYXBwcm92YWxfaWQnOiBOb25lLCAnc3VibWlzc2lvbl9zdGF0ZSc6ICdjb25maXJtZWQnLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2Rpc3Bvc2l0aW9uJzogeyd2ZXJiJzogJ2FjY2VwdCcsICdtZW1vX3ZlcnNpb24nOiAxLCAnbWVtb19pZCc6IHJldmlldyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYXBwcm92YWxfaWQnOiBhcHByb3ZhbCwgJ2lkJzogZGlzcG9zaXRpb24sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ2FjdG9yJzogeydraW5kJzogJ3BlcnNvbicsICdzdWInOiAndWlkPXBsYXRmb3JtLXJvb3Qsb3U9cGVvcGxlLGRjPW5ldGtpbmdkb20sZGM9bG9jYWwnfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnYXQnOiByZWNlaXB0WydhY2NlcHRlZF9hdCddfX1dfVxuICAgIHJldHVybiByZWNlaXB0LCBvdXRjb21lXG5cblxuY2xhc3MgQXV0aG9yaXR5QW5kQ3VzdG9keSh1bml0dGVzdC5UZXN0Q2FzZSk6XG4gICAgZGVmIHRlc3RfZXhhY3RfbmVzdGVkX2ZvdW5kZXJfcmVjZWlwdF9ub3RfdG9wX2xldmVsX2FwcHJvdmFsKHNlbGYpOlxuICAgICAgICByZWNlaXB0LCBvdXRjb21lID0gYWNjZXB0ZWQoKVxuICAgICAgICByZXN1bHQgPSBhdXRob3JpdHkocmVjZWlwdCwgb3V0Y29tZSwgJ2EnICogNjQsIFJFVklTSU9OLCAnZHJpbGwtY29weScsIE5PVylcbiAgICAgICAgc2VsZi5hc3NlcnRFcXVhbChyZXN1bHRbJ2FwcHJvdmFsX2lkJ10sIHJlY2VpcHRbJ2FwcHJvdmFsX2lkJ10pXG5cbiAgICBkZWYgdGVzdF9wZW5kaW5nX3N0YWxlX3dyb25nX2FjdG9yX3ZlcnNpb25fYW5kX2JpbmRpbmdfcmVmdXNlKHNlbGYpOlxuICAgICAgICBtdXRhdGlvbnMgPSBbbGFtYmRhIHIsIG86IHIudXBkYXRlKGFjdGl2YXRpb249J1BFTkRJTkcnKSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSByLCBvOiByLnVwZGF0ZShtZW1vX3NoYTI1Nj0nY2hhbmdlZCcpLFxuICAgICAgICAgICAgICAgICAgICAgbGFtYmRhIHIsIG86IHIudXBkYXRlKHNvdXJjZV9yZXZpc2lvbj0nY2hhbmdlZCcpLFxuICAgICAgICAgICAgICAgICAgICAgbGFtYmRhIHIsIG86IHIudXBkYXRlKHRhcmdldD0nb3RoZXInKSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSByLCBvOiByLnVwZGF0ZSh2ZXJpZmllZF9hdD0nMjAyNi0xMC0xMVQwMTowMDowMFonKSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSByLCBvOiByLnVwZGF0ZSh2ZXJpZmljYXRpb25fbWV0aG9kPSdjYWxsZXItYXNzZXJ0ZWQnKSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSByLCBvOiByLnVwZGF0ZShydW50aW1lX2F1dGhlbnRpY2F0ZWRfZ3JhbnQ9VHJ1ZSksXG4gICAgICAgICAgICAgICAgICAgICBsYW1iZGEgciwgbzogb1sncmV2aWV3J10udXBkYXRlKHN0YWxlPVRydWUpLFxuICAgICAgICAgICAgICAgICAgICAgbGFtYmRhIHIsIG86IG9bJ3JldmlldyddLnVwZGF0ZShpbnRha2Vfc3RhdGU9J3BlbmRpbmcnKSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSByLCBvOiBvWydkaXNwb3NpdGlvbnMnXVswXS51cGRhdGUoc3VibWlzc2lvbl9zdGF0ZT0ndW5yZXNvbHZlZCcpLFxuICAgICAgICAgICAgICAgICAgICAgbGFtYmRhIHIsIG86IG9bJ2Rpc3Bvc2l0aW9ucyddWzBdWydkaXNwb3NpdGlvbiddLnVwZGF0ZShtZW1vX3ZlcnNpb249MyksXG4gICAgICAgICAgICAgICAgICAgICBsYW1iZGEgciwgbzogb1snZGlzcG9zaXRpb25zJ11bMF1bJ2Rpc3Bvc2l0aW9uJ10udXBkYXRlKGFjdG9yPXsna2luZCc6ICdhZ2VudCcsICdzdWInOiAncGxhdGZvcm0tcm9vdCd9KSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSByLCBvOiBvWydkaXNwb3NpdGlvbnMnXS5hcHBlbmQoY29weS5kZWVwY29weShvWydkaXNwb3NpdGlvbnMnXVswXSkpXVxuICAgICAgICBmb3IgbXV0YXRlIGluIG11dGF0aW9uczpcbiAgICAgICAgICAgIHdpdGggc2VsZi5zdWJUZXN0KG11dGF0ZT1tdXRhdGUpOlxuICAgICAgICAgICAgICAgIHIsIG8gPSBhY2NlcHRlZCgpXG4gICAgICAgICAgICAgICAgbXV0YXRlKHIsIG8pXG4gICAgICAgICAgICAgICAgd2l0aCBzZWxmLmFzc2VydFJhaXNlcyhWYWx1ZUVycm9yKTpcbiAgICAgICAgICAgICAgICAgICAgYXV0aG9yaXR5KHIsIG8sICdhJyAqIDY0LCBSRVZJU0lPTiwgJ3JlbGVhc2UnLCBOT1cpXG5cbiAgICBkZWYgdGVzdF9leHBpcmVkX3dpbmRvd19hbmRfdW5rbm93bl9waGFzZV9yZWZ1c2Uoc2VsZik6XG4gICAgICAgIHIsIG8gPSBhY2NlcHRlZCgpXG4gICAgICAgIHdpdGggc2VsZi5hc3NlcnRSYWlzZXMoVmFsdWVFcnJvcik6XG4gICAgICAgICAgICBhdXRob3JpdHkociwgbywgJ2EnICogNjQsIFJFVklTSU9OLCAnYXJiaXRyYXJ5JywgTk9XKVxuICAgICAgICBsYXRlID0gZGF0ZXRpbWUuZGF0ZXRpbWUoMjAyNiwgMTAsIDEzLCB0emluZm89ZGF0ZXRpbWUudGltZXpvbmUudXRjKVxuICAgICAgICByWyd2ZXJpZmllZF9hdCddID0gbGF0ZS5pc29mb3JtYXQoKVxuICAgICAgICB3aXRoIHNlbGYuYXNzZXJ0UmFpc2VzKFZhbHVlRXJyb3IpOlxuICAgICAgICAgICAgYXV0aG9yaXR5KHIsIG8sICdhJyAqIDY0LCBSRVZJU0lPTiwgJ3JvbGxiYWNrJywgbGF0ZSlcblxuICAgIGRlZiB0ZXN0X2R1cmFibGVfaW50ZW50X3ByZWNlZGVzX2Rpc3BhdGNoX2FuZF9uZXZlcl9yZXRyaWVzKHNlbGYpOlxuICAgICAgICB3aXRoIHRlbXBmaWxlLlRlbXBvcmFyeURpcmVjdG9yeSgpIGFzIGRpcmVjdG9yeTpcbiAgICAgICAgICAgIHBhcmVudCA9IFBhdGgoZGlyZWN0b3J5KVxuICAgICAgICAgICAgb3MuY2htb2QocGFyZW50LCAwbzcwMClcbiAgICAgICAgICAgIGpvdXJuYWwgPSBKb3VybmFsKHBhcmVudCwgeydjYXNlJzogJ2ZpeHR1cmUnfSlcbiAgICAgICAgICAgIGpvdXJuYWwuc3RhcnQoJ2RyaWxsLWJhY2t1cCcsIHsnYXBwcm92YWxfaWQnOiAnZml4dHVyZSd9KVxuICAgICAgICAgICAgc2VsZi5hc3NlcnRFcXVhbChqc29uLmxvYWRzKGpvdXJuYWwucGF0aC5yZWFkX3RleHQoKSlbJ3BoYXNlcyddWydkcmlsbC1iYWNrdXAnXVsnc3RhdGUnXSwgJ1JVTk5JTkcnKVxuICAgICAgICAgICAgam91cm5hbC5jbG9zZSgpICAjIHNpbXVsYXRlZCBjcmFzaCBiZWZvcmUgcmVzdWx0XG4gICAgICAgICAgICByZWNvdmVyZWQgPSBKb3VybmFsKHBhcmVudCwgeydjYXNlJzogJ2ZpeHR1cmUnfSlcbiAgICAgICAgICAgIHdpdGggc2VsZi5hc3NlcnRSYWlzZXMoVmFsdWVFcnJvcik6XG4gICAgICAgICAgICAgICAgcmVjb3ZlcmVkLnN0YXJ0KCdkcmlsbC1iYWNrdXAnLCB7fSlcbiAgICAgICAgICAgIHdpdGggc2VsZi5hc3NlcnRSYWlzZXMoVmFsdWVFcnJvcik6XG4gICAgICAgICAgICAgICAgcmVjb3ZlcmVkLnN0YXJ0KCdkcmlsbC1iYWNrdXAnLCB7fSlcbiAgICAgICAgICAgIHJlY292ZXJlZC5jbG9zZSgpXG5cbiAgICBkZWYgdGVzdF9waGFzZV9vcmRlcl9hbmRfdW5jZXJ0YWluX3JlbGVhc2VfYWxsb3dzX29ubHlfZGlzdGluY3RfcmVjb3Zlcnkoc2VsZik6XG4gICAgICAgIHdpdGggdGVtcGZpbGUuVGVtcG9yYXJ5RGlyZWN0b3J5KCkgYXMgZGlyZWN0b3J5OlxuICAgICAgICAgICAgb3MuY2htb2QoZGlyZWN0b3J5LCAwbzcwMClcbiAgICAgICAgICAgIGogPSBKb3VybmFsKGRpcmVjdG9yeSwgeydjYXNlJzogJ2ZpeHR1cmUnfSlcbiAgICAgICAgICAgIHdpdGggc2VsZi5hc3NlcnRSYWlzZXMoVmFsdWVFcnJvcik6XG4gICAgICAgICAgICAgICAgai5zdGFydCgncmVsZWFzZScsIHt9KVxuICAgICAgICAgICAgZm9yIHBoYXNlIGluIGFjdHVhdG9yLlBIQVNFU1s6Nl06XG4gICAgICAgICAgICAgICAgai5zdGFydChwaGFzZSwge30pXG4gICAgICAgICAgICAgICAgai5maW5pc2gocGhhc2UsIHsnYWxsX2NoZWNrc19wYXNzJzogVHJ1ZX0pXG4gICAgICAgICAgICBqLnN0YXJ0KCdyZWxlYXNlJywge30pXG4gICAgICAgICAgICBqLmZpbmlzaCgncmVsZWFzZScsIHsnYWxsX2NoZWNrc19wYXNzJzogRmFsc2V9KVxuICAgICAgICAgICAgd2l0aCBzZWxmLmFzc2VydFJhaXNlcyhWYWx1ZUVycm9yKTpcbiAgICAgICAgICAgICAgICBqLnN0YXJ0KCdyZWxlYXNlJywge30pXG4gICAgICAgICAgICB3aXRoIHNlbGYuYXNzZXJ0UmFpc2VzKFZhbHVlRXJyb3IpOlxuICAgICAgICAgICAgICAgIGouc3RhcnQoJ3ZlcmlmeScsIHt9KVxuICAgICAgICAgICAgai5zdGFydCgncm9sbGJhY2snLCB7fSlcbiAgICAgICAgICAgIGouZmluaXNoKCdyb2xsYmFjaycsIHsnYWxsX2NoZWNrc19wYXNzJzogVHJ1ZX0pXG4gICAgICAgICAgICBqLmNsb3NlKClcblxuICAgIGRlZiB0ZXN0X3NlY3JldF9vcl91bmtub3duX2NoaWxkX291dHB1dF9pc193aXRoaGVsZChzZWxmKTpcbiAgICAgICAgZm9yIGJvZHkgaW4gKCdub3QtanNvbicsICd7XCJhbGxfY2hlY2tzX3Bhc3NcIjp0cnVlLFwidG9rZW5cIjpcInByaXZhdGUtdmFsdWVcIn0nLFxuICAgICAgICAgICAgICAgICAgICAgJ3tcImFsbF9jaGVja3NfcGFzc1wiOnRydWUsXCJzbmFwc2hvdF9yZWFkb25seVwiOntcInNjaGVtYV92ZXJzaW9uXCI6NX19Jyk6XG4gICAgICAgICAgICB3aXRoIHNlbGYuYXNzZXJ0UmFpc2VzKFZhbHVlRXJyb3IpOlxuICAgICAgICAgICAgICAgIGFjdHVhdG9yLmpzb25fcmVzdWx0KHN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2VzcyhbXSwgMCwgYm9keSwgJycpKVxuICAgICAgICB3cm9uZyA9IGNvcHkuZGVlcGNvcHkoQ09VTlQpXG4gICAgICAgIHdyb25nWydvdXRib3gnXSA9IHsncHJpdmF0ZS1yb3ctdmFsdWUnOiAxfVxuICAgICAgICB3aXRoIHNlbGYuYXNzZXJ0UmFpc2VzKFZhbHVlRXJyb3IpOlxuICAgICAgICAgICAgY291bnRzKHdyb25nKVxuICAgICAgICBjb3VudHMoQ09VTlQpICAjIGJsb2NrZWQvdW5yZXNvbHZlZCBjb3VudHMgYXJlIG5vdCByZXF1aXJlZCB0byBiZSB6ZXJvLlxuXG4gICAgZGVmIHRlc3RfYmluYXJ5X2NvcHlfaXNfYm91bmRlZF9wcml2YXRlX2FuZF9uZXZlcl9lY2hvZWQoc2VsZik6XG4gICAgICAgIGJsb2IgPSBiJ3N5bnRoZXRpYyBwcml2YXRlIGZpeHR1cmUgYnl0ZXMnXG4gICAgICAgIGRpZ2VzdCA9IF9faW1wb3J0X18oJ2hhc2hsaWInKS5zaGEyNTYoYmxvYikuaGV4ZGlnZXN0KClcbiAgICAgICAgbWV0YWRhdGEgPSB7J2FsbF9jaGVja3NfcGFzcyc6IFRydWUsICdzbmFwc2hvdF9maWxlJzogJ2RyaWxsLnNxbGl0ZScsICdzbmFwc2hvdF9ieXRlcyc6IGxlbihibG9iKSxcbiAgICAgICAgICAgICAgICAgICAgJ3NuYXBzaG90X3NoYTI1Nic6IGRpZ2VzdCwgJ3NuYXBzaG90X3JlYWRvbmx5JzogQ09VTlQsXG4gICAgICAgICAgICAgICAgICAgICdwb2RfbmFtZSc6ICdmaXh0dXJlLXBvZCcsICdwb2RfdWlkJzogJ2ZpeHR1cmUtdWlkJ31cbiAgICAgICAgam91cm5hbCA9IHR5cGUoJ0ZpeHR1cmVKb3VybmFsJywgKCksIHsnZGF0YSc6IHsncGhhc2VzJzogeydkcmlsbC1iYWNrdXAnOiB7J3Jlc3VsdCc6IG1ldGFkYXRhfX19fSkoKVxuICAgICAgICByZWFsX3BvcGVuID0gc3VicHJvY2Vzcy5Qb3BlblxuICAgICAgICBmb3IgZXh0cmEgaW4gKGInJywgYidvdmVyc2l6ZWQnKTpcbiAgICAgICAgICAgIHdpdGggc2VsZi5zdWJUZXN0KGV4dHJhPWV4dHJhKSwgdGVtcGZpbGUuVGVtcG9yYXJ5RGlyZWN0b3J5KCkgYXMgZm9sZGVyOlxuICAgICAgICAgICAgICAgIG9zLmNobW9kKGZvbGRlciwgMG83MDApXG4gICAgICAgICAgICAgICAgb3V0cHV0ID0gaW8uU3RyaW5nSU8oKVxuXG4gICAgICAgICAgICAgICAgZGVmIGNoaWxkKCphcmdzLCAqKmt3YXJncyk6XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiByZWFsX3BvcGVuKFtzeXMuZXhlY3V0YWJsZSwgJy1jJywgJ2ltcG9ydCBzeXM7c3lzLnN0ZG91dC5idWZmZXIud3JpdGUoJyArIHJlcHIoYmxvYiArIGV4dHJhKSArICcpJ10sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0ZG91dD1zdWJwcm9jZXNzLlBJUEUsIHN0ZGVycj1zdWJwcm9jZXNzLkRFVk5VTEwpXG5cbiAgICAgICAgICAgICAgICB3aXRoIHBhdGNoLm9iamVjdChhY3R1YXRvciwgJ0NVU1RPRFknLCBQYXRoKGZvbGRlcikpLCBwYXRjaC5vYmplY3QoYWN0dWF0b3IsICdyZW1vdGUnLCByZXR1cm5fdmFsdWU9bWV0YWRhdGEpLCBcXFxuICAgICAgICAgICAgICAgICAgICAgICAgcGF0Y2goJ3N1YnByb2Nlc3MuUG9wZW4nLCBzaWRlX2VmZmVjdD1jaGlsZCksIHJlZGlyZWN0X3N0ZG91dChvdXRwdXQpOlxuICAgICAgICAgICAgICAgICAgICBpZiBleHRyYTpcbiAgICAgICAgICAgICAgICAgICAgICAgIHdpdGggc2VsZi5hc3NlcnRSYWlzZXMoVmFsdWVFcnJvcik6XG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgYWN0dWF0b3IuY29weV9zbmFwc2hvdCgnZHJpbGwtY29weScsIFJFVklTSU9OLCB7fSwgam91cm5hbClcbiAgICAgICAgICAgICAgICAgICAgZWxzZTpcbiAgICAgICAgICAgICAgICAgICAgICAgIHJlc3VsdCA9IGFjdHVhdG9yLmNvcHlfc25hcHNob3QoJ2RyaWxsLWNvcHknLCBSRVZJU0lPTiwge30sIGpvdXJuYWwpXG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmFzc2VydFRydWUocmVzdWx0WydvZmZfY2x1c3Rlcl9wcml2YXRlX2NvcHlfdmVyaWZpZWQnXSlcbiAgICAgICAgICAgICAgICAgICAgICAgIHNlbGYuYXNzZXJ0RXF1YWwoKFBhdGgoZm9sZGVyKSAvICdkcmlsbC5zcWxpdGUnKS5yZWFkX2J5dGVzKCksIGJsb2IpXG4gICAgICAgICAgICAgICAgc2VsZi5hc3NlcnRFcXVhbChvdXRwdXQuZ2V0dmFsdWUoKSwgJycpXG4gICAgICAgICAgICAgICAgc2VsZi5hc3NlcnRFcXVhbCgoUGF0aChmb2xkZXIpIC8gJ2RyaWxsLnNxbGl0ZScpLnN0YXQoKS5zdF9tb2RlICYgMG83NzcsIDBvNjAwKVxuXG4gICAgZGVmIHRlc3RfcmVjcmVhdGVkX29mZmxpbmVfdm9sdW1lX2Nhbm5vdF9iZV9kZWxldGVkKHNlbGYpOlxuICAgICAgICBpZGVudGl0eSA9IHsnTmFtZSc6IGFjdHVhdG9yLlZPTFVNRSwgJ0xhYmVscyc6IHsnbm9uY2UnOiAnb2xkJ30sICdDcmVhdGVkQXQnOiAnb2xkJ31cbiAgICAgICAgam91cm5hbCA9IHR5cGUoJ0ZpeHR1cmVKb3VybmFsJywgKCksIHsnZGF0YSc6IHsncGhhc2VzJzoge1xuICAgICAgICAgICAgJ29mZmxpbmUtdmVyaWZ5JzogeydyZXN1bHQnOiB7J2NyZWF0ZWRfdm9sdW1lX2lkZW50aXR5JzogaWRlbnRpdHl9fX19fSkoKVxuICAgICAgICBpbnNwZWN0ZWQgPSBbeyoqaWRlbnRpdHksICdDcmVhdGVkQXQnOiAncmVjcmVhdGVkJ31dXG4gICAgICAgIHdpdGggcGF0Y2gub2JqZWN0KGFjdHVhdG9yLCAnZG9ja2VyJywgcmV0dXJuX3ZhbHVlPXN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2VzcyhbXSwgMCwganNvbi5kdW1wcyhpbnNwZWN0ZWQpLCAnJykpIGFzIG1vY2tlZDpcbiAgICAgICAgICAgIHdpdGggc2VsZi5hc3NlcnRSYWlzZXMoVmFsdWVFcnJvcik6XG4gICAgICAgICAgICAgICAgYWN0dWF0b3IuY2xlYW51cF9vZmZsaW5lKGpvdXJuYWwpXG4gICAgICAgICAgICBzZWxmLmFzc2VydEVxdWFsKG1vY2tlZC5jYWxsX2NvdW50LCAxKVxuXG4gICAgZGVmIHRlc3Rfb2ZmbGluZV9wb3N0X2FkbWluX2ludGVncml0eV9hbmRfb3JpZ2luYWxfaGFzaF9hcmVfcmVjaGVja2VkKHNlbGYpOlxuICAgICAgICBmb3IgZGVmZWN0IGluICgnbm9uZScsICdwb3N0LWludGVncml0eScsICdvcmlnaW5hbC1oYXNoJyk6XG4gICAgICAgICAgICB3aXRoIHNlbGYuc3ViVGVzdChkZWZlY3Q9ZGVmZWN0KSwgdGVtcGZpbGUuVGVtcG9yYXJ5RGlyZWN0b3J5KCkgYXMgZGlyZWN0b3J5OlxuICAgICAgICAgICAgICAgIGZvbGRlcj1QYXRoKGRpcmVjdG9yeSk7b3MuY2htb2QoZm9sZGVyLDBvNzAwKVxuICAgICAgICAgICAgICAgIHNuYXBzaG90PWZvbGRlci8nZHJpbGwuc3FsaXRlJztzbmFwc2hvdC53cml0ZV9ieXRlcyhiJ3ByaXZhdGUgc3ludGhldGljIHNuYXBzaG90Jyk7b3MuY2htb2Qoc25hcHNob3QsMG82MDApXG4gICAgICAgICAgICAgICAgZGlnZXN0PWFjdHVhdG9yLnNoYShzbmFwc2hvdClcbiAgICAgICAgICAgICAgICBwcmlvcj17J3NuYXBzaG90X3NoYTI1Nic6ZGlnZXN0LCdzbmFwc2hvdF9yZWFkb25seSc6Q09VTlR9XG4gICAgICAgICAgICAgICAgam91cm5hbD10eXBlKCdGaXh0dXJlSm91cm5hbCcsKCkseydkYXRhJzp7J3BoYXNlcyc6eydkcmlsbC1jb3B5Jzp7J3Jlc3VsdCc6cHJpb3J9LCdvZmZsaW5lLXZlcmlmeSc6eydhdHRlbXB0X2lkJzonbm9uY2UnfX19LCdub3RlJzpsYW1iZGEgKmFyZ3M6Tm9uZX0pKClcbiAgICAgICAgICAgICAgICByZWFkcz1bXVxuICAgICAgICAgICAgICAgIHZvbHVtZT17J05hbWUnOmFjdHVhdG9yLlZPTFVNRSwnTGFiZWxzJzp7J2luZm9ybWVkLWRlY2lzaW9uLmNhc2UnOidpbmZkLXpsaWItMjAyNjEwMTEtcjEnLCdpbmZvcm1lZC1kZWNpc2lvbi51c2UnOidvZmZsaW5lLXJlc3RvcmUtb25seScsJ2luZm9ybWVkLWRlY2lzaW9uLmF0dGVtcHQnOidub25jZSd9LCdEcml2ZXInOidsb2NhbCcsJ1Njb3BlJzonbG9jYWwnLCdDcmVhdGVkQXQnOidmaXh0dXJlJywnT3B0aW9ucyc6Tm9uZX1cbiAgICAgICAgICAgICAgICBkZWYgZG9ja2VyKGFyZ3YsIGJvZHk9Tm9uZSwgKiprd2FyZ3MpOlxuICAgICAgICAgICAgICAgICAgICBpZiBhcmd2WzBdPT0naW5mbyc6Ym9keT1qc29uLmR1bXBzKCdibnQtbGFwMDAxJylcbiAgICAgICAgICAgICAgICAgICAgZWxpZiBhcmd2WzoyXT09Wyd2b2x1bWUnLCdscyddOmJvZHk9JydcbiAgICAgICAgICAgICAgICAgICAgZWxpZiBhcmd2WzoyXT09Wyd2b2x1bWUnLCdjcmVhdGUnXTpib2R5PWFjdHVhdG9yLlZPTFVNRVxuICAgICAgICAgICAgICAgICAgICBlbGlmIGFyZ3ZbOjJdPT1bJ3ZvbHVtZScsJ2luc3BlY3QnXTpib2R5PWpzb24uZHVtcHMoW3ZvbHVtZV0pXG4gICAgICAgICAgICAgICAgICAgIGVsaWYgJ2luZm9ybWVkLWRlY2lzaW9uLWFkbWluJyBpbiBhcmd2OlxuICAgICAgICAgICAgICAgICAgICAgICAgYm9keT1qc29uLmR1bXBzKHtrZXk6Q09VTlRba2V5XSBmb3Iga2V5IGluICgnc2NoZW1hX3ZlcnNpb24nLCdsaWZlY3ljbGUnLCdvdXRib3gnLCdzdWJtaXNzaW9ucycpfSlcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIGRlZmVjdD09J29yaWdpbmFsLWhhc2gnOnNuYXBzaG90LndyaXRlX2J5dGVzKGInY2hhbmdlZCBzeW50aGV0aWMgb3JpZ2luYWwnKVxuICAgICAgICAgICAgICAgICAgICBlbGlmICctLWVudHJ5cG9pbnQnIGluIGFyZ3YgYW5kICdweXRob24nIGluIGFyZ3Y6XG4gICAgICAgICAgICAgICAgICAgICAgICByZWFkcy5hcHBlbmQoYXJndik7cHJvb2Y9Y29weS5kZWVwY29weShDT1VOVClcbiAgICAgICAgICAgICAgICAgICAgICAgIGlmIGxlbihyZWFkcyk9PTIgYW5kIGRlZmVjdD09J3Bvc3QtaW50ZWdyaXR5Jzpwcm9vZlsnaW50ZWdyaXR5J109J2NvcnJ1cHQnXG4gICAgICAgICAgICAgICAgICAgICAgICBib2R5PWpzb24uZHVtcHMocHJvb2YpXG4gICAgICAgICAgICAgICAgICAgIGVsc2U6cmFpc2UgQXNzZXJ0aW9uRXJyb3IoJ3VuZXhwZWN0ZWQgb2ZmbGluZSBjb21tYW5kJylcbiAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2Vzcyhhcmd2LDAsYm9keSwnJylcbiAgICAgICAgICAgICAgICB3aXRoIHBhdGNoLm9iamVjdChhY3R1YXRvciwnQ1VTVE9EWScsZm9sZGVyKSxwYXRjaC5vYmplY3QoYWN0dWF0b3IsJ0NBU0UnLENBU0UpLHBhdGNoLm9iamVjdChhY3R1YXRvciwnZG9ja2VyJyxzaWRlX2VmZmVjdD1kb2NrZXIpLHBhdGNoKCdzdWJwcm9jZXNzLnJ1bicscmV0dXJuX3ZhbHVlPXN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2VzcyhbXSwwLCd7XCJhbGxfY2hlY2tzX3Bhc3NcIjp0cnVlLFwicHJpdmF0ZV9jb3B5X3ZlcmlmaWVkXCI6dHJ1ZX0nLCcnKSk6XG4gICAgICAgICAgICAgICAgICAgIGlmIGRlZmVjdD09J25vbmUnOlxuICAgICAgICAgICAgICAgICAgICAgICAgcmVzdWx0PWFjdHVhdG9yLm9mZmxpbmVfdmVyaWZ5KGpvdXJuYWwpXG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmFzc2VydEVxdWFsKHJlc3VsdFsnb2ZmbGluZV9wb3N0X2FkbWluX3JlYWRvbmx5J10sQ09VTlQpXG4gICAgICAgICAgICAgICAgICAgICAgICBzZWxmLmFzc2VydFRydWUocmVzdWx0WydvcmlnaW5hbF9zbmFwc2hvdF9oYXNoX3VuY2hhbmdlZCddKVxuICAgICAgICAgICAgICAgICAgICBlbHNlOlxuICAgICAgICAgICAgICAgICAgICAgICAgd2l0aCBzZWxmLmFzc2VydFJhaXNlcyhWYWx1ZUVycm9yKTphY3R1YXRvci5vZmZsaW5lX3ZlcmlmeShqb3VybmFsKVxuICAgICAgICAgICAgICAgIHNlbGYuYXNzZXJ0RXF1YWwobGVuKHJlYWRzKSwyKVxuXG4gICAgZGVmIHRlc3RfZnJlc2hfZnVsbF9zY2FuX2RiX2V4cGlyeV9hbmRfbmV3X2ZpbmRpbmdzX3JlZnVzZShzZWxmKTpcbiAgICAgICAgcmVjZWlwdD17J2ltYWdlJzphY3R1YXRvci5JTUFHRSwnYXJ0aWZhY3Rfc291cmNlX3JldmlzaW9uJzonOWI0YmIyNWQzMjU2OThhZTU0M2U3ZThhOGEzYzM1OTJkYjFmNThmYycsJ3NjYW5uZXInOidhcXVhc2VjL3RyaXZ5QHNoYTI1Njo2MmIxZTY1ZTg4NjliYzRiNGM2YWE0ZmEyYjIxNTk1MjU2YzdjMmY2MDE4YTlkOWFkNjFjYWY4NzE4N2MxOTY5Jywnbm9fc3VwcHJlc3Npb24nOlRydWUsJ2Z1bGxfYWxsc2V2ZXJpdHlfc2Nhbic6VHJ1ZSwnZXhpdF9jb2RlJzowLCdjb21wbGV0ZWRfYXQnOicyMDI2LTEwLTExVDAxOjU5OjAwWicsJ2RiX21ldGFkYXRhJzp7J1VwZGF0ZWRBdCc6JzIwMjYtMTAtMTBUMTk6MDA6MDBaJywnTmV4dFVwZGF0ZSc6JzIwMjYtMTAtMTFUMTk6MDA6MDBaJywnVmVyc2lvbic6Mn0sJ2RiX3NoYTI1Nic6J2EnKjY0LCdmdWxsX3JlcG9ydF9zaGEyNTYnOidiJyo2NH1cbiAgICAgICAgcmVwb3J0PXsnUmVzdWx0cyc6W3snVnVsbmVyYWJpbGl0aWVzJzpOb25lfV19XG4gICAgICAgIHNlbGYuYXNzZXJ0RXF1YWwoZnJlc2hfc2NhbihyZWNlaXB0LHJlcG9ydCxOT1cpWydhbGxzZXZlcml0eV9maW5kaW5ncyddLDApXG4gICAgICAgIG11dGF0aW9ucz1bbGFtYmRhIHIscDpyLnVwZGF0ZShub19zdXBwcmVzc2lvbj1GYWxzZSksbGFtYmRhIHIscDpyLnVwZGF0ZShpbWFnZT0nZm9yZWlnbicpLGxhbWJkYSByLHA6ci51cGRhdGUoc2Nhbm5lcj0nb3RoZXInKSxsYW1iZGEgcixwOnIudXBkYXRlKGNvbXBsZXRlZF9hdD0nMjAyNi0xMC0xMFQyMTowMDowMFonKSxsYW1iZGEgcixwOnJbJ2RiX21ldGFkYXRhJ10udXBkYXRlKE5leHRVcGRhdGU9JzIwMjYtMTAtMTFUMDE6NTk6MDBaJyksbGFtYmRhIHIscDpwWydSZXN1bHRzJ11bMF0udXBkYXRlKFZ1bG5lcmFiaWxpdGllcz1beydTZXZlcml0eSc6J0xPVyd9XSksbGFtYmRhIHIscDpwLnVwZGF0ZShSZXN1bHRzPVtdKV1cbiAgICAgICAgZm9yIG11dGF0ZSBpbiBtdXRhdGlvbnM6XG4gICAgICAgICAgICByLHA9Y29weS5kZWVwY29weShyZWNlaXB0KSxjb3B5LmRlZXBjb3B5KHJlcG9ydCk7bXV0YXRlKHIscClcbiAgICAgICAgICAgIHdpdGggc2VsZi5hc3NlcnRSYWlzZXMoVmFsdWVFcnJvcik6ZnJlc2hfc2NhbihyLHAsTk9XKVxuXG5cbmNsYXNzIEZpeGVkTmF0aXZlUHJvZ3JhbSh1bml0dGVzdC5UZXN0Q2FzZSk6XG4gICAgZGVmIGZpeHR1cmUoc2VsZik6XG4gICAgICAgIHNlYWxlZCA9IGpzb24ubG9hZHMoKENBU0UgLyAnc2VhbGVkLWxpdmUtZGVwbG95bWVudC5qc29uJykucmVhZF90ZXh0KCkpXG4gICAgICAgIGxpdmUgPSBjb3B5LmRlZXBjb3B5KHNlYWxlZClcbiAgICAgICAgbGl2ZVsnbWV0YWRhdGEnXS51cGRhdGUoZ2VuZXJhdGlvbj00MilcbiAgICAgICAgbGl2ZVsnc3RhdHVzJ10gPSB7J3JlYWR5UmVwbGljYXMnOiAxLCAndXBkYXRlZFJlcGxpY2FzJzogMSwgJ29ic2VydmVkR2VuZXJhdGlvbic6IDQyfVxuICAgICAgICBkZXBlbmRlbmNpZXMgPSBqc29uLmxvYWRzKChDQVNFIC8gJ3NlYWxlZC1uYXRpdmUtZGVwZW5kZW5jaWVzLmpzb24nKS5yZWFkX3RleHQoKSlcbiAgICAgICAgY20gPSBjb3B5LmRlZXBjb3B5KGRlcGVuZGVuY2llc1snY29uZmlnbWFwJ10pXG4gICAgICAgIGNtLnBvcCgnY2Fub25pY2FsX2RhdGFfc2hhMjU2JylcbiAgICAgICAgc2VydmljZSA9IGpzb24ubG9hZHMoKENBU0UgLyAnY2FuZGlkYXRlLWRlcGxveW1lbnQuanNvbicpLnJlYWRfdGV4dCgpKVxuICAgICAgICBzb3VyY2Vfc2VydmljZSA9IGpzb24ubG9hZHMoKFBhdGgoJy9ob21lL3dvcnNjaC9pbmZvcm1lZC1kZWNpc2lvbi9kZXBsb3kvbGlmZWN5Y2xlL3NlcnZpY2UuY2FuZGlkYXRlLmpzb24nKSkucmVhZF90ZXh0KCkpXG4gICAgICAgIGNtWydkYXRhJ10gPSBuZXh0KHggZm9yIHggaW4gc291cmNlX3NlcnZpY2VbJ2l0ZW1zJ10gaWYgeFsna2luZCddID09ICdDb25maWdNYXAnKVsnZGF0YSddXG4gICAgICAgIHB2YyA9IGNvcHkuZGVlcGNvcHkoZGVwZW5kZW5jaWVzWydwdmMnXSlcbiAgICAgICAgcHZjWydzdGF0dXMnXSA9IHsncGhhc2UnOiAnQm91bmQnfVxuICAgICAgICBhcHAgPSB7J21ldGFkYXRhJzogeyd1aWQnOiAnNDk5ODY4ZjItZWFiYy00ZGY0LWFlMzEtYjg5Y2U1OGQ3NTBkJywgJ3Jlc291cmNlVmVyc2lvbic6ICcxNyd9LFxuICAgICAgICAgICAgICAgJ3NwZWMnOiB7J3NvdXJjZSc6IHsncmVwb1VSTCc6ICdodHRwczovL2Zvcmdlam8uY291bG9tYi5zb2NpYWwvY291bG9tYi9pbmZvcm1lZC1kZWNpc2lvbi5naXQnLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAncGF0aCc6ICdkZXBsb3kvbGlmZWN5Y2xlJywgJ3RhcmdldFJldmlzaW9uJzogJ2M5NmIwYzZkODgxZTg1NWQxZDE1ODAwM2M2ZGU5MGVmMzkwMjYyZTInfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICdkZXN0aW5hdGlvbic6IHsnbmFtZXNwYWNlJzogJ2luZm9ybWVkLWRlY2lzaW9uJywgJ3NlcnZlcic6ICdodHRwczovL2t1YmVybmV0ZXMuZGVmYXVsdC5zdmMnfX19XG4gICAgICAgIG9iamVjdHMgPSB7J2RlcGxveW1lbnQnOiBsaXZlLCAnY29uZmlnbWFwJzogY20sICdwdmMnOiBwdmMsICdhcHBsaWNhdGlvbic6IGFwcH1cbiAgICAgICAgcGF5bG9hZCA9IHsncGhhc2UnOiAncmVsZWFzZScsICdzb3VyY2VfcmV2aXNpb24nOiBSRVZJU0lPTiwgJ2FwcHJvdmFsJzoge30sICdzZWFsZWQnOiBzZWFsZWQsXG4gICAgICAgICAgICAgICAgICAgJ2RlcGVuZGVuY2llcyc6IGRlcGVuZGVuY2llcywgJ2NhbmRpZGF0ZSc6IHNlcnZpY2UsXG4gICAgICAgICAgICAgICAgICAgJ3JvbGxiYWNrJzoganNvbi5sb2FkcygoQ0FTRSAvICdyb2xsYmFjay1kZXBsb3ltZW50Lmpzb24nKS5yZWFkX3RleHQoKSksXG4gICAgICAgICAgICAgICAgICAgJ3JlYWRvbmx5JzogKENBU0UgLyAnc3FsaXRlX3JlYWRvbmx5LnB5JykucmVhZF90ZXh0KCksJ3B1cnBvc2VfY2hlY2tzJzooQ0FTRS8nY2hlY2tfaW5zdGFsbGVkLnB5JykucmVhZF90ZXh0KCksXG4gICAgICAgICAgICAgICAgICAgJ2luc3RhbGxlZF96bGliJzoganNvbi5sb2FkcygoQ0FTRSAvICdpbnN0YWxsZWQtemxpYi1wcm9vZi5qc29uJykucmVhZF90ZXh0KCkpLCAncmVjZWlwdF9zYWZldHknOiAoUGF0aChfX2ZpbGVfXykucGFyZW50IC8gJ3JlY2VpcHRfc2FmZXR5LnB5JykucmVhZF90ZXh0KCl9XG4gICAgICAgIHJldHVybiBvYmplY3RzLCBwYXlsb2FkXG5cbiAgICBkZWYgZXhlY3V0ZV9mYWtlKHNlbGYsIG9iamVjdHMsIHBheWxvYWQsIGFtYmlndW91cz1GYWxzZSwgc2NoZW1hX3JlZnVzZWQ9RmFsc2UpOlxuICAgICAgICB3cml0ZXMgPSBbXVxuICAgICAgICBjYWxscyA9IFtdXG5cbiAgICAgICAgZGVmIGZha2UoYXJndiwgaW5wdXQ9Tm9uZSwgKiprd2FyZ3MpOlxuICAgICAgICAgICAgY2FsbHMuYXBwZW5kKGFyZ3YpXG4gICAgICAgICAgICBpZiBhcmd2WzoyXSA9PSBbJ2t1YmVjdGwnLCAnZ2V0J106XG4gICAgICAgICAgICAgICAga2luZCA9IGFyZ3ZbMl1cbiAgICAgICAgICAgICAgICBpZiBraW5kIGluIG9iamVjdHM6XG4gICAgICAgICAgICAgICAgICAgIGJvZHkgPSBvYmplY3RzW2tpbmRdXG4gICAgICAgICAgICAgICAgZWxpZiBraW5kID09ICdwb2RzJzpcbiAgICAgICAgICAgICAgICAgICAgYm9keSA9IHsnaXRlbXMnOiBbeydtZXRhZGF0YSc6IHsnbmFtZSc6ICdmaXh0dXJlLXBvZCcsICd1aWQnOiAnYzRjYjE1NTAtZmVkYS00MDk3LTlkZTQtZDg5MWE2NTdiNjYyJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdvd25lclJlZmVyZW5jZXMnOiBbeydraW5kJzogJ1JlcGxpY2FTZXQnLCAnbmFtZSc6ICdmaXh0dXJlLXJzJywgJ3VpZCc6ICdycy11aWQnfV19LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnc3BlYyc6IG9iamVjdHNbJ2RlcGxveW1lbnQnXVsnc3BlYyddWyd0ZW1wbGF0ZSddWydzcGVjJ10sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICdzdGF0dXMnOiB7J3BoYXNlJzogJ1J1bm5pbmcnLCAnY29udGFpbmVyU3RhdHVzZXMnOiBbeyduYW1lJzonaW5mb3JtZWQtZGVjaXNpb24nLCdyZWFkeSc6IFRydWUsJ2ltYWdlSUQnOm9iamVjdHNbJ2RlcGxveW1lbnQnXVsnc3BlYyddWyd0ZW1wbGF0ZSddWydzcGVjJ11bJ2NvbnRhaW5lcnMnXVswXVsnaW1hZ2UnXX1dIH19XX1cbiAgICAgICAgICAgICAgICBlbGlmIGtpbmQgPT0gJ3JlcGxpY2FzZXQnOlxuICAgICAgICAgICAgICAgICAgICBib2R5ID0geydtZXRhZGF0YSc6IHsndWlkJzogJ3JzLXVpZCcsICdvd25lclJlZmVyZW5jZXMnOiBbXG4gICAgICAgICAgICAgICAgICAgICAgICB7J2tpbmQnOiAnRGVwbG95bWVudCcsICd1aWQnOiBwYXlsb2FkWydzZWFsZWQnXVsnbWV0YWRhdGEnXVsndWlkJ119XX19XG4gICAgICAgICAgICAgICAgZWxpZiBraW5kID09ICdub2Rlcyc6XG4gICAgICAgICAgICAgICAgICAgIGJvZHkgPSB7J2l0ZW1zJzogW3snbWV0YWRhdGEnOiB7J25hbWUnOiAnZml4dHVyZS1ub2RlJ30sICdzdGF0dXMnOiB7XG4gICAgICAgICAgICAgICAgICAgICAgICAnYWRkcmVzc2VzJzogW3snYWRkcmVzcyc6ICc5Mi4yMDUuNjIuMjM5J31dLCAnYWxsb2NhdGFibGUnOiB7J2NwdSc6ICc0J30sXG4gICAgICAgICAgICAgICAgICAgICAgICAnY29uZGl0aW9ucyc6IFt7J3R5cGUnOiBrZXksICdzdGF0dXMnOiAnVHJ1ZScgaWYga2V5ID09ICdSZWFkeScgZWxzZSAnRmFsc2UnfVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZm9yIGtleSBpbiAoJ1JlYWR5JywgJ01lbW9yeVByZXNzdXJlJywgJ0Rpc2tQcmVzc3VyZScsICdQSURQcmVzc3VyZScpXX19XX1cbiAgICAgICAgICAgICAgICBlbHNlOlxuICAgICAgICAgICAgICAgICAgICByYWlzZSBBc3NlcnRpb25FcnJvcigndW5leHBlY3RlZCBmaXhlZCBtZXRhZGF0YSByb3V0ZScpXG4gICAgICAgICAgICAgICAgcmV0dXJuIHN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2Vzcyhhcmd2LCAwLCBqc29uLmR1bXBzKGJvZHkpLCAnJylcbiAgICAgICAgICAgIGlmIGFyZ3ZbOjJdID09IFsna3ViZWN0bCcsICdleGVjJ106XG4gICAgICAgICAgICAgICAgaWYgc2NoZW1hX3JlZnVzZWQ6XG4gICAgICAgICAgICAgICAgICAgIHJldHVybiBzdWJwcm9jZXNzLkNvbXBsZXRlZFByb2Nlc3MoYXJndiwgMSwgJycsICd3aXRoaGVsZCcpXG4gICAgICAgICAgICAgICAgcmV0dXJuIHN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2Vzcyhhcmd2LCAwLCBqc29uLmR1bXBzKHtcbiAgICAgICAgICAgICAgICAgICAgJ2FsbF9jaGVja3NfcGFzcyc6IFRydWUsICdsaXZlX3JlYWRvbmx5X3NjaGVtYV9pbnRlZ3JpdHlfY291bnRzJzogQ09VTlR9KSwgJycpXG4gICAgICAgICAgICBpZiBhcmd2WzoyXSA9PSBbJ2t1YmVjdGwnLCAnYXBwbHknXTpcbiAgICAgICAgICAgICAgICBhZG1pdHRlZCA9IGNvcHkuZGVlcGNvcHkocGF5bG9hZFsnc2VhbGVkJ10pXG4gICAgICAgICAgICAgICAgYWRtaXR0ZWRbJ3NwZWMnXVsndGVtcGxhdGUnXVsnc3BlYyddWydjb250YWluZXJzJ11bMF1bJ2ltYWdlJ10gPSAoYWN0dWF0b3IuSU1BR0UgaWYgcGF5bG9hZFsncGhhc2UnXT09J3JlbGVhc2UnIGVsc2UgcGF5bG9hZFsnc2VhbGVkJ11bJ3NwZWMnXVsndGVtcGxhdGUnXVsnc3BlYyddWydjb250YWluZXJzJ11bMF1bJ2ltYWdlJ10pXG4gICAgICAgICAgICAgICAgcmV0dXJuIHN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2Vzcyhhcmd2LCAwLCBqc29uLmR1bXBzKGFkbWl0dGVkKSwgJycpXG4gICAgICAgICAgICBpZiBhcmd2WzoyXSA9PSBbJ2t1YmVjdGwnLCAnZGVzY3JpYmUnXTpcbiAgICAgICAgICAgICAgICByZXR1cm4gc3VicHJvY2Vzcy5Db21wbGV0ZWRQcm9jZXNzKGFyZ3YsIDAsICdBbGxvY2F0ZWQgcmVzb3VyY2VzOlxcbiBjcHUgMzQ2MG0gKDg2JSlcXG4nLCAnJylcbiAgICAgICAgICAgIGlmIGFyZ3ZbOjJdID09IFsna3ViZWN0bCcsICdkaWZmJ106XG4gICAgICAgICAgICAgICAgcmV0dXJuIHN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2Vzcyhhcmd2LCAxLCAnLWltYWdlOiBiYXNlXFxuK2ltYWdlOiBjYW5kaWRhdGVcXG4nLCAnJylcbiAgICAgICAgICAgIGlmIGFyZ3ZbOjJdID09IFsna3ViZWN0bCcsICdwYXRjaCddOlxuICAgICAgICAgICAgICAgIGlmICctLWRyeS1ydW49c2VydmVyJyBub3QgaW4gYXJndjpcbiAgICAgICAgICAgICAgICAgICAgd3JpdGVzLmFwcGVuZChqc29uLmxvYWRzKGlucHV0KSlcbiAgICAgICAgICAgICAgICAgICAgaWYgYW1iaWd1b3VzOlxuICAgICAgICAgICAgICAgICAgICAgICAgcmV0dXJuIHN1YnByb2Nlc3MuQ29tcGxldGVkUHJvY2Vzcyhhcmd2LCAxLCAnJywgJ3dpdGhoZWxkJylcbiAgICAgICAgICAgICAgICByZXR1cm4gc3VicHJvY2Vzcy5Db21wbGV0ZWRQcm9jZXNzKGFyZ3YsIDAsICdhcHBsaWNhdGlvbi5hcmdvcHJvai5pby9pbmZvcm1lZC1kZWNpc2lvblxcbicsICcnKVxuICAgICAgICAgICAgcmFpc2UgQXNzZXJ0aW9uRXJyb3IoJ3VuZXhwZWN0ZWQgY29tbWFuZCcpXG5cbiAgICAgICAgc291cmNlID0gUFJPR1JBTS5yZXBsYWNlKCdfX1BBWUxPQURfXycsIHJlcHIoanNvbi5kdW1wcyhwYXlsb2FkKSkpLnJlcGxhY2UoXG4gICAgICAgICAgICAnX19HVUFSRF9fJywgcmVwcigoQ0FTRSAvICdjaGFuZ2VfZ3VhcmQucHknKS5yZWFkX3RleHQoKSkpXG4gICAgICAgIG91dHB1dCA9IGlvLlN0cmluZ0lPKClcbiAgICAgICAgd2l0aCBwYXRjaCgnc3VicHJvY2Vzcy5ydW4nLCBzaWRlX2VmZmVjdD1mYWtlKSwgcmVkaXJlY3Rfc3Rkb3V0KG91dHB1dCk6XG4gICAgICAgICAgICB0cnk6XG4gICAgICAgICAgICAgICAgZXhlYyhjb21waWxlKHNvdXJjZSwgJ2ZpeGVkLW5hdGl2ZS1maXh0dXJlJywgJ2V4ZWMnKSwgeydfX25hbWVfXyc6ICdfX2ZpeHR1cmVfXyd9KVxuICAgICAgICAgICAgZXhjZXB0IChWYWx1ZUVycm9yLCBTeXN0ZW1FeGl0KTpcbiAgICAgICAgICAgICAgICBwYXNzXG4gICAgICAgIHJlY2VpcHQgPSBqc29uLmxvYWRzKG91dHB1dC5nZXR2YWx1ZSgpKSBpZiBvdXRwdXQuZ2V0dmFsdWUoKSBlbHNlIHsnYWxsX2NoZWNrc19wYXNzJzogRmFsc2V9XG4gICAgICAgIHJldHVybiByZWNlaXB0LCB3cml0ZXMsIGNhbGxzXG5cbiAgICBkZWYgdGVzdF9vbmx5X2V4aXN0aW5nX2FwcGxpY2F0aW9uX29wZXJhdGlvbl9hbmRfZGVwbG95bWVudF9hcmVfcmVxdWVzdGVkKHNlbGYpOlxuICAgICAgICBvYmplY3RzLCBwYXlsb2FkID0gc2VsZi5maXh0dXJlKClcbiAgICAgICAgcmVjZWlwdCwgd3JpdGVzLCBjYWxscyA9IHNlbGYuZXhlY3V0ZV9mYWtlKG9iamVjdHMsIHBheWxvYWQpXG4gICAgICAgIHNlbGYuYXNzZXJ0VHJ1ZShyZWNlaXB0WydhbGxfY2hlY2tzX3Bhc3MnXSlcbiAgICAgICAgc2VsZi5hc3NlcnRGYWxzZShyZWNlaXB0Wydyb2xsb3V0X3ZlcmlmaWVkJ10pXG4gICAgICAgIHNlbGYuYXNzZXJ0RXF1YWwobGVuKHdyaXRlcyksIDEpXG4gICAgICAgIG9wZXJhdGlvbiA9IHdyaXRlc1swXVstMV1bJ3ZhbHVlJ11cbiAgICAgICAgc2VsZi5hc3NlcnRGYWxzZShvcGVyYXRpb25bJ3N5bmMnXVsncHJ1bmUnXSlcbiAgICAgICAgc2VsZi5hc3NlcnRFcXVhbChvcGVyYXRpb25bJ3N5bmMnXVsncmVzb3VyY2VzJ10sIFtcbiAgICAgICAgICAgIHsnZ3JvdXAnOiAnYXBwcycsICdraW5kJzogJ0RlcGxveW1lbnQnLCAnbmFtZSc6ICdpbmZvcm1lZC1kZWNpc2lvbicsICduYW1lc3BhY2UnOiAnaW5mb3JtZWQtZGVjaXNpb24nfV0pXG4gICAgICAgIHNlbGYuYXNzZXJ0VHJ1ZShhbnkoJy0tZHJ5LXJ1bj1zZXJ2ZXInIGluIGFyZ3YgYW5kIGFyZ3ZbOjJdID09IFsna3ViZWN0bCcsICdwYXRjaCddIGZvciBhcmd2IGluIGNhbGxzKSlcbiAgICAgICAgc2VsZi5hc3NlcnRFcXVhbCh3cml0ZXNbMF1bMF1bJ3BhdGgnXSwgJy9tZXRhZGF0YS91aWQnKVxuXG4gICAgZGVmIHRlc3Rfc291cmNlX292ZXJyaWRlc19hdXRvbWF0aW9uX2FuZF9jdXN0b2R5X2RyaWZ0X3N0b3BfYmVmb3JlX3dyaXRlKHNlbGYpOlxuICAgICAgICBtdXRhdGlvbnMgPSBbbGFtYmRhIG86IG9bJ2FwcGxpY2F0aW9uJ11bJ21ldGFkYXRhJ10udXBkYXRlKHVpZD0ncmVjcmVhdGVkJyksXG4gICAgICAgICAgICAgICAgICAgICBsYW1iZGEgbzogb1snYXBwbGljYXRpb24nXVsnc3BlYyddLnVwZGF0ZShzeW5jUG9saWN5PXsnYXV0b21hdGVkJzoge319KSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSBvOiBvWydhcHBsaWNhdGlvbiddWydzcGVjJ11bJ3NvdXJjZSddLnVwZGF0ZShrdXN0b21pemU9eydpbWFnZXMnOiBbJ290aGVyJ119KSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSBvOiBvWydhcHBsaWNhdGlvbiddWydzcGVjJ10udXBkYXRlKHNvdXJjZXM9W3t9XSksXG4gICAgICAgICAgICAgICAgICAgICBsYW1iZGEgbzogb1snY29uZmlnbWFwJ11bJ21ldGFkYXRhJ10udXBkYXRlKHVpZD0ncmVjcmVhdGVkJyksXG4gICAgICAgICAgICAgICAgICAgICBsYW1iZGEgbzogb1sncHZjJ11bJ3NwZWMnXS51cGRhdGUodm9sdW1lTmFtZT0ncmVwbGFjZWQnKSxcbiAgICAgICAgICAgICAgICAgICAgIGxhbWJkYSBvOiBvWydkZXBsb3ltZW50J11bJ3N0YXR1cyddLnVwZGF0ZShyZWFkeVJlcGxpY2FzPTApXVxuICAgICAgICBmb3IgbXV0YXRlIGluIG11dGF0aW9uczpcbiAgICAgICAgICAgIHdpdGggc2VsZi5zdWJUZXN0KG11dGF0ZT1tdXRhdGUpOlxuICAgICAgICAgICAgICAgIG9iamVjdHMsIHBheWxvYWQgPSBzZWxmLmZpeHR1cmUoKVxuICAgICAgICAgICAgICAgIG11dGF0ZShvYmplY3RzKVxuICAgICAgICAgICAgICAgIHJlY2VpcHQsIHdyaXRlcywgY2FsbHMgPSBzZWxmLmV4ZWN1dGVfZmFrZShvYmplY3RzLCBwYXlsb2FkKVxuICAgICAgICAgICAgICAgIHNlbGYuYXNzZXJ0RmFsc2UocmVjZWlwdFsnYWxsX2NoZWNrc19wYXNzJ10pXG4gICAgICAgICAgICAgICAgc2VsZi5hc3NlcnRFcXVhbCh3cml0ZXMsIFtdKVxuXG4gICAgZGVmIHRlc3RfcHJlb3Blbl9zY2hlbWFfcmVmdXNhbF9wcmV2ZW50c19pbWFnZV9vcGVyYXRpb24oc2VsZik6XG4gICAgICAgIG9iamVjdHMsIHBheWxvYWQgPSBzZWxmLmZpeHR1cmUoKVxuICAgICAgICByZWNlaXB0LCB3cml0ZXMsIGNhbGxzID0gc2VsZi5leGVjdXRlX2Zha2Uob2JqZWN0cywgcGF5bG9hZCwgc2NoZW1hX3JlZnVzZWQ9VHJ1ZSlcbiAgICAgICAgc2VsZi5hc3NlcnRGYWxzZShyZWNlaXB0WydhbGxfY2hlY2tzX3Bhc3MnXSlcbiAgICAgICAgc2VsZi5hc3NlcnRFcXVhbCh3cml0ZXMsIFtdKVxuXG5cbiAgICBkZWYgdGVzdF9yb2xsYmFja191bnJlYWR5X2NhbmRpZGF0ZV9uZXZlcl9yZWFkc19wb2Rfb3JfZGF0YWJhc2Uoc2VsZik6XG4gICAgICAgIG9iamVjdHMsIHBheWxvYWQgPSBzZWxmLmZpeHR1cmUoKVxuICAgICAgICBwYXlsb2FkWydwaGFzZSddID0gJ3JvbGxiYWNrJ1xuICAgICAgICBvYmplY3RzWydkZXBsb3ltZW50J11bJ3N0YXR1cyddLnVwZGF0ZShyZWFkeVJlcGxpY2FzPTAsIHVwZGF0ZWRSZXBsaWNhcz0wKVxuICAgICAgICBvYmplY3RzWydkZXBsb3ltZW50J11bJ3NwZWMnXVsndGVtcGxhdGUnXVsnc3BlYyddWydjb250YWluZXJzJ11bMF1bJ2ltYWdlJ10gPSBhY3R1YXRvci5JTUFHRVxuICAgICAgICBvYmplY3RzWydhcHBsaWNhdGlvbiddWydzcGVjJ11bJ3NvdXJjZSddWyd0YXJnZXRSZXZpc2lvbiddID0gUkVWSVNJT05cbiAgICAgICAgcmVjZWlwdCwgd3JpdGVzLCBjYWxscyA9IHNlbGYuZXhlY3V0ZV9mYWtlKG9iamVjdHMsIHBheWxvYWQsIHNjaGVtYV9yZWZ1c2VkPVRydWUpXG4gICAgICAgIHNlbGYuYXNzZXJ0VHJ1ZShyZWNlaXB0WydhbGxfY2hlY2tzX3Bhc3MnXSlcbiAgICAgICAgc2VsZi5hc3NlcnRGYWxzZShyZWNlaXB0Wydyb2xsb3V0X3ZlcmlmaWVkJ10pXG4gICAgICAgIHNlbGYuYXNzZXJ0RXF1YWwobGVuKHdyaXRlcyksIDEpXG4gICAgICAgIHNlbGYuYXNzZXJ0RmFsc2UoYW55KGFyZ3ZbOjJdID09IFsna3ViZWN0bCcsICdleGVjJ10gb3IgYXJndls6M10gPT0gWydrdWJlY3RsJywgJ2dldCcsICdwb2RzJ10gZm9yIGFyZ3YgaW4gY2FsbHMpKVxuICAgICAgICBzZWxmLmFzc2VydEVxdWFsKHdyaXRlc1swXVstMV1bJ3ZhbHVlJ11bJ3N5bmMnXVsncmV2aXNpb24nXSwgJ2M5NmIwYzZkODgxZTg1NWQxZDE1ODAwM2M2ZGU5MGVmMzkwMjYyZTInKVxuXG4gICAgZGVmIHRlc3Rfcm9sbGJhY2tfc3RpbGxfcmVmdXNlc19jdXN0b2R5X29yX25vbnBhaXJfaW1hZ2VfZHJpZnQoc2VsZik6XG4gICAgICAgIGZvciBkZWZlY3QgaW4gKCdjbScsICdwdmMnLCAnaW1hZ2UnKTpcbiAgICAgICAgICAgIHdpdGggc2VsZi5zdWJUZXN0KGRlZmVjdD1kZWZlY3QpOlxuICAgICAgICAgICAgICAgIG9iamVjdHMsIHBheWxvYWQgPSBzZWxmLmZpeHR1cmUoKTtwYXlsb2FkWydwaGFzZSddID0gJ3JvbGxiYWNrJ1xuICAgICAgICAgICAgICAgIG9iamVjdHNbJ2RlcGxveW1lbnQnXVsnc3RhdHVzJ11bJ3JlYWR5UmVwbGljYXMnXSA9IDBcbiAgICAgICAgICAgICAgICBpZiBkZWZlY3QgPT0gJ2NtJzpvYmplY3RzWydjb25maWdtYXAnXVsnbWV0YWRhdGEnXVsndWlkJ10gPSAncmVjcmVhdGVkJ1xuICAgICAgICAgICAgICAgIGVsaWYgZGVmZWN0ID09ICdwdmMnOm9iamVjdHNbJ3B2YyddWydzcGVjJ11bJ3ZvbHVtZU5hbWUnXSA9ICdyZXBsYWNlZCdcbiAgICAgICAgICAgICAgICBlbHNlOm9iamVjdHNbJ2RlcGxveW1lbnQnXVsnc3BlYyddWyd0ZW1wbGF0ZSddWydzcGVjJ11bJ2NvbnRhaW5lcnMnXVswXVsnaW1hZ2UnXSA9ICdmb3JlaWduLWltYWdlJ1xuICAgICAgICAgICAgICAgIHJlY2VpcHQsIHdyaXRlcywgY2FsbHMgPSBzZWxmLmV4ZWN1dGVfZmFrZShvYmplY3RzLCBwYXlsb2FkKVxuICAgICAgICAgICAgICAgIHNlbGYuYXNzZXJ0RmFsc2UocmVjZWlwdFsnYWxsX2NoZWNrc19wYXNzJ10pO3NlbGYuYXNzZXJ0RXF1YWwod3JpdGVzLCBbXSlcblxuICAgIGRlZiB0ZXN0X3JlY292ZXJ5X3ZlcmlmaWNhdGlvbl9pc19zZXBhcmF0ZV9yZWFkeV9vbGRfaW1hZ2Vfc2NoZW1hX3Byb29mKHNlbGYpOlxuICAgICAgICBvYmplY3RzLCBwYXlsb2FkID0gc2VsZi5maXh0dXJlKCk7cGF5bG9hZFsncGhhc2UnXSA9ICdyb2xsYmFjay12ZXJpZnknXG4gICAgICAgIHJlc291cmNlPXsnZ3JvdXAnOidhcHBzJywna2luZCc6J0RlcGxveW1lbnQnLCduYW1lJzonaW5mb3JtZWQtZGVjaXNpb24nLCduYW1lc3BhY2UnOidpbmZvcm1lZC1kZWNpc2lvbid9XG4gICAgICAgIHRhcmdldD0nYzk2YjBjNmQ4ODFlODU1ZDFkMTU4MDAzYzZkZTkwZWYzOTAyNjJlMidcbiAgICAgICAgcGF5bG9hZFsnb3BlcmF0aW9uX2ludGVudCddPXsnc3RhdGUnOidDT01QTEVURScsJ3N0YXJ0ZWRfYXQnOicyMDI2LTEwLTExVDAxOjAwOjAwWicsJ3Jlc3VsdCc6eydvcGVyYXRpb25fcmVxdWVzdGVkJzpUcnVlLCdzb3VyY2VfcmV2aXNpb24nOnRhcmdldCwncHJ1bmUnOkZhbHNlLCdyZXNvdXJjZXMnOltyZXNvdXJjZV19fVxuICAgICAgICBvYmplY3RzWydhcHBsaWNhdGlvbiddWydzdGF0dXMnXT17J29wZXJhdGlvblN0YXRlJzp7J3BoYXNlJzonU3VjY2VlZGVkJywnc3RhcnRlZEF0JzonMjAyNi0xMC0xMVQwMTowMDowMVonLCdmaW5pc2hlZEF0JzonMjAyNi0xMC0xMVQwMTowMTowMFonLCdvcGVyYXRpb24nOnsnaW5pdGlhdGVkQnknOnsndXNlcm5hbWUnOidjdXN0b2RpYW4tY29kZXgnfSwnc3luYyc6eydyZXZpc2lvbic6dGFyZ2V0LCdyZXNvdXJjZXMnOltyZXNvdXJjZV19fSwnc3luY1Jlc3VsdCc6eydyZXZpc2lvbic6dGFyZ2V0LCdyZXNvdXJjZXMnOlt7KipyZXNvdXJjZSwnc3RhdHVzJzonU3luY2VkJ31dfX19XG4gICAgICAgIHJlY2VpcHQsIHdyaXRlcywgY2FsbHMgPSBzZWxmLmV4ZWN1dGVfZmFrZShvYmplY3RzLCBwYXlsb2FkKVxuICAgICAgICBzZWxmLmFzc2VydFRydWUocmVjZWlwdFsnYWxsX2NoZWNrc19wYXNzJ10pO3NlbGYuYXNzZXJ0VHJ1ZShyZWNlaXB0WydyZWNvdmVyeV92ZXJpZmljYXRpb24nXSlcbiAgICAgICAgc2VsZi5hc3NlcnRFcXVhbCh3cml0ZXMsIFtdKVxuICAgICAgICBzZWxmLmFzc2VydFRydWUoYW55KGFyZ3ZbOjJdPT1bJ2t1YmVjdGwnLCdleGVjJ10gZm9yIGFyZ3YgaW4gY2FsbHMpKVxuICAgICAgICBvYmplY3RzWydkZXBsb3ltZW50J11bJ3N0YXR1cyddWydyZWFkeVJlcGxpY2FzJ10gPSAwXG4gICAgICAgIHJlY2VpcHQsIHdyaXRlcywgY2FsbHMgPSBzZWxmLmV4ZWN1dGVfZmFrZShvYmplY3RzLHBheWxvYWQpXG4gICAgICAgIHNlbGYuYXNzZXJ0RmFsc2UocmVjZWlwdFsnYWxsX2NoZWNrc19wYXNzJ10pXG5cbiAgICBkZWYgdGVzdF9hbWJpZ3VvdXNfcGF0Y2hfaXNfbmV2ZXJfcmV0cmllZChzZWxmKTpcbiAgICAgICAgb2JqZWN0cywgcGF5bG9hZCA9IHNlbGYuZml4dHVyZSgpXG4gICAgICAgIHJlY2VpcHQsIHdyaXRlcywgY2FsbHMgPSBzZWxmLmV4ZWN1dGVfZmFrZShvYmplY3RzLCBwYXlsb2FkLCBhbWJpZ3VvdXM9VHJ1ZSlcbiAgICAgICAgc2VsZi5hc3NlcnRGYWxzZShyZWNlaXB0WydhbGxfY2hlY2tzX3Bhc3MnXSlcbiAgICAgICAgc2VsZi5hc3NlcnRFcXVhbChsZW4od3JpdGVzKSwgMSlcblxuICAgIGRlZiB0ZXN0X2ZhbHNlX29taXRlbXB0eV9uZWVkc19ib3VuZF9jdXJyZW50X3JlcXVlc3RfYW5kX2V4YWN0X2NvbXBsZXRpb24oc2VsZik6XG4gICAgICAgIG9iamVjdHMscGF5bG9hZD1zZWxmLmZpeHR1cmUoKTtwYXlsb2FkWydwaGFzZSddPSdyb2xsYmFjay12ZXJpZnknXG4gICAgICAgIHJlc291cmNlPXsnZ3JvdXAnOidhcHBzJywna2luZCc6J0RlcGxveW1lbnQnLCduYW1lJzonaW5mb3JtZWQtZGVjaXNpb24nLCduYW1lc3BhY2UnOidpbmZvcm1lZC1kZWNpc2lvbid9XG4gICAgICAgIHRhcmdldD0nYzk2YjBjNmQ4ODFlODU1ZDFkMTU4MDAzYzZkZTkwZWYzOTAyNjJlMidcbiAgICAgICAgcGF5bG9hZFsnb3BlcmF0aW9uX2ludGVudCddPXsnc3RhdGUnOidDT01QTEVURScsJ3N0YXJ0ZWRfYXQnOicyMDI2LTEwLTExVDAxOjAwOjAwWicsJ3Jlc3VsdCc6eydvcGVyYXRpb25fcmVxdWVzdGVkJzpUcnVlLCdzb3VyY2VfcmV2aXNpb24nOnRhcmdldCwncHJ1bmUnOkZhbHNlLCdyZXNvdXJjZXMnOltyZXNvdXJjZV19fVxuICAgICAgICBvYmplY3RzWydhcHBsaWNhdGlvbiddWydzdGF0dXMnXT17J29wZXJhdGlvblN0YXRlJzp7J3BoYXNlJzonU3VjY2VlZGVkJywnc3RhcnRlZEF0JzonMjAyNi0xMC0xMVQwMTowMDowMVonLCdmaW5pc2hlZEF0JzonMjAyNi0xMC0xMVQwMTowMTowMFonLCdvcGVyYXRpb24nOnsnaW5pdGlhdGVkQnknOnsndXNlcm5hbWUnOidjdXN0b2RpYW4tY29kZXgnfSwnc3luYyc6eydyZXZpc2lvbic6dGFyZ2V0LCdyZXNvdXJjZXMnOltyZXNvdXJjZV19fSwnc3luY1Jlc3VsdCc6eydyZXZpc2lvbic6dGFyZ2V0LCdyZXNvdXJjZXMnOlt7KipyZXNvdXJjZSwnc3RhdHVzJzonU3luY2VkJ31dfX19XG4gICAgICAgIHNlbGYuYXNzZXJ0VHJ1ZShzZWxmLmV4ZWN1dGVfZmFrZShvYmplY3RzLHBheWxvYWQpWzBdWydhbGxfY2hlY2tzX3Bhc3MnXSlcbiAgICAgICAgbXV0YXRpb25zPVtsYW1iZGEgbyxwOm9bJ2FwcGxpY2F0aW9uJ11bJ3N0YXR1cyddWydvcGVyYXRpb25TdGF0ZSddWydvcGVyYXRpb24nXVsnc3luYyddLnVwZGF0ZShwcnVuZT1UcnVlKSxcbiAgICAgICAgICAgICAgICAgICBsYW1iZGEgbyxwOm9bJ2FwcGxpY2F0aW9uJ11bJ3N0YXR1cyddWydvcGVyYXRpb25TdGF0ZSddWydvcGVyYXRpb24nXVsnc3luYyddLnVwZGF0ZShwcnVuZT1Ob25lKSxcbiAgICAgICAgICAgICAgICAgICBsYW1iZGEgbyxwOm9bJ2FwcGxpY2F0aW9uJ11bJ3N0YXR1cyddWydvcGVyYXRpb25TdGF0ZSddLnVwZGF0ZShzdGFydGVkQXQ9JzIwMjYtMTAtMTFUMDA6NTk6MDBaJyksXG4gICAgICAgICAgICAgICAgICAgbGFtYmRhIG8scDpvWydhcHBsaWNhdGlvbiddWydzdGF0dXMnXVsnb3BlcmF0aW9uU3RhdGUnXVsnb3BlcmF0aW9uJ10udXBkYXRlKGluaXRpYXRlZEJ5PXsndXNlcm5hbWUnOidvdGhlcid9KSxcbiAgICAgICAgICAgICAgICAgICBsYW1iZGEgbyxwOm9bJ2FwcGxpY2F0aW9uJ11bJ3N0YXR1cyddWydvcGVyYXRpb25TdGF0ZSddWydzeW5jUmVzdWx0J10udXBkYXRlKHJldmlzaW9uPSdmb3JlaWduJyksXG4gICAgICAgICAgICAgICAgICAgbGFtYmRhIG8scDpvWydhcHBsaWNhdGlvbiddWydzdGF0dXMnXVsnb3BlcmF0aW9uU3RhdGUnXVsnc3luY1Jlc3VsdCddWydyZXNvdXJjZXMnXVswXS51cGRhdGUoc3RhdHVzPSdQcnVuZWQnKSxcbiAgICAgICAgICAgICAgICAgICBsYW1iZGEgbyxwOnBbJ29wZXJhdGlvbl9pbnRlbnQnXVsncmVzdWx0J10udXBkYXRlKHBydW5lPVRydWUpLFxuICAgICAgICAgICAgICAgICAgIGxhbWJkYSBvLHA6cC51cGRhdGUob3BlcmF0aW9uX2ludGVudD1Ob25lKV1cbiAgICAgICAgZm9yIG11dGF0ZSBpbiBtdXRhdGlvbnM6XG4gICAgICAgICAgICBvLHA9Y29weS5kZWVwY29weShvYmplY3RzKSxjb3B5LmRlZXBjb3B5KHBheWxvYWQpO211dGF0ZShvLHApXG4gICAgICAgICAgICByZWNlaXB0LHdyaXRlcyxjYWxscz1zZWxmLmV4ZWN1dGVfZmFrZShvLHApXG4gICAgICAgICAgICBzZWxmLmFzc2VydEZhbHNlKHJlY2VpcHRbJ2FsbF9jaGVja3NfcGFzcyddKTtzZWxmLmFzc2VydEVxdWFsKHdyaXRlcyxbXSlcblxuICAgIGRlZiB0ZXN0X29wdGltaXplZF9leGVjdXRvcl9yZWZ1c2VzX2JlZm9yZV9hbnlfaW5wdXQoc2VsZik6XG4gICAgICAgIHJlc3VsdD1zdWJwcm9jZXNzLnJ1bihbc3lzLmV4ZWN1dGFibGUsJy1PJyxzdHIoUGF0aChfX2ZpbGVfXykucGFyZW50LydhY3R1YXRvci5weScpLCctLXBoYXNlJywncmVsZWFzZScsJy0tcm9vdC1hdXRob3JpdHktcmVjZWlwdCcsJy9taXNzaW5nJywnLS1yb290LW5hdGl2ZS1vdXRjb21lJywnL21pc3NpbmcnLCctLXNvdXJjZS1yZXZpc2lvbicsUkVWSVNJT05dLGNhcHR1cmVfb3V0cHV0PVRydWUsdGV4dD1UcnVlKVxuICAgICAgICBzZWxmLmFzc2VydE5vdEVxdWFsKHJlc3VsdC5yZXR1cm5jb2RlLDApXG4gICAgICAgIHNlbGYuYXNzZXJ0SW4oJ29wdGltaXplZCBpbnRlcnByZXRlciByZWZ1c2VkJyxyZXN1bHQuc3RkZXJyKVxuXG5cbmlmIF9fbmFtZV9fID09ICdfX21haW5fXyc6XG4gICAgdW5pdHRlc3QubWFpbigpXG4iCiAgICB9CiAgfSwKICAibmF0aXZlX2V4ZWN1dGlvbiI6IGZhbHNlLAogICJjYXNlX2V2aWRlbmNlIjogewogICAgInNxbGl0ZV9yZWFkb25seS5weSI6IHsKICAgICAgInNoYTI1NiI6ICI5MDkzYzY0ZjdmMGUzOWIyNDlkYTlmZWZkZjVhYWY0ZTQyNThiYzMxZTdhMjdmZDg4ODQ3OWNlMzI5YTIwNTA5IiwKICAgICAgImNvbnRlbnQiOiAiXCJcIlwiUHJlLW9wZW4gc2NoZW1hL2ludGVncml0eS9jb3VudCBwcm9vZjsgc3RkbGliIG9ubHksIG5ldmVyIGNvbnN0cnVjdHMgU3RvcmUuXCJcIlwiXG5pbXBvcnQgYXJncGFyc2VcbmZyb20gY29udGV4dGxpYiBpbXBvcnQgY2xvc2luZ1xuaW1wb3J0IGhhc2hsaWJcbmltcG9ydCBvc1xuaW1wb3J0IHJlXG5pbXBvcnQgc3RhdFxuaW1wb3J0IGpzb25cbmZyb20gcGF0aGxpYiBpbXBvcnQgUGF0aFxuaW1wb3J0IHNxbGl0ZTNcblxuXG5kZWYgaW5zcGVjdF9yZWFkb25seShwYXRoLCAqLCBfY2xvc2VkX3NuYXBzaG90PUZhbHNlKTpcbiAgICBwYXRoID0gUGF0aChwYXRoKVxuICAgIGlmIG5vdCBwYXRoLmlzX2ZpbGUoKSBvciBwYXRoLmlzX3N5bWxpbmsoKTpcbiAgICAgICAgcmFpc2UgVmFsdWVFcnJvcignZXhpc3Rpbmcgbm9uc3ltbGluayBwcml2YXRlIGRhdGFiYXNlIHJlcXVpcmVkJylcbiAgICB1cmkgPSBwYXRoLnJlc29sdmUoKS5hc191cmkoKSArICc/bW9kZT1ybycgKyAoJyZpbW11dGFibGU9MScgaWYgX2Nsb3NlZF9zbmFwc2hvdCBlbHNlICcnKVxuICAgICMgc3FsaXRlMy5Db25uZWN0aW9uIGNvbnRleHQgbWFuYWdlbWVudCBlbmRzIHRyYW5zYWN0aW9uczsgaXQgZG9lcyBub3QgY2xvc2UuXG4gICAgd2l0aCBjbG9zaW5nKHNxbGl0ZTMuY29ubmVjdCh1cmksIHVyaT1UcnVlLCB0aW1lb3V0PTUpKSBhcyBkYjpcbiAgICAgICAgZGIuZXhlY3V0ZSgnUFJBR01BIHF1ZXJ5X29ubHk9T04nKVxuICAgICAgICBkYi5leGVjdXRlKCdCRUdJTicpXG4gICAgICAgIHZlcnNpb24gPSBkYi5leGVjdXRlKCdQUkFHTUEgdXNlcl92ZXJzaW9uJykuZmV0Y2hvbmUoKVswXVxuICAgICAgICBpZiB2ZXJzaW9uICE9IDU6XG4gICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdwcmUtb3BlbiBkYXRhYmFzZSBpcyBub3Qgc2NoZW1hNTsgcmVmdXNlIG1pZ3JhdGlvbicpXG4gICAgICAgIGlmIGRiLmV4ZWN1dGUoJ1BSQUdNQSBpbnRlZ3JpdHlfY2hlY2snKS5mZXRjaGFsbCgpICE9IFsoJ29rJywpXTpcbiAgICAgICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoJ3JlYWQtb25seSBpbnRlZ3JpdHkgY2hlY2sgcmVmdXNlZCcpXG4gICAgICAgIGxpZmVjeWNsZSA9IHtuYW1lOiBkYi5leGVjdXRlKCdTRUxFQ1QgQ09VTlQoKikgRlJPTSAnICsgdGFibGUpLmZldGNob25lKClbMF1cbiAgICAgICAgICAgICAgICAgICAgIGZvciBuYW1lLCB0YWJsZSBpbiAoKCdyZWNvcmRzJywgJ2xpZmVjeWNsZV9yZWNvcmRzJyksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICgnZXZlbnRzJywgJ2xpZmVjeWNsZV9ldmVudHMnKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCdwZW5kaW5nJywgJ2xpZmVjeWNsZV9wZW5kaW5nJykpfVxuICAgICAgICByZXN1bHQgPSB7J3NjaGVtYV92ZXJzaW9uJzogdmVyc2lvbiwgJ2xpZmVjeWNsZSc6IGxpZmVjeWNsZSxcbiAgICAgICAgICAgICAgICAgICdvdXRib3gnOiBkaWN0KGRiLmV4ZWN1dGUoJ1NFTEVDVCBzdGF0ZSxDT1VOVCgqKSBGUk9NIG91dGJveCBHUk9VUCBCWSBzdGF0ZScpKSxcbiAgICAgICAgICAgICAgICAgICdzdWJtaXNzaW9ucyc6IGRpY3QoZGIuZXhlY3V0ZSgnU0VMRUNUIHN0YXRlLENPVU5UKCopIEZST00gc3VibWlzc2lvbnMgR1JPVVAgQlkgc3RhdGUnKSksXG4gICAgICAgICAgICAgICAgICAnaW50ZWdyaXR5JzogJ29rJywgJ3JlYWRfb25seV9wcmVfb3Blbic6IFRydWUsICdzdG9yZV9jb25zdHJ1Y3RlZCc6IEZhbHNlfVxuICAgICAgICBkYi5yb2xsYmFjaygpXG4gICAgcmV0dXJuIHJlc3VsdFxuXG5cbmRlZiBpbnNwZWN0X2Nsb3NlZF9zbmFwc2hvdChwYXRoLCBleHBlY3RlZF9zaGEyNTYpOlxuICAgIFwiXCJcIk9ubHkgYSBwcml2YXRlbHkgcmV0YWluZWQsIGZyb3plbiBtYWluLWZpbGUgc25hcHNob3Qgd2l0aCBubyBXQUwgZnJhbWVzLlxuXG4gICAgQ2FsbGVyIG11c3QgZXN0YWJsaXNoIGV4Y2x1c2l2ZSBzbmFwc2hvdCBjcmVhdGlvbi9hZG1pbiBleGl0L25vIHdyaXRlciwgYW5kXG4gICAgc3VwcGx5IHRoZSByZWNvcmRlZCBmcm96ZW4gbWFpbiBkaWdlc3QuIE5ldmVyIHVzZSBmb3IgdGhlIGxpdmUgZGF0YWJhc2UuXG4gICAgSW1tdXRhYmxlIFNRTGl0ZSByZWFkcyBpZ25vcmUgc3RhbGUgU0hNIGFuZCBuZXZlciBjcmVhdGUvY2hhbmdlIHNpZGVjYXJzLlxuICAgIEV4aXN0aW5nIHNpZGVjYXJzIHJlbWFpbiBjdXN0b2R5IGV2aWRlbmNlOyBub3RoaW5nIGlzIGRlbGV0ZWQvY2hlY2twb2ludGVkLlxuICAgIFwiXCJcIlxuICAgIHBhdGggPSBQYXRoKHBhdGgpXG4gICAgaWYgcmUuZnVsbG1hdGNoKHInWzAtOWEtZl17NjR9JywgZXhwZWN0ZWRfc2hhMjU2KSBpcyBOb25lOlxuICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdleGFjdCBmcm96ZW4gc25hcHNob3QgZGlnZXN0IHJlcXVpcmVkJylcbiAgICBkZWYgbWV0YWRhdGEodGFyZ2V0KTpcbiAgICAgICAgaW5mbyA9IHRhcmdldC5sc3RhdCgpXG4gICAgICAgIGlmIG5vdCBzdGF0LlNfSVNSRUcoaW5mby5zdF9tb2RlKSBvciBzdGF0LlNfSU1PREUoaW5mby5zdF9tb2RlKSAhPSAwbzYwMCBvciBpbmZvLnN0X25saW5rICE9IDE6XG4gICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdwcml2YXRlIHNpbmdsZS1saW5rIHNuYXBzaG90IGN1c3RvZHkgcmVxdWlyZWQnKVxuICAgICAgICByZXR1cm4gaW5mb1xuICAgIG1haW4gPSBtZXRhZGF0YShwYXRoKVxuICAgIGlmIG5vdCAwIDwgbWFpbi5zdF9zaXplIDw9IDUxMiAqIDEwMjQqKjI6XG4gICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoJ2JvdW5kZWQgZnJvemVuIHNuYXBzaG90IHJlcXVpcmVkJylcbiAgICBkZWYgc2lkZWNhcnMoKTpcbiAgICAgICAgaWRlbnRpdGllcyA9IHt9XG4gICAgICAgIGZvciBzdWZmaXggaW4gKCctd2FsJywgJy1zaG0nKTpcbiAgICAgICAgICAgIHRhcmdldCA9IFBhdGgoc3RyKHBhdGgpICsgc3VmZml4KVxuICAgICAgICAgICAgaWYgb3MucGF0aC5sZXhpc3RzKHRhcmdldCk6XG4gICAgICAgICAgICAgICAgaW5mbyA9IG1ldGFkYXRhKHRhcmdldClcbiAgICAgICAgICAgICAgICBpZiBpbmZvLnN0X3VpZCAhPSBtYWluLnN0X3VpZCBvciBpbmZvLnN0X2dpZCAhPSBtYWluLnN0X2dpZDpcbiAgICAgICAgICAgICAgICAgICAgcmFpc2UgVmFsdWVFcnJvcignc25hcHNob3Qgc2lkZWNhciBjdXN0b2R5IGNoYW5nZWQnKVxuICAgICAgICAgICAgICAgIGlmIHN1ZmZpeCA9PSAnLXdhbCcgYW5kIGluZm8uc3Rfc2l6ZSAhPSAwOlxuICAgICAgICAgICAgICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdzbmFwc2hvdCBoYXMgV0FMIGZyYW1lczsgaW1tdXRhYmxlIHJlYWQgcmVmdXNlZCcpXG4gICAgICAgICAgICAgICAgaWRlbnRpdGllc1tzdWZmaXhdID0gKGluZm8uc3RfZGV2LCBpbmZvLnN0X2lubywgaW5mby5zdF9zaXplLCBpbmZvLnN0X210aW1lX25zKVxuICAgICAgICByZXR1cm4gaWRlbnRpdGllc1xuICAgIGJlZm9yZV9zaWRlY2FycyA9IHNpZGVjYXJzKClcbiAgICBkZWYgZGlnZXN0KCk6XG4gICAgICAgIHZhbHVlID0gaGFzaGxpYi5zaGEyNTYoKVxuICAgICAgICB3aXRoIHBhdGgub3BlbigncmInKSBhcyBzdHJlYW06XG4gICAgICAgICAgICBmb3IgYmxvY2sgaW4gaXRlcihsYW1iZGE6IHN0cmVhbS5yZWFkKDEwMjQqKjIpLCBiJycpOlxuICAgICAgICAgICAgICAgIHZhbHVlLnVwZGF0ZShibG9jaylcbiAgICAgICAgcmV0dXJuIHZhbHVlLmhleGRpZ2VzdCgpXG4gICAgaWYgZGlnZXN0KCkgIT0gZXhwZWN0ZWRfc2hhMjU2OlxuICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdmcm96ZW4gbWFpbi1maWxlIGRpZ2VzdCBjaGFuZ2VkIGJlZm9yZSBpbnNwZWN0aW9uJylcbiAgICBwcm9vZiA9IGluc3BlY3RfcmVhZG9ubHkocGF0aCwgX2Nsb3NlZF9zbmFwc2hvdD1UcnVlKVxuICAgIGFmdGVyID0gbWV0YWRhdGEocGF0aClcbiAgICBpZiAoYWZ0ZXIuc3RfZGV2LCBhZnRlci5zdF9pbm8sIGFmdGVyLnN0X3VpZCwgYWZ0ZXIuc3RfZ2lkLCBhZnRlci5zdF9zaXplKSAhPSAobWFpbi5zdF9kZXYsIG1haW4uc3RfaW5vLCBtYWluLnN0X3VpZCwgbWFpbi5zdF9naWQsIG1haW4uc3Rfc2l6ZSk6XG4gICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoJ2Zyb3plbiBtYWluLWZpbGUgaWRlbnRpdHkgY2hhbmdlZCcpXG4gICAgaWYgZGlnZXN0KCkgIT0gZXhwZWN0ZWRfc2hhMjU2IG9yIHNpZGVjYXJzKCkgIT0gYmVmb3JlX3NpZGVjYXJzOlxuICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdmcm96ZW4gc25hcHNob3QgY2hhbmdlZCBkdXJpbmcgaW5zcGVjdGlvbicpXG4gICAgcmV0dXJuIHByb29mXG5cblxuaWYgX19uYW1lX18gPT0gJ19fbWFpbl9fJzpcbiAgICBwYXJzZXIgPSBhcmdwYXJzZS5Bcmd1bWVudFBhcnNlcihkZXNjcmlwdGlvbj1fX2RvY19fKVxuICAgIHBhcnNlci5hZGRfYXJndW1lbnQoJy0tZGInLCB0eXBlPVBhdGgsIHJlcXVpcmVkPVRydWUpXG4gICAgYXJncyA9IHBhcnNlci5wYXJzZV9hcmdzKClcbiAgICB0cnk6XG4gICAgICAgIHByaW50KGpzb24uZHVtcHMoaW5zcGVjdF9yZWFkb25seShhcmdzLmRiKSwgc29ydF9rZXlzPVRydWUpKVxuICAgIGV4Y2VwdCBFeGNlcHRpb246XG4gICAgICAgIHJhaXNlIFN5c3RlbUV4aXQoJ3JlYWQtb25seSBzY2hlbWE1L2ludGVncml0eS9jb3VudCBnYXRlIHJlZnVzZWQ7IG5vIFN0b3JlL2FkbWluIG9wZW4nKSBmcm9tIE5vbmVcbiIKICAgIH0sCiAgICAiY2hhbmdlX2d1YXJkLnB5IjogewogICAgICAic2hhMjU2IjogImIzZWUyYjg5ZGUxNGUyMDRlZmVkZDAxNmQzOTAwYjZlMzFmY2M5ZjBlYTBmMzEyNWQzYTQ3OTRkOTM4Y2EyMDEiLAogICAgICAiY29udGVudCI6ICJcIlwiXCJSZWFkLW9ubHkgZXhhY3QgRGVwbG95bWVudCBhbmQgYWRtaXNzaW9uIGJvdW5kYXJpZXM7IG5vIG11dGF0aW9uIGF1dGhvcml0eS5cIlwiXCJcbmltcG9ydCBjb3B5XG5pbXBvcnQgaGFzaGxpYlxuaW1wb3J0IGpzb25cblxuQkFTRSA9ICdmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2Ojc2M2MzNjQwMmM4YmU3Y2M0YzFiZDFhNjE2MjNkMWFmZjkxMTZkNTIyNDE4Nzc0MjViNDVhNzAxOGNjMDg4M2EnXG5DQU5ESURBVEUgPSAnZm9yZ2Vqby5jb3Vsb21iLnNvY2lhbC9jb3Vsb21iL2luZm9ybWVkLWRlY2lzaW9uQHNoYTI1NjpmNTU4OGY0OTZiMWYyNjdkMDdjZGM5ZjUyMDhlNmYyZjI0MWY3NGI3MzljMjM5MjRiMDM3MDJkZWMxM2E1YzIzJ1xuXG5cbmRlZiBkZXBlbmRlbmN5X3Byb2plY3Rpb24oY29uZmlnbWFwLCBwdmMpOlxuICAgIGlmIChjb25maWdtYXAuZ2V0KCdraW5kJykgIT0gJ0NvbmZpZ01hcCcgb3IgY29uZmlnbWFwLmdldCgnaW1tdXRhYmxlJykgaXMgbm90IFRydWVcbiAgICAgICAgICAgIG9yIGNvbmZpZ21hcFsnbWV0YWRhdGEnXVsnbmFtZXNwYWNlJ10gIT0gJ2luZm9ybWVkLWRlY2lzaW9uJ1xuICAgICAgICAgICAgb3IgY29uZmlnbWFwWydtZXRhZGF0YSddWyduYW1lJ10gIT0gJ2luZm9ybWVkLWRlY2lzaW9uLXJ1bnRpbWUtNmMyMjE4NzhkMDUxJ1xuICAgICAgICAgICAgb3Igc2V0KGNvbmZpZ21hcC5nZXQoJ2RhdGEnLCB7fSkpICE9IHsncnVudGltZS5qc29uJ31cbiAgICAgICAgICAgIG9yIGNvbmZpZ21hcC5nZXQoJ2JpbmFyeURhdGEnKSk6XG4gICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoJ3VuZXhwZWN0ZWQgaW1tdXRhYmxlIHJ1bnRpbWUgY29uZmlnIGlkZW50aXR5JylcbiAgICBpZiAocHZjLmdldCgna2luZCcpICE9ICdQZXJzaXN0ZW50Vm9sdW1lQ2xhaW0nXG4gICAgICAgICAgICBvciBwdmNbJ21ldGFkYXRhJ11bJ25hbWVzcGFjZSddICE9ICdpbmZvcm1lZC1kZWNpc2lvbidcbiAgICAgICAgICAgIG9yIHB2Y1snbWV0YWRhdGEnXVsnbmFtZSddICE9ICdpbmZvcm1lZC1kZWNpc2lvbi1kYXRhJ1xuICAgICAgICAgICAgb3IgcHZjLmdldCgnc3RhdHVzJywge30pLmdldCgncGhhc2UnKSAhPSAnQm91bmQnXG4gICAgICAgICAgICBvciBub3QgcHZjLmdldCgnc3BlYycsIHt9KS5nZXQoJ3ZvbHVtZU5hbWUnKSk6XG4gICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoJ3VuZXhwZWN0ZWQgYm91bmQgUFZDIGN1c3RvZHknKVxuICAgIGNvbmZpZyA9IHByb2plY3Rpb24oY29uZmlnbWFwKVxuICAgIGNvbmZpZy5wb3AoJ2RhdGEnKVxuICAgIGNvbmZpZ1snY2Fub25pY2FsX2RhdGFfc2hhMjU2J10gPSBoYXNobGliLnNoYTI1Nihqc29uLmR1bXBzKFxuICAgICAgICBjb25maWdtYXBbJ2RhdGEnXSwgc29ydF9rZXlzPVRydWUsIHNlcGFyYXRvcnM9KCcsJywgJzonKSkuZW5jb2RlKCkpLmhleGRpZ2VzdCgpXG4gICAgcmV0dXJuIHsnY29uZmlnbWFwJzogY29uZmlnLCAncHZjJzogcHJvamVjdGlvbihwdmMpLCAncHZjX3BoYXNlJzogJ0JvdW5kJ31cblxuXG5kZWYgY2hlY2tfZGVwZW5kZW5jaWVzKGNvbmZpZ21hcCwgcHZjLCBzZWFsZWQpOlxuICAgIGlmIGRlcGVuZGVuY3lfcHJvamVjdGlvbihjb25maWdtYXAsIHB2YykgIT0gc2VhbGVkOlxuICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdydW50aW1lIGNvbmZpZyBvciBQVkMgaWRlbnRpdHkvY3VzdG9keSBjaGFuZ2VkJylcblxuXG5kZWYgcHJvamVjdGlvbih2YWx1ZSk6XG4gICAgdmFsdWUgPSBjb3B5LmRlZXBjb3B5KHZhbHVlKVxuICAgIHZhbHVlLnBvcCgnc3RhdHVzJywgTm9uZSlcbiAgICBmb3Iga2V5IGluICgncmVzb3VyY2VWZXJzaW9uJywgJ2dlbmVyYXRpb24nLCAnY3JlYXRpb25UaW1lc3RhbXAnLCAnbWFuYWdlZEZpZWxkcycpOlxuICAgICAgICB2YWx1ZVsnbWV0YWRhdGEnXS5wb3Aoa2V5LCBOb25lKVxuICAgIGZvciBrZXkgaW4gKCdrdWJlY3RsLmt1YmVybmV0ZXMuaW8vbGFzdC1hcHBsaWVkLWNvbmZpZ3VyYXRpb24nLCAnZGVwbG95bWVudC5rdWJlcm5ldGVzLmlvL3JldmlzaW9uJyk6XG4gICAgICAgIHZhbHVlWydtZXRhZGF0YSddLmdldCgnYW5ub3RhdGlvbnMnLCB7fSkucG9wKGtleSwgTm9uZSlcbiAgICByZXR1cm4gdmFsdWVcblxuXG5kZWYgY2hlY2tfY3VycmVudChjdXJyZW50LCBzZWFsZWQpOlxuICAgIGlmIHByb2plY3Rpb24oY3VycmVudCkgIT0gc2VhbGVkOlxuICAgICAgICByYWlzZSBWYWx1ZUVycm9yKCdzZWFsZWQgbGl2ZSBVSUQgb3IgY29uZmlndXJhdGlvbiBjaGFuZ2VkJylcblxuXG5kZWYgY2hlY2tfYWRtaXR0ZWQoYWRtaXR0ZWQsIHNlYWxlZCk6XG4gICAgZXhwZWN0ZWQgPSBjb3B5LmRlZXBjb3B5KHNlYWxlZClcbiAgICBjb250YWluZXJzID0gZXhwZWN0ZWRbJ3NwZWMnXVsndGVtcGxhdGUnXVsnc3BlYyddWydjb250YWluZXJzJ11cbiAgICBpZiBsZW4oY29udGFpbmVycykgIT0gMSBvciBjb250YWluZXJzWzBdWyduYW1lJ10gIT0gJ2luZm9ybWVkLWRlY2lzaW9uJyBvciBjb250YWluZXJzWzBdWydpbWFnZSddICE9IEJBU0U6XG4gICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoJ3VuZXhwZWN0ZWQgYmFzZWxpbmUgY29udGFpbmVyIGlkZW50aXR5JylcbiAgICBjb250YWluZXJzWzBdWydpbWFnZSddID0gQ0FORElEQVRFXG4gICAgaWYgcHJvamVjdGlvbihhZG1pdHRlZCkgIT0gZXhwZWN0ZWQ6XG4gICAgICAgIHJhaXNlIFZhbHVlRXJyb3IoJ2FkbWlzc2lvbiBjaGFuZ2VkIGNvbmZpZ3VyYXRpb24gYmV5b25kIHRoZSBzb2xlIGltYWdlIHBpbicpXG5cblxuZGVmIHRyYW5zcG9ydF9yZXN1bHQoZXhpdF9jb2RlLCBvdXRwdXQpOlxuICAgIGltcG9ydCBqc29uXG4gICAgdHJ5OlxuICAgICAgICB2YWx1ZSA9IGpzb24ubG9hZHMob3V0cHV0KVxuICAgIGV4Y2VwdCAoVmFsdWVFcnJvciwgVHlwZUVycm9yKTpcbiAgICAgICAgcmV0dXJuIHsnYWxsX2NoZWNrc19wYXNzJzogRmFsc2UsICdyZWFzb24nOiAnaW52YWxpZCB0cmFuc3BvcnQgcmVzdWx0J31cbiAgICBpZiBleGl0X2NvZGUgIT0gMCBvciBub3QgaXNpbnN0YW5jZSh2YWx1ZSwgZGljdCkgb3IgdmFsdWUuZ2V0KCdhbGxfY2hlY2tzX3Bhc3MnKSBpcyBub3QgVHJ1ZTpcbiAgICAgICAgcmV0dXJuIHsnYWxsX2NoZWNrc19wYXNzJzogRmFsc2UsICdyZWFzb24nOiAncmVhZC1vbmx5IHByZWZsaWdodCByZWZ1c2VkJ31cbiAgICByZXR1cm4gdmFsdWVcbiIKICAgIH0sCiAgICAiY2hlY2tfaW5zdGFsbGVkLnB5IjogewogICAgICAic2hhMjU2IjogImEzNGE1MWJlOWM5ZjJiMTZhMDZhODgxNzNiNWM2YzQ5MmM5OTU3ZmUwMjFjZjQxMmE4NmExNzM3NzMwNjQwYWQiLAogICAgICAiY29udGVudCI6ICJpbXBvcnQgY29weSxqc29uLHRpbWVcbmZyb20gZGF0ZXRpbWUgaW1wb3J0IGRhdGV0aW1lLHRpbWV6b25lXG5mcm9tIGluZm9ybWVkX2RlY2lzaW9uLnBvbGljeSBpbXBvcnQgUG9saWN5Q2xpZW50LENPTlRSQUNULHN1Ym1pdHRlZF9kaWdlc3RcbnBpbj0nc2hhMjU2OicrJ2InKjY0XG5yZXE9eydpZCc6J2ltYWdlLXB1cnBvc2UtZml4dHVyZScsJ3RlbmFudCc6J3RlbmFudDpwbGF0Zm9ybScsJ3N1YmplY3QnOnsnaWQnOidzeW50aGV0aWMnLCd0eXBlJzonc2VydmljZScsJ3RlbmFudCc6J3RlbmFudDpwbGF0Zm9ybSd9LCdyZXNvdXJjZSc6eydpZCc6J21lbW86Zml4dHVyZScsJ3R5cGUnOidtZW1vJywnc3lzdGVtJzonaW5mb3JtZWQtZGVjaXNpb24nLCd0ZW5hbnQnOid0ZW5hbnQ6cGxhdGZvcm0nfSwnYWN0aW9uJzoncmVnaXN0ZXInLCdjb250ZXh0Jzp7fX1cbmNsYXNzIFRyYW5zcG9ydDpcbiBkZWYgcmVxdWVzdChzZWxmLCphcmdzLCoqa3dhcmdzKTpcbiAgbm93PXRpbWUudGltZSgpOyBzdGFtcD1sYW1iZGEgdDpkYXRldGltZS5mcm9tdGltZXN0YW1wKHQsdGltZXpvbmUudXRjKS5pc29mb3JtYXQoKVxuICBiPXsqKmNvcHkuZGVlcGNvcHkoe2s6cmVxW2tdIGZvciBrIGluICgndGVuYW50Jywnc3ViamVjdCcsJ2FjdGlvbicsJ3Jlc291cmNlJywnY29udGV4dCcpfSksJ3JlcXVlc3RfZGlnZXN0Jzonc2hhMjU2OicrJ2UnKjY0LCdzdWJtaXR0ZWRfcmVxdWVzdF9kaWdlc3QnOnN1Ym1pdHRlZF9kaWdlc3QocmVxKX1cbiAgaWYgc2VsZi5wdXJwb3NlIT0nQUJTRU5UJzpiWydwdXJwb3NlJ109c2VsZi5wdXJwb3NlXG4gIGQ9eydpZCc6J3N5bnRoZXRpYy1kZWNpc2lvbicsJ2NvbnRyYWN0X3ZlcnNpb24nOkNPTlRSQUNULCdyZXF1ZXN0X2lkJzpyZXFbJ2lkJ10sJ2VmZmVjdCc6J2FsbG93JywnbWF0Y2hlZF9wb2xpY3lfdmVyc2lvbic6J3YxJywnc3ViamVjdCc6cmVxWydzdWJqZWN0J10sJ3Jlc291cmNlJzpyZXFbJ3Jlc291cmNlJ10sJ2JpbmRpbmcnOmIsJ2xpZmV0aW1lJzp7J2tpbmQnOid0dGwnLCdub3RfYmVmb3JlJzpzdGFtcChub3ctMSksJ2V4cGlyZXNfYXQnOnN0YW1wKG5vdys2MCl9LCdwcm92ZW5hbmNlJzp7J3BvbGljeV9wYWNrYWdlJzonZml4dHVyZScsJ3BvbGljeV92ZXJzaW9uJzondjEnLCdwb2xpY3lfcGFja2FnZV9kaWdlc3QnOnBpbiwncmVnaXN0cnlfc25hcHNob3RfZGlnZXN0Jzonc2hhMjU2OicrJ2MnKjY0LCdldmFsdWF0b3InOidmbGV4LWF1dGgvZml4dHVyZScsJ2RlY2lzaW9uX3RpbWUnOnN0YW1wKG5vdyl9fVxuICByZXR1cm4gMjAwLGRcbmNoZWNrcz1bXVxuZm9yIHB1cnBvc2UgaW4gKCdBQlNFTlQnLCcnLCdyZWFkYmFjaycsJ2VuZm9yY2VtZW50JywndW5rbm93bicsTm9uZSxGYWxzZSwwLFtdLHt9KTpcbiB0PVRyYW5zcG9ydCgpO3QucHVycG9zZT1wdXJwb3NlXG4gYz1Qb2xpY3lDbGllbnQoJ2h0dHBzOi8vZml4dHVyZS5pbnZhbGlkJyxsYW1iZGE6J3N5bnRoZXRpYy1vbmx5JyxwYWNrYWdlPSdmaXh0dXJlJyx2ZXJzaW9uPSd2MScscGFja2FnZV9kaWdlc3Q9cGluLHRyYW5zcG9ydD10KVxuIG89Yy5jaGVjayhyZXEpXG4gaWYgcHVycG9zZSBpbiAoJ0FCU0VOVCcsJycpOmFzc2VydCBvLm91dGNvbWU9PSdhbGxvdyc7by5yZXF1aXJlX2N1cnJlbnQodGltZS50aW1lKCkpXG4gZWxzZTphc3NlcnQgby5vdXRjb21lPT0naW52YWxpZF9kZWNpc2lvbicgYW5kIG8uZGVjaXNpb25fanNvbiBpcyBOb25lXG4gY2hlY2tzLmFwcGVuZChzdHIocHVycG9zZSkpXG5wcmludChqc29uLmR1bXBzKHsnaW5zdGFsbGVkX2ltYWdlX3B1cnBvc2VfdmFyaWFudHMnOmxlbihjaGVja3MpLCdiaW5kaW5nX2Zvcm1zX2FsbG93ZWQnOjIsJ2Fkdmlzb3J5X29yX21hbGZvcm1lZF9yZWZ1c2VkJzo4LCdzeW50aGV0aWNfb25seSc6VHJ1ZSwnbmF0aXZlX3Byb3ZpZGVyJzpGYWxzZX0pKVxuIgogICAgfSwKICAgICJwb3N0LXJlYWR5LW5hdGl2ZS1vdXRjb21lcy1jb21tYW5kLnB5IjogewogICAgICAic2hhMjU2IjogImNlODhhY2E1ZTJkNmNkNWFiNzUzMzg5OGJjOWIyNDZiZDE1OTQ4MmM0YWNjY2IwYWZlNGM0NTFlZTg4ZjU3ZmEiLAogICAgICAiY29udGVudCI6ICJcIlwiXCJSdW4gb25seSBpbnNpZGUgZXhpc3RpbmcgYWRtaXR0ZWQgU3RhdGVIdWIgUG9kIEFGVEVSIHZlcmlmaWVkIGNhbmRpZGF0ZSBSZWFkeS5cblxuVGhlIGZpeGVkIGV4aXN0aW5nIGRpc3BhdGNoZXIgbG9hZHMgaXRzIGludGVybmFsIGNyZWRlbnRpYWw7IHJvb3QgbmV2ZXIgcmVhZHNcbml0LiBIVFRQIHN1Y2Nlc3MgcHJvdmVzIGNhbm9uaWNhbCBsaWZlY3ljbGUub3V0Y29tZXMgUERQIGdhdGUgcmV0dXJuZWQsIG5vdCBhblxuYWR2aXNvcnkgdmVyZGljdCBlbWlzc2lvbiBvciBhIHN1YnN0aXR1dGUgZm9yIHNlcGFyYXRlIGV2aWRlbmNlIG9ibGlnYXRpb25zLlxuUm9vdCByZWNvcmRzIHRoaXMgYm91bmRlZCBtZXRhZGF0YSByZWNlaXB0IHdpdGggdmVyaWZpZWQgUG9kVUlEL2ltYWdlL3NvdXJjZS5cblRoaXMgcHJlcGFyYXRpb24gb25seSB3cml0ZXMgc291cmNlOyBubyByZXF1ZXN0IGlzIG1hZGUgaGVyZS5cblwiXCJcIlxuaW1wb3J0IGRhdGV0aW1lLGpzb24sdXJsbGliLnJlcXVlc3QsdXJsbGliLmVycm9yXG5mcm9tIGFwaS5zZXJ2aWNlcy5tZW1vX2Rpc3BhdGNoIGltcG9ydCBPUklHSU4sTm9SZWRpcmVjdCx0b2tlbl9maWxlXG5pZD0naW5mZC0yMDI2MTAxMS16bGliLXJlbWVkaWF0aW9uLXIxJ1xucmVxPXVybGxpYi5yZXF1ZXN0LlJlcXVlc3QoT1JJR0lOKycvdjEvZGVjaXNpb25zLycraWQrJy9vdXRjb21lcz92ZXJzaW9uPTEnLGhlYWRlcnM9eydBdXRob3JpemF0aW9uJzonQmVhcmVyICcrdG9rZW5fZmlsZSgnL3Zhci9ydW4vc2VjcmV0cy9zdGF0ZS1odWIvaW5mb3JtZWQtZGVjaXNpb24vbGlmZWN5Y2xlLXRva2VuJyl9KVxub3BlbmVyPXVybGxpYi5yZXF1ZXN0LmJ1aWxkX29wZW5lcih1cmxsaWIucmVxdWVzdC5Qcm94eUhhbmRsZXIoe30pLE5vUmVkaXJlY3QoKSlcbnRyeTpcbiB3aXRoIG9wZW5lci5vcGVuKHJlcSx0aW1lb3V0PTE1KSBhcyByZXNwb25zZTpcbiAgcmF3PXJlc3BvbnNlLnJlYWQoMTA0ODU3NylcbiAgaWYgbGVuKHJhdyk+MTA0ODU3NjpyYWlzZSBWYWx1ZUVycm9yKCdvdmVyc2l6ZScpXG4gIHI9anNvbi5sb2FkcyhyYXcpXG4gcHJpbnQoanNvbi5kdW1wcyh7J21lbW9faWQnOmlkLCdzb3VyY2VfbWVtb192ZXJzaW9uJzoxLCdvYnNlcnZlZF9hdCc6ZGF0ZXRpbWUuZGF0ZXRpbWUubm93KGRhdGV0aW1lLnRpbWV6b25lLnV0YykuaXNvZm9ybWF0KCksJ25hdGl2ZV9hdXRoZW50aWNhdGVkX2Nhbm9uaWNhbF9saWZlY3ljbGVfb3V0Y29tZXMnOlRydWUsJ25hdGl2ZV9wcm92aWRlcl9wYXRoX3JldHVybmVkJzpUcnVlLCdjcmVkZW50aWFsX21hdGVyaWFsX291dHB1dCc6RmFsc2UsJ3Jldmlldyc6ci5nZXQoJ3JldmlldycpLCdkaXNwb3NpdGlvbl9jb3VudCc6bGVuKHIuZ2V0KCdkaXNwb3NpdGlvbnMnLFtdKSksJ2Rpc3Bvc2l0aW9ucyc6W3trOmQuZ2V0KGspIGZvciBrIGluICgnaWQnLCdhcHByb3ZhbF9pZCcsJ3N1Ym1pc3Npb25fc3RhdGUnLCdkaXNwb3NpdGlvbicsJ2FwcHJvdmVkX2F0JywnZGVjaWRlZF9hdCcpfSBmb3IgZCBpbiByLmdldCgnZGlzcG9zaXRpb25zJyxbXSldfSkpXG5leGNlcHQgRXhjZXB0aW9uIGFzIGU6cHJpbnQoanNvbi5kdW1wcyh7J21lbW9faWQnOmlkLCdzdGF0dXMnOid1bnZlcmlmaWVkJywnZXJyb3JfY2xhc3MnOnR5cGUoZSkuX19uYW1lX199KSk7cmFpc2UgU3lzdGVtRXhpdCgxKVxuIgogICAgfSwKICAgICJpbnN0YWxsZWQtemxpYi1wcm9vZi5qc29uIjogewogICAgICAic2hhMjU2IjogIjZhZjYyZjg2ZjUwYWMxODVmYWI5NDE2M2MyNGFjODE1MGVhZDY5ZTYyZjhkODI1YjBkY2UxZTc1YTVhNTdiNjgiLAogICAgICAiY29udGVudCI6ICJ7XG4gIFwiaW1hZ2VcIjogXCJmb3JnZWpvLmNvdWxvbWIuc29jaWFsL2NvdWxvbWIvaW5mb3JtZWQtZGVjaXNpb25Ac2hhMjU2OmY1NTg4ZjQ5NmIxZjI2N2QwN2NkYzlmNTIwOGU2ZjJmMjQxZjc0YjczOWMyMzkyNGIwMzcwMmRlYzEzYTVjMjNcIixcbiAgXCJ6bGliX3BhY2thZ2VcIjogXCIxLjMuMi1yMVwiLFxuICBcImZpbGVzXCI6IHtcbiAgICBcImxpYi9hcGsvZGIvaW5zdGFsbGVkXCI6IFtcbiAgICAgIFwiZmlsZVwiLFxuICAgICAgNDIwLFxuICAgICAgMCxcbiAgICAgIDAsXG4gICAgICBcImY1OTA3NjIxOTY3MTBlMjVjMGU5ZjcyZGEwNjQyY2FkOGE1ZGNiYTBhYTllNThhMGNiYWE2NTY4NGQxNWMwMDFcIlxuICAgIF0sXG4gICAgXCJ1c3IvbGliL2xpYnouc28uMS4zLjJcIjogW1xuICAgICAgXCJmaWxlXCIsXG4gICAgICA0OTMsXG4gICAgICAwLFxuICAgICAgMCxcbiAgICAgIFwiZWNjOGI5ZGZjNDVlYjdmYTI5YjQxMGZmYWNhNjI1Nzg3Mzg5MGRlNTNiODBmZTkxNzM1NzM3YjQ5MDY3YzVmMlwiXG4gICAgXVxuICB9XG59XG4iCiAgICB9CiAgfQp9Cg==" - } - ] - }, - "publish": { - "version": 1, - "audience": { - "subjects": [], - "groups": [ - "net-kingdom-admins" - ] - } - }, - "review": { - "version": 1, - "options": { - "submitter": "state-hub", - "memo_class": "decision-record", - "review_group": "net-kingdom-admins", - "approval_intent": { - "action": "administer", - "principal": "informed-decision", - "purpose": "Approve only exact zlib85091 image-only T05 release and fresh protected native backup/restore gates; no prior T04 approval reuse or unrelated grant.", - "target": { - "id": "INFD-WP-0009-T05/zlib85091-r1", - "type": "runtime-change", - "system": "railiance01" - } - } - } - } - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47 deleted file mode 100644 index 2321c6c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47 +++ /dev/null @@ -1,7 +0,0 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization -resources: - - service.candidate.json - - registries.candidate.json -commonAnnotations: - informed-decision.coulomb.social/admission: approved; INFD-WP-0007 cutover diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453 deleted file mode 100644 index 02232fc..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453 +++ /dev/null @@ -1,41 +0,0 @@ -{ - "kind": "informed-decision.supervised-release-proposal", - "version": 1, - "proposal_id": "INFD-WP-0009-T05/zlib85091-r1", - "trial_id": "infd-zlib-20261011-r1", - "state": "prepared-unapproved", - "memo_id": "infd-20261011-zlib-remediation-r1", - "memo_version": 1, - "task": "INFD-WP-0009-T05", - "candidate_image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "artifact_source_revision": "9b4bb25d325698ae543e7e8a8a3c3592db1f58fc", - "prior_image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "prior_application_revision": "c96b0c6d881e855d1d158003c6de90ef390262e2", - "source_revision": null, - "published_artifact_verified": true, - "native_actions_executed": false, - "approval_id": null, - "authority_mode": "ADMINISTER", - "target": "realm:kubernetes/railiance01/informed-decision", - "cost_estimate_eur": 0, - "risk": "Private same-schema backup copies and brief Recreate availability gap. Failed or ambiguous phases retained; no replay. Rollback changes Deployment image/source only, retains latest database.", - "execute_by": "2026-10-12T23:59:59Z", - "release_must_start_by": "2026-10-12T23:29:59Z", - "max_snapshot_bytes": 536870912, - "maximum_new_snapshot_payload_bytes_each_site": 1073741824, - "local_extra_disposable_clone_max_bytes": 536870912, - "private_metadata_and_sqlite_sidecars_additional_headroom_bytes": 16777216, - "unbound_inputs": [ - "exact committed owner rendering revision retained in root supervised receipt", - "Product and Platform final acknowledgments", - "confirmed founder exact new disposition" - ], - "published_verification_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e", - "native_readonly_preflight_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9", - "executor_plan_sha256": "fd38385e3687ec6f9886d5025b394463c252b8198b0426c56f95740139eb7e9b", - "execution_bundle_sha256": "2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b", - "rollback_preflight_sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5", - "post_ready_native_provider_probe": "canonical lifecycle.outcomes SOURCE1 through existing admitted StateHub dispatcher; credentials stay inside Pod; bounded metadata correlated to verified candidate image/Pod/source", - "fresh_scan_contract_sha256": "b13806a7e1464454e3817e1e62b56b0fa07fe2fddbd94d7fdc530e2c3dd9e3d2", - "preparation_failure_history_sha256": "51b9983d2a806240f562dcb951955fc8360c181e6d08a8dfdcba95361c6dd596" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108 deleted file mode 100644 index 31cbd1c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108 +++ /dev/null @@ -1,170 +0,0 @@ -"""Supervised receipt bookkeeping, not an authenticated approval provider.""" -import datetime -import fcntl -import hashlib -import json -import os -import re -from pathlib import Path -import stat -import uuid - -FOUNDER = {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'} -MEMO_ID = 'infd-20261011-zlib-remediation-r1' -PROPOSAL = 'INFD-WP-0009-T05/zlib85091-r1' -PHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline', - 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify') - - -def require(value, reason): - if not value: - raise ValueError(reason) - - -def sha(path): - digest = hashlib.sha256() - with Path(path).open('rb') as stream: - for block in iter(lambda: stream.read(1024**2), b''): - digest.update(block) - return digest.hexdigest() - - -def private(path, directory=False): - info = Path(path).lstat() - require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode)) - and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600) - and (directory or info.st_nlink == 1), 'private owned custody required') - - -def authority(receipt, outcome, memo_sha, source_revision, phase, now=None): - """Root must freshly verify native outcome at fixed ORIGIN outside this helper. - - Receipt files/flags are not signed grants. This function checks the exact - supervised receipt relation; existing harness/founder authority remains the - actual permission boundary. It never authenticates a caller as human. - """ - now = now or datetime.datetime.now(datetime.timezone.utc) - require(phase in PHASES, 'fixed phase required') - require(receipt.get('proposal_id') == PROPOSAL and receipt.get('memo_id') == MEMO_ID - and receipt.get('memo_version') == 1 and receipt.get('memo_sha256') == memo_sha - and receipt.get('source_revision') == source_revision - and receipt.get('activation') == 'APPROVED' - and receipt.get('authority_mode') == 'ADMINISTER' - and receipt.get('target') == 'realm:kubernetes/railiance01/informed-decision' - and receipt.get('verification_method') == 'root-fixed-origin-native-readback' - and receipt.get('runtime_authenticated_grant') is False - and receipt.get('outcome_query') == '/lifecycle/memos/' + MEMO_ID + '/outcomes?version=1', - 'exact externally verified supervised receipt required') - verified = datetime.datetime.fromisoformat(receipt['verified_at'].replace('Z', '+00:00')) - require(verified.tzinfo is not None and 0 <= (now - verified).total_seconds() <= 120, - 'fresh root native readback required before every phase') - deadline = datetime.datetime.fromisoformat('2026-10-12T23:59:59+00:00') - require(now < deadline, 'approved execution window expired') - if phase == 'release': - require((deadline-now).total_seconds() >= 1800, 'reserve thirty minutes for verification and recovery') - require(outcome.get('memo_id') == MEMO_ID and outcome.get('review', {}).get('stale') is False - and outcome['review'].get('intake_state') == 'ready', 'native review is not ready/current') - review_id = outcome['review']['memo_id'] - require(review_id == receipt.get('review_memo_id'), 'native review identity changed') - accepted = [x for x in outcome.get('dispositions', []) - if x.get('submission_state') == 'confirmed' - and x.get('disposition', {}).get('verb') == 'accept' - and x['disposition'].get('memo_version') == 1] - require(len(accepted) == 1, 'one confirmed founder acceptance required') - accepted = accepted[0]['disposition'] - require(accepted.get('actor') == FOUNDER and accepted.get('memo_id') == review_id - and accepted.get('approval_id') == receipt.get('approval_id') - and accepted.get('id') == receipt.get('disposition_id') - and accepted.get('at') == receipt.get('accepted_at'), 'exact founder disposition mismatch') - require(re.fullmatch(r'[a-f0-9-]{36}', accepted['approval_id']) is not None - and re.fullmatch(r'disp-[a-f0-9-]{36}', accepted['id']) is not None - and re.fullmatch(r'review-[a-f0-9-]{36}', review_id) is not None, - 'native disposition identifiers are malformed') - accepted_at = datetime.datetime.fromisoformat(accepted['at'].replace('Z', '+00:00')) - require(accepted_at.tzinfo is not None and accepted_at <= verified, 'acceptance time not bounded') - return {key: receipt[key] for key in ('approval_id', 'disposition_id', 'accepted_at', 'verified_at')} - - -def fresh_scan(receipt, report, now=None): - """Root supervised scan bookkeeping; actual scanner proof stays retained.""" - now=now or datetime.datetime.now(datetime.timezone.utc) - require(receipt.get('image')=='forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23' - and receipt.get('artifact_source_revision')=='9b4bb25d325698ae543e7e8a8a3c3592db1f58fc' - and receipt.get('scanner')=='aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969' - and receipt.get('no_suppression') is True and receipt.get('full_allseverity_scan') is True - and receipt.get('exit_code')==0,'exact full immutable artifact scan required') - scanned=datetime.datetime.fromisoformat(receipt['completed_at'].replace('Z','+00:00')) - metadata=receipt['db_metadata'];updated=datetime.datetime.fromisoformat(metadata['UpdatedAt'].replace('Z','+00:00'));next_update=datetime.datetime.fromisoformat(metadata['NextUpdate'].replace('Z','+00:00')) - require(all(t.tzinfo is not None for t in (scanned,updated,next_update)) - and updated<=scanned<=now 10**12 - for number in value[group].values()): - raise ValueError('count-only receipt values refused') - return value diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/60507a1000a216623a3d53b21e64c6f4569b1aa064781d3ac672a9fa130e813c b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/60507a1000a216623a3d53b21e64c6f4569b1aa064781d3ac672a9fa130e813c deleted file mode 100644 index 605f4da..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/60507a1000a216623a3d53b21e64c6f4569b1aa064781d3ac672a9fa130e813c +++ /dev/null @@ -1,520 +0,0 @@ -[ - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-review.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-review.json", - "original_sha256": null, - "staged_sha256": "e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/mapping.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/mapping.json", - "original_sha256": null, - "staged_sha256": "dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/original-manifest.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/original-manifest.json", - "original_sha256": null, - "staged_sha256": "81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "original_sha256": null, - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "original_sha256": null, - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "original_sha256": null, - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "original_sha256": null, - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "original_sha256": null, - "staged_sha256": "09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "original_sha256": null, - "staged_sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "original_sha256": null, - "staged_sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "original_sha256": null, - "staged_sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "original_sha256": null, - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "original_sha256": null, - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "original_sha256": null, - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "original_sha256": null, - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "original_sha256": null, - "staged_sha256": "8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "original_sha256": null, - "staged_sha256": "196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "original_sha256": null, - "staged_sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "original_sha256": null, - "staged_sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "original_sha256": null, - "staged_sha256": "4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "original_sha256": null, - "staged_sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_initial_dispatch.py", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_initial_dispatch.py", - "original_sha256": null, - "staged_sha256": "609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a" - }, - { - "repo": "railiance-platform", - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/published-evidence-manifest.json", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/published-evidence-manifest.json", - "original_sha256": null, - "staged_sha256": "6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9" - }, - { - "repo": "railiance-platform", - "path": "intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "staged_path": "/tmp/infd-zlib-release-platform-review-stage/railiance-platform/intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "original_sha256": null, - "staged_sha256": "a187eae16bd6eb8b05fdbffc860a7e87b62e726379fde92915de15d6d8bd4290" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a deleted file mode 100644 index 4502f07..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a +++ /dev/null @@ -1,20 +0,0 @@ -"""Synthetic function and transport probes only. Never invoke main/native/Docker.""" -import ast,datetime,importlib.util,json,sys,subprocess -from pathlib import Path -from unittest.mock import patch -root=Path(sys.argv[1]);sys.path.insert(0,str(root));import actuator,native_program -from authority_journal import fresh_scan -actuator.CASE=root.parent -results={} -try:actuator.remote("drill-backup","e"*40,{},operation_intent=None) -except TypeError:results["operation_keyword_rejected_before_transport"]=True -captured=[] -def fake(argv,body=None,**kwargs): - captured.append(json.loads(ast.literal_eval(body)));return subprocess.CompletedProcess(argv,0,'{"all_checks_pass":true}','') -with patch.object(native_program,"PROGRAM","__PAYLOAD__"),patch.object(actuator,"run",side_effect=fake):actuator.remote("drill-copy","e"*40,{}) -results["copy_payload_missing_snapshot_identity"]="snapshot_identity" not in captured[0] -results["payload_missing_operation_intent"]="operation_intent" not in captured[0] -receipt={"image":actuator.IMAGE,"artifact_source_revision":"9b4bb25d325698ae543e7e8a8a3c3592db1f58fc","scanner":"aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969","no_suppression":True,"full_allseverity_scan":True,"exit_code":0,"completed_at":"2026-10-11T01:59:00Z","db_metadata":{"UpdatedAt":"2026-10-10T19:00:00Z","NextUpdate":"2026-10-11T19:00:00Z","Version":2},"db_sha256":"a"*64,"full_report_sha256":"b"*64} -results["incomplete_unbound_zero_report_accepted"]=fresh_scan(receipt,{"Results":[{}]},datetime.datetime(2026,10,11,2,tzinfo=datetime.timezone.utc))["allseverity_findings"]==0 -print(json.dumps(results,sort_keys=True)) -if not all(results.values()):raise SystemExit(1) diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889 deleted file mode 100644 index e9c2dee..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889 +++ /dev/null @@ -1,17 +0,0 @@ -{ - "revision": "9b4bb25d325698ae543e7e8a8a3c3592db1f58fc", - "published_digest": "sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "verified_registry_config_digest": "sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b", - "executed_loaded_daemon_id": "sha256:f2e07e3dd839b55afd5d517243a765a26965325e4e6750f508771ad556bd6d01", - "image_source": "actual anonymous verified descriptor/layer archive loaded locally", - "candidate_image_executed": true, - "network": "none", - "read_only_root": true, - "memory": "128MiB", - "cpus": 1, - "checker_source": "exact gitshow pinnedcommit", - "native_provider_test": false, - "native_workload_changed": false, - "database_or_admin_or_provider_credentials": false, - "verified_utc": "2026-10-11T09:39:37.502275+00:00" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68 deleted file mode 100644 index 80f324a..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68 +++ /dev/null @@ -1,20 +0,0 @@ -{ - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "zlib_package": "1.3.2-r1", - "files": { - "lib/apk/db/installed": [ - "file", - 420, - 0, - 0, - "f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001" - ], - "usr/lib/libz.so.1.3.2": [ - "file", - 493, - 0, - 0, - "ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2" - ] - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9 deleted file mode 100644 index a05b39b..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9 +++ /dev/null @@ -1,205 +0,0 @@ -[ - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/README.md", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/README.md", - "original_sha256": null, - "staged_sha256": "ae9225812b068e85557506662470169322294c67f1b643406188822eaa1e71f5" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/base-image-config.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/base-image-config.json", - "original_sha256": null, - "staged_sha256": "4ad7249c75fd9fc039ceb186772419e1cba5fd9759c404556459c80a2d285d10" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/base-image-manifest.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/base-image-manifest.json", - "original_sha256": null, - "staged_sha256": "978c575d3c7bf0de39dbe4e9567c39cd36f70b7e55bc17357bdb03fb3b96b0fb" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/base-runtime.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/base-runtime.json", - "original_sha256": null, - "staged_sha256": "20aabb9f4846ab4fa6e28e0763d153188da151ee32293366a51b0fdf872a4d24" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/base-sbom.cdx.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/base-sbom.cdx.json", - "original_sha256": null, - "staged_sha256": "dd7d64c9571391acd4d1f86c6026a1b094abab4ee56e5a9f799ec2fd3c7d915a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/base-sbom.cdx.json.log", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/base-sbom.cdx.json.log", - "original_sha256": null, - "staged_sha256": "8ab4473a5f74b36e82a4095e9c8d6e66d80aec64bd1cebd6c9f6734531869fca" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/base-trivy-full.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/base-trivy-full.json", - "original_sha256": null, - "staged_sha256": "3e91a3001adb008cec37d9ae3c08342f5d43c8ba0111eee75c63a6a7918808a9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/base-trivy-full.json.log", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/base-trivy-full.json.log", - "original_sha256": null, - "staged_sha256": "96a48dc39f7d674b4954d5fa98a76645528ec0fe705782fe114e3b4b9d776862" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/ci-publication-observation.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/ci-publication-observation.json", - "original_sha256": null, - "staged_sha256": "ffe6f2508bc9bebbdc85ac63eae5765562efbb8e920cc85b8da30a12ee1e4b17" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/image-config.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/image-config.json", - "original_sha256": null, - "staged_sha256": "253ab359013aeb3457d2abe0852bacd33697f973dd32c1b5738800e5dc633f43" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/image-manifest.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/image-manifest.json", - "original_sha256": null, - "staged_sha256": "9ca2abec6a26d9a9f802dfe75efa8bffdc28485e6bcaf525cc9b075df3700194" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/installed-purpose.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/installed-purpose.json", - "original_sha256": null, - "staged_sha256": "312312c6f0bd2751401b3bc5bcd30ee6af01ed7cb380f85b863d03b0c7e9d1ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/loaded-daemon-image-proof.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/loaded-daemon-image-proof.json", - "original_sha256": null, - "staged_sha256": "2e5c043a101d75e4c569adb6bc2f09020679b28ed2ad836c954f7d27101ce719" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/publication-digest.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/publication-digest.json", - "original_sha256": null, - "staged_sha256": "ca2a1dec38db8c5fbbc00ef4f27aa865edbbd4a7ca9dc21b2ba023664c507f38" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/published-verification.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/read_public_artifact.py", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/read_public_artifact.py", - "original_sha256": null, - "staged_sha256": "eaa31da1ebb2095ea5e1bc7c570311cc4747f0d7f718034dad934351d1920daf" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/registry-proof.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/registry-proof.json", - "original_sha256": null, - "staged_sha256": "3ef52d9e4800fc9bd238072241dc15f13a0260ee3df1e82e6f63e72902749d92" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/run_published_runtime.py", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/run_published_runtime.py", - "original_sha256": null, - "staged_sha256": "d9bd33fecd2a98c2381b4ba2dce12b5c3d9a69faa5e350291924fc885f5e775f" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/run_published_scan.py", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/run_published_scan.py", - "original_sha256": null, - "staged_sha256": "2583ab96350412fb3f20b5b05ded0949eb3ee8e5c0e8923a9c2c42d419d36639" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/runtime-comparison.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/runtime-comparison.json", - "original_sha256": null, - "staged_sha256": "993b80b9285e227a0bdfde4927cd2f053bcc4a58dd9bd1b633ac7a4637d6713c" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/sbom.cdx.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/sbom.cdx.json", - "original_sha256": null, - "staged_sha256": "4202263059780eae0448328044c1cf68b59758f329ae6bc981d916610527eae3" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/sbom.cdx.json.log", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/sbom.cdx.json.log", - "original_sha256": null, - "staged_sha256": "51873b37a0846ee1a618ea7591d79809dc0f8624f77a03fae7af953bcbd7282f" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/trivy-full.json", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/trivy-full.json", - "original_sha256": null, - "staged_sha256": "2ff68e765b9c65292611d5a969202a7654637168edefcedac5d2c7634009edae" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-published-verification/trivy-full.json.log", - "staged_path": "/tmp/infd-zlib-published-evidence-stage/informed-decision/docs/evidence/2026-10-11-zlib-published-verification/trivy-full.json.log", - "original_sha256": null, - "staged_sha256": "b3419af860d264449f61986f562491edaead27dfd3638ceabe94b4fe177f4bf1" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529 deleted file mode 100644 index c0b7f2e..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529 +++ /dev/null @@ -1,52 +0,0 @@ -{ - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "policy": { - "origin": "http://flex-auth-informed-decision-sitting.flex-auth.svc.cluster.local:8080", - "package": "informed-decision.compact-sitting", - "version": "v13", - "package_digest": "sha256:31be5985c0c1e6b0c3a039ed7310d1e94daf523a94f6d926e6e8d759d3711873", - "pod_name": "flex-auth-informed-decision-sitting" - }, - "keycape_egress_ips": [ - "92.205.62.239" - ], - "storage_class": "local-path", - "ordinary_approval_ids": [ - "2ce76d7f-01c3-4b63-8476-8d1230679769", - "c2af4bcf-15b4-4305-aac1-acc7e4dcb099", - "dc22666a-d5d7-46bc-9490-ebe9fe09dc38" - ], - "lifecycle_policy": { - "package": "informed-decision.lifecycle", - "version": "v2", - "package_digest": "sha256:da01e045fc108adec6b489e5d51f7e57366793016c5a64776963780a6aae457c", - "origin": "http://flex-auth-informed-decision-lifecycle.flex-auth.svc.cluster.local:8080", - "pod_name": "flex-auth-informed-decision-lifecycle" - }, - "lifecycle_delivery": true, - "intake_attributes": true, - "intake_delivery": { - "hub_origin": "http://state-hub.state-hub.svc.cluster.local:8000" - }, - "workload_api": { - "registries_configmap": "informed-decision-workload-registry", - "peers": [ - { - "namespace": "state-hub", - "app": "state-hub" - } - ], - "api_egress": [ - { - "ip": "10.43.0.1", - "port": 443 - }, - { - "ip": "92.205.62.239", - "port": 6443 - } - ], - "intake": true, - "lifecycle": true - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844 deleted file mode 100644 index e1ed42c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844 +++ /dev/null @@ -1,191 +0,0 @@ -{ - "kind": "informed-decision.memo-intake", - "version": 1, - "idempotency_key": "infd-20261011-zlib-remediation-r1-v1", - "memo_class": "decision-record", - "review_group": "net-kingdom-admins", - "drafted_by": "custodian-codex", - "approval_intent": { - "action": "administer", - "principal": "informed-decision", - "purpose": "Approve only exact zlib85091 image-only T05 release and fresh protected native backup/restore gates; no prior T04 approval reuse or unrelated grant.", - "target": { - "id": "INFD-WP-0009-T05/zlib85091-r1", - "type": "runtime-change", - "system": "railiance01" - } - }, - "memo": { - "id": "infd-20261011-zlib-remediation-r1", - "version": 1, - "question": "Approve the exact zlib85091 fixed image-only release, new protected backup/restore compatibility drill and database-preserving rollback?", - "requested_act": "approve", - "binding_level": "organizational", - "brief": "Published owner CI image f558 has zero full-scan findings across44packages; only zlib1.3.2-r0 to r1 changes. Fresh schema5 private backup/isolated clone drill and cutover snapshot precede one Deployment manual prune=false Recreate sync. Separate founder go required; old T04 acceptance/history unchanged. Rollback image763c/Applicationc96 retains latestDB. Release reserves30minutes before Oct12 deadline.", - "binding": { - "principal": { - "id": "pending-human-session", - "kind": "person", - "display_name": "Founder review" - }, - "target": { - "kind": "environment", - "id": "realm:kubernetes/railiance01/informed-decision", - "label": "Informed Decision zlib85091 image-only remediation on railiance01", - "environment": "production" - }, - "terms": "Approve only INFD-WP-0009-T05/zlib85091-r1 trialinfd-zlib-20261011-r1 and exact attached digests: new private drillbackup/copy, isolatedschema5pre/admin/postintegrityandcounts, exactcreatedclonecleanup, freshcutoverbackup/copy, manualfinalsourceDeployment-onlyimagef558/prune=false. ImmutableCM/PVC/config/credentials/audience/resources sealed. Freshrootnativeauthenticated confirmed founderACCEPTsource1 beforeeveryphase; receiptsarebookkeeping/noautonomousgrant. Rollbackonlyimage763c/Applicationc96 preserveslatestDB, separateverification. AllphasesbeginbeforeOct12 23:59:59Z; releasereserves30minutes; newdecisionbeyond. ExistingT04UNCERTAINEvidenceunaltered, nooldhelperamendment/phase retry, noresidualpromotion ordoctrinechange." - }, - "step_kind": "approve", - "packet": [ - { - "item_id": "decision-packet-md", - "label": "decision-packet.md", - "hash": "sha256:c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "item_id": "proposal-json", - "label": "proposal.json", - "hash": "sha256:29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453" - }, - { - "item_id": "native-backup-restore-protocol-md", - "label": "native-backup-restore-protocol.md", - "hash": "sha256:3c903dd82c13273cb61f590b258b059c32e0fd515d7c176ce4fec3e68a77bc7b" - }, - { - "item_id": "native-preflight-json", - "label": "native-preflight.json", - "hash": "sha256:3b6b67a0b4d51ef791e775b528b64aa5a6f14eff629737e67baee7e2f6f60762" - }, - { - "item_id": "rollback-preflight-json", - "label": "rollback-preflight.json", - "hash": "sha256:4c3c3166205808e81326ccdae1b56afef757b0ceb4e6c96a17bf384317aeee4a" - }, - { - "item_id": "published-verification-json", - "label": "published-verification.json", - "hash": "sha256:dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "item_id": "artifact-comparison-json", - "label": "artifact-comparison.json", - "hash": "sha256:a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "item_id": "installed-zlib-proof-json", - "label": "installed-zlib-proof.json", - "hash": "sha256:6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "item_id": "sealed-live-deployment-json", - "label": "sealed-live-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "sealed-native-dependencies-json", - "label": "sealed-native-dependencies.json", - "hash": "sha256:042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "item_id": "candidate-deployment-json", - "label": "candidate-deployment.json", - "hash": "sha256:487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "item_id": "rollback-deployment-json", - "label": "rollback-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "execution-bundle-json", - "label": "execution-bundle.json", - "hash": "sha256:2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b" - } - ], - "highlights": [ - { - "id": "protected-drill-and-recreate", - "item_id": "decision-packet-md", - "note": "This new T05 decision authorizes protected real-data backup/copy and no-network same-schema restore drill plus brief Recreate image release. Scan0 is actual published44package result, not installed acceptance. Rollback preserves latestDB; priorfailedT04evidence retained. No oldapprovalreuse. Allphase starts expireOct12; release reserves30minutesforrecovery; noautomaticextension.", - "required_ack": true, - "severity": "warning", - "locator": { - "kind": "section", - "heading": "Requested decision" - } - } - ], - "sealed": false, - "approval_id": null, - "approval_binding_digest": null - }, - "packet": [ - { - "item_id": "decision-packet-md", - "media_type": "text/markdown", - "content": "# Requested decision\n\nApprove the exact new T05 zlib-only image release and protected same-schema compatibility drill, after final independent Product and Platform acknowledgments. This packet is prepared and unapproved; fresh rollback server dry-run passed with exactly the baseline image and empty diff. Root retains exact final owner source revision and independent acknowledgments before asking for native founder disposition. The prior T04 approval is excluded.\n\nThe admitted owner CI published immutable image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23 from source9b4bb25d325698ae543e7e8a8a3c3592db1f58fc. Its actual full scan reports zero findings across44packages. The same scan DB finds oneMEDIUM on currentimage763c. Signed official Alpine zlib1.3.2-r1 is the sole package change; actual published filesystem proof shows onlylibz andAPKinstalleddatabase changes, zero313directorymetadata changes, retainedninebaselayers and unchanged36applicationfiles/purposepolicy/config/credentials/audience/resources. Registry-to-daemon config serialization differs under closed verified normalization, without runtime change.\n\nThe exact ordered protected drill and release are specified in native-backup-restore-protocol.md and hash-bound execution bundle. New backup paths, isolated clone and journal are distinct from old evidence. Real private data will enter only approved private custody and the isolated no-network clone; content never enters public evidence. Failed/uncertain phase states remain durable and cannot be retried. Prior T04 failed copy/verify are preserved.\n\nAfter schema5/integrity/exactclonecounts pass and clone cleanup succeeds, fresh cutover backup/copy precedes the exact manual image-only Deployment sync. Current baseline is healthygeneration25/image763c/Applicationc96. ImmutableCM6c/PVC/Podconfiguration/PDP origins remain sealed. FreshnodeCPU3460of4000m supports unchangedRecreate, with another check before effect. Brief availability interruption expected.\n\nRollback restores Deploymentimage763c andApplicationc96 while retaining latestschema5database; no old snapshot restoration. Separate installed/native health/custody/PDP-gated readback and actual native image rescan precede T05 closure. Allphase starts expireOct12 23:59:59Z; release reserves30minutesforverificationandrecovery. Afterexpiry a newfounderdecision is required. EUR0newrecurringinfrastructure, boundedprivate storage andunpricedattendedtime. No newdoctrine/autopilot/credentialgrant is authorized.\n" - }, - { - "item_id": "proposal-json", - "media_type": "application/json", - "content": "{\n \"kind\": \"informed-decision.supervised-release-proposal\",\n \"version\": 1,\n \"proposal_id\": \"INFD-WP-0009-T05/zlib85091-r1\",\n \"trial_id\": \"infd-zlib-20261011-r1\",\n \"state\": \"prepared-unapproved\",\n \"memo_id\": \"infd-20261011-zlib-remediation-r1\",\n \"memo_version\": 1,\n \"task\": \"INFD-WP-0009-T05\",\n \"candidate_image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"artifact_source_revision\": \"9b4bb25d325698ae543e7e8a8a3c3592db1f58fc\",\n \"prior_image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"prior_application_revision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\",\n \"source_revision\": null,\n \"published_artifact_verified\": true,\n \"native_actions_executed\": false,\n \"approval_id\": null,\n \"authority_mode\": \"ADMINISTER\",\n \"target\": \"realm:kubernetes/railiance01/informed-decision\",\n \"cost_estimate_eur\": 0,\n \"risk\": \"Private same-schema backup copies and brief Recreate availability gap. Failed or ambiguous phases retained; no replay. Rollback changes Deployment image/source only, retains latest database.\",\n \"execute_by\": \"2026-10-12T23:59:59Z\",\n \"release_must_start_by\": \"2026-10-12T23:29:59Z\",\n \"max_snapshot_bytes\": 536870912,\n \"maximum_new_snapshot_payload_bytes_each_site\": 1073741824,\n \"local_extra_disposable_clone_max_bytes\": 536870912,\n \"private_metadata_and_sqlite_sidecars_additional_headroom_bytes\": 16777216,\n \"unbound_inputs\": [\n \"exact committed owner rendering revision retained in root supervised receipt\",\n \"Product and Platform final acknowledgments\",\n \"confirmed founder exact new disposition\"\n ],\n \"published_verification_sha256\": \"dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e\",\n \"native_readonly_preflight_sha256\": \"56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9\",\n \"executor_plan_sha256\": \"fd38385e3687ec6f9886d5025b394463c252b8198b0426c56f95740139eb7e9b\",\n \"execution_bundle_sha256\": \"2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b\",\n \"rollback_preflight_sha256\": \"0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5\",\n \"post_ready_native_provider_probe\": \"canonical lifecycle.outcomes SOURCE1 through existing admitted StateHub dispatcher; credentials stay inside Pod; bounded metadata correlated to verified candidate image/Pod/source\",\n \"fresh_scan_contract_sha256\": \"b13806a7e1464454e3817e1e62b56b0fa07fe2fddbd94d7fdc530e2c3dd9e3d2\",\n \"preparation_failure_history_sha256\": \"51b9983d2a806240f562dcb951955fc8360c181e6d08a8dfdcba95361c6dd596\"\n}\n" - }, - { - "item_id": "native-backup-restore-protocol-md", - "media_type": "text/markdown", - "content": "# T05 zlib85091 release \u2014 preliminary, unapproved\n\nThis is a distinct proposal/trial for existing INFD-WP-0009-T05. The actual T04 source2 acceptance does not authorize it. Previous R1 copy and R2 verify UNCERTAIN journal states remain untouched. INFD-IN-0010 remains an unpromoted general verifier residual; this new task-specific verifier is a prerequisite of active T05, not an amendment of the old approved helper.\n\nExact intended image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23 was built by admitted owner CI11534/run4 from 9b4bb25d325698ae543e7e8a8a3c3592db1f58fc. Publication is not installed acceptance. Final packet requires independent published-image scan/runtime/filesystem/source proof and fresh read-only native projection/one-object dry-run evidence. Until all final inputs and independent owner acknowledgments are frozen, execution-plan state remains preliminary and the actuator refuses before any authority, journal or native action.\n\nAfter a NEW exact confirmed founder acceptance, root freshly authenticates fixed-origin native source-version1 outcomes before each phase. Receipt files are supervised bookkeeping, not signed grants or autonomy. Wrong task, source, memo/version, stale/pending/agent disposition, old T04 acceptance or changed inputs refuse. Root retains exact approved source revision/digests and action results in supervision. Approval does not promote any agent.\n\nFixed phase order: drill-backup \u2192 drill-copy \u2192 offline-verify \u2192 cleanup-offline \u2192 cutover-backup \u2192 cutover-copy \u2192 release \u2192 verify. New private native /data/private/zlib85091-r1, root .private-backups/20261011-infd-zlib-r1 and local volume infd-zlib-r1-restore-20261011 are distinct from all T04 custody. New private journal intent is fsynced RUNNING before dispatch; no attempted phase replay. Fixed output schema contains only counts, hashes, object identity and boolean results; raw errors and private content are withheld. Existing native snapshots/database/sidecars are never removed or restored over current data.\n\nFresh backup inspection uses copied explicit-close SQLite helpers: live and offline clone readers are mode=ro/query_only and WAL-aware, before any Store/admin open. Immutable reader is used only for freshly closed hash-bound private snapshot with absent/zero WAL frames and stable identity/metadata/digest before/after read/copy. Main and sidecars remain retained. Schema must equal5, integrity ok, and exact snapshot/isolated clone counts match before/admin/after. The detached local clone has network none, readonly root, no service/runtime config/credential mounts; only narrowly scoped UID0 CAP_CHOWN initializer precedes UID10001 reads/admin. Original private copy is hash-stable. Cleanup removes only the disposable volume whose nonce/labels/creation metadata match this trial, before cutover.\n\nEach snapshot is bounded512MiB. At most two new snapshots occupy each native/offcluster custody (1GiB payload each); the disposable offcluster clone adds512MiB while present, plus16MiB sidecar/metadata reserve. New native drill phase checks free space for both; local custody checks two snapshot bounds plus reserve. Existing T04 retention consumes additional pre-existing space. EUR0 recurring infrastructure estimate; human/compute time unpriced.\n\nCutover takes a fresh native backup and exact offcluster copy. Release then requires latest schema5 readonly proof, sealed Pod/Deployment/config/PVC custody and same baseline image763c/current Application sourcec96, a healthy single target92.205.62.239 node and fresh requested CPU \u2264 allocatable capacity. Recreate and resources remain unchanged. Server dry-run/client diff must show only the image. Application UID/resourceVersion/source CAS dispatches manual exact final revision, initiatedBy custodian-codex, explicit prune=false and only apps/Deployment informed-decision in its namespace. No whole-Application sync, namespace/config/credential/resource change or pruning.\n\nVerification reads imageID/readiness/owner chain/exact configured Pod fields, schema5/integrity/metadata counts, installed policy hash and synthetic purpose refusals, health200, installed fixed zlib and exact image proof. After exact candidate Ready and verified rollout, root executes attached post-ready-native-outcomes-command.py inside the existing admitted StateHub Pod. It calls canonical lifecycle.outcomes for infd-20261011-zlib-remediation-r1 SOURCEversion1 at dispatcher fixed ORIGIN with NoRedirect/proxy disabled; existing internal token_file stays inside that Pod and no token/body/private content is exported. Root retains bounded HTTP-success metadata/observationtime and correlates verified candidate PodUID/image/source, then rechecks those identities. This actual provider/PDP-gated preservation proof is separate from installed synthetic2allow8refuse; both are required before task closure. It does not assert provider advisory emissions or new doctrine. Argo3.5.3 omits false prune in operation status: missing or exactfalse is accepted only together with this journal's COMPLETE explicitfalse request, exact source/resources/initiation, operation start after our durable attempt, bounded finish and one Synced Deployment result. True/null/string/prior/foreign/extra-resource results refuse.\n\nRollback \u2192 rollback-verify is separate and permitted only after a recorded release attempt. It switches only Application source back to c96 and Deployment image763c; it never restores an old snapshot over the latest database. Recovery dispatch works even if candidate Pod is absent/unready and never depends on candidate DB inspection. Recovery verification separately requires Ready1/old image/current schema5/data-custody and exact recorded scoped completion. All phases must begin before2026-10-12T23:59:59Z; release must begin at least30minutes earlier. Beyond expiry stop and obtain a new founder decision; no automatic extension or emergency grant. A failed/uncertain attempt requires informed recovery rather than retry.\n\nT05 remains progress until actual installed acceptance and native rescan prove the finding absent. No Risk Engine grade is assigned. Only then may owner close T05/risk and consider finishing WP9, with existing residuals live first.\n\nActual release and installed verification also require fixed private root scan receipt and retained fullreport at /tmp/security-infd-zlib-fresh-scan-{receipt,full}.json, supervised metadata binding to exact immutablef558/artifactsource9b4/scannerdigest62b1. Before journal start, fullreporthash must match, no suppression/allseverity flags true, exit0, nonempty valid fullresults contain zero findings, scan completed within4hours and now precedes DB NextUpdate. The published DB expires2026-10-11T18:59:50Z; it is not valid until release deadline. Root refreshes DB and rescans the same immutable image as routine read-only verification when needed, retaining all raw publicscan artifacts. Any new finding refuses release/closure and is retained for informed reassessment/new decision as needed, never filtered or ignored. Existing risk stays open until actual installed proof/native rescan.\n\nRefinement preserves reviewed81/source bytes and exactdraft29 complete archives. Initial67 manifest hash is known but exact original67manifest/source snapshot was not retained before81; preparation-failure-history.json discloses this. No priornativeeffect occurred. New full CLI synthetic tests exercise main sourcehash/receipt/journal/dispatch wiring for drill-copy,release,verify and refuse source drift before newjournal/native boundary. Remote payload carries exact frozen snapshot identity and completed release intent. Full scan contract additionally requires SchemaVersion2/container_image, published configImageID622 (not daemonserializationf2), exact11DiffIDs/rootfs/source/contextlabels, exactly38OS+6Python packages with canonicalPURL/version inventory, fixedzlibr1 and no omissions/duplicates/truncated/emptycoverage. Archive scan RepoDigests absent is legitimate; not fabricated.\n" - }, - { - "item_id": "native-preflight-json", - "media_type": "application/json", - "content": "{\n \"all_checks_pass\": true,\n \"observed_at\": \"2026-10-11T09:40:15.540874+00:00\",\n \"native_changed\": false,\n \"protected_database_read\": false,\n \"secret_read\": false,\n \"deployment_generation\": 25,\n \"ready_replicas\": 1,\n \"argo\": {\n \"uid\": \"499868f2-eabc-4df4-ae31-b89ce58d750d\",\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n },\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"automated\": false,\n \"health\": {\n \"lastTransitionTime\": \"2026-10-11T07:00:42Z\",\n \"status\": \"Healthy\"\n },\n \"sync\": {\n \"comparedTo\": {\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n }\n },\n \"revision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\",\n \"status\": \"OutOfSync\"\n }\n },\n \"server_dry_run_passed\": true,\n \"admission_only_image_delta\": true,\n \"client_diff_exit\": 1,\n \"client_diff_sha256\": \"ccff7c1ecfd9eb59fc0ce566c1013a2af5df514af048c68474a272997024ef7b\",\n \"allocatable_cpu_m\": 4000,\n \"requested_cpu_m\": 3460,\n \"strategy\": \"Recreate\",\n \"full_receipt_sha256\": \"56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9\",\n \"full_receipt_path\": \"native-preflight-full.json\",\n \"projection_reference_files\": {\n \"sealed_live_deployment\": \"sealed-live-deployment.json\",\n \"sealed_dependencies\": \"sealed-native-dependencies.json\",\n \"candidate_deployment\": \"candidate-deployment.json\"\n }\n}\n" - }, - { - "item_id": "rollback-preflight-json", - "media_type": "application/json", - "content": "{\n \"all_checks_pass\": true,\n \"observed_at\": \"2026-10-11T09:43:42.851086+00:00\",\n \"native_changed\": false,\n \"protected_database_read\": false,\n \"secret_read\": false,\n \"deployment_generation\": 25,\n \"ready_replicas\": 1,\n \"argo\": {\n \"uid\": \"499868f2-eabc-4df4-ae31-b89ce58d750d\",\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n },\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"automated\": false,\n \"health\": {\n \"lastTransitionTime\": \"2026-10-11T07:00:42Z\",\n \"status\": \"Healthy\"\n },\n \"sync\": {\n \"comparedTo\": {\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n }\n },\n \"revision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\",\n \"status\": \"OutOfSync\"\n }\n },\n \"server_dry_run_passed\": true,\n \"admission_only_image_delta\": true,\n \"client_diff_exit\": 0,\n \"client_diff_sha256\": \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n \"allocatable_cpu_m\": 4000,\n \"requested_cpu_m\": 3460,\n \"strategy\": \"Recreate\",\n \"full_receipt_sha256\": \"0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5\",\n \"full_receipt_path\": \"rollback-preflight-full.json\",\n \"projection_reference_files\": {\n \"sealed_live_deployment\": \"sealed-live-deployment.json\",\n \"sealed_dependencies\": \"sealed-native-dependencies.json\",\n \"candidate_deployment\": \"rollback-deployment.json\"\n }\n}\n" - }, - { - "item_id": "published-verification-json", - "media_type": "application/json", - "content": "{\n \"proposal_id\": \"INFD-WP-0009-T05/zlib-85091-r1\",\n \"task_id\": \"INFD-WP-0009-T05\",\n \"risk_record\": \"INFD-RISK-20261011-ZLIB-85091\",\n \"state\": \"actual-owner-CI-published-independent-verification-passed-native-unapproved\",\n \"verified_utc\": \"2026-10-11T09:40:24.469646+00:00\",\n \"source_revision\": \"9b4bb25d325698ae543e7e8a8a3c3592db1f58fc\",\n \"ci_task\": 11534,\n \"ci_run\": 4,\n \"ci_status\": \"success\",\n \"reviewed_source_manifest_sha256\": \"724aa4777eb1c8976d868e5f504473f4b6d58f13d750d87aefa5c8d5ca0747ec\",\n \"all74_source_hashes_match_actual_commit\": true,\n \"published_image_digest\": \"sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"base_image_digest\": \"sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"registry_config_digest\": \"sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b\",\n \"executed_local_daemon_image_id\": \"sha256:f2e07e3dd839b55afd5d517243a765a26965325e4e6750f508771ad556bd6d01\",\n \"serialization_caveat\": \"Daemon load reserializes config; all diffIDs/platform/effective config+labels independently equal to registry; neither config nor daemon ID substitutes for registry image digest\",\n \"actual_registry_descriptors_and_layers_verified\": true,\n \"base9layers_unchanged\": true,\n \"overlay_layers\": 2,\n \"fullscan_baseline\": {\n \"MEDIUM\": 1,\n \"id\": \"CVE-2026-85091\"\n },\n \"fullscan_candidate_allseverity_findings\": 0,\n \"fullscan_no_suppressions\": true,\n \"same_fresh_DB_both_images\": true,\n \"scanner_db_sha256\": \"03cacb40452ceb74afdfca072cb82f79ee884dfac006ef24a4dfdad133b0f917\",\n \"scanner_db_metadata\": {\n \"Version\": 2,\n \"NextUpdate\": \"2026-10-11T18:59:50.889010572Z\",\n \"UpdatedAt\": \"2026-10-10T18:59:50.889010923Z\",\n \"DownloadedAt\": \"2026-10-10T23:32:32.380033131Z\"\n },\n \"scanner_caveat\": \"Alpine3.24 absent EOL list warning; actual38OS+6Python detectors ran. Zero means this verified DB snapshot, not unknown-future assurance or estate risk grade.\",\n \"sbom_count\": 44,\n \"sbom_only_delta\": {\n \"zlib\": [\n \"1.3.2-r0\",\n \"1.3.2-r1\"\n ]\n },\n \"final_changed_files\": [\n \"lib/apk/db/installed\",\n \"usr/lib/libz.so.1.3.2\"\n ],\n \"directory_count\": 313,\n \"directory_metadata_delta\": {},\n \"app_files_identical\": 36,\n \"installed_UID\": 10001,\n \"installed_GID\": 10001,\n \"policy_sha256\": \"9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62\",\n \"policy_mode\": \"0o644\",\n \"policy_UID\": 0,\n \"candidate_image_executed\": true,\n \"public_registry_layer_readback\": true,\n \"native_provider_test\": false,\n \"installed_purpose_variants\": 10,\n \"purpose_forms_allow\": 2,\n \"purpose_forms_refuse\": 8,\n \"runtime_readonly_networknone\": true,\n \"native_workload_or_database_or_snapshot_or_credentials_accessed\": false,\n \"native_rollout\": false,\n \"founder_release_approval\": false,\n \"risk_followthrough\": \"T05 remains active; manual review due2026-10-18, no controller or estate risk grade. Actual native installation/fresh scoped founder decision still required before closure.\",\n \"verification_refinement\": \"First run used registry config as daemon handle and refused before candidate execution125. Corrected by independently proving loaded daemon ID semantics+diffIDs exact, then executed. Not actual image failure.\",\n \"evidence_sha256\": {\n \"artifact-comparison.json\": \"a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4\",\n \"base-image-config.json\": \"4ad7249c75fd9fc039ceb186772419e1cba5fd9759c404556459c80a2d285d10\",\n \"base-image-manifest.json\": \"978c575d3c7bf0de39dbe4e9567c39cd36f70b7e55bc17357bdb03fb3b96b0fb\",\n \"base-runtime.json\": \"20aabb9f4846ab4fa6e28e0763d153188da151ee32293366a51b0fdf872a4d24\",\n \"base-sbom.cdx.json\": \"dd7d64c9571391acd4d1f86c6026a1b094abab4ee56e5a9f799ec2fd3c7d915a\",\n \"base-sbom.cdx.json.log\": \"8ab4473a5f74b36e82a4095e9c8d6e66d80aec64bd1cebd6c9f6734531869fca\",\n \"base-trivy-full.json\": \"3e91a3001adb008cec37d9ae3c08342f5d43c8ba0111eee75c63a6a7918808a9\",\n \"base-trivy-full.json.log\": \"96a48dc39f7d674b4954d5fa98a76645528ec0fe705782fe114e3b4b9d776862\",\n \"candidate-runtime.json\": \"d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124\",\n \"ci-publication-observation.json\": \"ffe6f2508bc9bebbdc85ac63eae5765562efbb8e920cc85b8da30a12ee1e4b17\",\n \"image-config.json\": \"253ab359013aeb3457d2abe0852bacd33697f973dd32c1b5738800e5dc633f43\",\n \"image-manifest.json\": \"9ca2abec6a26d9a9f802dfe75efa8bffdc28485e6bcaf525cc9b075df3700194\",\n \"installed-purpose.json\": \"312312c6f0bd2751401b3bc5bcd30ee6af01ed7cb380f85b863d03b0c7e9d1ad\",\n \"loaded-daemon-image-proof.json\": \"2e5c043a101d75e4c569adb6bc2f09020679b28ed2ad836c954f7d27101ce719\",\n \"publication-digest.json\": \"ca2a1dec38db8c5fbbc00ef4f27aa865edbbd4a7ca9dc21b2ba023664c507f38\",\n \"read_public_artifact.py\": \"eaa31da1ebb2095ea5e1bc7c570311cc4747f0d7f718034dad934351d1920daf\",\n \"registry-proof.json\": \"3ef52d9e4800fc9bd238072241dc15f13a0260ee3df1e82e6f63e72902749d92\",\n \"run_published_runtime.py\": \"d9bd33fecd2a98c2381b4ba2dce12b5c3d9a69faa5e350291924fc885f5e775f\",\n \"run_published_scan.py\": \"2583ab96350412fb3f20b5b05ded0949eb3ee8e5c0e8923a9c2c42d419d36639\",\n \"runtime-comparison.json\": \"993b80b9285e227a0bdfde4927cd2f053bcc4a58dd9bd1b633ac7a4637d6713c\",\n \"runtime-receipt.json\": \"6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889\",\n \"sbom.cdx.json\": \"4202263059780eae0448328044c1cf68b59758f329ae6bc981d916610527eae3\",\n \"sbom.cdx.json.log\": \"51873b37a0846ee1a618ea7591d79809dc0f8624f77a03fae7af953bcbd7282f\",\n \"scan-receipt.json\": \"aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a\",\n \"source-commit-proof.json\": \"b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b\",\n \"trivy-full.json\": \"2ff68e765b9c65292611d5a969202a7654637168edefcedac5d2c7634009edae\",\n \"trivy-full.json.log\": \"b3419af860d264449f61986f562491edaead27dfd3638ceabe94b4fe177f4bf1\"\n }\n}\n" - }, - { - "item_id": "artifact-comparison-json", - "media_type": "application/json", - "content": "{\n \"base_image_config_id\": \"sha256:f34de21379c4f50ea0361ca1d8296c8e72f989499ad288016b81204997ae4779\",\n \"candidate_local_image_config_id\": \"sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b\",\n \"candidate_registry_digest\": null,\n \"base_layers_preserved\": 9,\n \"new_layers\": 2,\n \"effective_runtime_config_identical\": true,\n \"raw_config_bytes_identical\": false,\n \"serialization_note\": \"Legacy base explicitly serialized empty/false/null defaults; candidate Docker-save config omits equivalent known defaults. Exact closed default normalization only; runtime fields unchanged.\",\n \"labels_before\": {\n \"org.opencontainers.image.base.digest\": \"sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173\",\n \"org.opencontainers.image.revision\": \"3e4bcb26f8eeaa8c64345c565477b5ac8ccbb168\",\n \"social.coulomb.release.scope\": \"INFD-WP-0009-T04/purpose-overlay-v1\",\n \"social.coulomb.source.policy.sha256\": \"9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62\"\n },\n \"labels_after\": {\n \"org.opencontainers.image.base.digest\": \"sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"org.opencontainers.image.revision\": \"9b4bb25d325698ae543e7e8a8a3c3592db1f58fc\",\n \"social.coulomb.release.scope\": \"INFD-WP-0009-T05/zlib-85091-r1\",\n \"social.coulomb.source.context.sha256\": \"c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d\",\n \"social.coulomb.source.policy.sha256\": \"9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62\"\n },\n \"final_changed_files\": {\n \"lib/apk/db/installed\": {\n \"before\": [\n \"file\",\n 420,\n 0,\n 0,\n \"6383ea3806247e8dbf9ebee8cb30eb8eca63a8ea59662a793127d6a61b988d70\"\n ],\n \"after\": [\n \"file\",\n 420,\n 0,\n 0,\n \"f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001\"\n ]\n },\n \"usr/lib/libz.so.1.3.2\": {\n \"before\": [\n \"file\",\n 493,\n 0,\n 0,\n \"a1e2c03dcdedba98a06a00cadfb67a0e0b1cd93986860ec0c5ce66e789c44e57\"\n ],\n \"after\": [\n \"file\",\n 493,\n 0,\n 0,\n \"ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2\"\n ]\n }\n },\n \"directory_metadata_compared\": true,\n \"directory_count_base\": 313,\n \"directory_count_candidate\": 313,\n \"directory_metadata_delta\": {},\n \"sbom_package_count_base\": 44,\n \"sbom_package_count_candidate\": 44,\n \"sbom_only_delta\": {\n \"zlib\": [\n \"1.3.2-r0\",\n \"1.3.2-r1\"\n ]\n },\n \"full_scan_baseline_findings\": 1,\n \"full_scan_candidate_allseverity_findings\": 0,\n \"db_snapshot_same\": true,\n \"native_workload_changed\": false\n}\n" - }, - { - "item_id": "installed-zlib-proof-json", - "media_type": "application/json", - "content": "{\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"zlib_package\": \"1.3.2-r1\",\n \"files\": {\n \"lib/apk/db/installed\": [\n \"file\",\n 420,\n 0,\n 0,\n \"f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001\"\n ],\n \"usr/lib/libz.so.1.3.2\": [\n \"file\",\n 493,\n 0,\n 0,\n \"ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2\"\n ]\n }\n}\n" - }, - { - "item_id": "sealed-live-deployment-json", - "media_type": "application/json", - "content": "{\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n}\n" - }, - { - "item_id": "sealed-native-dependencies-json", - "media_type": "application/json", - "content": "{\n \"configmap\": {\n \"apiVersion\": \"v1\",\n \"immutable\": true,\n \"kind\": \"ConfigMap\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/ConfigMap:informed-decision/informed-decision-runtime-6c221878d051\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision-runtime-6c221878d051\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"90113d2d-54d9-4e82-b1d3-90b49ebed2b5\"\n },\n \"canonical_data_sha256\": \"8ec462d12347548f506eeebf1e3c7cc47648019c8a783804d09bcc79b0f22af6\"\n },\n \"pvc\": {\n \"apiVersion\": \"v1\",\n \"kind\": \"PersistentVolumeClaim\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/PersistentVolumeClaim:informed-decision/informed-decision-data\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\",\n \"pv.kubernetes.io/bind-completed\": \"yes\",\n \"pv.kubernetes.io/bound-by-controller\": \"yes\",\n \"volume.beta.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\",\n \"volume.kubernetes.io/selected-node\": \"239.62.205.92.host.secureserver.net\",\n \"volume.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\"\n },\n \"finalizers\": [\n \"kubernetes.io/pvc-protection\"\n ],\n \"name\": \"informed-decision-data\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n },\n \"spec\": {\n \"accessModes\": [\n \"ReadWriteOnce\"\n ],\n \"resources\": {\n \"requests\": {\n \"storage\": \"1Gi\"\n }\n },\n \"storageClassName\": \"local-path\",\n \"volumeMode\": \"Filesystem\",\n \"volumeName\": \"pvc-d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n }\n },\n \"pvc_phase\": \"Bound\"\n}\n" - }, - { - "item_id": "candidate-deployment-json", - "media_type": "application/json", - "content": "{\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n}\n" - }, - { - "item_id": "rollback-deployment-json", - "media_type": "application/json", - "content": "{\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n}\n" - }, - { - "item_id": "execution-bundle-json", - "media_type": "application/json", - "content": "{\n \"kind\": \"informed-decision.supervised-execution-source-bundle\",\n \"version\": 1,\n \"proposal_id\": \"INFD-WP-0009-T05/zlib85091-r1\",\n \"codec\": \"gzip+base64\",\n \"files\": {\n \"execution/actuator.py\": {\n \"sha256\": \"eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 22015,\n \"content_base64\": \"H4sIAAAAAAACA708aXPbxpLf9SvmKR9A2jwVHzEdZlexlMRvHcklH0lKpUKBwJBEBAIIDkmM4/++fcwMBgcpOZt9qVfPFDDT09PTd/fg8PDwPJZiGd7JQORlKrObMIef6drL5UsRyxuZiayMRbEOcxGUWRivRJ6UmS9FmsnUy7wiTOLR4eHhQbhJk6wQXraCx7nUfwOkdRQu9J+/50msfyf5wTJLNiL1Chwi1OO38Kcekhk4uYykXyRZbh4UXmF+l4s0S3yZV2+35mcRbuTBQbjEZ6Nl5K3yUZLCw/BPOTsQ8F/mwabFu21eyM3pXVj0HP0+EGFcyAy2WiAh5LIE6jj9g4O3Px2/O30n5qLnBFkYRcOF51+XqTMQ6m8/Sbf4V7JcRmEsh0DIcElP/Eh6cZkO1RtnQDjwf45fFgkMtcDpJxpgBnSAw8GfFcwsiSKcYv/WSwK2B4FcAvZ/lGEmezdeVMoB/OnBUfSZAkCcOCkEvZoZfJguH/HhaZYlWU/NUQDztdfDo1MwgnAl8wJIok58BO+Pnj7r9entbVis6WR5CpyAjHtOtnD6wsvhLAH0plp5mWRiESX+NdBfhED7XuRtFoE3UyNH8H9Bbzo5evLo0VF/IBaO059ZdNTYjMo08ArZI1iMCBxlmcX6/Vre8a+e3lWahTc4BdEcwLCMuG47/8GLcqnJFS8T2Ki1nQi5sadXYEL38Nnonfv63cnrix7OGeWFu0kC2UeCG9BCAmRhBl+c/lgf3K9tzIsDoV+XYSDmc5Cj0Qp2FQa9Pr3WoH4+PzltrAuje5Pk+WTSgcEkeTaZtBfrVcPgVDS4GHj3GsFNgfyOIppIbmOQGb/MiyTYakKQwFycn7/XJHPdZRhJ1+3DMeZJdCN7/RHoDBkXB69ArGAYjtaPzn85O73QU53xOtnI8S3oAX89RmSyjQyGgfTDHBQRrPTqw7v35yevj8+6pxRrOWT8Qq8a/hsMriaOhTNSO1KCmI+PJkfPppPpdAhrBsM/gb+H2dQ5eHf+6n9OcWNOGYd3s/F4fONlY9CY4wA4TmajHP5xDl7/fPwj7ssBfFfy92TkJ2WUbBb4OvSisfqzvaH/ZiGaLZ8+/eab5ZMXzxbT5dGz58HkuR/4L5ZPjybfyGfLo+XRk+ny+ZPF869f+Edfvzh6sph8/XxyBFCmX3tP4ZFz8PH8zYefCQd7B0M4AThaOdT7cw5+Pv7V/f6396Tdnk6PxCOh5EzrkTLugZK/AamDQ56fJTFoE9SxSVnMX0yUiCgxqxTzqJoXxikMxdkwUd4V8/cZaiTfS2GOdAEOvueHGrD6V0spmhEXcC+josf/qHWLbFspAtJnsA0cPYoSL8jVYODggKDhKHnny7QQvUrRDcT7bco/+3vUoZN7S8m2EvbryxCglLF344WRt4jAfsISPvAePPRlVnghMJwgi4dEq+kKhRZTzQdRRdGasLDnYQwSDSC06g5Cv2BJpwco/D3HiyLXX0v/OndTL89hJbDYRMOaRDuMbgCagzQ+2QgBWKYR2LiXsMlbwQdAKnstowD8gATPM0NbgjDATdiERQGSPhefGCAaniYGaIu8W3WcroaGj2PwGm6k66XAG7AFl7AIZeAutm6WJIVTx5kR15NKEOG4CH2QzsBdZV5cIEh2Sly0C0kcbd0FIMXmFd7FXpqvk8K87YRvRrHI1SYutoXMa09Qg1lbYV3lKq2xD39Q0zgvTQI39jZS/1aPIxxiNgEKS248F32QVRYWWxf0RFzkneBpJr93N94WJvlRGeCj2C8zwg5OGc6N9gHml103FzkQdIAMalS8YXXatRDqbBxFcFKQaqm9Djhpc5S0GSAFmArCDMywh2cCsFEQcEAHbD8KEdEgXC6tY1CU61pCb40p9fSBy6hjctGnqoEjSYsiYKy0zNIE4DBD8ylFob81aHWB5RFoaa0J6mQr0Kh6iX8ILP2VgpXxVjRrLb2oWLtow8HZzG0eA2EJA5nhjwUTXfroG+o9+HSizueaZslBOZCa6Itv55XoDtBiybsULDvIsZJ4IEMU5EbwlbiTxlL6zUWVV2y1q878dqBdNmRo9Nh6XyqOD2V8Sxujy6qWa7it+B+P541f4rirfheWbZHv4MAHL1op89GyjKKNV/jrXuZcesPlZPji6tOzJ59hCRsn1NGohtmIAo/Ay9Ach/KoG8cBODQVUwc+GpcCDJkiQ3MWLw80JlMyEd+KHeOQcYxX0Gb8Jto5BE57kSbduQfnJho0/oqPjKKrUf5HFBbSCo/0k36biji7iY7yTmgh5VGwv7bbs5keNVwb9GcuHZ6GmAwhRIuX4Qp+Q5DS044lOJNykxbbIY/Ug/o0Y53kaL3Yi7wSjwUt36QwYaPcpW53KJObBGMWNMUY2rH6Hgi2sCjthpigQcB6Fnp7lSFAYYsLeqw2+pX4gRIDoNBKoCHLPGoLEFHhLTEk3nh4jKxBc4HpAggm7iSeSqYTBRgPjipNUmkzMN4bE/RfnP94cfwz+xfeFp018i5aeDuz9l5qBo33AcOajwbK+YFXilAtezezaOdo9wSeGjoetLwGUNdgO2a2j9mjKGasXw5Rt4FHn0bJdgN4jHCo06cY1kX/t9fvtwHDeBnD/nwg7H7wTNChPeFhS/gg9yHGxzvgm/dfjLzJR3QDNimKL4Zbs0IAvUdx5bj5ZpRu67DaoBoGHkCZbeMT19jrB8Cq2/YdmzaDOPYChkqWnbvGzSjDaGHFCs6YyBZWbPVzP8MoZK7lCUakkQehg+O6b49/e3N+fOK6DqqINOsRlkG5SfOeErl+v29mmF3C1B8/HF9YEy1SefFKuqvSy9p06td0bS1iI9WZ52uOEcIoxPBmMiWvaVusk/hrMXSuBmo7ldo7mkz6WueR86ZVwT7V9zsIeexFSqlxQO9yGDQX9fQd2in1Zl7L5HGGpJmeY22VhaDr5nqZEbqgl6xpwHZe2utdXTpMAeeKpm7AScXhMPvvaHBaufJrruV2p1vzgEim7Zb126ZZY3wJi10hkQgH+oucpwSzx6Dm/5SxQVgwl2jbSw7U3Oy9Zedr3mvlq91n+I0T0wH4fj/G4RT4AtzGAFPhGnUrhUVnnQQ11E0Ud3Ww3wH8Ex3A3uVQ/XqkH/X/C/YCUJqeIKOD6bSMFiU6NJCJEt+LTOIKPQ0cxeyAaCY5Z1lp3AD/PHd/uTg/e/Ob+Iv/enVxevxe/3H666s3+vfZ+Q/nb96c/zKwk4Jfie/D2AMDb6jDWRSxSsD2k1NQJMLTGVRyvAaqhlAkSTQGPijGUbJSntlIQX1nwJHXPybfvZKNWy8X5NBhwC8WElhdom2PsRbBdQnwksOYKxFVshk2sgyIAEuMdm5VqtkaXLE3RsbIP5jPvy4XEBRF5MaIYSxaOTnhgKOGh/K4HQMCJ4mh3zFnOBQQnokxbmmsKDRGQ/DN08mL6TCbjhEqHmCVgS0yL86XEpWLlUd7S3vaoT7VRlB10tnM7Ymv354OulBu/QdzZZbZc09OP559ePOmwk2XYxA3XZkZncilB9rtnXrQaw8H+7AKc0zo682pVNzAAnP68fTsvQu8eVIBCEApYQAJ66ExGG0SkJYkDv1eH+g2fTqxVBV4AjeUnJpYpNzWY7XbNYYFmB+btUgCuhjcWmSvebXusLVuxzwt/nr+dyqHZ7bPP6oRdvLcHDcuROGL016DqyIk3FQDaZBxhEIXJ+Ck6KxtG4Qq9hCkWSc/LAD0dcf2FGEfzwUsYhdVuqmghn8736eTkTz20D2BZjN/Qy4ApW9lkLesDoahHQS0lcVtFtaLQ03JG916YdHT3sfGu+tNB/tYwqrVLGGRKGpwneEDspS9HYuqo+waA2dnhqXgRffIcKDRaJ+kGXgd4sDd72mTtqhVFFpGZb623qFSzbex36sNUjzX1/4QFdDI7NQrYmbFrjw3jR9xHQ0rTJRGmO/jnVapCiuSvOyOicohuho0eCiMdQZ8bHL1LwXnEAfN9PdX4kIOUfiE9sTGGOKqiNjsUbxH2wT/s5NzAxI9MGrIeQyVTRZP/j/7gZrSBO7SZJav6vSoHiPV1NgWke6jYd2BUsG+IUnT+9PdA5o+9UzMp0eP9FJcNHd94LyCkp1dudoZlzYsr9VPKMjPVb0BRlDF9rOKFlS1nSFse/WY4GEZTrMj2+Wq+6U1AdDjtVjAWSZlvjtSsOONZqRgsrnA4QYu+98MdheTAzNRhtcgT7LTcMn1oaG94zTYJVYJE05roSvjUZHl0yeM4sToDNyUz5+dqzrPaSid0m1FxGacKsJRrLWIi2HkpRP0YuqMxW6uOj9xQvgJGa/wL4N4fT8RuhiBvZubJCq52BLl7V3t2BCD6dyOqqaiMEN0ogYqvZ2nwAuIbN7MFThBmKdJjsVBwRgJb5FL0DhVMS6sAqUEX+xml0ZrCTzxigJzjS6KNhPCW0DgSom0lks68jkXZtWDTZE7m7YKHB0ASp6v8dClZJ3Sb09Q+MEk2hunLHxQpTtPi1/yiYF43HCqlViCH9IOu1fD/c0ftjmzyb0AFfpz8tRxA38PDJBtvoNmzFYNNlQE6uRD/U6zXpGFaY8kikFhlxAzGo3ESMQycA3DFsa5qgd1noV6vQtNM7uJKFaw5K1meCOyVqVcra9G1Or1FVS7ZG8kFNxQnkV7nnJRnB5cTkAgzig2r8hRf8+F8zckIw4BYHlp+RWNGSfMiKy4mBdpGMlwfeh5ijTPnf59MN+B2pc1kIpsYZ5EJB9N+u1U4Z+u5XZmUYFSNbUE0ZkqPKutw68TI1uMCPx4xax1DFEIC6rWQzE6KF2NbTzB5ZWrhP3AIKf9p3OYh66z+PD6ZDJ+dfzWffXT+S9ngFxYhBDq/8mcSu6T2n2RQKwvxdnpLxQka3LoHMJZIrCWMsZUu3j78dWYyyzjIrmGmGBDGQV5B2o6f6lzBiACkYgpdMDyqyzI25K3IyULChXyyg4PD+vtjIMkH6CrOqA+w87WQm4XuYCVYaeqYeS+vsJlEkHkPXfGSi84L3WJdn7669vTV+9PT3A18MLR5ARh1uMZ/Vm7OQWZhypPJiGjOMhKIwEgf71JAgVmQK1hgEYw18kjfvEYMSLi6ARclT/6y2SP/qrljv6yEk4DlUbiiua82SH4En39+eSgnbcxaZvCy0BOwF2rB+4Uu82R+kiOeLQolxxfWO2BB42QV0e3uOjjeS2SxZOEp989nR49UtM7KKvpqQ6JC546DKUUIgklo1wLMl92tyWqkSrYemmCLP3cxFfUtNdsXPzXXDPJHlxBqkPbDcQCt8xyzQPJ7e6jnk7AK+P/J9zs0bWXB7BYDLrs02GjFehwVqC3ftjpy/PLz4AJSFlVr9D8rmsOexzdvtViql82G0wpOqliZKyzglz/o/Xbjsp4VsYMOtvQvyH/FcviNsmoWxf8COXeIM8a3wmQSUvSzFP+cwNxYUavpkffbDocGBgDSoR2MplNFAwvBa8poT7i4zdvqodeQD0ppHW7YeXSL7HzYggqi/EErG+HeILAjSupvGjSrPgT+wzmrGAGeeaTj6Rs7mPhDAKQeqPUOhcEC5Ft0yRkcFwFgl/UOYlL+WhHKqV8ZeVuibPn3b2GpBbmfPwP7zacPlU5b7tqxQvx84TtOrHQf+6QrTMmiZvR/+856wecYscaf+tQdZzKtaPKg9SUegyUuv+Ulae/wH816IY+utrldT+4VNpvFGvUsLbbqoPOFD30FJOLXKEwfUlixXaA7Le+L0Ej3xFReCXShMSiqi2p1o+qVlfubcP11zM7A31TqMNQv5H+HPsRMF11HryyUvsKLy/YgDv4oJNqRTFDmmyO7p6yghU73Hu8jUQWLrPnaHgPLKF2n221QdU7Vg8rGGxXSFGfo71pdRAdvrRqVYOD545JbGVbSn/rc1UV4C+SO6qvlotNmOccDnxuJAbq29Eps1YhFbFiq81u7zENJ16DlzHYrdIn13lT5gW5PDn6DgX260ToajYZeKxBjlR9k25w/EdF92+KLaK6T2Th9VAzh5KBbrE1pd28cPdJKi3YKaX1qXPxYKndxwN0Xh08QH0jTn9PpvFv5gqTLFxhzUJ0JA0bOVxKVDYSuK32b5OiNalXziTMqtSEfmP1uyjadZPGJUqzhFpzbPpbGzHNIS7GGy4oBxUxz7r64ndFrzMTvTYm2IoCW8SsPyl0Z+s/q6x/xqGgSzFp7iaZy76kS/aW8KKUdTOxjpfyfOlW8ofNwZk9oWvPpgGad6vz4Or2maso2kiE//9lgPhmUDv/aaWDGunPjssb3ckg08X8JalSnV2/3HnwV+1ME7dRm0STSrRQt6rK48CTvm0fNG6fcZb+o5ULMnfv2nmjKoFHzSD5OkxBqZpkXj0JhMWjm12HR95p57mpaZ0JRv1uV4KxtZcHsY291L+QQRqyyExq7s94m0W4KkFzvQTwRC07bfkADYQ95RXvuXWN5Ko9VprpoPPiSRzgRF2XaklcvkvksNTfUwJGV2Mxo6SvyY6Os1WJDZBv6Q3WVannDQ537rpATNftWzNHELm5nprSA3urb99AUA5KIp/zxdSBSfJQYLMXAl64GeKtmoQiBVUGA5Dk/b+la5BfCE11pKrj+7ugOGgbVpdRvhSNJQj5epj7XvzgXQEM9BQVVPoH4eaNq534aIRruIZwrr4BNjf3DotNOjYRmHXrrkZw1QLakqNqCcV/WhjuhV+n/pBv4ahlOrverIvfehN4qzVvNpztam+bDF94w+XVpycTvN9AiDcaq5vtbfLO8wvdH87XkpqLfSU+8t00kGp11xzw073lY7pDqIqZqicMPQL0Tgxp8b4qSj3iCS+Uixt5cd2qmB5iBo7BDQ7q6s2tu304iLQ1ZoJ18l6jOixjXbPn6kA1PC0XUZivJbJsES6BFFVhu7q2hxd0lPMHYFB1RQJYGanIiMrGseBNH1zk0tFUUreMrggz0I9+UXrYfcI3ySvW1WaTn9cb9Ef62nmzv/pzJy81mv938lCFLNk1VzFMHWFuDx+bgGqMTlauinuqN52P6WF9bfX2+jpo5Wb984Cv8bbxRtU0RltvE/0DQLmNLsNW/wbC3cfCnaThxlvJXSeDXksmIzr8gb57BC5MN0uNIFTb5D2rRZiT1eryNoiThtXqIUYOVpntMHfz7Qavkuur6xCz8DtiAcZiIJzmnZKuLob9G8Bq8G7kbdxIu2pAfcTRW+TgKUCgxUg6o5GjS/+NwSD64P6385u4uvlwBkwRYOa91KBeo5+Kh/9R+tnrfzntukR0DwPwjf1/eAcWB1MC1ViP2j50M6++ntzICV86q5CTUK9Ump9wRS8bXLjbHdYLk50zzH3qDV09SIA7Msz9FikMsp3ed6NQVg1W8Y9dBrKo1cF/hiZMxwyvChlpUsUg7oPSVouv6etc01rShax7iAgUG5LThK9/Oj0+aabzEEz3TvFFO8joOg7DDAp7XMeqZ5mGrpZ90z1d/+Y/B9WIgSBf0UVfsZZZ2ePo9XcMrLtrevvKN6zlIHfD3pet5gHkSNSXMvdVkXAoRjtxMkbeRFhdOqFzSWMHUDO0VtzrSTUMEyJmWp4qv4H8s/ppGo6cVyemsRp0U7S+4cb9KsRUJx2x5NKJX+2/bjakpxSAmUuu1SPODre/1tNhdCq0Ky58eDjRjnL0frTxpxECR9T8+6YD0M3ObZSsrn7r6bzzHNqz26zNpEDJdBGt+YO3i0GI2utLjbgFqK1s8eDtAfN5DTNicATqqkGavam8YtGQtEiXyLDu0Vvu2dAHtaKPwaHOms17g2PH2i52uGbgw3cFJiYYhvi36k/Vn9CxT5Yf2V82qgxTuIqTTO7W8+YTOUgRyrQMeYqqeCZwToG8a1fQa02y/WbakFftTuardIv5ppCKI9Wc6nY5PtS75kaf3i5Ja17wq761tQwzcHuMuuGBVErhJKEo4/CPUpqGGoWUU+uP31xjX47CBS9QPdcXqPQRmHddZwBeg+dzI1eSUzv8zTLXUxrVQTV0tHQX1EsnHgnr2TKjxpTv5gI/4lN91uexmD6rvurT0Mp4gHyPuLAKAZr6aKVBrjeYKC6Xy9DHjyWo7ePWvWsJe8rva54Y4JVyOSfSDLgvy02u7axOnVK74XRR0PYysU2qd28vB/qyGZ4a3b6l8NzXncbU6M8DmRXgSLbcWKU2rr2LuXYsqgP+5OjQHcsU5EvrB9Sk23kFvdPY3OdwkiHTCmu208TB7iy7PbvfaH/utz+rpNMFVIawZKq6pdAXXwlqYQLeufhwdvb67EedpfHirRIsCTbeV7cAd5jP+rf0ap/C6ze/vUEV4HnjWnANuW4j3ronbODKqI4SqoxGJWEHEjtuG+yF3Pwu4K797SjgWLBzuWOuulzyJURpfR+iu8TCttNc+xft7TU/S6huVGsHqd91/O0PHJqPGvJ0+lhFiy+XYRzm69o21TfCbD8nLuwL8GpAn7pTdb8E3esZmxKVsuS5vtRav1Mxsj8m9j2seko/MX70ciGxHa86Fvpy2LyzZqFLevpbFfY2nCVoeYwp/YgH0+ddCHZ/5NJVZNftCP/UNKxGskvIRG59n+ylMFVgid8awgsIuqphtYt3dbp1fOlLl0EsAd/DPfY+P33utzOrSpE0BGPPoSOR9xw5ve43Pu9mfQV02vxYW+tKn15b39Sjr4zqYyCcXRerP67rdHydjutCNtcYjpntxsqxsvVGY+teBq1k1fdbOz7mVvsC3f8CSwfoYP9VAAA=\"\n },\n \"execution/authority_journal.py\": {\n \"sha256\": \"9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 11502,\n \"content_base64\": \"H4sIAAAAAAACA6Uaa1PjyPE7v2Jq80HynWxkAwabVZI98F5IWKAMu3eJ43KNpRHWIUs6jQR4Cf893fPQyw+4hKJAmunu6e7p54w+fPhwmycsfQw480jKXBYkGZnH8cMDY0kQ3VskijNCI0LzbMGiLHBpBpA0SdL4kYYE/wUeSzsfPnzYC5ZJnGbEA5AsWDL97rtRFuqXBeWLMJjr1994HOnnmOunlO35abwkCc0QmKjhG3jVIDyjmX7O88Db2/t8/fXqfDQmDnkxHoLIM4bEANFgAcMiBs/nOACQThLSzI/TZTuN48yKcydhcRIyy3OdiGWAeu/FS3wLY5eGxuvel9GX69nFOVA2gsj32j271+/a3W77OzDXTtmSeQHNgjhqp11j72Z8fXN9++kSwS+uPp+3f7lp27Y9aN/ZR/uIcXJkD7oS9G+fbke3AGgaXhqEYXtO3Yc8QX7luxsnK3yLfT8MItZ+ZGngixE3ZDTKk7aaMaw9UvwYbp7FAFohp0c0wZQBPmf4WNJM4zBElOqzXrK1t7fnMR+25vc8SJkJm58zC14pKLg1FKsHvrAWMTUs+EkpGBf5hoOjNI1TU+EognxBTdxnRcML7hnPQCXKUDow3zvqmy0x+xRkC2EGEqUTJywyjXRutAjlYBNAelmuDJtM5rCJDySISJCx1Azpcu7RoYLswB/P7Nq9wx9+6LUsMjeM1rCiR81NJ0/Qpk1BSzKSsixPIz2/YM/yydRSJWnwiCjIpgVg4FpZnK6czzTkTKsr8mMQtCJOiFZt6hWkok0c69zOLm7PL8Ym4nR4NlvGHmuhwgvShAFlUgCPRz/XgVs1wWjkET0NPkEcB9yvcw9SBZ7ZEtOa1Jfr81FjXYA27fjYtjdwYMd9215fzCzBYFc0uQhs9wHJdUH9hlIaiZ8iiDFuzrPYW2lFeIUJYiiK0yBbmSpgWSTOMzdegj0u2TKegclYhMd56rJZyiC2gWtaJAGLYhjPnpyrONKbAHFrDGGALGE14qcMjG5FpM2TCJz6kWnihAJA8AycXY8vfr64wnEOsY9ki4CTBQsh2HT2BNGxCqR+EDK+74f0nhOaMuEdPLhH6e5TGmW8Q+4Q2c8jF+MHcRfMfeBAkRH2TN1MUOPrERrcVwScUwALeAYhC/wljRiH1eI8goBcagmAlzSIBFVBD+jmGLpZugw46gYCPuDQdNUhFxmJ2KNC1/EemCcQCUMcBknzJY06WnniP+gUDBn/wt7q8N8pHmDCLF7wz3fQfwe0Wrd0sUHoqjIsgkFIdcvxihlUkZRG0HRNMKA4iTkNZwGAoVkVsRhNsAYqDEWDqQC/ZrTrGKAa1JhE624BkiFLwuiB3bQbxipRG4O7KcCmovMUyManGxD+2+jceANPm4lwbY17/uXi6uL2bjR+AzujKfxXWBBNw+XwIZ8zsESwmn2I/GFAI5fZ3X30+BQSZdtjrpRxN2Xhgmh9ADpbMmDS08uAu7aFZbSB8fsgaks/bWM0F+lrN+U0j9AGZ7WKZib8EVYAbxQxejcNFRJmv+csXSm+9sPAZ+4KEvM+bjrfN8iP2rLgydhXSPwvyoicbi1rQ94WPg8uDakqAn9TgSgAH9gYA2oOUYA6GzwQq6mAx7gFkGAUhYmhcWY0M6aQDaEycplp/AsLgB9te2jbRqvubhqjk30X6QvUhUENA6rQkk0+Qm7ASNAuOGp1sjgDp+TMjSOPQ3oBmG7Pbggvgi/B3dWBV29oISqZMxABYiOQXsm4oKT3ABTroHdIb2D51u7a7W7vrncwPBrArxa2JitK8bGgDCqRVS+wwZ4ZFFQYOZ+g0AQw9pxUtgKSooxZ0itkpVUpiHRm16TbsNK6kv4MSjqxbVGscdh8kWhSiOfLIMoxKGN5U3UTbaZY6K0asijb2x77BHINCuMOewJTeHltqSgFDLJdPqLwJxp3KvGCKKMPbIYFBStjhSdqzmKrEUGbk5jdd/M0ZeiUShKEmGGtsr7OpJBp2sgOBZLTiAJyplDFOiseBgdQt7ug0X2xt9R1WZIJN5s8iy14xpRV05wXcEhDAe4JBxkn03o5pEzkWSk1n6s0XFMQWIEfYMA01nBR089rK1U3CgxgruhIfrcQmdQITLfmubpSQ6i5tRrkPHYo4HsFz6QoQgQYpoBmuKroUT9O7GmDodqyGqzId3Eq2dONH4pUB6oZemELa2bboKx62wpiNV9WZ9+gtJFARcD30KDZJiYUCMZttFyZN7TSKytArOAQ89zFWtXU8fMwFFNmakxo27fbg/b05aD/CoZU7EhN3GmrGu43JMgaTeSivZ3wH6Ynt2+NYrGrNWKN1KI8u6oY6d6QnlJZmi9pKAuUhn2Cit+RUyoKe28mrSywLZlWeYCUqfOppb1a+BVyI9BEEV+pj0WD+vLAVkNtPBN4mYqYBQ8YtermjKcOdeMsVpKmpg8LVMXQelUNmcjcM+7SqOzIQAVxCv8hJmQpWOfWxqtS2SCF6uHTqW5VcCICywZeYx8b0xVHAaGrATepNCHO/9uC1LwsWNJ7CMeOY4DK7tlvcceN8zBezjs8dgMa7qvX9dr2r7IDGPpHRycn/uGgP+/6vf6xZx+7njvwj3r2Cev7Pb932PWPD+fHBwO3dzDoHc7tg2O7B1S6B/QIht6q3VOwYFDRbK19AJ4H88P5vHfkHQAjgxPKjg4P2DE7oSf0wD04GvQ84OnoxHffWEOpXpCkv+dQybj7GfTpKy1jvzfvsv4ROznpD+bu4fzQ7VN66NPevNc9GhwBiHvs9vy+3T2hA29AvX7Xpf7Jcffk2O0O+oM31o+ge8rBSBlX+Qic5C7N2TokBosZVM0cq0NsaJD1EmH3MtBGZzNXdECOY1s6ogJJEiyXeUbnISyp9C0NtZHQpKI8593FN9QLSciyTdV3I2RA+0OBGnUKXG8+04PG9FQeT729tEaZGF8lxqftC59G0IbMJOU/QPgKsCTx3SIVUTAMzY3xD6NUJmKUUqylpLQqjG041FJQHx2F9hFjwscKjjyNgsG2glirvD863cND214jraWUFvNNV0iO09uUr1Simr70DyFP1UuAuT4isAxQSC1xCYfDGPaYh+B3dA4tNBShuO4cW4p6p6HVCG4fcZEPTBV4vcDN5Dne2pxkYsx4HmYc6ocw4ApUzk+Kuall6DArfUEZvpjdfiJTLnLrLtiSblBVCfJJOdXdKpHBFjMGLpnOZAC2jIYbFgCSHS7WaHKjN2sm13KqS37RrmNBxXwqqtZ7p4EgIS+QgzMBIIBrgm7HuDhHSXTqm8hMMpMLiUqqZk81/PPA9y/OeR3fg0HA46oirhqlJKp6GsinPt+xtAKYrmG6pYhy4JLOWbiRlMo1oQSYbvWSmlTXt3VSsbAtsX2CqmKGhHSFBRmYv1yGyGUIKgCTkFjNDSnnjDsTU3VBZzgAhqxepSW1yuZszazrjQWHSeaZiizwOTGNEHq+dvJwD/2bkayyRRzhG6xhAu96goYJXr20pjpfXN8KFdwIBGmdohtHCRv2GUSPUIDilcBEcoPcSs/ayHJ5fJBQ9wEIckdCS5lv1KDROoUIwVxMK1CeRJlzcILNpoSdKFVNwc20GPLUvr92OlEJG3rFSqTAHlAPg8bqayqPVfOlqFuVoeUvMQop6/cx25nbHPDUpNTRFV3CZlk8S3fDFeFKgK51zsX1yRb0i6KzqPjTzdfxpSIIBqXF1EcMdM7Lg47iiECrrQNFOos8c9LYREu/C4OfWorsRApaedcCVYYqTELTLbibThuW2UHHMFun1b7/JcuhaDET6V2JuFjT8K9gCIeH6mpJW3c1fujBRtTYpNzG7R9eE3e8fJlws6BiIXsz6GS4g/WdxVlCUwoz3DHBOS1jCEGgwyKs6kx4qlzSbeRqBu4aR4GLpYDMzuDXZdYprVgGI07kFXU+B69YQIIER1P+39cRICm8sl70RCszmdg1L0S8ZNLDMbwj1gMHONDtHHR6eGG8QelgTvKWBLEKBxInStG9WvfdoWVjOYFQ28qJMgB9q5QrAUhsTaYQLDFgWBmYpyk6v1ZLhYZKHYGi0LB2ElIewGZk1xpALWJPxA8iD2/AUubnEMCJOnGVSS6MeZ6yRlMsewWpCL3Zw6K43jAJllAWbcNqGV4C1Or54ZYyHzkuSlFjuLlwxsxStjFKPG4MbWy4RZoif4f0GNFQ3ZlDDz6bBRG0MDOTs9C3wBDw6NQqQkzlXhsBOnK+vH3Gt3ID9O11BbR6iY3u1mrSyxZArUp7H8+l1Dl5+zfJbwcd2aijigt6cf0sbvO3kRBwhoVw17Oz8ejTHfmPfLm6/nx9eXn9i34fn/8ytpr30OIjlI6PRMxiWUsNX16f/WM2+hUIVN6vftqpkA3MlfCQcQu1dMQVLfQV9VTWIJkt6tFfDKNpgGpE9AtjCs1JSRXPyGcZWI3Z6IKKskZTwMM2aQVQ+TlOYRMWMdS2EBf8ZV+VXFsOvvEHS4XhVjZfynWG1UXEXQiYL3l5fV1H5vSR4WcT2pDFO85U9JUx/NCHQux9h4lBSCgtzPcqplWQ2WRFo1/PLrdZ1C/j66vLf67ZVJau6toQn6cAhu+J9Xw8qnva/G2K/ikyW7lflgIG741Qi05vvQhRH7A8QYSA/vrfkbEVxA9zvjDXp5FNvopcU8MFkN/jpjEBlO7iK9rbYrPlJx4b3Vlp/fxiPDq7ux7/s/RX1G6d1JpqaywXC9WRIFLirelGREwEbBMie8YDTvIT2OhIPIIt1Sn8iYxFE0wYfuom7jJAdGg/swW26hCmcRswA8FrDvOpgPbyVBUOGQjfqfsofhNVsfmMQqWlIrf8TEUfzLbWs7O8W8TkCKtUvFz52RRPbUNxhUZohruW6Y8oTgELk2BxPSgDEfMgWXEopzgedUOmN78HiSm/wph0hydAUb0Mj6etzZiTtS/GpsSpfFJWDY3Fhx5VAsO3gpgWT9QCtaXFzLR+UVlcpJ1df7m5HN2NGnftxd1Agpla1A+KLXkYU1yn6/xdj+6V+10t4WYBivvf7ZulKWgc/NwGr3FTT9z2q2JG7qTRyLt1YkoTIhZLHQyJMf56dXVx9bNRXGDTEIb1o7WzFK/oSTGAxxfi+zkTv7rs4J9DCBrvJiNMXdVIf+icvlMeN7Ze93amENg/Vvcm6BIs+V3iBn/arsiJ0qJIm1VFagtGz24U27u3Rl7BOJKX3VJANIPGoi6HrIYrMsD6ZVbcuOKauALlf5VMIUs+hIvLxyZAQb3igOU5hLq+gDJbfvA2S8QBTnG2L44kjK9XZ6Px3aeLK2O3omRob5QMRcgvir3W3n8BzIlR+u4sAAA=\"\n },\n \"execution/execution-plan.json\": {\n \"sha256\": \"fd38385e3687ec6f9886d5025b394463c252b8198b0426c56f95740139eb7e9b\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 4770,\n \"content_base64\": \"H4sIAAAAAAACA7VY247cxhF991cQ+xyu+n6xX2LESZwXI0AMBEgQDLqrq3fp5ZATkqPVxvC/5/TcVtJKT0EE3ZbTrOqqOnXq1Pz6TdfdPQ1Tufu2uxumOi97Ln1hGtZhnu7X44GX98OKZ3umxzQNlMZ+4ZHTyv1hTNPd75qF97y08zAiTz8flvkwr2ncDSfDf/npTz/0f/9rL4SI/c/CvvvPOORgRZT9Is8WtmW4Hcc9St+O9EooJ4V8PbZuaeN25rDwIS2413FKB7h7z+V8gj8wHbd52dVh5BVHf8VTPE+0HROe3x9e2vucbSjMMURtKldRS5SOfBCeOcnihCheMmtLJcUca85krTNslHXCuPN1mtnj9jgvw/ay+2U+LlMaL/ajMdJZQTawtC6ZYIMUKgqm4IQKWSaFz210goOMjsg1h6Jama30IVztT2kb3vMOET4saX8xXqmGStpGHaXIimMsxcoibUxaJNw3WE1G1CpwAeeSU6IowSkVCqkqcTW+MPFw2HZrqry9XIxbskZ7XVUhk4UzQgSlLMnguGZZbKrIG2kHd9qKXJJIUhUu0SOwIvMdbP92KgUBJJ+XAbDYAKBUXvpzaP183Gje89rj732ayuUaxCEkSpZVcQSv2VutQwyZYlbG5RatCYpMIqIsEIEhY1GyEKr1NV1jXClN/SXQ+1/WE0jvEn4hdU7mLLRjV1QrCclcjNba+lAIximrnG2ikJNy0agolWSvlXw1jpoTn24+bD2KNNebjyyClCWponNBpYLXGvXwgW0FCFjG5JAziwiU9zmIXIFG7bl4RS7pfPWB+5ehAPZoy8M4v+x5eg3EBF+DNgRUFcrkkzCkdFS5sPCKKdXgC0npsskpAeHaco5kY0zFUrRXJ+d2QkHmqa9pGI8L94/Din55ufkCNCNuWFQKwinAyzpgJEcro7WVgnYCKJHIpggplIrPUrTaSXKAZ3S3llm2oaIdW9rgdID518JIsiWyMrGQEOhCY2Jz6kRN1XEA2pRw2bpAviEQzYgEOmNrwhXY3JA9j2NO9ISacB2Hh8etr8dxvPkRMgSPttFWsSnwZimpWBzK4r1RRaCp0LVZZI1TVVaDwhmZlVVRgTve+PlCbWpIMQmfMrU3FJXqMhVgqcTqIzhWeC2NJS0LwjQpsqgspfEgGSLDVx914fWxv+D4MC8IZfiwoUI3TxqZ8sLoZCiZxlFCmey0Ax+wFDFqkuaEDfbgg2CVtUStY4quGf//lGy+ljMwiUbODCqb2IBVakzZ4K6qVvSkjUoqUF4+QSyUAFazDERXIYlVjbem+fc4bLxrJDBP45V2osA1nak4zrq1eATxgplLtSnVE/Gi9zUaMClfSwjB+EisVUxKYJTckHzM47A+YjJgKA0VA6uB+hZEKQbsVYFUoQslH5US0aSQGZeO2fuEwtcCnqtstWLtcwlal5TaKHotyspphI9LxlB9ngpPNPD6ijGjtELWQTBFWUM12pI5CxIYCKQsqCe5FIxBCyWtFcWoSlXRaBcl/nzmarw4+n/B7PPiv6Iru+wQRzYYMgybqIG3cBWyMylZAAGJqdWp6MFfzufE7FFxVwFLp76I4xMf9xvvISK2VySDuTICClGShyuwiAlGiyhAvbGEAph7DV7xRkqFYedBogFkR0AA2KF8vf9fKZM0cuEwE20QAdNXK4xfQBqT16EpqrceReJs2BFIQPpcNS4hAQ1mc+P+YYIYGVtlTlLlU/KHIadqcNWKBFIEPeUIQaBJYV4FaTGMi4vczpTQoFCIkVaLbMK7u43/5Thtw57fDDCHvnNgQIJuMbKKJCyaP3sgAbM4BR0rFWAKxrOSAJ+0VF2B5cI2hBu2mqZ74N3DMS3XwZs1s8oByEFVFTijcilCuqKjENmxlpUookuTqFoqW0A7HqyAEQcOFfLVNNPT7pali/WkTcIg4QgTKkuXBFogSK+zJUcmti7A+GahJFWDGodWAe29blIklbdT8ZKi1w6H5HLQJYSRbQNITmEIKhNgMlGJYEYAWlgKCiPAYwAkX6CbMKm4wcx8Aa40T9uCifU626XGDEweKUIlrGGNEFBAp4ChjFklEIKqpWSUp4CuyGrBinTBcMMEfdVI8/PEy+6sIz7XSudmfzcOEAwvNPK7VPbDehLmt/BfEYGBSK1SIAKTgA7kj7KLhCngnZSkAIUCrYt+cWBUMB30oQG/Qtlgql3DfuN0bRsA8RdcahAjaAtGC3gu5KakAIYCFBaIR82Am5O2+kaEoRgLnVNJZePADsqHr7p8OkJ57If/nKn7Je3H5g5iDaJQZnLeoh+NtkYCKtDTUKJGK2jd0iS2xWQXARwOZgLxsoY28BjgX3W38AOkztK4+22QMaNTS7YV/AphXrTMArgU6IhkZNACksoq7AdQLVDu2cuAaZg5eDScifyxHL7Y3g379HDaYrBvPfAv8z3Nx3He5/t1JqxB7y4/vnuzkP1+fUxYQL6tFk1cTXRZVvAAaJGgWKtVIDRXVVXgBG+y1/GkBqFjtG/EQFIni0d3ly1twJ70P1/Gg9Y0mlNBKENgkGk6WiYnndL4F+IA+rNYhUuFJq5sNjZ5KDAiEYJOl83u3AILvx8uq+TdHjDoM/f8oanFOmC76k/90v/4x+9/6J+H7bG/Ln99xfv9n6HC8zjn9RLgIzaQ1lD/vFR+Gcaxb3PheLih4fSMZtDT5clcMTEmPuuH21MAJU3HQ3/59PYYuyYOfmb0+vRjs5et+frjp9av8+rN/Locw9N/nUK67Jvn1jhVrdFUxx82btvn+NK1E9AHTfhw6TCVtg56ERnCaw/D1J3nfNfkV/PQZUZdueNEj90pYd91p4R3YL06tIp3bUa34vcna2VYscUN7QIdIN3RcVngsDvAGm/d3378/t25mN15L/quu0yvLs/z0xPzYZgeumHtJhhLU7vwPM37+bh26/AwwR9W3Wn7eJ3nXT5Nj/Z9QC9FL9XPSn9rI37/43xuSc87bJKH47ZruHjksX2VsC1HPn18WaPRfWkHzD/Py9M4p7JDnAhjd/HSXqlpXM/vYI6WVurdythLkdCGpLvvx/GcprUbJhqPpQXzsdQEnVAr/kvX8Iv0tqRfk3yL5r77aT5VbWpo7+bllIY9LFDHe0YnTvRyTsR3ePtlRqKvF+rAjodT6ue8JVhP3RkF6AGo0KW7fX/T/QEr1sgbd8AE0t8+hp0G+dP7DWbIGoq/ADk8zceHx26bOwBpfu5ugbSMwk2z8TxMZX6+/6Rn97yfd59/BXTJeOtnfv7ykZtU311Xwt05kXyt3Te/ffNfHbl/7KISAAA=\"\n },\n \"execution/native_program.py\": {\n \"sha256\": \"fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 18899,\n \"content_base64\": \"H4sIAAAAAAACA9U8a3PbSHLf71cgug8iZJAEqYclyriK1tJ6VfHaKkm+1J2oQoHAkMQKBLAYUDLt6L+nu+eBAQhSWmdzldTd2uA8enp6+j093tnZ+Tn+yiLr1j20CrbISmbxsIjz8tTi2bIImZUXLA+KoIyz1ErZIyusKOZ5UIZzxq247O3s7Pzl6vrzh+uzXy3P2h2n8SLPitIKs3zlREHJynjBnHnA50k8cX7jWepk3CmYw5eTvMhCxrnDVxzmTS34uzdNghnvZTlMi7+xURHEnFnXyxTBXBRFVnTGu6o3suK0ZAWgCH8C/tMlZ9F41x6nebBKsiDycL0efvGO71+d/ePj57Nz34cB7CsLoenDl7Nr0ZADisyT8+7Gu/R7vHtPiNEPK81KWNACBKIiTpLuJAgflvl41xnvhssyA9qYTWIMkqE2QjcULGG0BHxDRzyVzVmSIJTaj64aYEuC/D1IlpocUzrCFM7okUlUC/b7Mi4kMSIWREmcMk8dR09/TItsEfNsmhWLoARQQ3d41B243cHwdrg/OjyB/79x3ZHrSkBTBbnziBjAOQZAYXs0Ti0gExKI2texlAPHqZq/jkuaPVWt+Me3LGW9ZRna79QOgCR4cEvixqc4jbIni33N9U5Z+uh939vLeA++4gJ4bbz7H19+unj/+dPPlx/Gu6Pxbp+VYb8IUuDfov+wz/G/3ipYJOPdZ7nDZdoJitmjM8milfcJkHAQm2xZeicubRX4bVmkVsXBPT0nTnMYhzOdkn0tvdsCqBQGOUxgPoDAXmpTIOXfDqIO/2kqB1HnATbopAGID/4BQhcyuTxfJqWHawKjPiwnLCwT4pcZK+Hvat54t5tCg56ODRmNRMEA7rYJnDgRAbYnNhdmEfM811HctWBlAIcTEGY1UVPUMERNQuJlBDuDIZwFCYsM4RINsP6pFbGcwZbTMGbcGGE2kxQmwNwekYU6k2y1YGlJe4lT5F8WdSMWxjzGjbW3AirhQgEJs3QazxZB3j66Wwid0z0Kh8PB8dvjyD0cbAGcP4YKMnxugIkU3IbcnIUPvrn1TrhwAJxjtsHAIM/VYvCZxCHp5y3EAN7MQnFe6rhhItBZnSvQGH4tYzwUzxvvHpycHB8dT4ddFkzC7kE0PegGbH/QnRyfhOzwOHp76EYE+axa3/pyeW6F8yCdsbaleM5CsYwwLeIbzEv25fqjXHZeljkf9fuwhxn7LeuF2TLJFpMez8I4SPryZ39tk71ZDLwAzBikkbVtObBcc7mW4KJ+Ek9ZuAoTtnF+xHgZp5LEMPX7eFdLFOmUdppzVoDapgFqWyirRQrWivdAygMSEmSW57aVeyDNcMB8lYZXGdAY7cP3Z1s2B2BPQGcTpa2YW6inJBQOAzbSwCb8NdUdRRL4KIFLWHnNHuUengkaKvVWvAgiCKfd4ILsKQXVysHQsRo3gKIGRQV6AE0g7J/l+NERusCu7CyejbJ3IzWptpmSLfIE9i63pttBpMsAzESBKuPOxaZ4EcxwnPf+7NP55fnZ7UV9ocrckg3D0R6qmv/peoZepSZyHH46u7lwNCK2YeAtOYhboDF5HIEbBhzW5aARFoGVB3EhdO2fRg7xITVOuCwKUKYd3LijljBO5N/WCKX2hjMUP5RBueQ1HkUVtbpmxBrIKJ43gLnEVYrGcpJiqmWOHkD0h+ZkE5K06AODrUophWlydF3BzYwx91JG41C4dteI7YDoLCTBNFjE7H6hZePeamNXiyXg9gDlT44mbngUHR8P2PHhYTSIBofHrrsPTezEZdP9E/CzhmyIKmcDN75GdzYF9p64TLQ625CvsZ509A3e0/yG2/kxTDxPogFnqiVfxxRxKu0DOTLftbc9or/RXiWJT7zJ/TzgcNijn4OEeorgSfpR/lNczucsAe0yIpcK+AQ1M7rBPiBbZOCL+nQ0MYv8ycovsqzERTRl1CjBDGrqspyDNKA6g2kz8BVLtT7o6bJYSW9XeNr/soDAiAFQIPIMPGK+xQsEomYRp4/uNtegKyYBKXqGeYqzPto4b8OcNSdSawSJ15obCe2VE/n7khWruheJG+LeXW7BelaOZDUcSgVUepSoyEDpcSmGKLwNX0aqhghIisJ+C44cqI0FHIR9b2KbsBSBc1QzFFwEYWnxOJ0ljNSNMGaImxlSncJvD6eBUm3svUU/Sd62UcAhkE0BOmgKUmhi9c1KW2k/Gr11ZEO9e3+WodCrG7g29qdn31AHugTO3b3dhruaedc6C5eWMJH3JMlaOPCHwZJBkg4TqowfBkTkuTwnKQB4EG5GHNURdP37ePfNn0X9Hgc7WAqgzsC+G5DmNnkyiGAm533hP5julvWijtqgkFq02LqOskd1pt8UR4QH4WRweOh2pywKugfuydvuScQOutHxySA4Onw7OToaEuSsiGfgZCeWWN66gu2BLQI9XK4a+1KE/ANMjtOQzqhMwQNFD1mNs//rOzJZXL43zgEQmme8/MTKp6x40L+v5JHj9+XVe0ESESe+T8BOfSK2Rb9iHhTsSiQHPlWxwrOpLcDG1aWfeOuBrYDtFXK6idQifOBZVhtBbgCNtQSyCaZAqonAdilzdnDI01KTjkZxb+3MlCuF3ddsysAdDJUgn5rKUgBA2Sa5ET8F02LaQZ669N9uKkMk8YODxA3UjhPwkbFsId0+mmaCFurgfmPgXBEVZmxiRhOgaBMxV7rqZMr9xGbYnIhJNkDSSkPNEhsnZXVuZCboyDI8sCZS26iNR1qRTxJ4HudN2U6h3St2dnZ0xrWZY0Vt9r+bZZVJ1OuLs/PPnz7+w0ys3pz9fHErWoBB4Zw83z97f3v5+ZPvn0YTIFUfidHPi/gR+LyPzil76vHfQd/BUZ9OsySCDTaHfYMNHh+6J4NuMUA7UMtIOkUtEdmShDRyEhnHU4AT7SAzu64rGZqaZ0azdgwlC3+5PO9/uDxvplhB1wocO7kTQXtYZsXKI6+RsOIegE7wTDp5JVIdbOjd+Jc355fXHQ4Ojr8Al8nGTWgoIqrQI68vPhgjbRUdURPsx9yNmvPr5/MLY47nddzsreu2rOJmR66rYXaqXuBlApAmcfoAayCfyh2r1HO45GUWreqUUVRpnCQwOxpgcKVkfzSBwROQfcpUhxnwIu/EKarG0kf9kKXJCkfZ1l+tX7/c3OLNRMggWgEBtm4AR9YPogVIW5aztEecLjjvtS66OCZgeDwpOihYDbeMbYKvoAOzJT32FbxRDv1vwBd+wvjBHuGoN8bUqsuA8agGPE75Okns02nBmPfYm/qT4DGIkz38nBZysmIa/Pk3VxwwfL7zDgfDvYE7PNjbG1IrQvmbh317naGlCUFJrxoVxJkP7DeDIwUACDMB4kfVjQJPg5zPQaLCAKwYmuPaAW+DPzJVMwqlImAiKSjE3K5kDLW/wZMLYClrwqxlGkPMoIwY7y0eYIyc7RAv26fQHM6Bv+vNMEGxmGwnvoPmaQwGDWMcibVWPi9TTDNPpbBkbl+sAWffBzdQrWCcXRsRxFQkgia1aGrdPjG517h7uGvLNtNIYnWD67vdaAIf0YS+xTpobenjvv2qQl9kqEuLwdA1bw5ooXrER3xoRHBiiIzfKAWpnO2RQA9CbSWXWcqBLmBDfUUPGeE/662AkwMOEhO3kqjsZZR/akUZNQAyxUoQTB2/JDOePCqz5XQaf5W6QQiqY2/k19pJvRFzbdSJSt7rPUriReirTxUstPWfZx9BPtElPLXixWJZBpOEtdyqRPEMQlZPGvUeOJTDw6MO9mCcQUpOLoo+6ASDGoDOS4C0oG3gJidJhrlFkFEw3Z0kWEyiYCTG9MjnkkJvOxOKY0Zi1Z5Iw3VoOhGsyL6x1Jc4yUFz9lV8EVZqk03VHSYZ7EmfpMK5BpGMWE3zyrNSdNT20n23pvEaYkMJLKXBMG4hwP8HaQpsmZYdEpFouch553tb2ksktqoUnrCDvjKUMEJ9GnTQ46BbtZndYuPQuX6S5rDJqkSvlCyb2Y5qDZqVdqsyZ3mAaWRf2zOFvewm93lkOF7PNuUYm5baTH4ZObH2RBhowWzqbaMd5nEqyomUuo8u7awAY+YLfq0TkqaIDn8RrGB0mCwjbEplrhw+F4u4VMs8N+2gvsAgZ1046MsiQbZDXgZaYwdYq2WCBqgPznb/kaWPfRjRz1flPEv3e4NhnwOXdcHsPkB4z/W9l690fD+nm6Eehuen2Ft5mQJ2LTiquZA4uvI3UV2rFnIi3XUHsjYDRmRHBwcUksEojEYtgUyV5ovi6ZTCacKikjGBWk3GiL9HosNvl1MxRsiYbTfUj95jHQSQ9iR6Gw4m7CCcHA1OTqLBAZu6QQBADqfHhyeHg+nB4cQNwwkMO3bd4O00YifDIay4f3h0NJHZiWqPMoUtlmnsMF8WOWi7NeOMEZeoW0Blj4YXg1lRKuD7V1+urz7fXPj+KysG9t16wlUsupZwrVAW4tfNkyBkGJoqPC0hLtYUPE0jucLxjs5MvsoF9HV+tbYc7H03lvMpFeXLWf5jUMSBELABojWBYDlOZz4yMvdhRvaEa49QjwfRY8zB7fOzAsQukbyuzeLo2KHr0HIOhj/0pYKr6xjM54MFKHTGHsMUuV1x3dA4sxDzNtan7JoJr7NTl9LeL7e3V6rvFxCJhIko07JE7Cl6fDm8Aypk6uwFxYw7e3sPT/jRWrMzZ0EC4qDmN3LhKCUQ/TZQmSzjJPJFX8fA2LZPuUxSet+flZ0q4LBU6q8vlvtGvNynLOg3dZFQySW4sySe4r561O8Phm97LvxvMDp2j11waAlm5QW6JLtA1hxcNiagmeUk1NwTuHne0HV1Jll3kjQfD072bfudB3+b8YekEXGpQSC92TtC5x4B49KYG/D1JbPv//Pj5U++r12HWjf4y/RbalaZthr09ntDyizouwBR/SKHCWNTuwmo2B7hmeRnCXGkg9oQj6G5PppPyqLTVUZHHkZFPTFdHiBahSB/6EdY+yBXJCyXvCCTAf996/GsR1sQd/ICdVxW37sFxHJm8GZRoyfiFbVow5LgiCplgfu5c4lewhGoGYoNpmWbHSGAg/t1E0Qdw3rHTHfs39eUHO0TEdpkfgz7gxsyrc+c5HrUbnZE5yazow9MGRzC7WATbmKUgVmDge7UEdxLL5vMCnKd8h/r48kX41690TFZu8bW9hZwSmELo+AJ1e4Iky49Rq9mXFUfHiOs4oI3ACwpG/EAXUdIsK+1k/pwGtoaPyZMZsvafGPCud1b3HqtKl3E9fj/9A9F/wLYD8X+p+vhp7rWAB118+ns6uaXz7eGngK/Ds8kDeFTDnSiOCyljyabVNFF3ScHaTK8cjAOWYGKVN+tiKiryujoC5a6UviDsZyCctcSYtyjcXpNZCfC5TZQMgwReuCFABA7dRQod0vmRJUqysXUBlsXrKInFOMmzYQNqOmW/8dx5eaY7sUw8JVRZEssKuM+zl6sZhbZnr6Iptpqm+kCxLL+at2URZxb4Bpa7z9eWhOq8B1Z6HVbuDr1qCwRkD4ilbxMSdAdWdlOWeSecHAF0kYJkMEV8lp2BzSF7yO5fN/yPNAMPritcer7sNcdvK8V5QHirob+7OEVF7jhHRxb3ZuA5oIOqpKmNcDOmAPVZYoaZiIVsjiHswimrFyRuNVmqisXNbOh0Mzao3VNR10NgDpYWcelbkDWcal0XWNuU5cpmYQDboAQLt360nX7R0uTpWurjkGWEJnQ+KXimLar7eoqshtuq6rpiptPiuRFi4j5gAmcwWEjhFsL3mrF/KA6wmyBWeAqzVkETypJrMqgnJbcp5pbi+dUJKd1rxykKkubykKXSzQRm+HlTz1ykaXfUilJwEhIkhNvG0VxELoNmwsK7PXCq7YEkYpsUiAnqZ9tRVI4iLcVNZ1WeWAJZv2U4KtyN5cp3dlgiC+JQbBN0leg1iuZatfsNFMWJWUps0QxHQGshR/Y4NHgRikS3moH6kCjCI6Fyyvqk2Fv6B72juCv/RN5S00BCoJpFMHImWR+bV0hLNCQfY176TCD8B51KoSiIVZjrHI83FFoQqY1ww1rVhDgd61gouqRG7uW5Ty4LSrnEbEIFlY0xj7QGPIv5ZYfJAP9yhZZsbrCrSwLwUfnMX+oNVxdnle/6YaeKLBMhX9Ll6O5HKAIAUdCD6nQj2xhP9E9YZoH8ZmGIEardBj8aEBe40hAKAL/ZGHJBFILU1IOyjOBCF7UtUZnCbgSWPuIwiyKvEeq/OhU5iIgwEYvA6wlZ0ERzjsFHFy+HI/5m854HL2xFzCDVrJ7MwjT884AHEHM9oReG5+JJQWWxAQAC0RC3TjSYjTobtQd3NP1OP00C68WqKnIcOnR9h7ehdfqQxT+7zwFnAouQpDKUsgWdVvvr76A9xAyFnF98dmoNZGV8aqaI6+qj1Ux/+bSaD0ay8tYrUvVCxrvzXTZIOB6y4oFvUbA3zYZtBjfJ8yss2KWWRqwvjzkrCwTky/VY4bX1zdrLfsjBc6WRQ+p6lX/1dIh0CqORMHWtkUNp0freVF6GhWrNhHDlzErdedZrPBBj6feZGDjVJnlNd3vGN4z4m7Xjh1grT+SAlGZywIRsQbd03IkZsNCYnvZSLEiSGWUn4J0yyMJyxL9f957iG00xwcLNUelyH5j5DJ31DZAsQqMqKCQqKxMAlZmlHPkzAlbgT62REpNoiCODsKoVvUI7Y1TajmVSiXCcONMSGpch2pUwiTGlDeOaFGGWlo8WQQYo+L7CYOV75hcY4XQvyOM+rG6JQ7SLi7xdbz7LLLRoRhbic7IEDEqklnSPZ0sYMecEY7Wz2dGdzCbdCQtA1wr3BFRVTaq15Q5yiBsfHX04suk5/tnsqr0htfDxTOxMmZ8jAdB+FZSmaI++V9Yl46RGvRtfjz2LLb4OrCKDH8HFlUb2LbE2vhXLYcC0RcT+82HCmsLvvJ9w9q6Mkz5saUNjlkDDH5WA6hhaQwYuvWZHEF8jeo1pIqOXOvGl18NduuPB0mTokPnCT1JvwlmF/MGXj9ij31QY3HtkYB0YDTLKW2NCL65a1PONUmnOYaoKxhrKrj2BE0bQaWS6jpYKCiNRst6Nb+TRm9dkGbpQC1YTOLZMlvytnKURojUiLbEVZ/G36/8Lepo2GmvpmpEOloqFk/DuFNqypQgThEXqiaRdXXQqAL++r2MZ+grtC289OlWHIURb8oBB7ItEjGhZn1UsypZ3EiHkY6WriYjMjbS6S3R3cvPYLLc+8N+1imSA+iz7rgZEwXJsEGkcLAeyasnLKpTEt0YkLVlcanTzOFSg4Gzeurw/vOvVx8vbi+qJ0CYrFogb6EVrbhaQGhe5Uhe8WrwBbfJjTe84LV9aG4zgn5CuT5BmjQaQmzSMmbNo7SNJ2E6My3nCF+VfUWBistkhf/GBNVFqZVwe0CpAgXhpX/FQGxfRBg4wQ9KiuenYN/5/BUANGeoGWelfuhRZaP+SW36X0ewT9U7RJ/Wff0qEs1XLdKo5sB5vfIbam88C9Q0+A5YVDLX0Nk0Sm1xU79c5J1XB/fOUxMdg3/MqjwJCPdsgRPIraBEN7VsxFCaCuYrqZtliIEXsqFAAmRRs/MaP4lKZc2VKmzCSVS1DH/f6d57zbN2Vce/XRPU3EJRu/iCY6gh1xCvHkx7Xl1W6n3/cn9QX3bL5F4jjjQOtcpAKpmEDYJEVbwMv6/bNI4Y+MIhYuarMVATRj0pA2S1QhQP9brZBIMSceCyxmNNMwo4noDesIMQGZ2aj3JUn84cPYqMkfNIjznE0a3DUBf5ttSGBpDKNAn+JiyMKoPqSLfvonp0/Se/g9ZPigqr6iJJqmcP6r6LvMVdcx2ERyAL5UTB3eEmxwFNAUxcydmhzEw0X0RXZh+fmIQsL62fYMgFfSKuAbcY3iBV/0DKJg9LODZY9IQVVlT/46FX26H5dk9d6ZDvI4d56l6qnpGXXiWqGcPna8nNnwItDSdw7Y5OYIx3pVQqFpcdepwhN/JSXl6V8Y/T3b/8N95QIjTTSQAA\"\n },\n \"execution/receipt_safety.py\": {\n \"sha256\": \"5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 1036,\n \"content_base64\": \"H4sIAAAAAAACA42TTW/bMAyG7/kVnC9yiq4fA3bZ2t626y7DLkVgKDIdC1UkgZK8GsP++yjZSd00A6qTKNEPX76iq6r6Yc0IIVJSMZE0oFyy8bqXoQdChdrHAEpaMCgHhNgjeNKDjAj4jCpF7Sx4TserqqpWLXYTIdSDNAnXX1bAS3dQB4zzGXy4hz8iqB73shmQAjPEJQijO1SjMpgDl+LWPeddSNu9Djkp5FDbiDvScRSXhX1mCULZNo47azxh4zwWfoiOI8Wc0i624u8rgiMoAh9PtW2y4s+L+xcN5Uq4J/Ef0hklG9ABrIvwkxIui76Rd8z8Lk3A0wpHQx8Xzm1md/ntHLXFMByQ3yPvuHqr7e5t27nGAjd7z6y7zDp8xoQWjWZXWBsHW+PU0zkXT3DLBzwyCb2cOdo2ndG7PuaA++807aebZAmDM0MuMo9SXiR1QPiV6d+IHNWizNzH7PNhaiH00uN1iHlUCbsUsP06WR3gt459j6YV68LsWPOOXPKgLdTvmcOFGJ5tacc6jh5rm/ZbpPXh2XhMih3lFO7gZhE9wO3NxcXtp9MZ7l5SWMzkYNG2uZrE1+tF8Xe7MTc+GzH3Tcj//Fxk9Q8H4RSfDAQAAA==\"\n },\n \"execution/test_execution.py\": {\n \"sha256\": \"a1d29ea6822be12817b2d60109cec44b061d167f147b10f9aa352529e1a4c475\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 38465,\n \"content_base64\": \"H4sIAAAAAAACA+19/XPbRtPY7/wrUHVakDZIkbIkS1QxrWIpiVpbUiU5aV4+LAYEjhIiEkAAUBaj4f/e3b0PHL5ISnGe553O60lsArjbu9vb26/b29vZ2bldhtkDywLP8BZpFvnL3SxxwzSOkszIWJqlPeMyMkI3C56Y4buZaxlnkffIEiNKDG8GdeCn62VBFPZ2dnZawZyqelG8lL+hFjQwZ/I5iOSv39MolL+jtDVNorkRu9nDLJgY4vU1PMoi6WISJ5HH0lS9WaqfGZvH02CmWlmEQYYD4FDlU28OnZewoSnvgX/3ojBjz5nWcML8IGFe5qSZHy2yloQLY124WZTweu4ie4iSIFs6v0eLJHRnsvr/5I9WXsIypglLH5zUc0NemWPVgTHdJ+5cjfjm6qeb0y+8CPSABTF0wp2ybGko7C7CLG21Pp3enhs24ajtODh6x+n0oJVo9sTanV7sJizMxD+ty6tfobCcjZ780d7r7x1axqAP/w8sY88y4Ff2ZxBOI1sVxr/+jELWW2Rep3Vz/svF7cXVJYAzmWm8M/b7rU9XXy/v4MWLmXoPbO46TyxJgSrMoXFgGWYA+L1HNMCzGT2a8Cphru9E4WwJGGBOFDMse5csGHwDUoR3MCtpliy8jPnw6Ud3ljKrZcg/5iyYMm/pzRh8fAFwXpT4Kfz+AADYEwwZH47gAUD7QXgPT4OVDgDmdRI9U+28CCDA9NkMZiahZgf9FXZoMZkHKQ4opfLQtWmQzHkJ+L4IBdrpxWrVarV8NgVq8VgM3W93htSuG8NsPwGZAOYG4k+X/trHv47ko/xjUq2EPQXsG9bhv7p74g/96O7jX0fyUf7hVf0A1nIa4PrE+vjY/SD+0I/uPv51JB/lH9kwkR9NK3QcILkzJ8AhmheXP551f73u9vv94+5d/2D3T1g8Rwf940E3GZgalgWu52weiapAWn4XqW7Qh+FivW7CAJWBi92k6qJ8TkODBojpg7t3cIhQXSTEw32cK1h7HnMQV6K2JFj4iLzqiRrCSqfXsNx+OT+r6XC+tOeRz6jw2ZeLy4vbu/Mb7GDmJvcsw/dAyLP58HExYUnIgMvsJm4wC9zQY/3BLq4jpJOuzzzenWpTMMxgGnjUK2fOoF3CUxJFWXcaPENl6Mh9EHY5x+jiypm4Hl9FwApgcTrYXSB5hMJ8BxhKmKk1Q5TuRVDqjwVLaAnuqsWzi3hMdzdOyq6Akf53MSv2oHEo0AOXUIMAu4M+0PRdf2/Y78N//2bSLPB1UVNsMDw45sUqwDn1Ozkp8RcIT6wr/lo+WZzgBf3zb9qLFTUghkU0rkMmyh+pV2NLtk/rv6YPaeYSK5I4B5bnPjKQHzAjgkz8pbmqDkvrEjKX0UtpOJfAeAssKIeZs6EK2OofvSEaBEzWhJYOzUbtqjOqI93cUKXhxukpT8mbgHsgLGg8j0FICyeGAeBCI5zhi0Xg2/HMzXApdnFZWdHCjlkUz5jlezasWqh670dzfJpFnjurmadtupLppKMT+Xi1Gq8ET81ANZClLEl/IDC8mZumxqnkO6eh/4nrZG2lvtzBX5/clAlxgiIG3zvsGbDghPATmpuCeuCzxJHKQxhlThbFzgyE4syR2G+nbDYVcDReb2kLIpdeWql0MUOBoNhju1zR0nixxnj9JJjNuqgYwsSALpLDxI70YOgsyc7/WEDPeCOjAtnA+ssRq7/vtIqoEJLcoeXofEsi+E0kIunacUPfmQS8VMKmi5SVcTFfZMT2UhjoaObOJ75rJDBAmNzeIkatqJ3LEdu8Pr88u7j8yew00Ew9BE1+2ab34Ib3sIxfB6Ik6d4Khosy24xAhCSvrKsxfLvExwW7fwu8giyEYbmzGUu6nEZePbw1ItJGfXMraNFIsv+xwj5S2F+CoIsIW+mg20IryI3xqJ93rCQpbF09fTP0wqt8DLrIsD98b+i0cu2ctbv3MIMaZy9wdXP1tuZ7wFAA921kTj2fsRh/tOt62emMVQPQLGcUzAjCnGUMCz34FmQPnMFBh5F7t3kVm//TGVb6i32s570F7gRkDQWrH/MG+Wq5cYOUpe1f3NmCnSdJlNQ0SZZJzs8BbgMPT9iMgfSRDLwsg2KwmH3nG0xV9I247CJ8DKNvoRM/QK0GXts83tcOZashuMkkyBI3qUihGU7kBvv4wybLWA1qVNCExwAY4feCNEJqdbO/e5hJNJsJEwEbLs+Vv0jcCYhHNMvDDA1wj/ksdZDg0StCsxeCvoBaRJYE0J/StFGvpdMF9BL0TABWz8hpEiXLdsdwU8OXj8UhcI+EdF2oQkVqjtKe9wC2V5uXtox+9LHfL5aRXhdbOlxUYTDRkVKBRYAFBRoXA+ZQV7UHHDLJ2kI9QZQtYhOrF3XWJigV3QV9Wr1Z5PppW7aALq0eeTvQx9TudEYmLQhkKcV24Zmr9mht3Hy9vCStorbb3iyCtdQxjP9spMF8gZPsG17ipg/GhAGNMaGqFSqjj4T8Gm9B2Fs4i2qwCc3/gjby8gKHZQWSmFWUoArNmZgHPXGD0BHszwFtJPqWct8VrJgsCEH5FmCX33WhqDWgvtcvA20+tZJ/y5T+LtGcC4MyhlEyEhZRMEqHae/659Pb89vR8HDcDJRqVQHyItMgDNIHVcaEaXC8B+Y9pk4M/Rfew/Iy39xdBVkvUwOdDPt/Dgb/EliSPMvagcqGc+mwBhlaoW1xXb+iUuaBFIElpRQC7yGY+Q4YjPEic4LUwfE+sJlfXjxISROwgJGQ2iYYst3fhWX/slPq0s4wQ9/xThY9snBnuBMnaJ+x7hPibGfV5CBpBpSGbpw+RLiyXR/X+s7wZafo3N4ZHqxWZmf4l+dOLRIcnsM5dzvf7+h9iubxjAGLv+Yv2qMx+ekRN4AMs6OpE2jxAj8oKrPkni8VGkkH+Fg4eDnCkugbRxp5st+spvBNija1lDcs3vLuoPCazHA7yd/NLRRRxgC5ZMCMA7/+Y4GKpZFFINqMP1kS9UoEBtY8sFQHR4rUNCEHiO+IEWnKDPMeIlahMujDBFAwMVO1GyaqGoJxGpMlNGSqGn5wD+1CHcfhezKO0zaBL+HWldnpcau+jXA7vQf2zItrOh8Yti7uphHi69cW2jiSAlF0IBsnydZL/5gFGTP1Arx7Q2PGQt5qPbXnFZTfnHdNhyXJHT7SNDWAiiNAqjvXxUsX3mG38NOioDZ18TGnpVxty5Yxa5s/8lJChAGIdgeZDmKI785wdQl/FoQ73/fBxQI/JU5X8KdTcFmBEhfj/hK0p62pa3xV4DSgnSUusZqJCZ2YmBGt8z/Rat5k1lFdm/6Gvm+U99NoBvpFlREogc8L1Ep74bUG1gkDCqLeLSjo4f3FleS7+h9cIsRs2+/c5D61jHfvHr/hrwYzUHknJc7ao3SZ9tgz88DqBJMBprfr4STnW7AnWIJvk/Ymi+mUJb1vYJ/AvBrvAVKcEEnCb44d+GF2QMPd0r/KAdv6xF1cn1v4niWJ/v7s/JfLr58/12CBposMm140+R3moS0ZLgzk09fbu6uz32BMZI1wxHdgEpsqJGwe0frjyHKIWdqS/KDiPxqHRiDbZpkKAVYa+MxhgDwvs2nCOlZ5D7rNJ71h5oIpx++wsfG3ugYqQor4rOQX7aJnN7dBX1aWXOidWrgM1KnmFnNP83dutmS2Ia9VHudoOnVEVIOSHtSstObR37wFUG4LtnWCMnZL/LvDbUJi3cjviG+3NgLlRNC7ZxnRHdY0zS3qbeoMGp5t/If2O43/ipzn40dkQIfIgIoCN0GNjtyogLFZEDLnKZot5oAsN8TthglzfEYKS1nWApmHWZAtSfJdcvmhZviXq89fv8DaNj+7E6AO4u9hBPYXRQ3MQITgeuUtn2bq5XeQKwX8mWJQXaFJF8QMGFVi6GLIckTwTf5clWVQEKYxwzgG3FB4efdOFiwPR+HVXI1bW3Evn6JxysxoC9WRHBX+Yh6nbdU7TkskoOakln1HzdYDWydcxJJg2rVrtEK1vBs9j3Qk1AstY1AmRkmCcQQPrj8HS12Fm5Dux3fPQaKhhuaAXon0iwoXqzU4ADBgQ5ocIeMqTZp18yAW3FIXQLsIdLN+wIHa/J9tNIQGjwDvJC5iu+Q8OynrDg2qg+SgNi+2W2QEJ/Izl+GCPU2kdWDkKrIsaGoty3eW4BoVdYS0TVsRBaihqk61NLRJzv+K1iqU1jqdlVTWVdU45tRm1/MFYAuSK+RM4UWXMMOcA1CvVlaFSYAin+GsZuQxFHwL+ICFditwMLEHQXrYEPf0CxxC01b91B6NKx84t7El0yzzTMUyX8xKyEmPu4F44A2FdqhADwy2qamwoPJyhDByjMfqEobriouB56O2zLMEI6jgDd9Ot8xbwCPTnjW+p9xTlnkVizAIQlCtOss5XhvQ+MQNYJsHSGzQbEE/wiqj/ti2aQjmkCprfNCchFl35sb9/sBsUlkkmOEewBmZfFag44D6MQdomq+sylm+rF6a2FfCEqxcAtMGN+JlxmsGVp3ZLrFTk/x40FyztlZu7eWRLflaHMGvMfFV+MGZailC0NIi+Szpk7AKIXedVbPaBR0XzNUuMeVhAy8Tu+QaL5P11k272e2C0E6WcQRyQKHEAAED4mGZPUTb4ImWt9xvxKKdExDU0dRe666pGTJa+gSsY9t71AmFhJKsGlIDIy0Ec2ybXpQkC1ixW08oAVmjxyeoCxinJLphykgdaJuLkD0LxUcwE8OL5nPobgOmhf25Tn+hhd+3yPVVp/muUZdyW0/owVZTudPbc9PCvxuLSMVLN9z4O1GlYOYlC6Dz16hofavZM1lrmfCPKxNx0swCFZGQYrPJ+sqZkVLzSdi1X2NgFWN6apQ1JbzH1ga6bzTbpIKnFALS9BahjIcZv8UC3ULdfTV+KnjJ17G1V1ZsecD4dAFUgFHjjj/hm+xL4dH85kx5GFPatLfOI6ZAaQjm7r2uNVx8Of0JlAYXFusUQ8cqMbPm8WR/Mtk78D+AxnV85LKD/Q/sIztyj9wP3oeD4z1/MpgeHE095NRo9ZGwd2FMKfN2M6DQ5f/g6trwcG8yYIcH7Ojo8Hji7U/2vUPX3Z+6e5O9wcHxARTxPnp708P+4Mg99o9d/3DgudOjj4Ojj97g+PDYRA3KSRdxDPjgvSMHqUmYgTWSom8XlX3siPzInoMMVgiG7sJa8uQSo4jThoBTExAs3TioSH2lGBSuoqga/bvBsYpkNS/Zc8aLFaHqZX6R8ZR7K2pCarGma7473BfDSBi5kdW3CX5baX5zED0wUba2v9xG/rRrEpV0cehdWYq2EaSDQWw6a0SBLa0HxMt0hV62FlyFovNjDio0kIOzZPcsDLwYmfrMSUIGHqCZDSqoxs6j8OKhCuwqUoVN+3Mdq64kkb9t4r54cA9DqS0kyDiPg6srpBOSrVPFnhbwplccFahKBTblhGPXU2MBSjwyb4jXFQO9flnMoMPuJADTLWCApRfzVmAUSOjz1a/malyEI2sKaGBobPj+Uoag9aQXR3F7XT9H5rXrPQLqX1tYH6MILSNXT7n+lwpeL3CmL85qJ7FQYWSeBdPpxVlT1/SSBPQTxlrf46OnfgmjS7X+7t0LCKP7Hjp1kdpdkAlJ2iPy62nMlXduVY/7W1KP5bgH9YVOBee+A4MWCDuYsXSZghVmvjZCDaCWlE+xajtW+TUu4s6JCj+LX71nrbMGKy7yAxUA/SOeeLikkw7X/GjU2vhnwaPKoi9l7ozcbWU+h65P/rGLh3zA1Iln0XKOB6XWsTksW9lV5XCKhfDIQJko7xkuUwoW3t8rF0fH6yIVm7B0SOAGuhSAzS7PFnExVHobTQDJT8z/ScGGD/t7uciAgYGRAXQInGE9HsSxEr3CWlx48wom9LpygczdWFe8vDmtMxNmPwphIDMHkSQlni5QkqfAYw1dhtp+gPjYduKEZlMPlVxo5u5DNGe736IEDNPqgZ1d3pB2YkbA6qm+iA40omvEnUs4xSF+fKal+Yyrstg9sNJgDSMxgK7+POJhr1DNNkzOfr4ATjsSnGohfvI2zAiU0OcCHkuER14vdEH/gJvnmk8dbFVxNkYqRvAgdnf3j4+PDo+me13mTrzuvj/d77rsw6A7OTr22MGR//Gg7/OjfnyYShMyzMHHuqMw6LygBnh54XmPo683n7HSQ5bF6XB3F7B3z36Pel60mEXzSS+NvMCd7YrH6hT27oPM3GrD0cRgPdpqL016fuTrJj9XZnrHh5O+d+gfHQ3Y0cGBP/AHB0f9/gd4xY77bPrhGET7Httbd5wEmsIQMlcdy8Fd9TR2+a5HzfkxXLcJOddyjOSnz4ACpi4I1V4KU75a5RPJLdiUJjNfOxg0AIvfMrRFOwSSRZ/3E84F/M0PWM1EVD4/wZPDjd0lrqYiFeWhTRuO44koSxw4nbMkjgRP/Ect1kydtDGEQXu0NA5BUGhZ1YNRAU7DWk4jP29gNA2w8yCKnNuSh12Zu714WQRlmfEiAbuYCZvfHArFnB6dIMQDB4CUasXaLqjyDrp8GwapCnG/MHl5akdJe1PaWWQcWOngsYjq3S0XrfR31Sq5ewRpWpKWcjuYBx4wZ+o+chFvVQpbBqhHwf0iWqS2OHsnHIzcKPaFYaCFZKI3kM725CwUd5jEq5bubaaGua85CONFtt7ZTFAK7r1WnQ96uEc8Hfg7LFsvw51JEw+T1kRGogTATXCstlfdFQCIVCJQSKz3Z1AQnS3LjLBOFRi5OXmDNkXxpOZaaC9CWtGZxYJ8aAgMEmLD2/cmg4ODfnfKfLe73z/+2D322X7XPzoeuIcHHyeHh3vmG87f4c7tN9CGbtiUASl6THRNHh4RGtQtoxMk5T4mqdbFJOUhS+OtzwFK2SVRrPNYjOvGr/AvbpTMyN0v323fAJfXQ53P3izCEM8Mcf7N7Y1bKicHz4dZJ0fEiVQRbsZdRBdn5vDVIxjlbUtTjvubxquxoQ4+NlNaIjRbPE69nt7qlBAxVVbt5DfiNqeKs3yUav4FaxlJeTTWtfoRFYKxrTYNLIx89t3WEAKjAIicDpp1Ctf30S0iiUA8Irjjvd5e/6B3CP98OAYCRyE8w005Cu/iAQ7xgrS7tVoLTLmvn1nGnVX49ciWehdNpC2T2BRbEk5uiOjI9WqYxJnN1bZLvbCLxAFBY1/YPEqW1zhA2kSEKQ3SR/35+uJMPXbGDRTZ5AreuLNBR/TzkM4kWmBATeutWxul4Aykk5oYn3WiBIVmnSyBOkXJOPxLezBkhmJ7Mnr7+w26nrKbI2VRhVWKlSMGmQeB8IhiGdC6eiU6Ufdd1uETty8yHtRTtL0q7KPTWPdtfBVabBd9+TgC1SwXD7jRLfVwvt5q2NpbWv9u0yyR8MoJAabqJcGE1c3Ja3pinnLGBzMoLdR0+I/QAP5nfNg/7M+N9tHhf+n8g2yu1/QvmE7/Ut9wYXUJ2UNjAtP3j/C9eFLGzas7RXuSDVzB7Hb9ZNlNFqEtTEuKvF+7m82VaKnrarYFqcmdTmMQhlTW120+/msYkG7l9uBtBFV+7wVR1aFQi/2NgiLf+i55p/AkJE/71ENdCEz/tuk416e/fb46PXMciu6Lk7a+Hc9Xcod8TrxGMYTRcX76enqjVc49aLQn6twv3KRqSmrz1hBiXt1mr250F6KZ0YLaNpg5qwt3Q3FG+y5gZ4qUBxapRbnrUgUQCeFHYZ4OnU8ADAwRHaIIPJZiSdgznjw2NEe1ZdySL/38OaiLtkbRU95iLfoVKzG6HaT8ylvOlJuPmrUqUfki0wZfexY3N8vRkCgB2TMdS7x3NIrGFFvcUUzbx7lyLyIi/1hQ+pCyI71sbuMhCnTyS797p5pBpNA9Wb5gyZeB1m4nii1+mfSjhKRxbR1CXF4JfTiA9trw7dqteN71DkWZKgxIvMFQ+HcUht0BCER+Tmrc3BNVd4SHi0iqxrBE2NqO1FRS8gmPBhetmhfzHlTOmLIjxDEZsfWmjTQoal2KG5yOq3HzFLnhsl0jQfQAqU1SiTR7Kg21iGzW7TDnk4BKCXls0YzZlSr4LtpnlUOI3A2JR3qSAM+6u4ssmufrQeQ/dHygFGRSUezwg9QOP8yyRYoYnltCd5WO63aDMBuRFum9IWdFLVCuoamNayCS6wi+L+0XUwyLnKgvq9XboY+kE16184gomgd/MhlQQqaf2Mwcr77DQDiNi13nLaBpG03fC9O0U1LuGI+dxMhb2xTydktwJR+K2HJRqWn07T67//dnF9mem5eyjYiKdQrWd+D6mzn5Gva/gU8Av6wmiUoYbtE7ujmMJ94SnjfSIerOBea/RCZW3NiUaaj1l1C1HkWlMz5i/8PBo7lApo6yPVRmENB1HDzsGSW0l4umyttRVbZeKd+l3KIpw3vlwrKM0ia6rUUZfUd/p35cjczyaiMbeO2ovMuIMOWG2T+XrP696F8V8KhwbHB+GUKfGA0/1G+1WGKD482qh67/6XqanDbrFbvDnaalB0o8xQbiTKVKRYHlFkZh7AaJYFSks2w6yuTNTbGpS6d3kV5feWrpDeLkZLtVveXqHpXiYxBav9UY5kz+Zhh2vqPRoC8Ijz7PsCu1hXrfvgaZ9scV6ILeIE9k0FkdAVdoDfWe5+/Lg2SgI/chmf9+ZPbJK2S0TKjjFFICBqmTsthNXLGTDqth5otlIBgbbWK/Wg6tpVR5yEvPg0kM21bWlzC+hO1VML3W7cFpdled2aUalDkat+YpVbma2/887Zc5xrN1PJWs+enqy/Xn87tzDOfGJDH1kdIqplkehnvRoCoXggzBrkR+8EFYwgQe8t16zbYdjuTvcTVwpSo6BVewtU7c8uGoHVLzduF5jPmYIVcO7LR+XAMcF09+U19mIMauGuOH7YIsQH7xAx0BBD6diNnm3DqgsGyfPeOhOJISdJSwjJEGHPAqNwrZ6yu+vHsnHy21BWfeAgQ6UKwHA/2zfTUnjWVrl/nWOus6n4R+QE5Jf6kfrJP6f0kKvQGz6xC7Wbcvck3lXnd4Ij/gl3oeP/9fZMrkITp/hxVT1hxLx2gQsBPNAzqkugR8MF8kCQKVKsGgJcm7yBPEkymLQP811p8YnL1Bjtj/IUX+Q4r8/ylFylx3M4sjRXXN0q6e+ImseBg1ztqoPGn6m9wwk24BIpk1KZv/vjYxbPC7tqlloebkWDxH1M/PEf1dA82zWCvitQu0q065fNceaLSsuXtkBvT8iNff1mhhN0ZPu031bfMaJ9zf3IO4jpmOJFN8C82q00FluJz8Xuldjiong6QT2KoNd1FHg6Ly0SCpOgjNgRQHu4ZZlOt9F9OyallGcRbgNgaKemw+SpSLRWz5gProUBxD9UQtnYsubnhXcqSZ3SvTSrOkPkJ611Q+Qtx+h9JdLmesPGwe3tH1LyrZc1cMHT7tUloEDPqUpcQuuLiAoVyE02pXcX1LuhPHMBMxbYrzrWkiMgsDAZq1tMuocKC7x7emUTbpBze1Chdh21QIN5AckxgqscQQDkg8F0+geEY37SDYYjb7NAvORE7qtafAgvApkhHiPEGsyHAg9v0qMeAifZ3I2WilD4ssmJW/Vq73KiQVxI1HSsJzQvcT+PZofCIQuSZk4k2pAdGzbusJvE6EL9DGL7syO48pX/fmj36QtDGTl12TBoj0YFve+GbSY5cnL+iKw40n9HIDHAq4FV2g3+YJ/SOqQS+x3/Rql+cipjfqM18yyLc9gqGezfyTLFzJMIxChns29eVkmZVJ46+Lt63xdzXnFKo+Tk4alH0O561hTWNvLNVpeix2uc2HiNclyAMWPCsJBd6YLytz3RDXnVi0Np7js9Ycm7OaD7pYdSdPrLpzLFYlqshac7TEWnNmfTP+6UwMoZswWsU0GUN2u3KEaxfDDVOREKlwcs8ytz3jV1NS7orzsLGlO5/VlUrYfZBSJv3KscHGaaeBVBEijDi+rLDMCX8j7/vjC4Y/cHvbonggJ3rkfF2W1+mPs0kZR1XsUzxzQ83IA/aNwP3uIuTHt8gsiheTGRk9jkoqoXZ9hCVXuEJu0w1yppLNdLgZ7JZwWMgbpi22Dkfb9+cGK4vYlupC3EN8F/sR89Zj7kkC7gZITYhlUUxuL0ipOjL30Dc4hP9ka4wAc4nhXMoQlYbxivnWxkr0UTqEoKElZ6ddnMEarqMHFEKJTr3WJtQKO7+i40R8UokYODblLXPiUiMhW2U2ZK0xUbDTY6Q5tDuFLMmWmnnslITWMPf1gywOZA5kCrLYfllVlhpFEVoUtw8IHbXN3Wwew+L3FqR05RnTitqYYAbqpHxzvaJq1sVNnFSyV4mGNdULOTk4znndF5U0bcgzsW8HBHONNEEYN7IakvBc/UB8dXo616mnJ0qFqzgNzw7IkwKeiOkYEayxzYsU9X7oqsxdI0u/GkXjEw1MQy9fapOvFEjtNe3n2B13ViUilCk/7fwqTXOIJDsq8MVxjcNLnZct3nlZ6GZFu7BMbfWYw1ctn+I6wcS0dl0GSBxu15sFeSrIE7mnatdEtQp3XjGpeSmneSWleZ7RvJoGssRgKunWmxNEWlr68uIhxTx5uf6+mL6cEkra8sINoUZbcoJLshMEJUNDMUpSGwRknkJMXYhRetELQHl75tdcdMY1N2vkEJFn6Q2suVgjL2XV3a4h7Fp1iAklV17Ftot51xsDl4mR1G3IF4FJM3comm0kljVu4aL3d82aKfg2/14/e825svzaEm0G+LCbLs8ooa5oyA7bud6HcShFK0Ld/9cqZ81ENgaimwJWOkMRUC54G4akt9Fdwb9a0rgVz1Vg6POgUxN5+s012TfVqZR92oqjXAxm9xP3kNBoOjhLT13QKnHDZLh9EkA5y+9NPJFRlw1wbeMo1zrCyukG92GUvK55k9fxNzTNtyDT9AGVzPxu4LrDOLrzIc1a9TnkEwzbgs89+slPCJ7g8VC7nF2El+3xK1SmBpApGmXQevvZQgCnYA7dh1wzxS1TrUBGBTCAhX/OeoGPySW5r8+kNiiy6LnHJXjaUXtfenIxhIK8PQH5xvDeUexojygLz9gQDXeEB0WeJmqMNdl45dbay7boh3bHVsMVWzrbwhqK+/FMwJX281N1UtqolOBjS10LsKkJmUa4GXyds1gNRe28FRyh3Gu6FkKivNqVgwmbBEQVOWsFgyW30+if1V/M/6lpcYKd1i7CjYeyPn2+MLLEDVNcdDWMEzgGKBzRN97uGjZXmTjJd6wmnodg0XNNCpPg61Rpf9xLY1g0bRAz1qAzGoyLlwWcbI8mhG1NKkfV1P0eduONKBIB/HIdcX9IPv7NN4XQRSFb3RPyXt0S0nlPd4M0XPqx5s6PwtlaGLo9KjkClJOdu4fXOthfYwM2++DfYBFKYFWbZg3ARhtxre+/tW3O3Jurqzszd21uyJnrkUhtKHL16+X5Ddi9nP7X5ui9OL00rck6YCqXr1DAGwui4VrM08uVocYKdIZRLw8vGpP66uUkr6grLC560YvzlVXuRtlhpfmwSimEqz6ATZDyBHzbgYIRwJrF9WRalM7G2vpOmqrm2vqrSXbnbhDWnFChANp1hfglyNuqCaQNc+urVSOauKZiEUyrcqSztNOInXHAQuapmvHS8TTPUYyxRgAUiqTqshnO4EubjrRfWmiRb56KTS9dANdsg6IFzSPcrL2azwR2oyJxGdFGXsUHYIoONQ6cqzVi6HnyVHmVJtdDXjtgoSutH+zgVYNt6r/sqLwZl4l7P90p3hEk79ElcnntKKQV/M8YhrCPKS12aa9bMAc8VaD0oLcOpbzryrcbKv3n4EbFTfuLFA1JPraaOrxZ0vHkBjHmi2XZDzx18Gno/wBS4HMwB71p3VYxIQZM7XQGbNm5/zOIncki9GdMLUYeAKihrLokhVaDtUlKHe5bwhuV3ypIQGvyLOcuN15kbfpKNLPIZ8E3N/jegNiVAKGO23yVcxxYB2aE9AwLox6xLm9rRLDGPUo61K5h26SL4rB6PsPlimlz2nyEvcnhPr70WRur8xMIGDzJPwMhAhOkjS0+71tmP+eXpnGIi1C2yYSyWxvZUXL8EYSC449DE37CbY4KkprM50dirqhya3c5AWxuaKNV2xZ4FTMz5pYtVgCjlntjbAThJom73BaReV08ZvR/7cGmWzjbV7c8c8H51Y9canaG5UkUECsBwyg4Z4rasyx2AGmPwcSZ4VKSXILvX+ImVa7ANCyKKM+OgHla4sze2dkR34KIDDcL1IoWqhY+jNzJb+dAnBDx0EcS0rAgYZztvrUhTSvwhWkSIanzD45K/qk2YWl0spef5dvT6wvry+n/cX4AZXIzCJlHVkD5hNpA8upqqnGarKbqIBzdR1VHLi4nvxjFusWfwOmSn4BzZxwOYg0Wh6yG2m8LjQoePyL1ObQpT0RCkrTApLDsbn4lS1cWqmNTJyx8YjPQbGsg8N7XMreaobQlJCuVY3Lw9DaJg+7DYmJa9zhGuzjktl4Ahv8nA3LJ2pjhVNxgg+cLEh+jEPXPIcu6jyA1/GjO77hJoQT0jAuWWz5bQNk8VK3fovXikO6X8Cgj/rsz5NlAyCsqlgj/XraXadESuPf2QBrwao+Bzh2bLUEmtugAWPpuHNg6qbZlSlMR9kfXHYvjYz2HLoVw2pwqYTjlW1zkXFIYruwv8Bu1EXvC2Qq/SBaNqv+2t99/N+jv7bcoBunFvDn/31/Pb++cL+d3P1+dmUPz+uoW7EPz+vTuZ+fi8screLX7NNiVE4DSCmrc/Obc3t2gs21o4u1PV5d355d3zt1v1+fcGy8jMXeF7frz3d21c/oVGrm5+LfTOzBZodwPwEdZkqufeeIXXv7i7PzL9RVA/vSb87/Of9Oullq1eHimJW82iYOe1OnaL+/eTcgpILv1+fzyp7ufzSF6axQqOpb5Lb0PehQYaA6DqPcDyoaLK/55pZAnAkHtvX6fXKd5LAkSE3Bzea+sPTEN8z3Wfo+/3rUlH3o/6Kpmu4NOC/PivK7Hsok1vVZFVp2WSIJT5EsYjQaWFR8U68lh7Q8+tNbc+SPBGjJeADizREwZEWBPhKUNWTW3Do5eCKFyJAnnLY7kI+q9klL67iGfnE4LpNCr4zjBwOEyjBx3dCHQhqhJCxiZDdMTAaTwKUiAS5vXvwEZX57BVP16c3F3/sNvd+efrs6Q8Ad6NqKGS3MKMZalYMnKuSqND+cloaOd0QbMUgIAMNpl1iQ64upwC8IROSyVWi2M7P8HGZKJXUGWAAA=\"\n }\n },\n \"native_execution\": false,\n \"case_evidence\": {\n \"artifact-comparison.json\": {\n \"sha256\": \"a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 2697,\n \"content_base64\": \"H4sIAAAAAAACA8VWSa/bRgy+51cIPsf27EtyTQsUKIoeCvRQBAJnhuMIbyy5GtkvTpD/Xsrbc4IsTXqoAcMWhyI/8uMy7581zSJAxbbbwgbbOPS527RdWrxoFvUNCG1eZKkSCi6tjyprhsCk4RF4csKb6NCK7J1X3kMSzjFuguOC0bMFVNb6xfPZSYQ+dQkmbMsQoXzFnxEiYIwSQ/I2BmOCQRulyVKH7CUHmzAmr4wKRjvNuZCRx+ii9y5ZHT71N+Kmq9N4bFO3wTqRp35fyvNb5AWOONZ2N2LF8YAzFH867fHxckgicRJhzhin7kBW9/3Ube8iQHqmwEh1Gvd40h7h8XoejhPWj7QylHpWI7cdlO4dTN3Qt/0w4ZyNX3ED8djMEBt8uytd7KZybK7KmBrc7qbj+mRnPYfUJMywL1N92dyib14N8QHHZYUDNmcszbDtptrg3/vuAIUANQ/98Njf3l41P72FODWxDJW8XMRNP4zbG8pm6MvxZXNJQpM7LKk2+z6+gX6DaXXmoEDAUtuAeRjnmN6TkMTDuFkNO+wJzgRdT/ldnaphNce6urF0rQebdTQxCB99tgyYMCGjZFR3XiphVUrReGG4sSZmk5Q12fGUpKAyTNzKE5avuR3x0FUKavYpUQXyRSYQAVw0SiodtdFUyUEDVVkMgRt3NVqHSHSs4rAvwzaQqYJzEDWSp9neL7/9/Gr55+9Lxphf/sHUercfd5TX5XDAkYpreeBfMFWH/RhxtRuI+ePqnIvZok828oCKOoN7n7jCzAAQks5Oe82z0oERSlJzjIHNCb0Q9LbUcyuJBXn7cE8P5AnHH2fHSOpNxUR0gdo0KgKXOBAdQtJvzp5zk7QQijtrldBBabCMUyKZcxK+ix0fVAhCE7faeAeolUSLDhwQCO1FCjxrl+MPsqPX70oXlk4zz5fjN4iZgeLb6Y6ZqLVCI8ApoJnJYgCukzIelDaOKpNFxbyQqB0LzCHNKqNRCMtEoIFp0/9WCLnraSJferfNXcH6VA+UkTXsHtYprLu+TlDKaUSeT+cZeu3uvy6Sk8GCl2jmjxLs6eHzfxdGOokgHTNCEaWJRj0GRBcDtXpwGMFIcAjaG0qxpT0gbDJUZ8E7GvtscTH1+mpzca3r/4Yra8+sEdwbyxkKHRnSGBIJmFEiQnKgEzFNmfdELMysgyG+VeKky/gN1+n3w4XifR3Xc2Lp+44IXvGVXInvzaqX30QPHEVkMsWEKQC1DDPAGMHOwVhgyAKnVSq9M84wjCzqiIbWraNdr1DbH8rqv8BF+90Fn3JUGoPNIKizOcsZiGahrbPSeZZQy+BYRk9TXFtpg/LM2Kiz+CSrt0pO3Uj7eaBlv8UJaAECLeDtDsZTzd4W85MaNVk/tfNoo3PJ5WePb9v0szo3TwnLBDOJZyw1DNt2B/HhfNG586PUlxTuPd1rzev2yf65huZZdUfE4lRDy5HdqLhKLjX4+qnf6bLQVvJ1AlRo2lLTk+N+M/c9/0Tn6SZFvV+R9lY3He9fYOeEhLb2sKtvhqmtsMX7fPdwujM9DuNDGSBdR831FvTsw7N/AFhkaueJCgAA\"\n },\n \"candidate-deployment.json\": {\n \"sha256\": \"487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 8268,\n \"content_base64\": \"H4sIAAAAAAACA+1ZW1PbOBR+51cwfsbEMeTGvizDrcxCmgHanZ0dpqPIx4lAtrySHJp2+O+V5IT4ItspvWwfygtYOjqX71wlPu/s7jooIe+BC8Ji50h/JaKz6Dp7euuRxIFePIWEsmUEsczWI5AoQBKpvc/qWzOJYyaRVEzEy6Je5jOGg339K+HsYZ+wjuQIK74zlxjWJA4ZjyBwA8BEK3FkNNhIPKpQdCorRisjsLK1j1lKWTTdFwwTRDsoiIjIGcvZAoI/di/H56fu3xPX87zBLk6lWuWOYfqc8XZiFIFVYSdHIBKEm6nSzGzUGw6h6yF35AW+ezgcHrijQQ/cfhAMp8MQMBr5Wr6R7ogE8AZrpfOMgxCngAJKYrgFzOJA4973vJUYrvAjGOnF7svSwmjyhgjJ+PKKRETq7fURARSw2sn7L0ISz6/QFGjerSZokv3HdAo8BglC+7Uen9Wp5wKcQsWBhNkyL00uE8PiBjAHtVt0gIQooXo1r185EBuC8QfFR05FI4FW0bLihVmUsFin1FHmG3hy9pqPtEK8BjmnUTF2MsZK/YilsbwFviAYjjHWX3fsEbTVIaICcgap4JJIhRnXRv2b0zBvoKKDeFEiqBIVMklj+uGvs39OjidnHy5vb9+d3RQQWJEvEE0N/VzKRBx1Oo+45DSndOh57yuVOHk7vju+HJ/d6L/OLy8a1VCei0MyS7mJsA5X0JEI9h9E3g9Fb2Q/90WuDonQzHBU3pzBAyuH4uqzWu7+FHPk9/pHYU9VkfBw1J92Q78/CLwBDvAo7PneEPqhH/qH3XBwOB0cjLB/MPIPp97BwPMVl+4B6qklx6bQJKV0wlTxWBp4wjGTE1Vt1rU/R07JAmJVhyacTaEU8IYgRISmHO7m6vycUV34DsrIGq9egLSc16UOybnBfA6Iyvknm2MSxuU6PGz7As8h8/Wbu7tJa6w4CXDCgk1RXRfIPMsUY2V43rBuhUhHBUvlhpG/0yC4tb8U7BXbZdpL6k4yjIbe0Nurz4U6BFURlAwzqmnuTiavSTeLJt0GTZ4Yf6QMBa/RpjHnVF8JyO+g/V5Bq1ixlCu2NiSpnjCEHSOcpNq4nudFNvMjiNSIoilUnbsm7fZz+C8F0SatRVb/0CKq0X4BOOVELk9UeMNHWzw4iFL2NOFkQSjM4ExgRE3jqHbatbIoQVNCiSRQY07AWWIpAGbv+OrKqazfb4EfCt7GdHnDmDxXmoqlUPOWEiJ5Cs0QSMRlmnxNPnX93wlVg6YEHpHYBMi14q378dr4ABad3LZL2cxpP/3SybVXy/QLRtMIrvX0t2VHMXPjRiU9ejeUcbP/im5RlKImrE5bVyyIXY1k3y5ZRkmTHL39zTIK02STtFbCdQ7bsvY1mi0QN9gLfR2TouqDTpJFV5PSqvypm9IvoS1KAyKblK0l+Pm6bi5+TQpvqFyUkJ+h+XowcznMiLrDNzp/K+Kfj+1qVrCH5Vp3QaTUr1SNxC2679T14M1Obj51glhsyvUJTbXUc8JFPiT1xKUb7obwmD6hZT5KTKsLUpV243UZhhClVLovGwXqxhHGCcUFZ2liOqDnFfrbeu9kjuJ8p3kb6ynimgjzelR81FCeOW7gaLbHzDBYgVrdfyeAW08rU/SrihpFQt3syrPE5n0p6w+nGSyO1THPBYjy7yTW95oSzRr5Kl2uRV9whGFSmjMOeznirD+3vLsUmm35rqgfdlUcxfK9YXVCEYlsUxrWG+O6K6hbbeT54C4kaflVKErk8pRwm1AlgqRaHec6m8GrIxb5BOuHSqdbuQnU3aEtA8Avp2NpeGjSL+v81yixKbhK7msWaLb+qDpb174suCuc3D72/e5wMAy8Xnc77YvDyJZ2NA4F+l7/AFhCsIWVw2HVDS8X0eq96LPtpiSsr5820t1sNIA4e9sPKXx0USrnlpaQxdPHhGTIbPL64OVtvvZ6I40KFprnnbaV+1flZtPc42T983W+IOru+BWeeITlxn4bSG0QVeCoRoexpr7AZRB8E4qNw9ivHd22gP2+8bq3ndZNla41tur5FuMMo33MZU3yFixcUdYQPlvX72tQy4eJy9V849Zz/1EJ/8MaSXXS36qLNB7bJufapvT/oYphqhqFmrUzwY3VrEj6iqrGEp2ziNa+JbaVvSp+9XG1U4691T909X+pd553vgCDB8bnTCAAAA==\"\n },\n \"candidate-runtime.json\": {\n \"sha256\": \"d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 5291,\n \"content_base64\": \"H4sIAAAAAAACA61Yy47cyBG8+ysEnd2Nej98sq+GARtYAz4SWVlZEj39WrJb2tHC/+4ostnTs6td+GBBAkZFMiorHxFR8/PH21g//umDVkrpP374+Ond/y7nw8ivw/yZjA9Y/5hrZF3EcQk656qdNEUkVH1LPnvdnC+KueC1pBTFViUbg6+tD6EE8/EN9Hiu0iHVOTj3tL7Go7BAl8vQxoO87f/zx2EYT+N1GPaX1+Xj1qSIcqmQT1YitrAtKEnNuZy9quxbdZkai7E52aQbaWdFJFknOvd9h+FI4+kBaQveCLGRqS42bqpUZY2kkF1WznkVo6faYrAUJLQsLqXEbFms1GzKxzXy6fyFDgMfRjld78g+sFG6VrI+cXFFmtakfcV+upSQfDBckTCxsThKSZWmkT4fSZJBxt8hf75eL3fc2ogYB5OaFKmQiGKixCmWpGrJuSDASKmxjSGKFM41mKApJ+BbuySfbnXc4lQBIQZ8YJ3XiKRwJIc1U1sI1WebvXGsowo+lRSRl1wMuUi6aMXVdjym0/k0Mh3Gb7IdX8dSOJSkmZPNwYaSYwpWSpVWLcK20Vetmsvao6y5NKMTKpGzQbr9Ans+XVEsmTbM5lQ11mgR27wJ0RqblMafINFpNIRBaqKYpHVuaOtWmFHHWoxK+Llj1nG+nOfxOp5PG2qtVWc2zrmWJHAuReEb5ZTz3LQxkZAa7wRj0lQsaLmInX1pSaXcUkeVL2OVE2+H1yELUkfsrTRvHYVakvUFZ0YhjI6R0csFgEU5oyzazDgjRvuMghndIXvJh+tEJ8Q7bcWSSlkplDIhXeJRhpo0UhdIAFV1ZWqZUH+qtk+tV7aqoIqEEKzNtADfyvB1Gq9SiF/uuDmVzERJZXQto0hJuKFbsBkpYsnJ4SgxII1euVZVouDwo/bB60xLE4wo1suWAYrWounxQcmCmIvhZNBLSlMfyZQI/a4q8oFCGa0sDlS59zUTEpLeAIdtCLZyGRzeBoy1Y8+h+YBMNN1q5hiRDzI6gJ9aSiidZIt6gQGsMGF2OT6FOlQ5jF9ker0juxi1NyA0dKvxFZ+yRUcocJtnW6N25H2Ovo+nJkTLyHnDSSv1n+oTMnJ8B8WHVCvoQseswSEJ3V1UdCpIczV6dCjYjExMjQr4gorFPKuAORT8e07ELNOX8dFjGXk1yVM0njGZ0UcthkJmKd5j1gPGj8kJ26yDioRzGTS1z1WQE70AH8Ym/MqHDVMMMoc5N0k8WMqklLliIqiJYomAc1hu1mvvrXLMsWI1BXYOL8t7zIHPxyOd6nwHj6BsBsfUGqVZW1lxq9GwRmcKIfGpgr1BtE0I3RFSNRHN4QPlohDSe/BnRnQle2d09Q5d3pQDz6rimgMbZFbaumxRJopRGWOpOJ8iONfrvoYjvAeeMMvy9Q5dQg0Z08PoMiumOY2R0gongSBqSCCIogVGOUlplBXlxUyCbxODVE1+D/2+gKazv0nKQFcDEi6IprhUI9q/609kYAWNqa2U0DitGZMsZRzXo+zqPfZt3LrYJUHFM2rOLkVBGNVVcEO0itANEEYIZ+3MBm6srTUU0RL+FEiPWubjKMfzHS7kqDzisWilUAXlcpaM6yWj5kX7HnincvboLWohawiDCx7ik4pfknseK29wxTiMS/YWAoKTa6RJoytiRNVRMSRTZ5BaTZhkHbgLDRpjwcVf/2YdtjH4f3iUSWYINz2LgsOZmCtYG4PJTuPoRvWJqkVEgfYLAKXgPwRB7oqGdnIevYwZ9mqFPX+RE73JQhVlIImYTYMpK5QRco0OY4Bmr4p8jS3pLkHiTITMtATKAnUaZKqlpTST8Hl6zBTYBJXWXqcMFkDiQ9LOkVUxVeVjQK5tF+yM3KEdUBUDgkD/hkYJ/L4i/niT+fpQWai+aIK4kW+olkPJxTkF+kM9U08EeD3q4iHTMBeGQCoJ5xdwmLWUVsynIaoOJUQVOgsHTzlHeIGCWIS7UwEn+yJpTURXXw8/AEOgesghJVkAb6freHyYC+uzA15FBB6+AbwCu5NjDg0yAhpJHv5HZcigquJqcKgQSJUrONDy0kXz9akywSBFJQZoIhjTQN9BdNHAQzR4TCuAhpPDGIGRcjQ1QwnYQYBhAgPc4Ap4nh7kjDGBPSrCcGRo5IwqqAgcAkNrNiB85WtzhBmE0JhmI4Js2iIP0M3FADwmujWFfFeOaPOuvCV15UxdiKPqrQc6qEg8obiYdsWQqOCYwJwQbV5I6KtsglQW8UY1SInTSQiuEAUOsBYFKXDoHBw3wkeCKxtEHhzdKkgUzRrRlwvaeXo5nKlukFAAKa0zWNLJoGlKgZFrtdTQlMAJeqAGcGXGUUAUsGoaglpz71rIxn8Wr/syXOBI6JPMi/kH+PXz+bRD6atc5oUylQnwZnqPC4ta2ZUOFzjEXaFZDvR6vl0XV7+Pe7Ob9Hdf2FW60m+89SKv60b7sJvU04NJDgKE9TPj9np7fHnZXc/nw7w+UY/vym1+LeefFje4t3GvdpPVTw92ZTzNn3/9mGnHMl3HBjt9lbdTB71t+Ys3dgX5Och3XvxUy3HdwGxR4Xy363iYd4exzOuzsLe7abkVvEzF7+C52/oAIZnH8tv7xjyShkWc/5cnx2r5Zu4Y+7Sbwn0VfmCQaVqfuLh3bx/w9Hq5nu0K5ftH25PWxm31aVs4osMjyu3VE9+mWeavy0Dvw3BZ8uH1U2Cn+bCWV/UKmvvqhU5y+L3P5vnwvdiu43Thewn3/i26Zf0pk+8e3tYrL2Jwpqd+W4cRvZ6XCxneN/fgjrf5fs6lIx9ru6WKv3xyT8AOXH4cT+28NP1vHWuCRh7Q2ouMYMstlJl7Nh6R5235xwOuDI8+wJHso4BIz/3u++t+vn7bxq3vz/cvfvr2QEJG+97L8jcsbhsv1d6IAYRzqE+/mcB9NkACYEVcDiYWihoKiasFFhJI0QhkKsDmwme71DWvwj44bYpEpYzcR3fo8/78C4f7tFf5Ioc/H8b5Ou/XJWTq9tP+PH3a4TIHOXNqP820v9zKne6VgTnAlZuadjnBiuYCk1kLdFZDJlLo0VVTodi4WUqAPwf5NtyyYc+eiOb3tsaVVzMk5XlrjesQrgLdEUJZDA6CKx9k0FrjoJENroBhKzRELKLiEVYuWDYlCoCU4v9xa8TrRXx+3trA7zrYDyiqghkAnM8BN1EEA6+Ju5TGtQzZ1hE6AtsIn4brKe6FMPq4HraluivJ4yp2kVO/Qo939u/9Ox2lIioeZxizdTQwuT3if7z+9V//XKdI+/1KnAuFfJro8nn1CKoP+fLkziF4d124vPKFMCnTnboWSaPxChv44HGzRNcbcrh7jwETugWyNOjWsf2ic+m/ufreC3j+7alxhRk34tpgeaX0XztlqL1q/dobDFQy2gSZFI8kqQYfD28U+y+IMixAtzrrr0WO3bH2rbAlw3bMUM4ldNf5gQ5r6oY1emm3mRaysI80Df/+eh22Iw/z8fzSeeA63aTbdZRiXnnn7/jxhx/+9mEhvg/Gf/jL7dOHPsgL3ci1G4GBmIEindUaNuoQfeY7+Qwd692TborHKtNwnga+wTMd8XN/fbg9QfznD/8Fe/Hz7qsUAAA=\"\n },\n \"change_guard.py\": {\n \"sha256\": \"b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 3153,\n \"content_base64\": \"H4sIAAAAAAACA6VX30/jOBB+71/h4yVBKqVNf8IJ6XZhV9qHRas7jheEKseetAbHtmwHUa3432+cOG1pC8fd9iWOMx5/M9/MZ/fo6OhPoPxEK7ki8EyZJ1dgpF6VoDyhihPKS+Gc0IrkulKcWgHud6I0KStPfZinlV9qK/yqd3R01BGl0dYTps2qHS+pW0qRt68PTqtO5/Onv76QC5IU2i7gQfeYrqQu857TTFB5Gl9PhUKDEvgJByYCjD/ckmbjyfl0MmTDyaifsVkOU8ZGbJDzAZ0MJtkQn0VxNhhM+DjLRoPZdDrKxvloTKf9wYyx/mw2pEnn8tP11berTze/BKMYj2ezYnQ2yQdFNpny/pRxdlaMs/4MJkVW4PbFdJRPh2csG55lo7w/nPYz9DIY0jFOJZ1Oh0NBOBhQHBRbzY3VD8BCalOmVSEWJTVdYp7Y8XmH4E8UZPOhtwCfJo9C8eSY/IaBXNZfvlOTEG3Jjp0oA2u5BDQWDln05MZWULttf9ur7pISPOXU0+T+LlG0BGcog+S+3movKcl/cPSWjxNbKS9KOJmwLEPqZrw/Huw5dhjNTmy18y75+XJcJ+JnEh31Qr0lL29Ca1bnQlG7ugo+jmOew89S4YDcUlnBF2u1TZNKwbNBeoCTdTZJ3Co6JQKJ9NgQyfGaMKRvj6ofYJ1wHm1vtaxKuJRUlHuh4spfpeGgizcJqI0OuGjwO+z6yjWJbmYM9jfEkD4HjdhbHOps4wCzt738qY79OiD6cOZrKSI/bi8Jq5zXvM10zP8FOdRD2yY9o00sme3pu4RRpZVgVM7Dx3nT5Ziqi1bFes1UGqqqx6vSuHSNeavcm0x3iUPFmz/Cyl2ERsN3MNRSr627SJMu5iE5x7B72PiaQ4qjJTxzsQDn0waYBV9ZheW8dp6cx41wMWYVX7eiDTLRzM8bXs5bUl6i0rAlsMf5Wm9QzndkJoCkEvhGbj4kToH/ZuF7JO50ChZHYLFtmNNIJ4KkagHYKBH01rZPwVvEVo+Rm3Da9DiACYNosTFoyF4X7rVW8StWPkFuiFAEkYHTlWVwG7oSmwizuAAFtj7kwhuzUI9vED86K02YLKmiCPSrAMldslW/9c7bXVejwN3eBPBY5Rii7IWnVeDB9YQ+ldT5E2qMFNijTdaqDSa+Pqx3lll4arTgXUh1A1KF/Vl7bNv6ANJYhrWLV4XEKmtx+zQ+94tnu2Iamw+WSmNBpHgC8ve3q41ox/j3iqQBFC4sHkUibQc7kNYisls20aqVA08Fsu/QrF1x12gX6qeH0kjqIYzbuc2S5L6NXULdJnG+jnsQ44hzd/1/kePkgL0oseaaBeEa9VHRRDmQ6GTjbfegemObC7K+Kh0gtU1zHV272XuQNjfKSOAOrTmsNKq7XwKqJx6uNQxihFoT7S1VLtwl59i0lfQpPAs/DwraJbrypvJt+W3dOMO7t6uddsDgaiWXmnKXxsWxThgYT9IN9C65WZlmuH1QtQJNpZzXJejmhjqHyvuVSoeQUFpouIOcB3pxV8E3EZAmgng/wcyuQwnp7LenJ5aCQs1RDBp16xIuGDYSfm4krunkHQSvLnn/E7Fd/z0wFgopFsuAuagc8Aj6lTT8A1MJEwlRDAAA\"\n },\n \"check_installed.py\": {\n \"sha256\": \"a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 1912,\n \"content_base64\": \"H4sIAAAAAAACA4VVS2/jNhC++1eoJ0peWpsN2qDQgodt0B7TovUtMARaGsVcSyRLUk7cwP99Z6iHtUHaXgQ+hsOZ70GpzhoXksrYM//qjeZBdbBqnOmSWgagWaKGmGkeQ/4xegxTujGug7qsoVJeGZ1b06rqPB37I87uWwU68PvfH7Z/frnfct/vOxUCHVNP4MPKKi2YP8jbn+4K9oHt2frux5WDv8UrUzUrmOrkE2xs76zxsGnUS+gdMM4CaKkDBgyDwrYyUEG4hXd8hQr3xhT+rMMBW6jo2NkCLYE7qeo/81w4c+BN7zBsytRBZ4pFDUMyWqVrzz5ARyWPyGwmZP7nGlkFCirwvieFORzGV0YHeKEeLpdV1Urvk62T2hO0xSqpoUkQpB4RTD20DV9L9+T5en18pkGGIYk2z4Ioy+mTZp8TH2RnRSu7fS2TUEy85kQnDeJ+Gmae8z5UWa68oUJlSDNMuhev6zWpJq8BLA3S12OBpTwedwnGJUcURpJO/V7JmNtcwHptM7tktB4bGpVBLM2yAJJFTDaq523sW12lGJBdsGDVJARQPiroB8G+/PLXrw9bVuwf2bjIdmIZg6dq8VY7SzKpaoftlCdwcamY9T03QacJFkqz4wyaJkqSybY1z5gDAa0OWO5gmmsmdvq0lHBMMc12S0nGnXmKW3ula6WfsDHOWtVEbkm5R1wn4YUWE2sTyj0gUbg18I0y2XxC9OHFKkxXyrDY+XB3k6FCrTMnInSwwliyldURvYmpr454v5vvD8ycofX5qHl3Lr2W1h/Mu/RXA/1wkm0vg3F0ZwsvG9mHw8fr7RNF5dD63EV2ISU4wDCd3N7c8HqF2FdHLx53KxLtSPwg3VEfnEWpynqPZeMQyNUVdBBl3esjJkYxPKBR+G+y9cBv+OOOv17IfEHMbkXnhUlZYlZYJZbvY8oOIVhffJyayZXGXlE6fLDrUohGt2fGRzDFFfwRdBEx/x5rQVCHqSIR0MlGVHkEITplRT75FxSyAl8fwBfd5AZfBNOBEKOMP5uc9I66KaveOepk8eJgVkBc3js+tjf/O1gidY0RM4P0T0qUTwheRCuylUtrQdcpyiUda8U7rFN4LcXndd9Zn6JzNVLftuiu+Pcox+DyJJ3CV8mzogWdDkmz2TglPXOof+oM0DG3+GTVJ3z+UJzGlZ1sxx+eg6b3FPEzv9JSRlqKresBXSaDOuG9aBtV43teRIFcsNpvvWim+3gHAAA=\"\n },\n \"fresh-scan-contract.json\": {\n \"sha256\": \"b13806a7e1464454e3817e1e62b56b0fa07fe2fddbd94d7fdc530e2c3dd9e3d2\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 9733,\n \"content_base64\": \"H4sIAAAAAAACA71aS4/jxhG+768Q5pCTxen3YwMjROAYSA6GDwZ8CAKhnzP0UqRCUrurNfzfUy2KEsWZ0Xh3yACzs2Drq6qPVfVVN6n5/d1qdVdszUO4e7+6i3XzEH6rM1fvy3prs7Z2hSnvT5f3RQWAbfBrH1zRFnWVt4+GcPE+cq5UZFpYHImQHknnnY6cIBVEJJEwHCWzkmpHqCbMIioRAS+YGg5Ld9+daWxcXcXiYVP4ROjkXxBig3M0WK+ls0JYEaSjIlJuo6bYSPDlNRPMCq44xoQ67JxyWisvue39+yJG8NuC43/D9ersnjKlmLE2aKIEjS5oSoOHANqhgBgXECGi4JXw3Dnko/VRR+8MI8IF6o7uR+7gAxuIE0EbhwihEnPKOeFUwj+FEUJMCUcsA/bOEuIil9gKo7jBxPuJuwAxvUHRRmE585TRgCChXHglBefUGi49ZFV4iTXcPrPUYBeYjwgLKsTEHUNaGY8oBUIE7lYTo4x2HCtwpgUi3DOJsApIKoegoNJr57lIicExhom7qC0DBwwTgxyShikdmIiWewtF1x55p5hnwVEApaQYqznFkbGAvI544k4wxTg2yHsDJfGICYUDJZZD+aGpuKeGRWS8YlEqGjRPBSbWCS0IgupMSyEwo8hgjKkWWjJqtQ5QEEtQRIR6zmBRU+uiBs4ayqxTzSC+dcxwPXHnouMaPEnmhYwCWcYwdgRS7XzgqUslJiFAo0vhPNIMGyI5hntBRlhEJu4I5FNjpaAHtDOax6BtAKqcR4mMBVGJoBgwptRyAibUQ+6ipVZyEwyduMOYKwzZgp61PtDAoRgEw01RgghknxLoFSAKXR6xDApB/0AqidKaIemnjYJA0YwgLGnSMvwGOUPxIlQHGihYhYixgnkc4C4jw9pEjK0hFBGLeDB34O0/T4Xd1HUXkwZ/76N1h91x9pTmEJp24PBEq7OrdXa9zq7Y2TU7u2pn1+3syp1du7Ord3b9zqtg0DD8/mOk47beNy5sSmNDOdIxHB2yehcqkHlnigq0nB3xmTVtyHzxENputKVLQWEDZ4g4ZWEvd8xh67ERWEBxoZqQWgwCJqAbJSUjHNRnUo+DpJWiZhgUL4ZtwsfjGSXFhG63FkRCITIoKXBQJCQQFAAkuCYeJMNVvOwex3PP+RjUhDKkm2hd3c+qf/704w/rX39ew2TQ618Qv/9SFnatONJ43eAXvPR5yxLR8LnL+jwkb45zFgRJwomUIWcNBj0LbWCWqSg8cqB/Av2gkIUDFRxoBA+EwAnKYgKFvx1vV5eFO4zCaZiV2AYG5yistccsgDChIT2PimsOIucWOWcBphAyMqaOO/YvTwcvcnfuh3pU/h/NtigPyb8pd1CHgdRPZntMGc0Iy/DFtqg+hqqrm8N5wp/nfGmqh/Xuw0N7aejdoXusq7R4Wfv58K9ff7lckgzzbDSYAPt+d9gV97vDb5+6/PRx39HffVNAF2NxFe/ZcAmV9x++KVhz2HX1Q2N2j4fLKkcZej7qCJ6fUG8J/+Sof/kIZSRDz1B4+nTQI99CY3dwO9O0obks0WeDn4E5fWPIT6bomtC2VxEz8kzMAZn3gGeD1u0k5JU64Do7cWj2lQ+7ERBmvkAa4QwhjIi+jm92H+57T/cTB/nE7m+mcY/fV3X67y++aLum/v6ixW8g3F+v01SHI1u978aJkhm5DMCnXJ/Y5oNJT/OzEhvBlqG59qYzb+F6dLA44Q/hMG6CTKwb9BrHZJP30KVonbbAcf6S0z9B7mSZnw3mp7j7sO7qupxI9nbmBpt8gM5Oy+7bg60/XxZwRmWG1g19uelONvkFuhSttS2q9vFbyPWWS1J0Zu1C0xWxcKYL05ko8M22mxjnI5ulia4tzOAyvInvyceitB+83Y4rT25KJaHzE2h2KjC69l1Rtms4QLdjTiKj64a9SOrKLh/g89NrLF+nFxdjatD05GVig0XeA5ehNM0WIbc3s7NRfsbOTgzcQ8SvmMG9wXIDGPzbL+SqcplaN+IWITDIB9wShFy93YSmGZNiMmOvpOlklZ/Bi1A7PkLQcQF5ysNtZr1RPmCXIHb12JUC3Wz13iAfcEsQKqqunAyEm0lK+H4cLJKfyu3hyaf9dFkUmdjsjjsIx69JcLDOp0aLUG3L8eEWpQMhucmuLfMBtwShnalC+Y2Z623/L3lr2/IrhZksFlVlVzQ7d3V+zPgrujya5ANyMVJPNuw/yWzYtpejt98Xftz/jKRjy01mySQ/I5cg9RFOn/X49Q5Ng/OmKo8m+YCcndR2317N1+Oj7cuEEjwfUIuQWR/Pml9JqTdajthpcK+70GzTu7fjq4pvmGTP+ll+qjXB+PJqRUE/3ZTDYJIPyNlJtS7N9Mnw0Lc4nSzyATg/pf+WRRcmJ34YUfT2qXFklp/R85Nry40ri1B1X/Um4WK25GuE7sv1K7/Uze5WznqDfMDNTujzl0kZYXdOffwyo5NFPgBnp5S+uLru9puH6wTPB9SrZC5/ADB82bMBtdRV4Uy5uXwhhaPnNlripUPMSIqFIYoab4NBkbOI4Mdx46wlWFmMA1wJKW0MSjom+pcEd7u9LYv2MfhN3JdlE3Z1042CkBiFClJwq53gRBOBsedGCw3lNrDOBJUYID5EF7xx3BMnBWUIabgMoyDOdEVdbbamKmJox0GENcRRhi3TwlgZpY7a8egpNRpuCTsSlU9fRCKsDLMsCOkZ3I9JMOv6l/x3568Z//HZuG5VbLf7ztgyrM63uEqZLz6GVeEhr0V3WJnKr85Z/usqFpUvqod2BRtRt7Jh5R6D+5AMq/BpVVer2IT2cfXD37O7d3+8+x/NJG+cBSYAAA==\"\n },\n \"fresh-scan-receipt-template.json\": {\n \"sha256\": \"92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 549,\n \"content_base64\": \"H4sIAAAAAAACA22RwY7cIAyG7/MUKOd2JxAgsKc+RO+RMWZLRcIskFFXVd+9JLOtVlWP/P7x99v+eWFsiCu80PDMhpDLC33PT5j3lFf3VDNGSNf35zVu3bCS/+wJY415+1K/gVD6OShlTJBWOx6Env04o0cblBgN6SCCkDzM0s2TRTFZId04zaPoXfgEqkvDpyMGlBYDYFtq3gvSUuh+Uo5k1knnhPJTx1kDpOREMxkwMOGkrPCdrEzAR6eKsG1Ujo/wukMlvLYS729/8mrhOGlFxmjrUDqJGkAGEE5wZVW34Iwi6JEbsN6C1xwhmJmbGbnV9kEJe0oLpFTpTiW2t+XAdmYrO52GLS91v90K1fcx/lboR2wLZn9sfettThHzekvUyC/QPureLSs18NDgH/kxzkfxzFTolkv7T3XL7bzzV+okaMRiZV1jfek7JNb37WlDYrkw6LnzHdJw+XX5DVUNbXAlAgAA\"\n },\n \"fresh-scan-report-fixture.json\": {\n \"sha256\": \"30607043a4ca44e42024b63659ae10993c14c7a04e7c5685255ccafe4d3fb55c\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 11944,\n \"content_base64\": \"H4sIAAAAAAACA8Va227jRhJ991cM9LBPIafv7B4gWAYZBPAimx0kk+RhsTD6anNNiVqSyowmmH/fal5kWVpzshbpPPhGSefU6ao6XU3696tXr1Y/2Tu/1r/4uimqzerNK/JVvPpN3RZB2/b9fuvh4spWm1YXG1/fFGt961fdm/7uW+10q+ENv8PfcOU6vnj9Nn6iudOEizeCEOOtpd44lVkjhBE+s1QEyk1QFOvMeesUE8wILjnGhFpsrbRKSZdx0zEB8tsihOu3DSD/s7vw6sBAmZRMG+MVkYIG6xWl3gGHssgjxgWQBOSdFI5bi1wwLqjgrGZEWE/twHAECC8ZT6zwSltECM0wp5wTTjP4khghxKSwxDCQYA0hNvAMG6El15g4dwbogddpFEwQhjNHGfWIMMyFk5ngnBrNM0cVES7DClaBGaqx9cwFhAUV4gyQISW1Q5RCUAQ0K6KlVpZjCXBKIMIdyxCWHmXSIoND5pR1XMTlwSH4M8CgDAMIholGFmWaSeWZCIY7w3BQDjkrmWPeUnhTXBptFKc4MOaRUwGfAQomGccaOachOQ4xIbGnxHCohQxCcVSzgLSTLGSSesVjtomxQgmCIE/nSRGYUaQxxlQJlTFqlPKQGkNQQIQ6zuCiosYGBXErSLmK2YMIjGWaqzNAGyxXgJUxJ7IgkGEMY0tg0a3zPJZthon3JqOZsA4phjXJOAY9SAuDyBkggZVVWEqoCGW14sEr4yFczkOGtOFcCi8ZRE2p4QQ+Qh2sYTDUZFx7Tc8AMeYSw6pBFRvnqeeQFoJBGiWIQB4ogcqBYKHuA868RFBNsKREKsVQ5s7LBklIGEE4o86qAN85gcAgfRpBOXkjEdFGMIc9aA0MKx0wNppQRAziXq86vH8NHfmPnw59D399p9dFuY+Nr8stGMUD+Q963VkITQlLcY/x+asjv/i22oTi9hisrqo2NEdX4Fo7OFGp9+BVB3h4xYE13BTu2BsW8YdFPGIRn1jEKxbxi0U8YxHfWMQ7FvGPRTxkfh/pvGT47fPBLeypGcC177Xx5WM7gKtVfZtWW785jCZN2s0mqdGNT11x65v2aBDJBIWxgyFipYEJxDKLjcNaYAFVAGmHtcfgAAQaTmYZIxxaV8fGAE+QkurHK/Ekee1/K4ZJagVNYgz0FwV+aELPoaFhjaFxIBSuiINu4zKcFm5lC12mttqV1doAYOmjoMZWvQFe//Dd2+TXdwlYjEreI/76U1mYRHKkcFLjSaym2tXWpzFo/7FN+5XpxjvOmRck9l2gDFmjMdiCUBrsUQbhkAUbIVA+EhkkwT254J6QDBKKCdTJH2HdVmVh90ekCkwYG89gKsRKOcw8dDdUsuNBcsXBLbhB1hp4m0RIZyGWaVf4PI6R5KGMPl8d/4zfu4Ja/eibXdk+bAyHDebbUjfx8qpqku397cN2shoH3tNd7J2295Dfx5vMo3oc97l0u2/vqk1S7zbOb5vHS/MwZ6+gAQVSCKcIYUROjOba+Q1M4IWvT8o+hvLzj99HAAj8jd7ev+5DfX3Cm5/g/1XX9u7rTRV//MUVTVtXXx9vyY9X8qgln5DZsyax2WBLrnbtk0JpmqXkrDSfofCMMh+he3EfpbgRbBFxSXfOeXmFHe/SMu/9fqJMU5HUaC5lkSrvIRcSM/jlRK4ixYySBsL8ADy7sO190lZV2UxoQjNlaaTKR8i5xZhdszfVxyel4JRmKUpqenkzDVT5A+RCYhJTbJq7F5TUEy4ozOrE+ngLqLC69V/YwwSepZ1OOPMj7IXlJQa2zNL/GSoH6iXF3jqznqhNMotxRJJ8AJtbAOwZu7YomwTm3GZCiUhpUrOLpTyiy0fY2UXVhifxsDMhCFqbXC5nJMp7wEWEfCEzhMwzHh248gPm3HIAHQiX32l7nsW2WYA3n8hUbaUyqcUcMoAnH/EWkGGr9Y2v6wkpLEvZTCkZyPID6BKC6v22rehEifG4lvPo6bnyEXMBOSEUk1JmafyeJx/xFpBRbNpy2opnSUik6Y14iVxs7K5ufPPhSSEiFTfbbtbgeC4bG0nzU/AlBDblxEEVxeMXmUVTU+Yj3gIytnrjy5fNUk/5EjlqmvJlzC0SLelsbVFv7dSpLuUzeVvHlI+IS0n50rg5s55x6FxM1G5XuAk3YCQO7rPoiUz5AXEBKb/BkbB6UguCNSQzOVvHlI+Ic0tZ75qpXbS78Xe5jMiSj2hLSEi6A+DLCOm5FpMz7M5J6+t1sQlVd2/55Xae/0m/+C5Ue+3K48c3pzol1P8s5jAy5SPi3FIaGzfuadtWcygZiPIRcHYh/ymL1k/fKYCtgs5zljtiyw+os0tqyhtbFhDcS9z7fWBb8MZv+2nyQVfsWTtHfnqefMSbW8bHT9OFBjNj7NbLdQxE+Qg4t5D4bH+y92c5XkeWfET7vyVcHf8vxyDo/El7qTe3TzxrHx5cw4vPed7+bv+3X99PzIGYp89coe1+W7ze7v/9oc0HmOc9bZi6RQLAF4UXwfMe5HnBdXeIbmu9vds/3S4onoUvifKIJR/QnhVunCHqtXeJ87b/P5unh2Y4wF8Q8hlT3iM+K+zt3m41DEL11I3li4p0wI83lJ8X4gddtLVvph8ykwtiHAnyHuiPeMlV/O3z1X8BmrvO4aguAAA=\"\n },\n \"installed-zlib-proof.json\": {\n \"sha256\": \"6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 492,\n \"content_base64\": \"H4sIAAAAAAACA32QTW7CMBCF95wCZV2S8fifVe9RVdV47GlTAkGkbEDcvU6gq6q1ZGtm3vPTZ19X63XT7+m9NNt1I+PpvXyOLY/nYdyndhq5p6F7tF1/qIZ9yZtcuJ/68fA8fRBatxVrQxATXVKCzmfwnDmKRQjFCQoaJd4kryOjjmgSaA9YU5QmW0fN04xxGfr0diTePWhUq1vcnNRdlX4oUx1fa1Pb6u3ouOvyjDV90TCUXNWXRX3Yl4vzMgg/5e+iERvBO1TReQUFLUOJ4jETOINMOZDNnAiIYrGBoNbkrAsmq+oF1SxBr/e85jyduhmu7kv9wHZ5xd9kUf9DVphDilnY2JK8EMZkFIgQk0Prg9chQi5WpwBSovLaeu2TieA8W8EHWT1vq9vqG1zLL/rsAQAA\"\n },\n \"native-preflight-full.json\": {\n \"sha256\": \"56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 21208,\n \"content_base64\": \"H4sIAAAAAAACA+1cWXPbOBJ+969w8XVDGaR4el/W6xyT2vgox5mp3a2UCgRBCTFFcEBQiSbl/74ASEk8IIn25pitZR5im2g2Gt2NPoCv+PXk9NSAaTpDC4weilkOi8I4P+WsxC/kEI0KzFY4nkEuHhs2sD3TAqZl3YPw3AHnljtxHRD4zl8AOAfAUC9lkJMVFixhNsexeC+BaVHxyxnlGHHBMIYcRrDAM4Zhm6bAiGHefx7jPKXrJc74bI4zzMQsNBMUtquGJf1avJWnBEG5CKvmBlMxXSol2nEQw1/FqFx8Tn7FrKhYib/y4mxlqXWIwQeSSRmMl7sX65El5lAuYctIssoyypVUReOxHGBziuKJ/CEU8GlC6BlnEAnuc5OoCUiWULbEsRljRKQw50qS3bznPYqz3pNaNjVlb3CCaJnSZTQpKCIwPYPxkhSNZTMqzPzX07fXr1+av92aAAD/FJVcPGVGzfZxw19YeIm1YhstkiKH6BhdWSkAukGALQDNEMS26QTB1Ax9F5teHAdRkGAEQ7uSo5bCKHKMmtoXK5gzXBQvhR+kJMPvMaJZLC3hAbCdrusf9cOVkuoXUnDK1u/IkkgPsXavFTgVbktZ265LyNHiHYxw2ja4cqt88lBGmGWY40JafL/Otu899hRdCD/heL5uz8vXuWJ1J3eKGO8biONlnsqRtrx9pz3ouN/Nk1rCqllSnRa1ekR0mdOs2sPKcviz8eLYSwOUv1N/S7aun1UTiOUsaZnx9yI6EoQvEJJ/3dMHnDVj1vYF4YocCqdkcon/bknbXrCgxdmqR6QjbO1Eqe3ZP1798/Li9tXs7fv3H17ddbRSv7CCaaneWHCeF+dnZw+oY1Cj99rji2cIc3lzfX/x9vrVnfzt9ds3R8QRds0SMi+rsH7GhDrJEk8+FW0bdS1V/fvY5W2QJZwrvsLec/yJdp22/rMfRP9WLKDteue+N0VTzwE2CiLsI+QgK4ot6FmePRU/kyS0LC92bduxAt93bDdyXOgDK0AIiOgFDb1It2Wa3lIRgtZKUck15bciau0yS3NPiJyViYh2y2iEe1tDkSSQpCXD9wvBY0FTGUqnfT0rW7/BXMtDhk7IF8oGC5Es+eIPvalyyvjGcfQUhagiKg/45f7+dpAfGTlmhMa7YL0Luk3GJUJCEc1lWhoy6TG05Dtm9skRAQZkstb6i+E7c7vlbyu9BSAALw7tmv16lVUTRTSVVPeXt8/foBqZrIMyfabsIaWiEHumXEf3qazayOjk39fJBUNaMsFcr99U1jvFPr2hvJSLdQFY6hWyxEtRNKn2wPWuyDCNMPx7iYvjsx6d03O0Ux7ViegySkb4+lJsCfxF7zWyM6KfbxlZkRTP8asCwXTTdfRy/EZwmMOIpIQTvHdxMaO5NpCo0Yt37wzNyMeBeoXxTZau7yjlr4XUxboQtWDd0h1XCoeMl/nT9qJlj5vxCZuRY7YkmXKjKzGDrAk2yojx6qwxbKZ0bgx5f1tPSIv331jRtFziK1mhPiF/qfp2J5psGw6mCkXx7MzUnk1Uf2fHM3Jr+rpg/FYS8GV+eD5J8I3malW+h2cdQLqJAPo9/1wZV5Apq1SHM0XfOmd55YWHxRcBVXSBfyq5YRkTfljsAyQ/S+pde3tY9B2dCXPyI9ewKR1Nhuek4OyIawwk/1n6ruuVfc67WUVBOJenekfIj67i5FDub462KmojzopdMrhMSynBa8KKtvPKelAm+h3pRfoZrtuepFJrXIrter0J8DiBZcrN7UCH/khBZSTFG0bLXGVcADrZdDN6qQ6Nd5LdZLKSuSKFOmvrHvQIm10c5KoIrqli0jjX7lJ8KDDbw0EsS544iZIokcm1X8/sTuOqDPSyUpOx12SPHbU1T5D2nGl1qDYW0VE2yoM3DCJ826l2HLdFXtUGA06lWmm+3xvLI3ThbRn/VTG8TCFZ6itIJIeu97Xdpq6MaG+Hzhbvn58tc75+SZh+ejEZKaVoxlXVQ+iKP/IH3hwCG5amp9l/jqAtQ/7UEvcKmWPSVjXIFcz14tZh4orGkr0d6rqDvacuZq0/00O2bQV+EAPXGr6Wdnn0pFUdKVHkWccndXM1aNVBoDPStgnXdX5f9d1goT1h1hOfVqUKzqpblyTFX0xY8oU2CVWe9yUnlbZ2EWLauC/Z27hxJYiW6vFkyLOP/9UeP1yZ1VeIz7cUET3zE+30gNc7vejVd1x5GjXp/Eitbn8IrZTyTfR7pHz8X9kVOjf/Hl7+YvgKDkfRAX54iHvbJxGcIMb3BoHWimvavaSPe0Y+7tVl05VMJuow89AcPyJ4fNcE1u9jBmevI68O3bPHe5CfGB9RKtKT6CEqAY7EyTbxs+MlzeXOh+mBU9tjIbWv08MeeKLz1e31/snmf2VSseeymIi6dwSojACVEaAyAlRGgMoIUDkOUElcEZESJ/QiK7E9PwY+ilGYuDYIsJfYie1Yie9E/jRE9jS0nQhMfWALLtYUuuLRCFAZASojQGUEqIwAlRGgMgJUxs04AlRGgMoIUBkBKiNAZQSojACVEaAyAlRGgMoIUBkBKiNAZQSojACVEaAyAlRGgMoIUPm/BKjU31CJcY6zWCTv5slx7azLlrN2QCtbvIq6ZFyWHEaqU2l2O1soy+XW+bdDWljAAVDAs/AsZ9uJh0BZ9HVgq3P5PriE5s3/E6vUzpsDwC87+EsILGsa27HpOnFoOjiwzciKp2YIIifEEY7tyO2jPBDMaEZExao+9zOrbq4luwAjx7Njy546vusEiQs8jHGUWHiK5McWfM8JgBWiAPrBNABODMIIIT+MQGLbMPHaSJt8hYY538bHbrXd4A/2N60Qg3yv1+J+W19rcc5XHaRKJHRoyhOHFFdllbHunPFp3hFFUGxGa1Pe7zKqGhjdi1WzP4mE/jscJO4JzrEpBVcLrDgwmKEFZpIipcLPTBWZdTw77BRcSugrq7KIYU/DiWdPbOBOQnuyoAWfqPMirL5/xSZZJxnsYfsUKbVbOiEiD4heuYfDMdozCZc3649ptTrJj4PCQ8eBnh4O4gBEthtZpuVY0HQcKzIjoTszDBJoJ3EYRD7qh4MeUsmA6hJLpvLeeu8wjH9jIpHfyG5Ru8B918b7L2+N2kLqFOINGQCuqujF3iyKTfbVetr2Eqvyp8bVZo9mw0fa8EmK7Aa9Wb6AhWL1d7nBjG3CloFol6FrmzlhGHhBYpsYRsh04sQxIZ5aZhSECLtB7LtgA6Ooe54WerEueSqI6llKEozWqHGbJ5GL9MPduyZ066nYpsm80SwbXCwD87sa+6jqrdCLAPLiILBw4LqxFVtuAMBUPMIhwMk0BLZn4w4QUzgXr08cmysaCgCtIkALkbbbinUxOilWqD2pQuBB3v7+nbx3VvfJTTmEa/F7ESJECSfmvSeVczQ/teefA3Du2P9qyCQSUVmoa2XFb92Bnq6zVkaU4RoyHN/T9vbTa+ZpIeHJGuptsa6vDfO37+Jz38Tv9EhZ9hx+GoPflPwmeS8N3KvSpRVmMVvPRPmnPuWovG/7McdtDTCjWbqeKbifKOpTVehsqaqeZhaTJJnhLxXOuDewq+QQShIfWRiJYIUjN0wQENHE85AFrCkUtZobJ67lwAQ4AfICx3eg7dth6APbwYkfVZ+MlDgWBFVfMEN5OZOnyQ6oDkA20Vx0IJuhqeNVQw34cQNs/HjyH6VWW6nYUgAA\"\n },\n \"native-preflight.json\": {\n \"sha256\": \"3b6b67a0b4d51ef791e775b528b64aa5a6f14eff629737e67baee7e2f6f60762\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 1871,\n \"content_base64\": \"H4sIAAAAAAACA7VVy27bOhDd+ysCb29lUzT1oL/gLgoUSNNNN8KIHNpMqUdFKqhR5N87lF9ykgJtcO9OnMeZM8PR4c/F3d0SnKvUHtU3X/Xg/XJ7F4YRP0RXV3scnlBXEMi85IznScqSNH1gcivYNs1WmWBlIf5hbMvYckpqIdgnJEhod6gpz4DzR7x+6AKqQIAaAtTgsRoQbmM8qgHDa7vG3nWHBttQ7bDFgap0LUXwbHLH+ANl9c4qiE2kkxmGXUeHn/RNp9FGzKWQssxLwxOEWiVCG5EAbtKkLqXCrNRFxvTUS6TTjYPCC0RsAsI+ohwJrZ01qA7K4Slj4tJ3X+4/xqB9CL3frtemG3b42K1UN7quqVe+Uxbc+nRc25YCGtSJRmU9Nbba2XAFDNQGhnt8mnwRV8m8ZirXZZlimWU61WlWMrYhE0qGZiPprjjy5QTxfGpGow+2PU/u0lELDfoepjaXr6hcaUzLMMzb+jbWOLQY0K80GhhdWPkndVsUxtA1EG5Xgex7BDdN8sLDgQ8PA7TeRoYPtsGXW1fQlm0F/zrjFCCM8b6X/054h9vi/tCqeQnVNT0MqB+6mfW3k/mb6bxjQjOib+/an+3b/7Jz/8neTQ0uXjRKVN+B98aFfxrDJ/M5XvDiXOj5JCHxFio9kCCM7aRq0/ZddA10Y32kUHWtO1S2gR1WGl2AeZRyNqqNtsZU+MOGi6jMHX4PPMunVpQxhUpRGS2xzqRRjNQkz1XK0g1wMJk2WSrAMFGqvBSFAF5wKQvGBZqiPqonqXGnojg6ktB+rBqCFoyxk8h9H2lRST/Pro3Ijy4fSBFxd4hM7qOE0umIaEYS+AEV2j7M6BKzTSElqZ8GFDnkRkItBFfcGFnzTPKU5xmrNxnWstSlzFWG5aYwLFXIjXwD/Lypxxcg6Qc0zu72IYlRq0d/+lviM/BIz0CcP4XggK3CyliH/irVnv5m6tPFp+Qq/RH96EmiJ7l6rvDXZPJiqwndTsjnzBO9ufcmW0GrLT1QLwtf7K/Kxs1bPC9+AUGxm5FPBwAA\"\n },\n \"post-ready-native-outcomes-command.py\": {\n \"sha256\": \"ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 1655,\n \"content_base64\": \"H4sIAAAAAAACA4VUXW/bNhR9168g/EIJkeW4WNvBgTd0aLrkpQtcby9FIdDidXwXidRIyo4T5L/vUIqcpsCwB5si7/c9597JZLLqjLCmPgo2njUJumcf2NwKpRsOgbT4ElSgq24jbqwWHz6tL1diT463DFmljGYNuViR0sciSdY7Elu+h+zkSbNvVah25ERtlfaCA34mkDOqFpUjTSawqi+EszYIQ3AvHPz5hEMhrtbrG+G7qiLvRevsnnyMaw1XMK95S9WxqqmwXahsA+HNxxtxG3NyFDpnSOfCwK8yidJ79tYdYwGaqyCoYe/ZogVOKATZIOPQwXSLB0+tctEP7dEZU5Gwm5rhGQa+SFYxWUeVdSgp7NiLje2MRuUNBYWmqCglboM4cNi9NA19/PP644wbdUszbztXUYG+caxuCNlnFEE5OA6oaFC6QBlw+U9HPghoNwp4oauwnkwmCTetdUFENAI3lP/trck7V9e8KZ6txis5Z12ydbYRquXCk9sz2ls01NhyhEs8O/xjdf379ef8s12RZlQU8mDvyJRbrilhvZRstnr65vzNu/n5fD59QICpowbKfSFTN5cJ4i9fp1KshjMd3J/J2X4+01RxhMPP5BlrvI2Y/ormRcFyLvMdmIHb8lF+6MLOOn7o48iF/I2UA3Xk2UuCKfwqN3OdmXkC1YKf+cjn6a7bzJC4dUh0Osadndg07V3I7ClLbEuG3I/pbzqudTnI0h9kN87eH68wGjVkj0/Zd71LsywJ7rhIBk4M9kU8UljnETjUvJy/zYTyANu36AZBXTh1WI73Ik5HOj//6ee3799nEPJW1NGFOmS/DM/vFk6xJ/GXqju6jHin0vZdfCAZTdwyEqToJ7I3TEA/TGXaP+uuaX36KHtGsJYL1rkcaFj2b8+AyMU8l3YTGUS6VEEuRv4Vpw9jD+npEv8erKECyGYFpjFCoNCXXBrguKdSAdW4ECqY6PI06eUJm3JkhVysXUcnw7gbMKiuBH135Tj8o9LLnikRD7OIDzhqO+T8SdUeKg6DTge5cMUthXS8IrM4EtZzpFlZYchh0rd70PtO6mX+9Vv22gJpfn28W+he+S7rV8sd9p9IJRqbS9XGxJFNf8MOet5JZU9U+crXSX1oNmRgrh4u2VPvWkfX/5XZtyfwj+4rwlK67I+4asA0Wvwv+MimQy2yM+MmQ/x+k5RVrTxE4dhSSllRlkY1VJYIdjGw8MvRB2ou7zmk8yz5F9zBrst3BgAA\"\n },\n \"preparation-failure-history.json\": {\n \"sha256\": \"51b9983d2a806240f562dcb951955fc8360c181e6d08a8dfdcba95361c6dd596\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 1070,\n \"content_base64\": \"H4sIAAAAAAACA5VSwW7bMAy99yuInIsuVq1IXu+7rQN2HYaAkqhYqC0ZkpwsG/bvo5rmsKLDtoNhmH4kH997P24ANhFrONKevCdbN+/B41Totv0JMdSA007tZ4zBU6n7MqKQO0ZtOuqNxPutFkPvpNZWGa8kSScUmV5J7MiJVrHaykH73hmhpfXbTvbKG7H50wpzrlT2mSqGSO4PhGyal4kq7Utas+VXxKWMqf6tL6ZKjf1jqnDtqeTAkE+ZYI0YY1qjJac7wJmi46c+wFNMpzhiGWHJVCgfyd0CfUNbIZNNsdS82hpSBFxdaBMLLZix0nSG4GFJpQQz0R08JiZmcrDYUHbCMEPycOUNKYdDiDjBsw53v8mku7ec0B06M1Cn+sHtjBt8NygpURjjlJGstx4IlRZ9L+W9MsZ4GoSkjpHaY69er/gXbfnci7Quo69ieIuXQtu53aAtGuG8cbQzdou6NxadE/ZeiMGJ3vfYoxw6IT0p0TKy86gFbV94XRf8HytqYS5c+sKfXPjIEHChLFgte4ilUIG0EDvEpu1DrGwz1MQ+zBwROIU6prXC3PAhHqCZOfP6/MyKJ36+ABc8TwkdTGifmutXVsHxwFDP714vufZ/4ByNUCxGOHAUAK2lpUWC5oXb+JR4iQjj1qmCTUcedGgZdZQhp1Q5eRQWJskKOKx4Hf3pFBuCjoFOnOwDxp16aNle+agcvjeetY2y45ojJHMJtO423P/1WcDCx+5r5kC8yHrz8+YXqlwcii4EAAA=\"\n },\n \"published-verification.json\": {\n \"sha256\": \"dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 5912,\n \"content_base64\": \"H4sIAAAAAAACA31Y2W7cRhZ9z1cQerVbqn1RnpIoGQiTDZNlAA+CRi23JI5bpEKy5ShB/n1OkexuSZbHsC11V/Hu95x7+ddnTXN2P/T3/Rh22zafXTZn199/c7X5948bxpjf/Mz0xZ+7Nm6cZp5vBn72tj4yhfH9J64vF4YWFwZK/XC69K/rn/65EUwYzjjfvPv2+stF6vLEOIWJ6t2Qpn3YbfoPHQ2br6439/u4a8dbypu2y3RP+K+bNg80tKVNYWr7bnMfxhHnHT490GbfhXv49EB5kbxcpbzdT6kqqCZsONtw/jPzl4pdCnWujDfKvGHskrHVnn4/JIIPD+0IHfVBH1WMQmcptPEukFaSLLnggkxSe5EjL9qVtAhI7baGCQ9yrqU6fDfsq6zjx+r2fqzSx31KNI5r/KCWPsDm1Yy70LWFxmk73gZor/etUCEoay1Fnpy3JjvjSBfNlLKyqGgyjOEyW82ys4FK0MllnQKzytJqZdjtrDpouQ2I8whlU7rdLnnYpv7urp2gcBr2ND9yTMi2vQs3tM3tDSybXZiNuyxaO1eUN4iHMDYzm3LyRQvmyBRRhOLFqmilT0J6oSKTlolMiUuYKORiWQwjfUqDNYi4UUwkF8mmpBKPmQfDjZD4WYrnHO4LaHLWKqGj0sEy7lJizslwCPJNO07DI3zsSnvzsRojRKSUJMXsbYrGRANt0hSpY/GSBwujs1cGwdZOcy5k4im55L1D2OOihv6gtJ8Qrl2fENAc6K7vVs+WHjrETRCzJHN20ketQ8k6a448y2CNDgI1qlF7pMggp0g0fOMha21iNpkdGgnlHnbtn3NrbFN4oDD7dDXrbXZ9yM1Ah1s0Nov3nzcohSa3pVxfjRf3uzCVfri7oFIo1bZar73ZhUi7sXnSi7vHhn5HqTRT3xxC+nnTUTvd0rA+1nT90CyeN9dXzbiP49ROiMrYQM3xsWaOSrMmYinQpQyPuco0pqG9n/ph3IYOQQ2PhF8PTf60UGsF+fV836Xb0N08vwCMGHC+ysCJmL8u+91uTKHbVgG7tquw9BcOcPTd11fXv3xXe/rt8sWSwK9+/XpGthXQcPT3c0n4l9sMhNsiyiNVa6fHbUEU2+6mambP73f9dtwDxQAIyOL41Ogx3NG24OR2e/XlNvbT7VJLzy9BCPBzm+MTxGAyhRQVU1okilahwEpFA5GiE8V6IudULiExZghdGvAhh8yljAwdZc9eir6jKcCpcIrPr4jjgpZiDdD39Mf0y31ewf0J9nJ3qf2lZufOecaZtuLd2frMcj9/MT17hL18xAt5fOQKfFFL+5WnhLyUAn/PpWNMSi75u1OGDt6c+uSL3T1SLs+FakIcUd/N1z982wDxpuZDGDrkC60yF6V0P/z0xvz4ON2iqjNNaBRUZTOE7rx5R0Pf3FHoxma6bcfmUJ/N1ZfNCIIab/vpLbpiavbd+w7Gb8p+2g/UgMr2kJCoQV/QzIpNZdPmZoB352sOYn8H1Np31WKlTt/13a62yO5pTip/49N/5k/4zM/h3GZga+hO38yF2zS/ncq37SoDLI2Dat3NRbYIOoPUi3D//iLHi7aDnbvdSrg424/DRT3Hvz/Px/58VlCl/zbLze0wx+rx6AOy8uLkUFsnbxabwO2LJcBOJAcjwK4+b+bDoyHbX66vapdiKuEvTv7x4uS+37Xp8Umb+GxBJ6QA+Nz7zBUVFgIBaIvTXvOidGQpRVxDQQVbMnkh8LTUlSHE2VOxd32eC5/1RqlnJ4uFS9+f4GGhhQNlfES76QSEM2jhY8gxpPdPby5z0LbOQIjRsJ0WVisByPMiGPf7AbMfbR8C6KCbxjkyq7rlpLLAWFGr/3AEyOdnA5X9WJ10C6sio+0dzZbN5djR9KEfUOQzkL60sh7Vxt32gBTke6Z9/H5okvp7GmjONRzAWFKnpDk0J4dWWUMPM/fPfS2osDwHakdV9DIbzkVzujSPq6U+/WG6Hfr9zW3NGaZZMNNdQLhqx0PD5w0GsUp1y3zW5D0dgcbVdq50N1UzwHyv9m/zxQwdzWJxs2Zi5uqLGdSbMfX3wInVbuBKmgfQBoS5q4p/36NHchMJ0Qcr73rgxQEXnk7FNS8AsjuaG+zsm3YAgiE5DZKVT4y78nMYD/SMbs874FBXL5X58kHXoUqbpT6hhAt93nzVD7Vr68XHF4PBXIPdTbNA89MJAHGt3Tu+WUcOyESM3wItqWsO9X/efA+EXNB2nQ1KaHcnh6lWOMDy1L4r6IVhasFk0wbz6z2Ke+y78/+OyxwfeNLZk1A+V6pjGJg9JjZpWAnFkMuBCWaiNi5hquaYrzBoYsjTmKBLInVAuVqqm9mqzRLFowYVMsY2n6wu2ZfEMOliSnfG4lvuiacY6onVPlU61pDtASZBZOF05uwVDYcN4KjDW5e0xTaSbCwsk/QZoEVeG4yoPmWMqZZFS1rHxK3UNmYM2iXKiMGclfhMx9q0R+ECiBejL8opE6IqwZBwmE4NxmstuUOMuOZEUggvpTFBc8jMxVmBqUE8D1FlpvOU/zhKz9lmo5LXlkvPQ8oq8+JMQpxNgCCvAANREWlDOniMJp6SKNXV7LkOn5Z+vutvqgaHx7EFIcpYNCRyCrOY14QoZ0MGuxILlDC3Z47MZIP1xFipqmsGCXuuYRrah8dNnc2OHkiC3RIUAvRlzCVKEqYFktgvpBJFZyWxmwRsuogSWZ2MDCZYj9AxF/z/k3/wwZugXEYuC6IFN5TXChwUsHpag4rRyEhihSxmJ8xvxMFUEisq+Msat/LQU3L5KMtYEMmwmJPxCrsiKRGKEwqJFkhK3cxkNAxbo3BYNaQWoTopvYb7jJ+ynNplS1/X8R5j0/Aw/35UhTUCqx9Wlph8pAidTgcEhbBGY7nRRlCJ0YFJQa1ORxSYZIELIk4qrrNnpa7X2g3UGyI2cIYFkiLSaLPAmsKcFuDGjAL1tniLzUoKULu2EllgpOG4xKYsnwv/uNNSqOKSwQoG/31x2FYRd42V2sVSchJOOU0mpoAtVye4yNCZBWst4/4YpiPtblYCPZUTNkcukilIhrCaK8YR+oTOzZIRmVAYJ8wbESNsqdFBHzJM5ckSBpRwnLsWmN0sMLsCByC4L6dQkU4MCyVnHBsqhhxtPGoYpovCfMU96yMKCwHEGoq+0ArlJyzuJ7L8WLhP872sa0cVNVoctCVdjoAopBUoS0UVYUNwRlNG+lWwKQB/BY/IERPAENjCbJHuoKIOENt16jnA+fn9Y1VBIUgOCEIhidrWQROPyQIPmeTYwJVVFphoi+WOSYUlxkslNc8c9ZVDOalYWPBFkCQhjWAIhSopSGYWiLutbxNy4rqgygBUyIvMhQNaUNkoZCsQPgtO8cfOQ79tT29LDt23uJAhFsVHCZH2LkEDR++CARJxEXWSqDRAUYBrUjPhYbkqyTldNFnwyus66jqzKhDaoSu8wdOKC+B+ESxictWoECCsR6Ng4avlQA6bVPAJJCcy+AnUYaR/oqCa/RqRei+jY9GDtEggu6heDMPKCwuvCtMo4aSCdrk6yyOaLSQblJEV0LhML1VgiiCs9kf5GJmFAQUncKXimMKZDhjLrQ/1dVFwgMba3SI7EQXXCslJxaCaXSaNHfEg/yMCUpjahJFMg0QZIAgTgJPC4UfikOAiDgCIUgDS0RrecfCOMdhShcV1+argA25r7qyMEuEAeQK/guGOArgeHecdq0MHyA7ZtLgiSyAbiq+xwkYhAORH4cjlRyEJ+OM5MzyCzMFkWQgXeX39paQEyYMwvHIJTRWBr2AgAWxH9QhOVgp+JLbljd9mebv3ovwjcxzdJbKMWXsJb2RhzNaXi5QccRRmnV1quVhbC6AQpicLgAKEBXmcLF7hTVEQXQLgR5+MFl4YaALFG/SlqG+5anFwXMmESSsHDGoCQ5hUjHl8pE+IPoQe2A/rEF2WBYpHgdW5d6aAXpTnVUAAlqFIpZEgbYC6B6+COq0tKpblzc1nf3/2Pyef7p8YFwAA\"\n },\n \"rollback-deployment.json\": {\n \"sha256\": \"f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 8268,\n \"content_base64\": \"H4sIAAAAAAACA+1ZW1PbOBR+51cwfsbEDrkY9mUZbmUW0gzQ7uzsMB1FPkkEsuWV5NC0w3+vJCfEF9lO6WX7UF5CpKNz+c5Vyued3V0HJeQ9cEFY7Bzpb4noLHxnT289kjjUi6eQULaMIJbZegQShUgitfdZfddM4phJJBUT8bKol/mM4XBffyScPewT1pEcYcV35hLDmsRTxiMI3RAw0UocGQ02Eo8qFJ3KitHKCKxs7WOWUhZN9gXDBNEOCiMicsZytoDwj93L0fmp+/fY9TxvuItTqVa5Y5g+Z7ydGEVgVdjJEYgE4WaqNDMb9YMAfA+5h17YdXtBcOAeDvvgDsIwmARTwOiwq+Ub6Y5IAG+wVjrPOAhxCiikJIZbwCwONe4Dz1uJ4Qo/gpFe9F+WFkaTN0RIxpdXJCJSb6+PCKCA1U7efxGSeH6FJkDzbjVBk+w/phPgMUgQ2q/1+KxOPRfgFCoOJMyWeWlymRgWN4A5qN2iAyRECdWref3KgdgQjD8oPnIqGgm0ipYVL8yihMU6pY4y38CTs9d8pBXiNcg5jYqxkzFW6kcsjeUt8AXBcIyx/nbHHkFbPUVUQM4gFVwSqTDj2qh/cxrmDVR0EC9KBFWiQiZpTD/8dfbPyfH47MPl7e27s5sCAivyBaKpoZ9LmYijTucRl5zmlA49732lEidvR3fHl6OzG/3f+eVFoxrKc/GUzFJuIqzDFXQkgv0HkfdD0RvZ332Rq0MiNDMclTdn8MDKobj6Wi13f4o56vYHR8PBAT4Y9LwuDiYwxLiH/Unoo4E/6B6oz+n00PcHYb/b7fnBcNjr9ie9Php6foCxpyoOcmwKjVNKx0wVj6WBZzpicqyqzbr258gpWUCs6tCYswmUAt4QTBGhKYe7uTo/Z1QXvoMyssarFyAt53WpQ3JuMJ8DonL+yeaYhHG5Dg/bvsBzyHz95u5u3BorTgKcsHBTVNcFMs8yxVgZnjfMrxDpqGCp3DDq7jQIbu0vBXvFdpn2krrjDKPAC7y9+lyoQ1AVQckwo5rm7mT8mnSzaOI3aPLE+CNlKHyNNo05p/pKSH4H7fcKWsWKpVyxtSFJ9YQh7BjhJNXG9T0vspkfQaRGFE2h6tw1abefw38piDZpLbIGPYuoRvsF4JQTuTxR4Q0fbfHgIErZ05iTBaEwgzOBETWNo9pp18qiBE0IJZJAjTkhZ4mlAJi946srp7J+vwV+KHwb0+UNY/JcaSqWQs1bSojkKTRDIBGXafI1+eR3fydUDZoSeERiEyDXirfux2vjQ1h0ctsuZTOn/fRLJ9deLdMvGE0juNbT35YdxcyNG5X06N1Qxs3+K7pFUYqasDptXbEgdjWSfbtkGSVNcvT2N8soTJNN0loJ1zlsy9rXaLZA3GAv9HVMiqoPOkkWXU1Kq/Knbkq/hLYoDYlsUraW4Ofrurn4NSm8oXJRQn6G5uvBzOUwI+oO3+j8rYh/PrarWcEelmvdBZFSv1I1ErfovlPXgzc7ufnUCWOxKdcnNNVSzwkX+ZDUE5duuBvCY/qElvkoMa0uTFXajdZlGKYopdJ92ShQN44wzlRccJYmpgN6XqG/rfdO5ijOd5q3sZ4irokwr0fFRw3lmeMGjmZ7xAyDFajV/XcCuPW0MkW/qqhRZKqbXXmW2LwvZf3hNIPFsTrmuQBR/p3E+l5TolkjX6XLtegLjjCMS3NGr58jzvpzy7tLodmW74r6YVfFUSzfG1YnFJHINqVhvTGqu4K61UaeD+5CkpZfhaJELk8JtwlVIkiq1XGusxm8OmKRT7B+qHT8yk2g7g5tGQB+OR1Lw0OTflnnv0aJTcFVcl+zULPtHlZn69qXBXeFkzvA3a4fDIPQ6/vbaV8cRra0o3Eo0Pf6B8ASwi2sDIKqG14uotV70WfbTUlYXz9tpLvZaABx9rY/pfDRRamcW1pCFk8fE5Ihs8nrg5e3+drrjTQqWGied9pW7l+Vm01zj5P1z9f5gqi741d44hGWG/ttILVBVIGjGh3GmvoCl0HwTSg2DmO/dnTbAvb7xuvedlo3VbrW2KrnW4wzjPYxlzXJW7BwRVlD+Gxdv69BLR8mLlfzjVvP/Ucl/A9rJNVJf6su0nhsm5xrm9L/hyqGqWoUatbOBDdWsyLpK6oaS3TOIlr7lthW9qr41cfVTjn2Vj/o6l+pd553vgDnT+7LTCAAAA==\"\n },\n \"rollback-preflight-full.json\": {\n \"sha256\": \"0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 21208,\n \"content_base64\": \"H4sIAAAAAAACA+1cW5ObOBZ+71/RxesGGzBg6H2Z3s5lUtsXV6czU7NbKZcQwlYaECuEE0+q//tKAttcZBv3ppPZKuZhklhHR0fnHJ2L9BXfzs7PNRDHc7hE8DGfZyDPtYtzRgv0SgyRIEd0hcI5YPxnzTIsVzcN3TQfDP/CnlzY1shzTMNz/2YYF4ahyUkpYHiFOEuQLlDI50Ugzkt+GSUMQcYZhoCBAORoThFo0uQIUsS6v4coi8k6QSmbL1CKKF+FpJzCcuSwoF/zWVmMIRCbMCtuIObLxUKiHQc+/I2Pis1n+DdE85IV/1eWj1em3AcffMSpkEF7vZtYjSSIAbGFLSPBKk0Jk1LltZ/FAF0QGI7EH1wBn0eYjBkFkHNf6FgugNOI0ASFeoggFsJcSEl26150KMadXyrZ5JKdwREkRUySYJQTiEE8BmGC89q2KeFm/vv5+9u3r/XfZ7phGNNzWDD+K9Uqtk8b/tzCCVKKrTVI8gzAY3RFqQDgeB4yDaD7RmjptudNdH/qIN0NQy/wIgSBb5VyVFJoeYZgXft8BwuK8vw194MYp+gDgiQNhSVcw9gu1/aP6seVlOpXnDNC19c4wcJDzN20HMXcbQlt2jUBDC6vQYDipsGlW2WjxyJANEUM5cLi+3W2nffUUXTO/YShxbq5LltnktW9OCl8vGsghpIsFiNNebtOe9BxX8yTGsLKVWKVFpV6hCTJSFqeYWk59EV7dWxSD+Xv1N+Qre1n5QJ8OwkpUvaBR0cM0SWE4l8P5BGl9Zi1ncBdkQHulFRs8d8NaZsb5rQoXXWIVISNkyi0Pf/nmz+uLmdv5u8/fPj45r6llWrCCsSFnLFkLMsvxuNH2DKo1pn29OoZwlzd3T5cvr99cy/+9vb9uyPicLumEV4UZVgfU65OnKDR57xpo7alyv8+tXlrOAELyZfbe4E+k7bTVv/sBtFf8iWwHPdi6k7gxLUNC3oBmkJoQzMITeCarjXhf0aRb5pu6FiWbXrTqW05ge2AqWF6EBo8egFNLdKsiOMZ4SFoLRUV3RI241Frl1nqZ4LnrJRHtBklAeocDUkSARwXFD0sOY8liUUonXT1LG39DjElDxE6AVtKGyx5smTLP9WmyghlG8dRU+S8iig94NeHh1kvP9IyRDEJd8F6F3TrjAsIuSLq2zQVZMJjSMF2zKyzIwL0yGSN/ef9T+b2yM9KvXmGZ7w6dGr261VUTQSSWFA9XM2ef0AVMpkHZfpC6GNMeCH2TLmOnlNRteHByV/WyTlDUlDOXK3fWNQ7+T69wawQm3UMI1ErJEEJL5pke+C4N7ifRij6T4Hy46seXdO1lUse1QnvMgqK2fqKHwn0Ve01ojMiX2YUr3CMFuhNDkG86To6OX4jOMhAgGPMMNq7uZCSTBlI5Ojl9bWmGPnUU68gvEvj9T0h7C2XOl/nvBasWrrjSmGAsiI77Sya1nAYTziMDNEEp9KNbvgKoibYKCNEq3FtWI/JQuszf1tPCIt3Z6xIXCToRlSoJ+QvWd/uRBNtw8FUISmenZmaq/Hqb3w8IzeWrwrG7yUBS7LD6wmC77RWo/I9vGoP0k0EUJ/558q4AlRapbycybvWGWelFx4WnwdU3gX+peQGRYjZYbEPkPwsqXft7WHRd3Q6yPCP3MOmdNQpWuCc0SOu0ZP8Z+m7qlf2Oe9mFzlmTNzqHSE/uouzQ7m/PtqoqLUwzXfJ4CouhARvMc2bzivqQZHod6SX8RewbnqSTK1hwY/r7SbAowgUMdO3Ay36IwWVFuXvKCkymXENo5VNN6NX8tJ4J9ldKiqZG5zLu7b2RQ+32eVBrpLglkgmtXvtNsXHHNE9HPi2xI0TL4kikVy79czuNq7MQK9LNWl7TfbUUlv9BmnPnVaLamMRFWWtPHhHAUSzVrVjOw3ysjbocSvVSPPd3lhcoXNvS9lvkuFVDHCiriChGLrd13brqjKieRxaR7x7f5ZkbP0aU/XyfDFcCNG0m7KHUBV/+E+0uQTWTEVPs/8eQVmG/KUl7hQyx6Qta5AbkKnFrcLEDQkFe8tXdQd7b130Sn+6Cy3L9KZeaDhm/700y6OTdnWkRBF3HZ/ly1WvXXueykjbJlzV+X1Td4O58oZZTXxeliooLV9dohh91UHBlsokVHre1wyX2tpFiEntvWRv48akIEqqp7M+v336n8744cqsekJ8vqUw75lPtNMjWu/0olbfceUp1KTyI7m7/SG0VMp30e+R8vH/5VSo3PwlvPxV/x0cjqI9/PAQ96ZPQjCClO0NAo0dV7R7SZ/2jHzaq8u6K+mU12H6oTV+RPB40QTW7WN6Z68jU/ue2eM9yE+MjzDm6Yn3EKUAR+Jkk/jZ8ZJk4uSD+MCt7bGQ2tXpYQ88U/nq9nn/bPN/aVJ+5tIQ87p3AKgMAJUBoDIAVAaAygBQGQAqw3PhAFAZACoDQGUAqAwAlQGgMgBUBoDKAFAZACoDQGUAqAwAlQGgMgBUBoDKAFAZACoDQGUAqAwAlQGgMgBUBoDKAFAZACovAlCpvqESogylIU/e9ZvjylmThrO2QCtbvIp8ZEwKBgLZqdS7nS2U5Wrr/NshJSzgACjgWXiW8XbhPlAWdR3Y6FxeBpdQf/k/sUptzewBftnBX3zDNCehFeqOHfq6jTxLD8xwovtGYPsoQKEVOF2UBwQpSTGvWOXnfubly7Vg5yFou1ZoWhN76the5BguQiiITDSB4i176tqeYfrQA1Nv4hl2aPgBhFM/MCLLApHbRNpkK9jP+TY+NlN2gz/Y35RC9PK9Tov7fX2twTlbtZAqAdehLm4cYlSWVdq6dcenmMOLoFAP1rp436VENjCqiWWzPwq4/lscBO4JLJAuBJcbLDlQkMIlooIiJtzPdBmZVTxb7CRciusrLbOIZk38kWuNLMMZ+dZoSXI2kvdFSH7/io7SVjLYw/YUKZVHOsI8D/BeuYPD0ZorcZfXq49pNTrJT73CQ8uBTg8HoWcElhOYummbQLdtM9ADrjvd9yJgRaHvBVPYDQcdpJIG5COWSOWd/d4jEP5OeSK/E92icoP7no33P95qlYXkLcQ73ANcVdLzs5nnm+yr9LTtI1bpT7WnzQ7Nho+w4UmKbAe9ebYEuWT1D3HAtG3CFoFol6Erm9m+77leZOkIBFC3w8jWAZqYeuD5EDleOHWMDYyi6nka6MWq5CkhquMYRwiuYe01TyAXycf76zp061Rs02hRa5Y1xreB2H2FfZT1lu8GBnRDzzOR5zihGZqOZxgT/hPyDRRNfMNyLdQCYnLnYtWNY31HfQGgZQRoINJ2R7EqRkf5CjYXlQg8wJrfvxPvzvI9uS4Hdy32wEMEL+H4ug+4dI76p/amF4ZxYVv/qsnEE1GRy2dlyW/dgp6u00ZGFOEaUBQ+kObxU2vmtJBwsoY6R6zta/387UV87rv4nRopS5/DT2Hwu4LdRR+EgTtVurDCPKTrOS//5KccpfdtP+a4rQHmJI3Xcwn340V9LAudLVXZ08xDHEVz9FVe2BqdgV0lhyZ8J7Zt8YAFTWjaPoiCyIae77tR4Fu2NQXINpHt2n7gT2wIbN/xfTOYeo4V8L2Xn4wUOBYIZF8wh1kxF7fJtlFegGyiOe9ANkMT2y2HavDjGtj46ey/h/jiJthSAAA=\"\n },\n \"rollback-preflight.json\": {\n \"sha256\": \"4c3c3166205808e81326ccdae1b56afef757b0ceb4e6c96a17bf384317aeee4a\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 1872,\n \"content_base64\": \"H4sIAAAAAAACA7VVTW/jNhC9+1cEvm5lUxQli/4FeyiwQJpeehFG5NBmQomqSAU1FvnvHcrfSRZoF+1N5My8eY8cPX5fPDwswblG7VG9hGaAEJbbhzhO+EsK+Tbg+Iq6gUjbS854leUsy/MnJrei2Aq+qsuc1dUXxraMLeeiHqJ9RYKEfoea6gy4cMQbRh9RRQLUEKGFgM2IcJ8TUI0YP+5rHJw/dNjHZoc9jtTF95TByzmc8g9UNTirIInI520Yd54W3+mbVpNNmEshZV3VhmcIrcqENiIDLPKsraXCstabkulZS6Ljp1HhBSKJgLhPKEdCa2cNqoNyeKqYuQz+98dfU9I+xiFs12vjxx0++5Xyk/NduwpeWXDr03Jte0roUGcalQ0kbLWz8QoYSQbGR3ydYwlXyaplqtJ1nWNdljrXeVkzVtAWSoamkHRXHPlyhng7idEYou3PJ3dR1EOHYYBZ5vIDlSuNeRjGW1kvU4tjjxHDSqOBycVVeFX3TWGKvoN4Pwq0v0dw80leeDgI8WmEPtjE8Ml2+H7qNjRlNHV/3HCKEKd038uvM97hvnk49Oq2hfLdACPqJ3+z+8OT+Ten8xMndEP081n7Z/P2v8zcfzJ3s8DFO6FE9SfwPrnwb1P8Zn5LF7w4N3o7WUi6hUaPZAhTP7vaPH0XXwPd2ZAoNL53h8Z2sMNGo4twm6WcTW6jrTEN/mWTBbIPgbAHXlaJDhakRAgua6NylQsJpjVC1VJWppVc8A2gyFFUQrayEAqELKXM201d8pa0H92T3NirZI6OLHSYmo6gBWPsZHJ/TjSo5J/nUCGqYyhEckTcHRKTx2ShtDoimokMfkSFdog3dFle1xspdVFyFFpIXSrgUleYy81GcM04ygpEy9qCskxuxKYm/i0vueTYlp+Anyd19M61oF6yYUTj7G4fs5S3eg6n/yU9BM/0EKQboBQcsVfYGOswXM060P9MSl16TK7mn/CPkSxFsmvkCn8tpij2mtDtjHyuPD5R2W30rlpBry09Ue8bX4S975pGb/G2+BveBvmhUAcAAA==\"\n },\n \"runtime-receipt.json\": {\n \"sha256\": \"6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 808,\n \"content_base64\": \"H4sIAAAAAAACA2WSy47bIBSG93kKK9t2Wi4GTJbdd9e9deAcEjQ2RBhnJqr67sXjzjRVJUu20X85H/Dz0HXHQre4xJyOp+5oXe+cUCiF0nYAUr0kQwMMIL1UVqDjQQ3BHz9vzuvqprhcCEeMZ1rqlrBcoHlPQalhCL3VzSC0QWY8ehuUYAPpIILoeTC9M9J6Ia3oHZOGCSTPJai2tBfcqMQQW36hc1xquY8+pxDP//dpIRx5L8mhNd5p7TQZL3WQygUrOZiWjbbXvdNqUJwL6bn3g7d2QKPc3kev5Nfa+qYMuGEBzTmNER/JBDFDEnGQ1ikFARUqbkQvwWgFQlut2vZRT9ooFhQbjOGASmmHGhnfm+IMZxqXvBZPWzj4usLUQcrpPud16d7RO6TFl3ituXyd4E6lg+Iv8UbdPmN7eZim+x7rIWFEqDTuBe9AraKWld40iepLLs9ba2uj3VgIcMxpuo8l5/oon9sWlPum5mL4Hr/9KbquS1vj+8+F/DOVBxx6bUDdOdblkl+6a0yJ0Od5jnW3J6gNYbyWfIvYnHU/zQDTQo+CbdCNc/QXSOc3jr+axgkOFhpzGQHnmLaPj0hfCCnV2OT/uD7u1Fr9NqpgQj9x9sT5D2ZPsj3mi2JCGPWJsRNjx8Ovw2/MQgIWKAMAAA==\"\n },\n \"scan-receipt.json\": {\n \"sha256\": \"aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 4538,\n \"content_base64\": \"H4sIAAAAAAACA+1VyW7bMBC95ysCXxtKJLVSpxbouae2hxSFwTVmYokqSTlOi/x7KclwLGdBkybooQIM0Hp8M5wZ8s38Ojk9XThOm0baRXW6oD866iSPvdWbm/duRXGWVzlmSOaZLMucMJ6ylOeUpopihlFGskDhBccqh6ikRBAqcsSpKgtUFhyRnCzO+lMEW47++nNgwilnKUwzzCUrUqqE4hQWmLMSq4LIcFgqFOUQ5lLhlIYPQQVKEgYVQcXeZS09FdTT4PRXgAL4VVqnTRMAfDYin+TWf2kDS/ZHY4hzgCBA6DMqq4xUGYzKkkAEswKfL3Y2I1988BMTeGxCcLI3+Wium7Wh4gErnFQJDr8oKSFMEpSg80Uwuh2ycLSWS2WlWy1DQsz41VLX9EK64MTbTg6kxixd17aBtUtuv2MlN1b05G9DHGMZwga1fKU3Q84sXCoYnEae2l3AgaKMrekQ7KULXve4C6yQ/bLz/KhkkFRJWSVpVBRpXuB3EFYQ3hnKrfZLbkR/Ktyj3NQ1bcQ+xAEUhl/Ju1gCAgA3jdIXh1js6zbWjRKg7WxrQhptx9baraQAG2m10pz6UJFY1q2/AaPTB/wAsDLOHyJdo7dVHMcbamPbNfFoGrmwHNLC1tSPraffjfTXxk5sGtPIo8Tazh0iaLpdy9rYmwlhGv3m4Zr8XGt2UBDCUsZwJqo4XJ8O6vEvcNJ3AxDUuZJVPCyHVm/XH3YHDI90WhzdtN0kkX128SPveix5HzsQegLfzwgAd6VbIBjoxhZxf++SbuijhLF3Ti53062P3sxOZwfYRG8Dx3T+6TyHmgPVrdfRYL0jfh/W27O/ED+/4evwZMX2GR2gjMosx1k6d4C5A8wd4AUd4L7o/qwNOGbqiIvt85rAaw5/EhWYFKScpT9L/x9K/z+Y+y8d+a897VMY5UUG01nys+Rnyb/9oH9qxp/0/25PfgMU+IvRuhEAAA==\"\n },\n \"sealed-live-deployment.json\": {\n \"sha256\": \"f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 8268,\n \"content_base64\": \"H4sIAAAAAAACA+1ZW1PbOBR+51cwfsbEDrkY9mUZbmUW0gzQ7uzsMB1FPkkEsuWV5NC0w3+vJCfEF9lO6WX7UF5CpKNz+c5Vyued3V0HJeQ9cEFY7Bzpb4noLHxnT289kjjUi6eQULaMIJbZegQShUgitfdZfddM4phJJBUT8bKol/mM4XBffyScPewT1pEcYcV35hLDmsRTxiMI3RAw0UocGQ02Eo8qFJ3KitHKCKxs7WOWUhZN9gXDBNEOCiMicsZytoDwj93L0fmp+/fY9TxvuItTqVa5Y5g+Z7ydGEVgVdjJEYgE4WaqNDMb9YMAfA+5h17YdXtBcOAeDvvgDsIwmARTwOiwq+Ub6Y5IAG+wVjrPOAhxCiikJIZbwCwONe4Dz1uJ4Qo/gpFe9F+WFkaTN0RIxpdXJCJSb6+PCKCA1U7efxGSeH6FJkDzbjVBk+w/phPgMUgQ2q/1+KxOPRfgFCoOJMyWeWlymRgWN4A5qN2iAyRECdWref3KgdgQjD8oPnIqGgm0ipYVL8yihMU6pY4y38CTs9d8pBXiNcg5jYqxkzFW6kcsjeUt8AXBcIyx/nbHHkFbPUVUQM4gFVwSqTDj2qh/cxrmDVR0EC9KBFWiQiZpTD/8dfbPyfH47MPl7e27s5sCAivyBaKpoZ9LmYijTucRl5zmlA49732lEidvR3fHl6OzG/3f+eVFoxrKc/GUzFJuIqzDFXQkgv0HkfdD0RvZ332Rq0MiNDMclTdn8MDKobj6Wi13f4o56vYHR8PBAT4Y9LwuDiYwxLiH/Unoo4E/6B6oz+n00PcHYb/b7fnBcNjr9ie9Php6foCxpyoOcmwKjVNKx0wVj6WBZzpicqyqzbr258gpWUCs6tCYswmUAt4QTBGhKYe7uTo/Z1QXvoMyssarFyAt53WpQ3JuMJ8DonL+yeaYhHG5Dg/bvsBzyHz95u5u3BorTgKcsHBTVNcFMs8yxVgZnjfMrxDpqGCp3DDq7jQIbu0vBXvFdpn2krrjDKPAC7y9+lyoQ1AVQckwo5rm7mT8mnSzaOI3aPLE+CNlKHyNNo05p/pKSH4H7fcKWsWKpVyxtSFJ9YQh7BjhJNXG9T0vspkfQaRGFE2h6tw1abefw38piDZpLbIGPYuoRvsF4JQTuTxR4Q0fbfHgIErZ05iTBaEwgzOBETWNo9pp18qiBE0IJZJAjTkhZ4mlAJi946srp7J+vwV+KHwb0+UNY/JcaSqWQs1bSojkKTRDIBGXafI1+eR3fydUDZoSeERiEyDXirfux2vjQ1h0ctsuZTOn/fRLJ9deLdMvGE0juNbT35YdxcyNG5X06N1Qxs3+K7pFUYqasDptXbEgdjWSfbtkGSVNcvT2N8soTJNN0loJ1zlsy9rXaLZA3GAv9HVMiqoPOkkWXU1Kq/Knbkq/hLYoDYlsUraW4Ofrurn4NSm8oXJRQn6G5uvBzOUwI+oO3+j8rYh/PrarWcEelmvdBZFSv1I1ErfovlPXgzc7ufnUCWOxKdcnNNVSzwkX+ZDUE5duuBvCY/qElvkoMa0uTFXajdZlGKYopdJ92ShQN44wzlRccJYmpgN6XqG/rfdO5ijOd5q3sZ4irokwr0fFRw3lmeMGjmZ7xAyDFajV/XcCuPW0MkW/qqhRZKqbXXmW2LwvZf3hNIPFsTrmuQBR/p3E+l5TolkjX6XLtegLjjCMS3NGr58jzvpzy7tLodmW74r6YVfFUSzfG1YnFJHINqVhvTGqu4K61UaeD+5CkpZfhaJELk8JtwlVIkiq1XGusxm8OmKRT7B+qHT8yk2g7g5tGQB+OR1Lw0OTflnnv0aJTcFVcl+zULPtHlZn69qXBXeFkzvA3a4fDIPQ6/vbaV8cRra0o3Eo0Pf6B8ASwi2sDIKqG14uotV70WfbTUlYXz9tpLvZaABx9rY/pfDRRamcW1pCFk8fE5Ihs8nrg5e3+drrjTQqWGied9pW7l+Vm01zj5P1z9f5gqi741d44hGWG/ttILVBVIGjGh3GmvoCl0HwTSg2DmO/dnTbAvb7xuvedlo3VbrW2KrnW4wzjPYxlzXJW7BwRVlD+Gxdv69BLR8mLlfzjVvP/Ucl/A9rJNVJf6su0nhsm5xrm9L/hyqGqWoUatbOBDdWsyLpK6oaS3TOIlr7lthW9qr41cfVTjn2Vj/o6l+pd553vgDnT+7LTCAAAA==\"\n },\n \"sealed-native-dependencies.json\": {\n \"sha256\": \"042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 1841,\n \"content_base64\": \"H4sIAAAAAAACA71Uy27bMBC85ysMnUtZoiVLSm9NkaKHpEEPyaEoAj5WNhuJVEnKQBrk37uUZSd+II9DexKw3J2dnZ3Vw8lkEgmja7VoWRedTh4wgCHWqWuwThmNsWiVRh/WcdW2vWe8AQx728MYvlNahsSzAekCkcaHFjyTzLMtcsDW2njmEds9C4cHuzBCxuHTWfMrVmbqLRMIviBqwFe6NrYFSSQIFdidTrctTw8epwcRYnvtVQtkLihNy6KUSb6ZbT3ffkUsTN+YlsfOCMWaKZOtchtdWIc8VyA/Tr5enn8mN1ckSZJiInqPURuNsI8b/EizFo6O8TKvoc51TBwvfsrr1ypVSZrOJJUkz2RFMigp4amckSrhWQUcJOX5mtxILRJMG60Ea27Dsm7dktF8HqBKENmcypTOsiLPyjpP5gDA6xRmohAiK+ZZmaSVKFlRzsokk0nFhSgqntSUsnoe2gxNom4lXrfXxkdX4dF50P7aNH0LZw1T7f/z1NH2b/LXwOtf+ekZbreK73oOVoMHF4biqBwRpu0ajAxz3YN7ucL0WMLvsUp7a5oGOxyWrQYBYo6a79U7byxbAAmUh9HW9ZZpsQQbMhqDjiId88sjiHtg0IBA4kQbObiczqp4TmOa5HFF46VxPnYgegsOLGoRY+XroO9heHCstdKsUX/QClj046nVbg+0dcD3yD4scUz7+YaT37HKe09clgmnOU9JmqWMZFnKCUetSFXWjNayKnkhdk/cdSB2LkYIcO4C5d6b7zsweWOVh28a2RwMhBswvRWwd2cWfvfg/G40tF3vIHBOv6ho+/R4oPeYibfm3OWo2hEHjau+GH1yrhpw93ir7X7GBiNs6B1yPf9h3XZL5gaQT+FYopPHk79qFEgoMQcAAA==\"\n },\n \"source-commit-proof.json\": {\n \"sha256\": \"b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 239,\n \"content_base64\": \"H4sIAAAAAAACA12OwW6EMAxE73wF4rwHIDY2/ZWqipzYKZEKrAgtWlX998JKvfT6NPNmvqu6bjb7yiWvS/NSN2OAEHpU1+MwshiCMzIWFhcdjr2GLiGn2Nz+mnaY+lmWnKzsvkxyNi8T9SACRGShizzSoDywYcIWgFyCMOgp6pwStsoklgQjK0ZpCcj+L9xln8rpJXjyOZdZ9jjZxV7fnmw9Ftv8ZqJ+XT4e/j1ff9bjTOzbp1U/1S/dW3277wAAAA==\"\n },\n \"sqlite_readonly.py\": {\n \"sha256\": \"9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509\",\n \"codec\": \"gzip+base64\",\n \"uncompressed_bytes\": 4610,\n \"content_base64\": \"H4sIAAAAAAACA5VYbW/bNhD+7l/BZcAkZbLysqbAkhpDlqVZgeZlSbthCAKBFk82F0l0SSqNW/S/744SZfmlaeMvNt/ujnfPPXf01tbWlYahmkHFTDaFku/IysJESzvfyVRdWTbTSuVHzFhRyDFTVTGPWQUPoFmmKmN1nVnDbqzSkGxtbQ1kOVPaMq4nM64NDHKtStpp4dGSgHY9K5SR1cRvn3IzxVU/VMb/0uB/Gcut//2fUVUjecbttCf2CofdgQ+FtPDLYDAQkDNZmRlkNtXABV0ipJMx245ZSraASE3FZ2aq7Og1LwxEhwOGH9rFRk6uOxG5WZmzSlm3mEiT5rKAMGJKdzNmXhayug9bKfTRXBpgf/OihlOtlQ4DeJTGohNQVtUeQG/LB26BCW75mOMBDR9qqUEEjeZaSzTHqdFgVPGAihNuUpxHC35mwW+lEjDSKsBBGPwky7JGSQWM9gIye/WyDPCuLGil/+idlpyoqkJ3SVX54LGSV3wCJSAmoBKGWc0rw90ec8SkZUKBcX5xOhIn8aNE/7XBDr3wrBEeotEx3Wj0TtcQMytLULUdHUQR44aJ8cJ5YpzAI2S1hTC4uj4+Oz9mH2rQ85RCObq8aO1f3fr76dmb/hqi1tCVRpsE1gZ02u4IoiQHm01Vhf693b3rJKALvZAfRuxgYeHmEM98cnXxlI2LmmQ7OML45qiYlXKiuXWa+7o2mNnlZ4oisntvKS8KBADadBsG6j6Io7tv2UaZMCT/LUQyJ7K1SfRMKWQO2TwrAF33ueIlHC6ZdnP69vTkHTu5fH/xLtyO2Ovry3NGCHTQ+5ovlz45Zg8JjpszaBMLycZMaWGCmAWdCamfjOLNojZ9MNseELkrktq55wnCcApE87IkPxlFXxYZD6YuLHksaMLdoevQg6gvA2e735sMCjA3xuoRtwmJybPB/0SREC9HoTnEzq4v31+x3/9t9qCZGzWYelxKQ4aZZ6rpnfxOXR3oUJNDLHOIdBmdYt6klDe41nBDYKjCpF3JQXQeMkfUX/qZr1VRjHmGxDtoImBrXbWBWCkEK0TY1gN4pEWan/L9g5ctf2Nhu6RE4Z6f8TfK5rICETMsRJ8wxUscDqkUsI5cHftViv1z/BZ3IbpNMnACTzBdsYKWtUEyNYR4aaaoPCtqIx96EjL0CdHCDhclpgSWDLuDAj+i40DHjFfCyTP1bIY22SkVDMoOEH2zMJQTVJOwC1e4iXAo32h7Qeo8OzWc/cbXDHbz11vUwyguhslJhSGgoNLKn+ek3HcCZCXsIAtVE9whBWRY/Btpp77K+WkU52zK8PJKzBk84EKVwRER45R2IkcKKADDsOMIaaYILCLxsXi6MmMjktdFUXLknFAHt7vDX/kwv/v88sWXYD3ApOwCqenpQo1lzvuzC03j09UCTRArERrk0tBiGwS21wTIKldodDOfFJQg4RLhu9qAs8lN+ubm+vQspBOJsSmVdddi+NXzyz9OV1aR/XfVy91d2uZXKtdW4Mret2tV03pQqS5g6M4tcNgGa+W2vSQjhW7OBXe0cMJa27TLXrlNZJ6Rn4C9wkq6t8+22d7u/ovt7f2nYjHGprSH7s7ADWHwgOt3YQQ1K63ETgV5ecEdlA6mznP56CpPMPzIC6KkoZmWQbTsuiZ6HnvIR80Vsdw1EqKl3XhpZRLXsRWu4zPrsFiBxyp+1vflXXxrKSi63p807EV/sryKw8ONpW7dz51jWy92CGiSvA+AnlWtC0cj1njQcYQ3xsWaMPpsG/B50ONQbDY7hiJq2tCvrIf7tjHtDt3bZY2Ah7izTlYqXjJ1MSqpM00rs475TrxbGUNORaojulEPgh0qG9roY/KBroyb20dQ0jBTjxdcGXEIopqIXdE4cB0yQg94uexNAvK4UNjFIY6pSIQFL8eCH7a7E/JY2CZaFLNxEGwAojMpqWeIQgiduLW7N1um8Ogv5FPcjynUK2z7VGKvVdGWX1u8tc715XvRKbv3KXrvKw+8tdcd9RPNSZ6je77KVKFb7mDSjRxOuhHmW280WRoRiJwXQp9/TpIfOEG9vI37aRovMWT0LMe1qFxN1adD40pLh1basILm7zBhUS3amAl83FE9X41ZCyEXOmzL6F2aUvOfpo460pQuk6aBf4Nr4wLl/1RIjvWkpofolVsJBZhMyxkpGKWpUFmaRr2TCRci5e0RpPahGCO12/kMRlcOI7529LGhJ8Y9tJ0A90UifCJbPV/4AwsnyqX/JBJRlzMTrkGRTiZijOlmlLbpPcxNo6uRhn0fzCx2SvSFl1h19c3cWCixkbL9V1v7hFz7w2bCXcvmKJF6qubPmbaBdE11xNx/J9T2DP4HpRObnwISAAA=\"\n }\n }\n}\n" - } - ] -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4 deleted file mode 100644 index b81024f..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4 +++ /dev/null @@ -1,354 +0,0 @@ -"""One fixed supervised phase; never run this during source preparation.""" -import argparse -import hashlib -import json -import os -from pathlib import Path -import re -import selectors -import stat -import subprocess -import sys -import time - -if sys.flags.optimize: - raise SystemExit('optimized interpreter refused') - -PHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline', - 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify') - - -def require(value, reason): - if not value: - raise ValueError(reason) - - -def sha(path): - digest = hashlib.sha256() - with Path(path).open('rb') as stream: - for block in iter(lambda: stream.read(1024**2), b''): - digest.update(block) - return digest.hexdigest() - - -def private(path, directory=False): - info = Path(path).lstat() - require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode)) - and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600) - and (directory or info.st_nlink == 1), 'private owned custody required') - -ROOT = Path(__file__).resolve().parent -CASE = ROOT.parent -OWNER = Path('/home/worsch/informed-decision') -CUSTODIAN = Path('/home/worsch/the-custodian') -CUSTODY = CUSTODIAN / '.private-backups/20261011-infd-zlib-r1' -SOCKET = 'unix:///var/run/docker.sock' -IMAGE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23' -VOLUME = 'infd-zlib-r1-restore-20261011' -MAX_BYTES = 512 * 1024**2 - - -def run(argv, body=None, timeout=90): - return subprocess.run(argv, input=body, text=True, capture_output=True, timeout=timeout) - - -def json_result(result): - try: - value = json.loads(result.stdout) - except (ValueError, TypeError): - raise ValueError('safe phase receipt unavailable; outcome uncertain') from None - require(result.returncode == 0 and isinstance(value, dict) and value.get('all_checks_pass') is True, - 'phase did not verify complete; raw output withheld; no retry') - permitted = {'phase', 'all_checks_pass', 'raw_output_withheld', 'native_approval_verified_by_root', - 'native_authenticated_grant', 'source_readonly_baseline', 'snapshot_readonly', - 'snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'native_parent_private', - 'native_uid', 'pod_name', 'pod_uid', 'live_readonly_schema_integrity_counts', - 'live_counts_may_include_concurrent_commits', 'operation_requested', 'source_revision', - 'resources', 'prune', 'rollout_verified', 'latest_live_database_retained', - 'client_diff_sha256', 'native_rollout_verified', 'current_schema5_database_retained', - 'private_copy_verified', 'installed_purpose_checks', 'policy_sha256', - 'policy_mode', 'policy_uid', 'installed_zlib_files', 'zlib_package', 'health_statuses', 'native_provider_probe', 'recovery_verification'} - require(set(value) <= permitted, 'unexpected output fields withheld') - from receipt_safety import counts - for name in ('source_readonly_baseline', 'snapshot_readonly', 'live_readonly_schema_integrity_counts'): - if name in value: - counts(value[name]) - for name in ('snapshot_sha256', 'client_diff_sha256'): - if name in value: - require(re.fullmatch(r'[a-f0-9]{64}', value[name]) is not None, 'invalid output digest withheld') - if 'snapshot_bytes' in value: - require(type(value['snapshot_bytes']) is int and 0 < value['snapshot_bytes'] <= MAX_BYTES, - 'invalid output size withheld') - if 'snapshot_file' in value: - require(value['snapshot_file'] in ('drill.sqlite', 'cutover.sqlite'), 'invalid output file withheld') - return value - - -def docker(argv, body=None, timeout=120): - return run(['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET] + argv, - body=body, timeout=timeout) - - -def remote(phase, revision, approved): - # Fixed module imported only after main verifies every executor source hash. - from native_program import PROGRAM - payload = {'phase': phase, 'source_revision': revision, 'approval': approved, - 'sealed': json.loads((CASE / 'sealed-live-deployment.json').read_text()), - 'dependencies': json.loads((CASE / 'sealed-native-dependencies.json').read_text()), - 'candidate': json.loads((CASE / 'candidate-deployment.json').read_text()), - 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()), - 'receipt_safety': (ROOT / 'receipt_safety.py').read_text(), - 'purpose_checks': (CASE / 'check_installed.py').read_text(), - 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'readonly': (CASE / 'sqlite_readonly.py').read_text()} - script = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace( - '__GUARD__', repr((CASE / 'change_guard.py').read_text())) - return json_result(run(['ssh', 'railiance01', 'python3 -'], script, timeout=200)) - - -def copy_snapshot(phase, revision, approved, journal): - metadata = remote(phase, revision, approved) - backup_phase = 'drill-backup' if phase == 'drill-copy' else 'cutover-backup' - prior = journal.data['phases'][backup_phase]['result'] - for key in ('snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'snapshot_readonly'): - require(metadata[key] == prior[key], 'closed frozen snapshot changed') - name = metadata['snapshot_file'] - require(name in ('drill.sqlite', 'cutover.sqlite') and 0 < metadata['snapshot_bytes'] <= MAX_BYTES, - 'fixed bounded snapshot required') - pod = metadata['pod_name'] - require(re.fullmatch(r'[a-z0-9]([-a-z0-9]*[a-z0-9])?', pod) is not None, 'fixed owner pod name required') - local = CUSTODY / name - fd = os.open(local, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - # Binary snapshot stdout goes only to a private file, never tool/chat/log output. - # Snapshot schema/count metadata was validated before opening this destination. - with os.fdopen(fd, 'wb') as destination: - command = ('kubectl exec -n informed-decision ' + pod + - ' -c informed-decision -- cat /data/private/zlib85091-r1/' + name) - transfer = subprocess.Popen(['ssh', 'railiance01', command], stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) - selector = selectors.DefaultSelector() - selector.register(transfer.stdout, selectors.EVENT_READ) - deadline = time.monotonic() + 150 - received = 0 - try: - while True: - remaining = deadline - time.monotonic() - require(remaining > 0 and selector.select(remaining), 'private transfer timed out') - block = os.read(transfer.stdout.fileno(), 1024**2) - if not block: - break - received += len(block) - require(received <= metadata['snapshot_bytes'] and received <= MAX_BYTES, - 'private copy exceeds frozen snapshot size') - destination.write(block) - transfer.wait(timeout=max(1, deadline - time.monotonic())) - finally: - selector.close() - transfer.stdout.close() - if transfer.poll() is None: - transfer.kill() - transfer.wait() - destination.flush() - os.fsync(destination.fileno()) - private(local) - require(transfer.returncode == 0 and local.stat().st_size == metadata['snapshot_bytes'] - and sha(local) == metadata['snapshot_sha256'], 'private copy incomplete/uncertain; retain, no retry') - # Re-read identity/hash after transfer. This is verification, not a write retry. - after = remote(phase, revision, approved) - require(after['pod_uid'] == metadata['pod_uid'] and after['snapshot_sha256'] == metadata['snapshot_sha256'], - 'source identity/snapshot changed during transfer') - return {**metadata, 'off_cluster_private_copy_verified': True, 'snapshot_contents_output': False} - - -def offline_verify(journal): - from receipt_safety import counts - snapshot = CUSTODY / 'drill.sqlite' - private(snapshot) - previous = journal.data['phases']['drill-copy']['result'] - require(sha(snapshot) == previous['snapshot_sha256'], 'protected snapshot bytes changed') - identity = docker(['info', '--format', '{{json .Name}}']) - require(identity.returncode == 0 and json.loads(identity.stdout) == 'bnt-lap001', - 'local offline Docker engine identity changed') - listed = docker(['volume', 'ls', '--format', '{{.Name}}']) - require(listed.returncode == 0 and VOLUME not in listed.stdout.splitlines(), - 'disposable volume absence not verified') - nonce = journal.data['phases']['offline-verify']['attempt_id'] - labels = {'informed-decision.case': 'infd-zlib-20261011-r1', - 'informed-decision.use': 'offline-restore-only', 'informed-decision.attempt': nonce} - created = docker(['volume', 'create', '--driver', 'local', '--label', 'informed-decision.case=infd-zlib-20261011-r1', - '--label', 'informed-decision.attempt=' + nonce, - '--label', 'informed-decision.use=offline-restore-only', VOLUME]) - require(created.returncode == 0 and created.stdout.strip() == VOLUME, 'volume creation uncertain; no retry') - inspected = docker(['volume', 'inspect', VOLUME]) - require(inspected.returncode == 0, 'new volume identity unavailable') - volume = json.loads(inspected.stdout) - require(len(volume) == 1 and volume[0]['Name'] == VOLUME and volume[0].get('Labels') == labels - and volume[0].get('Driver') == 'local' and not volume[0].get('Options') - and volume[0].get('Scope') == 'local', 'new isolated volume identity changed') - identity = {key: volume[0][key] for key in ('Name', 'Labels', 'Driver', 'Scope', 'CreatedAt')} - journal.note('offline-verify', 'created_volume_identity', identity) - # One-time UID0/CAP_CHOWN initialization is isolated to the NEW named volume. - # No host/live PVC/config/token mount exists; this detail needs packet review. - initializer = """import hashlib,os,stat,sys -if sys.flags.optimize:raise RuntimeError('optimized interpreter refused') -folder='/restore';expected=EXPECTED -if os.listdir(folder):raise ValueError('new empty private volume required') -os.chmod(folder,0o700) -fd=os.open(folder+'/review.sqlite',os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_WRONLY,0o600) -digest=hashlib.sha256();size=0 -with os.fdopen(fd,'wb') as target: - while True: - block=sys.stdin.buffer.read(1024**2) - if not block:break - size+=len(block) - if size>512*1024**2:raise ValueError('private restore size exceeds bound') - target.write(block);digest.update(block) - target.flush();os.fsync(target.fileno()) -if digest.hexdigest()!=expected:raise ValueError('private copied snapshot differs') -os.chown(folder+'/review.sqlite',10001,10001);os.chown(folder,10001,10001) -print('{"all_checks_pass":true,"private_copy_verified":true}') -""".replace('EXPECTED', repr(previous['snapshot_sha256'])) - with snapshot.open('rb') as source: - argv = ['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET, - 'run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m', - '--user', '0:0', '--cap-drop', 'ALL', '--cap-add', 'CHOWN', - '--security-opt', 'no-new-privileges', '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore', - '--entrypoint', 'python', IMAGE, '-c', initializer] - copied = subprocess.run(argv, stdin=source, text=True, capture_output=True, timeout=150) - json_result(copied) - options = ['run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m', - '--user', '10001:10001', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges', - '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore'] - readonly = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'], - (CASE / 'sqlite_readonly.py').read_text()) - require(readonly.returncode == 0, 'offline pre-open schema/integrity gate refused; never open Store') - checked = counts(json.loads(readonly.stdout)) - require(checked == previous['snapshot_readonly'], 'frozen snapshot/clone readonly counts differ') - admin = docker(options + ['--entrypoint', 'informed-decision-admin', IMAGE, - 'inspect', '--db', '/restore/review.sqlite']) - require(admin.returncode == 0, 'offline admin result uncertain') - admin_counts = json.loads(admin.stdout) - require(admin_counts == {key: checked[key] for key in ('schema_version', 'lifecycle', 'outbox', 'submissions')}, - 'offline admin changed frozen snapshot counters') - # Admin Store construction must not silently alter schema/integrity/counters. - post = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'], - (CASE / 'sqlite_readonly.py').read_text()) - require(post.returncode == 0, 'offline post-admin readonly integrity gate refused') - post_checked = counts(json.loads(post.stdout)) - require(post_checked == checked == previous['snapshot_readonly'], - 'offline admin altered frozen snapshot proof') - private(snapshot) - require(sha(snapshot) == previous['snapshot_sha256'], 'original protected snapshot changed during drill') - return {'all_checks_pass': True, 'offline_volume': VOLUME, 'offline_readonly': checked, - 'offline_post_admin_readonly': post_checked, 'original_snapshot_hash_unchanged': True, - 'created_volume_identity': identity, - 'admin_counts': admin_counts, 'network': 'none', 'runtime_tokens_or_config_mounted': False, - 'service_entrypoint_started': False, 'original_snapshot_retained': True} - - -def cleanup_offline(journal): - inspected = docker(['volume', 'inspect', VOLUME]) - require(inspected.returncode == 0, 'owned disposable volume identity unverified') - value = json.loads(inspected.stdout) - expected = journal.data['phases']['offline-verify']['result']['created_volume_identity'] - require(len(value) == 1 and {key: value[0].get(key) for key in expected} == expected - and not value[0].get('Options'), 'volume ownership/recreation changed') - removed = docker(['volume', 'rm', VOLUME]) - require(removed.returncode == 0 and removed.stdout.strip() == VOLUME - and docker(['volume', 'inspect', VOLUME]).returncode != 0, - 'cleanup outcome ambiguous; do not retry') - return {'all_checks_pass': True, 'only_disposable_offline_volume_removed': VOLUME, - 'native_and_offcluster_original_snapshots_retained': True} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--phase', choices=PHASES, required=True) - parser.add_argument('--root-authority-receipt', type=Path, required=True) - parser.add_argument('--root-native-outcome', type=Path, required=True) - parser.add_argument('--source-revision', required=True) - args = parser.parse_args() - require(args.root_authority_receipt == Path('/tmp/security-infd-zlib-root-authority.json') - and args.root_native_outcome == Path('/tmp/security-infd-zlib-native-outcome-latest.json'), - 'fixed supervised receipt paths required') - require(re.fullmatch(r'[0-9a-f]{40}', args.source_revision) is not None, 'exact source commit required') - # Verify all prepared executor/input bytes before reading authority or dispatching. - plan = json.loads((ROOT / 'execution-plan.json').read_text()) - require(plan.get('state') == 'prepared-reviewed' and plan.get('published_artifact_verified') is True, 'unfrozen proposal cannot execute') - require(set(plan['executor_files']) == {'actuator.py', 'authority_journal.py', 'native_program.py', 'receipt_safety.py'}, - 'fixed executor sources required') - require(set(plan['owner_source_files']) == {'deploy/lifecycle/admission.candidate.json', - 'deploy/lifecycle/service.candidate.json', - 'deploy/lifecycle/kustomization.yaml', - 'deploy/lifecycle/registries.candidate.json'}, - 'fixed owner image sources required') - for relative, digest in plan['executor_files'].items(): - target = ROOT / relative - require(not target.is_symlink() and sha(target) == digest, 'executor source bytes changed') - for relative, digest in plan['case_files'].items(): - require(not Path(relative).is_absolute() and '..' not in Path(relative).parts, - 'case source path escaped') - target = CASE / relative - require(not target.is_symlink() and sha(target) == digest, 'case source bytes changed') - for relative, digest in plan['owner_source_files'].items(): - target = OWNER / relative - require(not target.is_symlink() and sha(target) == digest, 'owner image-only source changed') - committed = subprocess.run(['git', '-C', str(OWNER), 'show', args.source_revision + ':' + relative], - capture_output=True) - require(committed.returncode == 0 and hashlib.sha256(committed.stdout).hexdigest() == digest, - 'committed owner render source differs from approved inputs') - head = run(['git', '-C', str(OWNER), 'rev-parse', 'HEAD']) - require(head.returncode == 0 and head.stdout.strip() == args.source_revision, 'owner source HEAD differs') - from authority_journal import Journal, authority - private(args.root_authority_receipt) - private(args.root_native_outcome) - receipt = json.loads(args.root_authority_receipt.read_text()) - require(receipt.get('native_outcome_sha256') == sha(args.root_native_outcome), 'native outcome bytes changed') - require(receipt.get('executor_plan_sha256') == sha(ROOT / 'execution-plan.json'), - 'root verified executor plan differs') - approved = authority(receipt, json.loads(args.root_native_outcome.read_text()), sha(CASE / 'memo.json'), - args.source_revision, args.phase) - parent = CUSTODY.parent - private(parent, directory=True) - ignored = run(['git', '-C', str(CUSTODIAN), 'check-ignore', '--no-index', str(CUSTODY / 'drill.sqlite')]) - require(ignored.returncode == 0, 'offcluster custody is not ignored') - if not CUSTODY.exists(): - require(args.phase == 'drill-backup', 'first approved phase must create unique private custody') - os.mkdir(CUSTODY, 0o700) - private(CUSTODY, directory=True) - capacity = os.statvfs(CUSTODY) - require(capacity.f_bavail * capacity.f_frsize >= 2 * MAX_BYTES + 16 * 1024**2, - 'independent protected custody headroom insufficient') - os.makedirs(CUSTODY / 'empty-docker-config', mode=0o700, exist_ok=True) - private(CUSTODY / 'empty-docker-config', directory=True) - require(not list((CUSTODY / 'empty-docker-config').iterdir()), 'public Docker read config must stay empty') - journal = Journal(CUSTODY, {'proposal': plan['proposal_id'], 'source_revision': args.source_revision, - 'memo_sha256': sha(CASE / 'memo.json'), 'plan_sha256': sha(ROOT / 'execution-plan.json')}) - try: - journal.start(args.phase, approved) # fsynced RUNNING before any phase effect. - if args.phase in ('drill-copy', 'cutover-copy'): - result = copy_snapshot(args.phase, args.source_revision, approved, journal) - elif args.phase == 'offline-verify': - result = offline_verify(journal) - elif args.phase == 'cleanup-offline': - result = cleanup_offline(journal) - else: - result = remote(args.phase, args.source_revision, approved, operation_intent=journal.data['phases'].get('rollback' if args.phase == 'rollback-verify' else 'release') if args.phase in ('verify', 'rollback-verify') else None) - journal.finish(args.phase, result) - print(json.dumps(result)) # Only count/hash/identity receipts, never snapshot bytes. - except BaseException as error: - safe = {'all_checks_pass': False, 'phase': args.phase, 'failure_class': type(error).__name__, - 'failure': 'fixed phase outcome uncertain; original evidence retained; no retry', - 'raw_output_withheld': True} - if journal.data['phases'].get(args.phase, {}).get('state') == 'RUNNING': - journal.finish(args.phase, safe) - print(json.dumps(safe)) - raise SystemExit(1) from None - finally: - journal.close() - - -if __name__ == '__main__': - try: - main() - except Exception: - raise SystemExit('supervised execution refused before phase; raw output withheld') from None diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac deleted file mode 100644 index c0ce95b..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac +++ /dev/null @@ -1,402 +0,0 @@ -"""Synthetic custody/transport tests. No native data, Docker or cluster action.""" -import copy -import datetime -import io -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import unittest -from unittest.mock import patch -from contextlib import redirect_stdout - -import actuator -from authority_journal import Journal, authority, fresh_scan -from native_program import PROGRAM -from receipt_safety import counts - -CASE = Path(__file__).resolve().parent.parent -NOW = datetime.datetime(2026, 10, 11, 2, 0, tzinfo=datetime.timezone.utc) -REVISION = 'e' * 40 -COUNT = {'schema_version': 5, 'integrity': 'ok', 'read_only_pre_open': True, 'store_constructed': False, - 'lifecycle': {'records': 3, 'events': 8, 'pending': 1}, - 'outbox': {'pending': 2, 'delivered': 10}, 'submissions': {'confirmed': 1, 'unresolved': 1}} - - -def accepted(): - approval = '11111111-1111-4111-8111-111111111111' - review = 'review-22222222-2222-4222-8222-222222222222' - disposition = 'disp-33333333-3333-4333-8333-333333333333' - receipt = {'proposal_id': 'INFD-WP-0009-T05/zlib85091-r1', - 'memo_id': 'infd-20261011-zlib-remediation-r1', 'memo_version': 1, - 'memo_sha256': 'a' * 64, 'source_revision': REVISION, 'activation': 'APPROVED', - 'authority_mode': 'ADMINISTER', 'target': 'realm:kubernetes/railiance01/informed-decision', - 'verification_method': 'root-fixed-origin-native-readback', 'runtime_authenticated_grant': False, 'outcome_query': '/lifecycle/memos/infd-20261011-zlib-remediation-r1/outcomes?version=1', - 'verified_at': '2026-10-11T02:00:00Z', 'accepted_at': '2026-10-11T01:59:00Z', - 'review_memo_id': review, 'approval_id': approval, 'disposition_id': disposition} - outcome = {'memo_id': receipt['memo_id'], 'review': {'memo_id': review, 'stale': False, 'intake_state': 'ready'}, - 'dispositions': [{'approval_id': None, 'submission_state': 'confirmed', - 'disposition': {'verb': 'accept', 'memo_version': 1, 'memo_id': review, - 'approval_id': approval, 'id': disposition, - 'actor': {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'}, - 'at': receipt['accepted_at']}}]} - return receipt, outcome - - -class AuthorityAndCustody(unittest.TestCase): - def test_exact_nested_founder_receipt_not_top_level_approval(self): - receipt, outcome = accepted() - result = authority(receipt, outcome, 'a' * 64, REVISION, 'drill-copy', NOW) - self.assertEqual(result['approval_id'], receipt['approval_id']) - - def test_pending_stale_wrong_actor_version_and_binding_refuse(self): - mutations = [lambda r, o: r.update(activation='PENDING'), - lambda r, o: r.update(memo_sha256='changed'), - lambda r, o: r.update(source_revision='changed'), - lambda r, o: r.update(target='other'), - lambda r, o: r.update(verified_at='2026-10-11T01:00:00Z'), - lambda r, o: r.update(verification_method='caller-asserted'), - lambda r, o: r.update(runtime_authenticated_grant=True), - lambda r, o: o['review'].update(stale=True), - lambda r, o: o['review'].update(intake_state='pending'), - lambda r, o: o['dispositions'][0].update(submission_state='unresolved'), - lambda r, o: o['dispositions'][0]['disposition'].update(memo_version=3), - lambda r, o: o['dispositions'][0]['disposition'].update(actor={'kind': 'agent', 'sub': 'platform-root'}), - lambda r, o: o['dispositions'].append(copy.deepcopy(o['dispositions'][0]))] - for mutate in mutations: - with self.subTest(mutate=mutate): - r, o = accepted() - mutate(r, o) - with self.assertRaises(ValueError): - authority(r, o, 'a' * 64, REVISION, 'release', NOW) - - def test_expired_window_and_unknown_phase_refuse(self): - r, o = accepted() - with self.assertRaises(ValueError): - authority(r, o, 'a' * 64, REVISION, 'arbitrary', NOW) - late = datetime.datetime(2026, 10, 13, tzinfo=datetime.timezone.utc) - r['verified_at'] = late.isoformat() - with self.assertRaises(ValueError): - authority(r, o, 'a' * 64, REVISION, 'rollback', late) - - def test_durable_intent_precedes_dispatch_and_never_retries(self): - with tempfile.TemporaryDirectory() as directory: - parent = Path(directory) - os.chmod(parent, 0o700) - journal = Journal(parent, {'case': 'fixture'}) - journal.start('drill-backup', {'approval_id': 'fixture'}) - self.assertEqual(json.loads(journal.path.read_text())['phases']['drill-backup']['state'], 'RUNNING') - journal.close() # simulated crash before result - recovered = Journal(parent, {'case': 'fixture'}) - with self.assertRaises(ValueError): - recovered.start('drill-backup', {}) - with self.assertRaises(ValueError): - recovered.start('drill-backup', {}) - recovered.close() - - def test_phase_order_and_uncertain_release_allows_only_distinct_recovery(self): - with tempfile.TemporaryDirectory() as directory: - os.chmod(directory, 0o700) - j = Journal(directory, {'case': 'fixture'}) - with self.assertRaises(ValueError): - j.start('release', {}) - for phase in actuator.PHASES[:6]: - j.start(phase, {}) - j.finish(phase, {'all_checks_pass': True}) - j.start('release', {}) - j.finish('release', {'all_checks_pass': False}) - with self.assertRaises(ValueError): - j.start('release', {}) - with self.assertRaises(ValueError): - j.start('verify', {}) - j.start('rollback', {}) - j.finish('rollback', {'all_checks_pass': True}) - j.close() - - def test_secret_or_unknown_child_output_is_withheld(self): - for body in ('not-json', '{"all_checks_pass":true,"token":"private-value"}', - '{"all_checks_pass":true,"snapshot_readonly":{"schema_version":5}}'): - with self.assertRaises(ValueError): - actuator.json_result(subprocess.CompletedProcess([], 0, body, '')) - wrong = copy.deepcopy(COUNT) - wrong['outbox'] = {'private-row-value': 1} - with self.assertRaises(ValueError): - counts(wrong) - counts(COUNT) # blocked/unresolved counts are not required to be zero. - - def test_binary_copy_is_bounded_private_and_never_echoed(self): - blob = b'synthetic private fixture bytes' - digest = __import__('hashlib').sha256(blob).hexdigest() - metadata = {'all_checks_pass': True, 'snapshot_file': 'drill.sqlite', 'snapshot_bytes': len(blob), - 'snapshot_sha256': digest, 'snapshot_readonly': COUNT, - 'pod_name': 'fixture-pod', 'pod_uid': 'fixture-uid'} - journal = type('FixtureJournal', (), {'data': {'phases': {'drill-backup': {'result': metadata}}}})() - real_popen = subprocess.Popen - for extra in (b'', b'oversized'): - with self.subTest(extra=extra), tempfile.TemporaryDirectory() as folder: - os.chmod(folder, 0o700) - output = io.StringIO() - - def child(*args, **kwargs): - return real_popen([sys.executable, '-c', 'import sys;sys.stdout.buffer.write(' + repr(blob + extra) + ')'], - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL) - - with patch.object(actuator, 'CUSTODY', Path(folder)), patch.object(actuator, 'remote', return_value=metadata), \ - patch('subprocess.Popen', side_effect=child), redirect_stdout(output): - if extra: - with self.assertRaises(ValueError): - actuator.copy_snapshot('drill-copy', REVISION, {}, journal) - else: - result = actuator.copy_snapshot('drill-copy', REVISION, {}, journal) - self.assertTrue(result['off_cluster_private_copy_verified']) - self.assertEqual((Path(folder) / 'drill.sqlite').read_bytes(), blob) - self.assertEqual(output.getvalue(), '') - self.assertEqual((Path(folder) / 'drill.sqlite').stat().st_mode & 0o777, 0o600) - - def test_recreated_offline_volume_cannot_be_deleted(self): - identity = {'Name': actuator.VOLUME, 'Labels': {'nonce': 'old'}, 'CreatedAt': 'old'} - journal = type('FixtureJournal', (), {'data': {'phases': { - 'offline-verify': {'result': {'created_volume_identity': identity}}}}})() - inspected = [{**identity, 'CreatedAt': 'recreated'}] - with patch.object(actuator, 'docker', return_value=subprocess.CompletedProcess([], 0, json.dumps(inspected), '')) as mocked: - with self.assertRaises(ValueError): - actuator.cleanup_offline(journal) - self.assertEqual(mocked.call_count, 1) - - def test_offline_post_admin_integrity_and_original_hash_are_rechecked(self): - for defect in ('none', 'post-integrity', 'original-hash'): - with self.subTest(defect=defect), tempfile.TemporaryDirectory() as directory: - folder=Path(directory);os.chmod(folder,0o700) - snapshot=folder/'drill.sqlite';snapshot.write_bytes(b'private synthetic snapshot');os.chmod(snapshot,0o600) - digest=actuator.sha(snapshot) - prior={'snapshot_sha256':digest,'snapshot_readonly':COUNT} - journal=type('FixtureJournal',(),{'data':{'phases':{'drill-copy':{'result':prior},'offline-verify':{'attempt_id':'nonce'}}},'note':lambda *args:None})() - reads=[] - volume={'Name':actuator.VOLUME,'Labels':{'informed-decision.case':'infd-zlib-20261011-r1','informed-decision.use':'offline-restore-only','informed-decision.attempt':'nonce'},'Driver':'local','Scope':'local','CreatedAt':'fixture','Options':None} - def docker(argv, body=None, **kwargs): - if argv[0]=='info':body=json.dumps('bnt-lap001') - elif argv[:2]==['volume','ls']:body='' - elif argv[:2]==['volume','create']:body=actuator.VOLUME - elif argv[:2]==['volume','inspect']:body=json.dumps([volume]) - elif 'informed-decision-admin' in argv: - body=json.dumps({key:COUNT[key] for key in ('schema_version','lifecycle','outbox','submissions')}) - if defect=='original-hash':snapshot.write_bytes(b'changed synthetic original') - elif '--entrypoint' in argv and 'python' in argv: - reads.append(argv);proof=copy.deepcopy(COUNT) - if len(reads)==2 and defect=='post-integrity':proof['integrity']='corrupt' - body=json.dumps(proof) - else:raise AssertionError('unexpected offline command') - return subprocess.CompletedProcess(argv,0,body,'') - with patch.object(actuator,'CUSTODY',folder),patch.object(actuator,'CASE',CASE),patch.object(actuator,'docker',side_effect=docker),patch('subprocess.run',return_value=subprocess.CompletedProcess([],0,'{"all_checks_pass":true,"private_copy_verified":true}','')): - if defect=='none': - result=actuator.offline_verify(journal) - self.assertEqual(result['offline_post_admin_readonly'],COUNT) - self.assertTrue(result['original_snapshot_hash_unchanged']) - else: - with self.assertRaises(ValueError):actuator.offline_verify(journal) - self.assertEqual(len(reads),2) - - def test_fresh_full_scan_db_expiry_and_new_findings_refuse(self): - receipt={'image':actuator.IMAGE,'artifact_source_revision':'9b4bb25d325698ae543e7e8a8a3c3592db1f58fc','scanner':'aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969','no_suppression':True,'full_allseverity_scan':True,'exit_code':0,'completed_at':'2026-10-11T01:59:00Z','db_metadata':{'UpdatedAt':'2026-10-10T19:00:00Z','NextUpdate':'2026-10-11T19:00:00Z','Version':2},'db_sha256':'a'*64,'full_report_sha256':'b'*64} - report={'Results':[{'Vulnerabilities':None}]} - self.assertEqual(fresh_scan(receipt,report,NOW)['allseverity_findings'],0) - mutations=[lambda r,p:r.update(no_suppression=False),lambda r,p:r.update(image='foreign'),lambda r,p:r.update(scanner='other'),lambda r,p:r.update(completed_at='2026-10-10T21:00:00Z'),lambda r,p:r['db_metadata'].update(NextUpdate='2026-10-11T01:59:00Z'),lambda r,p:p['Results'][0].update(Vulnerabilities=[{'Severity':'LOW'}]),lambda r,p:p.update(Results=[])] - for mutate in mutations: - r,p=copy.deepcopy(receipt),copy.deepcopy(report);mutate(r,p) - with self.assertRaises(ValueError):fresh_scan(r,p,NOW) - - -class FixedNativeProgram(unittest.TestCase): - def fixture(self): - sealed = json.loads((CASE / 'sealed-live-deployment.json').read_text()) - live = copy.deepcopy(sealed) - live['metadata'].update(generation=42) - live['status'] = {'readyReplicas': 1, 'updatedReplicas': 1, 'observedGeneration': 42} - dependencies = json.loads((CASE / 'sealed-native-dependencies.json').read_text()) - cm = copy.deepcopy(dependencies['configmap']) - cm.pop('canonical_data_sha256') - service = json.loads((CASE / 'candidate-deployment.json').read_text()) - source_service = json.loads((Path('/home/worsch/informed-decision/deploy/lifecycle/service.candidate.json')).read_text()) - cm['data'] = next(x for x in source_service['items'] if x['kind'] == 'ConfigMap')['data'] - pvc = copy.deepcopy(dependencies['pvc']) - pvc['status'] = {'phase': 'Bound'} - app = {'metadata': {'uid': '499868f2-eabc-4df4-ae31-b89ce58d750d', 'resourceVersion': '17'}, - 'spec': {'source': {'repoURL': 'https://forgejo.coulomb.social/coulomb/informed-decision.git', - 'path': 'deploy/lifecycle', 'targetRevision': 'c96b0c6d881e855d1d158003c6de90ef390262e2'}, - 'destination': {'namespace': 'informed-decision', 'server': 'https://kubernetes.default.svc'}}} - objects = {'deployment': live, 'configmap': cm, 'pvc': pvc, 'application': app} - payload = {'phase': 'release', 'source_revision': REVISION, 'approval': {}, 'sealed': sealed, - 'dependencies': dependencies, 'candidate': service, - 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()), - 'readonly': (CASE / 'sqlite_readonly.py').read_text(),'purpose_checks':(CASE/'check_installed.py').read_text(), - 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'receipt_safety': (Path(__file__).parent / 'receipt_safety.py').read_text()} - return objects, payload - - def execute_fake(self, objects, payload, ambiguous=False, schema_refused=False): - writes = [] - calls = [] - - def fake(argv, input=None, **kwargs): - calls.append(argv) - if argv[:2] == ['kubectl', 'get']: - kind = argv[2] - if kind in objects: - body = objects[kind] - elif kind == 'pods': - body = {'items': [{'metadata': {'name': 'fixture-pod', 'uid': 'c4cb1550-feda-4097-9de4-d891a657b662', - 'ownerReferences': [{'kind': 'ReplicaSet', 'name': 'fixture-rs', 'uid': 'rs-uid'}]}, - 'spec': objects['deployment']['spec']['template']['spec'], - 'status': {'phase': 'Running', 'containerStatuses': [{'name':'informed-decision','ready': True,'imageID':objects['deployment']['spec']['template']['spec']['containers'][0]['image']}] }}]} - elif kind == 'replicaset': - body = {'metadata': {'uid': 'rs-uid', 'ownerReferences': [ - {'kind': 'Deployment', 'uid': payload['sealed']['metadata']['uid']}]}} - elif kind == 'nodes': - body = {'items': [{'metadata': {'name': 'fixture-node'}, 'status': { - 'addresses': [{'address': '92.205.62.239'}], 'allocatable': {'cpu': '4'}, - 'conditions': [{'type': key, 'status': 'True' if key == 'Ready' else 'False'} - for key in ('Ready', 'MemoryPressure', 'DiskPressure', 'PIDPressure')]}}]} - else: - raise AssertionError('unexpected fixed metadata route') - return subprocess.CompletedProcess(argv, 0, json.dumps(body), '') - if argv[:2] == ['kubectl', 'exec']: - if schema_refused: - return subprocess.CompletedProcess(argv, 1, '', 'withheld') - return subprocess.CompletedProcess(argv, 0, json.dumps({ - 'all_checks_pass': True, 'live_readonly_schema_integrity_counts': COUNT}), '') - if argv[:2] == ['kubectl', 'apply']: - admitted = copy.deepcopy(payload['sealed']) - admitted['spec']['template']['spec']['containers'][0]['image'] = (actuator.IMAGE if payload['phase']=='release' else payload['sealed']['spec']['template']['spec']['containers'][0]['image']) - return subprocess.CompletedProcess(argv, 0, json.dumps(admitted), '') - if argv[:2] == ['kubectl', 'describe']: - return subprocess.CompletedProcess(argv, 0, 'Allocated resources:\n cpu 3460m (86%)\n', '') - if argv[:2] == ['kubectl', 'diff']: - return subprocess.CompletedProcess(argv, 1, '-image: base\n+image: candidate\n', '') - if argv[:2] == ['kubectl', 'patch']: - if '--dry-run=server' not in argv: - writes.append(json.loads(input)) - if ambiguous: - return subprocess.CompletedProcess(argv, 1, '', 'withheld') - return subprocess.CompletedProcess(argv, 0, 'application.argoproj.io/informed-decision\n', '') - raise AssertionError('unexpected command') - - source = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace( - '__GUARD__', repr((CASE / 'change_guard.py').read_text())) - output = io.StringIO() - with patch('subprocess.run', side_effect=fake), redirect_stdout(output): - try: - exec(compile(source, 'fixed-native-fixture', 'exec'), {'__name__': '__fixture__'}) - except (ValueError, SystemExit): - pass - receipt = json.loads(output.getvalue()) if output.getvalue() else {'all_checks_pass': False} - return receipt, writes, calls - - def test_only_existing_application_operation_and_deployment_are_requested(self): - objects, payload = self.fixture() - receipt, writes, calls = self.execute_fake(objects, payload) - self.assertTrue(receipt['all_checks_pass']) - self.assertFalse(receipt['rollout_verified']) - self.assertEqual(len(writes), 1) - operation = writes[0][-1]['value'] - self.assertFalse(operation['sync']['prune']) - self.assertEqual(operation['sync']['resources'], [ - {'group': 'apps', 'kind': 'Deployment', 'name': 'informed-decision', 'namespace': 'informed-decision'}]) - self.assertTrue(any('--dry-run=server' in argv and argv[:2] == ['kubectl', 'patch'] for argv in calls)) - self.assertEqual(writes[0][0]['path'], '/metadata/uid') - - def test_source_overrides_automation_and_custody_drift_stop_before_write(self): - mutations = [lambda o: o['application']['metadata'].update(uid='recreated'), - lambda o: o['application']['spec'].update(syncPolicy={'automated': {}}), - lambda o: o['application']['spec']['source'].update(kustomize={'images': ['other']}), - lambda o: o['application']['spec'].update(sources=[{}]), - lambda o: o['configmap']['metadata'].update(uid='recreated'), - lambda o: o['pvc']['spec'].update(volumeName='replaced'), - lambda o: o['deployment']['status'].update(readyReplicas=0)] - for mutate in mutations: - with self.subTest(mutate=mutate): - objects, payload = self.fixture() - mutate(objects) - receipt, writes, calls = self.execute_fake(objects, payload) - self.assertFalse(receipt['all_checks_pass']) - self.assertEqual(writes, []) - - def test_preopen_schema_refusal_prevents_image_operation(self): - objects, payload = self.fixture() - receipt, writes, calls = self.execute_fake(objects, payload, schema_refused=True) - self.assertFalse(receipt['all_checks_pass']) - self.assertEqual(writes, []) - - - def test_rollback_unready_candidate_never_reads_pod_or_database(self): - objects, payload = self.fixture() - payload['phase'] = 'rollback' - objects['deployment']['status'].update(readyReplicas=0, updatedReplicas=0) - objects['deployment']['spec']['template']['spec']['containers'][0]['image'] = actuator.IMAGE - objects['application']['spec']['source']['targetRevision'] = REVISION - receipt, writes, calls = self.execute_fake(objects, payload, schema_refused=True) - self.assertTrue(receipt['all_checks_pass']) - self.assertFalse(receipt['rollout_verified']) - self.assertEqual(len(writes), 1) - self.assertFalse(any(argv[:2] == ['kubectl', 'exec'] or argv[:3] == ['kubectl', 'get', 'pods'] for argv in calls)) - self.assertEqual(writes[0][-1]['value']['sync']['revision'], 'c96b0c6d881e855d1d158003c6de90ef390262e2') - - def test_rollback_still_refuses_custody_or_nonpair_image_drift(self): - for defect in ('cm', 'pvc', 'image'): - with self.subTest(defect=defect): - objects, payload = self.fixture();payload['phase'] = 'rollback' - objects['deployment']['status']['readyReplicas'] = 0 - if defect == 'cm':objects['configmap']['metadata']['uid'] = 'recreated' - elif defect == 'pvc':objects['pvc']['spec']['volumeName'] = 'replaced' - else:objects['deployment']['spec']['template']['spec']['containers'][0]['image'] = 'foreign-image' - receipt, writes, calls = self.execute_fake(objects, payload) - self.assertFalse(receipt['all_checks_pass']);self.assertEqual(writes, []) - - def test_recovery_verification_is_separate_ready_old_image_schema_proof(self): - objects, payload = self.fixture();payload['phase'] = 'rollback-verify' - resource={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'} - target='c96b0c6d881e855d1d158003c6de90ef390262e2' - payload['operation_intent']={'state':'COMPLETE','started_at':'2026-10-11T01:00:00Z','result':{'operation_requested':True,'source_revision':target,'prune':False,'resources':[resource]}} - objects['application']['status']={'operationState':{'phase':'Succeeded','startedAt':'2026-10-11T01:00:01Z','finishedAt':'2026-10-11T01:01:00Z','operation':{'initiatedBy':{'username':'custodian-codex'},'sync':{'revision':target,'resources':[resource]}},'syncResult':{'revision':target,'resources':[{**resource,'status':'Synced'}]}}} - receipt, writes, calls = self.execute_fake(objects, payload) - self.assertTrue(receipt['all_checks_pass']);self.assertTrue(receipt['recovery_verification']) - self.assertEqual(writes, []) - self.assertTrue(any(argv[:2]==['kubectl','exec'] for argv in calls)) - objects['deployment']['status']['readyReplicas'] = 0 - receipt, writes, calls = self.execute_fake(objects,payload) - self.assertFalse(receipt['all_checks_pass']) - - def test_ambiguous_patch_is_never_retried(self): - objects, payload = self.fixture() - receipt, writes, calls = self.execute_fake(objects, payload, ambiguous=True) - self.assertFalse(receipt['all_checks_pass']) - self.assertEqual(len(writes), 1) - - def test_false_omitempty_needs_bound_current_request_and_exact_completion(self): - objects,payload=self.fixture();payload['phase']='rollback-verify' - resource={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'} - target='c96b0c6d881e855d1d158003c6de90ef390262e2' - payload['operation_intent']={'state':'COMPLETE','started_at':'2026-10-11T01:00:00Z','result':{'operation_requested':True,'source_revision':target,'prune':False,'resources':[resource]}} - objects['application']['status']={'operationState':{'phase':'Succeeded','startedAt':'2026-10-11T01:00:01Z','finishedAt':'2026-10-11T01:01:00Z','operation':{'initiatedBy':{'username':'custodian-codex'},'sync':{'revision':target,'resources':[resource]}},'syncResult':{'revision':target,'resources':[{**resource,'status':'Synced'}]}}} - self.assertTrue(self.execute_fake(objects,payload)[0]['all_checks_pass']) - mutations=[lambda o,p:o['application']['status']['operationState']['operation']['sync'].update(prune=True), - lambda o,p:o['application']['status']['operationState']['operation']['sync'].update(prune=None), - lambda o,p:o['application']['status']['operationState'].update(startedAt='2026-10-11T00:59:00Z'), - lambda o,p:o['application']['status']['operationState']['operation'].update(initiatedBy={'username':'other'}), - lambda o,p:o['application']['status']['operationState']['syncResult'].update(revision='foreign'), - lambda o,p:o['application']['status']['operationState']['syncResult']['resources'][0].update(status='Pruned'), - lambda o,p:p['operation_intent']['result'].update(prune=True), - lambda o,p:p.update(operation_intent=None)] - for mutate in mutations: - o,p=copy.deepcopy(objects),copy.deepcopy(payload);mutate(o,p) - receipt,writes,calls=self.execute_fake(o,p) - self.assertFalse(receipt['all_checks_pass']);self.assertEqual(writes,[]) - - def test_optimized_executor_refuses_before_any_input(self): - result=subprocess.run([sys.executable,'-O',str(Path(__file__).parent/'actuator.py'),'--phase','release','--root-authority-receipt','/missing','--root-native-outcome','/missing','--source-revision',REVISION],capture_output=True,text=True) - self.assertNotEqual(result.returncode,0) - self.assertIn('optimized interpreter refused',result.stderr) - - -if __name__ == '__main__': - unittest.main() diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c deleted file mode 100644 index ea29c3b..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c +++ /dev/null @@ -1,62 +0,0 @@ -{ - "kind": "informed-decision.supervised-mechanical-release-plan", - "version": 1, - "proposal_id": "INFD-WP-0009-T05/zlib85091-r1", - "trial_id": "infd-zlib-20261011-r1", - "state": "prepared-unapproved", - "executor_files": { - "actuator.py": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80", - "authority_journal.py": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108", - "native_program.py": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20", - "receipt_safety.py": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - "case_files": { - "post-ready-native-outcomes-command.py": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa", - "scan-receipt.json": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a", - "source-commit-proof.json": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b", - "candidate-deployment.json": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95", - "artifact-comparison.json": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4", - "rollback-deployment.json": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "sqlite_readonly.py": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509", - "published-verification.json": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e", - "sealed-native-dependencies.json": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369", - "sealed-live-deployment.json": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e", - "native-preflight.json": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9", - "fresh-scan-receipt-template.json": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd", - "rollback-preflight.json": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5", - "installed-zlib-proof.json": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68", - "runtime-receipt.json": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889", - "change_guard.py": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201", - "check_installed.py": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad", - "candidate-runtime.json": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - "owner_source_files": { - "deploy/lifecycle/admission.candidate.json": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529", - "deploy/lifecycle/service.candidate.json": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278", - "deploy/lifecycle/kustomization.yaml": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47", - "deploy/lifecycle/registries.candidate.json": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - "candidate_image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "prior_image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "source_revision": "must-be-exact-final-owner-HEAD-with-approved-four-Git-blobs", - "phases": [ - "drill-backup", - "drill-copy", - "offline-verify", - "cleanup-offline", - "cutover-backup", - "cutover-copy", - "release", - "verify", - "rollback", - "rollback-verify" - ], - "authorization": "fresh externally authenticated root fixed-origin native readback before each phase; exact confirmed platform-root disposition and current packet SHA/source commit; receipt bookkeeping is not an autonomous signed grant", - "execute_by": "2026-10-12T23:59:59Z", - "raw_output_withheld": true, - "native_data_or_workload_action_executed": false, - "deadline_semantics": "All phases including verification/recovery must begin before execute_by. No extension or automatic emergency grant; beyond deadline stop and obtain a fresh founder decision. Complete the bounded drill and rollout early enough to allow recovery within the window.", - "source_memo_version": 1, - "native_review_memo_version": 1, - "published_artifact_verified": true -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3 deleted file mode 100644 index b88546d..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3 +++ /dev/null @@ -1,11 +0,0 @@ -# Requested decision - -Approve the exact new T05 zlib-only image release and protected same-schema compatibility drill, after final independent Product and Platform acknowledgments. This packet is prepared and unapproved; its final committed source and rollback dry-run are still pending. The prior T04 approval is excluded. - -The admitted owner CI published immutable image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23 from source9b4bb25d325698ae543e7e8a8a3c3592db1f58fc. Its actual full scan reports zero findings across44packages. The same scan DB finds oneMEDIUM on currentimage763c. Signed official Alpine zlib1.3.2-r1 is the sole package change; actual published filesystem proof shows onlylibz andAPKinstalleddatabase changes, zero313directorymetadata changes, retainedninebaselayers and unchanged36applicationfiles/purposepolicy/config/credentials/audience/resources. Registry-to-daemon config serialization differs under closed verified normalization, without runtime change. - -The exact ordered protected drill and release are specified in native-backup-restore-protocol.md and hash-bound execution bundle. New backup paths, isolated clone and journal are distinct from old evidence. Real private data will enter only approved private custody and the isolated no-network clone; content never enters public evidence. Failed/uncertain phase states remain durable and cannot be retried. Prior T04 failed copy/verify are preserved. - -After schema5/integrity/exactclonecounts pass and clone cleanup succeeds, fresh cutover backup/copy precedes the exact manual image-only Deployment sync. Current baseline is healthygeneration25/image763c/Applicationc96. ImmutableCM6c/PVC/Podconfiguration/PDP origins remain sealed. FreshnodeCPU3460of4000m supports unchangedRecreate, with another check before effect. Brief availability interruption expected. - -Rollback restores Deploymentimage763c andApplicationc96 while retaining latestschema5database; no old snapshot restoration. Separate installed/native health/custody/PDP-gated readback and actual native image rescan precede T05 closure. Allphase starts expireOct12 23:59:59Z; release reserves30minutesforverificationandrecovery. Afterexpiry a newfounderdecision is required. EUR0newrecurringinfrastructure, boundedprivate storage andunpricedattendedtime. No newdoctrine/autopilot/credentialgrant is authorized. diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/796dbbff5c7ca61af2c84c2f2ad3de23b56f67210a30bded23a1ff5db806d26f b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/796dbbff5c7ca61af2c84c2f2ad3de23b56f67210a30bded23a1ff5db806d26f deleted file mode 100644 index 2c3dc1a..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/796dbbff5c7ca61af2c84c2f2ad3de23b56f67210a30bded23a1ff5db806d26f +++ /dev/null @@ -1,372 +0,0 @@ -[ - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-review.json", - "sha256": "e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/mapping.json", - "sha256": "dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/original-manifest.json", - "sha256": "81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "sha256": "750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "sha256": "09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "sha256": "8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "sha256": "196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "sha256": "15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "sha256": "0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "sha256": "a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "sha256": "4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_initial_dispatch.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_initial_dispatch.py", - "sha256": "609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/published-evidence-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/published-evidence-manifest.json", - "sha256": "6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9" - }, - { - "path": "intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "sha256": "a187eae16bd6eb8b05fdbffc860a7e87b62e726379fde92915de15d6d8bd4290" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07 deleted file mode 100644 index 0b5744a..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07 +++ /dev/null @@ -1,205 +0,0 @@ -[ - { - "repo": "informed-decision", - "path": "deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/admission.candidate.json", - "original_sha256": "92382a56b349c3b895e5806e975082527bb99d1a001140e69d857e5b0391fa55", - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/kustomization.yaml", - "original_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47", - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/registries.candidate.json", - "original_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e", - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/service.candidate.json", - "original_sha256": "38b78b9cbee5ade7750a58d5156ca4c5773352e947acb304ee714b59e749ceb4", - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a deleted file mode 100644 index 0e1ccaf..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a +++ /dev/null @@ -1,191 +0,0 @@ -{ - "kind": "informed-decision.memo-intake", - "version": 1, - "idempotency_key": "infd-20261011-zlib-remediation-r1-v1", - "memo_class": "decision-record", - "review_group": "net-kingdom-admins", - "drafted_by": "custodian-codex", - "approval_intent": { - "action": "administer", - "principal": "informed-decision", - "purpose": "Approve only exact zlib85091 image-only T05 release and fresh protected native backup/restore gates; no prior T04 approval reuse or unrelated grant.", - "target": { - "id": "INFD-WP-0009-T05/zlib85091-r1", - "type": "runtime-change", - "system": "railiance01" - } - }, - "memo": { - "id": "infd-20261011-zlib-remediation-r1", - "version": 1, - "question": "Approve the exact zlib85091 fixed image-only release, new protected backup/restore compatibility drill and database-preserving rollback?", - "requested_act": "approve", - "binding_level": "organizational", - "brief": "Published owner CI image f558 has zero full-scan findings across44packages; only zlib1.3.2-r0 to r1 changes. Fresh schema5 private backup/isolated clone drill and cutover snapshot precede one Deployment manual prune=false Recreate sync. Separate founder go required; old T04 acceptance/history unchanged. Rollback image763c/Applicationc96 retains latestDB. Release reserves30minutes before Oct12 deadline.", - "binding": { - "principal": { - "id": "pending-human-session", - "kind": "person", - "display_name": "Founder review" - }, - "target": { - "kind": "environment", - "id": "realm:kubernetes/railiance01/informed-decision", - "label": "Informed Decision zlib85091 image-only remediation on railiance01", - "environment": "production" - }, - "terms": "Approve only INFD-WP-0009-T05/zlib85091-r1 trialinfd-zlib-20261011-r1 and exact attached digests: new private drillbackup/copy, isolatedschema5pre/admin/postintegrityandcounts, exactcreatedclonecleanup, freshcutoverbackup/copy, manualfinalsourceDeployment-onlyimagef558/prune=false. ImmutableCM/PVC/config/credentials/audience/resources sealed. Freshrootnativeauthenticated confirmed founderACCEPTsource1 beforeeveryphase; receiptsarebookkeeping/noautonomousgrant. Rollbackonlyimage763c/Applicationc96 preserveslatestDB, separateverification. AllphasesbeginbeforeOct12 23:59:59Z; releasereserves30minutes; newdecisionbeyond. ExistingT04UNCERTAINEvidenceunaltered, nooldhelperamendment/phase retry, noresidualpromotion ordoctrinechange." - }, - "step_kind": "approve", - "packet": [ - { - "item_id": "decision-packet-md", - "label": "decision-packet.md", - "hash": "sha256:c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "item_id": "proposal-json", - "label": "proposal.json", - "hash": "sha256:a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "item_id": "native-backup-restore-protocol-md", - "label": "native-backup-restore-protocol.md", - "hash": "sha256:0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "item_id": "native-preflight-json", - "label": "native-preflight.json", - "hash": "sha256:56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "item_id": "rollback-preflight-json", - "label": "rollback-preflight.json", - "hash": "sha256:0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "item_id": "published-verification-json", - "label": "published-verification.json", - "hash": "sha256:dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "item_id": "artifact-comparison-json", - "label": "artifact-comparison.json", - "hash": "sha256:a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "item_id": "installed-zlib-proof-json", - "label": "installed-zlib-proof.json", - "hash": "sha256:6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "item_id": "sealed-live-deployment-json", - "label": "sealed-live-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "sealed-native-dependencies-json", - "label": "sealed-native-dependencies.json", - "hash": "sha256:042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "item_id": "candidate-deployment-json", - "label": "candidate-deployment.json", - "hash": "sha256:487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "item_id": "rollback-deployment-json", - "label": "rollback-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "execution-bundle-json", - "label": "execution-bundle.json", - "hash": "sha256:09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - } - ], - "highlights": [ - { - "id": "protected-drill-and-recreate", - "item_id": "decision-packet-md", - "note": "This new T05 decision authorizes protected real-data backup/copy and no-network same-schema restore drill plus brief Recreate image release. Scan0 is actual published44package result, not installed acceptance. Rollback preserves latestDB; priorfailedT04evidence retained. No oldapprovalreuse. Allphase starts expireOct12; release reserves30minutesforrecovery; noautomaticextension.", - "required_ack": true, - "severity": "warning", - "locator": { - "kind": "section", - "heading": "Requested decision" - } - } - ], - "sealed": false, - "approval_id": null, - "approval_binding_digest": null - }, - "packet": [ - { - "item_id": "decision-packet-md", - "media_type": "text/markdown", - "content": "# Requested decision\n\nApprove the exact new T05 zlib-only image release and protected same-schema compatibility drill, after final independent Product and Platform acknowledgments. This packet is prepared and unapproved; fresh rollback server dry-run passed with exactly the baseline image and empty diff. Root retains exact final owner source revision and independent acknowledgments before asking for native founder disposition. The prior T04 approval is excluded.\n\nThe admitted owner CI published immutable image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23 from source9b4bb25d325698ae543e7e8a8a3c3592db1f58fc. Its actual full scan reports zero findings across44packages. The same scan DB finds oneMEDIUM on currentimage763c. Signed official Alpine zlib1.3.2-r1 is the sole package change; actual published filesystem proof shows onlylibz andAPKinstalleddatabase changes, zero313directorymetadata changes, retainedninebaselayers and unchanged36applicationfiles/purposepolicy/config/credentials/audience/resources. Registry-to-daemon config serialization differs under closed verified normalization, without runtime change.\n\nThe exact ordered protected drill and release are specified in native-backup-restore-protocol.md and hash-bound execution bundle. New backup paths, isolated clone and journal are distinct from old evidence. Real private data will enter only approved private custody and the isolated no-network clone; content never enters public evidence. Failed/uncertain phase states remain durable and cannot be retried. Prior T04 failed copy/verify are preserved.\n\nAfter schema5/integrity/exactclonecounts pass and clone cleanup succeeds, fresh cutover backup/copy precedes the exact manual image-only Deployment sync. Current baseline is healthygeneration25/image763c/Applicationc96. ImmutableCM6c/PVC/Podconfiguration/PDP origins remain sealed. FreshnodeCPU3460of4000m supports unchangedRecreate, with another check before effect. Brief availability interruption expected.\n\nRollback restores Deploymentimage763c andApplicationc96 while retaining latestschema5database; no old snapshot restoration. Separate installed/native health/custody/PDP-gated readback and actual native image rescan precede T05 closure. Allphase starts expireOct12 23:59:59Z; release reserves30minutesforverificationandrecovery. Afterexpiry a newfounderdecision is required. EUR0newrecurringinfrastructure, boundedprivate storage andunpricedattendedtime. No newdoctrine/autopilot/credentialgrant is authorized.\n" - }, - { - "item_id": "proposal-json", - "media_type": "application/json", - "content": "{\n \"kind\": \"informed-decision.supervised-release-proposal\",\n \"version\": 1,\n \"proposal_id\": \"INFD-WP-0009-T05/zlib85091-r1\",\n \"trial_id\": \"infd-zlib-20261011-r1\",\n \"state\": \"prepared-unapproved\",\n \"memo_id\": \"infd-20261011-zlib-remediation-r1\",\n \"memo_version\": 1,\n \"task\": \"INFD-WP-0009-T05\",\n \"candidate_image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"artifact_source_revision\": \"9b4bb25d325698ae543e7e8a8a3c3592db1f58fc\",\n \"prior_image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"prior_application_revision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\",\n \"source_revision\": null,\n \"published_artifact_verified\": true,\n \"native_actions_executed\": false,\n \"approval_id\": null,\n \"authority_mode\": \"ADMINISTER\",\n \"target\": \"realm:kubernetes/railiance01/informed-decision\",\n \"cost_estimate_eur\": 0,\n \"risk\": \"Private same-schema backup copies and brief Recreate availability gap. Failed or ambiguous phases retained; no replay. Rollback changes Deployment image/source only, retains latest database.\",\n \"execute_by\": \"2026-10-12T23:59:59Z\",\n \"release_must_start_by\": \"2026-10-12T23:29:59Z\",\n \"max_snapshot_bytes\": 536870912,\n \"maximum_new_snapshot_payload_bytes_each_site\": 1073741824,\n \"local_extra_disposable_clone_max_bytes\": 536870912,\n \"private_metadata_and_sqlite_sidecars_additional_headroom_bytes\": 16777216,\n \"unbound_inputs\": [\n \"exact committed owner rendering revision retained in root supervised receipt\",\n \"Product and Platform final acknowledgments\",\n \"confirmed founder exact new disposition\"\n ],\n \"published_verification_sha256\": \"dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e\",\n \"native_readonly_preflight_sha256\": \"56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9\",\n \"executor_plan_sha256\": \"750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c\",\n \"execution_bundle_sha256\": \"09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9\",\n \"rollback_preflight_sha256\": \"0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5\",\n \"post_ready_native_provider_probe\": \"canonical lifecycle.outcomes SOURCE1 through existing admitted StateHub dispatcher; credentials stay inside Pod; bounded metadata correlated to verified candidate image/Pod/source\"\n}\n" - }, - { - "item_id": "native-backup-restore-protocol-md", - "media_type": "text/markdown", - "content": "# T05 zlib85091 release \u2014 preliminary, unapproved\n\nThis is a distinct proposal/trial for existing INFD-WP-0009-T05. The actual T04 source2 acceptance does not authorize it. Previous R1 copy and R2 verify UNCERTAIN journal states remain untouched. INFD-IN-0010 remains an unpromoted general verifier residual; this new task-specific verifier is a prerequisite of active T05, not an amendment of the old approved helper.\n\nExact intended image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23 was built by admitted owner CI11534/run4 from 9b4bb25d325698ae543e7e8a8a3c3592db1f58fc. Publication is not installed acceptance. Final packet requires independent published-image scan/runtime/filesystem/source proof and fresh read-only native projection/one-object dry-run evidence. Until all final inputs and independent owner acknowledgments are frozen, execution-plan state remains preliminary and the actuator refuses before any authority, journal or native action.\n\nAfter a NEW exact confirmed founder acceptance, root freshly authenticates fixed-origin native source-version1 outcomes before each phase. Receipt files are supervised bookkeeping, not signed grants or autonomy. Wrong task, source, memo/version, stale/pending/agent disposition, old T04 acceptance or changed inputs refuse. Root retains exact approved source revision/digests and action results in supervision. Approval does not promote any agent.\n\nFixed phase order: drill-backup \u2192 drill-copy \u2192 offline-verify \u2192 cleanup-offline \u2192 cutover-backup \u2192 cutover-copy \u2192 release \u2192 verify. New private native /data/private/zlib85091-r1, root .private-backups/20261011-infd-zlib-r1 and local volume infd-zlib-r1-restore-20261011 are distinct from all T04 custody. New private journal intent is fsynced RUNNING before dispatch; no attempted phase replay. Fixed output schema contains only counts, hashes, object identity and boolean results; raw errors and private content are withheld. Existing native snapshots/database/sidecars are never removed or restored over current data.\n\nFresh backup inspection uses copied explicit-close SQLite helpers: live and offline clone readers are mode=ro/query_only and WAL-aware, before any Store/admin open. Immutable reader is used only for freshly closed hash-bound private snapshot with absent/zero WAL frames and stable identity/metadata/digest before/after read/copy. Main and sidecars remain retained. Schema must equal5, integrity ok, and exact snapshot/isolated clone counts match before/admin/after. The detached local clone has network none, readonly root, no service/runtime config/credential mounts; only narrowly scoped UID0 CAP_CHOWN initializer precedes UID10001 reads/admin. Original private copy is hash-stable. Cleanup removes only the disposable volume whose nonce/labels/creation metadata match this trial, before cutover.\n\nEach snapshot is bounded512MiB. At most two new snapshots occupy each native/offcluster custody (1GiB payload each); the disposable offcluster clone adds512MiB while present, plus16MiB sidecar/metadata reserve. New native drill phase checks free space for both; local custody checks two snapshot bounds plus reserve. Existing T04 retention consumes additional pre-existing space. EUR0 recurring infrastructure estimate; human/compute time unpriced.\n\nCutover takes a fresh native backup and exact offcluster copy. Release then requires latest schema5 readonly proof, sealed Pod/Deployment/config/PVC custody and same baseline image763c/current Application sourcec96, a healthy single target92.205.62.239 node and fresh requested CPU \u2264 allocatable capacity. Recreate and resources remain unchanged. Server dry-run/client diff must show only the image. Application UID/resourceVersion/source CAS dispatches manual exact final revision, initiatedBy custodian-codex, explicit prune=false and only apps/Deployment informed-decision in its namespace. No whole-Application sync, namespace/config/credential/resource change or pruning.\n\nVerification reads imageID/readiness/owner chain/exact configured Pod fields, schema5/integrity/metadata counts, installed policy hash and synthetic purpose refusals, health200, installed fixed zlib and exact image proof. After exact candidate Ready and verified rollout, root executes attached post-ready-native-outcomes-command.py inside the existing admitted StateHub Pod. It calls canonical lifecycle.outcomes for infd-20261011-zlib-remediation-r1 SOURCEversion1 at dispatcher fixed ORIGIN with NoRedirect/proxy disabled; existing internal token_file stays inside that Pod and no token/body/private content is exported. Root retains bounded HTTP-success metadata/observationtime and correlates verified candidate PodUID/image/source, then rechecks those identities. This actual provider/PDP-gated preservation proof is separate from installed synthetic2allow8refuse; both are required before task closure. It does not assert provider advisory emissions or new doctrine. Argo3.5.3 omits false prune in operation status: missing or exactfalse is accepted only together with this journal's COMPLETE explicitfalse request, exact source/resources/initiation, operation start after our durable attempt, bounded finish and one Synced Deployment result. True/null/string/prior/foreign/extra-resource results refuse.\n\nRollback \u2192 rollback-verify is separate and permitted only after a recorded release attempt. It switches only Application source back to c96 and Deployment image763c; it never restores an old snapshot over the latest database. Recovery dispatch works even if candidate Pod is absent/unready and never depends on candidate DB inspection. Recovery verification separately requires Ready1/old image/current schema5/data-custody and exact recorded scoped completion. All phases must begin before2026-10-12T23:59:59Z; release must begin at least30minutes earlier. Beyond expiry stop and obtain a new founder decision; no automatic extension or emergency grant. A failed/uncertain attempt requires informed recovery rather than retry.\n\nT05 remains progress until actual installed acceptance and native rescan prove the finding absent. No Risk Engine grade is assigned. Only then may owner close T05/risk and consider finishing WP9, with existing residuals live first.\n\nActual release and installed verification also require fixed private root scan receipt and retained fullreport at /tmp/security-infd-zlib-fresh-scan-{receipt,full}.json, supervised metadata binding to exact immutablef558/artifactsource9b4/scannerdigest62b1. Before journal start, fullreporthash must match, no suppression/allseverity flags true, exit0, nonempty valid fullresults contain zero findings, scan completed within4hours and now precedes DB NextUpdate. The published DB expires2026-10-11T18:59:50Z; it is not valid until release deadline. Root refreshes DB and rescans the same immutable image as routine read-only verification when needed, retaining all raw publicscan artifacts. Any new finding refuses release/closure and is retained for informed reassessment/new decision as needed, never filtered or ignored. Existing risk stays open until actual installed proof/native rescan.\n" - }, - { - "item_id": "native-preflight-json", - "media_type": "application/json", - "content": "{\n \"all_checks_pass\": true,\n \"observed_at\": \"2026-10-11T09:40:15.540874+00:00\",\n \"native_changed\": false,\n \"protected_database_read\": false,\n \"secret_read\": false,\n \"deployment_generation\": 25,\n \"ready_replicas\": 1,\n \"sealed_live_deployment\": {\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n },\n \"candidate_deployment\": {\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n },\n \"sealed_dependencies\": {\n \"configmap\": {\n \"apiVersion\": \"v1\",\n \"immutable\": true,\n \"kind\": \"ConfigMap\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/ConfigMap:informed-decision/informed-decision-runtime-6c221878d051\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision-runtime-6c221878d051\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"90113d2d-54d9-4e82-b1d3-90b49ebed2b5\"\n },\n \"canonical_data_sha256\": \"8ec462d12347548f506eeebf1e3c7cc47648019c8a783804d09bcc79b0f22af6\"\n },\n \"pvc\": {\n \"apiVersion\": \"v1\",\n \"kind\": \"PersistentVolumeClaim\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/PersistentVolumeClaim:informed-decision/informed-decision-data\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\",\n \"pv.kubernetes.io/bind-completed\": \"yes\",\n \"pv.kubernetes.io/bound-by-controller\": \"yes\",\n \"volume.beta.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\",\n \"volume.kubernetes.io/selected-node\": \"239.62.205.92.host.secureserver.net\",\n \"volume.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\"\n },\n \"finalizers\": [\n \"kubernetes.io/pvc-protection\"\n ],\n \"name\": \"informed-decision-data\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n },\n \"spec\": {\n \"accessModes\": [\n \"ReadWriteOnce\"\n ],\n \"resources\": {\n \"requests\": {\n \"storage\": \"1Gi\"\n }\n },\n \"storageClassName\": \"local-path\",\n \"volumeMode\": \"Filesystem\",\n \"volumeName\": \"pvc-d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n }\n },\n \"pvc_phase\": \"Bound\"\n },\n \"argo\": {\n \"uid\": \"499868f2-eabc-4df4-ae31-b89ce58d750d\",\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n },\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"automated\": false,\n \"health\": {\n \"lastTransitionTime\": \"2026-10-11T07:00:42Z\",\n \"status\": \"Healthy\"\n },\n \"sync\": {\n \"comparedTo\": {\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n }\n },\n \"revision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\",\n \"status\": \"OutOfSync\"\n }\n },\n \"server_dry_run_passed\": true,\n \"admission_only_image_delta\": true,\n \"client_diff_exit\": 1,\n \"client_diff_sha256\": \"ccff7c1ecfd9eb59fc0ce566c1013a2af5df514af048c68474a272997024ef7b\",\n \"allocatable_cpu_m\": 4000,\n \"requested_cpu_m\": 3460,\n \"strategy\": \"Recreate\"\n}\n" - }, - { - "item_id": "rollback-preflight-json", - "media_type": "application/json", - "content": "{\n \"all_checks_pass\": true,\n \"observed_at\": \"2026-10-11T09:43:42.851086+00:00\",\n \"native_changed\": false,\n \"protected_database_read\": false,\n \"secret_read\": false,\n \"deployment_generation\": 25,\n \"ready_replicas\": 1,\n \"sealed_live_deployment\": {\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n },\n \"candidate_deployment\": {\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n },\n \"sealed_dependencies\": {\n \"configmap\": {\n \"apiVersion\": \"v1\",\n \"immutable\": true,\n \"kind\": \"ConfigMap\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/ConfigMap:informed-decision/informed-decision-runtime-6c221878d051\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision-runtime-6c221878d051\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"90113d2d-54d9-4e82-b1d3-90b49ebed2b5\"\n },\n \"canonical_data_sha256\": \"8ec462d12347548f506eeebf1e3c7cc47648019c8a783804d09bcc79b0f22af6\"\n },\n \"pvc\": {\n \"apiVersion\": \"v1\",\n \"kind\": \"PersistentVolumeClaim\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/PersistentVolumeClaim:informed-decision/informed-decision-data\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\",\n \"pv.kubernetes.io/bind-completed\": \"yes\",\n \"pv.kubernetes.io/bound-by-controller\": \"yes\",\n \"volume.beta.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\",\n \"volume.kubernetes.io/selected-node\": \"239.62.205.92.host.secureserver.net\",\n \"volume.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\"\n },\n \"finalizers\": [\n \"kubernetes.io/pvc-protection\"\n ],\n \"name\": \"informed-decision-data\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n },\n \"spec\": {\n \"accessModes\": [\n \"ReadWriteOnce\"\n ],\n \"resources\": {\n \"requests\": {\n \"storage\": \"1Gi\"\n }\n },\n \"storageClassName\": \"local-path\",\n \"volumeMode\": \"Filesystem\",\n \"volumeName\": \"pvc-d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n }\n },\n \"pvc_phase\": \"Bound\"\n },\n \"argo\": {\n \"uid\": \"499868f2-eabc-4df4-ae31-b89ce58d750d\",\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n },\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"automated\": false,\n \"health\": {\n \"lastTransitionTime\": \"2026-10-11T07:00:42Z\",\n \"status\": \"Healthy\"\n },\n \"sync\": {\n \"comparedTo\": {\n \"destination\": {\n \"namespace\": \"informed-decision\",\n \"server\": \"https://kubernetes.default.svc\"\n },\n \"source\": {\n \"path\": \"deploy/lifecycle\",\n \"repoURL\": \"https://forgejo.coulomb.social/coulomb/informed-decision.git\",\n \"targetRevision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\"\n }\n },\n \"revision\": \"c96b0c6d881e855d1d158003c6de90ef390262e2\",\n \"status\": \"OutOfSync\"\n }\n },\n \"server_dry_run_passed\": true,\n \"admission_only_image_delta\": true,\n \"client_diff_exit\": 0,\n \"client_diff_sha256\": \"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\",\n \"allocatable_cpu_m\": 4000,\n \"requested_cpu_m\": 3460,\n \"strategy\": \"Recreate\"\n}\n" - }, - { - "item_id": "published-verification-json", - "media_type": "application/json", - "content": "{\n \"proposal_id\": \"INFD-WP-0009-T05/zlib-85091-r1\",\n \"task_id\": \"INFD-WP-0009-T05\",\n \"risk_record\": \"INFD-RISK-20261011-ZLIB-85091\",\n \"state\": \"actual-owner-CI-published-independent-verification-passed-native-unapproved\",\n \"verified_utc\": \"2026-10-11T09:40:24.469646+00:00\",\n \"source_revision\": \"9b4bb25d325698ae543e7e8a8a3c3592db1f58fc\",\n \"ci_task\": 11534,\n \"ci_run\": 4,\n \"ci_status\": \"success\",\n \"reviewed_source_manifest_sha256\": \"724aa4777eb1c8976d868e5f504473f4b6d58f13d750d87aefa5c8d5ca0747ec\",\n \"all74_source_hashes_match_actual_commit\": true,\n \"published_image_digest\": \"sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"base_image_digest\": \"sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"registry_config_digest\": \"sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b\",\n \"executed_local_daemon_image_id\": \"sha256:f2e07e3dd839b55afd5d517243a765a26965325e4e6750f508771ad556bd6d01\",\n \"serialization_caveat\": \"Daemon load reserializes config; all diffIDs/platform/effective config+labels independently equal to registry; neither config nor daemon ID substitutes for registry image digest\",\n \"actual_registry_descriptors_and_layers_verified\": true,\n \"base9layers_unchanged\": true,\n \"overlay_layers\": 2,\n \"fullscan_baseline\": {\n \"MEDIUM\": 1,\n \"id\": \"CVE-2026-85091\"\n },\n \"fullscan_candidate_allseverity_findings\": 0,\n \"fullscan_no_suppressions\": true,\n \"same_fresh_DB_both_images\": true,\n \"scanner_db_sha256\": \"03cacb40452ceb74afdfca072cb82f79ee884dfac006ef24a4dfdad133b0f917\",\n \"scanner_db_metadata\": {\n \"Version\": 2,\n \"NextUpdate\": \"2026-10-11T18:59:50.889010572Z\",\n \"UpdatedAt\": \"2026-10-10T18:59:50.889010923Z\",\n \"DownloadedAt\": \"2026-10-10T23:32:32.380033131Z\"\n },\n \"scanner_caveat\": \"Alpine3.24 absent EOL list warning; actual38OS+6Python detectors ran. Zero means this verified DB snapshot, not unknown-future assurance or estate risk grade.\",\n \"sbom_count\": 44,\n \"sbom_only_delta\": {\n \"zlib\": [\n \"1.3.2-r0\",\n \"1.3.2-r1\"\n ]\n },\n \"final_changed_files\": [\n \"lib/apk/db/installed\",\n \"usr/lib/libz.so.1.3.2\"\n ],\n \"directory_count\": 313,\n \"directory_metadata_delta\": {},\n \"app_files_identical\": 36,\n \"installed_UID\": 10001,\n \"installed_GID\": 10001,\n \"policy_sha256\": \"9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62\",\n \"policy_mode\": \"0o644\",\n \"policy_UID\": 0,\n \"candidate_image_executed\": true,\n \"public_registry_layer_readback\": true,\n \"native_provider_test\": false,\n \"installed_purpose_variants\": 10,\n \"purpose_forms_allow\": 2,\n \"purpose_forms_refuse\": 8,\n \"runtime_readonly_networknone\": true,\n \"native_workload_or_database_or_snapshot_or_credentials_accessed\": false,\n \"native_rollout\": false,\n \"founder_release_approval\": false,\n \"risk_followthrough\": \"T05 remains active; manual review due2026-10-18, no controller or estate risk grade. Actual native installation/fresh scoped founder decision still required before closure.\",\n \"verification_refinement\": \"First run used registry config as daemon handle and refused before candidate execution125. Corrected by independently proving loaded daemon ID semantics+diffIDs exact, then executed. Not actual image failure.\",\n \"evidence_sha256\": {\n \"artifact-comparison.json\": \"a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4\",\n \"base-image-config.json\": \"4ad7249c75fd9fc039ceb186772419e1cba5fd9759c404556459c80a2d285d10\",\n \"base-image-manifest.json\": \"978c575d3c7bf0de39dbe4e9567c39cd36f70b7e55bc17357bdb03fb3b96b0fb\",\n \"base-runtime.json\": \"20aabb9f4846ab4fa6e28e0763d153188da151ee32293366a51b0fdf872a4d24\",\n \"base-sbom.cdx.json\": \"dd7d64c9571391acd4d1f86c6026a1b094abab4ee56e5a9f799ec2fd3c7d915a\",\n \"base-sbom.cdx.json.log\": \"8ab4473a5f74b36e82a4095e9c8d6e66d80aec64bd1cebd6c9f6734531869fca\",\n \"base-trivy-full.json\": \"3e91a3001adb008cec37d9ae3c08342f5d43c8ba0111eee75c63a6a7918808a9\",\n \"base-trivy-full.json.log\": \"96a48dc39f7d674b4954d5fa98a76645528ec0fe705782fe114e3b4b9d776862\",\n \"candidate-runtime.json\": \"d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124\",\n \"ci-publication-observation.json\": \"ffe6f2508bc9bebbdc85ac63eae5765562efbb8e920cc85b8da30a12ee1e4b17\",\n \"image-config.json\": \"253ab359013aeb3457d2abe0852bacd33697f973dd32c1b5738800e5dc633f43\",\n \"image-manifest.json\": \"9ca2abec6a26d9a9f802dfe75efa8bffdc28485e6bcaf525cc9b075df3700194\",\n \"installed-purpose.json\": \"312312c6f0bd2751401b3bc5bcd30ee6af01ed7cb380f85b863d03b0c7e9d1ad\",\n \"loaded-daemon-image-proof.json\": \"2e5c043a101d75e4c569adb6bc2f09020679b28ed2ad836c954f7d27101ce719\",\n \"publication-digest.json\": \"ca2a1dec38db8c5fbbc00ef4f27aa865edbbd4a7ca9dc21b2ba023664c507f38\",\n \"read_public_artifact.py\": \"eaa31da1ebb2095ea5e1bc7c570311cc4747f0d7f718034dad934351d1920daf\",\n \"registry-proof.json\": \"3ef52d9e4800fc9bd238072241dc15f13a0260ee3df1e82e6f63e72902749d92\",\n \"run_published_runtime.py\": \"d9bd33fecd2a98c2381b4ba2dce12b5c3d9a69faa5e350291924fc885f5e775f\",\n \"run_published_scan.py\": \"2583ab96350412fb3f20b5b05ded0949eb3ee8e5c0e8923a9c2c42d419d36639\",\n \"runtime-comparison.json\": \"993b80b9285e227a0bdfde4927cd2f053bcc4a58dd9bd1b633ac7a4637d6713c\",\n \"runtime-receipt.json\": \"6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889\",\n \"sbom.cdx.json\": \"4202263059780eae0448328044c1cf68b59758f329ae6bc981d916610527eae3\",\n \"sbom.cdx.json.log\": \"51873b37a0846ee1a618ea7591d79809dc0f8624f77a03fae7af953bcbd7282f\",\n \"scan-receipt.json\": \"aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a\",\n \"source-commit-proof.json\": \"b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b\",\n \"trivy-full.json\": \"2ff68e765b9c65292611d5a969202a7654637168edefcedac5d2c7634009edae\",\n \"trivy-full.json.log\": \"b3419af860d264449f61986f562491edaead27dfd3638ceabe94b4fe177f4bf1\"\n }\n}\n" - }, - { - "item_id": "artifact-comparison-json", - "media_type": "application/json", - "content": "{\n \"base_image_config_id\": \"sha256:f34de21379c4f50ea0361ca1d8296c8e72f989499ad288016b81204997ae4779\",\n \"candidate_local_image_config_id\": \"sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b\",\n \"candidate_registry_digest\": null,\n \"base_layers_preserved\": 9,\n \"new_layers\": 2,\n \"effective_runtime_config_identical\": true,\n \"raw_config_bytes_identical\": false,\n \"serialization_note\": \"Legacy base explicitly serialized empty/false/null defaults; candidate Docker-save config omits equivalent known defaults. Exact closed default normalization only; runtime fields unchanged.\",\n \"labels_before\": {\n \"org.opencontainers.image.base.digest\": \"sha256:7f5c6cb29c9f70a026bfe30e72934274ddc69261676cf6d476f81dd32812d173\",\n \"org.opencontainers.image.revision\": \"3e4bcb26f8eeaa8c64345c565477b5ac8ccbb168\",\n \"social.coulomb.release.scope\": \"INFD-WP-0009-T04/purpose-overlay-v1\",\n \"social.coulomb.source.policy.sha256\": \"9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62\"\n },\n \"labels_after\": {\n \"org.opencontainers.image.base.digest\": \"sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"org.opencontainers.image.revision\": \"9b4bb25d325698ae543e7e8a8a3c3592db1f58fc\",\n \"social.coulomb.release.scope\": \"INFD-WP-0009-T05/zlib-85091-r1\",\n \"social.coulomb.source.context.sha256\": \"c554e62a84af340cba15d469a4568f6d0c40923e580b08ed7565e22702b1207d\",\n \"social.coulomb.source.policy.sha256\": \"9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62\"\n },\n \"final_changed_files\": {\n \"lib/apk/db/installed\": {\n \"before\": [\n \"file\",\n 420,\n 0,\n 0,\n \"6383ea3806247e8dbf9ebee8cb30eb8eca63a8ea59662a793127d6a61b988d70\"\n ],\n \"after\": [\n \"file\",\n 420,\n 0,\n 0,\n \"f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001\"\n ]\n },\n \"usr/lib/libz.so.1.3.2\": {\n \"before\": [\n \"file\",\n 493,\n 0,\n 0,\n \"a1e2c03dcdedba98a06a00cadfb67a0e0b1cd93986860ec0c5ce66e789c44e57\"\n ],\n \"after\": [\n \"file\",\n 493,\n 0,\n 0,\n \"ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2\"\n ]\n }\n },\n \"directory_metadata_compared\": true,\n \"directory_count_base\": 313,\n \"directory_count_candidate\": 313,\n \"directory_metadata_delta\": {},\n \"sbom_package_count_base\": 44,\n \"sbom_package_count_candidate\": 44,\n \"sbom_only_delta\": {\n \"zlib\": [\n \"1.3.2-r0\",\n \"1.3.2-r1\"\n ]\n },\n \"full_scan_baseline_findings\": 1,\n \"full_scan_candidate_allseverity_findings\": 0,\n \"db_snapshot_same\": true,\n \"native_workload_changed\": false\n}\n" - }, - { - "item_id": "installed-zlib-proof-json", - "media_type": "application/json", - "content": "{\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"zlib_package\": \"1.3.2-r1\",\n \"files\": {\n \"lib/apk/db/installed\": [\n \"file\",\n 420,\n 0,\n 0,\n \"f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001\"\n ],\n \"usr/lib/libz.so.1.3.2\": [\n \"file\",\n 493,\n 0,\n 0,\n \"ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2\"\n ]\n }\n}\n" - }, - { - "item_id": "sealed-live-deployment-json", - "media_type": "application/json", - "content": "{\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n}\n" - }, - { - "item_id": "sealed-native-dependencies-json", - "media_type": "application/json", - "content": "{\n \"configmap\": {\n \"apiVersion\": \"v1\",\n \"immutable\": true,\n \"kind\": \"ConfigMap\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/ConfigMap:informed-decision/informed-decision-runtime-6c221878d051\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision-runtime-6c221878d051\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"90113d2d-54d9-4e82-b1d3-90b49ebed2b5\"\n },\n \"canonical_data_sha256\": \"8ec462d12347548f506eeebf1e3c7cc47648019c8a783804d09bcc79b0f22af6\"\n },\n \"pvc\": {\n \"apiVersion\": \"v1\",\n \"kind\": \"PersistentVolumeClaim\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:/PersistentVolumeClaim:informed-decision/informed-decision-data\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\",\n \"pv.kubernetes.io/bind-completed\": \"yes\",\n \"pv.kubernetes.io/bound-by-controller\": \"yes\",\n \"volume.beta.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\",\n \"volume.kubernetes.io/selected-node\": \"239.62.205.92.host.secureserver.net\",\n \"volume.kubernetes.io/storage-provisioner\": \"rancher.io/local-path\"\n },\n \"finalizers\": [\n \"kubernetes.io/pvc-protection\"\n ],\n \"name\": \"informed-decision-data\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n },\n \"spec\": {\n \"accessModes\": [\n \"ReadWriteOnce\"\n ],\n \"resources\": {\n \"requests\": {\n \"storage\": \"1Gi\"\n }\n },\n \"storageClassName\": \"local-path\",\n \"volumeMode\": \"Filesystem\",\n \"volumeName\": \"pvc-d80b25b1-141a-441b-b205-98fa2fd98b7c\"\n }\n },\n \"pvc_phase\": \"Bound\"\n}\n" - }, - { - "item_id": "candidate-deployment-json", - "media_type": "application/json", - "content": "{\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n}\n" - }, - { - "item_id": "rollback-deployment-json", - "media_type": "application/json", - "content": "{\n \"apiVersion\": \"apps/v1\",\n \"kind\": \"Deployment\",\n \"metadata\": {\n \"annotations\": {\n \"argocd.argoproj.io/tracking-id\": \"informed-decision:apps/Deployment:informed-decision/informed-decision\",\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"name\": \"informed-decision\",\n \"namespace\": \"informed-decision\",\n \"uid\": \"a588e10a-90d2-4883-975e-6dd8b8feca92\"\n },\n \"spec\": {\n \"progressDeadlineSeconds\": 600,\n \"replicas\": 1,\n \"revisionHistoryLimit\": 10,\n \"selector\": {\n \"matchLabels\": {\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"strategy\": {\n \"type\": \"Recreate\"\n },\n \"template\": {\n \"metadata\": {\n \"annotations\": {\n \"informed-decision.coulomb.social/admission\": \"approved; INFD-WP-0007 cutover\"\n },\n \"labels\": {\n \"app.kubernetes.io/component\": \"review\",\n \"app.kubernetes.io/name\": \"informed-decision\"\n }\n },\n \"spec\": {\n \"automountServiceAccountToken\": false,\n \"containers\": [\n {\n \"env\": [\n {\n \"name\": \"INFD_KEYCAPE_ISSUER\",\n \"value\": \"https://kc.coulomb.social\"\n },\n {\n \"name\": \"INFD_CONTAINER_CONFIG\",\n \"value\": \"/configuration/runtime.json\"\n }\n ],\n \"image\": \"forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a\",\n \"imagePullPolicy\": \"IfNotPresent\",\n \"livenessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"name\": \"informed-decision\",\n \"ports\": [\n {\n \"containerPort\": 8080,\n \"name\": \"http\",\n \"protocol\": \"TCP\"\n },\n {\n \"containerPort\": 8081,\n \"name\": \"workload\",\n \"protocol\": \"TCP\"\n }\n ],\n \"readinessProbe\": {\n \"failureThreshold\": 3,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"resources\": {\n \"limits\": {\n \"cpu\": \"500m\",\n \"memory\": \"256Mi\"\n },\n \"requests\": {\n \"cpu\": \"5m\",\n \"memory\": \"64Mi\"\n }\n },\n \"securityContext\": {\n \"allowPrivilegeEscalation\": false,\n \"capabilities\": {\n \"drop\": [\n \"ALL\"\n ]\n },\n \"readOnlyRootFilesystem\": true\n },\n \"startupProbe\": {\n \"failureThreshold\": 12,\n \"httpGet\": {\n \"path\": \"/healthz\",\n \"port\": \"http\",\n \"scheme\": \"HTTP\"\n },\n \"periodSeconds\": 10,\n \"successThreshold\": 1,\n \"timeoutSeconds\": 2\n },\n \"terminationMessagePath\": \"/dev/termination-log\",\n \"terminationMessagePolicy\": \"File\",\n \"volumeMounts\": [\n {\n \"mountPath\": \"/data\",\n \"name\": \"data\"\n },\n {\n \"mountPath\": \"/run/informed-decision\",\n \"name\": \"runtime\"\n },\n {\n \"mountPath\": \"/tmp\",\n \"name\": \"tmp\"\n },\n {\n \"mountPath\": \"/configuration\",\n \"name\": \"configuration\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/policy\",\n \"name\": \"caller\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/audit\",\n \"name\": \"audit\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/kubernetes\",\n \"name\": \"kubernetes-api\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/workload-registry\",\n \"name\": \"workload-registry\",\n \"readOnly\": true\n },\n {\n \"mountPath\": \"/var/run/secrets/informed-decision/requester\",\n \"name\": \"sitting-requester\",\n \"readOnly\": true\n }\n ]\n }\n ],\n \"dnsPolicy\": \"ClusterFirst\",\n \"restartPolicy\": \"Always\",\n \"schedulerName\": \"default-scheduler\",\n \"securityContext\": {\n \"fsGroup\": 10001,\n \"fsGroupChangePolicy\": \"OnRootMismatch\",\n \"runAsGroup\": 10001,\n \"runAsNonRoot\": true,\n \"runAsUser\": 10001,\n \"seccompProfile\": {\n \"type\": \"RuntimeDefault\"\n }\n },\n \"serviceAccount\": \"review\",\n \"serviceAccountName\": \"review\",\n \"terminationGracePeriodSeconds\": 45,\n \"volumes\": [\n {\n \"name\": \"data\",\n \"persistentVolumeClaim\": {\n \"claimName\": \"informed-decision-data\"\n }\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"runtime\"\n },\n {\n \"emptyDir\": {\n \"medium\": \"Memory\",\n \"sizeLimit\": \"16Mi\"\n },\n \"name\": \"tmp\"\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-runtime-6c221878d051\"\n },\n \"name\": \"configuration\"\n },\n {\n \"name\": \"caller\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"audience\": \"flex-auth\",\n \"expirationSeconds\": 3600,\n \"path\": \"token\"\n }\n }\n ]\n }\n },\n {\n \"name\": \"audit\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"token\",\n \"path\": \"token\"\n }\n ],\n \"secretName\": \"informed-decision-audit\"\n }\n },\n {\n \"name\": \"kubernetes-api\",\n \"projected\": {\n \"defaultMode\": 288,\n \"sources\": [\n {\n \"serviceAccountToken\": {\n \"expirationSeconds\": 600,\n \"path\": \"token\"\n }\n },\n {\n \"configMap\": {\n \"items\": [\n {\n \"key\": \"ca.crt\",\n \"path\": \"ca.crt\"\n }\n ],\n \"name\": \"kube-root-ca.crt\"\n }\n }\n ]\n }\n },\n {\n \"configMap\": {\n \"defaultMode\": 292,\n \"name\": \"informed-decision-workload-registry\"\n },\n \"name\": \"workload-registry\"\n },\n {\n \"name\": \"sitting-requester\",\n \"secret\": {\n \"defaultMode\": 288,\n \"items\": [\n {\n \"key\": \"client-secret\",\n \"path\": \"client-secret\"\n }\n ],\n \"optional\": false,\n \"secretName\": \"informed-decision-sitting-requester\"\n }\n }\n ]\n }\n }\n }\n}\n" - }, - { - "item_id": "execution-bundle-json", - "media_type": "application/json", - "content": "{\n \"kind\": \"informed-decision.supervised-execution-source-bundle\",\n \"version\": 1,\n \"proposal_id\": \"INFD-WP-0009-T05/zlib85091-r1\",\n \"files\": {\n \"execution/actuator.py\": {\n \"sha256\": \"dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80\",\n \"content\": \"\\\"\\\"\\\"One fixed supervised phase; never run this during source preparation.\\\"\\\"\\\"\\nimport argparse\\nimport hashlib\\nimport json\\nimport os\\nfrom pathlib import Path\\nimport re\\nimport selectors\\nimport stat\\nimport subprocess\\nimport sys\\nimport time\\n\\nif sys.flags.optimize:\\n raise SystemExit('optimized interpreter refused')\\n\\nPHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline',\\n 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify')\\n\\n\\ndef require(value, reason):\\n if not value:\\n raise ValueError(reason)\\n\\n\\ndef sha(path):\\n digest = hashlib.sha256()\\n with Path(path).open('rb') as stream:\\n for block in iter(lambda: stream.read(1024**2), b''):\\n digest.update(block)\\n return digest.hexdigest()\\n\\n\\ndef private(path, directory=False):\\n info = Path(path).lstat()\\n require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode))\\n and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600)\\n and (directory or info.st_nlink == 1), 'private owned custody required')\\n\\nROOT = Path(__file__).resolve().parent\\nCASE = ROOT.parent\\nOWNER = Path('/home/worsch/informed-decision')\\nCUSTODIAN = Path('/home/worsch/the-custodian')\\nCUSTODY = CUSTODIAN / '.private-backups/20261011-infd-zlib-r1'\\nSOCKET = 'unix:///var/run/docker.sock'\\nIMAGE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23'\\nVOLUME = 'infd-zlib-r1-restore-20261011'\\nMAX_BYTES = 512 * 1024**2\\n\\n\\ndef run(argv, body=None, timeout=90):\\n return subprocess.run(argv, input=body, text=True, capture_output=True, timeout=timeout)\\n\\n\\ndef json_result(result):\\n try:\\n value = json.loads(result.stdout)\\n except (ValueError, TypeError):\\n raise ValueError('safe phase receipt unavailable; outcome uncertain') from None\\n require(result.returncode == 0 and isinstance(value, dict) and value.get('all_checks_pass') is True,\\n 'phase did not verify complete; raw output withheld; no retry')\\n permitted = {'phase', 'all_checks_pass', 'raw_output_withheld', 'native_approval_verified_by_root',\\n 'native_authenticated_grant', 'source_readonly_baseline', 'snapshot_readonly',\\n 'snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'native_parent_private',\\n 'native_uid', 'pod_name', 'pod_uid', 'live_readonly_schema_integrity_counts',\\n 'live_counts_may_include_concurrent_commits', 'operation_requested', 'source_revision',\\n 'resources', 'prune', 'rollout_verified', 'latest_live_database_retained',\\n 'client_diff_sha256', 'native_rollout_verified', 'current_schema5_database_retained',\\n 'private_copy_verified', 'installed_purpose_checks', 'policy_sha256',\\n 'policy_mode', 'policy_uid', 'installed_zlib_files', 'zlib_package', 'health_statuses', 'native_provider_probe', 'recovery_verification'}\\n require(set(value) <= permitted, 'unexpected output fields withheld')\\n from receipt_safety import counts\\n for name in ('source_readonly_baseline', 'snapshot_readonly', 'live_readonly_schema_integrity_counts'):\\n if name in value:\\n counts(value[name])\\n for name in ('snapshot_sha256', 'client_diff_sha256'):\\n if name in value:\\n require(re.fullmatch(r'[a-f0-9]{64}', value[name]) is not None, 'invalid output digest withheld')\\n if 'snapshot_bytes' in value:\\n require(type(value['snapshot_bytes']) is int and 0 < value['snapshot_bytes'] <= MAX_BYTES,\\n 'invalid output size withheld')\\n if 'snapshot_file' in value:\\n require(value['snapshot_file'] in ('drill.sqlite', 'cutover.sqlite'), 'invalid output file withheld')\\n return value\\n\\n\\ndef docker(argv, body=None, timeout=120):\\n return run(['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET] + argv,\\n body=body, timeout=timeout)\\n\\n\\ndef remote(phase, revision, approved):\\n # Fixed module imported only after main verifies every executor source hash.\\n from native_program import PROGRAM\\n payload = {'phase': phase, 'source_revision': revision, 'approval': approved,\\n 'sealed': json.loads((CASE / 'sealed-live-deployment.json').read_text()),\\n 'dependencies': json.loads((CASE / 'sealed-native-dependencies.json').read_text()),\\n 'candidate': json.loads((CASE / 'candidate-deployment.json').read_text()),\\n 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()),\\n 'receipt_safety': (ROOT / 'receipt_safety.py').read_text(),\\n 'purpose_checks': (CASE / 'check_installed.py').read_text(),\\n 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'readonly': (CASE / 'sqlite_readonly.py').read_text()}\\n script = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace(\\n '__GUARD__', repr((CASE / 'change_guard.py').read_text()))\\n return json_result(run(['ssh', 'railiance01', 'python3 -'], script, timeout=200))\\n\\n\\ndef copy_snapshot(phase, revision, approved, journal):\\n metadata = remote(phase, revision, approved)\\n backup_phase = 'drill-backup' if phase == 'drill-copy' else 'cutover-backup'\\n prior = journal.data['phases'][backup_phase]['result']\\n for key in ('snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'snapshot_readonly'):\\n require(metadata[key] == prior[key], 'closed frozen snapshot changed')\\n name = metadata['snapshot_file']\\n require(name in ('drill.sqlite', 'cutover.sqlite') and 0 < metadata['snapshot_bytes'] <= MAX_BYTES,\\n 'fixed bounded snapshot required')\\n pod = metadata['pod_name']\\n require(re.fullmatch(r'[a-z0-9]([-a-z0-9]*[a-z0-9])?', pod) is not None, 'fixed owner pod name required')\\n local = CUSTODY / name\\n fd = os.open(local, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)\\n # Binary snapshot stdout goes only to a private file, never tool/chat/log output.\\n # Snapshot schema/count metadata was validated before opening this destination.\\n with os.fdopen(fd, 'wb') as destination:\\n command = ('kubectl exec -n informed-decision ' + pod +\\n ' -c informed-decision -- cat /data/private/zlib85091-r1/' + name)\\n transfer = subprocess.Popen(['ssh', 'railiance01', command], stdout=subprocess.PIPE,\\n stderr=subprocess.DEVNULL)\\n selector = selectors.DefaultSelector()\\n selector.register(transfer.stdout, selectors.EVENT_READ)\\n deadline = time.monotonic() + 150\\n received = 0\\n try:\\n while True:\\n remaining = deadline - time.monotonic()\\n require(remaining > 0 and selector.select(remaining), 'private transfer timed out')\\n block = os.read(transfer.stdout.fileno(), 1024**2)\\n if not block:\\n break\\n received += len(block)\\n require(received <= metadata['snapshot_bytes'] and received <= MAX_BYTES,\\n 'private copy exceeds frozen snapshot size')\\n destination.write(block)\\n transfer.wait(timeout=max(1, deadline - time.monotonic()))\\n finally:\\n selector.close()\\n transfer.stdout.close()\\n if transfer.poll() is None:\\n transfer.kill()\\n transfer.wait()\\n destination.flush()\\n os.fsync(destination.fileno())\\n private(local)\\n require(transfer.returncode == 0 and local.stat().st_size == metadata['snapshot_bytes']\\n and sha(local) == metadata['snapshot_sha256'], 'private copy incomplete/uncertain; retain, no retry')\\n # Re-read identity/hash after transfer. This is verification, not a write retry.\\n after = remote(phase, revision, approved)\\n require(after['pod_uid'] == metadata['pod_uid'] and after['snapshot_sha256'] == metadata['snapshot_sha256'],\\n 'source identity/snapshot changed during transfer')\\n return {**metadata, 'off_cluster_private_copy_verified': True, 'snapshot_contents_output': False}\\n\\n\\ndef offline_verify(journal):\\n from receipt_safety import counts\\n snapshot = CUSTODY / 'drill.sqlite'\\n private(snapshot)\\n previous = journal.data['phases']['drill-copy']['result']\\n require(sha(snapshot) == previous['snapshot_sha256'], 'protected snapshot bytes changed')\\n identity = docker(['info', '--format', '{{json .Name}}'])\\n require(identity.returncode == 0 and json.loads(identity.stdout) == 'bnt-lap001',\\n 'local offline Docker engine identity changed')\\n listed = docker(['volume', 'ls', '--format', '{{.Name}}'])\\n require(listed.returncode == 0 and VOLUME not in listed.stdout.splitlines(),\\n 'disposable volume absence not verified')\\n nonce = journal.data['phases']['offline-verify']['attempt_id']\\n labels = {'informed-decision.case': 'infd-zlib-20261011-r1',\\n 'informed-decision.use': 'offline-restore-only', 'informed-decision.attempt': nonce}\\n created = docker(['volume', 'create', '--driver', 'local', '--label', 'informed-decision.case=infd-zlib-20261011-r1',\\n '--label', 'informed-decision.attempt=' + nonce,\\n '--label', 'informed-decision.use=offline-restore-only', VOLUME])\\n require(created.returncode == 0 and created.stdout.strip() == VOLUME, 'volume creation uncertain; no retry')\\n inspected = docker(['volume', 'inspect', VOLUME])\\n require(inspected.returncode == 0, 'new volume identity unavailable')\\n volume = json.loads(inspected.stdout)\\n require(len(volume) == 1 and volume[0]['Name'] == VOLUME and volume[0].get('Labels') == labels\\n and volume[0].get('Driver') == 'local' and not volume[0].get('Options')\\n and volume[0].get('Scope') == 'local', 'new isolated volume identity changed')\\n identity = {key: volume[0][key] for key in ('Name', 'Labels', 'Driver', 'Scope', 'CreatedAt')}\\n journal.note('offline-verify', 'created_volume_identity', identity)\\n # One-time UID0/CAP_CHOWN initialization is isolated to the NEW named volume.\\n # No host/live PVC/config/token mount exists; this detail needs packet review.\\n initializer = \\\"\\\"\\\"import hashlib,os,stat,sys\\nif sys.flags.optimize:raise RuntimeError('optimized interpreter refused')\\nfolder='/restore';expected=EXPECTED\\nif os.listdir(folder):raise ValueError('new empty private volume required')\\nos.chmod(folder,0o700)\\nfd=os.open(folder+'/review.sqlite',os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_WRONLY,0o600)\\ndigest=hashlib.sha256();size=0\\nwith os.fdopen(fd,'wb') as target:\\n while True:\\n block=sys.stdin.buffer.read(1024**2)\\n if not block:break\\n size+=len(block)\\n if size>512*1024**2:raise ValueError('private restore size exceeds bound')\\n target.write(block);digest.update(block)\\n target.flush();os.fsync(target.fileno())\\nif digest.hexdigest()!=expected:raise ValueError('private copied snapshot differs')\\nos.chown(folder+'/review.sqlite',10001,10001);os.chown(folder,10001,10001)\\nprint('{\\\"all_checks_pass\\\":true,\\\"private_copy_verified\\\":true}')\\n\\\"\\\"\\\".replace('EXPECTED', repr(previous['snapshot_sha256']))\\n with snapshot.open('rb') as source:\\n argv = ['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET,\\n 'run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m',\\n '--user', '0:0', '--cap-drop', 'ALL', '--cap-add', 'CHOWN',\\n '--security-opt', 'no-new-privileges', '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore',\\n '--entrypoint', 'python', IMAGE, '-c', initializer]\\n copied = subprocess.run(argv, stdin=source, text=True, capture_output=True, timeout=150)\\n json_result(copied)\\n options = ['run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m',\\n '--user', '10001:10001', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges',\\n '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore']\\n readonly = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'],\\n (CASE / 'sqlite_readonly.py').read_text())\\n require(readonly.returncode == 0, 'offline pre-open schema/integrity gate refused; never open Store')\\n checked = counts(json.loads(readonly.stdout))\\n require(checked == previous['snapshot_readonly'], 'frozen snapshot/clone readonly counts differ')\\n admin = docker(options + ['--entrypoint', 'informed-decision-admin', IMAGE,\\n 'inspect', '--db', '/restore/review.sqlite'])\\n require(admin.returncode == 0, 'offline admin result uncertain')\\n admin_counts = json.loads(admin.stdout)\\n require(admin_counts == {key: checked[key] for key in ('schema_version', 'lifecycle', 'outbox', 'submissions')},\\n 'offline admin changed frozen snapshot counters')\\n # Admin Store construction must not silently alter schema/integrity/counters.\\n post = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'],\\n (CASE / 'sqlite_readonly.py').read_text())\\n require(post.returncode == 0, 'offline post-admin readonly integrity gate refused')\\n post_checked = counts(json.loads(post.stdout))\\n require(post_checked == checked == previous['snapshot_readonly'],\\n 'offline admin altered frozen snapshot proof')\\n private(snapshot)\\n require(sha(snapshot) == previous['snapshot_sha256'], 'original protected snapshot changed during drill')\\n return {'all_checks_pass': True, 'offline_volume': VOLUME, 'offline_readonly': checked,\\n 'offline_post_admin_readonly': post_checked, 'original_snapshot_hash_unchanged': True,\\n 'created_volume_identity': identity,\\n 'admin_counts': admin_counts, 'network': 'none', 'runtime_tokens_or_config_mounted': False,\\n 'service_entrypoint_started': False, 'original_snapshot_retained': True}\\n\\n\\ndef cleanup_offline(journal):\\n inspected = docker(['volume', 'inspect', VOLUME])\\n require(inspected.returncode == 0, 'owned disposable volume identity unverified')\\n value = json.loads(inspected.stdout)\\n expected = journal.data['phases']['offline-verify']['result']['created_volume_identity']\\n require(len(value) == 1 and {key: value[0].get(key) for key in expected} == expected\\n and not value[0].get('Options'), 'volume ownership/recreation changed')\\n removed = docker(['volume', 'rm', VOLUME])\\n require(removed.returncode == 0 and removed.stdout.strip() == VOLUME\\n and docker(['volume', 'inspect', VOLUME]).returncode != 0,\\n 'cleanup outcome ambiguous; do not retry')\\n return {'all_checks_pass': True, 'only_disposable_offline_volume_removed': VOLUME,\\n 'native_and_offcluster_original_snapshots_retained': True}\\n\\n\\ndef main():\\n parser = argparse.ArgumentParser(description=__doc__)\\n parser.add_argument('--phase', choices=PHASES, required=True)\\n parser.add_argument('--root-authority-receipt', type=Path, required=True)\\n parser.add_argument('--root-native-outcome', type=Path, required=True)\\n parser.add_argument('--source-revision', required=True)\\n parser.add_argument('--root-fresh-scan-receipt', type=Path, required=True)\\n args = parser.parse_args()\\n require(args.root_authority_receipt == Path('/tmp/security-infd-zlib-root-authority.json')\\n and args.root_native_outcome == Path('/tmp/security-infd-zlib-native-outcome-latest.json'),\\n 'fixed supervised receipt paths required')\\n require(re.fullmatch(r'[0-9a-f]{40}', args.source_revision) is not None, 'exact source commit required')\\n # Verify all prepared executor/input bytes before reading authority or dispatching.\\n plan = json.loads((ROOT / 'execution-plan.json').read_text())\\n require(plan.get('state') == 'prepared-unapproved' and plan.get('published_artifact_verified') is True, 'unfrozen proposal cannot execute')\\n require(set(plan['executor_files']) == {'actuator.py', 'authority_journal.py', 'native_program.py', 'receipt_safety.py'},\\n 'fixed executor sources required')\\n require(set(plan['owner_source_files']) == {'deploy/lifecycle/admission.candidate.json',\\n 'deploy/lifecycle/service.candidate.json',\\n 'deploy/lifecycle/kustomization.yaml',\\n 'deploy/lifecycle/registries.candidate.json'},\\n 'fixed owner image sources required')\\n for relative, digest in plan['executor_files'].items():\\n target = ROOT / relative\\n require(not target.is_symlink() and sha(target) == digest, 'executor source bytes changed')\\n for relative, digest in plan['case_files'].items():\\n require(not Path(relative).is_absolute() and '..' not in Path(relative).parts,\\n 'case source path escaped')\\n target = CASE / relative\\n require(not target.is_symlink() and sha(target) == digest, 'case source bytes changed')\\n for relative, digest in plan['owner_source_files'].items():\\n target = OWNER / relative\\n require(not target.is_symlink() and sha(target) == digest, 'owner image-only source changed')\\n committed = subprocess.run(['git', '-C', str(OWNER), 'show', args.source_revision + ':' + relative],\\n capture_output=True)\\n require(committed.returncode == 0 and hashlib.sha256(committed.stdout).hexdigest() == digest,\\n 'committed owner render source differs from approved inputs')\\n head = run(['git', '-C', str(OWNER), 'rev-parse', 'HEAD'])\\n require(head.returncode == 0 and head.stdout.strip() == args.source_revision, 'owner source HEAD differs')\\n from authority_journal import Journal, authority, fresh_scan\\n private(args.root_authority_receipt)\\n private(args.root_native_outcome)\\n receipt = json.loads(args.root_authority_receipt.read_text())\\n require(receipt.get('native_outcome_sha256') == sha(args.root_native_outcome), 'native outcome bytes changed')\\n require(receipt.get('executor_plan_sha256') == sha(ROOT / 'execution-plan.json'),\\n 'root verified executor plan differs')\\n approved = authority(receipt, json.loads(args.root_native_outcome.read_text()), sha(CASE / 'memo.json'),\\n args.source_revision, args.phase)\\n if args.phase in ('release', 'verify'):\\n require(args.root_fresh_scan_receipt == Path('/tmp/security-infd-zlib-fresh-scan-receipt.json'), 'fixed fresh scan receipt path required')\\n private(args.root_fresh_scan_receipt)\\n scan_receipt=json.loads(args.root_fresh_scan_receipt.read_text())\\n report_path=Path('/tmp/security-infd-zlib-fresh-scan-full.json');private(report_path)\\n require(sha(report_path)==scan_receipt.get('full_report_sha256'), 'fresh scan report bytes changed')\\n fresh_scan(scan_receipt,json.loads(report_path.read_text()))\\n parent = CUSTODY.parent\\n private(parent, directory=True)\\n ignored = run(['git', '-C', str(CUSTODIAN), 'check-ignore', '--no-index', str(CUSTODY / 'drill.sqlite')])\\n require(ignored.returncode == 0, 'offcluster custody is not ignored')\\n if not CUSTODY.exists():\\n require(args.phase == 'drill-backup', 'first approved phase must create unique private custody')\\n os.mkdir(CUSTODY, 0o700)\\n private(CUSTODY, directory=True)\\n capacity = os.statvfs(CUSTODY)\\n require(capacity.f_bavail * capacity.f_frsize >= 2 * MAX_BYTES + 16 * 1024**2,\\n 'independent protected custody headroom insufficient')\\n os.makedirs(CUSTODY / 'empty-docker-config', mode=0o700, exist_ok=True)\\n private(CUSTODY / 'empty-docker-config', directory=True)\\n require(not list((CUSTODY / 'empty-docker-config').iterdir()), 'public Docker read config must stay empty')\\n journal = Journal(CUSTODY, {'proposal': plan['proposal_id'], 'source_revision': args.source_revision,\\n 'memo_sha256': sha(CASE / 'memo.json'), 'plan_sha256': sha(ROOT / 'execution-plan.json')})\\n try:\\n journal.start(args.phase, approved) # fsynced RUNNING before any phase effect.\\n if args.phase in ('drill-copy', 'cutover-copy'):\\n result = copy_snapshot(args.phase, args.source_revision, approved, journal)\\n elif args.phase == 'offline-verify':\\n result = offline_verify(journal)\\n elif args.phase == 'cleanup-offline':\\n result = cleanup_offline(journal)\\n else:\\n result = remote(args.phase, args.source_revision, approved, operation_intent=journal.data['phases'].get('rollback' if args.phase == 'rollback-verify' else 'release') if args.phase in ('verify', 'rollback-verify') else None)\\n journal.finish(args.phase, result)\\n print(json.dumps(result)) # Only count/hash/identity receipts, never snapshot bytes.\\n except BaseException as error:\\n safe = {'all_checks_pass': False, 'phase': args.phase, 'failure_class': type(error).__name__,\\n 'failure': 'fixed phase outcome uncertain; original evidence retained; no retry',\\n 'raw_output_withheld': True}\\n if journal.data['phases'].get(args.phase, {}).get('state') == 'RUNNING':\\n journal.finish(args.phase, safe)\\n print(json.dumps(safe))\\n raise SystemExit(1) from None\\n finally:\\n journal.close()\\n\\n\\nif __name__ == '__main__':\\n try:\\n main()\\n except Exception:\\n raise SystemExit('supervised execution refused before phase; raw output withheld') from None\\n\"\n },\n \"execution/authority_journal.py\": {\n \"sha256\": \"2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108\",\n \"content\": \"\\\"\\\"\\\"Supervised receipt bookkeeping, not an authenticated approval provider.\\\"\\\"\\\"\\nimport datetime\\nimport fcntl\\nimport hashlib\\nimport json\\nimport os\\nimport re\\nfrom pathlib import Path\\nimport stat\\nimport uuid\\n\\nFOUNDER = {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'}\\nMEMO_ID = 'infd-20261011-zlib-remediation-r1'\\nPROPOSAL = 'INFD-WP-0009-T05/zlib85091-r1'\\nPHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline',\\n 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify')\\n\\n\\ndef require(value, reason):\\n if not value:\\n raise ValueError(reason)\\n\\n\\ndef sha(path):\\n digest = hashlib.sha256()\\n with Path(path).open('rb') as stream:\\n for block in iter(lambda: stream.read(1024**2), b''):\\n digest.update(block)\\n return digest.hexdigest()\\n\\n\\ndef private(path, directory=False):\\n info = Path(path).lstat()\\n require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode))\\n and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600)\\n and (directory or info.st_nlink == 1), 'private owned custody required')\\n\\n\\ndef authority(receipt, outcome, memo_sha, source_revision, phase, now=None):\\n \\\"\\\"\\\"Root must freshly verify native outcome at fixed ORIGIN outside this helper.\\n\\n Receipt files/flags are not signed grants. This function checks the exact\\n supervised receipt relation; existing harness/founder authority remains the\\n actual permission boundary. It never authenticates a caller as human.\\n \\\"\\\"\\\"\\n now = now or datetime.datetime.now(datetime.timezone.utc)\\n require(phase in PHASES, 'fixed phase required')\\n require(receipt.get('proposal_id') == PROPOSAL and receipt.get('memo_id') == MEMO_ID\\n and receipt.get('memo_version') == 1 and receipt.get('memo_sha256') == memo_sha\\n and receipt.get('source_revision') == source_revision\\n and receipt.get('activation') == 'APPROVED'\\n and receipt.get('authority_mode') == 'ADMINISTER'\\n and receipt.get('target') == 'realm:kubernetes/railiance01/informed-decision'\\n and receipt.get('verification_method') == 'root-fixed-origin-native-readback'\\n and receipt.get('runtime_authenticated_grant') is False\\n and receipt.get('outcome_query') == '/lifecycle/memos/' + MEMO_ID + '/outcomes?version=1',\\n 'exact externally verified supervised receipt required')\\n verified = datetime.datetime.fromisoformat(receipt['verified_at'].replace('Z', '+00:00'))\\n require(verified.tzinfo is not None and 0 <= (now - verified).total_seconds() <= 120,\\n 'fresh root native readback required before every phase')\\n deadline = datetime.datetime.fromisoformat('2026-10-12T23:59:59+00:00')\\n require(now < deadline, 'approved execution window expired')\\n if phase == 'release':\\n require((deadline-now).total_seconds() >= 1800, 'reserve thirty minutes for verification and recovery')\\n require(outcome.get('memo_id') == MEMO_ID and outcome.get('review', {}).get('stale') is False\\n and outcome['review'].get('intake_state') == 'ready', 'native review is not ready/current')\\n review_id = outcome['review']['memo_id']\\n require(review_id == receipt.get('review_memo_id'), 'native review identity changed')\\n accepted = [x for x in outcome.get('dispositions', [])\\n if x.get('submission_state') == 'confirmed'\\n and x.get('disposition', {}).get('verb') == 'accept'\\n and x['disposition'].get('memo_version') == 1]\\n require(len(accepted) == 1, 'one confirmed founder acceptance required')\\n accepted = accepted[0]['disposition']\\n require(accepted.get('actor') == FOUNDER and accepted.get('memo_id') == review_id\\n and accepted.get('approval_id') == receipt.get('approval_id')\\n and accepted.get('id') == receipt.get('disposition_id')\\n and accepted.get('at') == receipt.get('accepted_at'), 'exact founder disposition mismatch')\\n require(re.fullmatch(r'[a-f0-9-]{36}', accepted['approval_id']) is not None\\n and re.fullmatch(r'disp-[a-f0-9-]{36}', accepted['id']) is not None\\n and re.fullmatch(r'review-[a-f0-9-]{36}', review_id) is not None,\\n 'native disposition identifiers are malformed')\\n accepted_at = datetime.datetime.fromisoformat(accepted['at'].replace('Z', '+00:00'))\\n require(accepted_at.tzinfo is not None and accepted_at <= verified, 'acceptance time not bounded')\\n return {key: receipt[key] for key in ('approval_id', 'disposition_id', 'accepted_at', 'verified_at')}\\n\\n\\ndef fresh_scan(receipt, report, now=None):\\n \\\"\\\"\\\"Root supervised scan bookkeeping; actual scanner proof stays retained.\\\"\\\"\\\"\\n now=now or datetime.datetime.now(datetime.timezone.utc)\\n require(receipt.get('image')=='forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23'\\n and receipt.get('artifact_source_revision')=='9b4bb25d325698ae543e7e8a8a3c3592db1f58fc'\\n and receipt.get('scanner')=='aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969'\\n and receipt.get('no_suppression') is True and receipt.get('full_allseverity_scan') is True\\n and receipt.get('exit_code')==0,'exact full immutable artifact scan required')\\n scanned=datetime.datetime.fromisoformat(receipt['completed_at'].replace('Z','+00:00'))\\n metadata=receipt['db_metadata'];updated=datetime.datetime.fromisoformat(metadata['UpdatedAt'].replace('Z','+00:00'));next_update=datetime.datetime.fromisoformat(metadata['NextUpdate'].replace('Z','+00:00'))\\n require(all(t.tzinfo is not None for t in (scanned,updated,next_update))\\n and updated<=scanned<=now0 and size<=512*1024**2 and free>=size*(2 if action==\\\\'drill-backup\\\\' else 1)+16*1024**2,\\\\'bounded native snapshot capacity required\\\\')\\\\n if action==\\\\'drill-backup\\\\':\\\\n require(not os.path.lexists(folder),\\\\'native case directory must be unique\\\\')\\\\n os.mkdir(folder,0o700);os.chmod(folder,0o700)\\\\n private(folder,True)\\\\n filename=\\\\'drill.sqlite\\\\' if action==\\\\'drill-backup\\\\' else \\\\'cutover.sqlite\\\\';output=folder+\\\\'/\\\\'+filename\\\\n require(not os.path.lexists(output),\\\\'snapshot output must be unique\\\\')\\\\n admin=subprocess.run([\\\\'informed-decision-admin\\\\',\\\\'backup\\\\',\\\\'--db\\\\',db,\\\\'--output\\\\',output],capture_output=True,text=True,timeout=120)\\\\n require(admin.returncode==0 and json.loads(admin.stdout)=={\\\\'status\\\\':\\\\'backed_up\\\\',\\\\'consistent_snapshot\\\\':True},\\\\'backup completion not verified; do not retry\\\\')\\\\n private(output)\\\\n for suffix in (\\\\'-wal\\\\',):\\\\n require(not os.path.exists(output+suffix) or os.stat(output+suffix).st_size==0,\\\\'snapshot has WAL frames; immutable read refused\\\\')\\\\n digest=hashlib.sha256()\\\\n with open(output,\\\\'rb\\\\') as stream:\\\\n for block in iter(lambda:stream.read(1024**2),b\\\\'\\\\'):digest.update(block)\\\\n frozen_digest=digest.hexdigest()\\\\n snapshot=counts(inspect_closed_snapshot(output,frozen_digest))\\\\n size=os.stat(output).st_size;require(0 10**12\\n for number in value[group].values()):\\n raise ValueError('count-only receipt values refused')\\n return value\\n\"\n },\n \"execution/test_execution.py\": {\n \"sha256\": \"7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac\",\n \"content\": \"\\\"\\\"\\\"Synthetic custody/transport tests. No native data, Docker or cluster action.\\\"\\\"\\\"\\nimport copy\\nimport datetime\\nimport io\\nimport json\\nimport os\\nfrom pathlib import Path\\nimport subprocess\\nimport sys\\nimport tempfile\\nimport unittest\\nfrom unittest.mock import patch\\nfrom contextlib import redirect_stdout\\n\\nimport actuator\\nfrom authority_journal import Journal, authority, fresh_scan\\nfrom native_program import PROGRAM\\nfrom receipt_safety import counts\\n\\nCASE = Path(__file__).resolve().parent.parent\\nNOW = datetime.datetime(2026, 10, 11, 2, 0, tzinfo=datetime.timezone.utc)\\nREVISION = 'e' * 40\\nCOUNT = {'schema_version': 5, 'integrity': 'ok', 'read_only_pre_open': True, 'store_constructed': False,\\n 'lifecycle': {'records': 3, 'events': 8, 'pending': 1},\\n 'outbox': {'pending': 2, 'delivered': 10}, 'submissions': {'confirmed': 1, 'unresolved': 1}}\\n\\n\\ndef accepted():\\n approval = '11111111-1111-4111-8111-111111111111'\\n review = 'review-22222222-2222-4222-8222-222222222222'\\n disposition = 'disp-33333333-3333-4333-8333-333333333333'\\n receipt = {'proposal_id': 'INFD-WP-0009-T05/zlib85091-r1',\\n 'memo_id': 'infd-20261011-zlib-remediation-r1', 'memo_version': 1,\\n 'memo_sha256': 'a' * 64, 'source_revision': REVISION, 'activation': 'APPROVED',\\n 'authority_mode': 'ADMINISTER', 'target': 'realm:kubernetes/railiance01/informed-decision',\\n 'verification_method': 'root-fixed-origin-native-readback', 'runtime_authenticated_grant': False, 'outcome_query': '/lifecycle/memos/infd-20261011-zlib-remediation-r1/outcomes?version=1',\\n 'verified_at': '2026-10-11T02:00:00Z', 'accepted_at': '2026-10-11T01:59:00Z',\\n 'review_memo_id': review, 'approval_id': approval, 'disposition_id': disposition}\\n outcome = {'memo_id': receipt['memo_id'], 'review': {'memo_id': review, 'stale': False, 'intake_state': 'ready'},\\n 'dispositions': [{'approval_id': None, 'submission_state': 'confirmed',\\n 'disposition': {'verb': 'accept', 'memo_version': 1, 'memo_id': review,\\n 'approval_id': approval, 'id': disposition,\\n 'actor': {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'},\\n 'at': receipt['accepted_at']}}]}\\n return receipt, outcome\\n\\n\\nclass AuthorityAndCustody(unittest.TestCase):\\n def test_exact_nested_founder_receipt_not_top_level_approval(self):\\n receipt, outcome = accepted()\\n result = authority(receipt, outcome, 'a' * 64, REVISION, 'drill-copy', NOW)\\n self.assertEqual(result['approval_id'], receipt['approval_id'])\\n\\n def test_pending_stale_wrong_actor_version_and_binding_refuse(self):\\n mutations = [lambda r, o: r.update(activation='PENDING'),\\n lambda r, o: r.update(memo_sha256='changed'),\\n lambda r, o: r.update(source_revision='changed'),\\n lambda r, o: r.update(target='other'),\\n lambda r, o: r.update(verified_at='2026-10-11T01:00:00Z'),\\n lambda r, o: r.update(verification_method='caller-asserted'),\\n lambda r, o: r.update(runtime_authenticated_grant=True),\\n lambda r, o: o['review'].update(stale=True),\\n lambda r, o: o['review'].update(intake_state='pending'),\\n lambda r, o: o['dispositions'][0].update(submission_state='unresolved'),\\n lambda r, o: o['dispositions'][0]['disposition'].update(memo_version=3),\\n lambda r, o: o['dispositions'][0]['disposition'].update(actor={'kind': 'agent', 'sub': 'platform-root'}),\\n lambda r, o: o['dispositions'].append(copy.deepcopy(o['dispositions'][0]))]\\n for mutate in mutations:\\n with self.subTest(mutate=mutate):\\n r, o = accepted()\\n mutate(r, o)\\n with self.assertRaises(ValueError):\\n authority(r, o, 'a' * 64, REVISION, 'release', NOW)\\n\\n def test_expired_window_and_unknown_phase_refuse(self):\\n r, o = accepted()\\n with self.assertRaises(ValueError):\\n authority(r, o, 'a' * 64, REVISION, 'arbitrary', NOW)\\n late = datetime.datetime(2026, 10, 13, tzinfo=datetime.timezone.utc)\\n r['verified_at'] = late.isoformat()\\n with self.assertRaises(ValueError):\\n authority(r, o, 'a' * 64, REVISION, 'rollback', late)\\n\\n def test_durable_intent_precedes_dispatch_and_never_retries(self):\\n with tempfile.TemporaryDirectory() as directory:\\n parent = Path(directory)\\n os.chmod(parent, 0o700)\\n journal = Journal(parent, {'case': 'fixture'})\\n journal.start('drill-backup', {'approval_id': 'fixture'})\\n self.assertEqual(json.loads(journal.path.read_text())['phases']['drill-backup']['state'], 'RUNNING')\\n journal.close() # simulated crash before result\\n recovered = Journal(parent, {'case': 'fixture'})\\n with self.assertRaises(ValueError):\\n recovered.start('drill-backup', {})\\n with self.assertRaises(ValueError):\\n recovered.start('drill-backup', {})\\n recovered.close()\\n\\n def test_phase_order_and_uncertain_release_allows_only_distinct_recovery(self):\\n with tempfile.TemporaryDirectory() as directory:\\n os.chmod(directory, 0o700)\\n j = Journal(directory, {'case': 'fixture'})\\n with self.assertRaises(ValueError):\\n j.start('release', {})\\n for phase in actuator.PHASES[:6]:\\n j.start(phase, {})\\n j.finish(phase, {'all_checks_pass': True})\\n j.start('release', {})\\n j.finish('release', {'all_checks_pass': False})\\n with self.assertRaises(ValueError):\\n j.start('release', {})\\n with self.assertRaises(ValueError):\\n j.start('verify', {})\\n j.start('rollback', {})\\n j.finish('rollback', {'all_checks_pass': True})\\n j.close()\\n\\n def test_secret_or_unknown_child_output_is_withheld(self):\\n for body in ('not-json', '{\\\"all_checks_pass\\\":true,\\\"token\\\":\\\"private-value\\\"}',\\n '{\\\"all_checks_pass\\\":true,\\\"snapshot_readonly\\\":{\\\"schema_version\\\":5}}'):\\n with self.assertRaises(ValueError):\\n actuator.json_result(subprocess.CompletedProcess([], 0, body, ''))\\n wrong = copy.deepcopy(COUNT)\\n wrong['outbox'] = {'private-row-value': 1}\\n with self.assertRaises(ValueError):\\n counts(wrong)\\n counts(COUNT) # blocked/unresolved counts are not required to be zero.\\n\\n def test_binary_copy_is_bounded_private_and_never_echoed(self):\\n blob = b'synthetic private fixture bytes'\\n digest = __import__('hashlib').sha256(blob).hexdigest()\\n metadata = {'all_checks_pass': True, 'snapshot_file': 'drill.sqlite', 'snapshot_bytes': len(blob),\\n 'snapshot_sha256': digest, 'snapshot_readonly': COUNT,\\n 'pod_name': 'fixture-pod', 'pod_uid': 'fixture-uid'}\\n journal = type('FixtureJournal', (), {'data': {'phases': {'drill-backup': {'result': metadata}}}})()\\n real_popen = subprocess.Popen\\n for extra in (b'', b'oversized'):\\n with self.subTest(extra=extra), tempfile.TemporaryDirectory() as folder:\\n os.chmod(folder, 0o700)\\n output = io.StringIO()\\n\\n def child(*args, **kwargs):\\n return real_popen([sys.executable, '-c', 'import sys;sys.stdout.buffer.write(' + repr(blob + extra) + ')'],\\n stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)\\n\\n with patch.object(actuator, 'CUSTODY', Path(folder)), patch.object(actuator, 'remote', return_value=metadata), \\\\\\n patch('subprocess.Popen', side_effect=child), redirect_stdout(output):\\n if extra:\\n with self.assertRaises(ValueError):\\n actuator.copy_snapshot('drill-copy', REVISION, {}, journal)\\n else:\\n result = actuator.copy_snapshot('drill-copy', REVISION, {}, journal)\\n self.assertTrue(result['off_cluster_private_copy_verified'])\\n self.assertEqual((Path(folder) / 'drill.sqlite').read_bytes(), blob)\\n self.assertEqual(output.getvalue(), '')\\n self.assertEqual((Path(folder) / 'drill.sqlite').stat().st_mode & 0o777, 0o600)\\n\\n def test_recreated_offline_volume_cannot_be_deleted(self):\\n identity = {'Name': actuator.VOLUME, 'Labels': {'nonce': 'old'}, 'CreatedAt': 'old'}\\n journal = type('FixtureJournal', (), {'data': {'phases': {\\n 'offline-verify': {'result': {'created_volume_identity': identity}}}}})()\\n inspected = [{**identity, 'CreatedAt': 'recreated'}]\\n with patch.object(actuator, 'docker', return_value=subprocess.CompletedProcess([], 0, json.dumps(inspected), '')) as mocked:\\n with self.assertRaises(ValueError):\\n actuator.cleanup_offline(journal)\\n self.assertEqual(mocked.call_count, 1)\\n\\n def test_offline_post_admin_integrity_and_original_hash_are_rechecked(self):\\n for defect in ('none', 'post-integrity', 'original-hash'):\\n with self.subTest(defect=defect), tempfile.TemporaryDirectory() as directory:\\n folder=Path(directory);os.chmod(folder,0o700)\\n snapshot=folder/'drill.sqlite';snapshot.write_bytes(b'private synthetic snapshot');os.chmod(snapshot,0o600)\\n digest=actuator.sha(snapshot)\\n prior={'snapshot_sha256':digest,'snapshot_readonly':COUNT}\\n journal=type('FixtureJournal',(),{'data':{'phases':{'drill-copy':{'result':prior},'offline-verify':{'attempt_id':'nonce'}}},'note':lambda *args:None})()\\n reads=[]\\n volume={'Name':actuator.VOLUME,'Labels':{'informed-decision.case':'infd-zlib-20261011-r1','informed-decision.use':'offline-restore-only','informed-decision.attempt':'nonce'},'Driver':'local','Scope':'local','CreatedAt':'fixture','Options':None}\\n def docker(argv, body=None, **kwargs):\\n if argv[0]=='info':body=json.dumps('bnt-lap001')\\n elif argv[:2]==['volume','ls']:body=''\\n elif argv[:2]==['volume','create']:body=actuator.VOLUME\\n elif argv[:2]==['volume','inspect']:body=json.dumps([volume])\\n elif 'informed-decision-admin' in argv:\\n body=json.dumps({key:COUNT[key] for key in ('schema_version','lifecycle','outbox','submissions')})\\n if defect=='original-hash':snapshot.write_bytes(b'changed synthetic original')\\n elif '--entrypoint' in argv and 'python' in argv:\\n reads.append(argv);proof=copy.deepcopy(COUNT)\\n if len(reads)==2 and defect=='post-integrity':proof['integrity']='corrupt'\\n body=json.dumps(proof)\\n else:raise AssertionError('unexpected offline command')\\n return subprocess.CompletedProcess(argv,0,body,'')\\n with patch.object(actuator,'CUSTODY',folder),patch.object(actuator,'CASE',CASE),patch.object(actuator,'docker',side_effect=docker),patch('subprocess.run',return_value=subprocess.CompletedProcess([],0,'{\\\"all_checks_pass\\\":true,\\\"private_copy_verified\\\":true}','')):\\n if defect=='none':\\n result=actuator.offline_verify(journal)\\n self.assertEqual(result['offline_post_admin_readonly'],COUNT)\\n self.assertTrue(result['original_snapshot_hash_unchanged'])\\n else:\\n with self.assertRaises(ValueError):actuator.offline_verify(journal)\\n self.assertEqual(len(reads),2)\\n\\n def test_fresh_full_scan_db_expiry_and_new_findings_refuse(self):\\n receipt={'image':actuator.IMAGE,'artifact_source_revision':'9b4bb25d325698ae543e7e8a8a3c3592db1f58fc','scanner':'aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969','no_suppression':True,'full_allseverity_scan':True,'exit_code':0,'completed_at':'2026-10-11T01:59:00Z','db_metadata':{'UpdatedAt':'2026-10-10T19:00:00Z','NextUpdate':'2026-10-11T19:00:00Z','Version':2},'db_sha256':'a'*64,'full_report_sha256':'b'*64}\\n report={'Results':[{'Vulnerabilities':None}]}\\n self.assertEqual(fresh_scan(receipt,report,NOW)['allseverity_findings'],0)\\n mutations=[lambda r,p:r.update(no_suppression=False),lambda r,p:r.update(image='foreign'),lambda r,p:r.update(scanner='other'),lambda r,p:r.update(completed_at='2026-10-10T21:00:00Z'),lambda r,p:r['db_metadata'].update(NextUpdate='2026-10-11T01:59:00Z'),lambda r,p:p['Results'][0].update(Vulnerabilities=[{'Severity':'LOW'}]),lambda r,p:p.update(Results=[])]\\n for mutate in mutations:\\n r,p=copy.deepcopy(receipt),copy.deepcopy(report);mutate(r,p)\\n with self.assertRaises(ValueError):fresh_scan(r,p,NOW)\\n\\n\\nclass FixedNativeProgram(unittest.TestCase):\\n def fixture(self):\\n sealed = json.loads((CASE / 'sealed-live-deployment.json').read_text())\\n live = copy.deepcopy(sealed)\\n live['metadata'].update(generation=42)\\n live['status'] = {'readyReplicas': 1, 'updatedReplicas': 1, 'observedGeneration': 42}\\n dependencies = json.loads((CASE / 'sealed-native-dependencies.json').read_text())\\n cm = copy.deepcopy(dependencies['configmap'])\\n cm.pop('canonical_data_sha256')\\n service = json.loads((CASE / 'candidate-deployment.json').read_text())\\n source_service = json.loads((Path('/home/worsch/informed-decision/deploy/lifecycle/service.candidate.json')).read_text())\\n cm['data'] = next(x for x in source_service['items'] if x['kind'] == 'ConfigMap')['data']\\n pvc = copy.deepcopy(dependencies['pvc'])\\n pvc['status'] = {'phase': 'Bound'}\\n app = {'metadata': {'uid': '499868f2-eabc-4df4-ae31-b89ce58d750d', 'resourceVersion': '17'},\\n 'spec': {'source': {'repoURL': 'https://forgejo.coulomb.social/coulomb/informed-decision.git',\\n 'path': 'deploy/lifecycle', 'targetRevision': 'c96b0c6d881e855d1d158003c6de90ef390262e2'},\\n 'destination': {'namespace': 'informed-decision', 'server': 'https://kubernetes.default.svc'}}}\\n objects = {'deployment': live, 'configmap': cm, 'pvc': pvc, 'application': app}\\n payload = {'phase': 'release', 'source_revision': REVISION, 'approval': {}, 'sealed': sealed,\\n 'dependencies': dependencies, 'candidate': service,\\n 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()),\\n 'readonly': (CASE / 'sqlite_readonly.py').read_text(),'purpose_checks':(CASE/'check_installed.py').read_text(),\\n 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'receipt_safety': (Path(__file__).parent / 'receipt_safety.py').read_text()}\\n return objects, payload\\n\\n def execute_fake(self, objects, payload, ambiguous=False, schema_refused=False):\\n writes = []\\n calls = []\\n\\n def fake(argv, input=None, **kwargs):\\n calls.append(argv)\\n if argv[:2] == ['kubectl', 'get']:\\n kind = argv[2]\\n if kind in objects:\\n body = objects[kind]\\n elif kind == 'pods':\\n body = {'items': [{'metadata': {'name': 'fixture-pod', 'uid': 'c4cb1550-feda-4097-9de4-d891a657b662',\\n 'ownerReferences': [{'kind': 'ReplicaSet', 'name': 'fixture-rs', 'uid': 'rs-uid'}]},\\n 'spec': objects['deployment']['spec']['template']['spec'],\\n 'status': {'phase': 'Running', 'containerStatuses': [{'name':'informed-decision','ready': True,'imageID':objects['deployment']['spec']['template']['spec']['containers'][0]['image']}] }}]}\\n elif kind == 'replicaset':\\n body = {'metadata': {'uid': 'rs-uid', 'ownerReferences': [\\n {'kind': 'Deployment', 'uid': payload['sealed']['metadata']['uid']}]}}\\n elif kind == 'nodes':\\n body = {'items': [{'metadata': {'name': 'fixture-node'}, 'status': {\\n 'addresses': [{'address': '92.205.62.239'}], 'allocatable': {'cpu': '4'},\\n 'conditions': [{'type': key, 'status': 'True' if key == 'Ready' else 'False'}\\n for key in ('Ready', 'MemoryPressure', 'DiskPressure', 'PIDPressure')]}}]}\\n else:\\n raise AssertionError('unexpected fixed metadata route')\\n return subprocess.CompletedProcess(argv, 0, json.dumps(body), '')\\n if argv[:2] == ['kubectl', 'exec']:\\n if schema_refused:\\n return subprocess.CompletedProcess(argv, 1, '', 'withheld')\\n return subprocess.CompletedProcess(argv, 0, json.dumps({\\n 'all_checks_pass': True, 'live_readonly_schema_integrity_counts': COUNT}), '')\\n if argv[:2] == ['kubectl', 'apply']:\\n admitted = copy.deepcopy(payload['sealed'])\\n admitted['spec']['template']['spec']['containers'][0]['image'] = (actuator.IMAGE if payload['phase']=='release' else payload['sealed']['spec']['template']['spec']['containers'][0]['image'])\\n return subprocess.CompletedProcess(argv, 0, json.dumps(admitted), '')\\n if argv[:2] == ['kubectl', 'describe']:\\n return subprocess.CompletedProcess(argv, 0, 'Allocated resources:\\\\n cpu 3460m (86%)\\\\n', '')\\n if argv[:2] == ['kubectl', 'diff']:\\n return subprocess.CompletedProcess(argv, 1, '-image: base\\\\n+image: candidate\\\\n', '')\\n if argv[:2] == ['kubectl', 'patch']:\\n if '--dry-run=server' not in argv:\\n writes.append(json.loads(input))\\n if ambiguous:\\n return subprocess.CompletedProcess(argv, 1, '', 'withheld')\\n return subprocess.CompletedProcess(argv, 0, 'application.argoproj.io/informed-decision\\\\n', '')\\n raise AssertionError('unexpected command')\\n\\n source = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace(\\n '__GUARD__', repr((CASE / 'change_guard.py').read_text()))\\n output = io.StringIO()\\n with patch('subprocess.run', side_effect=fake), redirect_stdout(output):\\n try:\\n exec(compile(source, 'fixed-native-fixture', 'exec'), {'__name__': '__fixture__'})\\n except (ValueError, SystemExit):\\n pass\\n receipt = json.loads(output.getvalue()) if output.getvalue() else {'all_checks_pass': False}\\n return receipt, writes, calls\\n\\n def test_only_existing_application_operation_and_deployment_are_requested(self):\\n objects, payload = self.fixture()\\n receipt, writes, calls = self.execute_fake(objects, payload)\\n self.assertTrue(receipt['all_checks_pass'])\\n self.assertFalse(receipt['rollout_verified'])\\n self.assertEqual(len(writes), 1)\\n operation = writes[0][-1]['value']\\n self.assertFalse(operation['sync']['prune'])\\n self.assertEqual(operation['sync']['resources'], [\\n {'group': 'apps', 'kind': 'Deployment', 'name': 'informed-decision', 'namespace': 'informed-decision'}])\\n self.assertTrue(any('--dry-run=server' in argv and argv[:2] == ['kubectl', 'patch'] for argv in calls))\\n self.assertEqual(writes[0][0]['path'], '/metadata/uid')\\n\\n def test_source_overrides_automation_and_custody_drift_stop_before_write(self):\\n mutations = [lambda o: o['application']['metadata'].update(uid='recreated'),\\n lambda o: o['application']['spec'].update(syncPolicy={'automated': {}}),\\n lambda o: o['application']['spec']['source'].update(kustomize={'images': ['other']}),\\n lambda o: o['application']['spec'].update(sources=[{}]),\\n lambda o: o['configmap']['metadata'].update(uid='recreated'),\\n lambda o: o['pvc']['spec'].update(volumeName='replaced'),\\n lambda o: o['deployment']['status'].update(readyReplicas=0)]\\n for mutate in mutations:\\n with self.subTest(mutate=mutate):\\n objects, payload = self.fixture()\\n mutate(objects)\\n receipt, writes, calls = self.execute_fake(objects, payload)\\n self.assertFalse(receipt['all_checks_pass'])\\n self.assertEqual(writes, [])\\n\\n def test_preopen_schema_refusal_prevents_image_operation(self):\\n objects, payload = self.fixture()\\n receipt, writes, calls = self.execute_fake(objects, payload, schema_refused=True)\\n self.assertFalse(receipt['all_checks_pass'])\\n self.assertEqual(writes, [])\\n\\n\\n def test_rollback_unready_candidate_never_reads_pod_or_database(self):\\n objects, payload = self.fixture()\\n payload['phase'] = 'rollback'\\n objects['deployment']['status'].update(readyReplicas=0, updatedReplicas=0)\\n objects['deployment']['spec']['template']['spec']['containers'][0]['image'] = actuator.IMAGE\\n objects['application']['spec']['source']['targetRevision'] = REVISION\\n receipt, writes, calls = self.execute_fake(objects, payload, schema_refused=True)\\n self.assertTrue(receipt['all_checks_pass'])\\n self.assertFalse(receipt['rollout_verified'])\\n self.assertEqual(len(writes), 1)\\n self.assertFalse(any(argv[:2] == ['kubectl', 'exec'] or argv[:3] == ['kubectl', 'get', 'pods'] for argv in calls))\\n self.assertEqual(writes[0][-1]['value']['sync']['revision'], 'c96b0c6d881e855d1d158003c6de90ef390262e2')\\n\\n def test_rollback_still_refuses_custody_or_nonpair_image_drift(self):\\n for defect in ('cm', 'pvc', 'image'):\\n with self.subTest(defect=defect):\\n objects, payload = self.fixture();payload['phase'] = 'rollback'\\n objects['deployment']['status']['readyReplicas'] = 0\\n if defect == 'cm':objects['configmap']['metadata']['uid'] = 'recreated'\\n elif defect == 'pvc':objects['pvc']['spec']['volumeName'] = 'replaced'\\n else:objects['deployment']['spec']['template']['spec']['containers'][0]['image'] = 'foreign-image'\\n receipt, writes, calls = self.execute_fake(objects, payload)\\n self.assertFalse(receipt['all_checks_pass']);self.assertEqual(writes, [])\\n\\n def test_recovery_verification_is_separate_ready_old_image_schema_proof(self):\\n objects, payload = self.fixture();payload['phase'] = 'rollback-verify'\\n resource={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'}\\n target='c96b0c6d881e855d1d158003c6de90ef390262e2'\\n payload['operation_intent']={'state':'COMPLETE','started_at':'2026-10-11T01:00:00Z','result':{'operation_requested':True,'source_revision':target,'prune':False,'resources':[resource]}}\\n objects['application']['status']={'operationState':{'phase':'Succeeded','startedAt':'2026-10-11T01:00:01Z','finishedAt':'2026-10-11T01:01:00Z','operation':{'initiatedBy':{'username':'custodian-codex'},'sync':{'revision':target,'resources':[resource]}},'syncResult':{'revision':target,'resources':[{**resource,'status':'Synced'}]}}}\\n receipt, writes, calls = self.execute_fake(objects, payload)\\n self.assertTrue(receipt['all_checks_pass']);self.assertTrue(receipt['recovery_verification'])\\n self.assertEqual(writes, [])\\n self.assertTrue(any(argv[:2]==['kubectl','exec'] for argv in calls))\\n objects['deployment']['status']['readyReplicas'] = 0\\n receipt, writes, calls = self.execute_fake(objects,payload)\\n self.assertFalse(receipt['all_checks_pass'])\\n\\n def test_ambiguous_patch_is_never_retried(self):\\n objects, payload = self.fixture()\\n receipt, writes, calls = self.execute_fake(objects, payload, ambiguous=True)\\n self.assertFalse(receipt['all_checks_pass'])\\n self.assertEqual(len(writes), 1)\\n\\n def test_false_omitempty_needs_bound_current_request_and_exact_completion(self):\\n objects,payload=self.fixture();payload['phase']='rollback-verify'\\n resource={'group':'apps','kind':'Deployment','name':'informed-decision','namespace':'informed-decision'}\\n target='c96b0c6d881e855d1d158003c6de90ef390262e2'\\n payload['operation_intent']={'state':'COMPLETE','started_at':'2026-10-11T01:00:00Z','result':{'operation_requested':True,'source_revision':target,'prune':False,'resources':[resource]}}\\n objects['application']['status']={'operationState':{'phase':'Succeeded','startedAt':'2026-10-11T01:00:01Z','finishedAt':'2026-10-11T01:01:00Z','operation':{'initiatedBy':{'username':'custodian-codex'},'sync':{'revision':target,'resources':[resource]}},'syncResult':{'revision':target,'resources':[{**resource,'status':'Synced'}]}}}\\n self.assertTrue(self.execute_fake(objects,payload)[0]['all_checks_pass'])\\n mutations=[lambda o,p:o['application']['status']['operationState']['operation']['sync'].update(prune=True),\\n lambda o,p:o['application']['status']['operationState']['operation']['sync'].update(prune=None),\\n lambda o,p:o['application']['status']['operationState'].update(startedAt='2026-10-11T00:59:00Z'),\\n lambda o,p:o['application']['status']['operationState']['operation'].update(initiatedBy={'username':'other'}),\\n lambda o,p:o['application']['status']['operationState']['syncResult'].update(revision='foreign'),\\n lambda o,p:o['application']['status']['operationState']['syncResult']['resources'][0].update(status='Pruned'),\\n lambda o,p:p['operation_intent']['result'].update(prune=True),\\n lambda o,p:p.update(operation_intent=None)]\\n for mutate in mutations:\\n o,p=copy.deepcopy(objects),copy.deepcopy(payload);mutate(o,p)\\n receipt,writes,calls=self.execute_fake(o,p)\\n self.assertFalse(receipt['all_checks_pass']);self.assertEqual(writes,[])\\n\\n def test_optimized_executor_refuses_before_any_input(self):\\n result=subprocess.run([sys.executable,'-O',str(Path(__file__).parent/'actuator.py'),'--phase','release','--root-authority-receipt','/missing','--root-native-outcome','/missing','--source-revision',REVISION],capture_output=True,text=True)\\n self.assertNotEqual(result.returncode,0)\\n self.assertIn('optimized interpreter refused',result.stderr)\\n\\n\\nif __name__ == '__main__':\\n unittest.main()\\n\"\n }\n },\n \"native_execution\": false,\n \"case_evidence\": {\n \"sqlite_readonly.py\": {\n \"sha256\": \"9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509\",\n \"content\": \"\\\"\\\"\\\"Pre-open schema/integrity/count proof; stdlib only, never constructs Store.\\\"\\\"\\\"\\nimport argparse\\nfrom contextlib import closing\\nimport hashlib\\nimport os\\nimport re\\nimport stat\\nimport json\\nfrom pathlib import Path\\nimport sqlite3\\n\\n\\ndef inspect_readonly(path, *, _closed_snapshot=False):\\n path = Path(path)\\n if not path.is_file() or path.is_symlink():\\n raise ValueError('existing nonsymlink private database required')\\n uri = path.resolve().as_uri() + '?mode=ro' + ('&immutable=1' if _closed_snapshot else '')\\n # sqlite3.Connection context management ends transactions; it does not close.\\n with closing(sqlite3.connect(uri, uri=True, timeout=5)) as db:\\n db.execute('PRAGMA query_only=ON')\\n db.execute('BEGIN')\\n version = db.execute('PRAGMA user_version').fetchone()[0]\\n if version != 5:\\n raise ValueError('pre-open database is not schema5; refuse migration')\\n if db.execute('PRAGMA integrity_check').fetchall() != [('ok',)]:\\n raise ValueError('read-only integrity check refused')\\n lifecycle = {name: db.execute('SELECT COUNT(*) FROM ' + table).fetchone()[0]\\n for name, table in (('records', 'lifecycle_records'),\\n ('events', 'lifecycle_events'),\\n ('pending', 'lifecycle_pending'))}\\n result = {'schema_version': version, 'lifecycle': lifecycle,\\n 'outbox': dict(db.execute('SELECT state,COUNT(*) FROM outbox GROUP BY state')),\\n 'submissions': dict(db.execute('SELECT state,COUNT(*) FROM submissions GROUP BY state')),\\n 'integrity': 'ok', 'read_only_pre_open': True, 'store_constructed': False}\\n db.rollback()\\n return result\\n\\n\\ndef inspect_closed_snapshot(path, expected_sha256):\\n \\\"\\\"\\\"Only a privately retained, frozen main-file snapshot with no WAL frames.\\n\\n Caller must establish exclusive snapshot creation/admin exit/no writer, and\\n supply the recorded frozen main digest. Never use for the live database.\\n Immutable SQLite reads ignore stale SHM and never create/change sidecars.\\n Existing sidecars remain custody evidence; nothing is deleted/checkpointed.\\n \\\"\\\"\\\"\\n path = Path(path)\\n if re.fullmatch(r'[0-9a-f]{64}', expected_sha256) is None:\\n raise ValueError('exact frozen snapshot digest required')\\n def metadata(target):\\n info = target.lstat()\\n if not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode) != 0o600 or info.st_nlink != 1:\\n raise ValueError('private single-link snapshot custody required')\\n return info\\n main = metadata(path)\\n if not 0 < main.st_size <= 512 * 1024**2:\\n raise ValueError('bounded frozen snapshot required')\\n def sidecars():\\n identities = {}\\n for suffix in ('-wal', '-shm'):\\n target = Path(str(path) + suffix)\\n if os.path.lexists(target):\\n info = metadata(target)\\n if info.st_uid != main.st_uid or info.st_gid != main.st_gid:\\n raise ValueError('snapshot sidecar custody changed')\\n if suffix == '-wal' and info.st_size != 0:\\n raise ValueError('snapshot has WAL frames; immutable read refused')\\n identities[suffix] = (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns)\\n return identities\\n before_sidecars = sidecars()\\n def digest():\\n value = hashlib.sha256()\\n with path.open('rb') as stream:\\n for block in iter(lambda: stream.read(1024**2), b''):\\n value.update(block)\\n return value.hexdigest()\\n if digest() != expected_sha256:\\n raise ValueError('frozen main-file digest changed before inspection')\\n proof = inspect_readonly(path, _closed_snapshot=True)\\n after = metadata(path)\\n if (after.st_dev, after.st_ino, after.st_uid, after.st_gid, after.st_size) != (main.st_dev, main.st_ino, main.st_uid, main.st_gid, main.st_size):\\n raise ValueError('frozen main-file identity changed')\\n if digest() != expected_sha256 or sidecars() != before_sidecars:\\n raise ValueError('frozen snapshot changed during inspection')\\n return proof\\n\\n\\nif __name__ == '__main__':\\n parser = argparse.ArgumentParser(description=__doc__)\\n parser.add_argument('--db', type=Path, required=True)\\n args = parser.parse_args()\\n try:\\n print(json.dumps(inspect_readonly(args.db), sort_keys=True))\\n except Exception:\\n raise SystemExit('read-only schema5/integrity/count gate refused; no Store/admin open') from None\\n\"\n },\n \"change_guard.py\": {\n \"sha256\": \"b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201\",\n \"content\": \"\\\"\\\"\\\"Read-only exact Deployment and admission boundaries; no mutation authority.\\\"\\\"\\\"\\nimport copy\\nimport hashlib\\nimport json\\n\\nBASE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a'\\nCANDIDATE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23'\\n\\n\\ndef dependency_projection(configmap, pvc):\\n if (configmap.get('kind') != 'ConfigMap' or configmap.get('immutable') is not True\\n or configmap['metadata']['namespace'] != 'informed-decision'\\n or configmap['metadata']['name'] != 'informed-decision-runtime-6c221878d051'\\n or set(configmap.get('data', {})) != {'runtime.json'}\\n or configmap.get('binaryData')):\\n raise ValueError('unexpected immutable runtime config identity')\\n if (pvc.get('kind') != 'PersistentVolumeClaim'\\n or pvc['metadata']['namespace'] != 'informed-decision'\\n or pvc['metadata']['name'] != 'informed-decision-data'\\n or pvc.get('status', {}).get('phase') != 'Bound'\\n or not pvc.get('spec', {}).get('volumeName')):\\n raise ValueError('unexpected bound PVC custody')\\n config = projection(configmap)\\n config.pop('data')\\n config['canonical_data_sha256'] = hashlib.sha256(json.dumps(\\n configmap['data'], sort_keys=True, separators=(',', ':')).encode()).hexdigest()\\n return {'configmap': config, 'pvc': projection(pvc), 'pvc_phase': 'Bound'}\\n\\n\\ndef check_dependencies(configmap, pvc, sealed):\\n if dependency_projection(configmap, pvc) != sealed:\\n raise ValueError('runtime config or PVC identity/custody changed')\\n\\n\\ndef projection(value):\\n value = copy.deepcopy(value)\\n value.pop('status', None)\\n for key in ('resourceVersion', 'generation', 'creationTimestamp', 'managedFields'):\\n value['metadata'].pop(key, None)\\n for key in ('kubectl.kubernetes.io/last-applied-configuration', 'deployment.kubernetes.io/revision'):\\n value['metadata'].get('annotations', {}).pop(key, None)\\n return value\\n\\n\\ndef check_current(current, sealed):\\n if projection(current) != sealed:\\n raise ValueError('sealed live UID or configuration changed')\\n\\n\\ndef check_admitted(admitted, sealed):\\n expected = copy.deepcopy(sealed)\\n containers = expected['spec']['template']['spec']['containers']\\n if len(containers) != 1 or containers[0]['name'] != 'informed-decision' or containers[0]['image'] != BASE:\\n raise ValueError('unexpected baseline container identity')\\n containers[0]['image'] = CANDIDATE\\n if projection(admitted) != expected:\\n raise ValueError('admission changed configuration beyond the sole image pin')\\n\\n\\ndef transport_result(exit_code, output):\\n import json\\n try:\\n value = json.loads(output)\\n except (ValueError, TypeError):\\n return {'all_checks_pass': False, 'reason': 'invalid transport result'}\\n if exit_code != 0 or not isinstance(value, dict) or value.get('all_checks_pass') is not True:\\n return {'all_checks_pass': False, 'reason': 'read-only preflight refused'}\\n return value\\n\"\n },\n \"check_installed.py\": {\n \"sha256\": \"a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad\",\n \"content\": \"import copy,json,time\\nfrom datetime import datetime,timezone\\nfrom informed_decision.policy import PolicyClient,CONTRACT,submitted_digest\\npin='sha256:'+'b'*64\\nreq={'id':'image-purpose-fixture','tenant':'tenant:platform','subject':{'id':'synthetic','type':'service','tenant':'tenant:platform'},'resource':{'id':'memo:fixture','type':'memo','system':'informed-decision','tenant':'tenant:platform'},'action':'register','context':{}}\\nclass Transport:\\n def request(self,*args,**kwargs):\\n now=time.time(); stamp=lambda t:datetime.fromtimestamp(t,timezone.utc).isoformat()\\n b={**copy.deepcopy({k:req[k] for k in ('tenant','subject','action','resource','context')}),'request_digest':'sha256:'+'e'*64,'submitted_request_digest':submitted_digest(req)}\\n if self.purpose!='ABSENT':b['purpose']=self.purpose\\n d={'id':'synthetic-decision','contract_version':CONTRACT,'request_id':req['id'],'effect':'allow','matched_policy_version':'v1','subject':req['subject'],'resource':req['resource'],'binding':b,'lifetime':{'kind':'ttl','not_before':stamp(now-1),'expires_at':stamp(now+60)},'provenance':{'policy_package':'fixture','policy_version':'v1','policy_package_digest':pin,'registry_snapshot_digest':'sha256:'+'c'*64,'evaluator':'flex-auth/fixture','decision_time':stamp(now)}}\\n return 200,d\\nchecks=[]\\nfor purpose in ('ABSENT','','readback','enforcement','unknown',None,False,0,[],{}):\\n t=Transport();t.purpose=purpose\\n c=PolicyClient('https://fixture.invalid',lambda:'synthetic-only',package='fixture',version='v1',package_digest=pin,transport=t)\\n o=c.check(req)\\n if purpose in ('ABSENT',''):assert o.outcome=='allow';o.require_current(time.time())\\n else:assert o.outcome=='invalid_decision' and o.decision_json is None\\n checks.append(str(purpose))\\nprint(json.dumps({'installed_image_purpose_variants':len(checks),'binding_forms_allowed':2,'advisory_or_malformed_refused':8,'synthetic_only':True,'native_provider':False}))\\n\"\n },\n \"post-ready-native-outcomes-command.py\": {\n \"sha256\": \"ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa\",\n \"content\": \"\\\"\\\"\\\"Run only inside existing admitted StateHub Pod AFTER verified candidate Ready.\\n\\nThe fixed existing dispatcher loads its internal credential; root never reads\\nit. HTTP success proves canonical lifecycle.outcomes PDP gate returned, not an\\nadvisory verdict emission or a substitute for separate evidence obligations.\\nRoot records this bounded metadata receipt with verified PodUID/image/source.\\nThis preparation only writes source; no request is made here.\\n\\\"\\\"\\\"\\nimport datetime,json,urllib.request,urllib.error\\nfrom api.services.memo_dispatch import ORIGIN,NoRedirect,token_file\\nid='infd-20261011-zlib-remediation-r1'\\nreq=urllib.request.Request(ORIGIN+'/v1/decisions/'+id+'/outcomes?version=1',headers={'Authorization':'Bearer '+token_file('/var/run/secrets/state-hub/informed-decision/lifecycle-token')})\\nopener=urllib.request.build_opener(urllib.request.ProxyHandler({}),NoRedirect())\\ntry:\\n with opener.open(req,timeout=15) as response:\\n raw=response.read(1048577)\\n if len(raw)>1048576:raise ValueError('oversize')\\n r=json.loads(raw)\\n print(json.dumps({'memo_id':id,'source_memo_version':1,'observed_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'native_authenticated_canonical_lifecycle_outcomes':True,'native_provider_path_returned':True,'credential_material_output':False,'review':r.get('review'),'disposition_count':len(r.get('dispositions',[])),'dispositions':[{k:d.get(k) for k in ('id','approval_id','submission_state','disposition','approved_at','decided_at')} for d in r.get('dispositions',[])]}))\\nexcept Exception as e:print(json.dumps({'memo_id':id,'status':'unverified','error_class':type(e).__name__}));raise SystemExit(1)\\n\"\n },\n \"installed-zlib-proof.json\": {\n \"sha256\": \"6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68\",\n \"content\": \"{\\n \\\"image\\\": \\\"forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23\\\",\\n \\\"zlib_package\\\": \\\"1.3.2-r1\\\",\\n \\\"files\\\": {\\n \\\"lib/apk/db/installed\\\": [\\n \\\"file\\\",\\n 420,\\n 0,\\n 0,\\n \\\"f590762196710e25c0e9f72da0642cad8a5dcba0aa9e58a0cbaa65684d15c001\\\"\\n ],\\n \\\"usr/lib/libz.so.1.3.2\\\": [\\n \\\"file\\\",\\n 493,\\n 0,\\n 0,\\n \\\"ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2\\\"\\n ]\\n }\\n}\\n\"\n }\n }\n}\n" - } - ] -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47 deleted file mode 100644 index 269ae42..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47 +++ /dev/null @@ -1,478 +0,0 @@ -[ - { - "repo": "informed-decision", - "path": "deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/admission.candidate.json", - "original_sha256": "92382a56b349c3b895e5806e975082527bb99d1a001140e69d857e5b0391fa55", - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/kustomization.yaml", - "original_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47", - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/registries.candidate.json", - "original_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e", - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/deploy/lifecycle/service.candidate.json", - "original_sha256": "38b78b9cbee5ade7750a58d5156ca4c5773352e947acb304ee714b59e749ceb4", - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "original_sha256": null, - "staged_sha256": "09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "original_sha256": null, - "staged_sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "original_sha256": null, - "staged_sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "original_sha256": null, - "staged_sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "original_sha256": null, - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "original_sha256": null, - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "original_sha256": null, - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "original_sha256": null, - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/intake.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "original_sha256": null, - "staged_sha256": "8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "original_sha256": null, - "staged_sha256": "196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "original_sha256": null, - "staged_sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "original_sha256": null, - "staged_sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "original_sha256": null, - "staged_sha256": "4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9" - }, - { - "repo": "informed-decision", - "path": "workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "staged_path": "/tmp/infd-zlib-release-stage/informed-decision/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "original_sha256": "af785afbf188184b72563c10813663d44117df7cb7394ecb1b9a25cbdfea67a4", - "staged_sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8261e4f08dc73410a1c507d562e0b3fae8629ee1f8c39ed16b3c926744a816f6 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8261e4f08dc73410a1c507d562e0b3fae8629ee1f8c39ed16b3c926744a816f6 deleted file mode 100644 index 61c3558..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/8261e4f08dc73410a1c507d562e0b3fae8629ee1f8c39ed16b3c926744a816f6 +++ /dev/null @@ -1,37 +0,0 @@ ---- -id: RPF-IN-0009 -kind: intake -type: intake -title: "Platform technical review of distinct INFD zlib native release" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009-T05 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -New bounded Platform technical review of immutable published zlib artifact and separately proposed image-only native release/drill/cutover/recovery. Distinct from completed RPF-IN-0008 source candidate review and purpose R2 authorization. No native action, private database access, credentials, owner source writes, carried-forward approval or T05 closure. - -## Completed initial changes-requested review - -Initial exact67 manifest1e4 was observed at review start, then shared stage changed to68 manifest81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47 before snapshot preservation. Review stopped rather than treating that as unchanged frozen source. Observed68 bytes and manifest archived;29 published proof6ba2 hashes verified. Remote keyword dispatch and recorded backup snapshot identity are not plumbed; freshscan helper permits unbound incomplete zero reports. Fresh scan age and DBNextUpdate controls exist but artifact and full inventory coverage must be checked. Completed owner workrecord precedes conditional entry. Refinement remains this same review trial; no technical agreement or native approval issued. - -## Completed refined-source changes-requested review - -Same owner preparation trial reviewed immutable143 manifest `f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29`. Original wiring and incomplete scan proof defects fixed; all143 source hashes/plan file bindings verified,25 synthetic methods pass. Independent remote payload/partial scan repro confirms fixes; separate fixture shows unrelated privileged ephemeral container passes current Pod gate. Post-ready freeform output and recovery authority availability require explicit refinement. Exact previous74 owner-review files plus manifest/map archived before supersession. Source-only review is completed before conditional entry; no technical ACK, approval or native evidence inferred. - -Root selected a NEW human-proposal recovery alternative for further source preparation: independently root-confirmed same accepted receipt retained before release may support manual supervised763/c96 Deployment-only latestDB recovery for at most30minutes after release and before deadline, only exact approved custody/journal/source pins and demonstrable transport-unavailable/502/503/504 plus failed candidate placement. No401/403/revoked/invalidsignature/conflict fallback, no backup/release fallback; inability to query later revocation while unavailable must be disclosed. This is proposed scope awaiting exact code review and NEW founder disposition, not existing authority. - -## Completed final exact-source technical review - -Final immutable481 manifest `3f1ce7ed678a457f73ab6486c7e81ef3360daf4f7ec1196f3031c3ffaace57ec` reviewed in this same IN9 preparation trial. All481 hashes and plan executor/case pins match;36 synthetic tests and12 additional public-artifact/synthetic checks pass. Ephemeral Pod custody, private metadata filtering, original payload wiring and full fresh scan coverage now refuse challenged defects. Bounded supervised outage recovery is expressly NEW proposed founder consent, not old approval: rollback only failed exactcandidate/source, retained authenticated pre-release exact acceptance,30minutes/globaldeadline/UID/config/PVC/journal/source/image pins, no401/403/revocation/conflict/healthy candidate/backup or release fallback. Later revocation unobservable during outage is disclosed; recovered verification proves separate rollback placement, not provider acceptance/taskclosure. Exact prior123 owner files/manifest/map preserved before entry; original67 historical absence remains honest. Completed workrecord precedes formal source agreement. No native action or new approval inferred. - -## Final481 actual API compatibility finding; no ACK issued - -Before returning any agreement, Product identified actual provider shape incompatibility. Owner source lifecycle_review.outcomes returns only review/dispositions, optional reviewNone/not_submitted and optional submissionstate; lifecycle_service wraps it in current PDP authorization. The staged sanitizer instead requires invented topmemo_id and restrictive inventedstates. Independent read-only owner-source inspection confirms it. Prior unissued draft retained explicitly as superseded, not formal acceptance. Additional source correction must use fixed-request/HTTP200 correlation and bounded shape/count-only proof without detailed freeform dispositions or founder-authority inference. Completed sameIN9 changes-requested workrecord precedes entry. diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509 deleted file mode 100644 index 64274bd..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509 +++ /dev/null @@ -1,95 +0,0 @@ -"""Pre-open schema/integrity/count proof; stdlib only, never constructs Store.""" -import argparse -from contextlib import closing -import hashlib -import os -import re -import stat -import json -from pathlib import Path -import sqlite3 - - -def inspect_readonly(path, *, _closed_snapshot=False): - path = Path(path) - if not path.is_file() or path.is_symlink(): - raise ValueError('existing nonsymlink private database required') - uri = path.resolve().as_uri() + '?mode=ro' + ('&immutable=1' if _closed_snapshot else '') - # sqlite3.Connection context management ends transactions; it does not close. - with closing(sqlite3.connect(uri, uri=True, timeout=5)) as db: - db.execute('PRAGMA query_only=ON') - db.execute('BEGIN') - version = db.execute('PRAGMA user_version').fetchone()[0] - if version != 5: - raise ValueError('pre-open database is not schema5; refuse migration') - if db.execute('PRAGMA integrity_check').fetchall() != [('ok',)]: - raise ValueError('read-only integrity check refused') - lifecycle = {name: db.execute('SELECT COUNT(*) FROM ' + table).fetchone()[0] - for name, table in (('records', 'lifecycle_records'), - ('events', 'lifecycle_events'), - ('pending', 'lifecycle_pending'))} - result = {'schema_version': version, 'lifecycle': lifecycle, - 'outbox': dict(db.execute('SELECT state,COUNT(*) FROM outbox GROUP BY state')), - 'submissions': dict(db.execute('SELECT state,COUNT(*) FROM submissions GROUP BY state')), - 'integrity': 'ok', 'read_only_pre_open': True, 'store_constructed': False} - db.rollback() - return result - - -def inspect_closed_snapshot(path, expected_sha256): - """Only a privately retained, frozen main-file snapshot with no WAL frames. - - Caller must establish exclusive snapshot creation/admin exit/no writer, and - supply the recorded frozen main digest. Never use for the live database. - Immutable SQLite reads ignore stale SHM and never create/change sidecars. - Existing sidecars remain custody evidence; nothing is deleted/checkpointed. - """ - path = Path(path) - if re.fullmatch(r'[0-9a-f]{64}', expected_sha256) is None: - raise ValueError('exact frozen snapshot digest required') - def metadata(target): - info = target.lstat() - if not stat.S_ISREG(info.st_mode) or stat.S_IMODE(info.st_mode) != 0o600 or info.st_nlink != 1: - raise ValueError('private single-link snapshot custody required') - return info - main = metadata(path) - if not 0 < main.st_size <= 512 * 1024**2: - raise ValueError('bounded frozen snapshot required') - def sidecars(): - identities = {} - for suffix in ('-wal', '-shm'): - target = Path(str(path) + suffix) - if os.path.lexists(target): - info = metadata(target) - if info.st_uid != main.st_uid or info.st_gid != main.st_gid: - raise ValueError('snapshot sidecar custody changed') - if suffix == '-wal' and info.st_size != 0: - raise ValueError('snapshot has WAL frames; immutable read refused') - identities[suffix] = (info.st_dev, info.st_ino, info.st_size, info.st_mtime_ns) - return identities - before_sidecars = sidecars() - def digest(): - value = hashlib.sha256() - with path.open('rb') as stream: - for block in iter(lambda: stream.read(1024**2), b''): - value.update(block) - return value.hexdigest() - if digest() != expected_sha256: - raise ValueError('frozen main-file digest changed before inspection') - proof = inspect_readonly(path, _closed_snapshot=True) - after = metadata(path) - if (after.st_dev, after.st_ino, after.st_uid, after.st_gid, after.st_size) != (main.st_dev, main.st_ino, main.st_uid, main.st_gid, main.st_size): - raise ValueError('frozen main-file identity changed') - if digest() != expected_sha256 or sidecars() != before_sidecars: - raise ValueError('frozen snapshot changed during inspection') - return proof - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--db', type=Path, required=True) - args = parser.parse_args() - try: - print(json.dumps(inspect_readonly(args.db), sort_keys=True)) - except Exception: - raise SystemExit('read-only schema5/integrity/count gate refused; no Store/admin open') from None diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd deleted file mode 100644 index 3af5f7a..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd +++ /dev/null @@ -1,13 +0,0 @@ -{ - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "artifact_source_revision": "9b4bb25d325698ae543e7e8a8a3c3592db1f58fc", - "scanner": "aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969", - "full_allseverity_scan": true, - "no_suppression": true, - "exit_code": null, - "completed_at": null, - "db_metadata": null, - "db_sha256": null, - "full_report_sha256": null, - "note": "Template is not actual evidence or approval" -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788 deleted file mode 100644 index 66736d3..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788 +++ /dev/null @@ -1,188 +0,0 @@ -"""Supervised receipt bookkeeping, not an authenticated approval provider.""" -import datetime -import fcntl -import hashlib -import json -import os -import re -from pathlib import Path -import stat -import uuid - -FOUNDER = {'kind': 'person', 'sub': 'uid=platform-root,ou=people,dc=netkingdom,dc=local'} -MEMO_ID = 'infd-20261011-zlib-remediation-r1' -PROPOSAL = 'INFD-WP-0009-T05/zlib85091-r1' -PHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline', - 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify') - - -def require(value, reason): - if not value: - raise ValueError(reason) - - -def sha(path): - digest = hashlib.sha256() - with Path(path).open('rb') as stream: - for block in iter(lambda: stream.read(1024**2), b''): - digest.update(block) - return digest.hexdigest() - - -def private(path, directory=False): - info = Path(path).lstat() - require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode)) - and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600) - and (directory or info.st_nlink == 1), 'private owned custody required') - - -def authority(receipt, outcome, memo_sha, source_revision, phase, now=None): - """Root must freshly verify native outcome at fixed ORIGIN outside this helper. - - Receipt files/flags are not signed grants. This function checks the exact - supervised receipt relation; existing harness/founder authority remains the - actual permission boundary. It never authenticates a caller as human. - """ - now = now or datetime.datetime.now(datetime.timezone.utc) - require(phase in PHASES, 'fixed phase required') - require(receipt.get('proposal_id') == PROPOSAL and receipt.get('memo_id') == MEMO_ID - and receipt.get('memo_version') == 1 and receipt.get('memo_sha256') == memo_sha - and receipt.get('source_revision') == source_revision - and receipt.get('activation') == 'APPROVED' - and receipt.get('authority_mode') == 'ADMINISTER' - and receipt.get('target') == 'realm:kubernetes/railiance01/informed-decision' - and receipt.get('verification_method') == 'root-fixed-origin-native-readback' - and receipt.get('runtime_authenticated_grant') is False - and receipt.get('outcome_query') == '/lifecycle/memos/' + MEMO_ID + '/outcomes?version=1', - 'exact externally verified supervised receipt required') - verified = datetime.datetime.fromisoformat(receipt['verified_at'].replace('Z', '+00:00')) - require(verified.tzinfo is not None and 0 <= (now - verified).total_seconds() <= 120, - 'fresh root native readback required before every phase') - deadline = datetime.datetime.fromisoformat('2026-10-12T23:59:59+00:00') - require(now < deadline, 'approved execution window expired') - if phase == 'release': - require((deadline-now).total_seconds() >= 1800, 'reserve thirty minutes for verification and recovery') - require(outcome.get('memo_id') == MEMO_ID and outcome.get('review', {}).get('stale') is False - and outcome['review'].get('intake_state') == 'ready', 'native review is not ready/current') - review_id = outcome['review']['memo_id'] - require(review_id == receipt.get('review_memo_id'), 'native review identity changed') - accepted = [x for x in outcome.get('dispositions', []) - if x.get('submission_state') == 'confirmed' - and x.get('disposition', {}).get('verb') == 'accept' - and x['disposition'].get('memo_version') == 1] - require(len(accepted) == 1, 'one confirmed founder acceptance required') - accepted = accepted[0]['disposition'] - require(accepted.get('actor') == FOUNDER and accepted.get('memo_id') == review_id - and accepted.get('approval_id') == receipt.get('approval_id') - and accepted.get('id') == receipt.get('disposition_id') - and accepted.get('at') == receipt.get('accepted_at'), 'exact founder disposition mismatch') - require(re.fullmatch(r'[a-f0-9-]{36}', accepted['approval_id']) is not None - and re.fullmatch(r'disp-[a-f0-9-]{36}', accepted['id']) is not None - and re.fullmatch(r'review-[a-f0-9-]{36}', review_id) is not None, - 'native disposition identifiers are malformed') - accepted_at = datetime.datetime.fromisoformat(accepted['at'].replace('Z', '+00:00')) - require(accepted_at.tzinfo is not None and accepted_at <= verified, 'acceptance time not bounded') - return {key: receipt[key] for key in ('approval_id', 'disposition_id', 'accepted_at', 'verified_at')} - - -def fresh_scan(receipt, report, contract, now=None): - """Root supervised scan bookkeeping; actual scanner proof stays retained.""" - now=now or datetime.datetime.now(datetime.timezone.utc) - require(receipt.get('image')=='forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23' - and receipt.get('artifact_source_revision')=='9b4bb25d325698ae543e7e8a8a3c3592db1f58fc' - and receipt.get('scanner')=='aquasec/trivy@sha256:62b1e65e8869bc4b4c6aa4fa2b21595256c7c2f6018a9d9ad61caf87187c1969' - and receipt.get('no_suppression') is True and receipt.get('full_allseverity_scan') is True - and receipt.get('exit_code')==0,'exact full immutable artifact scan required') - scanned=datetime.datetime.fromisoformat(receipt['completed_at'].replace('Z','+00:00')) - metadata=receipt['db_metadata'];updated=datetime.datetime.fromisoformat(metadata['UpdatedAt'].replace('Z','+00:00'));next_update=datetime.datetime.fromisoformat(metadata['NextUpdate'].replace('Z','+00:00')) - require(all(t.tzinfo is not None for t in (scanned,updated,next_update)) - and updated<=scanned<=now1048576:raise ValueError('oversize') - r=json.loads(raw) - print(json.dumps({'memo_id':id,'source_memo_version':1,'observed_at':datetime.datetime.now(datetime.timezone.utc).isoformat(),'native_authenticated_canonical_lifecycle_outcomes':True,'native_provider_path_returned':True,'credential_material_output':False,'review':r.get('review'),'disposition_count':len(r.get('dispositions',[])),'dispositions':[{k:d.get(k) for k in ('id','approval_id','submission_state','disposition','approved_at','decided_at')} for d in r.get('dispositions',[])]})) -except Exception as e:print(json.dumps({'memo_id':id,'status':'unverified','error_class':type(e).__name__}));raise SystemExit(1) diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d162fda35ac46502f14cea91d7d614ce5041e15eae0d4705ede4bf9efc18b36a b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d162fda35ac46502f14cea91d7d614ce5041e15eae0d4705ede4bf9efc18b36a deleted file mode 100644 index bad46c5..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d162fda35ac46502f14cea91d7d614ce5041e15eae0d4705ede4bf9efc18b36a +++ /dev/null @@ -1,29 +0,0 @@ ---- -id: RPF-IN-0009 -kind: intake -type: intake -title: "Platform technical review of distinct INFD zlib native release" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009-T05 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -New bounded Platform technical review of immutable published zlib artifact and separately proposed image-only native release/drill/cutover/recovery. Distinct from completed RPF-IN-0008 source candidate review and purpose R2 authorization. No native action, private database access, credentials, owner source writes, carried-forward approval or T05 closure. - -## Completed initial changes-requested review - -Initial exact67 manifest1e4 was observed at review start, then shared stage changed to68 manifest81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47 before snapshot preservation. Review stopped rather than treating that as unchanged frozen source. Observed68 bytes and manifest archived;29 published proof6ba2 hashes verified. Remote keyword dispatch and recorded backup snapshot identity are not plumbed; freshscan helper permits unbound incomplete zero reports. Fresh scan age and DBNextUpdate controls exist but artifact and full inventory coverage must be checked. Completed owner workrecord precedes conditional entry. Refinement remains this same review trial; no technical agreement or native approval issued. - -## Completed refined-source changes-requested review - -Same owner preparation trial reviewed immutable143 manifest `f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29`. Original wiring and incomplete scan proof defects fixed; all143 source hashes/plan file bindings verified,25 synthetic methods pass. Independent remote payload/partial scan repro confirms fixes; separate fixture shows unrelated privileged ephemeral container passes current Pod gate. Post-ready freeform output and recovery authority availability require explicit refinement. Exact previous74 owner-review files plus manifest/map archived before supersession. Source-only review is completed before conditional entry; no technical ACK, approval or native evidence inferred. - -Root selected a NEW human-proposal recovery alternative for further source preparation: independently root-confirmed same accepted receipt retained before release may support manual supervised763/c96 Deployment-only latestDB recovery for at most30minutes after release and before deadline, only exact approved custody/journal/source pins and demonstrable transport-unavailable/502/503/504 plus failed candidate placement. No401/403/revoked/invalidsignature/conflict fallback, no backup/release fallback; inability to query later revocation while unavailable must be disclosed. This is proposed scope awaiting exact code review and NEW founder disposition, not existing authority. diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d23f10a3eca61ee3fb9c20213621122a394679a3f97c1abd8e91edff089803aa b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d23f10a3eca61ee3fb9c20213621122a394679a3f97c1abd8e91edff089803aa deleted file mode 100644 index 80eefe4..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d23f10a3eca61ee3fb9c20213621122a394679a3f97c1abd8e91edff089803aa +++ /dev/null @@ -1,15 +0,0 @@ -{ - "release_manifest_sha256": "3f1ce7ed678a457f73ab6486c7e81ef3360daf4f7ec1196f3031c3ffaace57ec", - "all481_hashes_and_plan_bindings_match": true, - "synthetic_test_methods_independently_passed": 36, - "additional_checks_passed": 12, - "additional_checks": [ - "actual ephemeral custody refuses before native effect", - "actual public full report matches exact scan contract", - "incomplete/wrongconfig/truncated/LOW reports refuse", - "boolean HTTPstatus/foreignattempt/healthycandidate/403/31minute recovery refusals", - "actual provider command mocked response private fields withheld" - ], - "public_scan_report_only": true, - "native_or_private_database_access": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124 deleted file mode 100644 index f194470..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124 +++ /dev/null @@ -1 +0,0 @@ -{"uid": 10001, "gid": 10001, "policy_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62", "policy_mode": "0o644", "policy_uid": 0, "app_file_sha256": {"__init__.py": "0ffebe048ba583e7b623f60e8f449950dc5fd49afce2398381fa143eee834e19", "__main__.py": "3ba1467fa2d47fcf0bd032e8694904450775adf763a6e6f9e4888cc3ce3ed92b", "approval_client.py": "56c201dda358cb4bef11a15da141bb68562cdde9e37b4a880bf1e9257ae82cb6", "approval_http.py": "dfaac1faed80a068aa78a8c87b80db99b8887a8fc3767eebc9d6261a98f1e334", "audit.py": "06da36a8f3451b4abc7a46da2df66d5939524c170658b879499b2a47a1b10cd3", "canonicalize.py": "517bbc6b81cc839636b97863ebdefd38aa375d10f4915feb9bf21867f992f765", "container.py": "5f40d2321ee3f52673238011116e741e7b2ac17e28119f100fbcc490db208fbc", "disposition.py": "5ddd19c2444f8e6c9bb090d04045cf1227a34554e000f07b3f677325bf8089f8", "evidence.py": "169ed59ac53ef534a6db835ba371a92177c0ffb000b04203507242e2159ebc21", "http_transport.py": "eda90098f8bf2e5a47d819bf6ae0b0d1dcaf9a87aad3cb61503d060be666339a", "hub_writeback.py": "98b9caa8096c2c3eb8ecfe33900a0ace98453e7608f504fd08a64f50156519a3", "intake.py": "a733b4b608b9ea90b2c8293901aa14388a0bf0d3719f121038bfdcc1faca2038", "intake_approval.py": "52f8b3643e4c5c6f56f2ef1fd9c7747da2165f8f88122e93b3f3ee3ecae48c73", "intake_delivery.py": "4771520cc96325d93bc3f8e08005c3d714a5597582cb1adccc0b0fa73dac0b0d", "intake_hub.py": "c3daddce31791a6e82f7b07406ef4d75bccba5a278fab01dab378a06abceabc8", "intake_service.py": "99ea285a725c58b7571e2a69ceb558f16bbcca4ec391607a0cc2d0459de3b318", "lifecycle.py": "e25f865828e541b2889cd6c9afe0ce7ca4441bf35155304cc7de7c86c44b28e8", "lifecycle_commands.py": "783ec451dd7ef33dc0cfd72c1633ea5208dfd463afeaa0b68d279f156a9b0828", "lifecycle_http.py": "d4b95421d54663f041a10b4f42ac9c013493fa7a770223ab45877b451a7a7c7d", "lifecycle_review.py": "b6d69be6c56f3e2f411501083e199195190df6c740a01b07e82a476268ca3629", "lifecycle_service.py": "2dde9280214e68e5e7a7b48d703886947c2a46139ada86e8ff2283a9d4b55a70", "lifecycle_ui.py": "448e9169607c487e362d4d0d1730aec33987b6dcf12c49dfff7de3aaaab7ae03", "memo.py": "697058863a4e6de2c143a2433eaaf5e15e5e7375dc5459af6911b146567e8b5d", "oidc.py": "6b24dac9537a12141a361c4577feaf42f6c197d8d87da16ca47a08daf6916915", "policy.py": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62", "presentation.py": "54375ccd8358fac41de220bccadbee09f8b7c8ebbeeafaafd381a7456331da50", "provenance.py": "de02c6bf162865ba9619d74cfd68dd0a5d7f81e6c9e427f67f848cb2c2214f83", "records.py": "82f696151895c51b168144a3078d05764da3499b96917c465629ce0836fa8e33", "requester.py": "66de1af80a5fb2442c1e4407148b581de24b671b5f76c9d2a0458d38e27833a8", "review.py": "d4a085b0ee3e65a9976b9b656ec87a8c735be8d74cfdc9bb55feeb90917c688e", "runtime.py": "535940eed76c5f21ec4c379796fbfddfd85fc309aad0de4d64afda06cdc2d3c5", "stance.py": "62b96b76eba4ec277344172f10f6233e979dda4d079f972d9a55c4e5aed96c3c", "store.py": "9c14b10becb99da19349073e9aa691c25a205df4a2c4d932f37d64f13e8d3b41", "ui.py": "ff0671dc7be4d08ab8f0d38504f70afd355ad3a8a6293620c75864ca56a53ec9", "web.py": "bd08a6b0ea0e418ea1e31e46edab6b7478de9779b823af4f5d72fd1346197e42", "workload.py": "b86cebf5e7a8182b24bb7f9fdbd6f0e17b578d690d9f06c541e713d7d995c5e8"}, "apk_packages": {".python-rundeps": "20260901.001029", "alpine-baselayout": "3.7.2-r1", "alpine-baselayout-data": "3.7.2-r1", "alpine-keys": "2.6-r0", "alpine-release": "3.24.1-r0", "apk-tools": "3.0.6-r0", "busybox": "1.37.0-r31", "busybox-binsh": "1.37.0-r31", "ca-certificates": "20260611-r0", "ca-certificates-bundle": "20260611-r0", "gdbm": "1.26-r0", "keyutils-libs": "1.6.3-r4", "krb5-conf": "1.0-r2", "krb5-libs": "1.22.2-r1", "libapk": "3.0.6-r0", "libbz2": "1.0.8-r6", "libcom_err": "1.47.4-r0", "libcrypto3": "3.5.8-r0", "libffi": "3.5.2-r1", "libintl": "1.0-r0", "libncursesw": "6.6_p20260516-r0", "libnsl": "2.0.1-r2", "libpanelw": "6.6_p20260516-r0", "libssl3": "3.5.8-r0", "libtirpc": "1.3.5-r1", "libtirpc-conf": "1.3.5-r1", "libuuid": "2.42.3-r1", "libverto": "0.3.2-r2", "musl": "1.2.6-r2", "musl-utils": "1.2.6-r2", "ncurses-terminfo-base": "6.6_p20260516-r0", "readline": "8.3.3-r1", "scanelf": "1.3.9-r1", "sqlite-libs": "3.53.4-r0", "ssl_client": "1.37.0-r31", "tzdata": "2026c-r0", "xz-libs": "5.8.3-r0", "zlib": "1.3.2-r1"}, "apk_world_sha256": "61a659443a49627ba7114faa13a4870a2ede26b314064829ced169412be7002e", "apk_keys_sha256": {"alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub": "9c102bcc376af1498d549b77bdbfa815ae86faa1d2d82f040e616b18ef2df2d4", "alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub": "12f899e55a7691225603d6fb3324940fc51cd7f133e7ead788663c2b7eecb00c", "alpine-devel@lists.alpinelinux.org-6165ee59.rsa.pub": "207e4696d3c05f7cb05966aee557307151f1f00217af4143c1bcaf33b8df733f"}, "python_dependencies": {"informed-decision": "0.2.0", "PyJWT": "2.15.1", "cryptography": "50.0.1", "cffi": "2.1.1", "pycparser": "3.0", "waitress": "3.0.2"}, "zlib_runtime_version": "1.3.2", "zlib_compile_version": "1.3.2", "libz_sha256": "ecc8b9dfc45eb7fa29b410ffaca6257873890de53b80fe91735737b49067c5f2", "compression_vectors_pass": 4, "malformed_zlib_refusals": 3, "crypto_jwt_waitress_smoke": true, "openssl": "OpenSSL 3.5.8 25 Aug 2026", "network_accessed": false, "database_opened": false, "provider_or_customer_data_used": false} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80 deleted file mode 100644 index b897ef9..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80 +++ /dev/null @@ -1,362 +0,0 @@ -"""One fixed supervised phase; never run this during source preparation.""" -import argparse -import hashlib -import json -import os -from pathlib import Path -import re -import selectors -import stat -import subprocess -import sys -import time - -if sys.flags.optimize: - raise SystemExit('optimized interpreter refused') - -PHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline', - 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify') - - -def require(value, reason): - if not value: - raise ValueError(reason) - - -def sha(path): - digest = hashlib.sha256() - with Path(path).open('rb') as stream: - for block in iter(lambda: stream.read(1024**2), b''): - digest.update(block) - return digest.hexdigest() - - -def private(path, directory=False): - info = Path(path).lstat() - require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode)) - and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600) - and (directory or info.st_nlink == 1), 'private owned custody required') - -ROOT = Path(__file__).resolve().parent -CASE = ROOT.parent -OWNER = Path('/home/worsch/informed-decision') -CUSTODIAN = Path('/home/worsch/the-custodian') -CUSTODY = CUSTODIAN / '.private-backups/20261011-infd-zlib-r1' -SOCKET = 'unix:///var/run/docker.sock' -IMAGE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23' -VOLUME = 'infd-zlib-r1-restore-20261011' -MAX_BYTES = 512 * 1024**2 - - -def run(argv, body=None, timeout=90): - return subprocess.run(argv, input=body, text=True, capture_output=True, timeout=timeout) - - -def json_result(result): - try: - value = json.loads(result.stdout) - except (ValueError, TypeError): - raise ValueError('safe phase receipt unavailable; outcome uncertain') from None - require(result.returncode == 0 and isinstance(value, dict) and value.get('all_checks_pass') is True, - 'phase did not verify complete; raw output withheld; no retry') - permitted = {'phase', 'all_checks_pass', 'raw_output_withheld', 'native_approval_verified_by_root', - 'native_authenticated_grant', 'source_readonly_baseline', 'snapshot_readonly', - 'snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'native_parent_private', - 'native_uid', 'pod_name', 'pod_uid', 'live_readonly_schema_integrity_counts', - 'live_counts_may_include_concurrent_commits', 'operation_requested', 'source_revision', - 'resources', 'prune', 'rollout_verified', 'latest_live_database_retained', - 'client_diff_sha256', 'native_rollout_verified', 'current_schema5_database_retained', - 'private_copy_verified', 'installed_purpose_checks', 'policy_sha256', - 'policy_mode', 'policy_uid', 'installed_zlib_files', 'zlib_package', 'health_statuses', 'native_provider_probe', 'recovery_verification'} - require(set(value) <= permitted, 'unexpected output fields withheld') - from receipt_safety import counts - for name in ('source_readonly_baseline', 'snapshot_readonly', 'live_readonly_schema_integrity_counts'): - if name in value: - counts(value[name]) - for name in ('snapshot_sha256', 'client_diff_sha256'): - if name in value: - require(re.fullmatch(r'[a-f0-9]{64}', value[name]) is not None, 'invalid output digest withheld') - if 'snapshot_bytes' in value: - require(type(value['snapshot_bytes']) is int and 0 < value['snapshot_bytes'] <= MAX_BYTES, - 'invalid output size withheld') - if 'snapshot_file' in value: - require(value['snapshot_file'] in ('drill.sqlite', 'cutover.sqlite'), 'invalid output file withheld') - return value - - -def docker(argv, body=None, timeout=120): - return run(['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET] + argv, - body=body, timeout=timeout) - - -def remote(phase, revision, approved): - # Fixed module imported only after main verifies every executor source hash. - from native_program import PROGRAM - payload = {'phase': phase, 'source_revision': revision, 'approval': approved, - 'sealed': json.loads((CASE / 'sealed-live-deployment.json').read_text()), - 'dependencies': json.loads((CASE / 'sealed-native-dependencies.json').read_text()), - 'candidate': json.loads((CASE / 'candidate-deployment.json').read_text()), - 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()), - 'receipt_safety': (ROOT / 'receipt_safety.py').read_text(), - 'purpose_checks': (CASE / 'check_installed.py').read_text(), - 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'readonly': (CASE / 'sqlite_readonly.py').read_text()} - script = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace( - '__GUARD__', repr((CASE / 'change_guard.py').read_text())) - return json_result(run(['ssh', 'railiance01', 'python3 -'], script, timeout=200)) - - -def copy_snapshot(phase, revision, approved, journal): - metadata = remote(phase, revision, approved) - backup_phase = 'drill-backup' if phase == 'drill-copy' else 'cutover-backup' - prior = journal.data['phases'][backup_phase]['result'] - for key in ('snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'snapshot_readonly'): - require(metadata[key] == prior[key], 'closed frozen snapshot changed') - name = metadata['snapshot_file'] - require(name in ('drill.sqlite', 'cutover.sqlite') and 0 < metadata['snapshot_bytes'] <= MAX_BYTES, - 'fixed bounded snapshot required') - pod = metadata['pod_name'] - require(re.fullmatch(r'[a-z0-9]([-a-z0-9]*[a-z0-9])?', pod) is not None, 'fixed owner pod name required') - local = CUSTODY / name - fd = os.open(local, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - # Binary snapshot stdout goes only to a private file, never tool/chat/log output. - # Snapshot schema/count metadata was validated before opening this destination. - with os.fdopen(fd, 'wb') as destination: - command = ('kubectl exec -n informed-decision ' + pod + - ' -c informed-decision -- cat /data/private/zlib85091-r1/' + name) - transfer = subprocess.Popen(['ssh', 'railiance01', command], stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) - selector = selectors.DefaultSelector() - selector.register(transfer.stdout, selectors.EVENT_READ) - deadline = time.monotonic() + 150 - received = 0 - try: - while True: - remaining = deadline - time.monotonic() - require(remaining > 0 and selector.select(remaining), 'private transfer timed out') - block = os.read(transfer.stdout.fileno(), 1024**2) - if not block: - break - received += len(block) - require(received <= metadata['snapshot_bytes'] and received <= MAX_BYTES, - 'private copy exceeds frozen snapshot size') - destination.write(block) - transfer.wait(timeout=max(1, deadline - time.monotonic())) - finally: - selector.close() - transfer.stdout.close() - if transfer.poll() is None: - transfer.kill() - transfer.wait() - destination.flush() - os.fsync(destination.fileno()) - private(local) - require(transfer.returncode == 0 and local.stat().st_size == metadata['snapshot_bytes'] - and sha(local) == metadata['snapshot_sha256'], 'private copy incomplete/uncertain; retain, no retry') - # Re-read identity/hash after transfer. This is verification, not a write retry. - after = remote(phase, revision, approved) - require(after['pod_uid'] == metadata['pod_uid'] and after['snapshot_sha256'] == metadata['snapshot_sha256'], - 'source identity/snapshot changed during transfer') - return {**metadata, 'off_cluster_private_copy_verified': True, 'snapshot_contents_output': False} - - -def offline_verify(journal): - from receipt_safety import counts - snapshot = CUSTODY / 'drill.sqlite' - private(snapshot) - previous = journal.data['phases']['drill-copy']['result'] - require(sha(snapshot) == previous['snapshot_sha256'], 'protected snapshot bytes changed') - identity = docker(['info', '--format', '{{json .Name}}']) - require(identity.returncode == 0 and json.loads(identity.stdout) == 'bnt-lap001', - 'local offline Docker engine identity changed') - listed = docker(['volume', 'ls', '--format', '{{.Name}}']) - require(listed.returncode == 0 and VOLUME not in listed.stdout.splitlines(), - 'disposable volume absence not verified') - nonce = journal.data['phases']['offline-verify']['attempt_id'] - labels = {'informed-decision.case': 'infd-zlib-20261011-r1', - 'informed-decision.use': 'offline-restore-only', 'informed-decision.attempt': nonce} - created = docker(['volume', 'create', '--driver', 'local', '--label', 'informed-decision.case=infd-zlib-20261011-r1', - '--label', 'informed-decision.attempt=' + nonce, - '--label', 'informed-decision.use=offline-restore-only', VOLUME]) - require(created.returncode == 0 and created.stdout.strip() == VOLUME, 'volume creation uncertain; no retry') - inspected = docker(['volume', 'inspect', VOLUME]) - require(inspected.returncode == 0, 'new volume identity unavailable') - volume = json.loads(inspected.stdout) - require(len(volume) == 1 and volume[0]['Name'] == VOLUME and volume[0].get('Labels') == labels - and volume[0].get('Driver') == 'local' and not volume[0].get('Options') - and volume[0].get('Scope') == 'local', 'new isolated volume identity changed') - identity = {key: volume[0][key] for key in ('Name', 'Labels', 'Driver', 'Scope', 'CreatedAt')} - journal.note('offline-verify', 'created_volume_identity', identity) - # One-time UID0/CAP_CHOWN initialization is isolated to the NEW named volume. - # No host/live PVC/config/token mount exists; this detail needs packet review. - initializer = """import hashlib,os,stat,sys -if sys.flags.optimize:raise RuntimeError('optimized interpreter refused') -folder='/restore';expected=EXPECTED -if os.listdir(folder):raise ValueError('new empty private volume required') -os.chmod(folder,0o700) -fd=os.open(folder+'/review.sqlite',os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_WRONLY,0o600) -digest=hashlib.sha256();size=0 -with os.fdopen(fd,'wb') as target: - while True: - block=sys.stdin.buffer.read(1024**2) - if not block:break - size+=len(block) - if size>512*1024**2:raise ValueError('private restore size exceeds bound') - target.write(block);digest.update(block) - target.flush();os.fsync(target.fileno()) -if digest.hexdigest()!=expected:raise ValueError('private copied snapshot differs') -os.chown(folder+'/review.sqlite',10001,10001);os.chown(folder,10001,10001) -print('{"all_checks_pass":true,"private_copy_verified":true}') -""".replace('EXPECTED', repr(previous['snapshot_sha256'])) - with snapshot.open('rb') as source: - argv = ['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET, - 'run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m', - '--user', '0:0', '--cap-drop', 'ALL', '--cap-add', 'CHOWN', - '--security-opt', 'no-new-privileges', '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore', - '--entrypoint', 'python', IMAGE, '-c', initializer] - copied = subprocess.run(argv, stdin=source, text=True, capture_output=True, timeout=150) - json_result(copied) - options = ['run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m', - '--user', '10001:10001', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges', - '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore'] - readonly = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'], - (CASE / 'sqlite_readonly.py').read_text()) - require(readonly.returncode == 0, 'offline pre-open schema/integrity gate refused; never open Store') - checked = counts(json.loads(readonly.stdout)) - require(checked == previous['snapshot_readonly'], 'frozen snapshot/clone readonly counts differ') - admin = docker(options + ['--entrypoint', 'informed-decision-admin', IMAGE, - 'inspect', '--db', '/restore/review.sqlite']) - require(admin.returncode == 0, 'offline admin result uncertain') - admin_counts = json.loads(admin.stdout) - require(admin_counts == {key: checked[key] for key in ('schema_version', 'lifecycle', 'outbox', 'submissions')}, - 'offline admin changed frozen snapshot counters') - # Admin Store construction must not silently alter schema/integrity/counters. - post = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'], - (CASE / 'sqlite_readonly.py').read_text()) - require(post.returncode == 0, 'offline post-admin readonly integrity gate refused') - post_checked = counts(json.loads(post.stdout)) - require(post_checked == checked == previous['snapshot_readonly'], - 'offline admin altered frozen snapshot proof') - private(snapshot) - require(sha(snapshot) == previous['snapshot_sha256'], 'original protected snapshot changed during drill') - return {'all_checks_pass': True, 'offline_volume': VOLUME, 'offline_readonly': checked, - 'offline_post_admin_readonly': post_checked, 'original_snapshot_hash_unchanged': True, - 'created_volume_identity': identity, - 'admin_counts': admin_counts, 'network': 'none', 'runtime_tokens_or_config_mounted': False, - 'service_entrypoint_started': False, 'original_snapshot_retained': True} - - -def cleanup_offline(journal): - inspected = docker(['volume', 'inspect', VOLUME]) - require(inspected.returncode == 0, 'owned disposable volume identity unverified') - value = json.loads(inspected.stdout) - expected = journal.data['phases']['offline-verify']['result']['created_volume_identity'] - require(len(value) == 1 and {key: value[0].get(key) for key in expected} == expected - and not value[0].get('Options'), 'volume ownership/recreation changed') - removed = docker(['volume', 'rm', VOLUME]) - require(removed.returncode == 0 and removed.stdout.strip() == VOLUME - and docker(['volume', 'inspect', VOLUME]).returncode != 0, - 'cleanup outcome ambiguous; do not retry') - return {'all_checks_pass': True, 'only_disposable_offline_volume_removed': VOLUME, - 'native_and_offcluster_original_snapshots_retained': True} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--phase', choices=PHASES, required=True) - parser.add_argument('--root-authority-receipt', type=Path, required=True) - parser.add_argument('--root-native-outcome', type=Path, required=True) - parser.add_argument('--source-revision', required=True) - parser.add_argument('--root-fresh-scan-receipt', type=Path, required=True) - args = parser.parse_args() - require(args.root_authority_receipt == Path('/tmp/security-infd-zlib-root-authority.json') - and args.root_native_outcome == Path('/tmp/security-infd-zlib-native-outcome-latest.json'), - 'fixed supervised receipt paths required') - require(re.fullmatch(r'[0-9a-f]{40}', args.source_revision) is not None, 'exact source commit required') - # Verify all prepared executor/input bytes before reading authority or dispatching. - plan = json.loads((ROOT / 'execution-plan.json').read_text()) - require(plan.get('state') == 'prepared-unapproved' and plan.get('published_artifact_verified') is True, 'unfrozen proposal cannot execute') - require(set(plan['executor_files']) == {'actuator.py', 'authority_journal.py', 'native_program.py', 'receipt_safety.py'}, - 'fixed executor sources required') - require(set(plan['owner_source_files']) == {'deploy/lifecycle/admission.candidate.json', - 'deploy/lifecycle/service.candidate.json', - 'deploy/lifecycle/kustomization.yaml', - 'deploy/lifecycle/registries.candidate.json'}, - 'fixed owner image sources required') - for relative, digest in plan['executor_files'].items(): - target = ROOT / relative - require(not target.is_symlink() and sha(target) == digest, 'executor source bytes changed') - for relative, digest in plan['case_files'].items(): - require(not Path(relative).is_absolute() and '..' not in Path(relative).parts, - 'case source path escaped') - target = CASE / relative - require(not target.is_symlink() and sha(target) == digest, 'case source bytes changed') - for relative, digest in plan['owner_source_files'].items(): - target = OWNER / relative - require(not target.is_symlink() and sha(target) == digest, 'owner image-only source changed') - committed = subprocess.run(['git', '-C', str(OWNER), 'show', args.source_revision + ':' + relative], - capture_output=True) - require(committed.returncode == 0 and hashlib.sha256(committed.stdout).hexdigest() == digest, - 'committed owner render source differs from approved inputs') - head = run(['git', '-C', str(OWNER), 'rev-parse', 'HEAD']) - require(head.returncode == 0 and head.stdout.strip() == args.source_revision, 'owner source HEAD differs') - from authority_journal import Journal, authority, fresh_scan - private(args.root_authority_receipt) - private(args.root_native_outcome) - receipt = json.loads(args.root_authority_receipt.read_text()) - require(receipt.get('native_outcome_sha256') == sha(args.root_native_outcome), 'native outcome bytes changed') - require(receipt.get('executor_plan_sha256') == sha(ROOT / 'execution-plan.json'), - 'root verified executor plan differs') - approved = authority(receipt, json.loads(args.root_native_outcome.read_text()), sha(CASE / 'memo.json'), - args.source_revision, args.phase) - if args.phase in ('release', 'verify'): - require(args.root_fresh_scan_receipt == Path('/tmp/security-infd-zlib-fresh-scan-receipt.json'), 'fixed fresh scan receipt path required') - private(args.root_fresh_scan_receipt) - scan_receipt=json.loads(args.root_fresh_scan_receipt.read_text()) - report_path=Path('/tmp/security-infd-zlib-fresh-scan-full.json');private(report_path) - require(sha(report_path)==scan_receipt.get('full_report_sha256'), 'fresh scan report bytes changed') - fresh_scan(scan_receipt,json.loads(report_path.read_text())) - parent = CUSTODY.parent - private(parent, directory=True) - ignored = run(['git', '-C', str(CUSTODIAN), 'check-ignore', '--no-index', str(CUSTODY / 'drill.sqlite')]) - require(ignored.returncode == 0, 'offcluster custody is not ignored') - if not CUSTODY.exists(): - require(args.phase == 'drill-backup', 'first approved phase must create unique private custody') - os.mkdir(CUSTODY, 0o700) - private(CUSTODY, directory=True) - capacity = os.statvfs(CUSTODY) - require(capacity.f_bavail * capacity.f_frsize >= 2 * MAX_BYTES + 16 * 1024**2, - 'independent protected custody headroom insufficient') - os.makedirs(CUSTODY / 'empty-docker-config', mode=0o700, exist_ok=True) - private(CUSTODY / 'empty-docker-config', directory=True) - require(not list((CUSTODY / 'empty-docker-config').iterdir()), 'public Docker read config must stay empty') - journal = Journal(CUSTODY, {'proposal': plan['proposal_id'], 'source_revision': args.source_revision, - 'memo_sha256': sha(CASE / 'memo.json'), 'plan_sha256': sha(ROOT / 'execution-plan.json')}) - try: - journal.start(args.phase, approved) # fsynced RUNNING before any phase effect. - if args.phase in ('drill-copy', 'cutover-copy'): - result = copy_snapshot(args.phase, args.source_revision, approved, journal) - elif args.phase == 'offline-verify': - result = offline_verify(journal) - elif args.phase == 'cleanup-offline': - result = cleanup_offline(journal) - else: - result = remote(args.phase, args.source_revision, approved, operation_intent=journal.data['phases'].get('rollback' if args.phase == 'rollback-verify' else 'release') if args.phase in ('verify', 'rollback-verify') else None) - journal.finish(args.phase, result) - print(json.dumps(result)) # Only count/hash/identity receipts, never snapshot bytes. - except BaseException as error: - safe = {'all_checks_pass': False, 'phase': args.phase, 'failure_class': type(error).__name__, - 'failure': 'fixed phase outcome uncertain; original evidence retained; no retry', - 'raw_output_withheld': True} - if journal.data['phases'].get(args.phase, {}).get('state') == 'RUNNING': - journal.finish(args.phase, safe) - print(json.dumps(safe)) - raise SystemExit(1) from None - finally: - journal.close() - - -if __name__ == '__main__': - try: - main() - except Exception: - raise SystemExit('supervised execution refused before phase; raw output withheld') from None diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e deleted file mode 100644 index 3ee1ded..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e +++ /dev/null @@ -1,342 +0,0 @@ -[ - { - "path": "deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "path": "deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "path": "deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "path": "deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "sha256": "750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "sha256": "09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/intake.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "sha256": "8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "sha256": "196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "sha256": "15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "sha256": "0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "sha256": "a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "path": "docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "sha256": "4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9" - }, - { - "path": "workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e deleted file mode 100644 index e2db332..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e +++ /dev/null @@ -1,96 +0,0 @@ -{ - "proposal_id": "INFD-WP-0009-T05/zlib-85091-r1", - "task_id": "INFD-WP-0009-T05", - "risk_record": "INFD-RISK-20261011-ZLIB-85091", - "state": "actual-owner-CI-published-independent-verification-passed-native-unapproved", - "verified_utc": "2026-10-11T09:40:24.469646+00:00", - "source_revision": "9b4bb25d325698ae543e7e8a8a3c3592db1f58fc", - "ci_task": 11534, - "ci_run": 4, - "ci_status": "success", - "reviewed_source_manifest_sha256": "724aa4777eb1c8976d868e5f504473f4b6d58f13d750d87aefa5c8d5ca0747ec", - "all74_source_hashes_match_actual_commit": true, - "published_image_digest": "sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23", - "base_image_digest": "sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "registry_config_digest": "sha256:622becc3ebd97cb66b6e7c36f35bf931a7decd9464b65851123c1cc8c998d75b", - "executed_local_daemon_image_id": "sha256:f2e07e3dd839b55afd5d517243a765a26965325e4e6750f508771ad556bd6d01", - "serialization_caveat": "Daemon load reserializes config; all diffIDs/platform/effective config+labels independently equal to registry; neither config nor daemon ID substitutes for registry image digest", - "actual_registry_descriptors_and_layers_verified": true, - "base9layers_unchanged": true, - "overlay_layers": 2, - "fullscan_baseline": { - "MEDIUM": 1, - "id": "CVE-2026-85091" - }, - "fullscan_candidate_allseverity_findings": 0, - "fullscan_no_suppressions": true, - "same_fresh_DB_both_images": true, - "scanner_db_sha256": "03cacb40452ceb74afdfca072cb82f79ee884dfac006ef24a4dfdad133b0f917", - "scanner_db_metadata": { - "Version": 2, - "NextUpdate": "2026-10-11T18:59:50.889010572Z", - "UpdatedAt": "2026-10-10T18:59:50.889010923Z", - "DownloadedAt": "2026-10-10T23:32:32.380033131Z" - }, - "scanner_caveat": "Alpine3.24 absent EOL list warning; actual38OS+6Python detectors ran. Zero means this verified DB snapshot, not unknown-future assurance or estate risk grade.", - "sbom_count": 44, - "sbom_only_delta": { - "zlib": [ - "1.3.2-r0", - "1.3.2-r1" - ] - }, - "final_changed_files": [ - "lib/apk/db/installed", - "usr/lib/libz.so.1.3.2" - ], - "directory_count": 313, - "directory_metadata_delta": {}, - "app_files_identical": 36, - "installed_UID": 10001, - "installed_GID": 10001, - "policy_sha256": "9d7c1be4cb6199d14ef0aaead5f85951f45b0ccbd7c800a7fde922a253566b62", - "policy_mode": "0o644", - "policy_UID": 0, - "candidate_image_executed": true, - "public_registry_layer_readback": true, - "native_provider_test": false, - "installed_purpose_variants": 10, - "purpose_forms_allow": 2, - "purpose_forms_refuse": 8, - "runtime_readonly_networknone": true, - "native_workload_or_database_or_snapshot_or_credentials_accessed": false, - "native_rollout": false, - "founder_release_approval": false, - "risk_followthrough": "T05 remains active; manual review due2026-10-18, no controller or estate risk grade. Actual native installation/fresh scoped founder decision still required before closure.", - "verification_refinement": "First run used registry config as daemon handle and refused before candidate execution125. Corrected by independently proving loaded daemon ID semantics+diffIDs exact, then executed. Not actual image failure.", - "evidence_sha256": { - "artifact-comparison.json": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4", - "base-image-config.json": "4ad7249c75fd9fc039ceb186772419e1cba5fd9759c404556459c80a2d285d10", - "base-image-manifest.json": "978c575d3c7bf0de39dbe4e9567c39cd36f70b7e55bc17357bdb03fb3b96b0fb", - "base-runtime.json": "20aabb9f4846ab4fa6e28e0763d153188da151ee32293366a51b0fdf872a4d24", - "base-sbom.cdx.json": "dd7d64c9571391acd4d1f86c6026a1b094abab4ee56e5a9f799ec2fd3c7d915a", - "base-sbom.cdx.json.log": "8ab4473a5f74b36e82a4095e9c8d6e66d80aec64bd1cebd6c9f6734531869fca", - "base-trivy-full.json": "3e91a3001adb008cec37d9ae3c08342f5d43c8ba0111eee75c63a6a7918808a9", - "base-trivy-full.json.log": "96a48dc39f7d674b4954d5fa98a76645528ec0fe705782fe114e3b4b9d776862", - "candidate-runtime.json": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124", - "ci-publication-observation.json": "ffe6f2508bc9bebbdc85ac63eae5765562efbb8e920cc85b8da30a12ee1e4b17", - "image-config.json": "253ab359013aeb3457d2abe0852bacd33697f973dd32c1b5738800e5dc633f43", - "image-manifest.json": "9ca2abec6a26d9a9f802dfe75efa8bffdc28485e6bcaf525cc9b075df3700194", - "installed-purpose.json": "312312c6f0bd2751401b3bc5bcd30ee6af01ed7cb380f85b863d03b0c7e9d1ad", - "loaded-daemon-image-proof.json": "2e5c043a101d75e4c569adb6bc2f09020679b28ed2ad836c954f7d27101ce719", - "publication-digest.json": "ca2a1dec38db8c5fbbc00ef4f27aa865edbbd4a7ca9dc21b2ba023664c507f38", - "read_public_artifact.py": "eaa31da1ebb2095ea5e1bc7c570311cc4747f0d7f718034dad934351d1920daf", - "registry-proof.json": "3ef52d9e4800fc9bd238072241dc15f13a0260ee3df1e82e6f63e72902749d92", - "run_published_runtime.py": "d9bd33fecd2a98c2381b4ba2dce12b5c3d9a69faa5e350291924fc885f5e775f", - "run_published_scan.py": "2583ab96350412fb3f20b5b05ded0949eb3ee8e5c0e8923a9c2c42d419d36639", - "runtime-comparison.json": "993b80b9285e227a0bdfde4927cd2f053bcc4a58dd9bd1b633ac7a4637d6713c", - "runtime-receipt.json": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889", - "sbom.cdx.json": "4202263059780eae0448328044c1cf68b59758f329ae6bc981d916610527eae3", - "sbom.cdx.json.log": "51873b37a0846ee1a618ea7591d79809dc0f8624f77a03fae7af953bcbd7282f", - "scan-receipt.json": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a", - "source-commit-proof.json": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b", - "trivy-full.json": "2ff68e765b9c65292611d5a969202a7654637168edefcedac5d2c7634009edae", - "trivy-full.json.log": "b3419af860d264449f61986f562491edaead27dfd3638ceabe94b4fe177f4bf1" - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e1aaea000c7de1be6cde62499d9fe2705c1e31b3fb8904fafa5333537978e785 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e1aaea000c7de1be6cde62499d9fe2705c1e31b3fb8904fafa5333537978e785 deleted file mode 100644 index cb3790a..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e1aaea000c7de1be6cde62499d9fe2705c1e31b3fb8904fafa5333537978e785 +++ /dev/null @@ -1,617 +0,0 @@ -[ - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/mapping.json", - "sha256": "796dbbff5c7ca61af2c84c2f2ad3de23b56f67210a30bded23a1ff5db806d26f" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/original-manifest.json", - "sha256": "60507a1000a216623a3d53b21e64c6f4569b1aa064781d3ac672a9fa130e813c" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-review.json", - "sha256": "e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/mapping.json", - "sha256": "dac8209ec57adc6df16673079f88a60cc56e89294de9389bc219ea19fdf51b5e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/original-manifest.json", - "sha256": "81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "sha256": "750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "sha256": "09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "sha256": "8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "sha256": "196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "sha256": "15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "sha256": "0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "sha256": "a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "sha256": "4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/observed-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_initial_dispatch.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_initial_dispatch.py", - "sha256": "609250d2d15e31128d0331354babbaed6c27ea165cdfcbe893d14e932237dd1a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/published-evidence-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/published-evidence-manifest.json", - "sha256": "6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-60507a10/owner-snapshot/intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "sha256": "a187eae16bd6eb8b05fdbffc860a7e87b62e726379fde92915de15d6d8bd4290" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_refined_boundaries.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/probe_refined_boundaries.py", - "sha256": "5ab431b060c2417df920c6268a9f39181b0164fc887b5dc3359c4c1d70270ebd" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/refined-changes-requested-review.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/refined-changes-requested-review.json", - "sha256": "354db3948f878ad14f790b02658739d0b620acfdebe0bcfd492667846e457fdf" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/mapping.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/mapping.json", - "sha256": "4f6633dbbfd522465da60156b811a1dc29ebad645752d4b1fd72b5f49afaefae" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/original-manifest.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/original-manifest.json", - "sha256": "f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/service.candidate.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/deploy/lifecycle/service.candidate.json", - "sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "sha256": "eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "sha256": "9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "sha256": "fd38385e3687ec6f9886d5025b394463c252b8198b0426c56f95740139eb7e9b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "sha256": "a1d29ea6822be12817b2d60109cec44b061d167f147b10f9aa352529e1a4c475" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "sha256": "2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-contract.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-contract.json", - "sha256": "b13806a7e1464454e3817e1e62b56b0fa07fe2fddbd94d7fdc530e2c3dd9e3d2" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-report-fixture.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-report-fixture.json", - "sha256": "30607043a4ca44e42024b63659ae10993c14c7a04e7c5685255ccafe4d3fb55c" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "sha256": "6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "sha256": "508f9bf9f11a35b8fc494e1d66e8f11aa0338df4aa9d7115a80e2dabdae81b55" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "sha256": "ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "sha256": "3c903dd82c13273cb61f590b258b059c32e0fd515d7c176ce4fec3e68a77bc7b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight-full.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight-full.json", - "sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "sha256": "3b6b67a0b4d51ef791e775b528b64aa5a6f14eff629737e67baee7e2f6f60762" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/preparation-failure-history.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/preparation-failure-history.json", - "sha256": "51b9983d2a806240f562dcb951955fc8360c181e6d08a8dfdcba95361c6dd596" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "sha256": "29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight-full.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight-full.json", - "sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "sha256": "4c3c3166205808e81326ccdae1b56afef757b0ceb4e6c96a17bf384317aeee4a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "sha256": "ecddcb00127774a17d5cf553417c60d1b6fb753b6eb91a49dabb7fc52678595e" - }, - { - "path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/reviewed-143-f170b34d/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - }, - { - "path": "intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "archived_path": "docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-aa941510/owner-snapshot/intakes/RPF-IN-0009-infd-zlib-native-release-review.md", - "sha256": "d162fda35ac46502f14cea91d7d614ce5041e15eae0d4705ede4bf9efc18b36a" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349 deleted file mode 100644 index d12207f..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/e24b3fdae42721342a26ebe34caf57c2d21153ff855e584a0c4ebd3eb9adb349 +++ /dev/null @@ -1,56 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-ZLIB-NATIVE-R1", - "work_record": "RPF-IN-0009", - "role": "railiance-platform technical owner only", - "status": "changes-requested-review-stopped-on-refreeze", - "requested_release_manifest_sha256": "1e4b5a308294d588c7bf75e5d27eb475a1ed2f75ec8c598f4db285cf01547fb2", - "requested_release_paths": 67, - "observed_preserved_manifest_sha256": "81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47", - "observed_preserved_paths": 68, - "original67_snapshot_not_retained_by_this_reviewer": true, - "publication_manifest_sha256": "6ba2c341b496ab7f79f9c5fd33a92831c2f8db25d018a4b4e67d41e0af9c5bc9", - "published29_and_observed68_hashes_match": true, - "findings": [ - { - "id": "dispatch-operation-intent", - "cause": "main calls remote with operation_intent keyword, while signature accepts only phase/revision/approved.", - "effect": "First remote phase TypeError after persisted RUNNING; no native dispatch.", - "required": "Typed payload argument plumbing plus full main/CLI mocked transport regression, not direct native_program fixture only." - }, - { - "id": "copy-backup-identity", - "cause": "remote payload omits snapshot_identity; native copy branch requires exact prior backup receipt.", - "effect": "drill-copy and cutover-copy refuse before protected transfer.", - "required": "Pass validated exact completed prior backup result into both pre/post metadata requests; linked phase proof regression." - }, - { - "id": "fresh-scan-artifact-and-coverage", - "cause": "fresh_scan accepts incomplete Results=[{}] without artifact type/config/diffID/source/inventory identity.", - "effect": "Zero claim can reference unrelated/incomplete report despite receipt label. Existing age/DBNextUpdate checks are sound but insufficient.", - "required": "Bind full report to verified immutable f558 artifact exact source/config/diffIDs and full44 inventory with38OS/6Python coverage; wrong/empty/truncated artifact reports refuse release and closure." - }, - { - "id": "concurrent-source-freeze", - "cause": "Requested frozen67 became68 before archival checkpoint.", - "effect": "No exact67 completion agreement possible.", - "required": "New separately immutable refined stage and exact independent review; preserve observed history and do not relabel initial revision accepted." - } - ], - "independent_synthetic_probes": { - "operation_keyword_rejected_before_transport": true, - "copy_payload_missing_snapshot_identity": true, - "payload_missing_operation_intent": true, - "incomplete_unbound_zero_report_accepted": true - }, - "history": "Full observed68 bytes+manifest/map archived; prior approved purpose helpers/journals untouched. New completed IN9 staged before entry; no IN8 scope stretch/Product ADHOC creation.", - "scope_limits": { - "technical_agreement": false, - "native_actions": false, - "private_database_access": false, - "credentials_read": false, - "owner_source_write": false, - "push": false, - "native_founder_approval": false, - "T05_complete": false - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461 deleted file mode 100644 index 3c6572c..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461 +++ /dev/null @@ -1,362 +0,0 @@ -"""One fixed supervised phase; never run this during source preparation.""" -import argparse -import hashlib -import json -import os -from pathlib import Path -import re -import selectors -import stat -import subprocess -import sys -import time - -if sys.flags.optimize: - raise SystemExit('optimized interpreter refused') - -PHASES = ('drill-backup', 'drill-copy', 'offline-verify', 'cleanup-offline', - 'cutover-backup', 'cutover-copy', 'release', 'verify', 'rollback', 'rollback-verify') - - -def require(value, reason): - if not value: - raise ValueError(reason) - - -def sha(path): - digest = hashlib.sha256() - with Path(path).open('rb') as stream: - for block in iter(lambda: stream.read(1024**2), b''): - digest.update(block) - return digest.hexdigest() - - -def private(path, directory=False): - info = Path(path).lstat() - require((stat.S_ISDIR(info.st_mode) if directory else stat.S_ISREG(info.st_mode)) - and info.st_uid == os.getuid() and stat.S_IMODE(info.st_mode) == (0o700 if directory else 0o600) - and (directory or info.st_nlink == 1), 'private owned custody required') - -ROOT = Path(__file__).resolve().parent -CASE = ROOT.parent -OWNER = Path('/home/worsch/informed-decision') -CUSTODIAN = Path('/home/worsch/the-custodian') -CUSTODY = CUSTODIAN / '.private-backups/20261011-infd-zlib-r1' -SOCKET = 'unix:///var/run/docker.sock' -IMAGE = 'forgejo.coulomb.social/coulomb/informed-decision@sha256:f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23' -VOLUME = 'infd-zlib-r1-restore-20261011' -MAX_BYTES = 512 * 1024**2 - - -def run(argv, body=None, timeout=90): - return subprocess.run(argv, input=body, text=True, capture_output=True, timeout=timeout) - - -def json_result(result): - try: - value = json.loads(result.stdout) - except (ValueError, TypeError): - raise ValueError('safe phase receipt unavailable; outcome uncertain') from None - require(result.returncode == 0 and isinstance(value, dict) and value.get('all_checks_pass') is True, - 'phase did not verify complete; raw output withheld; no retry') - permitted = {'phase', 'all_checks_pass', 'raw_output_withheld', 'native_approval_verified_by_root', - 'native_authenticated_grant', 'source_readonly_baseline', 'snapshot_readonly', - 'snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'native_parent_private', - 'native_uid', 'pod_name', 'pod_uid', 'live_readonly_schema_integrity_counts', - 'live_counts_may_include_concurrent_commits', 'operation_requested', 'source_revision', - 'resources', 'prune', 'rollout_verified', 'latest_live_database_retained', - 'client_diff_sha256', 'native_rollout_verified', 'current_schema5_database_retained', - 'private_copy_verified', 'installed_purpose_checks', 'policy_sha256', - 'policy_mode', 'policy_uid', 'installed_zlib_files', 'zlib_package', 'health_statuses', 'native_provider_probe', 'recovery_verification'} - require(set(value) <= permitted, 'unexpected output fields withheld') - from receipt_safety import counts - for name in ('source_readonly_baseline', 'snapshot_readonly', 'live_readonly_schema_integrity_counts'): - if name in value: - counts(value[name]) - for name in ('snapshot_sha256', 'client_diff_sha256'): - if name in value: - require(re.fullmatch(r'[a-f0-9]{64}', value[name]) is not None, 'invalid output digest withheld') - if 'snapshot_bytes' in value: - require(type(value['snapshot_bytes']) is int and 0 < value['snapshot_bytes'] <= MAX_BYTES, - 'invalid output size withheld') - if 'snapshot_file' in value: - require(value['snapshot_file'] in ('drill.sqlite', 'cutover.sqlite'), 'invalid output file withheld') - return value - - -def docker(argv, body=None, timeout=120): - return run(['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET] + argv, - body=body, timeout=timeout) - - -def remote(phase, revision, approved, snapshot_identity=None, operation_intent=None): - # Fixed module imported only after main verifies every executor source hash. - from native_program import PROGRAM - payload = {'snapshot_identity': snapshot_identity, 'operation_intent': operation_intent, 'phase': phase, 'source_revision': revision, 'approval': approved, - 'sealed': json.loads((CASE / 'sealed-live-deployment.json').read_text()), - 'dependencies': json.loads((CASE / 'sealed-native-dependencies.json').read_text()), - 'candidate': json.loads((CASE / 'candidate-deployment.json').read_text()), - 'rollback': json.loads((CASE / 'rollback-deployment.json').read_text()), - 'receipt_safety': (ROOT / 'receipt_safety.py').read_text(), - 'purpose_checks': (CASE / 'check_installed.py').read_text(), - 'installed_zlib': json.loads((CASE / 'installed-zlib-proof.json').read_text()), 'readonly': (CASE / 'sqlite_readonly.py').read_text()} - script = PROGRAM.replace('__PAYLOAD__', repr(json.dumps(payload))).replace( - '__GUARD__', repr((CASE / 'change_guard.py').read_text())) - return json_result(run(['ssh', 'railiance01', 'python3 -'], script, timeout=200)) - - -def copy_snapshot(phase, revision, approved, journal): - backup_phase = 'drill-backup' if phase == 'drill-copy' else 'cutover-backup' - prior = journal.data['phases'][backup_phase]['result'] - metadata = remote(phase, revision, approved, snapshot_identity=prior) - for key in ('snapshot_sha256', 'snapshot_bytes', 'snapshot_file', 'snapshot_readonly'): - require(metadata[key] == prior[key], 'closed frozen snapshot changed') - name = metadata['snapshot_file'] - require(name in ('drill.sqlite', 'cutover.sqlite') and 0 < metadata['snapshot_bytes'] <= MAX_BYTES, - 'fixed bounded snapshot required') - pod = metadata['pod_name'] - require(re.fullmatch(r'[a-z0-9]([-a-z0-9]*[a-z0-9])?', pod) is not None, 'fixed owner pod name required') - local = CUSTODY / name - fd = os.open(local, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - # Binary snapshot stdout goes only to a private file, never tool/chat/log output. - # Snapshot schema/count metadata was validated before opening this destination. - with os.fdopen(fd, 'wb') as destination: - command = ('kubectl exec -n informed-decision ' + pod + - ' -c informed-decision -- cat /data/private/zlib85091-r1/' + name) - transfer = subprocess.Popen(['ssh', 'railiance01', command], stdout=subprocess.PIPE, - stderr=subprocess.DEVNULL) - selector = selectors.DefaultSelector() - selector.register(transfer.stdout, selectors.EVENT_READ) - deadline = time.monotonic() + 150 - received = 0 - try: - while True: - remaining = deadline - time.monotonic() - require(remaining > 0 and selector.select(remaining), 'private transfer timed out') - block = os.read(transfer.stdout.fileno(), 1024**2) - if not block: - break - received += len(block) - require(received <= metadata['snapshot_bytes'] and received <= MAX_BYTES, - 'private copy exceeds frozen snapshot size') - destination.write(block) - transfer.wait(timeout=max(1, deadline - time.monotonic())) - finally: - selector.close() - transfer.stdout.close() - if transfer.poll() is None: - transfer.kill() - transfer.wait() - destination.flush() - os.fsync(destination.fileno()) - private(local) - require(transfer.returncode == 0 and local.stat().st_size == metadata['snapshot_bytes'] - and sha(local) == metadata['snapshot_sha256'], 'private copy incomplete/uncertain; retain, no retry') - # Re-read identity/hash after transfer. This is verification, not a write retry. - after = remote(phase, revision, approved, snapshot_identity=prior) - require(after['pod_uid'] == metadata['pod_uid'] and after['snapshot_sha256'] == metadata['snapshot_sha256'], - 'source identity/snapshot changed during transfer') - return {**metadata, 'off_cluster_private_copy_verified': True, 'snapshot_contents_output': False} - - -def offline_verify(journal): - from receipt_safety import counts - snapshot = CUSTODY / 'drill.sqlite' - private(snapshot) - previous = journal.data['phases']['drill-copy']['result'] - require(sha(snapshot) == previous['snapshot_sha256'], 'protected snapshot bytes changed') - identity = docker(['info', '--format', '{{json .Name}}']) - require(identity.returncode == 0 and json.loads(identity.stdout) == 'bnt-lap001', - 'local offline Docker engine identity changed') - listed = docker(['volume', 'ls', '--format', '{{.Name}}']) - require(listed.returncode == 0 and VOLUME not in listed.stdout.splitlines(), - 'disposable volume absence not verified') - nonce = journal.data['phases']['offline-verify']['attempt_id'] - labels = {'informed-decision.case': 'infd-zlib-20261011-r1', - 'informed-decision.use': 'offline-restore-only', 'informed-decision.attempt': nonce} - created = docker(['volume', 'create', '--driver', 'local', '--label', 'informed-decision.case=infd-zlib-20261011-r1', - '--label', 'informed-decision.attempt=' + nonce, - '--label', 'informed-decision.use=offline-restore-only', VOLUME]) - require(created.returncode == 0 and created.stdout.strip() == VOLUME, 'volume creation uncertain; no retry') - inspected = docker(['volume', 'inspect', VOLUME]) - require(inspected.returncode == 0, 'new volume identity unavailable') - volume = json.loads(inspected.stdout) - require(len(volume) == 1 and volume[0]['Name'] == VOLUME and volume[0].get('Labels') == labels - and volume[0].get('Driver') == 'local' and not volume[0].get('Options') - and volume[0].get('Scope') == 'local', 'new isolated volume identity changed') - identity = {key: volume[0][key] for key in ('Name', 'Labels', 'Driver', 'Scope', 'CreatedAt')} - journal.note('offline-verify', 'created_volume_identity', identity) - # One-time UID0/CAP_CHOWN initialization is isolated to the NEW named volume. - # No host/live PVC/config/token mount exists; this detail needs packet review. - initializer = """import hashlib,os,stat,sys -if sys.flags.optimize:raise RuntimeError('optimized interpreter refused') -folder='/restore';expected=EXPECTED -if os.listdir(folder):raise ValueError('new empty private volume required') -os.chmod(folder,0o700) -fd=os.open(folder+'/review.sqlite',os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW|os.O_WRONLY,0o600) -digest=hashlib.sha256();size=0 -with os.fdopen(fd,'wb') as target: - while True: - block=sys.stdin.buffer.read(1024**2) - if not block:break - size+=len(block) - if size>512*1024**2:raise ValueError('private restore size exceeds bound') - target.write(block);digest.update(block) - target.flush();os.fsync(target.fileno()) -if digest.hexdigest()!=expected:raise ValueError('private copied snapshot differs') -os.chown(folder+'/review.sqlite',10001,10001);os.chown(folder,10001,10001) -print('{"all_checks_pass":true,"private_copy_verified":true}') -""".replace('EXPECTED', repr(previous['snapshot_sha256'])) - with snapshot.open('rb') as source: - argv = ['docker', '--config', str(CUSTODY / 'empty-docker-config'), '--host', SOCKET, - 'run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m', - '--user', '0:0', '--cap-drop', 'ALL', '--cap-add', 'CHOWN', - '--security-opt', 'no-new-privileges', '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore', - '--entrypoint', 'python', IMAGE, '-c', initializer] - copied = subprocess.run(argv, stdin=source, text=True, capture_output=True, timeout=150) - json_result(copied) - options = ['run', '--rm', '-i', '--network', 'none', '--read-only', '--cpus', '1', '--memory', '128m', - '--user', '10001:10001', '--cap-drop', 'ALL', '--security-opt', 'no-new-privileges', - '--mount', 'type=volume,src=' + VOLUME + ',dst=/restore'] - readonly = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'], - (CASE / 'sqlite_readonly.py').read_text()) - require(readonly.returncode == 0, 'offline pre-open schema/integrity gate refused; never open Store') - checked = counts(json.loads(readonly.stdout)) - require(checked == previous['snapshot_readonly'], 'frozen snapshot/clone readonly counts differ') - admin = docker(options + ['--entrypoint', 'informed-decision-admin', IMAGE, - 'inspect', '--db', '/restore/review.sqlite']) - require(admin.returncode == 0, 'offline admin result uncertain') - admin_counts = json.loads(admin.stdout) - require(admin_counts == {key: checked[key] for key in ('schema_version', 'lifecycle', 'outbox', 'submissions')}, - 'offline admin changed frozen snapshot counters') - # Admin Store construction must not silently alter schema/integrity/counters. - post = docker(options + ['--entrypoint', 'python', IMAGE, '-', '--db', '/restore/review.sqlite'], - (CASE / 'sqlite_readonly.py').read_text()) - require(post.returncode == 0, 'offline post-admin readonly integrity gate refused') - post_checked = counts(json.loads(post.stdout)) - require(post_checked == checked == previous['snapshot_readonly'], - 'offline admin altered frozen snapshot proof') - private(snapshot) - require(sha(snapshot) == previous['snapshot_sha256'], 'original protected snapshot changed during drill') - return {'all_checks_pass': True, 'offline_volume': VOLUME, 'offline_readonly': checked, - 'offline_post_admin_readonly': post_checked, 'original_snapshot_hash_unchanged': True, - 'created_volume_identity': identity, - 'admin_counts': admin_counts, 'network': 'none', 'runtime_tokens_or_config_mounted': False, - 'service_entrypoint_started': False, 'original_snapshot_retained': True} - - -def cleanup_offline(journal): - inspected = docker(['volume', 'inspect', VOLUME]) - require(inspected.returncode == 0, 'owned disposable volume identity unverified') - value = json.loads(inspected.stdout) - expected = journal.data['phases']['offline-verify']['result']['created_volume_identity'] - require(len(value) == 1 and {key: value[0].get(key) for key in expected} == expected - and not value[0].get('Options'), 'volume ownership/recreation changed') - removed = docker(['volume', 'rm', VOLUME]) - require(removed.returncode == 0 and removed.stdout.strip() == VOLUME - and docker(['volume', 'inspect', VOLUME]).returncode != 0, - 'cleanup outcome ambiguous; do not retry') - return {'all_checks_pass': True, 'only_disposable_offline_volume_removed': VOLUME, - 'native_and_offcluster_original_snapshots_retained': True} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--phase', choices=PHASES, required=True) - parser.add_argument('--root-authority-receipt', type=Path, required=True) - parser.add_argument('--root-native-outcome', type=Path, required=True) - parser.add_argument('--source-revision', required=True) - parser.add_argument('--root-fresh-scan-receipt', type=Path, required=True) - args = parser.parse_args() - require(args.root_authority_receipt == Path('/tmp/security-infd-zlib-root-authority.json') - and args.root_native_outcome == Path('/tmp/security-infd-zlib-native-outcome-latest.json'), - 'fixed supervised receipt paths required') - require(re.fullmatch(r'[0-9a-f]{40}', args.source_revision) is not None, 'exact source commit required') - # Verify all prepared executor/input bytes before reading authority or dispatching. - plan = json.loads((ROOT / 'execution-plan.json').read_text()) - require(plan.get('state') == 'prepared-unapproved' and plan.get('published_artifact_verified') is True, 'unfrozen proposal cannot execute') - require(set(plan['executor_files']) == {'actuator.py', 'authority_journal.py', 'native_program.py', 'receipt_safety.py'}, - 'fixed executor sources required') - require(set(plan['owner_source_files']) == {'deploy/lifecycle/admission.candidate.json', - 'deploy/lifecycle/service.candidate.json', - 'deploy/lifecycle/kustomization.yaml', - 'deploy/lifecycle/registries.candidate.json'}, - 'fixed owner image sources required') - for relative, digest in plan['executor_files'].items(): - target = ROOT / relative - require(not target.is_symlink() and sha(target) == digest, 'executor source bytes changed') - for relative, digest in plan['case_files'].items(): - require(not Path(relative).is_absolute() and '..' not in Path(relative).parts, - 'case source path escaped') - target = CASE / relative - require(not target.is_symlink() and sha(target) == digest, 'case source bytes changed') - for relative, digest in plan['owner_source_files'].items(): - target = OWNER / relative - require(not target.is_symlink() and sha(target) == digest, 'owner image-only source changed') - committed = subprocess.run(['git', '-C', str(OWNER), 'show', args.source_revision + ':' + relative], - capture_output=True) - require(committed.returncode == 0 and hashlib.sha256(committed.stdout).hexdigest() == digest, - 'committed owner render source differs from approved inputs') - head = run(['git', '-C', str(OWNER), 'rev-parse', 'HEAD']) - require(head.returncode == 0 and head.stdout.strip() == args.source_revision, 'owner source HEAD differs') - from authority_journal import Journal, authority, fresh_scan - private(args.root_authority_receipt) - private(args.root_native_outcome) - receipt = json.loads(args.root_authority_receipt.read_text()) - require(receipt.get('native_outcome_sha256') == sha(args.root_native_outcome), 'native outcome bytes changed') - require(receipt.get('executor_plan_sha256') == sha(ROOT / 'execution-plan.json'), - 'root verified executor plan differs') - approved = authority(receipt, json.loads(args.root_native_outcome.read_text()), sha(CASE / 'memo.json'), - args.source_revision, args.phase) - if args.phase in ('release', 'verify'): - require(args.root_fresh_scan_receipt == Path('/tmp/security-infd-zlib-fresh-scan-receipt.json'), 'fixed fresh scan receipt path required') - private(args.root_fresh_scan_receipt) - scan_receipt=json.loads(args.root_fresh_scan_receipt.read_text()) - report_path=Path('/tmp/security-infd-zlib-fresh-scan-full.json');private(report_path) - require(sha(report_path)==scan_receipt.get('full_report_sha256'), 'fresh scan report bytes changed') - fresh_scan(scan_receipt,json.loads(report_path.read_text()),json.loads((CASE/'fresh-scan-contract.json').read_text())) - parent = CUSTODY.parent - private(parent, directory=True) - ignored = run(['git', '-C', str(CUSTODIAN), 'check-ignore', '--no-index', str(CUSTODY / 'drill.sqlite')]) - require(ignored.returncode == 0, 'offcluster custody is not ignored') - if not CUSTODY.exists(): - require(args.phase == 'drill-backup', 'first approved phase must create unique private custody') - os.mkdir(CUSTODY, 0o700) - private(CUSTODY, directory=True) - capacity = os.statvfs(CUSTODY) - require(capacity.f_bavail * capacity.f_frsize >= 2 * MAX_BYTES + 16 * 1024**2, - 'independent protected custody headroom insufficient') - os.makedirs(CUSTODY / 'empty-docker-config', mode=0o700, exist_ok=True) - private(CUSTODY / 'empty-docker-config', directory=True) - require(not list((CUSTODY / 'empty-docker-config').iterdir()), 'public Docker read config must stay empty') - journal = Journal(CUSTODY, {'proposal': plan['proposal_id'], 'source_revision': args.source_revision, - 'memo_sha256': sha(CASE / 'memo.json'), 'plan_sha256': sha(ROOT / 'execution-plan.json')}) - try: - journal.start(args.phase, approved) # fsynced RUNNING before any phase effect. - if args.phase in ('drill-copy', 'cutover-copy'): - result = copy_snapshot(args.phase, args.source_revision, approved, journal) - elif args.phase == 'offline-verify': - result = offline_verify(journal) - elif args.phase == 'cleanup-offline': - result = cleanup_offline(journal) - else: - result = remote(args.phase, args.source_revision, approved, operation_intent=journal.data['phases'].get('rollback' if args.phase == 'rollback-verify' else 'release') if args.phase in ('verify', 'rollback-verify') else None) - journal.finish(args.phase, result) - print(json.dumps(result)) # Only count/hash/identity receipts, never snapshot bytes. - except BaseException as error: - safe = {'all_checks_pass': False, 'phase': args.phase, 'failure_class': type(error).__name__, - 'failure': 'fixed phase outcome uncertain; original evidence retained; no retry', - 'raw_output_withheld': True} - if journal.data['phases'].get(args.phase, {}).get('state') == 'RUNNING': - journal.finish(args.phase, safe) - print(json.dumps(safe)) - raise SystemExit(1) from None - finally: - journal.close() - - -if __name__ == '__main__': - try: - main() - except Exception: - raise SystemExit('supervised execution refused before phase; raw output withheld') from None diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d deleted file mode 100644 index 7c38280..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d +++ /dev/null @@ -1,106 +0,0 @@ -{ - "id": "infd-20261011-zlib-remediation-r1", - "version": 1, - "question": "Approve the exact zlib85091 fixed image-only release, new protected backup/restore compatibility drill and database-preserving rollback?", - "requested_act": "approve", - "binding_level": "organizational", - "brief": "Published owner CI image f558 has zero full-scan findings across44packages; only zlib1.3.2-r0 to r1 changes. Fresh schema5 private backup/isolated clone drill and cutover snapshot precede one Deployment manual prune=false Recreate sync. Separate founder go required; old T04 acceptance/history unchanged. Rollback image763c/Applicationc96 retains latestDB. Release reserves30minutes before Oct12 deadline.", - "binding": { - "principal": { - "id": "pending-human-session", - "kind": "person", - "display_name": "Founder review" - }, - "target": { - "kind": "environment", - "id": "realm:kubernetes/railiance01/informed-decision", - "label": "Informed Decision zlib85091 image-only remediation on railiance01", - "environment": "production" - }, - "terms": "Approve only INFD-WP-0009-T05/zlib85091-r1 trialinfd-zlib-20261011-r1 and exact attached digests: new private drillbackup/copy, isolatedschema5pre/admin/postintegrityandcounts, exactcreatedclonecleanup, freshcutoverbackup/copy, manualfinalsourceDeployment-onlyimagef558/prune=false. ImmutableCM/PVC/config/credentials/audience/resources sealed. Freshrootnativeauthenticated confirmed founderACCEPTsource1 beforeeveryphase; receiptsarebookkeeping/noautonomousgrant. Rollbackonlyimage763c/Applicationc96 preserveslatestDB, separateverification. AllphasesbeginbeforeOct12 23:59:59Z; releasereserves30minutes; newdecisionbeyond. ExistingT04UNCERTAINEvidenceunaltered, nooldhelperamendment/phase retry, noresidualpromotion ordoctrinechange." - }, - "step_kind": "approve", - "packet": [ - { - "item_id": "decision-packet-md", - "label": "decision-packet.md", - "hash": "sha256:c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "item_id": "proposal-json", - "label": "proposal.json", - "hash": "sha256:29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453" - }, - { - "item_id": "native-backup-restore-protocol-md", - "label": "native-backup-restore-protocol.md", - "hash": "sha256:3c903dd82c13273cb61f590b258b059c32e0fd515d7c176ce4fec3e68a77bc7b" - }, - { - "item_id": "native-preflight-json", - "label": "native-preflight.json", - "hash": "sha256:3b6b67a0b4d51ef791e775b528b64aa5a6f14eff629737e67baee7e2f6f60762" - }, - { - "item_id": "rollback-preflight-json", - "label": "rollback-preflight.json", - "hash": "sha256:4c3c3166205808e81326ccdae1b56afef757b0ceb4e6c96a17bf384317aeee4a" - }, - { - "item_id": "published-verification-json", - "label": "published-verification.json", - "hash": "sha256:dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "item_id": "artifact-comparison-json", - "label": "artifact-comparison.json", - "hash": "sha256:a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "item_id": "installed-zlib-proof-json", - "label": "installed-zlib-proof.json", - "hash": "sha256:6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "item_id": "sealed-live-deployment-json", - "label": "sealed-live-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "sealed-native-dependencies-json", - "label": "sealed-native-dependencies.json", - "hash": "sha256:042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "item_id": "candidate-deployment-json", - "label": "candidate-deployment.json", - "hash": "sha256:487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "item_id": "rollback-deployment-json", - "label": "rollback-deployment.json", - "hash": "sha256:f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "item_id": "execution-bundle-json", - "label": "execution-bundle.json", - "hash": "sha256:2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b" - } - ], - "highlights": [ - { - "id": "protected-drill-and-recreate", - "item_id": "decision-packet-md", - "note": "This new T05 decision authorizes protected real-data backup/copy and no-network same-schema restore drill plus brief Recreate image release. Scan0 is actual published44package result, not installed acceptance. Rollback preserves latestDB; priorfailedT04evidence retained. No oldapprovalreuse. Allphase starts expireOct12; release reserves30minutesforrecovery; noautomaticextension.", - "required_ack": true, - "severity": "warning", - "locator": { - "kind": "section", - "heading": "Requested decision" - } - } - ], - "sealed": false, - "approval_id": null, - "approval_binding_digest": null -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ecddcb00127774a17d5cf553417c60d1b6fb753b6eb91a49dabb7fc52678595e b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ecddcb00127774a17d5cf553417c60d1b6fb753b6eb91a49dabb7fc52678595e deleted file mode 100644 index 73657f4..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/ecddcb00127774a17d5cf553417c60d1b6fb753b6eb91a49dabb7fc52678595e +++ /dev/null @@ -1,14 +0,0 @@ -{ - "kind": "source-only-native-authoring-packet", - "memo_id": "infd-20261011-zlib-remediation-r1", - "source_memo_version": 1, - "memo_sha256": "ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d", - "intake_sha256": "6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844", - "lifecycle_commands_sha256": "508f9bf9f11a35b8fc494e1d66e8f11aa0338df4aa9d7115a80e2dabdae81b55", - "register_bytes": 193685, - "document_count": 13, - "documents_hashes_verified": true, - "execution_bundle_lossless_verified": true, - "native_submitted": false, - "founder_approval": false -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29 deleted file mode 100644 index ee40b50..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29 +++ /dev/null @@ -1,1003 +0,0 @@ -[ - { - "repo": "informed-decision", - "path": "deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/deploy/lifecycle/admission.candidate.json", - "original_sha256": "92382a56b349c3b895e5806e975082527bb99d1a001140e69d857e5b0391fa55", - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/deploy/lifecycle/kustomization.yaml", - "original_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47", - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/deploy/lifecycle/registries.candidate.json", - "original_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e", - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "informed-decision", - "path": "deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/deploy/lifecycle/service.candidate.json", - "original_sha256": "38b78b9cbee5ade7750a58d5156ca4c5773352e947acb304ee714b59e749ceb4", - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "eb58dee98934fef0fd916c7807eea1d600d71ee35cda9b9fbbc5564e42560461" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "9441650c58e156a485810290ec86028b1a21655960e8196cc60d710f51b51788" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "fd38385e3687ec6f9886d5025b394463c252b8198b0426c56f95740139eb7e9b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "a1d29ea6822be12817b2d60109cec44b061d167f147b10f9aa352529e1a4c475" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "original_sha256": null, - "staged_sha256": "2ba25f7d3efbb1095e0a0dd7bbb577f76af05c425cb928f9a1c7bc52ea9f532b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/fresh-scan-contract.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-contract.json", - "original_sha256": null, - "staged_sha256": "b13806a7e1464454e3817e1e62b56b0fa07fe2fddbd94d7fdc530e2c3dd9e3d2" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "original_sha256": null, - "staged_sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/fresh-scan-report-fixture.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-report-fixture.json", - "original_sha256": null, - "staged_sha256": "30607043a4ca44e42024b63659ae10993c14c7a04e7c5685255ccafe4d3fb55c" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "original_sha256": null, - "staged_sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "original_sha256": null, - "staged_sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "original_sha256": null, - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "original_sha256": null, - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "original_sha256": null, - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "original_sha256": null, - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/archive-map.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/archive-map.json", - "original_sha256": null, - "staged_sha256": "b511ecca1f21626495ae07725f5761e251d3d4beca9e3be6e7cdfdfbfe2073bc" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/original-manifest.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/original-manifest.json", - "original_sha256": null, - "staged_sha256": "81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "original_sha256": null, - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "original_sha256": null, - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "original_sha256": null, - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/deploy/lifecycle/service.candidate.json", - "original_sha256": null, - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "c570bade38479af93f08f717fcebb328a0db38e5b58bd0e7983681bc5ae41b7a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "dabeda33ffe52989337e214dc6e6d3ef5f04797569145faf2ff69024b101ec80" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "2aa9bae40752922264e53382d898e3083b8d229b185329266942bdd397013108" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "750a4386277dc79c3213e6aed17ae5686332bfd587a55e5e87ebf3776a63ab0c" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "7458222049d8df2f01f2ce359b5864ae41cd73054ee59659ca8cd8ac17cf73ac" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution-bundle.json", - "original_sha256": null, - "staged_sha256": "09b0a8ed6c3c7d1e94f5f703a2d5834ca26fc166cfc8f4f9b4511ab70ec16ac9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/fresh-scan-receipt-template.json", - "original_sha256": null, - "staged_sha256": "92bbf8a891c7ef78df48430901219d8d07073cba74112a307a6d8bdec509e8dd" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/archive-map.json", - "original_sha256": null, - "staged_sha256": "44be75acb94d8125efb9416dea91059aeeb79625d23c7959bdc099a95c26a9ca" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/original-manifest.json", - "original_sha256": null, - "staged_sha256": "7ac1d698cab2dfbde6bc0a84bcadd2c3229d24f4a4a59125fe72b2856fa82e07" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/admission.candidate.json", - "original_sha256": null, - "staged_sha256": "6c76cedd0abc4a4261cfcb69c6597611c282bd4fed8b6aaf77aff04fdf942529" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/kustomization.yaml", - "original_sha256": null, - "staged_sha256": "1bbce81bc6758434354157f8601534320c5d0290587908fd15a6d25e3fa57f47" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/registries.candidate.json", - "original_sha256": null, - "staged_sha256": "9bad6db5fbc2178d31b04120016a41830c3552fbb9e2517b718f9bbe87cf649e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/deploy/lifecycle/service.candidate.json", - "original_sha256": null, - "staged_sha256": "383332cd4fd27a8b36e63a4d79addc43eb68615f742328d4577bfc2b467e2278" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/artifact-comparison.json", - "original_sha256": null, - "staged_sha256": "a1c5d9e249dc00600449998360faf6e8da0206b568c76d155602c4645fa5fce4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-deployment.json", - "original_sha256": null, - "staged_sha256": "487f834cc86dcbc7a04c2392bde072ecaf87dc116b4baa16c35eb9c599ad5c95" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/candidate-runtime.json", - "original_sha256": null, - "staged_sha256": "d58e60bdc69458fe42af8248e02acd924b3b605c828877352a7d9a3953e90124" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/change_guard.py", - "original_sha256": null, - "staged_sha256": "b3ee2b89de14e204efedd016d3900b6e31fcc9f0ea0f3125d3a4794d938ca201" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/check_installed.py", - "original_sha256": null, - "staged_sha256": "a34a51be9c9f2b16a06a88173b5c6c492c9957fe021cf412a86a1737730640ad" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/decision-packet.md", - "original_sha256": null, - "staged_sha256": "763f30c1433d1ca872b029519ec46ad1b97c47d60b7ea0eea0275a2c6b647dc3" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/actuator.py", - "original_sha256": null, - "staged_sha256": "741b490bdb315896f05f2e91404c02765517dc1beea81fa590017fccafd299b4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/authority_journal.py", - "original_sha256": null, - "staged_sha256": "040144321c69622c223a8437832a3c5a4a9595fc37774fde5ff0ad676828ebf0" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/execution-plan.json", - "original_sha256": null, - "staged_sha256": "9b458e2b4d418525987b6cb1b9a160c4b1a2c621625c1b5c571ea5c77e9d4361" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/native_program.py", - "original_sha256": null, - "staged_sha256": "fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/receipt_safety.py", - "original_sha256": null, - "staged_sha256": "5c54373f2dc4b064008225c186efb1d5afdeec36d15350bda0a12ded97602d1b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/execution/test_execution.py", - "original_sha256": null, - "staged_sha256": "aa86921fe37fcff382676490f937af2e62bd6472a6b4700c862aaf3ce4ab8225" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "3c76c6a8f95dcb55183583a937a4428b745a5c918643de8f2a2bc11dee846d98" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "48b0f0baf4efc239111411ad7d1c291f8eceaa0ddf1414dac73d49ccdb3bbe70" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/initial-7ac1d698/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "original_sha256": null, - "staged_sha256": "8154c90745d860b2984e4616f0bb1c35889b07b7ff8b841e7430db7592a15b7a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "original_sha256": null, - "staged_sha256": "196e2cef9ba38f075b458f164f05b5df594d188ee84b210744ef94c31f753294" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "15b3fc9c708593adb52d9eb240c1a6b55d5ac43b1637b8047499f15e525e368f" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "0614e2118927180d18f4a3fa833990a38e27340234a952abdb1d2ca93e7602a4" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "original_sha256": null, - "staged_sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "a992d799bc07e8fbf05b894a5b184b11092f0041ee68187e59a23c5395b263e9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "original_sha256": null, - "staged_sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "original_sha256": null, - "staged_sha256": "4a529f57b886260f8d1b109e06e487a936fa1eb5eeb79dbbd9e4e7a05f9ae2e9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/initial-81adb9e1/owner-snapshot/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "original_sha256": null, - "staged_sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/installed-zlib-proof.json", - "original_sha256": null, - "staged_sha256": "6af62f86f50ac185fab94163c24ac8150ead69e62f8d825b0dce1e75a5a57b68" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/intake.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/intake.json", - "original_sha256": null, - "staged_sha256": "6f173d6b257e9f0e4f360390e5846380eba7ecd5cadfd55c22b998bd7e7de844" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/lifecycle-commands.json", - "original_sha256": null, - "staged_sha256": "508f9bf9f11a35b8fc494e1d66e8f11aa0338df4aa9d7115a80e2dabdae81b55" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/memo.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/memo.json", - "original_sha256": null, - "staged_sha256": "ec470183fd6a6a315ec0e35d5841eb1e26cf04d58c5b3fdc13b6f34b1621f62d" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/native-backup-restore-protocol.md", - "original_sha256": null, - "staged_sha256": "3c903dd82c13273cb61f590b258b059c32e0fd515d7c176ce4fec3e68a77bc7b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/native-preflight-full.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/native-preflight-full.json", - "original_sha256": null, - "staged_sha256": "56637994dfdae46a6f9ab442c2ff9b259212650b35eb98d896c5e837f01ce2f9" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/native-preflight.json", - "original_sha256": null, - "staged_sha256": "3b6b67a0b4d51ef791e775b528b64aa5a6f14eff629737e67baee7e2f6f60762" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/post-ready-native-outcomes-command.py", - "original_sha256": null, - "staged_sha256": "ce88aca5e2d6cd5ab7533898bc9b246bd159482c4acccb0afe4c451ee88f57fa" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/preparation-failure-history.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/preparation-failure-history.json", - "original_sha256": null, - "staged_sha256": "51b9983d2a806240f562dcb951955fc8360c181e6d08a8dfdcba95361c6dd596" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/proposal.json", - "original_sha256": null, - "staged_sha256": "29de1dbddce09008a50516ec43b100bc2a1e08ace86a748a259fe5695e937453" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/published-verification.json", - "original_sha256": null, - "staged_sha256": "dd4eecf0c103dca7922094a8beeb99b77ae96fd1d5fe532e37bd833daa02614e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/rollback-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-preflight-full.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight-full.json", - "original_sha256": null, - "staged_sha256": "0188799d352e4d49d5ca29d6e197742d02e96a4b0b399df1f478e41b25292eb5" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/rollback-preflight.json", - "original_sha256": null, - "staged_sha256": "4c3c3166205808e81326ccdae1b56afef757b0ceb4e6c96a17bf384317aeee4a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/runtime-receipt.json", - "original_sha256": null, - "staged_sha256": "6442676dcfd941f0a05a4cb79a02dea839fcd332d82b2154c15cf6db68de5889" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/scan-receipt.json", - "original_sha256": null, - "staged_sha256": "aaaa91061bb036e6d228b1c1bd4333578dc948cb2bb5ac8ba269429121e7321a" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sealed-live-deployment.json", - "original_sha256": null, - "staged_sha256": "f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sealed-native-dependencies.json", - "original_sha256": null, - "staged_sha256": "042327a036ed254cf95dbeb0c056ac25b03a6a844b95a332c992df2943691369" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/source-commit-proof.json", - "original_sha256": null, - "staged_sha256": "b0811da2d3bd5938733f0078e5fec8e19a6d9752c4277b80bfe9837ed72c6a3b" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/sqlite_readonly.py", - "original_sha256": null, - "staged_sha256": "9093c64f7f0e39b249da9fefdf5aaf4e4258bc31e7a27fd888479ce329a20509" - }, - { - "repo": "informed-decision", - "path": "docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/docs/evidence/2026-10-11-zlib-image-release/submission-proof.json", - "original_sha256": null, - "staged_sha256": "ecddcb00127774a17d5cf553417c60d1b6fb753b6eb91a49dabb7fc52678595e" - }, - { - "repo": "informed-decision", - "path": "workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "staged_path": "/tmp/infd-zlib-release-refined-stage/informed-decision/workplans/INFD-WP-0009-lifecycle-consumer-pin-repair.md", - "original_sha256": "af785afbf188184b72563c10813663d44117df7cb7394ecb1b9a25cbdfea67a4", - "staged_sha256": "112e0aa642a7f48edf8073eee180b62d5ab6000aa4a6197ea6ffc10d7087beb0" - } -] diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35 deleted file mode 100644 index 5c0ac3a..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f586a511c1921490185bab95334f27de6d3cca20b348df20d18bcb9257d7ae35 +++ /dev/null @@ -1,21 +0,0 @@ -# T05 zlib85091 release — preliminary, unapproved - -This is a distinct proposal/trial for existing INFD-WP-0009-T05. The actual T04 source2 acceptance does not authorize it. Previous R1 copy and R2 verify UNCERTAIN journal states remain untouched. INFD-IN-0010 remains an unpromoted general verifier residual; this new task-specific verifier is a prerequisite of active T05, not an amendment of the old approved helper. - -Exact intended image f5588f496b1f267d07cdc9f5208e6f2f241f74b739c23924b03702dec13a5c23 was built by admitted owner CI11534/run4 from 9b4bb25d325698ae543e7e8a8a3c3592db1f58fc. Publication is not installed acceptance. Final packet requires independent published-image scan/runtime/filesystem/source proof and fresh read-only native projection/one-object dry-run evidence. Until all final inputs and independent owner acknowledgments are frozen, execution-plan state remains preliminary and the actuator refuses before any authority, journal or native action. - -After a NEW exact confirmed founder acceptance, root freshly authenticates fixed-origin native source-version1 outcomes before each phase. Receipt files are supervised bookkeeping, not signed grants or autonomy. Wrong task, source, memo/version, stale/pending/agent disposition, old T04 acceptance or changed inputs refuse. Root retains exact approved source revision/digests and action results in supervision. Approval does not promote any agent. - -Fixed phase order: drill-backup → drill-copy → offline-verify → cleanup-offline → cutover-backup → cutover-copy → release → verify. New private native /data/private/zlib85091-r1, root .private-backups/20261011-infd-zlib-r1 and local volume infd-zlib-r1-restore-20261011 are distinct from all T04 custody. New private journal intent is fsynced RUNNING before dispatch; no attempted phase replay. Fixed output schema contains only counts, hashes, object identity and boolean results; raw errors and private content are withheld. Existing native snapshots/database/sidecars are never removed or restored over current data. - -Fresh backup inspection uses copied explicit-close SQLite helpers: live and offline clone readers are mode=ro/query_only and WAL-aware, before any Store/admin open. Immutable reader is used only for freshly closed hash-bound private snapshot with absent/zero WAL frames and stable identity/metadata/digest before/after read/copy. Main and sidecars remain retained. Schema must equal5, integrity ok, and exact snapshot/isolated clone counts match before/admin/after. The detached local clone has network none, readonly root, no service/runtime config/credential mounts; only narrowly scoped UID0 CAP_CHOWN initializer precedes UID10001 reads/admin. Original private copy is hash-stable. Cleanup removes only the disposable volume whose nonce/labels/creation metadata match this trial, before cutover. - -Each snapshot is bounded512MiB. At most two new snapshots occupy each native/offcluster custody (1GiB payload each); the disposable offcluster clone adds512MiB while present, plus16MiB sidecar/metadata reserve. New native drill phase checks free space for both; local custody checks two snapshot bounds plus reserve. Existing T04 retention consumes additional pre-existing space. EUR0 recurring infrastructure estimate; human/compute time unpriced. - -Cutover takes a fresh native backup and exact offcluster copy. Release then requires latest schema5 readonly proof, sealed Pod/Deployment/config/PVC custody and same baseline image763c/current Application sourcec96, a healthy single target92.205.62.239 node and fresh requested CPU ≤ allocatable capacity. Recreate and resources remain unchanged. Server dry-run/client diff must show only the image. Application UID/resourceVersion/source CAS dispatches manual exact final revision, initiatedBy custodian-codex, explicit prune=false and only apps/Deployment informed-decision in its namespace. No whole-Application sync, namespace/config/credential/resource change or pruning. - -Verification reads imageID/readiness/owner chain/exact configured Pod fields, schema5/integrity/metadata counts, installed policy hash and synthetic purpose refusals, health200, installed fixed zlib and exact image proof. Real PDP-gated lifecycle readback is a separate root authenticated observation and not replaced by synthetic checks. Argo3.5.3 omits false prune in operation status: missing or exactfalse is accepted only together with this journal's COMPLETE explicitfalse request, exact source/resources/initiation, operation start after our durable attempt, bounded finish and one Synced Deployment result. True/null/string/prior/foreign/extra-resource results refuse. - -Rollback → rollback-verify is separate and permitted only after a recorded release attempt. It switches only Application source back to c96 and Deployment image763c; it never restores an old snapshot over the latest database. Recovery dispatch works even if candidate Pod is absent/unready and never depends on candidate DB inspection. Recovery verification separately requires Ready1/old image/current schema5/data-custody and exact recorded scoped completion. All phases must begin before2026-10-12T23:59:59Z; release must begin at least30minutes earlier. Beyond expiry stop and obtain a new founder decision; no automatic extension or emergency grant. A failed/uncertain attempt requires informed recovery rather than retry. - -T05 remains progress until actual installed acceptance and native rescan prove the finding absent. No Risk Engine grade is assigned. Only then may owner close T05/risk and consider finishing WP9, with existing residuals live first. diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e deleted file mode 100644 index b58daa6..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/f8a9a07abc2eb52cdf6bcdba2d9f79dec073145c31d0444a9e0fe1147e42cc4e +++ /dev/null @@ -1,286 +0,0 @@ -{ - "apiVersion": "apps/v1", - "kind": "Deployment", - "metadata": { - "annotations": { - "argocd.argoproj.io/tracking-id": "informed-decision:apps/Deployment:informed-decision/informed-decision", - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "name": "informed-decision", - "namespace": "informed-decision", - "uid": "a588e10a-90d2-4883-975e-6dd8b8feca92" - }, - "spec": { - "progressDeadlineSeconds": 600, - "replicas": 1, - "revisionHistoryLimit": 10, - "selector": { - "matchLabels": { - "app.kubernetes.io/name": "informed-decision" - } - }, - "strategy": { - "type": "Recreate" - }, - "template": { - "metadata": { - "annotations": { - "informed-decision.coulomb.social/admission": "approved; INFD-WP-0007 cutover" - }, - "labels": { - "app.kubernetes.io/component": "review", - "app.kubernetes.io/name": "informed-decision" - } - }, - "spec": { - "automountServiceAccountToken": false, - "containers": [ - { - "env": [ - { - "name": "INFD_KEYCAPE_ISSUER", - "value": "https://kc.coulomb.social" - }, - { - "name": "INFD_CONTAINER_CONFIG", - "value": "/configuration/runtime.json" - } - ], - "image": "forgejo.coulomb.social/coulomb/informed-decision@sha256:763c36402c8be7cc4c1bd1a61623d1aff9116d52241877425b45a7018cc0883a", - "imagePullPolicy": "IfNotPresent", - "livenessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "name": "informed-decision", - "ports": [ - { - "containerPort": 8080, - "name": "http", - "protocol": "TCP" - }, - { - "containerPort": 8081, - "name": "workload", - "protocol": "TCP" - } - ], - "readinessProbe": { - "failureThreshold": 3, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "resources": { - "limits": { - "cpu": "500m", - "memory": "256Mi" - }, - "requests": { - "cpu": "5m", - "memory": "64Mi" - } - }, - "securityContext": { - "allowPrivilegeEscalation": false, - "capabilities": { - "drop": [ - "ALL" - ] - }, - "readOnlyRootFilesystem": true - }, - "startupProbe": { - "failureThreshold": 12, - "httpGet": { - "path": "/healthz", - "port": "http", - "scheme": "HTTP" - }, - "periodSeconds": 10, - "successThreshold": 1, - "timeoutSeconds": 2 - }, - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "volumeMounts": [ - { - "mountPath": "/data", - "name": "data" - }, - { - "mountPath": "/run/informed-decision", - "name": "runtime" - }, - { - "mountPath": "/tmp", - "name": "tmp" - }, - { - "mountPath": "/configuration", - "name": "configuration", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/policy", - "name": "caller", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/audit", - "name": "audit", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/kubernetes", - "name": "kubernetes-api", - "readOnly": true - }, - { - "mountPath": "/workload-registry", - "name": "workload-registry", - "readOnly": true - }, - { - "mountPath": "/var/run/secrets/informed-decision/requester", - "name": "sitting-requester", - "readOnly": true - } - ] - } - ], - "dnsPolicy": "ClusterFirst", - "restartPolicy": "Always", - "schedulerName": "default-scheduler", - "securityContext": { - "fsGroup": 10001, - "fsGroupChangePolicy": "OnRootMismatch", - "runAsGroup": 10001, - "runAsNonRoot": true, - "runAsUser": 10001, - "seccompProfile": { - "type": "RuntimeDefault" - } - }, - "serviceAccount": "review", - "serviceAccountName": "review", - "terminationGracePeriodSeconds": 45, - "volumes": [ - { - "name": "data", - "persistentVolumeClaim": { - "claimName": "informed-decision-data" - } - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "runtime" - }, - { - "emptyDir": { - "medium": "Memory", - "sizeLimit": "16Mi" - }, - "name": "tmp" - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-runtime-6c221878d051" - }, - "name": "configuration" - }, - { - "name": "caller", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "audience": "flex-auth", - "expirationSeconds": 3600, - "path": "token" - } - } - ] - } - }, - { - "name": "audit", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "token", - "path": "token" - } - ], - "secretName": "informed-decision-audit" - } - }, - { - "name": "kubernetes-api", - "projected": { - "defaultMode": 288, - "sources": [ - { - "serviceAccountToken": { - "expirationSeconds": 600, - "path": "token" - } - }, - { - "configMap": { - "items": [ - { - "key": "ca.crt", - "path": "ca.crt" - } - ], - "name": "kube-root-ca.crt" - } - } - ] - } - }, - { - "configMap": { - "defaultMode": 292, - "name": "informed-decision-workload-registry" - }, - "name": "workload-registry" - }, - { - "name": "sitting-requester", - "secret": { - "defaultMode": 288, - "items": [ - { - "key": "client-secret", - "path": "client-secret" - } - ], - "optional": false, - "secretName": "informed-decision-sitting-requester" - } - } - ] - } - } - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/faeb2b4a9f57a3db029197d4488b528d0a93b6464bf60ade9b0ee189f5a9a95a b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/faeb2b4a9f57a3db029197d4488b528d0a93b6464bf60ade9b0ee189f5a9a95a deleted file mode 100644 index 2fb1d2e..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/faeb2b4a9f57a3db029197d4488b528d0a93b6464bf60ade9b0ee189f5a9a95a +++ /dev/null @@ -1,57 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-INFD-ZLIB-NATIVE-R3", - "work_record": "RPF-IN-0009", - "role": "railiance-platform technical owner only", - "status": "changes-requested-actual-provider-api-compatibility", - "release_manifest_sha256": "3f1ce7ed678a457f73ab6486c7e81ef3360daf4f7ec1196f3031c3ffaace57ec", - "release_paths": 481, - "all481_hashes_and_plan_bindings_verified": true, - "synthetic_tests_passed": 36, - "additional_synthetic_public_checks_passed": 12, - "prior_blockers_fixed": [ - "source dispatch and exact snapshot/intent payloads", - "full immutable artifact scan coverage/freshness", - "ephemeral Pod refusal before effects", - "freeform output filtering", - "explicit bounded proposed manual outage recovery" - ], - "remaining_required_refinement": [ - { - "id": "actual-provider-contract", - "source": "post-ready-native-outcomes-command.py", - "cause": "safe_outcome_metadata requires topmemo_id which actual outcomes neverreturns; actual provider also supports reviewNone/intake not_submitted and optional/state prepared/in_flight/comment fields not covered by fixture.", - "effect": "Actual successful postReady authenticated PDP path may be refused; synthetic metadata tests cannot establish native API compatibility.", - "required": "Fixed request memo/version/origin plus exactHTTP200 correlation and bounded real shape/count-only receipt; avoid detailed disposition state/actor/private data exports and do not infer authority from preservation proof. Exercise owner actualoutcomes-shaped fixture including absent topmemo_id and optionalrows/state, private sentinels withheld." - } - ], - "primary_owner_source_inspection": [ - { - "path": "/home/worsch/informed-decision/informed_decision/lifecycle_review.py", - "sha256": "b6d69be6c56f3e2f411501083e199195190df6c740a01b07e82a476268ca3629" - }, - { - "path": "/home/worsch/informed-decision/informed_decision/lifecycle_service.py", - "sha256": "2dde9280214e68e5e7a7b48d703886947c2a46139ada86e8ff2283a9d4b55a70" - } - ], - "actual_source_semantics": [ - "lifecycle_review.outcomes returns only review/dispositions (lines119,123,133), with optionalreviewNone/not_submitted.", - "lifecycle_service.outcomes calls _authorize for outcomes then current() after query; normal HTTP200 under that fixed current path is distinct PDP-gated preservation evidence, not founder disposition or advisory verdict." - ], - "history": { - "prior123_fullsnapshot_and_manifest_map_preserved": true, - "prior_manifest_sha256": "aa941510645a655190c967adb4a04f89dfae9c625981a27552dea8a27216df7f", - "unissued_draft_path": "unissued-final-technical-draft.json", - "unissued_draft_never_returned_as_ack": true, - "original67_snapshot_absence_retained": true - }, - "scope_limits": { - "technical_ack": false, - "native_action": false, - "provider_requests": false, - "database_access": false, - "owner_writes": false, - "human_approval": false, - "T05_complete": false - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20 b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20 deleted file mode 100644 index 04cb285..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/changes-requested-9d0ff679/files/fcf8fc3593910b2e99dd51d159a30aa9b853c40ff00c566a620d20eaadc8af20 +++ /dev/null @@ -1,2 +0,0 @@ -"""Fixed T05 remote script; source preparation never dispatches it.""" -PROGRAM = '\nimport copy,datetime,hashlib,json,os,re,subprocess,sys\nif sys.flags.optimize:raise RuntimeError(\'optimized interpreter refused\')\npayload=json.loads(__PAYLOAD__)\nexec(__GUARD__)\nphase=payload[\'phase\']\nif phase not in (\'drill-backup\',\'cutover-backup\',\'drill-copy\',\'cutover-copy\',\'release\',\'verify\',\'rollback\',\'rollback-verify\'):raise ValueError(\'fixed native phase required\')\ndeadline=datetime.datetime.fromisoformat(\'2026-10-12T23:59:59+00:00\')\ndef require(value,reason):\n if not value:raise ValueError(reason)\nrequire(datetime.datetime.now(datetime.timezone.utc)0 and size<=512*1024**2 and free>=size*(2 if action==\'drill-backup\' else 1)+16*1024**2,\'bounded native snapshot capacity required\')\n if action==\'drill-backup\':\n require(not os.path.lexists(folder),\'native case directory must be unique\')\n os.mkdir(folder,0o700);os.chmod(folder,0o700)\n private(folder,True)\n filename=\'drill.sqlite\' if action==\'drill-backup\' else \'cutover.sqlite\';output=folder+\'/\'+filename\n require(not os.path.lexists(output),\'snapshot output must be unique\')\n admin=subprocess.run([\'informed-decision-admin\',\'backup\',\'--db\',db,\'--output\',output],capture_output=True,text=True,timeout=120)\n require(admin.returncode==0 and json.loads(admin.stdout)=={\'status\':\'backed_up\',\'consistent_snapshot\':True},\'backup completion not verified; do not retry\')\n private(output)\n for suffix in (\'-wal\',):\n require(not os.path.exists(output+suffix) or os.stat(output+suffix).st_size==0,\'snapshot has WAL frames; immutable read refused\')\n digest=hashlib.sha256()\n with open(output,\'rb\') as stream:\n for block in iter(lambda:stream.read(1024**2),b\'\'):digest.update(block)\n frozen_digest=digest.hexdigest()\n snapshot=counts(inspect_closed_snapshot(output,frozen_digest))\n size=os.stat(output).st_size;require(0763/c96 Deployment-only latest schema5 DB retaining manual supervised recovery.", - "bound": "<=30minutes after actual release and before2026-10-12T23:59:59Z, checked again immediately beforepatch.", - "evidence": "Independentroot-confirmed exact native accepted receipt cached before release, privateimmutablehash/journal/attempt pins; fresh transport-unavailable or502/503/504 and actualfailedcandidateplacement.", - "forbidden": [ - "backup/release fallback", - "401/403", - "explicitrevoked", - "conflict", - "identity/scope mismatch", - "healthy candidate", - "foreign trial or futurecustomer reuse" - ], - "revocation_limitation_disclosed": true, - "current_revocation_verified_during_outage": false, - "autonomous_or_signed_grant": false, - "recovered_verify": "Separate COMPLETE rollback/original outageanchor and actual763Ready1/data/custody/current operation; never implies PDP acceptance or taskclosure." - }, - "history": { - "prior_review_manifest_sha256": "9d0ff679c63c58465b940ed72a258b8f9a12f413da97aad01ec7e311db55a445", - "exact130_files_plus_manifest_losslessly_preserved": true, - "dedup_distinct_blobs": 82, - "path_map_entries": 130, - "archive": "changes-requested-9d0ff679", - "prior_unissued_draft_remains_explicitly_superseded": true, - "original67_fullsnapshot_absence_not_fabricated": true - }, - "remaining_gates": [ - "Attach exact two owner ACKs and proposed checklist row19 only; confirm narrow final packet rebinding before human post.", - "Commit prepared source and retain exact actual revision/fourblob proof; NEW founder confirmed disposition for full proposal including bounded outage exception.", - "Fresh exact source/native custody/admission/headroom/current authority before each applicable phase; actual current immutable artifact fullscan/DB freshness before release and verify.", - "Actual native installed/schema/health/current canonical PDP preservation proof before T05/risk closure; preparation and synthetic tests do not establish completion." - ], - "scope_limits": { - "human_approval": false, - "native_action": false, - "backup_restore_database_access": false, - "credentials_read": false, - "owner_source_write": false, - "push": false, - "T05_complete": false, - "risk_closed": false, - "other_owner_role": false - } -} diff --git a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/postcontract-validation.json b/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/postcontract-validation.json deleted file mode 100644 index d44e8ba..0000000 --- a/docs/evidence/2026-10-11-infd-zlib-native-release-owner-review/postcontract-validation.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "source_manifest_sha256": "767c6e93adfa1a844b1e25947f33e782b087a747452575f056b3df25bc88873c", - "source_paths": 509, - "all_source_and_executor_case_hashes_match": true, - "synthetic_test_methods_passed": 38, - "additional_checks_passed": 18, - "actual_owner_source_outcomes_shape_exercised_in_mocked_repository_fixture": true, - "provider_synthetic_private_fields_never_emitted": true, - "all_four_render_hashes_unchanged_from_previous_review": true, - "authority_journal_actuator_native_program_unchanged_from_481": true, - "current_public_scan_matches_exact_full_contract": true, - "native_or_private_database_access": false -} diff --git a/docs/evidence/2026-10-11-issue-core-credential/contract-needs-owner-proof.json b/docs/evidence/2026-10-11-issue-core-credential/contract-needs-owner-proof.json deleted file mode 100644 index 3f06597..0000000 --- a/docs/evidence/2026-10-11-issue-core-credential/contract-needs-owner-proof.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "work_record": "RPF-WP-0044-T04", - "scope_approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", - "repository": "coulomb/rapp-issue-core", - "read_only": true, - "openbao_path": "platform/operators/argocd/repositories/rapp-issue-core", - "destination": "argocd/rapp-issue-core-repository", - "project": "railiance-tenants", - "url": "ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git", - "wrap_ttl_seconds": 60, - "new_network_scope": false, - "owner_review_ref": null, - "ssh_egress_existing_proof_ref": null, - "runtime_identity_ref": null, - "rollback_ref": null, - "ssh_known_hosts_sha256": null, - "expires_at": "2026-10-12T23:59:59+00:00", - "execution_mode": "attended-founder-bounded" -} diff --git a/docs/evidence/2026-10-11-issue-core-credential/contract-sealed-2026-10-20.json b/docs/evidence/2026-10-11-issue-core-credential/contract-sealed-2026-10-20.json deleted file mode 100644 index 40fc679..0000000 --- a/docs/evidence/2026-10-11-issue-core-credential/contract-sealed-2026-10-20.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "destination": "argocd/rapp-issue-core-repository", - "execution_mode": "attended-founder-bounded", - "expires_at": "2026-10-27T23:59:59+00:00", - "new_network_scope": false, - "openbao_path": "platform/operators/argocd/repositories/rapp-issue-core", - "owner_review_ref": "railiance-platform:docs/evidence/2026-10-11-issue-core-credential/owner-review.json@sha256:8835eafe5b46809d9c57e1d998673e3e01fe03b0b92f61d69e98b94dc8791ab5", - "project": "railiance-tenants", - "read_only": true, - "repository": "coulomb/rapp-issue-core", - "rollback_ref": "railiance-platform:docs/evidence/2026-10-11-issue-core-credential/owner-review.json#rollback; docs/runbooks/rpf-wp-0044-t04-attended.md#rollback", - "runtime_identity_ref": "attended founder Platform OpenBao OIDC/MFA via `warden access openbao-platform-admin-login --exec` enclosing nested `warden access forgejo-admin-api-token --exec`; owner-review.json runtime_authority_limits; D2 89f1058f", - "scope_approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", - "ssh_egress_existing_proof_ref": "the-custodian:docs/evidence/2026-10-11-issue-core-public-transport/public-authority-observation.json (repository_server_tcp_30022_reachable=true, no new network scope); railiance-platform:docs/evidence/2026-10-11-issue-core-transport/2026-10-20-verify-r1.json (host pin + project admission verified live 2026-10-20)", - "ssh_known_hosts_sha256": "7b6114ff6b96c9923d3c6139fdbaec02bb4e1fd93e6c9c9ac5064beb4570c652", - "url": "ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git", - "work_record": "RPF-WP-0044-T04", - "wrap_ttl_seconds": 60 -} diff --git a/docs/evidence/2026-10-11-issue-core-credential/execution-journal-r1.json b/docs/evidence/2026-10-11-issue-core-credential/execution-journal-r1.json deleted file mode 100644 index 1ce63a8..0000000 --- a/docs/evidence/2026-10-11-issue-core-credential/execution-journal-r1.json +++ /dev/null @@ -1 +0,0 @@ -{"approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", "audit_device_count": 1, "audit_enabled_metadata": true, "consumed_wrap_replay_denied_400": true, "contract_sha256": "0fd6d6fa0aeeffbdbfb7216b05be2c1aeae8719c606c18c70bc8d17a552b0970", "custody_version": 1, "custody_write_request_id": "8143b728-e58f-6a3f-5641-676b0215cf90", "destination": "argocd/rapp-issue-core-repository", "execution_mode": "attended-founder-bounded", "operatorpath_capability_mode": "scoped", "private_key_rotation_max_days": 180, "provider_key_id": 3, "public_key_sha256": "b3a1d8a1e476b98b9a5ce9b0664c669d7e2d493636695a979fff252456873003", "schema": "argocd-issue-core-credential/v1", "secret_values_observed_by_agent": false, "state": "delivered_not_adopted", "task": "RPF-WP-0044-T04", "unwrap_request_id": "94feaf03-8299-21c9-85ea-571ac90f5af3", "updated_at": "2026-10-20T10:37:03.699952+00:00", "wrap_accessor": "ATJnD2dxd9QI5BNCbzt2pxLV", "wrap_creation_path": "platform/data/operators/argocd/repositories/rapp-issue-core", "wrap_ttl_seconds": 60} diff --git a/docs/evidence/2026-10-11-issue-core-credential/installed-cli-proof.json b/docs/evidence/2026-10-11-issue-core-credential/installed-cli-proof.json deleted file mode 100644 index 3087f03..0000000 --- a/docs/evidence/2026-10-11-issue-core-credential/installed-cli-proof.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "synthetic_only": true, - "no_actual_credentials_or_services": true, - "installed_cli_combined_path_stdin_value_preserved": true, - "installed_cli_cas0_body_verified": true, - "list404_class": { - "exit": 2, - "stdout": "{}", - "stderr_empty": true - }, - "consumed_unwrap_error_class": { - "exit": 2, - "http_code": 400, - "exact_error": "wrapping token is not valid or does not exist" - }, - "help_confirmed": [ - "kv put field=- stdin semantics", - "cas=0 create-only", - "unwrap with no token argument uses current authenticated token" - ] -} diff --git a/docs/evidence/2026-10-11-issue-core-credential/owner-review.json b/docs/evidence/2026-10-11-issue-core-credential/owner-review.json deleted file mode 100644 index 0aa4813..0000000 --- a/docs/evidence/2026-10-11-issue-core-credential/owner-review.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "schema": "argocd-issue-core-fixed-command-review/v1", - "task": "RPF-WP-0044-T04", - "approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", - "second_founder_approval_required": false, - "source_scope_already_approved": "Dedicated Argo reader, only coulomb/rapp-issue-core read-only, exact operator custody path", - "command": "BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 warden access openbao-platform-admin-login --exec -- python3 scripts/provision_argocd_issue_core_repository.py --contract --contract-sha256 --journal ", - "runtime": "Existing attended Platform-admin login encloses nested warden forgejo-admin-api-token --exec; silent outer fds suppress proxy inherited fetch errors; credential injection stays inside child. Kubectl destination is fixed SSH railiance01 native API, never workstation kubecontext.", - "runtime_authority_limits": "Current admin lane is interim proxy, not native issuer. It requires caller OpenBao identity, and operatorpath create/read/update capability plus parent metadata list. Source plan autonomous label is not runtime proof. No exact delegated operatorpath identity found in source; absent such a proven identity the outer OIDC/MFA login is Orange. This command is explicitly attended-founder-bounded, not autopilot. Root capability is accepted only in this declared attended mode; refs are manual review attestations, not native approval tokens.", - "preflight": [ - "exact contract references existing D2 and owner review, expiry and hash", - "node public IP exact 92.205.62.239 before trust", - "actual Argo SSH host CM hash matches trusted provider pin for [forgejo.coulomb.social]:30022", - "existing SSH egress proof, no new network scope", - "AppProject existing sourceRepos admits exact SSH repository", - "operatorpath create/read/update capabilities and fresh custody", - "fresh exact destination Secret and no matching provider deploykey" - ], - "custody": "tmpfs generated ed25519 private key -> provider receives public key only -> OpenBao CAS0 exact SSH_PRIVATE_KEY -> 60-second response wrap -> exactly one unwrap with child token env -> Secret create encrypted SSH stdin, no last-applied annotation Installed Bao CLI actual synthetic HTTP fixture confirmed combined-path SSH_PRIVATE_KEY=- stdin and CAS0 request, LIST404 -> exit2 empty JSON {}, and unwrap400 specific native error.", - "receipt": "Fixed non-secret stages, provider key ID, public-key hash, Bao write/unwrap request IDs and wrap accessor, custody version. All raw provider/CLI stdout/stderr discarded. Successful issuance says delivered_not_adopted; Argo adoption/health remains independent. Exact wrap creation path verified/retained, enabled audit-device metadata/count checked before grant, consumed-token replay must return native OpenBao 400 specific invalid-wrapping-token error; transport/403/unrelated 400 are not proof.", - "replay": "Exclusive O_NOFOLLOW journal; any preexisting journal refuses. Pending/ambiguous stages retained uncertain_do_not_replay. Do not rerun; reconcile IDs/custody/destination first. No automatic second key, unwrap, retry, deletion or broad credential fallback.", - "rollback": "Owner verifies journal provider key ID + exact fixed repo and public hash, revokes only that deploykey through same admin exec lane. Remove only exact matched Secret version, soft-delete only exact new operatorpath custody version, restore Application previous Git transport/revision and manual sync; never delete shared policy/store or another repo key. Revocation/removal is separate reviewed owner recovery; issuer performs no destructive fallback.", - "limitations": [ - "No native SSH-key expiry; maximum 180-day lifecycle rotation remains owner task.", - "No automatic rollback command executes.", - "No actual credential read, issue, unwrap, Secret create, or adoption executed.", - "Contract hostpin, egress, ownerreview/runtime/rollback refs must be filled and reviewed before command runs." - ], - "checks": { - "unit_tests": 9, - "passed": true, - "installed_cli_synthetic_put_stdin_cas0": true, - "installed_cli_synthetic_list404_signature_verified": true, - "installed_cli_synthetic_unwrap400_signature_verified": true - } -} diff --git a/docs/evidence/2026-10-11-issue-core-transport/2026-10-20-dry-run-r1.json b/docs/evidence/2026-10-11-issue-core-transport/2026-10-20-dry-run-r1.json deleted file mode 100644 index c339100..0000000 --- a/docs/evidence/2026-10-11-issue-core-transport/2026-10-20-dry-run-r1.json +++ /dev/null @@ -1 +0,0 @@ -{"action": "dry-run", "approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", "captured_at": "2026-10-20T06:50:49.729226+00:00", "failure": "target_preimage_changed", "mutation_attempted": false, "passed": false, "plan_sha256": "2e3094f4970f600b13b145386eab323f41e344e379a9e8b83282178e387a5920", "resources": [], "schema": "argocd-public-transport-receipt/v1", "secret_or_credential_calls": false, "task": "RPF-WP-0044-T04"} diff --git a/docs/evidence/2026-10-11-issue-core-transport/2026-10-20-verify-r1.json b/docs/evidence/2026-10-11-issue-core-transport/2026-10-20-verify-r1.json deleted file mode 100644 index 639793a..0000000 --- a/docs/evidence/2026-10-11-issue-core-transport/2026-10-20-verify-r1.json +++ /dev/null @@ -1 +0,0 @@ -{"action": "verify", "approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", "captured_at": "2026-10-20T06:51:26.635213+00:00", "mutation_attempted": false, "passed": true, "plan_sha256": "2e3094f4970f600b13b145386eab323f41e344e379a9e8b83282178e387a5920", "resources": [{"after_sha256": "d581e92dd00161e8624bb3ee9e172f31f103ea9655975aaa0cc3ed32323b1dd2", "before_sha256": "54ecbee860c225158ae45e2bd93cba8b5b0418d27c2b06f8f1487b1c7453a8da", "field": "/data/ssh_known_hosts", "kind": "configmap", "name": "argocd-ssh-known-hosts-cm", "uid": "e1481984-dc5d-4fcc-9f2a-118c054865b7", "verified": true}, {"after_sha256": "d07d4296f3cb0c726dd30ea2c908982f197a7aaaa48862560e3776960e4fac5d", "before_sha256": "636ee06479d4be73d956cdbeb9d4bddb24cd463bbbb70de10b8cfd043791eb31", "field": "/spec/sourceRepos", "kind": "appproject", "name": "railiance-tenants", "uid": "f899f3ec-2c55-4cd0-9cb0-b2b4c6eb9be2", "verified": true}], "schema": "argocd-public-transport-receipt/v1", "secret_or_credential_calls": false, "task": "RPF-WP-0044-T04"} diff --git a/docs/evidence/2026-10-11-issue-core-transport/transport-plan.json b/docs/evidence/2026-10-11-issue-core-transport/transport-plan.json deleted file mode 100644 index e36173d..0000000 --- a/docs/evidence/2026-10-11-issue-core-transport/transport-plan.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "schema": "argocd-public-transport/v1", - "task": "RPF-WP-0044-T04", - "approval_ref": "89f1058f-33bc-47a8-8a49-0b44d426c67e", - "abort_owner": "railiance-platform with railiance-enablement", - "diff_ref": "source-manifest.json", - "rollback": "Run same helper --action rollback using reviewed plan; guarded exact after-to-before fields only. Revert corresponding owner source patches too.", - "namespace": "argocd", - "target": "railiance01", - "baseline_sha256": "28074ce931b8362d0aab101317aef2decae69af527f5b0ee0699b5877ff89bb3", - "public_authority_observation_sha256": "264a71f4bf8672dfb3ac5d75ed7a7f467ae17d2f6c19aaf55893ff13718e3093", - "provider_pod_uid": "bc771b51-e9b8-456d-b59c-e7041704389d", - "repo_server_tcp_30022_verified": true, - "resources": [ - { - "kind": "configmap", - "name": "argocd-ssh-known-hosts-cm", - "uid": "e1481984-dc5d-4fcc-9f2a-118c054865b7", - "path": "/data/ssh_known_hosts", - "before": "# This file was automatically generated by hack/update-ssh-known-hosts.sh. DO NOT EDIT\n[ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n[ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n[ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\nbitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=\nbitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO\nbitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\ngitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=\ngitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf\ngitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9\nssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\nvs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\n", - "after": "# This file was automatically generated by hack/update-ssh-known-hosts.sh. DO NOT EDIT\n[ssh.github.com]:443 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\n[ssh.github.com]:443 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\n[ssh.github.com]:443 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\nbitbucket.org ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBPIQmuzMBuKdWeF4+a2sjSSpBK0iqitSQ+5BM9KhpexuGt20JpTVM7u5BDZngncgrqDMbWdxMWWOGtZ9UgbqgZE=\nbitbucket.org ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIazEu89wgQZ4bqs3d63QSMzYVa0MuJ2e2gKTKqu+UUO\nbitbucket.org ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDQeJzhupRu0u0cdegZIa8e86EG2qOCsIsD1Xw0xSeiPDlCr7kq97NLmMbpKTX6Esc30NuoqEEHCuc7yWtwp8dI76EEEB1VqY9QJq6vk+aySyboD5QF61I/1WeTwu+deCbgKMGbUijeXhtfbxSxm6JwGrXrhBdofTsbKRUsrN1WoNgUa8uqN1Vx6WAJw1JHPhglEGGHea6QICwJOAr/6mrui/oB7pkaWKHj3z7d1IC4KWLtY47elvjbaTlkN04Kc/5LFEirorGYVbt15kAUlqGM65pk6ZBxtaO3+30LVlORZkxOh+LKL/BvbZ/iRNhItLqNyieoQj/uh/7Iv4uyH/cV/0b4WDSd3DptigWq84lJubb9t/DnZlrJazxyDCulTmKdOR7vs9gMTo+uoIrPSb8ScTtvw65+odKAlBj59dhnVp9zd7QUojOpXlL62Aw56U4oO+FALuevvMjiWeavKhJqlR7i5n9srYcrNV7ttmDw7kf/97P5zauIhxcjX+xHv4M=\ngithub.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEmKSENjQEezOmxkZMy7opKgwFB9nkt5YRrYMjNuG5N87uRgg6CLrbo5wAdT/y6v0mKV0U2w0WZ2YB/++Tpockg=\ngithub.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMqqnkVzrm0SdG6UOoqKLsabgH5C9okWi0dh2l9GKJl\ngithub.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCj7ndNxQowgcQnjshcLrqPEiiphnt+VTTvDP6mHBL9j1aNUkY4Ue1gvwnGLVlOhGeYrnZaMgRK6+PKCUXaDbC7qtbW8gIkhL7aGCsOr/C56SJMy/BCZfxd1nWzAOxSDPgVsmerOBYfNqltV9/hWCqBywINIR+5dIg6JTJ72pcEpEjcYgXkE2YEFXV1JHnsKgbLWNlhScqb2UmyRkQyytRLtL+38TGxkxCflmO+5Z8CSSNY7GidjMIZ7Q4zMjA2n1nGrlTDkzwDCsw+wqFPGQA179cnfGWOWRVruj16z6XyvxvjJwbz0wQZ75XK5tKSb7FNyeIEs4TT4jk+S4dhPeAUC5y+bDYirYgM4GC7uEnztnZyaVWQ7B381AK4Qdrwt51ZqExKbQpTUNn+EjqoTwvqNj4kqx5QUCI0ThS/YkOxJCXmPUWZbhjpCg56i+2aB6CmK2JGhn57K5mj0MNdBXA4/WnwH6XoPWJzK5Nyu2zB3nAZp+S5hpQs+p1vN1/wsjk=\ngitlab.com ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFSMqzJeV9rUzU4kWitGjeR4PWSa29SPqJ1fVkhtj3Hw9xjLVXVYrU9QlYWrOLXBpQ6KWjbjTDTdDkoohFzgbEY=\ngitlab.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAfuCHKVTjquxvt6CM6tdG4SLp1Btn/nOeHHE5UOzRdf\ngitlab.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsj2bNKTBSpIYDEGk9KxsGh3mySTRgMtXL583qmBpzeQ+jqCMRgBqB98u3z++J1sKlXHWfM9dyhSevkMwSbhoR8XIq/U0tCNyokEi/ueaBMCvbcTHhO7FcwzY92WK4Yt0aGROY5qX2UKSeOvuP4D6TPqKF1onrSzH9bx9XUf2lEdWT/ia1NEKjunUqu1xOB/StKDHMoX4/OKyIzuS0q/T1zOATthvasJFoPrAjkohTyaDUz2LN5JoH839hViyEG82yB+MjcFV5MU3N1l1QL3cVUCh93xSaua1N85qivl+siMkPGbO5xR/En4iEY6K2XPASUEMaieWVNTRCtJ4S8H+9\nssh.dev.azure.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\nvs-ssh.visualstudio.com ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC7Hr1oTWqNqOlzGJOfGJ4NakVyIzf1rXYd4d7wo6jBlkLvCA4odBlL0mDUyZ0/QUfTTqeu+tm22gOsv+VrVTMk6vwRU75gY/y9ut5Mb3bR5BV58dKXyq9A9UeB5Cakehn5Zgm6x1mKoVyf+FFn26iYqXJRgzIZZcZ5V6hrE0Qg39kZm4az48o0AUbf6Sp4SLdvnuMa2sVNwHBboS7EJkm57XQPVU3/QpyNLHbWDdzwtrlS+ez30S3AdYhLKEOxAG8weOnyrtLJAUen9mTkol8oII1edf7mWWbWVf0nBmly21+nZcmCTISQBtdcyPaEno7fFQMDD26/s0lfKob4Kw8H\n[forgejo.coulomb.social]:30022 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQC7jadWwHX2j1aeXSonkdwP8CI5j1a+ihzj6kq7697h+8B5X2SpYBvwOi2brYmgoiccyapB2aowmctMLLTtrhMaARtvnZPeoFgPhBrdHaUhHdmTeSg7MbRBu5D8yYTt3CwDxF1+MUFE+NuRhCXRHQLbLCqTLlU9hgZV4xuqsv71lvNHzcF3vMcolTBtdVzTsgCfeV9J8wQMXTC7rFfVuRi5fC6m994KN+vt94VHIZcZwFgjdCEZ4uo7U07Fdp3MT6WMqgLftc9ewBL/p+i5BtzVkF725d5QMx6TL6vLrC7X8lrkUWQL+oy5CeUrnOnsyiuwbYwlFVLsdypa2WbdlHtkllBXVD2IdTxeu038BAjWBXkN1WCGEXWJvj829I8FzObnO+QoOsbwdmGSTFqBX7+2vSymbEHtNcS0/0jk5xUvdnvFRvkzDvhQjMgiF8LIVCk4zWTBeWPQ5r5lLooFn8B1hmsgqHVSd2DXMPUMOuTQRTdxpNicNkBlwEQSwFYSCf3vL+x2e2pIx4YvkLD+1bEwNwB8HIuzF1Vex7Cjbo07pTwxhTJ52ZFQNkja/LqyAzO0bOEb9MF6KIxXKS/qL5C2cf8kHB5eXbiAwbv3jkxVzBiYPEMJYxO0pJKaKK8Cvs8cT2MYsxQkly1MENsvf15eOHqg26CPz/PiruHkiifhXw==\n", - "owner": "railiance-enablement" - }, - { - "kind": "appproject", - "name": "railiance-tenants", - "uid": "f899f3ec-2c55-4cd0-9cb0-b2b4c6eb9be2", - "path": "/spec/sourceRepos", - "before": [ - "https://forgejo.coulomb.social/coulomb/*.git", - "https://gitea.coulomb.social/coulomb/*.git" - ], - "after": [ - "https://forgejo.coulomb.social/coulomb/*.git", - "https://gitea.coulomb.social/coulomb/*.git", - "ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git" - ], - "owner": "railiance-platform" - } - ] -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/README.md b/docs/evidence/2026-10-11-openbao-adoption-proof/README.md deleted file mode 100644 index 31b9b72..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Verified OpenBao Argo adoption proof - -Run from the workstation: - -```sh -python3 scripts/argocd_adoption_proof.py --live --source-root . --observation observation.json --receipt proof.json -python3 -m unittest discover -s tests -p 'test_argocd_adoption_proof.py' -``` - -The helper reads nodes, the exact Application, ClusterSecretStore, ExternalSecret, -Argo configuration and controller version. It never reads a Secret. Raw command -output and errors are captured and discarded. Git archive renders the exact -pin in a disposable directory; only a public ClusterSecretStore is admitted. -Server API dry-runs and native diffs make no persistent native change. - -The final proof is verified: both native diffs return zero and both same-version -API dry-runs have no changed field paths after exact Argo tracking injection. -The observed 3.5.3 controller and configuration select annotation mode. Tracking -uses the Application destination namespace even for a cluster-scoped object; -no instance label is injected. This follows the upstream [v3.5.3 resource tracking -implementation](https://github.com/argoproj/argo-cd/blob/v3.5.3/util/argo/resource_tracking.go) -and the [3.0 default change](https://argo-cd.readthedocs.io/en/release-3.5/operator-manual/upgrading/2.14-3.0/). -Only the tracking annotation is injected; live fields are never copied into the -desired source. Raw source diffs remain diagnostic. Structured JSON pointers -identify only tracking/last-applied differences. Unknown or substantive fields -refuse proof; neither parser ambiguity nor a manual ignore can grant success. - -Check `status: verified`, never receipt presence. Five-minute stale and future -observations are refused. The successful observation is a point-in-time proof, -not authority or evidence of subsequent automation. Earlier failed receipts are -retained. Thirteen fixture tests include exact identity/pin/destination mismatch, -tracking/render semantics, proving age, readiness changes, extra tracked objects, -unknown/substantive/native diff failures and protected-error-output canaries. -Fixtures are synthetic; native acceptance is the separate final proof. diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/argo-normalization-diagnosis.json b/docs/evidence/2026-10-11-openbao-adoption-proof/argo-normalization-diagnosis.json deleted file mode 100644 index 5843546..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/argo-normalization-diagnosis.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "status": "verified-diagnostic", - "normalized_client_dry_run_changed_paths": [], - "same_version_read": true, - "diffs": { - "client": 0, - "server": 0, - "argo_manager_server": 0, - "argo_manager_client": 0 - }, - "manager_kubectl_client_side_apply": false, - "manager_argocd_controller": false, - "manager_argocd_application_controller": false -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/drift-metadata.json b/docs/evidence/2026-10-11-openbao-adoption-proof/drift-metadata.json deleted file mode 100644 index 954c241..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/drift-metadata.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "spec_equals_pin": true, - "desired_labels_present": true, - "tracking_empty_namespace": false, - "tracking_default_namespace": false, - "tracking_argocd_namespace": false, - "tracking_identity_prefix_matches": true, - "tracking_name_matches": true -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/dry-run-path-diagnosis.json b/docs/evidence/2026-10-11-openbao-adoption-proof/dry-run-path-diagnosis.json deleted file mode 100644 index ef5738a..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/dry-run-path-diagnosis.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "status": "verified-diagnostic", - "client_dry_run_changed_paths": [ - "/apiVersion", - "/metadata/annotations/argocd.argoproj.io~1tracking-id", - "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration" - ], - "server_dry_run_changed_paths": [ - "/apiVersion", - "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration" - ] -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-argo-normalized.json b/docs/evidence/2026-10-11-openbao-adoption-proof/observation-argo-normalized.json deleted file mode 100644 index 4615bb9..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-argo-normalized.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "application": { - "automated_present": false, - "condition_types": [ - "OrphanedResourceWarning" - ], - "destination": { - "namespace": "external-secrets", - "server": "https://kubernetes.default.svc" - }, - "health": "Healthy", - "name": "openbao-secretstore", - "operation": { - "finishedAt": "2026-09-21T17:09:18Z", - "phase": "Succeeded", - "startedAt": "2026-09-21T17:09:16Z" - }, - "operation_revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "pending_operation": false, - "project": "railiance-platform-addons", - "resources": [ - { - "group": "external-secrets.io", - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": null, - "status": "Synced" - } - ], - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "source": { - "path": "argocd/platform-addons/openbao-secretstore", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "d2dbc19c254247652c49fda8721c80d53bca206a" - }, - "sync": "Synced", - "sync_prune": false, - "sync_strategy_apply": true, - "uid": "f2932505-e649-4b99-b505-4789ed21301f" - }, - "captured_at": "2026-10-11T19:22:22.892821+00:00", - "estate_ip": "92.205.62.239", - "ready_checks": [ - { - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": "", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:49:59Z", - "status": "True" - } - ], - "tracking_matches": true, - "uid": "99563b77-f30f-451b-ac86-16b1b162c6a9" - }, - { - "kind": "ExternalSecret", - "name": "issue-core-runtime", - "namespace": "issue-core", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:54:20Z", - "status": "True" - } - ], - "tracking_matches": null, - "uid": "490dca37-6364-480a-a757-c3a266d32e47" - } - ], - "tracking_settings": { - "controller_version": "v3.5.3", - "installation_id_absent": true, - "instance_label_key": "app.kubernetes.io/instance", - "method": "annotation" - }, - "pinned_render_diff": { - "client_side_exit_code": 0, - "exit_code": 0, - "normalization": "argocd-v3-annotation-tracking-destination-namespace", - "normalized_client_changed_paths": [], - "normalized_render_sha256": "94e014dd61b81e09c8b137acae1b797f8976b8607fcaa46f54674d4d407b9843", - "normalized_server_changed_paths": [], - "output_suppressed": true, - "path": "argocd/platform-addons/openbao-secretstore", - "raw_client_changed_paths": [ - "/metadata/annotations/argocd.argoproj.io~1tracking-id", - "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration" - ], - "raw_client_exit_code": 1, - "raw_server_changed_paths": [ - "/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration" - ], - "raw_server_exit_code": 1, - "render_resource_count": 1, - "render_sha256": "35585d1b005272ec75e55cb4a68386bc515032693c2c70f6e76f68cad4e29fb3", - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "same_api_version": true, - "tracking_settings": { - "controller_version": "v3.5.3", - "installation_id_absent": true, - "instance_label_key": "app.kubernetes.io/instance", - "method": "annotation" - } - } -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-both-diffs.json b/docs/evidence/2026-10-11-openbao-adoption-proof/observation-both-diffs.json deleted file mode 100644 index 5b9e44a..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-both-diffs.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "application": { - "automated_present": false, - "condition_types": [ - "OrphanedResourceWarning" - ], - "destination": { - "namespace": "external-secrets", - "server": "https://kubernetes.default.svc" - }, - "health": "Healthy", - "name": "openbao-secretstore", - "operation": { - "finishedAt": "2026-09-21T17:09:18Z", - "phase": "Succeeded", - "startedAt": "2026-09-21T17:09:16Z" - }, - "operation_revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "pending_operation": false, - "project": "railiance-platform-addons", - "resources": [ - { - "group": "external-secrets.io", - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": null, - "status": "Synced" - } - ], - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "source": { - "path": "argocd/platform-addons/openbao-secretstore", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "d2dbc19c254247652c49fda8721c80d53bca206a" - }, - "sync": "Synced", - "sync_prune": false, - "sync_strategy_apply": true, - "uid": "f2932505-e649-4b99-b505-4789ed21301f" - }, - "captured_at": "2026-10-11T19:16:28.318163+00:00", - "estate_ip": "92.205.62.239", - "ready_checks": [ - { - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": "", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:49:59Z", - "status": "True" - } - ], - "tracking_matches": true, - "uid": "99563b77-f30f-451b-ac86-16b1b162c6a9" - }, - { - "kind": "ExternalSecret", - "name": "issue-core-runtime", - "namespace": "issue-core", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:54:20Z", - "status": "True" - } - ], - "tracking_matches": null, - "uid": "490dca37-6364-480a-a757-c3a266d32e47" - } - ], - "pinned_render_diff": { - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "path": "argocd/platform-addons/openbao-secretstore", - "render_sha256": "35585d1b005272ec75e55cb4a68386bc515032693c2c70f6e76f68cad4e29fb3", - "render_resource_count": 1, - "exit_code": 1, - "output_suppressed": true, - "changed_top_fields": [ - "unknown" - ], - "client_side_exit_code": 1 - } -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-classified.json b/docs/evidence/2026-10-11-openbao-adoption-proof/observation-classified.json deleted file mode 100644 index 374826f..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-classified.json +++ /dev/null @@ -1,82 +0,0 @@ -{ - "application": { - "automated_present": false, - "condition_types": [ - "OrphanedResourceWarning" - ], - "destination": { - "namespace": "external-secrets", - "server": "https://kubernetes.default.svc" - }, - "health": "Healthy", - "name": "openbao-secretstore", - "operation": { - "finishedAt": "2026-09-21T17:09:18Z", - "phase": "Succeeded", - "startedAt": "2026-09-21T17:09:16Z" - }, - "operation_revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "pending_operation": false, - "project": "railiance-platform-addons", - "resources": [ - { - "group": "external-secrets.io", - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": null, - "status": "Synced" - } - ], - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "source": { - "path": "argocd/platform-addons/openbao-secretstore", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "d2dbc19c254247652c49fda8721c80d53bca206a" - }, - "sync": "Synced", - "sync_prune": false, - "sync_strategy_apply": true, - "uid": "f2932505-e649-4b99-b505-4789ed21301f" - }, - "captured_at": "2026-10-11T19:15:47.973008+00:00", - "estate_ip": "92.205.62.239", - "ready_checks": [ - { - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": "", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:49:59Z", - "status": "True" - } - ], - "tracking_matches": true, - "uid": "99563b77-f30f-451b-ac86-16b1b162c6a9" - }, - { - "kind": "ExternalSecret", - "name": "issue-core-runtime", - "namespace": "issue-core", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:54:20Z", - "status": "True" - } - ], - "tracking_matches": null, - "uid": "490dca37-6364-480a-a757-c3a266d32e47" - } - ], - "pinned_render_diff": { - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "path": "argocd/platform-addons/openbao-secretstore", - "render_sha256": "35585d1b005272ec75e55cb4a68386bc515032693c2c70f6e76f68cad4e29fb3", - "render_resource_count": 1, - "exit_code": 1, - "output_suppressed": true, - "changed_top_fields": [ - "unknown" - ] - } -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-normalized.json b/docs/evidence/2026-10-11-openbao-adoption-proof/observation-normalized.json deleted file mode 100644 index 2f11706..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/observation-normalized.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "application": { - "automated_present": false, - "condition_types": [ - "OrphanedResourceWarning" - ], - "destination": { - "namespace": "external-secrets", - "server": "https://kubernetes.default.svc" - }, - "health": "Healthy", - "name": "openbao-secretstore", - "operation": { - "finishedAt": "2026-09-21T17:09:18Z", - "phase": "Succeeded", - "startedAt": "2026-09-21T17:09:16Z" - }, - "operation_revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "pending_operation": false, - "project": "railiance-platform-addons", - "resources": [ - { - "group": "external-secrets.io", - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": null, - "status": "Synced" - } - ], - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "source": { - "path": "argocd/platform-addons/openbao-secretstore", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "d2dbc19c254247652c49fda8721c80d53bca206a" - }, - "sync": "Synced", - "sync_prune": false, - "sync_strategy_apply": true, - "uid": "f2932505-e649-4b99-b505-4789ed21301f" - }, - "captured_at": "2026-10-11T19:15:05.030899+00:00", - "estate_ip": "92.205.62.239", - "ready_checks": [ - { - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": "", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:49:59Z", - "status": "True" - } - ], - "tracking_matches": true, - "uid": "99563b77-f30f-451b-ac86-16b1b162c6a9" - }, - { - "kind": "ExternalSecret", - "name": "issue-core-runtime", - "namespace": "issue-core", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:54:20Z", - "status": "True" - } - ], - "tracking_matches": null, - "uid": "490dca37-6364-480a-a757-c3a266d32e47" - } - ], - "pinned_render_diff": { - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "path": "argocd/platform-addons/openbao-secretstore", - "render_sha256": "35585d1b005272ec75e55cb4a68386bc515032693c2c70f6e76f68cad4e29fb3", - "render_resource_count": 1, - "exit_code": 1, - "output_suppressed": true - } -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/observation.json b/docs/evidence/2026-10-11-openbao-adoption-proof/observation.json deleted file mode 100644 index da17f73..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/observation.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "application": { - "automated_present": false, - "condition_types": [ - "OrphanedResourceWarning" - ], - "health": "Healthy", - "name": "openbao-secretstore", - "operation": { - "finishedAt": "2026-09-21T17:09:18Z", - "phase": "Succeeded", - "startedAt": "2026-09-21T17:09:16Z" - }, - "operation_revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "pending_operation": false, - "project": "railiance-platform-addons", - "resources": [ - { - "group": "external-secrets.io", - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": null, - "status": "Synced" - } - ], - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "source": { - "path": "argocd/platform-addons/openbao-secretstore", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "d2dbc19c254247652c49fda8721c80d53bca206a" - }, - "sync": "Synced", - "sync_prune": false, - "sync_strategy_apply": true, - "uid": "f2932505-e649-4b99-b505-4789ed21301f" - }, - "captured_at": "2026-10-11T19:12:20.205677+00:00", - "estate_ip": "92.205.62.239", - "ready_checks": [ - { - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": "", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:49:59Z", - "status": "True" - } - ], - "tracking_matches": false, - "uid": "99563b77-f30f-451b-ac86-16b1b162c6a9" - }, - { - "kind": "ExternalSecret", - "name": "issue-core-runtime", - "namespace": "issue-core", - "ready": [ - { - "lastTransitionTime": "2026-08-19T19:54:20Z", - "status": "True" - } - ], - "tracking_matches": null, - "uid": "490dca37-6364-480a-a757-c3a266d32e47" - } - ], - "pinned_render_diff": { - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "path": "argocd/platform-addons/openbao-secretstore", - "render_sha256": "35585d1b005272ec75e55cb4a68386bc515032693c2c70f6e76f68cad4e29fb3", - "render_resource_count": 1, - "exit_code": 1, - "output_suppressed": true - } -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-argo-normalized-final.json b/docs/evidence/2026-10-11-openbao-adoption-proof/proof-argo-normalized-final.json deleted file mode 100644 index 667ccb4..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-argo-normalized-final.json +++ /dev/null @@ -1,19 +0,0 @@ -{ - "schema_version": "argocd-adoption-proof/v1", - "status": "verified", - "application": "openbao-secretstore", - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "captured_at": "2026-10-11T19:22:22.892821+00:00", - "verified_at": "2026-10-11T19:23:40.325446+00:00", - "proving_seconds": 1735984.892821, - "resource_count": 1, - "ready_count": 2, - "manual_apply_prune_off": true, - "warnings": [ - "OrphanedResourceWarning" - ], - "native_mutation": false, - "verifier_sha256": "c058c0ea646bcc3410e0d26db6b42a5471a8fa45a1df62b9ec85f2bf7d03f660", - "contract_sha256": "427084e4f40d34778534397a99379789e355a3b3573b97a61762da8e43abc31e", - "observation_sha256": "01e0850e496fe9234e91d883f1b4c7e7a574dd14bed60ac4585520bf1ddb79b2" -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-argo-normalized.json b/docs/evidence/2026-10-11-openbao-adoption-proof/proof-argo-normalized.json deleted file mode 100644 index 2ed24a3..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-argo-normalized.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "schema_version": "argocd-adoption-proof/v1", - "status": "verified", - "application": "openbao-secretstore", - "revision": "d2dbc19c254247652c49fda8721c80d53bca206a", - "captured_at": "2026-10-11T19:22:22.892821+00:00", - "verified_at": "2026-10-11T19:22:27.223821+00:00", - "proving_seconds": 1735984.892821, - "resource_count": 1, - "ready_count": 2, - "manual_apply_prune_off": true, - "warnings": [ - "OrphanedResourceWarning" - ], - "native_mutation": false, - "contract_sha256": "427084e4f40d34778534397a99379789e355a3b3573b97a61762da8e43abc31e", - "observation_sha256": "01e0850e496fe9234e91d883f1b4c7e7a574dd14bed60ac4585520bf1ddb79b2" -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-both-diffs.json b/docs/evidence/2026-10-11-openbao-adoption-proof/proof-both-diffs.json deleted file mode 100644 index c980dda..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-both-diffs.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "schema_version": "argocd-adoption-proof/v1", - "status": "unverified", - "error": "adoption proof incomplete; raw output suppressed", - "native_mutation": false -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-classified.json b/docs/evidence/2026-10-11-openbao-adoption-proof/proof-classified.json deleted file mode 100644 index c980dda..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-classified.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "schema_version": "argocd-adoption-proof/v1", - "status": "unverified", - "error": "adoption proof incomplete; raw output suppressed", - "native_mutation": false -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-normalized.json b/docs/evidence/2026-10-11-openbao-adoption-proof/proof-normalized.json deleted file mode 100644 index c980dda..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/proof-normalized.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "schema_version": "argocd-adoption-proof/v1", - "status": "unverified", - "error": "adoption proof incomplete; raw output suppressed", - "native_mutation": false -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/proof.json b/docs/evidence/2026-10-11-openbao-adoption-proof/proof.json deleted file mode 100644 index c980dda..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/proof.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "schema_version": "argocd-adoption-proof/v1", - "status": "unverified", - "error": "adoption proof incomplete; raw output suppressed", - "native_mutation": false -} diff --git a/docs/evidence/2026-10-11-openbao-adoption-proof/validation.json b/docs/evidence/2026-10-11-openbao-adoption-proof/validation.json deleted file mode 100644 index 6ba2ed7..0000000 --- a/docs/evidence/2026-10-11-openbao-adoption-proof/validation.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "schema_version": "argocd-adoption-validation/v1", - "workplan": "RPF-WP-0044", - "task": "RPF-WP-0044-T03", - "native_mutation": false, - "task_closed": true, - "tests": { - "passed": 13, - "failed": 0 - }, - "compilation": "passed", - "native_proof": "verified", - "proof_ref": "proof-argo-normalized-final.json", - "observation_ref": "observation-argo-normalized.json", - "native_diff": { - "canonical_server_side": 0, - "canonical_client_side": 0, - "canonical_client_changed_paths": [], - "canonical_server_changed_paths": [], - "raw_source_server_side": 1, - "raw_source_client_side": 1 - }, - "normalization": "Argo v3 annotation tracking with Application destination namespace fallback; no live fields copied to desired source", - "upstream_refs": [ - "https://github.com/argoproj/argo-cd/blob/v3.5.3/util/argo/resource_tracking.go", - "https://argo-cd.readthedocs.io/en/release-3.5/operator-manual/upgrading/2.14-3.0/" - ], - "observed_runtime": { - "controller_version": "v3.5.3", - "installation_id_absent": true, - "instance_label_key": "app.kubernetes.io/instance", - "method": "annotation" - }, - "verifier_sha256": "c058c0ea646bcc3410e0d26db6b42a5471a8fa45a1df62b9ec85f2bf7d03f660", - "retained_failure_history": [ - "proof.json", - "proof-normalized.json", - "proof-classified.json", - "proof-both-diffs.json" - ], - "secret_reads": 0, - "subprocess_output_suppressed": true, - "validated_at": "2026-10-11T19:24:51.574009+00:00" -} diff --git a/docs/evidence/2026-10-11-policy-nexus-adoption-readonly-precheck.json b/docs/evidence/2026-10-11-policy-nexus-adoption-readonly-precheck.json deleted file mode 100644 index ce51cce..0000000 --- a/docs/evidence/2026-10-11-policy-nexus-adoption-readonly-precheck.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "task": "RPF-WP-0043-T03", - "collected_at": "2026-10-11T19:05Z", - "collected_by": "claude-code (agt-claude-railiance01 via k3s-api-railiance01 tunnel), read-only", - "cluster_node": "239.62.205.92.host.secureserver.net (railiance01)", - "step1_helm": { - "release": "policy-nexus", "namespace": "policy-nexus", "chart": "policy-nexus-0.2.0", - "deployed_revision": 9, "deployed_at": "2026-10-11T01:23:33", - "rollback_target_revision": 9, - "image_repository": "forgejo.coulomb.social/coulomb/policy-nexus", - "image_digest": "sha256:4e0db6a57eb00f577159aa6ee10f910dd67b03fd0541c6ce849f5426fdb99d06", - "publicationManifestDigest": "ba53cc4900e6bb9ab8aa4bac4e58cb5fe7a03d656bafb3f9cb3d9c8ebff7843a", - "sourceInventoryDigest": "40d9e8852fe4c9e75b71f371d0fdae38664b068e7498711b0ac5671d0f41efb5", - "sourceSetDigest": "15bda2e332285afc862df8974cf29de403737df5073fa362adfc6d203cf2f256" - }, - "step2_render_diff": { - "status": "not-run", - "reason": "railiance-apps values path/revision not yet supplied (RAPP-POLICY-NEXUS-WP-0002-T02)" - }, - "step3_objects": { - "listed_kinds": ["Deployment", "Service", "ConfigMap", "Ingress", "NetworkPolicy", "ServiceAccount", "PersistentVolumeClaim"], - "objects": ["Deployment/policy-nexus", "Service/policy-nexus", "ConfigMap/kube-root-ca.crt", "Ingress/policy-nexus-http", "Ingress/policy-nexus-https", "NetworkPolicy/policy-nexus-acme-solver", "NetworkPolicy/policy-nexus-default-deny", "NetworkPolicy/policy-nexus-traefik", "ServiceAccount/default", "ServiceAccount/policy-nexus"], - "argocd_tracking_label_or_annotation_present": false, - "secrets": "not listed (agent guard forbids Secret reads); Helm release Secrets carry no ArgoCD tracking label by construction", - "stop_conditions_hit": false - } -} diff --git a/docs/evidence/2026-10-11-target-revenue-adoption-proof/observation.json b/docs/evidence/2026-10-11-target-revenue-adoption-proof/observation.json deleted file mode 100644 index cb6f79a..0000000 --- a/docs/evidence/2026-10-11-target-revenue-adoption-proof/observation.json +++ /dev/null @@ -1,159 +0,0 @@ -{ - "application": { - "automated_present": false, - "condition_types": [ - "OrphanedResourceWarning" - ], - "destination": { - "namespace": "target-revenue", - "server": "https://kubernetes.default.svc" - }, - "health": "Healthy", - "name": "target-revenue", - "operation": { - "finishedAt": "2026-09-28T14:23:27Z", - "phase": "Succeeded", - "startedAt": "2026-09-28T14:23:26Z" - }, - "operation_revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0", - "pending_operation": false, - "project": "railiance-tenants", - "resources": [ - { - "group": null, - "kind": "Service", - "name": "target-revenue", - "namespace": "target-revenue", - "status": "Synced" - }, - { - "group": "apps", - "kind": "Deployment", - "name": "target-revenue", - "namespace": "target-revenue", - "status": "Synced" - }, - { - "group": "external-secrets.io", - "kind": "ExternalSecret", - "name": "target-revenue-runtime", - "namespace": "target-revenue", - "status": "Synced" - }, - { - "group": "networking.k8s.io", - "kind": "Ingress", - "name": "target-revenue", - "namespace": "target-revenue", - "status": "Synced" - }, - { - "group": "postgresql.cnpg.io", - "kind": "Cluster", - "name": "target-revenue-pg", - "namespace": "target-revenue", - "status": "Synced" - } - ], - "revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0", - "source": { - "path": "k8s/railiance", - "repoURL": "https://forgejo.coulomb.social/coulomb/target-revenue.git", - "targetRevision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0" - }, - "sync": "Synced", - "sync_prune": false, - "sync_strategy_apply": true, - "sync_strategy_force": false, - "uid": "42285c08-97c6-452f-ad8a-4f46d9691bc9" - }, - "captured_at": "2026-10-11T19:51:12.812423+00:00", - "estate_ip": "92.205.62.239", - "live_hooks": [], - "pinned_render_diff": { - "client_side_exit_code": 0, - "normalized_render_sha256": "3d08814b968978d634f4ecb444db31d24ff19d15ae12d8411dacb6e1448859ff", - "output_suppressed": true, - "path": "k8s/railiance", - "render_resource_count": 5, - "render_sha256": "c728d28ac2b7fd61be00e65d356d53a6b729573fdd5a15d420efb86b7c89f50a", - "revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0", - "skipped_hooks": [ - "target-revenue-bootstrap-binky", - "target-revenue-migrate" - ] - }, - "ready_checks": [ - { - "available_replicas": 1, - "condition": "Available", - "generation": 6, - "instances": null, - "kind": "Deployment", - "name": "target-revenue", - "namespace": "target-revenue", - "observed_generation": 6, - "ready": [ - { - "lastTransitionTime": "2026-08-15T22:30:36Z", - "status": "True" - } - ], - "ready_instances": null, - "replicas": 1, - "tracking_matches": true, - "uid": "c6b7a312-968a-4d10-894d-d4f6b7b11205" - }, - { - "available_replicas": null, - "condition": "Ready", - "generation": 2, - "instances": null, - "kind": "ExternalSecret", - "name": "target-revenue-runtime", - "namespace": "target-revenue", - "observed_generation": null, - "ready": [ - { - "lastTransitionTime": "2026-09-05T16:39:12Z", - "status": "True" - } - ], - "ready_instances": null, - "replicas": null, - "tracking_matches": true, - "uid": "cde6e839-a964-443d-a19d-32cc5b53a9cb" - }, - { - "available_replicas": null, - "condition": "Ready", - "generation": 1, - "instances": 1, - "kind": "Cluster", - "name": "target-revenue-pg", - "namespace": "target-revenue", - "observed_generation": null, - "ready": [ - { - "lastTransitionTime": "2026-08-05T15:51:45Z", - "status": "True" - } - ], - "ready_instances": 1, - "replicas": null, - "tracking_matches": true, - "uid": "333e9409-0a9c-47fa-bb73-6c7309f0a099" - } - ], - "tracking_settings": { - "controller_version": "v3.5.3", - "installation_id_absent": true, - "instance_label_key": "app.kubernetes.io/instance", - "method": "annotation" - }, - "public_health": { - "url": "https://revenue.coulomb.social/healthz", - "status": 200 - }, - "metadata_captured_at": "2026-10-11T19:51:13.501350+00:00" -} diff --git a/docs/evidence/2026-10-11-target-revenue-adoption-proof/receipt.json b/docs/evidence/2026-10-11-target-revenue-adoption-proof/receipt.json deleted file mode 100644 index 059bf3f..0000000 --- a/docs/evidence/2026-10-11-target-revenue-adoption-proof/receipt.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "schema_version": "argocd-workload-adoption-proof/v1", - "status": "verified", - "application": "target-revenue", - "revision": "a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0", - "captured_at": "2026-10-11T19:51:12.812423+00:00", - "verified_at": "2026-10-11T19:51:12.813305+00:00", - "proving_seconds": 1142865.812423, - "resource_count": 5, - "ready_count": 3, - "manual_apply_prune_off": true, - "hooks_skipped": [ - "target-revenue-bootstrap-binky", - "target-revenue-migrate" - ], - "hooks_absent": true, - "child_automation_enabled": false, - "public_health_status": 200, - "native_mutation": false, - "verifier_sha256": "cf5d2113425d3e014f937371af31d9bcb11671ea036c1b8b8a65aaac47f954b4", - "tracking_helper_sha256": "c058c0ea646bcc3410e0d26db6b42a5471a8fa45a1df62b9ec85f2bf7d03f660", - "contract_sha256": "9946a3f3b5b7612ba9d6352b4dc795f2e5e0385d7d296cc4bf14411e1d7805dd", - "observation_sha256": "d77a033160e62b75f9f7df384222f8cd11ea6e8303f02e83909b4f25d201d1dd" -} diff --git a/docs/evidence/2026-10-13-playwright-consumer-preparation.json b/docs/evidence/2026-10-13-playwright-consumer-preparation.json deleted file mode 100644 index d757b80..0000000 --- a/docs/evidence/2026-10-13-playwright-consumer-preparation.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "recorded_at": "2026-10-13T19:21:02.085796+00:00", - "workplan": "RPF-WP-0056", - "status": "consumer-source-prepared-activation-pending", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "public_ca_source": "playwright/kube-root-ca.crt on railiance01, read over existing authenticated SSH", - "pilot_ready_replicas": 1, - "tests": { - "command": "PLAYWRIGHT_TEST_TMPDIR=/var/tmp PYTHONDONTWRITEBYTECODE=1 python3 -m pytest -q tests/test_playwright_consumer.py", - "passed": 9, - "exit_code": 0, - "notes": "Temporary directory override required because /tmp is a Git worktree on this workstation. Tokens are synthetic. Installed kubectl parsed generated configuration." - }, - "configuration_syntax": "YAML and JSON parsed; not Kubernetes-native validation", - "owner_replies_received": false, - "live_consumer_authentication_proven": false, - "impersonation_used_for_consumer_proof": false, - "remaining": [ - "native JWT/CEL validation and cluster activation", - "public PKCE and dedicated per-agent client registrations", - "governed coding-agent credential delivery", - "supported per-caller API transport", - "real human and coding-agent identity, authorization and browser trials", - "provider owner availability acceptance" - ], - "sources": [ - { - "path": "scripts/playwright_consumer.py", - "sha256": "76e2ee82ed1d232c6c5f5ade5a43a2bb5217de83ecbdb8a7f4d5ff8d03cad0ac" - }, - { - "path": "tests/test_playwright_consumer.py", - "sha256": "6f323661d1af3d980504154de4049e7c1398848c9039794c529b516977d26dd4" - }, - { - "path": "config/playwright/railiance01-ca.pem", - "sha256": "794d3badd7c3743fe66c3671e7f152277f2ac9adddbc5cebe534802432861bd7" - }, - { - "path": "config/playwright/authentication-config.candidate.yaml", - "sha256": "074c7e99c687f4105d70551c9a071b577d45372bc333eb1749dce81eb029d104" - }, - { - "path": "config/playwright/keycape-public-client.candidate.json", - "sha256": "df079d45bc146adf65a7a46acc0ee1145fbc863ef8b468a99785a95699f35b5e" - }, - { - "path": "docs/playwright-consumer-access.md", - "sha256": "cf16e48cb6cd8bfb5f99164e692c2da6fda0e38c897346a267d10103787f873c" - }, - { - "path": "docs/playwright-identity-handoff.md", - "sha256": "5f98ad3b5f377749dbb3e5e07204d1c40f657837161c17720c3da92bf7684cd3" - } - ] -} diff --git a/docs/evidence/2026-10-13-playwright-managed/after-restart-receipt.json b/docs/evidence/2026-10-13-playwright-managed/after-restart-receipt.json deleted file mode 100644 index 0b82d98..0000000 --- a/docs/evidence/2026-10-13-playwright-managed/after-restart-receipt.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "id": "b11fd28b-58fe-4b20-a15e-32b18ea76f2b", - "api_version": "1", - "status": "passed", - "error": null, - "label": "independent-browser-trial", - "target": "fixture", - "viewports": [ - { - "name": "desktop", - "width": 1280, - "height": 800 - }, - { - "name": "narrow", - "width": 390, - "height": 844 - } - ], - "timeout_ms": 60000, - "created_at": "2026-10-13T18:52:59.076Z", - "expires_at": "2026-10-13T19:52:59.076Z", - "service_version": "0.1.0", - "playwright_version": "1.64.0", - "release": "forgejo.coulomb.social/coulomb/rapp-playwright@sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441", - "execution_location": "railiance01/playwright", - "browser_version": "156.0.8078.4", - "completed_steps": 14, - "steps": [ - { - "viewport": "desktop", - "index": 0, - "action": "visible", - "status": "passed", - "at": "2026-10-13T18:53:01.091Z", - "selector": "h1" - }, - { - "viewport": "desktop", - "index": 1, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:53:01.214Z", - "selector": "#count" - }, - { - "viewport": "desktop", - "index": 2, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:53:01.323Z", - "key": "Tab" - }, - { - "viewport": "desktop", - "index": 3, - "action": "focused", - "status": "passed", - "at": "2026-10-13T18:53:01.360Z", - "selector": "#increment" - }, - { - "viewport": "desktop", - "index": 4, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:53:01.393Z", - "key": "Enter" - }, - { - "viewport": "desktop", - "index": 5, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:53:01.407Z", - "selector": "#count" - }, - { - "viewport": "desktop", - "index": 6, - "action": "screenshot", - "status": "passed", - "at": "2026-10-13T18:53:01.454Z" - }, - { - "viewport": "narrow", - "index": 0, - "action": "visible", - "status": "passed", - "at": "2026-10-13T18:53:02.526Z", - "selector": "h1" - }, - { - "viewport": "narrow", - "index": 1, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:53:02.786Z", - "selector": "#count" - }, - { - "viewport": "narrow", - "index": 2, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:53:02.877Z", - "key": "Tab" - }, - { - "viewport": "narrow", - "index": 3, - "action": "focused", - "status": "passed", - "at": "2026-10-13T18:53:02.891Z", - "selector": "#increment" - }, - { - "viewport": "narrow", - "index": 4, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:53:02.896Z", - "key": "Enter" - }, - { - "viewport": "narrow", - "index": 5, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:53:02.905Z", - "selector": "#count" - }, - { - "viewport": "narrow", - "index": 6, - "action": "screenshot", - "status": "passed", - "at": "2026-10-13T18:53:02.915Z" - } - ], - "artifacts": [ - { - "name": "desktop-6-keyboard-result.png", - "bytes": 26622 - }, - { - "name": "desktop-final.png", - "bytes": 26622 - }, - { - "name": "narrow-6-keyboard-result.png", - "bytes": 24280 - }, - { - "name": "narrow-final.png", - "bytes": 24280 - } - ], - "started_at": "2026-10-13T18:52:59.078Z", - "blocked_requests": 12, - "finished_at": "2026-10-13T18:53:03.351Z", - "downloads": [ - { - "name": "desktop-6-keyboard-result.png", - "bytes": 26622, - "sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad" - }, - { - "name": "desktop-final.png", - "bytes": 26622, - "sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad" - }, - { - "name": "narrow-6-keyboard-result.png", - "bytes": 24280, - "sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8" - }, - { - "name": "narrow-final.png", - "bytes": 24280, - "sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8" - } - ] -} diff --git a/docs/evidence/2026-10-13-playwright-managed/desktop-6-keyboard-result.png b/docs/evidence/2026-10-13-playwright-managed/desktop-6-keyboard-result.png deleted file mode 100644 index 91a2040..0000000 Binary files a/docs/evidence/2026-10-13-playwright-managed/desktop-6-keyboard-result.png and /dev/null differ diff --git a/docs/evidence/2026-10-13-playwright-managed/desktop-final.png b/docs/evidence/2026-10-13-playwright-managed/desktop-final.png deleted file mode 100644 index 91a2040..0000000 Binary files a/docs/evidence/2026-10-13-playwright-managed/desktop-final.png and /dev/null differ diff --git a/docs/evidence/2026-10-13-playwright-managed/narrow-6-keyboard-result.png b/docs/evidence/2026-10-13-playwright-managed/narrow-6-keyboard-result.png deleted file mode 100644 index c2ad4e9..0000000 Binary files a/docs/evidence/2026-10-13-playwright-managed/narrow-6-keyboard-result.png and /dev/null differ diff --git a/docs/evidence/2026-10-13-playwright-managed/narrow-final.png b/docs/evidence/2026-10-13-playwright-managed/narrow-final.png deleted file mode 100644 index c2ad4e9..0000000 Binary files a/docs/evidence/2026-10-13-playwright-managed/narrow-final.png and /dev/null differ diff --git a/docs/evidence/2026-10-13-playwright-managed/negative-checks.json b/docs/evidence/2026-10-13-playwright-managed/negative-checks.json deleted file mode 100644 index 5aad21a..0000000 --- a/docs/evidence/2026-10-13-playwright-managed/negative-checks.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "wrong_token": true, - "wrong_artifact_token": true, - "ui_failure_distinguished": true, - "bounded_cancellation": true, - "private_target_refused": true, - "preflight_healthy": true -} diff --git a/docs/evidence/2026-10-13-playwright-managed/preflight.json b/docs/evidence/2026-10-13-playwright-managed/preflight.json deleted file mode 100644 index 7d3b61e..0000000 --- a/docs/evidence/2026-10-13-playwright-managed/preflight.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "available": true, - "error": null, - "checked_at": "2026-10-13T18:49:56.214Z", - "browser_version": "156.0.8078.4", - "api_version": "1", - "service_version": "0.1.0", - "playwright_version": "1.64.0", - "execution_location": "railiance01/playwright", - "release": "forgejo.coulomb.social/coulomb/rapp-playwright@sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441", - "allowed_origins": [ - "https://onco.cc" - ], - "fixture": true, - "active": 0, - "queued": 0, - "limits": { - "concurrency": 1, - "max_queue": 4, - "max_timeout_ms": 120000, - "retention_ms": 3600000, - "max_runs": 64 - } -} diff --git a/docs/evidence/2026-10-13-playwright-managed/receipt.json b/docs/evidence/2026-10-13-playwright-managed/receipt.json deleted file mode 100644 index af78292..0000000 --- a/docs/evidence/2026-10-13-playwright-managed/receipt.json +++ /dev/null @@ -1,184 +0,0 @@ -{ - "id": "6a51e164-cec5-46ed-89b1-92b93dd16d23", - "api_version": "1", - "status": "passed", - "error": null, - "label": "independent-browser-trial", - "target": "fixture", - "viewports": [ - { - "name": "desktop", - "width": 1280, - "height": 800 - }, - { - "name": "narrow", - "width": 390, - "height": 844 - } - ], - "timeout_ms": 60000, - "created_at": "2026-10-13T18:50:40.554Z", - "expires_at": "2026-10-13T19:50:40.554Z", - "service_version": "0.1.0", - "playwright_version": "1.64.0", - "release": "forgejo.coulomb.social/coulomb/rapp-playwright@sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441", - "execution_location": "railiance01/playwright", - "browser_version": "156.0.8078.4", - "completed_steps": 14, - "steps": [ - { - "viewport": "desktop", - "index": 0, - "action": "visible", - "status": "passed", - "at": "2026-10-13T18:50:42.655Z", - "selector": "h1" - }, - { - "viewport": "desktop", - "index": 1, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:50:42.815Z", - "selector": "#count" - }, - { - "viewport": "desktop", - "index": 2, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:50:42.851Z", - "key": "Tab" - }, - { - "viewport": "desktop", - "index": 3, - "action": "focused", - "status": "passed", - "at": "2026-10-13T18:50:42.911Z", - "selector": "#increment" - }, - { - "viewport": "desktop", - "index": 4, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:50:42.924Z", - "key": "Enter" - }, - { - "viewport": "desktop", - "index": 5, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:50:42.937Z", - "selector": "#count" - }, - { - "viewport": "desktop", - "index": 6, - "action": "screenshot", - "status": "passed", - "at": "2026-10-13T18:50:42.949Z" - }, - { - "viewport": "narrow", - "index": 0, - "action": "visible", - "status": "passed", - "at": "2026-10-13T18:50:43.919Z", - "selector": "h1" - }, - { - "viewport": "narrow", - "index": 1, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:50:44.063Z", - "selector": "#count" - }, - { - "viewport": "narrow", - "index": 2, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:50:44.180Z", - "key": "Tab" - }, - { - "viewport": "narrow", - "index": 3, - "action": "focused", - "status": "passed", - "at": "2026-10-13T18:50:44.228Z", - "selector": "#increment" - }, - { - "viewport": "narrow", - "index": 4, - "action": "press", - "status": "passed", - "at": "2026-10-13T18:50:44.260Z", - "key": "Enter" - }, - { - "viewport": "narrow", - "index": 5, - "action": "text", - "status": "passed", - "at": "2026-10-13T18:50:44.275Z", - "selector": "#count" - }, - { - "viewport": "narrow", - "index": 6, - "action": "screenshot", - "status": "passed", - "at": "2026-10-13T18:50:44.314Z" - } - ], - "artifacts": [ - { - "name": "desktop-6-keyboard-result.png", - "bytes": 26622 - }, - { - "name": "desktop-final.png", - "bytes": 26622 - }, - { - "name": "narrow-6-keyboard-result.png", - "bytes": 24280 - }, - { - "name": "narrow-final.png", - "bytes": 24280 - } - ], - "started_at": "2026-10-13T18:50:40.557Z", - "blocked_requests": 12, - "finished_at": "2026-10-13T18:50:44.840Z", - "downloads": [ - { - "name": "desktop-6-keyboard-result.png", - "bytes": 26622, - "sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad" - }, - { - "name": "desktop-final.png", - "bytes": 26622, - "sha256": "b15998824256b86c74f2bbb75f061061be65db0b4f3bb5444529ac69a723cdad" - }, - { - "name": "narrow-6-keyboard-result.png", - "bytes": 24280, - "sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8" - }, - { - "name": "narrow-final.png", - "bytes": 24280, - "sha256": "49d27cb169b8e180ce93969c6d2b8619a7684cf035bb9dcae9cd91d8bd9952e8" - } - ] -} diff --git a/docs/evidence/2026-10-13-playwright-managed/runtime.json b/docs/evidence/2026-10-13-playwright-managed/runtime.json deleted file mode 100644 index 481d6c1..0000000 --- a/docs/evidence/2026-10-13-playwright-managed/runtime.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "observed_at": "2026-10-13T18:52:58.325239+00:00", - "image": "forgejo.coulomb.social/coulomb/rapp-playwright@sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441", - "replica_ready": true, - "pvc_phase": "Bound", - "pvc_uid": "da36929a-1d69-457a-ad99-7580ef86ab18", - "pvc_size": "2Gi", - "initial_pod_uid": "3475ad46-d9d8-4b97-99b7-54b4d984d2f2", - "restarted_pod_uid": "a22030a4-c12d-4e97-be8d-98a8d80808c2", - "same_image_restart_completed_artifacts_verified": true, - "preflight_after_restart": { - "available": true, - "error": null, - "checked_at": "2026-10-13T18:51:59.595Z", - "browser_version": "156.0.8078.4", - "api_version": "1", - "service_version": "0.1.0", - "playwright_version": "1.64.0", - "execution_location": "railiance01/playwright", - "release": "forgejo.coulomb.social/coulomb/rapp-playwright@sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441", - "allowed_origins": [ - "https://onco.cc" - ], - "fixture": true, - "active": 0, - "queued": 0, - "limits": { - "concurrency": 1, - "max_queue": 4, - "max_timeout_ms": 120000, - "retention_ms": 3600000, - "max_runs": 64 - } - }, - "access_path": "Existing authenticated operator SSH; administrator impersonation of playwright-consumers; loopback-only port-forward", - "actual_consumer_authentication_proven": false, - "cross_version_rollback_proven": false, - "abrupt_interruption_recovery_proven": false, - "source_workload_sha256": "d5d3f3d816dd4320059b4fcdc8409fb84d6075aeb1f409f4d112263d7e14191d" -} diff --git a/docs/evidence/2026-10-13-playwright-rbac.json b/docs/evidence/2026-10-13-playwright-rbac.json deleted file mode 100644 index cd070e8..0000000 --- a/docs/evidence/2026-10-13-playwright-rbac.json +++ /dev/null @@ -1,194 +0,0 @@ -{ - "schema": "platform-playwright-rbac/v1", - "observed_at": "2026-10-13T18:49:52.369304+00:00", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "role_uid": "d24e8b51-7456-4000-8816-b4a49a06ca37", - "binding_uid": "190eb32f-8649-4b36-a525-8936cf3ceb51", - "group": "playwright-consumers", - "checks": [ - { - "member": true, - "attributes": { - "verb": "get", - "resource": "pods", - "subresource": "", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": true, - "allowed": true, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "list", - "resource": "pods", - "subresource": "", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": true, - "allowed": true, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "create", - "resource": "pods", - "subresource": "portforward", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": true, - "allowed": true, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "get", - "resource": "deployments", - "subresource": "", - "group": "apps", - "namespace": "playwright", - "name": "playwright" - }, - "expected": true, - "allowed": true, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "get", - "resource": "deployments", - "subresource": "", - "group": "apps", - "namespace": "playwright", - "name": "other" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "get", - "resource": "secrets", - "subresource": "", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "create", - "resource": "pods", - "subresource": "exec", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "create", - "resource": "pods", - "subresource": "", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "patch", - "resource": "deployments", - "subresource": "", - "group": "apps", - "namespace": "playwright", - "name": "playwright" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "create", - "resource": "pods", - "subresource": "portforward", - "group": "", - "namespace": "default", - "name": "" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": true, - "attributes": { - "verb": "create", - "resource": "rolebindings", - "subresource": "", - "group": "rbac.authorization.k8s.io", - "namespace": "playwright", - "name": "" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": false, - "attributes": { - "verb": "list", - "resource": "pods", - "subresource": "", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": false, - "allowed": false, - "passed": true - }, - { - "member": false, - "attributes": { - "verb": "create", - "resource": "pods", - "subresource": "portforward", - "group": "", - "namespace": "playwright", - "name": "" - }, - "expected": false, - "allowed": false, - "passed": true - } - ], - "passed": true, - "actual_consumer_authentication_proven": false -} diff --git a/docs/evidence/2026-10-13-playwright-source-review.json b/docs/evidence/2026-10-13-playwright-source-review.json deleted file mode 100644 index b8e3557..0000000 --- a/docs/evidence/2026-10-13-playwright-source-review.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "schema": "platform-playwright-source-review/v1", - "source_commit": "70268154c06cf78c5d9e1f4eb900213ffd436d1b", - "source_sha256": { - "declarations/rapp.yaml": "5b030686f1e3ca737805e681fc8b714e1452f879a3fa30bbac8ca30798bb3343", - "manifests/workload.yaml": "7d76ad38e25597b629660d31782867d84d7241020a2e450e346a3792ce43290c", - "releases/0.1.0.json": "364dcbc16fd50155b7d8eddb0ecb87b1709f0efdc8a41c9f15afd81e5a18506e", - "docs/platform-admission.md": "c37dc9f77d5be5bc55c873af98e8ecb0bc945ddd37ea42010d169f5d472b53c0", - "docs/operations.md": "fea5f9e25752c0199839559da5bba0ebca77be7c4906dbce4b7b6278ce5b4dab" - }, - "image": "forgejo.coulomb.social/coulomb/rapp-playwright@sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441", - "provider_unit_tests_passed": 3, - "managed_runtime_accepted": false, - "group": "playwright-consumers", - "member_identities": [] -} diff --git a/docs/evidence/2026-10-13-task-execution-source-correction.json b/docs/evidence/2026-10-13-task-execution-source-correction.json deleted file mode 100644 index e30ec81..0000000 --- a/docs/evidence/2026-10-13-task-execution-source-correction.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "schema": "platform-source-correction/v1", - "observed_at": "2026-10-13T18:53:24.250702+00:00", - "task": "RPF-WP-0035-T12", - "source_sha256": { - "scripts/task_execution_plan.py": "f81bd36523c42ce880111f774409cb7c554c6ffc06c2686a1864e6e6cd70111a", - "tests/test_task_execution_plan.py": "14c587c05a612955f6fc972ad3967a051b36fb127aecc246a28168331a034468", - "docs/task-execution-plan-compiler.md": "cf28c8e1ab702a66d5608f56566f184afd8d41b2693a38034e001de46f023b94" - }, - "initial_regression": "cached catalogue validator replacement caused proposal refusal despite unchanged pinned source; regression failed before correction", - "test_command": "PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/home/worsch/secrets-engine/src python3 -m pytest -q tests/test_task_execution_plan.py tests/test_task_execution_journal.py", - "compiler_tests_passed": 45, - "journal_tests_passed": 15, - "native_execution_authorized": false, - "independent_integration_review": "pending" -} diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/approval.json b/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/approval.json deleted file mode 100644 index 05106ef..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/approval.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "procedure": "audit-core-database-lease-recovery", - "task_id": "RAILIANCE-WP-0024-T02", - "status": "approved", - "approval_id": "rpf-wp-0015-t02-20261014t1631z-founder-go", - "window": { - "start": "2026-10-14T16:31:00Z", - "end": "2026-10-14T16:46:00Z" - }, - "abort_operator": "founder (platform-root), attended", - "owners": { - "audit-core": { - "acknowledged": true, - "message_id": "b3c564a6-93f9-4c07-872b-7feb22cccb73" - }, - "rapp-postgres": { - "acknowledged": true, - "message_id": "08a84caf-5cf9-4f90-8dba-98f9f43677e2" - }, - "railiance-platform": { - "acknowledged": true, - "message_id": "0f8f0692-1549-4070-8428-0d9284e4d8d5" - } - }, - "synthetic_load": { - "contract_id": "RAILIANCE-WP-0024-T02-SYNTHETIC-01", - "driver_revision": "sha256:941ba251f638869626b06e9cbf430c70188c0bdf4715e3eff9c7610366f68662" - }, - "synthetic_sender": { - "identity": "t02-synthetic-sender-20261014", - "source": "t02-synthetic-20261014", - "tenant": "tenant:trial:t02-synthetic-20261014", - "expires_at": "2026-10-14T16:46:00Z" - } -} diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/exercise.json b/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/exercise.json deleted file mode 100644 index 740d485..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/exercise.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "approval_id": "rpf-wp-0015-t02-20261014t1631z-founder-go", - "completed_at": "2026-10-14T16:34:10Z", - "health_during_failure": 200, - "lease_handle_fingerprint": "9dd61a51945e", - "lease_revoked": true, - "load_cleanup_status": "clean", - "mount_generation_changed": true, - "procedure": "audit-core-database-lease-recovery", - "readiness_during_failure": 503, - "readiness_recovered": true, - "restart_count_unchanged": true, - "same_pod_uid": true, - "secret_resource_version_changed": true, - "secret_values_observed": false, - "synthetic_load_contract": "RAILIANCE-WP-0024-T02-SYNTHETIC-01", - "synthetic_load_driver_revision": "sha256:941ba251f638869626b06e9cbf430c70188c0bdf4715e3eff9c7610366f68662", - "synthetic_recovery_status": "duplicate", - "synthetic_unavailable_attempts": 1, - "task_id": "RAILIANCE-WP-0024-T02" -} diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/window-summary.json b/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/window-summary.json deleted file mode 100644 index 73391a1..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/window-summary.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "approval_id": "rpf-wp-0015-t02-20261014t1631z-founder-go", - "cleanup": { - "approval_id": "rpf-wp-0015-t02-20261014t1631z-founder-go", - "audit_core_ready": "1/1", - "audit_core_restarted": true, - "bearer_file_removed": true, - "cleanup_completed_at": "2026-10-14T16:34:31Z", - "cleanup_gate": { - "completed_at": "2026-10-14T16:34:10Z", - "mode": "evidence-recorded" - }, - "identities_removed": 1, - "identity": "t02-synthetic-sender-20261014", - "lane": "audit-core-synthetic-sender", - "secret_values_observed": false - }, - "exercise_evidence": "rpf-wp-0015-t02-20261014t1631z-founder-go-exercise.json", - "finished_at": "2026-10-14T16:34:31Z", - "identity": { - "expires_at": "2026-10-14T16:46:00Z", - "name": "t02-synthetic-sender-20261014", - "source": "t02-synthetic-20261014", - "tenant": "tenant:trial:t02-synthetic-20261014" - }, - "outcome": "completed", - "procedure": "t02-attended-window", - "project": { - "approval_id": "rpf-wp-0015-t02-20261014t1631z-founder-go", - "audit_core_pod_uid_after_projection": "7618cf45-d801-487a-9b3d-51559fd14ec3", - "audit_core_restarted": true, - "bearer_file_mode": "0600", - "identity": { - "expires_at": "2026-10-14T16:46:00Z", - "name": "t02-synthetic-sender-20261014", - "source": "t02-synthetic-20261014", - "tenant": "tenant:trial:t02-synthetic-20261014" - }, - "lane": "audit-core-synthetic-sender", - "next_step": "run the T02 exercise; its baseline must capture this pod UID", - "projected_at": "2026-10-14T16:33:08Z", - "secret_values_observed": false - }, - "secret_values_observed": false, - "started_at": "2026-10-14T16:32:36Z", - "steps": [ - "project", - "port-forward", - "exercise", - "evidence", - "cleanup" - ] -} \ No newline at end of file diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/approval.json b/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/approval.json deleted file mode 100644 index d938635..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/approval.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "procedure": "audit-core-database-lease-recovery", - "task_id": "RAILIANCE-WP-0024-T02", - "status": "approved", - "approval_id": "rpf-wp-0015-t02-20261014t1615z-founder-go", - "window": { - "start": "2026-10-14T16:15:00Z", - "end": "2026-10-14T16:30:00Z" - }, - "abort_operator": "founder (platform-root), attended", - "owners": { - "audit-core": { - "acknowledged": true, - "message_id": "5fe769a7-d35b-4e37-a671-673d13cf8948" - }, - "rapp-postgres": { - "acknowledged": true, - "message_id": "f13ec9dd-f525-4ed0-b291-be5dc26bed0e" - }, - "railiance-platform": { - "acknowledged": true, - "message_id": "18e59587-2b78-478e-88e6-7487b8933743" - } - }, - "synthetic_load": { - "contract_id": "RAILIANCE-WP-0024-T02-SYNTHETIC-01", - "driver_revision": "sha256:941ba251f638869626b06e9cbf430c70188c0bdf4715e3eff9c7610366f68662" - }, - "synthetic_sender": { - "identity": "t02-synthetic-sender-20261014", - "source": "t02-synthetic-20261014", - "tenant": "tenant:trial:t02-synthetic-20261014", - "expires_at": "2026-10-14T16:30:00Z" - } -} \ No newline at end of file diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/window-summary.json b/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/window-summary.json deleted file mode 100644 index 8aad474..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t02-window/window-summary.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "abort_reason": "T02 exercise failed (exit 1): database lease recovery failed: exact runtime prefix must contain one live handle (observed 2)", - "approval_id": "rpf-wp-0015-t02-20261014t1615z-founder-go", - "cleanup": { - "approval_id": "rpf-wp-0015-t02-20261014t1615z-founder-go", - "audit_core_ready": "1/1", - "audit_core_restarted": true, - "bearer_file_removed": true, - "cleanup_completed_at": "2026-10-14T16:17:41Z", - "cleanup_gate": { - "mode": "abort", - "reason": "t02 window aborted: T02 exercise failed (exit 1): database lease recovery failed: exact runtime prefix must contain one live handle (observed 2)" - }, - "identities_removed": 1, - "identity": "t02-synthetic-sender-20261014", - "lane": "audit-core-synthetic-sender", - "secret_values_observed": false - }, - "finished_at": "2026-10-14T16:17:41Z", - "identity": { - "expires_at": "2026-10-14T16:30:00Z", - "name": "t02-synthetic-sender-20261014", - "source": "t02-synthetic-20261014", - "tenant": "tenant:trial:t02-synthetic-20261014" - }, - "outcome": "aborted", - "procedure": "t02-attended-window", - "project": { - "approval_id": "rpf-wp-0015-t02-20261014t1615z-founder-go", - "audit_core_pod_uid_after_projection": "2f788f76-0e99-412f-b9af-d9e899a3c61a", - "audit_core_restarted": true, - "bearer_file_mode": "0600", - "identity": { - "expires_at": "2026-10-14T16:30:00Z", - "name": "t02-synthetic-sender-20261014", - "source": "t02-synthetic-20261014", - "tenant": "tenant:trial:t02-synthetic-20261014" - }, - "lane": "audit-core-synthetic-sender", - "next_step": "run the T02 exercise; its baseline must capture this pod UID", - "projected_at": "2026-10-14T16:17:02Z", - "secret_values_observed": false - }, - "secret_values_observed": false, - "started_at": "2026-10-14T16:16:34Z", - "steps": [ - "project", - "port-forward", - "abort-cleanup" - ] -} \ No newline at end of file diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/openbao-snapshot-receipt.json b/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/openbao-snapshot-receipt.json deleted file mode 100644 index 176ebc5..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/openbao-snapshot-receipt.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "receipt_version": 1, - "receipt_id": "RPF-WP-0015-T03-REBOOT-20261014-01-SNAPSHOT-20261014T181052Z", - "created_at": "2026-10-14T18:10:52Z", - "operator": "railiance-platform", - "source_cluster": "railiance01", - "source_namespace": "openbao", - "source_pod": "openbao-0", - "cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf", - "raft_applied_index": 6040594, - "snapshot_created": true, - "source_initialized": true, - "source_unsealed": true, - "snapshot_sha256": "sha256:a6312444eaf5698f7340f930648aef93243a9633cec80a5e1c7255ddea01590b", - "snapshot_encrypted": true, - "encrypted_snapshot_sha256": "sha256:8069449ccba422434d756f1cfddaf1a76b799d3ed7d00d8b245271eda0e88c41", - "encrypted_location_ref": "offhost-custody:RPF-WP-0015-T03-REBOOT-20261014-01:20261014T181053Z", - "encrypted_copy_off_host": true, - "off_host_evidence": "upload acknowledged (HTTP 201, If-None-Match: *); remote hash not read back", - "upload_folder": "openbao", - "recipients": [ - "age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz" - ], - "custodian_escrow_ref": "founder-offline:workstation age identity railiance-backup.key (derives age1zvry...r3tsz, verified 2026-10-14)", - "escrow_key_read_decision": "reviews/RPF-WP-0015-T03-founder-go-20261014.json#escrow_key_read", - "encryption_verified": true, - "hash_verified": true, - "no_secret_material_recorded": true -} diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/post-reboot-chain-head.json b/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/post-reboot-chain-head.json deleted file mode 100644 index 8849cd7..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/post-reboot-chain-head.json +++ /dev/null @@ -1,3 +0,0 @@ -{"verify_chain":{"intact":true,"events":508,"first_break":null,"head":"c99426d8fb53454c2da6217330c2cde62e440deb3d5ca2ebb04155c0a3108c4d","head_event_id":"t02-rpf-wp-0015-t02-20261014t1631z-founder-go"}, - "attestation":{"schema":"audit-core.chain-head.v1","chain_hash":"c99426d8fb53454c2da6217330c2cde62e440deb3d5ca2ebb04155c0a3108c4d","event_count":508,"observed_at":"2026-10-14T21:30:55+00:00","ok":true}, - "pre_reboot_head_present":true,"run_by_role":"founder"} diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/post-reboot-preflight.json b/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/post-reboot-preflight.json deleted file mode 100644 index 786bfec..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/post-reboot-preflight.json +++ /dev/null @@ -1,274 +0,0 @@ -{ - "automated_checks": { - "all_cnpg_clusters_healthy": true, - "all_eso_stores_valid_and_synced": true, - "core_platform_workloads_ready": true, - "firewall_matches_declaration": true, - "k3s_api_tunnel_answers": true, - "ntp_synchronized": true, - "post_reboot_comparison_passed": true, - "required_backups_fresh": true - }, - "automated_checks_passed": true, - "operator_gates": { - "approved_window_id_recorded": false, - "attended_abort_operator_named": false, - "fresh_human_decision_recorded": false, - "host_cluster_platform_database_audit_owners_acknowledged": false, - "new_scenario_id_recorded": false, - "openbao_snapshot_evidence_present": false, - "ops_warden_revision_accepted_for_attended_login": true, - "provider_console_receipt_verified": false, - "railiance_infra_independent_abort_owner_acknowledged": false, - "unseal_quorum_attested": false - }, - "ready_for_live_execution": false, - "reboot_comparison": { - "boot_id_changed": true, - "boots_since_pre_capture": 1, - "database_identity_changes": [], - "exactly_one_reboot": true, - "failing_pod_diff": { - "baseline_count": 6, - "current_count": 6, - "new_failing": [], - "no_new_failures": true, - "resolved": [] - }, - "passed": true - }, - "reboot_evidence": { - "captured_at": "2026-10-14T18:27:38Z", - "cnpg_backups": { - "databases/apps-pg": { - "age_hours": 16.21, - "method": "barmanObjectStore", - "name": "apps-pg-daily-20261014021500", - "stopped_at": "2026-10-14T02:15:10Z" - }, - "databases/forgejo-db": { - "age_hours": 15.87, - "method": "barmanObjectStore", - "name": "forgejo-db-daily-20261014023500", - "stopped_at": "2026-10-14T02:35:37Z" - }, - "databases/platform-pg": { - "age_hours": 16.2, - "method": "barmanObjectStore", - "name": "platform-pg-daily-20261014021500", - "stopped_at": "2026-10-14T02:15:21Z" - }, - "databases/platform-pg-2": { - "age_hours": 16.21, - "method": "barmanObjectStore", - "name": "platform-pg-2-daily-20261014021500", - "stopped_at": "2026-10-14T02:15:10Z" - } - }, - "cnpg_clusters": { - "databases/apps-pg": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "apps-pg-1", - "healthy_pvc": [ - "apps-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "642213aa-802a-4721-9ba1-974857cd3fd6", - "ready_instances": 1, - "system_id": "7671819021750181909", - "uid": "834d8a5e-ea2c-4b12-ad97-5561af348953" - }, - "databases/forgejo-db": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "forgejo-db-1", - "healthy_pvc": [ - "forgejo-db-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "4f8ad794-9ab9-4bb4-a052-b1d68a878ea5", - "ready_instances": 1, - "system_id": "7658381155162636309", - "uid": "4fe798f3-1bf2-465e-b1c6-99adfa657aca" - }, - "databases/net-kingdom-pg": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "net-kingdom-pg-1", - "healthy_pvc": [ - "net-kingdom-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "b4470081-21ec-4e57-b551-87a95d5efdc2", - "ready_instances": 1, - "system_id": "7620984416961720340", - "uid": "0cd04a7c-a183-48ac-975a-9f244ea82c73" - }, - "databases/platform-pg": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "platform-pg-1", - "healthy_pvc": [ - "platform-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "26504fd2-0164-4fdb-b2cb-a9687bb18e47", - "ready_instances": 1, - "system_id": "7672396747569053717", - "uid": "74c22737-fbdd-4fb0-b3ab-419484585fbf" - }, - "databases/platform-pg-2": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "platform-pg-2-1", - "healthy_pvc": [ - "platform-pg-2-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "a35cfa5b-b6a7-4819-8eb9-3c001815350e", - "ready_instances": 1, - "system_id": "7676862216806043670", - "uid": "4c71b30d-2d49-4b89-8532-ef896dcd289c" - }, - "databases/state-hub-db": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "state-hub-db-1", - "healthy_pvc": [ - "state-hub-db-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "ab93c4b9-1a44-4d18-8ba4-e1dc8a37cd18", - "ready_instances": 1, - "system_id": "7659449445025882132", - "uid": "1b7133b2-fff9-44b7-a898-0189139b2508" - }, - "target-revenue/target-revenue-pg": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "target-revenue-pg-1", - "healthy_pvc": [ - "target-revenue-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "950e002f-3b4d-4fcf-8f55-2d07578a63e4", - "ready_instances": 1, - "system_id": "7670575750956527638", - "uid": "333e9409-0a9c-47fa-bb73-6c7309f0a099" - }, - "user-engine/user-engine-pg": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "user-engine-pg-1", - "healthy_pvc": [ - "user-engine-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "76dd0b1e-a4ba-4046-ad7d-5f01c3240383", - "ready_instances": 1, - "system_id": "7667319508680269845", - "uid": "27388eda-32e8-4cd3-a8e3-b1b3789f929c" - } - }, - "core_platform_workloads": { - "missing": [], - "ready": true, - "required": [ - "kube-system/Deployment/coredns", - "kube-system/Deployment/local-path-provisioner", - "kube-system/Deployment/traefik", - "kube-system/Deployment/metrics-server", - "cert-manager/Deployment/cert-manager", - "cnpg-system/Deployment/cnpg-controller-manager", - "external-secrets/Deployment/external-secrets", - "knative-serving/Deployment/activator", - "knative-serving/Deployment/controller", - "knative-serving/Deployment/net-kourier-controller", - "kourier-system/Deployment/3scale-kourier-gateway", - "argocd/Deployment/argocd-repo-server", - "argocd/StatefulSet/argocd-application-controller", - "openbao/StatefulSet/openbao" - ], - "unready": [] - }, - "eso": { - "cluster_store_count": 32, - "external_secret_count": 46, - "invalid_cluster_stores": [], - "ready": true, - "unsynced_external_secrets": [] - }, - "failing_pods": [ - "coulomb/ihp-railiance-probe-7c99c6fcc4-jwhnb", - "coulomb/ihp-railiance-probe-7c99c6fcc4-l9f2z", - "coulomb/ihp-railiance-probe-7c99c6fcc4-nmtlq", - "forgejo/forgejo-gitea-7d94988c79-krqdr", - "forgejo/forgejo-runner-6fcb669668-7g8m7", - "telemetry/telemetry-smtp-proof-20260928-ltd7q" - ], - "host": { - "boot_id": "eaf7763f-123e-4012-87ab-31319a900732", - "firewall": { - "active": true, - "allowed_targets": [ - "2224/tcp", - "OpenSSH" - ], - "forbidden_ports_open": [], - "matches_declaration": true - }, - "ntp_synchronized": true - }, - "k3s_api_tunnel_answers": true, - "missing_expected_cnpg_clusters": [], - "terminating_pvcs": [] - }, - "receipt_gate_failures": { - "provider_console_receipt": "not supplied (RAIL-HO-WP-0011-T09 pending)" - }, - "secret_values_observed": false, - "state": { - "openbao": { - "cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf", - "initialized": true, - "raft_applied_index": 6041986, - "seal_type": "shamir", - "sealed": false, - "shares": 3, - "threshold": 2 - }, - "audit_core": { - "image": "forgejo.coulomb.social/coulomb/audit-core@sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615", - "image_matches_reviewed_digest": true, - "pod_uid": "dab9e572-97be-4fe7-9e31-4579d744cf3d", - "ready_replicas": 1, - "replicas": 1, - "restart_count": 1, - "running_pod_image": "forgejo.coulomb.social/coulomb/audit-core@sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615", - "running_pod_image_digest": "sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615", - "running_pod_image_matches_reviewed_digest": true - }, - "node": { - "k3s_active": true, - "name": "239.62.205.92.host.secureserver.net", - "ready": true, - "uptime_seconds": 377.36 - }, - "platform_pg": { - "continuous_archiving": true, - "instances": 1, - "last_backup_succeeded": true, - "phase": "Cluster in healthy state", - "ready_instances": 1 - } - } -} \ No newline at end of file diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/pre-reboot-chain-head.json b/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/pre-reboot-chain-head.json deleted file mode 100644 index 78a5995..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/pre-reboot-chain-head.json +++ /dev/null @@ -1,3 +0,0 @@ -{"verify_chain":{"intact":true,"events":508,"first_break":null,"head":"c99426d8fb53454c2da6217330c2cde62e440deb3d5ca2ebb04155c0a3108c4d","head_event_id":"t02-rpf-wp-0015-t02-20261014t1631z-founder-go","head_accepted_at":"2026-10-14T16:33:29+00:00"}, - "attestation":{"schema":"audit-core.chain-head.v1","chain_hash":"c99426d8fb53454c2da6217330c2cde62e440deb3d5ca2ebb04155c0a3108c4d","event_count":508,"observed_at":"2026-10-14T18:11:11+00:00"}, - "run_by_role":"founder"} diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/pre-reboot-preflight.json b/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/pre-reboot-preflight.json deleted file mode 100644 index 63ff71e..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/pre-reboot-preflight.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "automated_checks": { - "all_cnpg_clusters_healthy": true, - "all_eso_stores_valid_and_synced": true, - "core_platform_workloads_ready": true, - "firewall_matches_declaration": true, - "k3s_api_tunnel_answers": true, - "ntp_synchronized": true, - "required_backups_fresh": true, - "terminating_pvcs_resolved_or_listed": true - }, - "automated_checks_passed": true, - "operator_gates": { - "approved_window_id_recorded": true, - "attended_abort_operator_named": true, - "fresh_human_decision_recorded": true, - "host_cluster_platform_database_audit_owners_acknowledged": true, - "new_scenario_id_recorded": true, - "openbao_snapshot_evidence_present": true, - "ops_warden_revision_accepted_for_attended_login": true, - "provider_console_receipt_verified": true, - "railiance_infra_independent_abort_owner_acknowledged": true, - "unseal_quorum_attested": true - }, - "ready_for_live_execution": true, - "reboot_comparison": null, - "reboot_evidence": { - "captured_at": "2026-10-14T18:12:00Z", - "cnpg_backups": { - "databases/apps-pg": { - "age_hours": 15.95, - "method": "barmanObjectStore", - "name": "apps-pg-daily-20261014021500", - "stopped_at": "2026-10-14T02:15:10Z" - }, - "databases/forgejo-db": { - "age_hours": 15.61, - "method": "barmanObjectStore", - "name": "forgejo-db-daily-20261014023500", - "stopped_at": "2026-10-14T02:35:37Z" - }, - "databases/platform-pg": { - "age_hours": 15.94, - "method": "barmanObjectStore", - "name": "platform-pg-daily-20261014021500", - "stopped_at": "2026-10-14T02:15:21Z" - }, - "databases/platform-pg-2": { - "age_hours": 15.95, - "method": "barmanObjectStore", - "name": "platform-pg-2-daily-20261014021500", - "stopped_at": "2026-10-14T02:15:10Z" - } - }, - "cnpg_clusters": { - "databases/apps-pg": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "apps-pg-1", - "healthy_pvc": [ - "apps-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "642213aa-802a-4721-9ba1-974857cd3fd6", - "ready_instances": 1, - "system_id": "7671819021750181909", - "uid": "834d8a5e-ea2c-4b12-ad97-5561af348953" - }, - "databases/forgejo-db": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "forgejo-db-1", - "healthy_pvc": [ - "forgejo-db-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "4f8ad794-9ab9-4bb4-a052-b1d68a878ea5", - "ready_instances": 1, - "system_id": "7658381155162636309", - "uid": "4fe798f3-1bf2-465e-b1c6-99adfa657aca" - }, - "databases/net-kingdom-pg": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "net-kingdom-pg-1", - "healthy_pvc": [ - "net-kingdom-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "b4470081-21ec-4e57-b551-87a95d5efdc2", - "ready_instances": 1, - "system_id": "7620984416961720340", - "uid": "0cd04a7c-a183-48ac-975a-9f244ea82c73" - }, - "databases/platform-pg": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "platform-pg-1", - "healthy_pvc": [ - "platform-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "26504fd2-0164-4fdb-b2cb-a9687bb18e47", - "ready_instances": 1, - "system_id": "7672396747569053717", - "uid": "74c22737-fbdd-4fb0-b3ab-419484585fbf" - }, - "databases/platform-pg-2": { - "backup_configured": true, - "continuous_archiving": true, - "current_primary": "platform-pg-2-1", - "healthy_pvc": [ - "platform-pg-2-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "a35cfa5b-b6a7-4819-8eb9-3c001815350e", - "ready_instances": 1, - "system_id": "7676862216806043670", - "uid": "4c71b30d-2d49-4b89-8532-ef896dcd289c" - }, - "databases/state-hub-db": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "state-hub-db-1", - "healthy_pvc": [ - "state-hub-db-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "ab93c4b9-1a44-4d18-8ba4-e1dc8a37cd18", - "ready_instances": 1, - "system_id": "7659449445025882132", - "uid": "1b7133b2-fff9-44b7-a898-0189139b2508" - }, - "target-revenue/target-revenue-pg": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "target-revenue-pg-1", - "healthy_pvc": [ - "target-revenue-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "950e002f-3b4d-4fcf-8f55-2d07578a63e4", - "ready_instances": 1, - "system_id": "7670575750956527638", - "uid": "333e9409-0a9c-47fa-bb73-6c7309f0a099" - }, - "user-engine/user-engine-pg": { - "backup_configured": false, - "continuous_archiving": null, - "current_primary": "user-engine-pg-1", - "healthy_pvc": [ - "user-engine-pg-1" - ], - "instances": 1, - "phase": "Cluster in healthy state", - "primary_pvc_uid": "76dd0b1e-a4ba-4046-ad7d-5f01c3240383", - "ready_instances": 1, - "system_id": "7667319508680269845", - "uid": "27388eda-32e8-4cd3-a8e3-b1b3789f929c" - } - }, - "core_platform_workloads": { - "missing": [], - "ready": true, - "required": [ - "kube-system/Deployment/coredns", - "kube-system/Deployment/local-path-provisioner", - "kube-system/Deployment/traefik", - "kube-system/Deployment/metrics-server", - "cert-manager/Deployment/cert-manager", - "cnpg-system/Deployment/cnpg-controller-manager", - "external-secrets/Deployment/external-secrets", - "knative-serving/Deployment/activator", - "knative-serving/Deployment/controller", - "knative-serving/Deployment/net-kourier-controller", - "kourier-system/Deployment/3scale-kourier-gateway", - "argocd/Deployment/argocd-repo-server", - "argocd/StatefulSet/argocd-application-controller", - "openbao/StatefulSet/openbao" - ], - "unready": [] - }, - "eso": { - "cluster_store_count": 32, - "external_secret_count": 46, - "invalid_cluster_stores": [], - "ready": true, - "unsynced_external_secrets": [] - }, - "failing_pods": [ - "coulomb/ihp-railiance-probe-7c99c6fcc4-jwhnb", - "coulomb/ihp-railiance-probe-7c99c6fcc4-l9f2z", - "coulomb/ihp-railiance-probe-7c99c6fcc4-nmtlq", - "forgejo/forgejo-gitea-7d94988c79-krqdr", - "forgejo/forgejo-runner-6fcb669668-7g8m7", - "telemetry/telemetry-smtp-proof-20260928-ltd7q" - ], - "host": { - "boot_id": "31b5be76-4471-43f1-bd4f-137869c81368", - "firewall": { - "active": true, - "allowed_targets": [ - "2224/tcp", - "OpenSSH" - ], - "forbidden_ports_open": [], - "matches_declaration": true - }, - "ntp_synchronized": true - }, - "k3s_api_tunnel_answers": true, - "missing_expected_cnpg_clusters": [], - "terminating_pvcs": [] - }, - "receipt_gate_failures": {}, - "secret_values_observed": false, - "state": { - "openbao": { - "cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf", - "initialized": true, - "raft_applied_index": 6040650, - "seal_type": "shamir", - "sealed": false, - "shares": 3, - "threshold": 2 - }, - "audit_core": { - "image": "forgejo.coulomb.social/coulomb/audit-core@sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615", - "image_matches_reviewed_digest": true, - "pod_uid": "dab9e572-97be-4fe7-9e31-4579d744cf3d", - "ready_replicas": 1, - "replicas": 1, - "restart_count": 0, - "running_pod_image": "forgejo.coulomb.social/coulomb/audit-core@sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615", - "running_pod_image_digest": "sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615", - "running_pod_image_matches_reviewed_digest": true - }, - "node": { - "k3s_active": true, - "name": "239.62.205.92.host.secureserver.net", - "ready": true, - "uptime_seconds": 5168538.95 - }, - "platform_pg": { - "continuous_archiving": true, - "instances": 1, - "last_backup_succeeded": true, - "phase": "Cluster in healthy state", - "ready_instances": 1 - } - } -} \ No newline at end of file diff --git a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/snapshot-failed-1-identity-format.json b/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/snapshot-failed-1-identity-format.json deleted file mode 100644 index 69afbfe..0000000 --- a/docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/snapshot-failed-1-identity-format.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "status": "failed", - "scenario_id": "RPF-WP-0015-T03-REBOOT-20261014-01", - "error": "offsite lane AGE_PRIVATE_KEY is unusable", - "stages": [ - "status_before", - "snapshot_save", - "status_after", - "encrypt", - "verify", - "shred" - ], - "temp_dir_removed": true -} diff --git a/docs/evidence/2026-10-15-cadence-receiver/qonto-collector-native-admission.json b/docs/evidence/2026-10-15-cadence-receiver/qonto-collector-native-admission.json deleted file mode 100644 index 7ba89c4..0000000 --- a/docs/evidence/2026-10-15-cadence-receiver/qonto-collector-native-admission.json +++ /dev/null @@ -1,59 +0,0 @@ -{ - "own_reconciliation_status": 200, - "own_reconciliation": { - "source": "qonto-assistant", - "tenant": "tenant:friendly:binky", - "since": "2026-10-15T19:36:00+00:00", - "until": "2026-10-15T19:37:05.369346+00:00", - "counts": [ - { - "class": "audit-core.heartbeat", - "count": 1 - }, - { - "class": "audit.deny", - "count": 1 - } - ], - "stream_instance_id": "b351fa6b-e0d6-4f88-b7c9-6bacb1a112b7", - "means": "the count of events audit-core accepted and stored in this window. Divergence from the source's own count is a finding for the source; agreement proves neither completeness nor that any event occurred." - }, - "duplicate_status": 200, - "wrong_source_status": 400, - "wrong_tenant_status": 400, - "read_refusal_status": 403, - "invalid_bearer_status": 401, - "global_pending_count": 0, - "instance_receipts": [ - { - "accepted_at": "2026-10-15T19:36:30.769604+00:00", - "covered_event_classes": [ - "audit.deny" - ], - "event_id": "28dad6de11fb25fe6e0e145351f047360d6898afb53afd4cd677e560ec69132a", - "source": "qonto-assistant", - "status_code": 202, - "stream_instance_id": "b351fa6b-e0d6-4f88-b7c9-6bacb1a112b7", - "stream_sequence": 1, - "tenant": "tenant:friendly:binky", - "type": "audit-core.heartbeat" - }, - { - "accepted_at": "2026-10-15T19:36:30.861014+00:00", - "covered_event_classes": [], - "event_id": "6a9f34445e13062e38ab309a6fc800dc9a2fa24cb8a46ecd919ae18fe9409874", - "source": "qonto-assistant", - "status_code": 202, - "stream_instance_id": "b351fa6b-e0d6-4f88-b7c9-6bacb1a112b7", - "stream_sequence": 2, - "tenant": "tenant:friendly:binky", - "type": "audit.deny" - } - ], - "observed_at": "2026-10-15T19:37:03.422744+00:00", - "credential_values_emitted": false, - "source": "qonto-assistant", - "tenant": "tenant:friendly:binky", - "stream_instance_id": "b351fa6b-e0d6-4f88-b7c9-6bacb1a112b7", - "independent_readback": false -} diff --git a/docs/evidence/2026-10-15-cadence-receiver/qonto-collector-restart-persistence.json b/docs/evidence/2026-10-15-cadence-receiver/qonto-collector-restart-persistence.json deleted file mode 100644 index 2196930..0000000 --- a/docs/evidence/2026-10-15-cadence-receiver/qonto-collector-restart-persistence.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "receipts": [ - { - "accepted_at": "2026-10-15T19:36:30.769604+00:00", - "covered_event_classes": [ - "audit.deny" - ], - "event_id": "28dad6de11fb25fe6e0e145351f047360d6898afb53afd4cd677e560ec69132a", - "source": "qonto-assistant", - "status_code": 202, - "stream_instance_id": "b351fa6b-e0d6-4f88-b7c9-6bacb1a112b7", - "stream_sequence": 1, - "tenant": "tenant:friendly:binky", - "type": "audit-core.heartbeat" - }, - { - "accepted_at": "2026-10-15T19:36:30.861014+00:00", - "covered_event_classes": [], - "event_id": "6a9f34445e13062e38ab309a6fc800dc9a2fa24cb8a46ecd919ae18fe9409874", - "source": "qonto-assistant", - "status_code": 202, - "stream_instance_id": "b351fa6b-e0d6-4f88-b7c9-6bacb1a112b7", - "stream_sequence": 2, - "tenant": "tenant:friendly:binky", - "type": "audit.deny" - } - ], - "pending_count": 0, - "started_at": "2026-10-15T19:37:41.503622+00:00", - "pod_uid_before": "1a39bd4b-19dc-4c1f-adaa-56a11423d945", - "native_admission_receipt": "qonto-collector-native-admission.json", - "pod_uid_after": "92ef9104-469c-4b28-a0cc-f781f53bb26e", - "observed_at": "2026-10-15T19:38:35.874453+00:00", - "first_202_receipts_preserved": true, - "pvc": "rapp-qonto-audit-outbox", - "image": "forgejo.coulomb.social/coulomb/rapp-qonto-audit-collector@sha256:82bfd4100e14ddb2cefc4acc02b6b74609846b4a26ad40d9089ed564693f250a" -} diff --git a/docs/evidence/2026-10-15-cadence-receiver/receiver-custody-live.json b/docs/evidence/2026-10-15-cadence-receiver/receiver-custody-live.json deleted file mode 100644 index 14014d2..0000000 --- a/docs/evidence/2026-10-15-cadence-receiver/receiver-custody-live.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "schema_version": "platform.cadence-custody/v1", - "status": "delivered_pending_native_producer_and_independent_acceptance", - "credential_values_emitted": false, - "started_at": "2026-10-15T19:35:14.571483+00:00", - "packet_sha256": "81478f43abd2dbcb924966e9a3404e26bc226c91f2921b493ef0d5e2c23d4729", - "receiver_before": { - "image": "forgejo.coulomb.social/coulomb/audit-core@sha256:b479801b0bc76df66a36a85376c45aaf399c6d82feade9ba81430b774c4a8271", - "deployment_uid": "b85fe3d0-75c9-4e0d-8c34-c6f1df0881bb", - "deployment_resource_version": "75784583", - "pod_uid": "22e4df05-dfb0-4e70-8d5a-5a5950c860bd", - "capabilities": { - "load_bearing": true, - "redact": true, - "write_only": true, - "source_exact": true, - "tenant_exact": true - }, - "synthetic_probe_only": true, - "credential_reads": 0, - "cadence_instance_capabilities": { - "cadence_registration": true, - "sqlite_instance_filter": true, - "postgres_instance_filter": true, - "synthetic_cadence_parse": true, - "synthetic_heartbeat_parse": true, - "synthetic_exact_scope": true - } - }, - "phase": "delivery", - "registry_version_before": 15, - "custody_version": 1, - "custody_request_id": "77a526ef-dbcc-147d-b98b-a339f4efcd62", - "approval": { - "sender": "approval-engine", - "source": "approval-engine", - "tenant": "tenant:platform", - "may_read": false, - "emission_cadence_sha256": "cc58c8d53ebf50acf540d73b7607d579942bda7734c3e8722108e9c9204af655", - "heartbeat_classes": { - "issuance": 7200, - "use": 7200, - "supersession": 7200, - "revocation": 7200 - }, - "effective_deadline_seconds": 10800, - "changed": true, - "credentials_preserved": true, - "other_registry_fields_preserved": true - }, - "registry_request_id": "47ca933d-4a5d-699b-c474-094947f78fb9", - "registry_version_after": 16, - "registry_readback_matches": true, - "other_senders_preserved": true, - "approval_credentials_preserved": true, - "reader_exact_path_negatives_and_revocation": true, - "wrong_identity_denied": true, - "coding_agent_deny_wins": true, - "projection_matches": true, - "receiver_registry_projection_matches": true, - "receiver_after": { - "image": "forgejo.coulomb.social/coulomb/audit-core@sha256:b479801b0bc76df66a36a85376c45aaf399c6d82feade9ba81430b774c4a8271", - "deployment_uid": "b85fe3d0-75c9-4e0d-8c34-c6f1df0881bb", - "deployment_resource_version": "75785764", - "pod_uid": "8333754a-eca0-4dd0-9230-72e41bb1e632", - "capabilities": { - "load_bearing": true, - "redact": true, - "write_only": true, - "source_exact": true, - "tenant_exact": true - }, - "synthetic_probe_only": true, - "credential_reads": 0, - "cadence_instance_capabilities": { - "cadence_registration": true, - "sqlite_instance_filter": true, - "postgres_instance_filter": true, - "synthetic_cadence_parse": true, - "synthetic_heartbeat_parse": true, - "synthetic_exact_scope": true - } - }, - "receiver_reloaded": true, - "receiver_runtime_registry_matches": true, - "completed_at": "2026-10-15T19:35:47.205117+00:00" -} diff --git a/docs/evidence/2026-10-15-reuse-private-reader-custody-r1.json b/docs/evidence/2026-10-15-reuse-private-reader-custody-r1.json deleted file mode 100644 index c63e763..0000000 --- a/docs/evidence/2026-10-15-reuse-private-reader-custody-r1.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "id": "RPF-WP-0058-CUSTODY-VERIFICATION-R1", - "ccr_id": "CCR-2026-0046", - "decision_ref": "9833715e-a22a-45c0-ab6c-881f0f4f1a0e", - "decision_status": "resolved/made observed from primary State Hub", - "approval_receipt_sha256": "ce11b56faa51cb0912c6bdd228a10d0e5dec5a93f295410eaaf370399e7abd85", - "approved_executed_ccr_sha256": "23039fae6137e7bdd71de0ed3278596b1ed60ae636018eb4bfcc83e53c4ada02", - "policy_receipt_sha256": "01d417a09d547b88a792fbc00bcc1502f1235da2d666196632a71d7f1c11bdbd", - "policy_attempt": "93578ba93d6153fdb07c65db", - "policy_terminal_at": "2026-10-15T03:06:13.283133+00:00", - "policy_outcome": "policy_and_custody_verified_delivery_pending", - "bootstrap_receipt_sha256": "f5ddd86299cdf31ca895f76b84d7fc14d2ac791e5982632c5c989a32221a0171", - "bootstrap_attempt": "bb2693a7640f00b4e401467e", - "bootstrap_terminal_at": "2026-10-15T03:06:12.816932+00:00", - "bootstrap_outcome": "provisioned_verified_delivery_pending", - "forgejo_identity": "reuse-surface-source", - "forgejo_team_id": 5, - "forgejo_pat_id": 72, - "token_scope": "read:repository", - "repository_scope": "Exactly seven approved private repositories, complete grant preflight and seven pinned index hashes verified by bounded bootstrap.", - "custody_activation_at": "2026-10-15T03:06:09.579844+00:00", - "manual_rotation_due_at": "2027-01-13T03:06:09.579844+00:00", - "manual_rotation_due_date": "2027-01-13", - "maintenance_task": "RPF-WP-0058-T05", - "maintenance_task_uuid": "f0780e75-b0bd-5bf5-b926-c8413abb885c", - "expiry_enforced_automatically": false, - "warden_terminal_audit": { - "access_audit_at": "2026-10-15T03:06:13.494014+00:00", - "unified_audit_at": "2026-10-15T03:06:13.495110+00:00", - "outcome": "ok", - "exit_code": 0, - "meaning": "Installed owner envelope confirms silent child success, self-revocation and helper cleanup; no token/helper was read." - }, - "delivery_status": "pending_apps_phase_b_and_native_proof", - "no_reexecution_or_remint": true -} diff --git a/docs/evidence/2026-10-15-reuse-private-reader-hosted-establishment-r1.json b/docs/evidence/2026-10-15-reuse-private-reader-hosted-establishment-r1.json deleted file mode 100644 index dbba69d..0000000 --- a/docs/evidence/2026-10-15-reuse-private-reader-hosted-establishment-r1.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "id": "RPF-WP-0058-HOSTED-ESTABLISHMENT-R1", - "recorded_at": "2026-10-15T03:42:15.733740+00:00", - "task": "RPF-WP-0058-T04", - "status": "done", - "root_completion_commit": "e5aaa0db51edc4c9f935d03ab58808b86e340823", - "authoritative_receipts": [ - { - "repo": "the-custodian", - "commit": "e5aaa0db51edc4c9f935d03ab58808b86e340823", - "path": "docs/assessments/netkingdom-2026-10-14/hosted-private-registration-verification-r1.json", - "sha256": "d1b1f850bea3a45e722db47d9a30dfe482a183885721b40712c2b5826295d515" - }, - { - "repo": "the-custodian", - "commit": "e5aaa0db51edc4c9f935d03ab58808b86e340823", - "path": "docs/assessments/netkingdom-2026-10-14/private-native-fetch-verification-r1.json", - "sha256": "a30e28a282d464178ed608bec29d6d325d92355e5bc8c54b4e6e8c310eae1629" - }, - { - "repo": "the-custodian", - "commit": "e5aaa0db51edc4c9f935d03ab58808b86e340823", - "path": "docs/assessments/netkingdom-2026-10-14/phase-b-runtime-verification-r1.json", - "sha256": "0f7db53d2a956947ccb49729f349e47474775a587ef906ca91f1ccc9f8c6ba9a" - } - ], - "phase_b_runtime_verified_at": "2026-10-15T03:30:20.559962+00:00", - "hardened_image_digest": "sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0", - "native_verified_at": "2026-10-15T03:30:20.627885+00:00", - "native_index_count": 7, - "native_entry_count": 7, - "native_cache_used": false, - "private_registrations_applied": 7, - "full_sql_readback_verified": true, - "hosted_composed_at": "2026-10-15T03:31:48+00:00", - "hosted_forced_fresh": true, - "reviewed_expected": 70, - "reviewed_present_exactly_once": 70, - "missing_or_duplicate_or_wrongsource_ids": [], - "federation_warning_present": false, - "scoped_writer_attempt": "8f23bb90-aef9-405d-81b6-bf550400dd93", - "scoped_writer_revoked": true, - "scoped_writer_warden_exit": 0, - "maintenance_task": "RPF-WP-0058-T05", - "maintenance_task_uuid": "f0780e75-b0bd-5bf5-b926-c8413abb885c", - "maintenance_status": "wait", - "manual_due_at": "2027-01-13T03:06:09.579844+00:00", - "automatic_expiry_or_reminder": false, - "no_new_tests_or_live_changes_by_this_closeout": true, - "root_residual_ref": "CUST-IN-0018" -} diff --git a/docs/evidence/2026-10-15-sign-in-diagnosis/acceptance.json b/docs/evidence/2026-10-15-sign-in-diagnosis/acceptance.json deleted file mode 100644 index 886fa08..0000000 --- a/docs/evidence/2026-10-15-sign-in-diagnosis/acceptance.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "workplan": "NK-WP-0051", - "date": "2026-10-15", - "result": "pass", - "source": "Attended founder observations in this session, ops-mason native verifier receipts, railiance-platform delegated applier and overlay checks", - "admin": { - "uid": "platform-root", - "groups": [ - "activity-core-operators", - "net-kingdom-admins", - "net-kingdom-users", - "whynot-design" - ], - "assurance": "aal2", - "client_id": "openbao-admin", - "contained_session_exit": 0, - "ccr": "CCR-2026-0045", - "ccr_status": "applied" - }, - "member": { - "uid": "bernd.worsch-3", - "groups": [ - "reuse-surface-hub-writers", - "tenant:trial:demo-company:users", - "whynot-design" - ], - "assurance": "aal2", - "client_id": "openbao-admin", - "result": "accepted", - "revoked": true - }, - "non_member": { - "uid": "tegwick", - "groups": [ - "railiance-admins" - ], - "assurance": "aal2", - "client_id": "openbao-admin", - "result": "refused", - "openbao_error": "Vault login failed. error validating claims: claim \"groups\" does not match any associated bound claim values" - }, - "overlay": { - "apply": "passed", - "verify": "passed after transient connection reset during gateway rollout", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "ui_role_browser_clickthrough": "Not separately exercised; retained in RPF-WP-0057-T04. Native attended OIDC logins for both roles passed expected outcomes." - }, - "verification": { - "directory_helper": "68 passed", - "platform_credential_and_ui_tests": "64 passed, 2 unrelated failures: CCR-2026-0035 schema validation; core-hub resource inventory expectation", - "ccr_0045_validate": "passed", - "ccr_0045_dry_run": "passed; no warnings" - }, - "limits": [ - "No KV secret values read", - "User diagnosis values are founder observations, not independently captured browser evidence", - "No independent live platform-admin entity-policy inventory performed", - "No changes to directory memberships, admin entity policy, or ops-mason-survey" - ], - "receipts": { - "reuse-surface-hub-writer-member.txt": "1b61f06bba55562dc833345f8c10cfa286212f84815b9fcdcdb6f28c9cf0a328", - "reuse-surface-hub-writer-non-member.txt": "94fa475eb31e4c6a61c223b997b426c960a5144384d4aa688b4de01c8a47d322" - } -} diff --git a/docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-member.txt b/docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-member.txt deleted file mode 100644 index d0cb1b0..0000000 --- a/docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-member.txt +++ /dev/null @@ -1,21 +0,0 @@ -mode=member -at=2026-10-15T17:16:54Z -addr=http://127.0.0.1:18200 -role=reuse-surface-hub-writer-workload-kv-read -policy=workload-kv-read-reuse-surface-runtime -uid=bernd.worsch-3 -uid_source=operator -assurance=aal2 -assurance_source=operator -login=ok -openbao_error= -entity_id=c204099b-cb94-363f-e207-5ff4d4aa6e94 -policies=workload-kv-read-reuse-surface-runtime -identity_policies= -platform/data/workloads/reuse/reuse-surface/runtime-secrets: read -platform/metadata/workloads/reuse/reuse-surface/runtime-secrets: read -platform/data/workloads/reuse/reuse-surface: deny -platform/metadata/workloads/reuse/reuse-surface: deny -platform/data/workloads/issue-core/issue-core/issue-core-runtime: deny -revoked=yes -result=pass diff --git a/docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-non-member.txt b/docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-non-member.txt deleted file mode 100644 index 44ce20f..0000000 --- a/docs/evidence/2026-10-15-sign-in-diagnosis/reuse-surface-hub-writer-non-member.txt +++ /dev/null @@ -1,21 +0,0 @@ -mode=non-member -at=2026-10-15T17:20:11Z -addr=http://127.0.0.1:18200 -role=reuse-surface-hub-writer-workload-kv-read -policy=workload-kv-read-reuse-surface-runtime -uid=tegwick -uid_source=operator -assurance=aal2 -assurance_source=operator -login=refused -openbao_error=Error authenticating: Error making API request. | URL: GET http://127.0.0.1:18200/v1/auth/netkingdom/oidc/callback?client_nonce=cnM23po0ewSOMtT1AhuU&code=[redacted]&id_token=&state=st_GrdNJIELAV9Evdxna5YB | Code: 400. Errors: | * Vault login failed. error validating claims: claim "groups" does not match any associated bound claim values -entity_id= -policies= -identity_policies= -platform/data/workloads/reuse/reuse-surface/runtime-secrets: not-run -platform/metadata/workloads/reuse/reuse-surface/runtime-secrets: not-run -platform/data/workloads/reuse/reuse-surface: not-run -platform/metadata/workloads/reuse/reuse-surface: not-run -platform/data/workloads/issue-core/issue-core/issue-core-runtime: not-run -revoked=n/a -result=pass diff --git a/docs/evidence/2026-10-16-flex-auth-native-plan-public-supplement.json b/docs/evidence/2026-10-16-flex-auth-native-plan-public-supplement.json deleted file mode 100644 index 4bc8e09..0000000 --- a/docs/evidence/2026-10-16-flex-auth-native-plan-public-supplement.json +++ /dev/null @@ -1,328 +0,0 @@ -{ - "applications": { - "eso-token-renewer": { - "exists": true, - "ok": true, - "selected": { - "automated_present": false, - "metadata": { - "name": "eso-token-renewer", - "namespace": "argocd", - "owners": [], - "resource_version": "76246569", - "uid": "b10d1e31-08da-4364-b537-4e166eeeb25e" - }, - "operation_phase": "Succeeded", - "operation_present": false, - "resources": [ - { - "kind": "ConfigMap", - "name": "eso-token-renewer-worker-5c86dt7fm7", - "namespace": "external-secrets" - }, - { - "kind": "ServiceAccount", - "name": "eso-token-renewer", - "namespace": "external-secrets" - }, - { - "kind": "CronJob", - "name": "eso-token-renewer", - "namespace": "external-secrets" - } - ], - "source": { - "chart": null, - "path": "argocd/platform-addons/eso-token-renewer", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "b2ebe108495c4d06ebb6ca33893ea4a2a23c9725" - }, - "sources": [] - }, - "stderr_present": false - }, - "informed-decision": { - "exists": true, - "ok": true, - "selected": { - "automated_present": false, - "metadata": { - "name": "informed-decision", - "namespace": "argocd", - "owners": [], - "resource_version": "76247431", - "uid": "499868f2-eabc-4df4-ae31-b89ce58d750d" - }, - "operation_phase": "Succeeded", - "operation_present": false, - "resources": [ - { - "kind": "ConfigMap", - "name": "informed-decision-runtime-6c221878d051", - "namespace": "informed-decision" - }, - { - "kind": "ConfigMap", - "name": "informed-decision-runtime-c81577741a09", - "namespace": "informed-decision" - }, - { - "kind": "ConfigMap", - "name": "informed-decision-workload-registry", - "namespace": "informed-decision" - }, - { - "kind": "PersistentVolumeClaim", - "name": "informed-decision-data", - "namespace": "informed-decision" - }, - { - "kind": "Service", - "name": "informed-decision", - "namespace": "informed-decision" - }, - { - "kind": "Service", - "name": "informed-decision-workload", - "namespace": "informed-decision" - }, - { - "kind": "ServiceAccount", - "name": "review", - "namespace": "informed-decision" - }, - { - "kind": "Deployment", - "name": "informed-decision", - "namespace": "informed-decision" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-review-ingress", - "namespace": "approval-engine" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-lifecycle-ingress", - "namespace": "flex-auth" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-review-ingress", - "namespace": "flex-auth" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-review", - "namespace": "informed-decision" - }, - { - "kind": "ClusterRole", - "name": "informed-decision-tokenreview", - "namespace": null - }, - { - "kind": "ClusterRoleBinding", - "name": "informed-decision-tokenreview", - "namespace": null - } - ], - "source": { - "chart": null, - "path": "deploy/lifecycle", - "repoURL": "https://forgejo.coulomb.social/coulomb/informed-decision.git", - "targetRevision": "312ab97e876e17341bf16f9e95397e14c2ac2720" - }, - "sources": [] - }, - "stderr_present": false - }, - "openbao-secretstore": { - "exists": true, - "ok": true, - "selected": { - "automated_present": false, - "metadata": { - "name": "openbao-secretstore", - "namespace": "argocd", - "owners": [], - "resource_version": "76247497", - "uid": "f2932505-e649-4b99-b505-4789ed21301f" - }, - "operation_phase": "Succeeded", - "operation_present": false, - "resources": [ - { - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": null - } - ], - "source": { - "chart": null, - "path": "argocd/platform-addons/openbao-secretstore", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "d2dbc19c254247652c49fda8721c80d53bca206a" - }, - "sources": [] - }, - "stderr_present": false - }, - "railiance-apps-root": { - "exists": true, - "ok": true, - "selected": { - "automated_present": false, - "metadata": { - "name": "railiance-apps-root", - "namespace": "argocd", - "owners": [], - "resource_version": "76247264", - "uid": "3078d1f5-07c2-44cc-bff7-c2ee2a2c9e7c" - }, - "operation_phase": "Succeeded", - "operation_present": false, - "resources": [ - { - "kind": "Application", - "name": "activity-core", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "bao-notice", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "eso-token-renewer", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "informed-decision", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "informed-decision-requester", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "openbao-secretstore", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "secret-annotation-guard", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "state-hub-memo-dispatch", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "target-revenue", - "namespace": "argocd" - } - ], - "source": { - "chart": null, - "path": "argocd/railiance01/applications", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "main" - }, - "sources": [] - }, - "stderr_present": false - } - }, - "held_fence": false, - "objects": { - "deployment/flex-auth-secrets-engine": { - "exists": true, - "ok": true, - "selected": { - "conditions": [ - { - "reason": "NewReplicaSetAvailable", - "status": "True", - "type": "Progressing" - }, - { - "reason": "MinimumReplicasAvailable", - "status": "True", - "type": "Available" - } - ], - "field_managers": [ - { - "manager": "helm", - "operation": "Update" - }, - { - "manager": "k3s", - "operation": "Update" - } - ], - "metadata": { - "name": "flex-auth-secrets-engine", - "namespace": "flex-auth", - "owners": [], - "resource_version": "75291996", - "uid": "f83a5f5c-7dd7-4aac-bc02-d447e423e6ae" - } - }, - "stderr_present": false - }, - "externalsecret.external-secrets.io/flex-auth-secrets-engine-envelope-signing": { - "exists": false, - "ok": true - }, - "persistentvolumeclaim/flex-auth-secrets-engine-outbox": { - "exists": false, - "ok": true - }, - "secretstore.external-secrets.io/openbao-flex-auth-envelope-signing": { - "exists": false, - "ok": true - }, - "serviceaccount/flex-auth-envelope-signing-eso": { - "exists": false, - "ok": true - }, - "serviceaccount/flex-auth-secrets-engine": { - "exists": true, - "ok": true, - "selected": { - "conditions": [], - "field_managers": [ - { - "manager": "helm", - "operation": "Update" - } - ], - "metadata": { - "name": "flex-auth-secrets-engine", - "namespace": "flex-auth", - "owners": [], - "resource_version": "57721771", - "uid": "4fd62828-c7b6-47ce-ac44-11b9fab851c4" - } - }, - "stderr_present": false - } - }, - "observed_at": "2026-10-16T19:12:36.858755+00:00", - "persistent_mutations": false, - "schema": "platform-native-plan-public-supplement.v1", - "transport": { - "exit_code": 0, - "stderr_present": false - }, - "collector_sha256": "sha256:151f54e7815d97f0e28437f8db17f20c1eb436dce56ec332d36b88e0abfbc91d", - "coordinator_completed_at": "2026-10-16T19:12:36.585389+00:00" -} diff --git a/docs/evidence/2026-10-16-flex-auth-native-plan-review.json b/docs/evidence/2026-10-16-flex-auth-native-plan-review.json deleted file mode 100644 index efd3b43..0000000 --- a/docs/evidence/2026-10-16-flex-auth-native-plan-review.json +++ /dev/null @@ -1,302 +0,0 @@ -{ - "schema": "railiance-platform.bounded-native-plan-review.v1", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "work_item": "RPF-WP-0035-T12", - "decision": "approved", - "scope": "Exact P4/R2 Platform custody/read-lane and existing Argo Application manual-sync/freeze execution proposal, contingent on mandatory observed execution gates. Owner plan review only; not founder approval, native execution, CCR approval recording or completed native-admission disposition.", - "intent_basis": "INTENT Secure Custody, Stable Interfaces and Operable and Observable; Staff PEP-shaped attended founder-gated effects. SCOPE retains OpenBao custody/policy/delivery acceptance, not workload/Helm, cluster runtime/storage, identity or authorization ownership.", - "exact_revisions": { - "platform_revision": "84c529f239c81e6d3ca337015700b03e3a862f53", - "packet_p4_manifest_sha256": "9eec861838a8e9138f2c821cff3014998fbe74bb01447642bf50d44e8b3b7cb3", - "rollout_r2_manifest_sha256": "5be7a7f438624dcb35b7dbc96e7c9e5a8e2d5d1b1966cb4338aa5aecbe3a8b59", - "chart_revision": "dc042022f7c30671cf0a7605cf43ef6d96d04242", - "image_revision": "84c78e072c8f3daf2bc3b90f8efc8fbeb16c3cf0", - "image_digest": "sha256:09a83b273740133518cc7e7915fb211381e06545d347d2dd1974042525ac981f", - "deployment_projection_sha256": "8ce375f065552c551538f3acc7300c427407057eeff1d0b43755bfdf8a46169c", - "ccr_proposed_sha256": "44bbbf4aff84c3b085afdb14708106fd9a12ac03a0c47774913931a87fb94ada" - }, - "evidence": { - "live_preflight_sha256": "7a45f821e57b6ea9a4603db52a603ab230016da2d4145d1b772baa7f4a11840b", - "observed_at": "2026-10-16T18:53:06.325922+00:00", - "all_seven_server_dry_runs_accepted": true, - "projection_source_matches_server_admitted_stopped_render": true, - "held_fence": false, - "actual_signer_stopped": false, - "application_supplement_sha256": "f02a9b595d9246a358d19b37bdad8fde158b7f0c9657a6ff088874732511a32c", - "application_supplement_observed_at": "2026-10-16T19:12:36.858755+00:00", - "application_collector_sha256": "sha256:151f54e7815d97f0e28437f8db17f20c1eb436dce56ec332d36b88e0abfbc91d" - }, - "platform_read_lane": { - "ccr": "CCR-2026-0050", - "policy": "workload-kv-read-flex-auth-envelope-signing", - "role": "external-secrets-flex-auth-envelope-signing", - "sa": "flex-auth/flex-auth-envelope-signing-eso", - "audience": "openbao", - "ttl_seconds": 900, - "max_ttl_seconds": 900, - "explicit_max_ttl_seconds": 900, - "no_default_policy": true, - "permissions": "Exact platform/data/workloads/flex-auth/envelope-signing read plus lookup-self read/revoke-self update; no metadata/list/sibling/write/delete grant. KV document has all three fields; ESO projects only ED25519_PRIVATE_KEY to seed. No field-level authorization claim.", - "delivery": "Namespaced SecretStore flex-auth/openbao-flex-auth-envelope-signing and ExternalSecret flex-auth/flex-auth-secrets-engine-envelope-signing; Owner target creation, Retain deletion, 5m refresh; dedicated SA automount false. This proposed namespaced lane does not mutate existing ClusterSecretStore/openbao or legacy ESO token renewer." - }, - "observed_application_scope": [ - { - "name": "railiance-apps-root", - "uid": "3078d1f5-07c2-44cc-bff7-c2ee2a2c9e7c", - "automated": false, - "last_phase": "Succeeded", - "relevance": "Parent of existing nine Applications; hold manual sync during ceremony to prevent child source/policy changes.", - "source": { - "chart": null, - "path": "argocd/railiance01/applications", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "main" - }, - "queued_operation_present": false, - "resource_tuples": [ - { - "kind": "Application", - "name": "activity-core", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "bao-notice", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "eso-token-renewer", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "informed-decision", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "informed-decision-requester", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "openbao-secretstore", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "secret-annotation-guard", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "state-hub-memo-dispatch", - "namespace": "argocd" - }, - { - "kind": "Application", - "name": "target-revenue", - "namespace": "argocd" - } - ], - "resource_version": "76247264" - }, - { - "name": "informed-decision", - "uid": "499868f2-eabc-4df4-ae31-b89ce58d750d", - "automated": false, - "last_phase": "Succeeded", - "relevance": "Resource inventory includes NetworkPolicies informed-decision-lifecycle-ingress and informed-decision-review-ingress in flex-auth. Hold manual sync affecting those policies; no signer Deployment/ESO resource ownership observed.", - "source": { - "chart": null, - "path": "deploy/lifecycle", - "repoURL": "https://forgejo.coulomb.social/coulomb/informed-decision.git", - "targetRevision": "312ab97e876e17341bf16f9e95397e14c2ac2720" - }, - "queued_operation_present": false, - "resource_tuples": [ - { - "kind": "ConfigMap", - "name": "informed-decision-runtime-6c221878d051", - "namespace": "informed-decision" - }, - { - "kind": "ConfigMap", - "name": "informed-decision-runtime-c81577741a09", - "namespace": "informed-decision" - }, - { - "kind": "ConfigMap", - "name": "informed-decision-workload-registry", - "namespace": "informed-decision" - }, - { - "kind": "PersistentVolumeClaim", - "name": "informed-decision-data", - "namespace": "informed-decision" - }, - { - "kind": "Service", - "name": "informed-decision", - "namespace": "informed-decision" - }, - { - "kind": "Service", - "name": "informed-decision-workload", - "namespace": "informed-decision" - }, - { - "kind": "ServiceAccount", - "name": "review", - "namespace": "informed-decision" - }, - { - "kind": "Deployment", - "name": "informed-decision", - "namespace": "informed-decision" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-review-ingress", - "namespace": "approval-engine" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-lifecycle-ingress", - "namespace": "flex-auth" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-review-ingress", - "namespace": "flex-auth" - }, - { - "kind": "NetworkPolicy", - "name": "informed-decision-review", - "namespace": "informed-decision" - }, - { - "kind": "ClusterRole", - "name": "informed-decision-tokenreview", - "namespace": null - }, - { - "kind": "ClusterRoleBinding", - "name": "informed-decision-tokenreview", - "namespace": null - } - ], - "resource_version": "76247431" - }, - { - "name": "openbao-secretstore", - "uid": "f2932505-e649-4b99-b505-4789ed21301f", - "automated": false, - "last_phase": "Succeeded", - "relevance": "Owns ClusterSecretStore/openbao only; existing shared delivery controller-adjacent app. No proposed signer namespaced lane ownership observed; preserve existing state and no related manual sync during ceremony.", - "source": { - "chart": null, - "path": "argocd/platform-addons/openbao-secretstore", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "d2dbc19c254247652c49fda8721c80d53bca206a" - }, - "queued_operation_present": false, - "resource_tuples": [ - { - "kind": "ClusterSecretStore", - "name": "openbao", - "namespace": null - } - ], - "resource_version": "76247497" - }, - { - "name": "eso-token-renewer", - "uid": "b10d1e31-08da-4364-b537-4e166eeeb25e", - "automated": false, - "last_phase": "Succeeded", - "relevance": "Owns legacy renewer ConfigMap/SA/CronJob in external-secrets, not new dedicated Kubernetes-auth role. Preserve unchanged; no stop/disable of unrelated shared renewer proposed.", - "source": { - "chart": null, - "path": "argocd/platform-addons/eso-token-renewer", - "repoURL": "https://forgejo.coulomb.social/coulomb/railiance-platform.git", - "targetRevision": "b2ebe108495c4d06ebb6ca33893ea4a2a23c9725" - }, - "queued_operation_present": false, - "resource_tuples": [ - { - "kind": "ConfigMap", - "name": "eso-token-renewer-worker-5c86dt7fm7", - "namespace": "external-secrets" - }, - { - "kind": "ServiceAccount", - "name": "eso-token-renewer", - "namespace": "external-secrets" - }, - { - "kind": "CronJob", - "name": "eso-token-renewer", - "namespace": "external-secrets" - } - ], - "resource_version": "76246569" - } - ], - "observed_signer_controller": "No Application in the current complete inventory owns flex-auth-secrets-engine Deployment, signer SecretStore/ExternalSecret or outbox PVC. Helm release revision4 and native Deployment/ReplicaSet controller are actual signer write path; Access owns exact manual/CI/Helm writer freeze. No new signer Argo adoption or controller configuration change is proposed.", - "mandatory_execution_gates": [ - "Initial founder rollout/CCR decision may authorize only its explicitly enumerated read-lane apply and Access stopped-candidate preparation effects under reviewed owner scopes; it does not require those effects to exist before authorization. After that authorization and execution, assemble separately reviewed final one-use custody scope from approved CCR bytes/confirmed native bindings, authenticated founder entity, future absolute UTC expiry, held exclusive-window receipt, stopped native projection/fence and independent consumer baseline. All five dispositions bind this exact completed scope before custody. Actual founder/expiry/CCR/window remain null in frozen P4 preparation; this review fills none.", - "Before window refresh exact Application UID/source revision/resource tuples/current operation and manual sync settings for the identified apps and root. Freeze explicit manual sync/CI/Helm actors by founder executor/abort-owner agreement; require no pending/running Argo operation affecting relevant resources. Last Succeeded alone is not proof of no newly queued operation. Preserve before-state metadata; no speculative absence-of-all-possible-writers requirement. The19:12:36 supplement establishes all four selected Applications manual/no queued operation at that time, not a reserved future window.", - "Platform approves only source read-lane policy/role/delivery design here. Separate founder CCR decision and reviewed role/policy apply/readback are required; verify exact SA namespace/audience, native policy and all 900s/max/no-default attributes. Native positive/negative ESO login/path tests and safely reduced Ready/SecretSynced/version correspondence are separate evidence; no raw Secret/JWT outputs.", - "Access installs exact stopped candidate through reviewed Helm path and authenticates competing workload writers; actual observedGeneration, Deployment/owned ReplicaSets0, no owned/terminating/signer-SA/seed-mount pods and no HPA must hold. Exact native projection must equal8ce375f, independently of successful server dry-run. Secrets owns stopped/failclosed consumer; cluster owns refreshed headroom/storage/PV retention.", - "Founder runs only exact approved silent child/source/admission-file hash through attended Warden OIDC. Use private0700 receipt/admission custody,0600 files, trusted dependencies/runtime and explicit same-UID containment assessment. 5", - "# owner login is contained by warden; do not invoke it separately" - ], - "founder_act": { - "kind": "oidc_login", - "summary": "Interactive OIDC/MFA login via railiance-platform", - "details": { - "lane_id": "openbao-platform-admin-login", - "auth_method": "attended KeyCape OIDC/MFA at OpenBao auth mount netkingdom, role platform-admin", - "fetch_command": "warden access openbao-platform-admin-login --exec -- ", - "desk_hint": "warden desk --from-plan (act=oidc_login); execute only through: warden access openbao-platform-admin-login --exec -- " - } - }, - "ccr_stub": null, - "catalog": { - "path": "/home/worsch/ops-warden/registry/routing/catalog.yaml", - "source": "repo", - "content_hash": "b007b9598551", - "mtime_iso": "2026-10-20T11:42:31.833164+00:00", - "package_version": "0.1.2", - "entry_count": 49, - "active_count": 41, - "newest_reviewed": "2026-10-20", - "oldest_reviewed": "2026-06-18", - "stale_entry_count": 18, - "using_bundled": false, - "warnings": [ - "18 catalog entries past 90d review cadence", - "17 interim delegations need re-verifying (risk-scaled blocker cadence) \u2014 see `warden route gaps`" - ] - }, - "candidates": [ - { - "id": "openbao-platform-admin-login", - "title": "Attended OpenBao platform administration login", - "score": 9, - "status": "active", - "resolvable": true, - "exec_capable": true, - "warden_executes": false, - "lane": "login", - "risk": "high" - }, - { - "id": "openbao-structure-survey", - "title": "OpenBao existing-structure survey for a construction plan (section 2)", - "score": 9, - "status": "active", - "resolvable": false, - "exec_capable": false, - "warden_executes": false, - "lane": "ceremony", - "risk": "high" - }, - { - "id": "openbao-api-key", - "title": "API key, DB credential, or dynamic lease", - "score": 6, - "status": "active", - "resolvable": false, - "exec_capable": true, - "warden_executes": false, - "lane": "secret", - "risk": "high" - }, - { - "id": "openbao-shamir-recovery-ceremony", - "title": "Attended OpenBao Shamir seal and unseal recovery ceremony", - "score": 6, - "status": "active", - "resolvable": false, - "exec_capable": false, - "warden_executes": false, - "lane": "ceremony", - "risk": "high" - }, - { - "id": "ops-warden-warden-sign-token", - "title": "Scoped OpenBao token for ops-warden SSH signing (warden-sign)", - "score": 6, - "status": "active", - "resolvable": false, - "exec_capable": false, - "warden_executes": false, - "lane": "secret", - "risk": "high" - } - ], - "reasons": [ - "login lane requires interactive founder/operator identity act" - ], - "actor": null, - "domain": null -} diff --git a/docs/evidence/2026-10-27-coulomb-backup-admission.json b/docs/evidence/2026-10-27-coulomb-backup-admission.json deleted file mode 100644 index 542b9cc..0000000 --- a/docs/evidence/2026-10-27-coulomb-backup-admission.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "decision_id": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e", - "boundaries": { - "decision_id": "9557e2b8-2b37-5b94-a5e6-e5fe08d0351e", - "proof": { - "completed_objects_created": false, - "credential_values_printed": false, - "kv_version": 2, - "negative_login_denials": { - "wrong_audience": 403, - "wrong_serviceaccount": 403 - }, - "own_multipart_create_abort": true, - "positive_reader_and_revoke": true, - "provider_denials": { - "bucket_policy": 403, - "delete": 403, - "iam_list": 403, - "native_prefix": 403, - "sibling_prefix": 403 - }, - "reader_denials": { - "admin": 403, - "list": 403, - "metadata": 403, - "sibling": 403, - "write_cas0_existing": 403 - }, - "schema": "coulomb.backup-boundary-proof.v1", - "status": "verified" - }, - "verifier_sha256": "071285b9ecf33a1b80ff6af14f23ccf0a76cad1b30a9f9b34d27262ef1e636de", - "delivery_source_sha256": "afefce5b3de43539d4c4d02a4b92da395326eb2f3b410b5baf4586afeb05b107" - }, - "delivery": { - "application": "coulomb-social", - "ccr": "CCR-2026-0036", - "ccr_sha256": "1f624cd21f73bcbbb5920c6bd8d8dcf9ccee2e8389a06560644b35947c333925", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "delivered_at": "2026-10-27T21:18:21.952636+00:00", - "directory_mode": "0700", - "kv_path": "platform/workloads/coulomb-social/backup-primary", - "kv_version": 2, - "mode": "0600", - "outcome": "created", - "owner": "root:root", - "readback_equal": true, - "schema": "platform.application-backup-credential-delivery.v1", - "status": "delivered", - "target": "/etc/coulomb-social-backup/primary.env", - "token_revoked": true - } -} diff --git a/docs/evidence/2026-10-29-coulomb-provider-kit-recovery.json b/docs/evidence/2026-10-29-coulomb-provider-kit-recovery.json deleted file mode 100644 index bbdaab3..0000000 --- a/docs/evidence/2026-10-29-coulomb-provider-kit-recovery.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "age_identity_from_separate_provider_escrow": true, - "application": "coulomb-social", - "application_checks": { - "actual_pages_rendered_through_view": 39, - "content_files_unchanged": 82, - "database_restore": { - "content_files": 82, - "content_hashes_match": true, - "content_was_empty": false, - "database_container": "csoc-recovery-20261029214329", - "database_rows": 112, - "database_rows_and_sequences_match": true, - "database_tables": 13, - "elapsed_seconds": 4.43, - "network": "csoc-recovery-20261029214329-net", - "network_internal": true, - "postgres_image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70", - "published_ports": 0, - "restored_directory": "/tmp/csoc-recovery-20261029214329-0rvudi86", - "status": "restored_verified" - }, - "external_identity_provider_used": false, - "fixture_transaction_rolled_back": true, - "packaged_application_image": "sha256:9a646d8e01a61ba5316d3c2eb0273064e17f2d0300ad07af766ccedda65ecb18", - "production_access": false, - "real_browser_journey_verified": false, - "recovered_spaces_with_content": 1, - "restored_owner_access_verified": true, - "same_tenant_ungranted_page_denials": 39, - "status": "isolated_application_recovery_verified", - "synthetic_viewer_mutation_denials": 1, - "synthetic_viewer_reads": 1 - }, - "application_restore_verified": true, - "captured_at": "2026-10-29T21:22:46Z", - "completed_at": "2026-10-29T21:43:40.590019+00:00", - "fresh_docker_daemon": false, - "kit_members_verified": 2098, - "operator_account_recovery_time_included": false, - "original_node_or_plaintext_used": false, - "personal_session_used": false, - "protected_kit": { - "automatic_expiry": false, - "bucket": "railiance-coulomb-social-recovery", - "bytes": 277585920, - "object_key": "kits/db05c8f3b5587dc1fc09a5352c317dd3357527dbe72366ab7ca1041db578c834/recovery-kit.tar", - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "schema": "platform.coulomb-protected-recovery-kit.v1", - "sha256": "db05c8f3b5587dc1fc09a5352c317dd3357527dbe72366ab7ca1041db578c834", - "version_id": "1793309971213507" - }, - "real_browser_journey_verified": false, - "recovery_seconds": 30.232, - "registry_pulls_performed": false, - "restore": { - "content_files": 82, - "content_hashes_match": true, - "content_was_empty": false, - "database_container": "csoc-recovery-20261029214329", - "database_rows": 112, - "database_rows_and_sequences_match": true, - "database_tables": 13, - "elapsed_seconds": 4.43, - "network": "csoc-recovery-20261029214329-net", - "network_internal": true, - "postgres_image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70", - "published_ports": 0, - "restored_directory": "/tmp/csoc-recovery-20261029214329-0rvudi86", - "status": "restored_verified" - }, - "schema": "platform.coulomb-provider-kit-recovery.v1", - "stages_seconds": { - "all_kit_member_hashes_and_extraction": 2.501, - "application_image_load": 1.229, - "decrypt_and_validate_pair": 0.121, - "isolated_database_content_restore": 4.552, - "packaged_app_views_and_permissions": 6.555, - "postgres_image_load": 1.754, - "provider_age_escrow_retrieval": 0.503, - "provider_kit_retrieval": 12.373 - }, - "started_at": "2026-10-29T21:43:07.435367+00:00", - "status": "application_restored_verified", - "timers_activated": false, - "unpack": { - "application_restore_verified": false, - "captured_at": "2026-10-29T21:22:46Z", - "content_sha256": "2e81fa5fe2e1a82dfdec4df855a8c5643f59573cc0418a4a26f908d87727b125", - "database_sha256": "93f8f01edc9b9a2cd9580d0bab2048d1f5a8e2394e7fa266d419636f502e15f1", - "encrypted_sha256": "acbf92eda5074761bb6d00170e6e459e787359afcd377f2f18324ba17496510a", - "status": "decrypted_pair_verified", - "version_id": "1793309020705518" - }, - "object_key": "platform-pg/application-archives/coulomb-social/2026/10/29/20261029T212246Z-fd128164c7bb22a3a6c42acf.tar.age", - "version_id": "1793309020705518", - "ciphertext_sha256": "acbf92eda5074761bb6d00170e6e459e787359afcd377f2f18324ba17496510a", - "ciphertext_bytes": 2120392, - "image": "forgejo.coulomb.social/coulomb/coulomb-social@sha256:9a646d8e01a61ba5316d3c2eb0273064e17f2d0300ad07af766ccedda65ecb18", - "age_recipient": "age1qm30jk8q0cezey652dk2dhcr3d9vs528v6lg8mavl6d4q3e3vgnqmrmyy3", - "database_sha256": "93f8f01edc9b9a2cd9580d0bab2048d1f5a8e2394e7fa266d419636f502e15f1", - "content_sha256": "2e81fa5fe2e1a82dfdec4df855a8c5643f59573cc0418a4a26f908d87727b125", - "application_data_images_and_recovery_tools_from_provider_kit": true, - "bootstrap_api_client_preinstalled": true, - "executed_drill_source": "e1918b3", - "metadata_enriched_from_hash_verified_kit": true, - "protected_recovery_proof": { - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "bucket": "railiance-coulomb-social-recovery", - "object_key": "kits/receipts/a77477cac53086b83ad2cb512973ae0d1dfecba028a29a92e06a1bcfe5287f29/recovery-proof.json", - "version_id": "1793310476676486", - "sha256": "a77477cac53086b83ad2cb512973ae0d1dfecba028a29a92e06a1bcfe5287f29", - "bytes": 4237, - "independent_readback_verified": true, - "automatic_expiry": false - } -} diff --git a/docs/evidence/2026-10-29-coulomb-recovery-api-bootstrap.json b/docs/evidence/2026-10-29-coulomb-recovery-api-bootstrap.json deleted file mode 100644 index 16fe786..0000000 --- a/docs/evidence/2026-10-29-coulomb-recovery-api-bootstrap.json +++ /dev/null @@ -1,445 +0,0 @@ -{ - "programmatic_direction_reference": "6139e02b-3fb7-4eff-be8a-efe127c45ce5", - "applications": { - "bootstrap": "2a355842-e03b-42a3-8af7-6cd8f8b98bcd", - "custodian": "f80037a2-87c9-44d2-9163-3e6907af3ced", - "reader": "7a4055fc-a3f5-40bf-8013-cd0eec9028de" - }, - "automatic_expiry_enabled": false, - "bootstrap_key_revoked_verified": true, - "buckets": { - "railiance-coulomb-social-archives": { - "created": true, - "lifecycle_rules": [], - "policy": { - "Id": "coulomb-independent-recovery", - "Statement": [ - { - "Action": "s3:*", - "Effect": "Allow", - "Principal": { - "SCW": "user_id:3d029efb-4f70-4f82-a3bb-cefbe255232b" - }, - "Resource": [ - "railiance-coulomb-social-archives", - "railiance-coulomb-social-archives/*" - ], - "Sid": "PlatformOwner" - }, - { - "Action": "s3:*", - "Effect": "Allow", - "Principal": { - "SCW": "application_id:2a355842-e03b-42a3-8af7-6cd8f8b98bcd" - }, - "Resource": [ - "railiance-coulomb-social-archives", - "railiance-coulomb-social-archives/*" - ], - "Sid": "TemporaryConstruction" - }, - { - "Action": [ - "s3:GetObject", - "s3:GetObjectVersion" - ], - "Effect": "Allow", - "Principal": { - "SCW": "application_id:7a4055fc-a3f5-40bf-8013-cd0eec9028de" - }, - "Resource": "railiance-coulomb-social-archives/archives/*", - "Sid": "readerObjects" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions" - ], - "Condition": { - "StringLike": { - "s3:prefix": [ - "archives/", - "archives/*" - ] - } - }, - "Effect": "Allow", - "Principal": { - "SCW": "application_id:7a4055fc-a3f5-40bf-8013-cd0eec9028de" - }, - "Resource": "railiance-coulomb-social-archives", - "Sid": "readerList" - }, - { - "Action": [ - "s3:GetObject", - "s3:GetObjectVersion", - "s3:PutObject" - ], - "Effect": "Allow", - "Principal": { - "SCW": "application_id:f80037a2-87c9-44d2-9163-3e6907af3ced" - }, - "Resource": "railiance-coulomb-social-archives/archives/*", - "Sid": "custodianObjects" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions" - ], - "Condition": { - "StringLike": { - "s3:prefix": [ - "archives/", - "archives/*" - ] - } - }, - "Effect": "Allow", - "Principal": { - "SCW": "application_id:f80037a2-87c9-44d2-9163-3e6907af3ced" - }, - "Resource": "railiance-coulomb-social-archives", - "Sid": "custodianList" - }, - { - "Action": [ - "s3:GetObject", - "s3:GetObjectVersion", - "s3:PutObject", - "s3:AbortMultipartUpload", - "s3:ListMultipartUploadParts" - ], - "Effect": "Allow", - "Principal": { - "SCW": "application_id:681d00bf-b965-4323-b657-97bfde423511" - }, - "Resource": "railiance-coulomb-social-archives/archives/*", - "Sid": "publisherObjects" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions" - ], - "Condition": { - "StringLike": { - "s3:prefix": [ - "archives/", - "archives/*" - ] - } - }, - "Effect": "Allow", - "Principal": { - "SCW": "application_id:681d00bf-b965-4323-b657-97bfde423511" - }, - "Resource": "railiance-coulomb-social-archives", - "Sid": "publisherList" - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:GetBucketVersioning", - "s3:GetLifecycleConfiguration" - ], - "Effect": "Allow", - "Principal": { - "SCW": [ - "application_id:7a4055fc-a3f5-40bf-8013-cd0eec9028de", - "application_id:f80037a2-87c9-44d2-9163-3e6907af3ced", - "application_id:681d00bf-b965-4323-b657-97bfde423511" - ] - }, - "Resource": "railiance-coulomb-social-archives", - "Sid": "ArchiveMetadata" - } - ], - "Version": "2023-04-17" - }, - "public_acl_absent": true, - "versioning": "Enabled" - }, - "railiance-coulomb-social-recovery": { - "created": true, - "lifecycle_rules": [], - "policy": { - "Id": "coulomb-independent-recovery", - "Statement": [ - { - "Action": "s3:*", - "Effect": "Allow", - "Principal": { - "SCW": "user_id:3d029efb-4f70-4f82-a3bb-cefbe255232b" - }, - "Resource": [ - "railiance-coulomb-social-recovery", - "railiance-coulomb-social-recovery/*" - ], - "Sid": "PlatformOwner" - }, - { - "Action": "s3:*", - "Effect": "Allow", - "Principal": { - "SCW": "application_id:2a355842-e03b-42a3-8af7-6cd8f8b98bcd" - }, - "Resource": [ - "railiance-coulomb-social-recovery", - "railiance-coulomb-social-recovery/*" - ], - "Sid": "TemporaryConstruction" - }, - { - "Action": [ - "s3:GetObject", - "s3:GetObjectVersion" - ], - "Effect": "Allow", - "Principal": { - "SCW": "application_id:7a4055fc-a3f5-40bf-8013-cd0eec9028de" - }, - "Resource": "railiance-coulomb-social-recovery/kits/*", - "Sid": "readerObjects" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions" - ], - "Condition": { - "StringLike": { - "s3:prefix": [ - "kits/", - "kits/*" - ] - } - }, - "Effect": "Allow", - "Principal": { - "SCW": "application_id:7a4055fc-a3f5-40bf-8013-cd0eec9028de" - }, - "Resource": "railiance-coulomb-social-recovery", - "Sid": "readerList" - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:GetBucketVersioning", - "s3:GetLifecycleConfiguration" - ], - "Effect": "Allow", - "Principal": { - "SCW": "application_id:7a4055fc-a3f5-40bf-8013-cd0eec9028de" - }, - "Resource": "railiance-coulomb-social-recovery", - "Sid": "readerMetadata" - }, - { - "Action": [ - "s3:GetObject", - "s3:GetObjectVersion", - "s3:PutObject" - ], - "Effect": "Allow", - "Principal": { - "SCW": "application_id:f80037a2-87c9-44d2-9163-3e6907af3ced" - }, - "Resource": "railiance-coulomb-social-recovery/kits/*", - "Sid": "custodianObjects" - }, - { - "Action": [ - "s3:ListBucket", - "s3:ListBucketVersions" - ], - "Condition": { - "StringLike": { - "s3:prefix": [ - "kits/", - "kits/*" - ] - } - }, - "Effect": "Allow", - "Principal": { - "SCW": "application_id:f80037a2-87c9-44d2-9163-3e6907af3ced" - }, - "Resource": "railiance-coulomb-social-recovery", - "Sid": "custodianList" - }, - { - "Action": [ - "s3:GetBucketLocation", - "s3:GetBucketVersioning", - "s3:GetLifecycleConfiguration" - ], - "Effect": "Allow", - "Principal": { - "SCW": "application_id:f80037a2-87c9-44d2-9163-3e6907af3ced" - }, - "Resource": "railiance-coulomb-social-recovery", - "Sid": "custodianMetadata" - } - ], - "Version": "2023-04-17" - }, - "public_acl_absent": true, - "versioning": "Enabled" - } - }, - "completed_at": "2026-10-29T20:59:12.852425+00:00", - "decision": "27bbc1ac-e275-42be-ba34-6cf16eae4e78", - "native_application_recovery_verified": false, - "native_checks": { - "archives_custodian_delete": { - "denied": true, - "http_status": 403 - }, - "archives_custodian_outside_prefix": { - "denied": true, - "http_status": 403 - }, - "archives_custodian_policy_write": { - "denied": true, - "http_status": 403 - }, - "archives_publisher_delete": { - "denied": true, - "http_status": 403 - }, - "archives_publisher_outside_prefix": { - "denied": true, - "http_status": 403 - }, - "archives_publisher_policy_write": { - "denied": true, - "http_status": 403 - }, - "archives_reader_delete": { - "denied": true, - "http_status": 403 - }, - "archives_reader_outside_prefix": { - "denied": true, - "http_status": 403 - }, - "archives_reader_policy_write": { - "denied": true, - "http_status": 403 - }, - "archives_reader_put": { - "denied": true, - "http_status": 403 - }, - "archives_reader_version_get_hash": { - "verified": true, - "version_id": "1793307392673579" - }, - "custodian_iam_admin": { - "denied": true, - "http_status": 403 - }, - "kits_custodian_delete": { - "denied": true, - "http_status": 403 - }, - "kits_custodian_outside_prefix": { - "denied": true, - "http_status": 403 - }, - "kits_custodian_policy_write": { - "denied": true, - "http_status": 403 - }, - "kits_publisher_delete": { - "denied": true, - "http_status": 403 - }, - "kits_publisher_outside_prefix": { - "denied": true, - "http_status": 403 - }, - "kits_publisher_policy_write": { - "denied": true, - "http_status": 403 - }, - "kits_reader_delete": { - "denied": true, - "http_status": 403 - }, - "kits_reader_outside_prefix": { - "denied": true, - "http_status": 403 - }, - "kits_reader_policy_write": { - "denied": true, - "http_status": 403 - }, - "kits_reader_put": { - "denied": true, - "http_status": 403 - }, - "kits_reader_version_get_hash": { - "verified": true, - "version_id": "1793307384956710" - }, - "publisher_create_readback": { - "verified": true - }, - "publisher_escrow": { - "denied": true, - "http_status": 403 - }, - "publisher_iam_admin": { - "denied": true, - "http_status": 403 - }, - "publisher_own_multipart_abort": { - "verified": true - }, - "publisher_protected_kit_get": { - "denied": true, - "http_status": 403 - }, - "reader_escrow": { - "denied": true, - "http_status": 403 - }, - "reader_iam_admin": { - "denied": true, - "http_status": 403 - } - }, - "organization_wide_grants": false, - "personal_browser_context_closed": true, - "personal_state_saved": false, - "private_values_printed": false, - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "project_only_policies_verified": 4, - "protected_secret_references": { - "coulomb-social-age-recovery": { - "id": "634e4b9f-0b61-4329-a83c-1ed0e76c4636", - "revision": 1 - }, - "coulomb-social-recovery-access-canary": { - "id": "db825503-cf12-4bfd-8cc5-f16d09edd8b5", - "revision": 1 - }, - "coulomb-social-recovery-custodian-api": { - "id": "cf44e9bd-9edc-450b-9f10-564ee3008305", - "revision": 1 - }, - "coulomb-social-recovery-reader-api": { - "id": "241742f7-d3b5-4163-8d87-3a3d9585778d", - "revision": 1 - } - }, - "public_recipient": "age1qm30jk8q0cezey652dk2dhcr3d9vs528v6lg8mavl6d4q3e3vgnqmrmyy3", - "publisher_pair_replaced": false, - "routine_age_and_both_pairs_retrieved_without_personal_session": true, - "schema": "platform.coulomb-recovery-bootstrap.v1", - "temporary_pair_removed": true, - "timers_activated": false -} diff --git a/docs/evidence/2026-10-31-coulomb-provider-kit-post-date-recovery.json b/docs/evidence/2026-10-31-coulomb-provider-kit-post-date-recovery.json deleted file mode 100644 index 319e442..0000000 --- a/docs/evidence/2026-10-31-coulomb-provider-kit-post-date-recovery.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "schema": "platform.coulomb-provider-kit-recovery.v1", - "status": "application_restored_verified", - "started_at": "2026-10-31T07:39:13.861634+00:00", - "completed_at": "2026-10-31T07:40:07.620278+00:00", - "stages_seconds": { - "provider_kit_retrieval": 18.124, - "all_kit_member_hashes_and_extraction": 4.717, - "provider_age_escrow_retrieval": 0.501, - "application_image_load": 2.432, - "postgres_image_load": 3.177, - "decrypt_and_validate_pair": 0.131, - "isolated_database_content_restore": 7.451, - "packaged_app_views_and_permissions": 9.607 - }, - "recovery_seconds": 46.692, - "protected_kit": { - "schema": "platform.coulomb-protected-recovery-kit.v1", - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "bucket": "railiance-coulomb-social-recovery", - "object_key": "kits/4166eb8ba97f7bd79e912c2efaee2622fcd472b05ed80070ccf7e6728fec5bd6/recovery-kit.tar", - "version_id": "1793432303941465", - "sha256": "4166eb8ba97f7bd79e912c2efaee2622fcd472b05ed80070ccf7e6728fec5bd6", - "bytes": 276019200, - "automatic_expiry": false - }, - "kit_members_verified": 2098, - "application": "coulomb-social", - "captured_at": "2026-10-31T07:13:52Z", - "application_restore_verified": true, - "application_data_images_and_recovery_tools_from_provider_kit": true, - "bootstrap_api_client_preinstalled": true, - "age_identity_from_separate_provider_escrow": true, - "original_node_or_plaintext_used": false, - "personal_session_used": false, - "registry_pulls_performed": false, - "fresh_docker_daemon": false, - "operator_account_recovery_time_included": false, - "real_browser_journey_verified": false, - "unpack": { - "status": "decrypted_pair_verified", - "version_id": "1793430889823443", - "encrypted_sha256": "840e633047fddd88c6e6a1a3d6909d9af1ad80eaba8c90f83d8e81188dfc17a4", - "captured_at": "2026-10-31T07:13:52Z", - "database_sha256": "498a11e221dcdef38075d23ef89a26f213316c6164addb70d47bfcb98a1999a7", - "content_sha256": "644db8f09684bd4a79c126a01a329904351c30bb340295b29c8bb0ca71092dfe", - "application_restore_verified": false - }, - "restore": { - "status": "restored_verified", - "database_tables": 13, - "database_rows": 114, - "database_rows_and_sequences_match": true, - "content_files": 86, - "content_hashes_match": true, - "network_internal": true, - "published_ports": 0, - "postgres_image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70", - "elapsed_seconds": 7.35, - "content_was_empty": false, - "database_container": "csoc-recovery-20261031073947", - "network": "csoc-recovery-20261031073947-net" - }, - "application_checks": { - "status": "isolated_application_recovery_verified", - "recovered_spaces_with_content": 1, - "actual_pages_rendered_through_view": 40, - "restored_owner_access_verified": true, - "synthetic_viewer_reads": 1, - "synthetic_viewer_mutation_denials": 1, - "same_tenant_ungranted_page_denials": 40, - "fixture_transaction_rolled_back": true, - "production_access": false, - "external_identity_provider_used": false, - "real_browser_journey_verified": false, - "content_files_unchanged": 86, - "packaged_application_image": "sha256:512819463a1902b43a615beee14be7bf97e6a05e016385082b9380692ad1a8cd", - "database_restore": { - "status": "restored_verified", - "database_tables": 13, - "database_rows": 114, - "database_rows_and_sequences_match": true, - "content_files": 86, - "content_hashes_match": true, - "network_internal": true, - "published_ports": 0, - "postgres_image_id": "sha256:ff23cdce56cac62ada6f66013e1a50864c0abbe79d132d40b6e05bd80f378a70", - "elapsed_seconds": 7.35, - "content_was_empty": false, - "database_container": "csoc-recovery-20261031073947", - "network": "csoc-recovery-20261031073947-net", - "restored_directory": "/home/worsch/.config/railiance-platform/coulomb-social-recovery/drill-20261031T071352Z/provider-only-recovery/csoc-recovery-20261031073947-fkl67w71" - } - }, - "timers_activated": false, - "object_key": "platform-pg/application-archives/coulomb-social/2026/10/31/20261031T071352Z-abf5997f01a14d9cb05e4bba.tar.age", - "version_id": "1793430889823443", - "ciphertext_sha256": "840e633047fddd88c6e6a1a3d6909d9af1ad80eaba8c90f83d8e81188dfc17a4", - "ciphertext_bytes": 2181848, - "image": "forgejo.coulomb.social/coulomb/coulomb-social@sha256:512819463a1902b43a615beee14be7bf97e6a05e016385082b9380692ad1a8cd", - "age_recipient": "age1qm30jk8q0cezey652dk2dhcr3d9vs528v6lg8mavl6d4q3e3vgnqmrmyy3", - "database_sha256": "498a11e221dcdef38075d23ef89a26f213316c6164addb70d47bfcb98a1999a7", - "content_sha256": "644db8f09684bd4a79c126a01a329904351c30bb340295b29c8bb0ca71092dfe", - "protected_recovery_proof": { - "project_id": "06d107bc-9d84-4672-b2a0-95701cc0c07f", - "bucket": "railiance-coulomb-social-recovery", - "object_key": "kits/receipts/b93e3003627581eb510086ba207bf283085d88cb9b3dfd0e74ffa8ebf02c39f0/recovery-proof.json", - "version_id": "1793432409572654", - "sha256": "b93e3003627581eb510086ba207bf283085d88cb9b3dfd0e74ffa8ebf02c39f0", - "bytes": 4365, - "independent_readback_verified": true, - "automatic_expiry": false - }, - "temporary_drill_identity_removed": true, - "restore_work_root_override": "protected provider-only recovery directory", - "driver_sha256": "68415a3165f6f137919fc06dbbcc09c8ec025500e86c8649d3b1c0ce9d884028" -} diff --git a/docs/evidence/email-connect-smtp-admission-preparation-2026-10-31.json b/docs/evidence/email-connect-smtp-admission-preparation-2026-10-31.json deleted file mode 100644 index f7b0a5d..0000000 --- a/docs/evidence/email-connect-smtp-admission-preparation-2026-10-31.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "schema": "platform.email-connect-smtp-admission-preparation.v1", - "prepared_at": "2026-10-31T10:47:52.552287+00:00", - "proposal": { - "status": "prepared", - "proposal_digest": "sha256:14a051abc35d94b9fbfbc8b47f19f9ba7bc95c779ea98fbb9083072386e72160", - "ccr_status": "proposed", - "binding_confirmed": false, - "proposal": { - "schema": "email-connect.smtp-admission-proposal.v1", - "scope": { - "id": "CCR-2026-0056", - "kind": "credential-change-request", - "schema_version": 1, - "request_type": "workload-kv-read", - "title": "Email-connect platform SMTP password projection", - "created": "2026-10-31", - "requester": { - "agent": "coulomb-social", - "reason": "CSOC-WP-0015-T02 platform sender migration; user selected platform-root with Bernd as the human reply and bounce operator. This ownership choice is not credential admission." - }, - "target": { - "domain": "communication", - "tenant": "platform", - "workload": "email-connect", - "environment": "production", - "purpose": "Fixed admitted registration and invitation templates from platform@coulomb.social; no new caller or arbitrary notification authority." - }, - "openbao": { - "mount": "platform", - "kv_path": "platform/workloads/railiance-telemetry/smtp", - "fields": [ - "SMTP_PASSWORD" - ], - "metadata_read": false, - "token_self_lifecycle": true, - "policy_name": "email-connect-platform-smtp-eso", - "policy_file": "openbao/policies/email-connect-platform-smtp-eso.hcl", - "auth": { - "method": "kubernetes", - "mount": "kubernetes", - "role": "email-connect-platform-smtp-eso", - "bound_claims": { - "service_account_names": [ - "email-connect-platform-smtp-eso" - ], - "service_account_namespaces": [ - "email-connect" - ] - }, - "policies": [ - "email-connect-platform-smtp-eso" - ], - "ttl": "15m", - "audience": "openbao", - "token_max_ttl": "15m", - "token_explicit_max_ttl": "15m", - "token_no_default_policy": true - } - }, - "access_frontdoor": { - "type": "external-secrets", - "catalog_id": "email-connect-platform-smtp", - "selector": "Namespace email-connect and ServiceAccount email-connect-platform-smtp-eso only", - "resolvable": false - }, - "delivery": { - "surface": "external-secrets", - "namespace": "email-connect", - "service_account": "email-connect-platform-smtp-eso", - "secret_store": "openbao-email-connect-platform-smtp", - "external_secret": "email-connect-platform-smtp", - "target_secret": "email-connect-platform-smtp", - "target_key": "password", - "target": "Dedicated namespace SecretStore and password-only ExternalSecret; caller tokens unchanged.", - "bootstrap_command": "Existing mailbox is reused; this request does not create or rotate a value." - }, - "risk": { - "classification": "high", - "notes": [ - "Shared mailbox reader outside the automatic single-reader profile; existing telemetry and Authelia grants must be preserved.", - "OpenBao authorizes the whole exact KV record, not individual fields. ESO projects only SMTP_PASSWORD; this is not field-level OpenBao access control.", - "SMTP credential can send as the platform mailbox. No IMAP access or mailbox forwarding operation is included.", - "No new application sender, mailbox scan, suppression operator or timer is activated by admission alone." - ] - }, - "verification": { - "positive": [ - "Exact scoped role and policy readback; certificate-verified STARTTLS AUTH without mail submission.", - "Native service account binding confirmation precedes apply; actual workload login and ESO Ready are verified separately after projection." - ], - "negative": [ - "Changed policy, role, settings, approval, source digest or unconfirmed binding refuses.", - "Agent boundary denies both KV data and metadata; workload token denies metadata, sibling and mutation capabilities." - ], - "activation_conditions": [ - "Exact CCR approval and native bound claims confirmed through the owner workflow.", - "Attended owner helper receipt verified with unchanged mailbox version and other consumers; Warden session revocation confirmed.", - "Native workload positive and negative login, scoped projection Ready and separate supervised application sender rollout accepted." - ], - "evidence": [] - }, - "lifecycle": { - "deactivate": "Stop the admitted consumer and reconcile pending sends, revoke only this dedicated reader role/policy, and remove only its projection after owner verification. Preserve the mailbox and other consumers.", - "rotate": "Coordinate provider rotation across telemetry, Authelia and email-connect; verify all projections and consumers before retiring the predecessor. Never rotate a single consumer copy independently.", - "compromised": "Pause affected sending and coordinate shared-mailbox credential rotation through the owner; preserve ledger evidence and investigate all admitted consumers. Do not auto-resend unknown outcomes." - } - }, - "openbao_cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf", - "role": { - "bound_service_account_names": [ - "email-connect-platform-smtp-eso" - ], - "bound_service_account_namespaces": [ - "email-connect" - ], - "token_policies": [ - "email-connect-platform-smtp-eso" - ], - "audience": "openbao", - "token_ttl": "15m", - "token_max_ttl": "15m", - "token_explicit_max_ttl": "15m", - "token_no_default_policy": true - }, - "policy": "path \"platform/data/workloads/railiance-telemetry/smtp\" { capabilities = [\"read\"] }\npath \"auth/token/lookup-self\" { capabilities = [\"read\"] }\npath \"auth/token/revoke-self\" { capabilities = [\"update\"] }\n" - } - }, - "tests": { - "command": "python3 -m unittest discover -s tests -p 'test_*smtp*.py' -q", - "passed": 29, - "new_admission_cases": 15, - "failed": 0 - }, - "test_source_sha256": "05bfbea264ab8506a22671501f9c9921ed4a43935435edeea8b80dc9cddf3709", - "openbao_public_health_observation": { - "endpoint": "http://127.0.0.1:18200/v1/sys/health", - "cluster_id": "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf", - "initialized": true, - "sealed": false, - "version": "2.5.4" - }, - "execution_ready": false, - "ccr_approved": false, - "native_binding_confirmed": false, - "owner_admission_executed": false, - "email_sent": false, - "private_credentials_read_by_agent": false, - "source_files": [ - { - "path": "scripts/email_connect_smtp_delivery.py", - "sha256": "85de6bfeefe505594f71217fb4949762a68466f0ee79be30745e0449338e7f93" - }, - { - "path": "scripts/telemetry_smtp_delivery.py", - "sha256": "a0e4f60e1ec8874f4e3c013b8c9819df461503befcfd4ffee8d981e29025db95" - }, - { - "path": "scripts/state_hub_preflight_lane.py", - "sha256": "8bf63beda028de7c2375084afb7c22fb00a731c54ebf0b3ba369a699d920e0f5" - }, - { - "path": "scripts/repair_eso_kubernetes_auth.py", - "sha256": "dd58e4ab3f88c777aacb44368c2c099b6e32637ce76f5157e7a874cfbe9d5f80" - }, - { - "path": "scripts/telemetry_smtp_entry.py", - "sha256": "bdc7baa2340a99a247c0d88eeb4f49dc80d62023b3974f393870dfcad29e1cd0" - }, - { - "path": "scripts/credential-change.py", - "sha256": "35bb9287f457ca5282759b9fd3ec9ac12a08130fc70af8230d569d6a3c957ada" - }, - { - "path": "openbao/policies/email-connect-platform-smtp-eso.hcl", - "sha256": "023c502bb72453224d8ba05db75871289f69681e9ae6a8461504c867ef9c0e38" - } - ], - "representation_note": "Source hashes are listed separately; reconstruct proposal.source_sha256 from source_files for digest verification." -} diff --git a/docs/evidence/email-connect-smtp-foundation-preparation-2026-10-31.json b/docs/evidence/email-connect-smtp-foundation-preparation-2026-10-31.json deleted file mode 100644 index d0ef570..0000000 --- a/docs/evidence/email-connect-smtp-foundation-preparation-2026-10-31.json +++ /dev/null @@ -1,111 +0,0 @@ -{ - "schema": "platform.email-connect-smtp-foundation-preparation.v1", - "prepared_at": "2026-10-31T10:59:04.381919+00:00", - "proposal": { - "proposal_digest": "sha256:f8da6c831160e1c6354ce54f936809d2d4fb48eb45eb23484dc0bcdfe0bb52f0", - "proposal": { - "schema": "platform.email-connect-smtp-foundation-proposal.v1", - "foundation": { - "schema": "platform.email-connect-smtp-foundation.v1", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", - "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", - "deployment_generation": 74, - "resources": [ - { - "apiVersion": "v1", - "automountServiceAccountToken": false, - "kind": "ServiceAccount", - "metadata": { - "name": "email-connect-platform-smtp-eso", - "namespace": "email-connect" - } - }, - { - "apiVersion": "external-secrets.io/v1", - "kind": "SecretStore", - "metadata": { - "name": "openbao-email-connect-platform-smtp", - "namespace": "email-connect" - }, - "spec": { - "provider": { - "vault": { - "auth": { - "kubernetes": { - "mountPath": "kubernetes", - "role": "email-connect-platform-smtp-eso", - "serviceAccountRef": { - "audiences": [ - "openbao" - ], - "name": "email-connect-platform-smtp-eso" - } - } - }, - "path": "platform", - "server": "http://openbao.openbao.svc:8200", - "version": "v2" - } - } - } - }, - { - "apiVersion": "external-secrets.io/v1", - "kind": "ExternalSecret", - "metadata": { - "name": "email-connect-platform-smtp", - "namespace": "email-connect" - }, - "spec": { - "data": [ - { - "remoteRef": { - "conversionStrategy": "Default", - "decodingStrategy": "None", - "key": "workloads/railiance-telemetry/smtp", - "metadataPolicy": "None", - "property": "SMTP_PASSWORD" - }, - "secretKey": "password" - } - ], - "refreshInterval": "1h", - "secretStoreRef": { - "kind": "SecretStore", - "name": "openbao-email-connect-platform-smtp" - }, - "target": { - "creationPolicy": "Owner", - "deletionPolicy": "Retain", - "name": "email-connect-platform-smtp", - "template": { - "engineVersion": "v2", - "mergePolicy": "Replace", - "type": "Opaque" - } - } - } - } - ] - }, - "admission_digest": "sha256:14a051abc35d94b9fbfbc8b47f19f9ba7bc95c779ea98fbb9083072386e72160", - "kubeconfig": "/home/worsch/.kube/config-railiance01", - "controller_sha256": "4f1355c0cd48368fa1ae4dcb0138296b3880359329a4ace35dee51783b0ff13f" - }, - "ccr_status": "proposed" - }, - "validation": { - "smtp_tests_passed": 41, - "smtp_tests_failed": 0, - "server_dry_run_resources": 3, - "native_target_secret_absent": true, - "target_metadata_only_api": true - }, - "test_source_sha256": "afdb7877637ced8a1d755bea1b609378b9b74603f3561762ed1dcb38ddd8d82c", - "approved": false, - "executed": false, - "resources_installed": false, - "email_sent": false, - "secrets_read_by_agent": false -} diff --git a/docs/keycape-client-secret-generate-type-pin-review.md b/docs/keycape-client-secret-generate-type-pin-review.md deleted file mode 100644 index 5ca47d3..0000000 --- a/docs/keycape-client-secret-generate-type-pin-review.md +++ /dev/null @@ -1,30 +0,0 @@ -# Exact case-type pin correction — 2026-10-10 - -The broker's `CASE_TYPE` still required browser-RP @1.0 after the founder -authorized @1.1 and net-kingdom re-pinned the accepted Forgejo case. That made -the real case fail `case_type_mismatch` before any backend call. - -`keycape_client_secret_generate.py` now accepts exactly -`oidc-browser-relying-party@1.1`; @1.0, arbitrary later versions and other -types remain refused. Existing exact-byte case-ref, materialized status, -client match, confidential/reserved identity, caller/root/agent boundary, -scoped grant, CAS0 create-once, silent receipt and revocation checks are unchanged. - -Authority evidence: NK-WP-0048, 2026-10-10 founder go for 1.1; source type -`security-case-types/oidc-browser-relying-party/type.yaml` version 1.1, -acknowledged; accepted Forgejo case carries @1.1. No case status, live backend -or credential changed during this correction. - -Regression uses the actual accepted Forgejo file shape with materialized -status solely in the offline fake test fixture. Real accepted (unmaterialized) -case remains refused. Ten subprocess CLI tests pass against a fake bao in an -isolated temporary tree, including silence, exact mismatch before any backend -call, reserved/agent/root identity guards, scoped token delivery, CAS0/race and -revocation. No live broker execution is claimed. - -The materialization/run circularity remains an owner contract question: -MASON-WP-0010 describes mint-secret as needed before materialized, while -WARDEN-WP-0044 and this broker require materialized before mint-secret. -Owner review should define structural materialization with exact applied reader -receipts, then follow-through creates custody and verifies delivery. This -correction does not silently relax status gating or assert those receipts. diff --git a/docs/keycape-issuer-eso-contract.md b/docs/keycape-issuer-eso-contract.md deleted file mode 100644 index 56cfa1e..0000000 --- a/docs/keycape-issuer-eso-contract.md +++ /dev/null @@ -1,68 +0,0 @@ -# Proposed fixed issuer ESO contract — RPF-1.3-T4 / NK48-R5-F2 - -Status: proposed source; not admitted, deployed, or owner-acknowledged. This is a -new Platform declaration for review, not an existing Forgejo grant discovery. - -The only substitution in `manifests/templates/keycape-issuer-eso.yaml` -(sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743) is the admitted `client_id`. The proposed issuer -store is **ClusterSecretStore `openbao-keycape-{client_id}-issuer`**, cluster -scoped (no metadata namespace), restricted by conditions to ExternalSecrets in -`sso`. For Forgejo it is `openbao-keycape-forgejo-issuer`. The suffix makes the -issuer purpose explicit and keeps type-admitted IDs ending in `-` representable. -The store's DNS subdomain name can exceed 63 characters; the existing adapter's -63-character target Secret reference bound is preserved (client ID at most 48). -No type parameter is added or changed. - -The provider is `http://openbao.openbao.svc:8200`, KV v2 mount `platform`, -Kubernetes auth mount `kubernetes`, role `external-secrets-keycape-{client_id}`, -serviceAccountRef `external-secrets/external-secrets`, audience `openbao`. -CCR-2026-0041 already records that Forgejo role/identity/audience, not this new -store name or an unrestricted client family grant. Other clients still require -their own existing owner-controlled role/case admission. - -The ExternalSecret is `sso/keycape-{client_id}-client`, references precisely that -ClusterSecretStore, refreshes every 5m and targets the same name with Owner -creation and Retain remote deletion. Its only data entry maps `client-secret` -from remoteRef `workloads/keycape/{client_id}`, property `CLIENT_SECRET`. The -provider mount plus mount-relative remoteRef equals custody reference -`platform/workloads/keycape/{client_id}`; do not double-prefix `platform/`. -This is distinct from the Apps-owned consumer namespace SecretStore -`openbao-keycape-{client_id}-consumer` and its dedicated consumer identity. - -Store provisioning belongs to Platform's separate owner-controlled path. The -proposed `platform-secret-delivery` lane retains ONLY create/get/watch/delete on -the exact ExternalSecret in `sso`; this source does not delegate store creation -or Secret read/list. Never apply the multi-document template through that lane. -The pure `scripts/keycape_issuer_eso.py` module exposes only the ExternalSecret -document through `external_secret()` and validates fixed public observations -through `require_fixed_store()` and `require_ready_delivery()`. It performs no -API call and issues no evidence/receipt. It is not a real capability or admission -boundary; a caller-supplied observation alone establishes no authenticity. - -The existing RPF-1.3-T4 delivery binding must validate the case/client binding, -verify this exact preexisting store's public identity/spec/Ready state, apply or -compare the exact ExternalSecret, and wait for matching spec plus Ready=True. -The proposed helper accepts extra controller metadata but deliberately refuses -spec drift. Actual API defaulting/normalization and installed ESO schema support -must be checked in that implementation; these fixtures establish no native -compatibility or credential isolation. ESO's shared controller remains trusted. -No Secret bodies, values, arbitrary remoteRefs, or raw errors may enter receipts. - -Existing owner requirements remain: closed `netkingdom.deliver-secret.v1` -metadata receipt with authenticated before/after UID/resourceVersion provenance, -unchanged objects reported NO_CHANGE, attempt-session revocation, and created-only -rollback guarded by exact UID/resourceVersion (drift or uncertain ownership goes -to owner reconciliation). The helper neither deletes a target Secret nor creates -an OpenBao role/policy nor changes CCR-0041. These capabilities and catalog routing -remain unimplemented/unaccepted under RPF-1.3-T4. Independent Platform source -review of this declaration precedes adopting the conditional NetKingdom type -text; no descriptor acceptance, acknowledgment, pin, grant, catalog or native -permission is inferred. - -Grounding: CCR-2026-0041 exact role/identity; the approved historical -`docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml` issuer -ClusterSecretStore and target pattern; `scripts/catalog_lane_scaffold.py` -ClusterSecretStore/namespace restriction and mount-relative remoteRef model; -RPF-1.3-C1 and RPF-1.3-T4 in the frozen owner review. The scaffold's historical -audience field placement differs from the issuer draft; this candidate uses -the issuer draft's serviceAccountRef.audiences, not a claim of installed support. diff --git a/docs/object-storage/railiance-platform-pg-backup-policy.json b/docs/object-storage/railiance-platform-pg-backup-policy.json deleted file mode 100644 index fe804bd..0000000 --- a/docs/object-storage/railiance-platform-pg-backup-policy.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "Version": "2023-04-17", - "Id": "resource-control-scoped-backup", - "Statement": [ - { - "Sid": "BackupBucketProbe", - "Effect": "Allow", - "Principal": {"SCW": "application_id:ee80333e-5bab-4a04-85a9-5bdb46b7b3c1"}, - "Action": ["s3:GetBucketLocation", "s3:GetBucketVersioning", "s3:GetLifecycleConfiguration", "s3:ListBucket"], - "Resource": "railiance-platform-pg-backup", - "Condition": {"Bool": {"aws:SecureTransport": "true"}} - }, - { - "Sid": "BackupListPrefix", - "Effect": "Allow", - "Principal": {"SCW": "application_id:ee80333e-5bab-4a04-85a9-5bdb46b7b3c1"}, - "Action": ["s3:ListBucketMultipartUploads", "s3:ListBucketVersions"], - "Resource": "railiance-platform-pg-backup", - "Condition": {"StringLike": {"s3:prefix": ["platform-pg/", "platform-pg/*"]}} - }, - { - "Sid": "BackupObjectsUnderPrefix", - "Effect": "Allow", - "Principal": {"SCW": "application_id:ee80333e-5bab-4a04-85a9-5bdb46b7b3c1"}, - "Action": ["s3:AbortMultipartUpload", "s3:DeleteObject", "s3:DeleteObjectVersion", "s3:GetObject", "s3:GetObjectVersion", "s3:ListMultipartUploadParts", "s3:PutObject"], - "Resource": "railiance-platform-pg-backup/platform-pg/*" - }, - { - "Sid": "CoulombSocialBackupProbe", - "Effect": "Allow", - "Principal": {"SCW": "application_id:681d00bf-b965-4323-b657-97bfde423511"}, - "Action": ["s3:GetBucketLocation", "s3:GetBucketVersioning", "s3:GetLifecycleConfiguration"], - "Resource": "railiance-platform-pg-backup", - "Condition": {"Bool": {"aws:SecureTransport": "true"}} - }, - { - "Sid": "CoulombSocialBackupList", - "Effect": "Allow", - "Principal": {"SCW": "application_id:681d00bf-b965-4323-b657-97bfde423511"}, - "Action": ["s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:ListBucketVersions"], - "Resource": "railiance-platform-pg-backup", - "Condition": {"StringLike": {"s3:prefix": ["platform-pg/application-archives/coulomb-social/", "platform-pg/application-archives/coulomb-social/*"]}} - }, - { - "Sid": "CoulombSocialBackupObjects", - "Effect": "Allow", - "Principal": {"SCW": "application_id:681d00bf-b965-4323-b657-97bfde423511"}, - "Action": ["s3:AbortMultipartUpload", "s3:GetObject", "s3:GetObjectVersion", "s3:ListMultipartUploadParts", "s3:PutObject"], - "Resource": "railiance-platform-pg-backup/platform-pg/application-archives/coulomb-social/*" - }, - { - "Sid": "EmailConnectBackupProbe", - "Effect": "Allow", - "Principal": {"SCW": "application_id:0eb0f958-77a4-4c70-9aa7-14ba46411415"}, - "Action": ["s3:GetBucketLocation", "s3:GetBucketVersioning", "s3:GetLifecycleConfiguration"], - "Resource": "railiance-platform-pg-backup", - "Condition": {"Bool": {"aws:SecureTransport": "true"}} - }, - { - "Sid": "EmailConnectBackupList", - "Effect": "Allow", - "Principal": {"SCW": "application_id:0eb0f958-77a4-4c70-9aa7-14ba46411415"}, - "Action": ["s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:ListBucketVersions"], - "Resource": "railiance-platform-pg-backup", - "Condition": {"StringLike": {"s3:prefix": ["platform-pg/application-archives/email-connect/", "platform-pg/application-archives/email-connect/*"]}} - }, - { - "Sid": "EmailConnectBackupObjects", - "Effect": "Allow", - "Principal": {"SCW": "application_id:0eb0f958-77a4-4c70-9aa7-14ba46411415"}, - "Action": ["s3:AbortMultipartUpload", "s3:GetObject", "s3:GetObjectVersion", "s3:ListMultipartUploadParts", "s3:PutObject"], - "Resource": "railiance-platform-pg-backup/platform-pg/application-archives/email-connect/*" - } - ] -} diff --git a/docs/openbao-isolated-restore.md b/docs/openbao-isolated-restore.md deleted file mode 100644 index 85b3473..0000000 --- a/docs/openbao-isolated-restore.md +++ /dev/null @@ -1,159 +0,0 @@ -# Isolated original-OpenBao restore (RPF-WP-0064) - -Adapter: `scripts/openbao-isolated-restore/`. Workplan: -`workplans/RPF-WP-0064-isolated-original-openbao-restore.md`. - -This restores the original OpenBao Raft snapshot into a disposable, -networkless container on the founder workstation. It unseals the snapshot with -the original Shamir shares and reads back configuration metadata only. It -claims no restore capability for production, no consumer continuity, no live -JWT login, no phase B network and no owner acceptance. Every receipt lists -these under `claims_not_made`. - -## Fixed properties - -| Property | Value | -| --- | --- | -| Where | Founder workstation Docker only. The launcher refuses `railiance01*` hosts and unsets `KUBECONFIG`, `BAO_*` and `VAULT_*`. | -| Image | `quay.io/openbao/openbao@sha256:`, run with `--pull=never`. The admission must name an exact digest. Original-material runs also require `PINNED_IMAGE_DIGEST` in `restore_adapter.py`, which T06 sets after rapp-openbao confirms the production pin. Until then they refuse. | -| Container | `--network none`, `--read-only`, `--cap-drop ALL`, `no-new-privileges`, `--user 100:1000`, and tmpfs only for `/openbao/data`, `/openbao/config`, `/openbao/file`, `/openbao/logs` and `/tmp`. The image's VOLUME paths are overlaid with tmpfs, so no Docker volume is created. There are no binds, published ports or kubeconfig. | -| Listener | `127.0.0.1:8200` inside the container namespace only. Unauthenticated generate-root is enabled on this listener alone, because OpenBao 2.x disables it by default and the readback credential needs it. | -| Network proof | `docker container inspect`: `NetworkMode=none`, networks `["none"]`, no ports, mounts or binds, the exact tmpfs set and not privileged. `/proc/net/dev` inside the container lists only `lo`. Two `wget` probes (`192.0.2.1` and `kc.coulomb.social`) must both fail. | -| Deadline | 45 minutes (`max_duration_seconds: 2700`) from the session start, capped by the admission `not_after`. A reaper process enforces it as a backstop, with 60 s of grace. | -| Admission namespace | `railiance-openbao-restore-attempt`. It is an SSH signature over the exact admission bytes. The anchor is `/etc/railiance-openbao-restore/allowed_signers`, overridable with `RAILIANCE_OPENBAO_RESTORE_ANCHOR`. For original material the anchor must not be writable by the running account. | - -## Custody path - -The prompts are hidden (`getpass` on the controlling TTY). Custody values never -pass through argv, the environment, files or logs. - -1. **Backup age identity.** It is checked against the admission's - `age_recipient` with `age-keygen -y`, then held in a sealed memfd. It is - passed to `age -d` only as `/dev/fd/N` and closed straight after decryption. -2. **Snapshot.** The ciphertext size and sha256 must match the admission. The - plaintext sha256 must match the RPF-WP-0015 receipt's `snapshot_sha256` - (admission `plaintext_sha256`). The plaintext is held in a sealed memfd and - streamed to `bao operator raft snapshot restore -force /dev/stdin` over - `docker exec` stdin. It never reaches disk. -3. **Throwaway initialization.** A 1-of-1 init of the empty target. Its share and - credential stay in process memory. The restore invalidates them, and a failed - `lookup-self` after the restore proves this. -4. **Two original unseal shares.** After the restore the target must report - `sealed: true`. The two shares then unseal it, and the restored `cluster_id` - and Raft applied index are compared with the admission. -5. **Readback credential.** Decision for T02 review: generate-root from the two - shares already entered, with the OTP decoded in memory. The operator OIDC - path cannot work without a network. The credential is revoked with - `revoke-self` before teardown, and the revocation is verified. - -## Readback - -Readback is metadata only. The allow-list in `ALLOWED_READS` contains no -`/data/` path. - -| Object | Path | Compared with | -| --- | --- | --- | -| `auth_mount_keycape_services` | `sys/auth` | type `jwt` | -| `auth_mount_netkingdom` | `sys/auth` | type `oidc`/`jwt` | -| `kv_mount_platform` | `sys/mounts` | type `kv`, version 2 | -| `keycape_services_config` | `auth/keycape-services/config` | `openbao/auth/keycape-services-config.json` | -| `secrets_engine_jwt_role` | `auth/keycape-services/role/secrets-engine-openbao` | `openbao/auth/secrets-engine-jwt-role.json` | -| `netkingdom_oidc_config` | `auth/netkingdom/config` | `openbao/auth/netkingdom-oidc-config.json` (except `_note`) | -| `platform_admin_role` | `auth/netkingdom/role/platform-admin` | `openbao/auth/netkingdom-platform-admin-role.json` | -| `policy_secrets_engine_login_self` | `sys/policies/acl/secrets-engine-login-self` | `openbao/policies/secrets-engine-login-self.hcl` | -| `kv_registration_metadata` | `platform/metadata/workloads/keycape/secrets-engine-openbao` | current version present, not deleted, not destroyed | - -The comparison projects only the fields the source file declares. Durations -such as `5m` are normalised to seconds, and string lists are sorted. The receipt -records each object as `present`, `source_match` and a typed `observed_sha256` -of the projected fields, never the values. A mismatch means the 2026-10-14 -snapshot differs from the current source. That is a finding, not a repair. - -## Outcomes - -- `restored`: every required object is present and matches its source. -- `refused`: an object is missing (`object_missing`) or differs - (`object_mismatch`). Nothing is repaired. -- `aborted`: any other failed check, a timeout, Ctrl-C/SIGTERM/SIGHUP - (`operator_abort`) or the loss of the parent process (`parent_lost`, written by - the reaper). - -Teardown always runs: revoke the credential, close the memfds, wipe the share -buffers, `docker rm -f -v`, then a `docker container inspect` that must fail. -The receipt is written with mode 0600 and `O_EXCL`, then checked by the -independent verifier. - -## Receipt v2 and verifier - -The schema is `railiance-openbao-restore-receipt/v2`. It extends -`docs/openbao-restore-drill-evidence.example.json`; the shape is in -`docs/openbao-restore-receipt-v2.example.json`. All digests are typed -`sha256:`, and no key name pairs with a bare hex value. - -The verifier `scripts/openbao-isolated-restore/verify-restore-receipt.py` -(`make openbao-verify-restore-receipt`) does not import the adapter. It rejects -unknown or missing fields, duplicate keys and placeholders. It also rejects -credential, share, age, PEM, hex or base64 secret shapes, `data` fields and KV -`/data/` paths. It requires teardown evidence and the exact `claims_not_made` -set, and checks result consistency, the 45-minute deadline and -`--not-after`. Finally it compares `source_sha256` with a clean checkout at -`adapter_commit`. The receipt stays outside Git and is referenced by its -sha256. - -## Admission (what T05 signs) - -The shape is in `docs/openbao-restore-admission.example.json`, schema -`railiance-openbao-restore-admission/v1`: - -- `attempt_id`: `RPF-WP-0064-T07-` -- `phase: "A"`, `phase_b_admitted: false`, `material: "original"` -- `not_before`, `not_after` (at most 24 h), `max_duration_seconds: 2700`, - `abort_operator` -- `image`: the T06-confirmed digest reference -- `adapter_commit` and `source_sha256` for all ten closure files: four adapter - files, four auth JSON files, the login-self policy and the JWT-login contract -- `snapshot`: `ciphertext_sha256`, `ciphertext_bytes`, `plaintext_sha256`, - `age_recipient`, `source_cluster_id`, `source_applied_index`, - `source_receipt_sha256` (taken from the RPF-WP-0015-T03 receipt and the - stored object) -- `unseal: {key_threshold: 2, key_shares: 3}`, - `readback_credential: "generate-root-from-entered-shares"` - -Sign it: - -```bash -ssh-keygen -Y sign -f ~/.ssh/ -n railiance-openbao-restore-attempt admission.json -``` - -## Attended run (T07) - -```bash -scripts/openbao-isolated-restore/run-isolated-restore.sh \ - /abs/admission.json /abs/admission.json.sig sha256: \ - /abs/.age /abs/receipt-dir -make openbao-verify-restore-receipt RECEIPT=/abs/receipt-dir/.json SOURCE_ROOT= -``` - -`preflight.py` runs first and is metadata only. It checks the admission bytes -and signature, the window, the source closure at a clean `adapter_commit`, the -pinned image cached locally, the ciphertext hash and age header, `/dev/shm`, -the required tools and that no stale `rpf64-restore-*` container exists. - -## Phase B (design stub, not implemented) - -Phase B is a separate admission, not part of this adapter. It would attach a -single `docker network create --internal` network holding only this restored -target and the isolated NK49 KeyCape. Inside it `kc.coulomb.social` resolves to -the isolated KeyCape, using its own trusted TLS chain. Egress is shown to be -refused before admission. Custody stays the same. The secrets-engine consumer -run (SECRETS-WP-0015-T04) owns the login attempt. - -## Rehearsal (T04) - -```bash -make openbao-isolated-restore-test # offline: admission, verifier adversarial fixtures, launcher -make openbao-isolated-restore-image # pull the public image (no custody) -make openbao-isolated-restore-rehearsal # native: real OpenBao, --network none, generated material -``` - -The workplan T04 note records what ran natively and what was mocked. diff --git a/docs/openbao-restore-admission.example.json b/docs/openbao-restore-admission.example.json deleted file mode 100644 index 0fbc9ea..0000000 --- a/docs/openbao-restore-admission.example.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "schema": "railiance-openbao-restore-admission/v1", - "namespace": "railiance-openbao-restore-attempt", - "attempt_id": "RPF-WP-0064-T07-YYYYMMDD-01", - "phase": "A", - "phase_b_admitted": false, - "material": "original", - "not_before": "YYYY-MM-DDTHH:MM:SSZ", - "not_after": "YYYY-MM-DDTHH:MM:SSZ", - "max_duration_seconds": 2700, - "abort_operator": "founder", - "image": "quay.io/openbao/openbao@sha256:0000000000000000000000000000000000000000000000000000000000000000", - "adapter_commit": "replace-with-40-hex-adapter-commit", - "snapshot": { - "ciphertext_sha256": "sha256:1111111111111111111111111111111111111111111111111111111111111111", - "ciphertext_bytes": 0, - "plaintext_sha256": "sha256:2222222222222222222222222222222222222222222222222222222222222222", - "age_recipient": "replace-with-railiance-backup-age-recipient", - "source_cluster_id": "replace-with-source-cluster-id", - "source_applied_index": 0, - "source_receipt_sha256": "sha256:3333333333333333333333333333333333333333333333333333333333333333" - }, - "unseal": { - "key_threshold": 2, - "key_shares": 3 - }, - "readback_credential": "generate-root-from-entered-shares", - "source_sha256": { - "docs/contracts/secrets-engine-openbao-jwt-login.yaml": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/keycape-services-config.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/netkingdom-oidc-config.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/netkingdom-platform-admin-role.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/secrets-engine-jwt-role.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/policies/secrets-engine-login-self.hcl": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/preflight.py": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/restore_adapter.py": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/run-isolated-restore.sh": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/verify-restore-receipt.py": "sha256:0000000000000000000000000000000000000000000000000000000000000000" - } -} diff --git a/docs/openbao-restore-receipt-v2.example.json b/docs/openbao-restore-receipt-v2.example.json deleted file mode 100644 index 2f83f32..0000000 --- a/docs/openbao-restore-receipt-v2.example.json +++ /dev/null @@ -1,180 +0,0 @@ -{ - "schema": "railiance-openbao-restore-receipt/v2", - "extends": "docs/openbao-restore-drill-evidence.example.json", - "attempt_id": "RPF-WP-0064-T07-YYYYMMDD-01", - "material": "original", - "phase": "A", - "admission_sha256": "sha256:5555555555555555555555555555555555555555555555555555555555555555", - "admission_namespace": "railiance-openbao-restore-attempt", - "signer_principal": "founder", - "signer_fingerprint": "replace-with-ssh-signer-fingerprint", - "anchor_protected": true, - "abort_operator": "founder", - "adapter_commit": "replace-with-40-hex-adapter-commit", - "source_sha256": { - "docs/contracts/secrets-engine-openbao-jwt-login.yaml": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/keycape-services-config.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/netkingdom-oidc-config.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/netkingdom-platform-admin-role.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/auth/secrets-engine-jwt-role.json": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "openbao/policies/secrets-engine-login-self.hcl": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/preflight.py": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/restore_adapter.py": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/run-isolated-restore.sh": "sha256:0000000000000000000000000000000000000000000000000000000000000000", - "scripts/openbao-isolated-restore/verify-restore-receipt.py": "sha256:0000000000000000000000000000000000000000000000000000000000000000" - }, - "started_at": "YYYY-MM-DDTHH:MM:SSZ", - "finished_at": "YYYY-MM-DDTHH:MM:SSZ", - "not_after": "YYYY-MM-DDTHH:MM:SSZ", - "deadline_seconds": 2700, - "elapsed_seconds": 0, - "image": "quay.io/openbao/openbao@sha256:0000000000000000000000000000000000000000000000000000000000000000", - "image_id": "sha256:6666666666666666666666666666666666666666666666666666666666666666", - "snapshot": { - "ciphertext_sha256": "sha256:1111111111111111111111111111111111111111111111111111111111111111", - "ciphertext_bytes": 0, - "plaintext_sha256": "sha256:2222222222222222222222222222222222222222222222222222222222222222", - "source_cluster_id": "replace-with-source-cluster-id", - "source_applied_index": 0, - "source_receipt_sha256": "sha256:3333333333333333333333333333333333333333333333333333333333333333", - "ciphertext_verified": true, - "plaintext_verified": true - }, - "restored": { - "cluster_id": "replace-with-restored-cluster-id", - "applied_index": 0, - "sealed_after_restore": true, - "unsealed_with_original_shares": true, - "cluster_id_match": true, - "applied_index_not_behind": true, - "throwaway_init_invalidated": true, - "leader_wait": { - "waited_seconds": 3, - "polls": 4, - "elected": true, - "last_leader": { - "ha_enabled": true, - "is_self": true, - "leader_address": "http://127.0.0.1:8200", - "leader_cluster_address": "https://127.0.0.1:8201", - "raft_committed_index": 6040620, - "raft_applied_index": 6040620, - "active_time": "2026-10-24T09:00:00Z" - }, - "last_seal": { - "sealed": null, - "initialized": null, - "storage_type": null, - "version": null - }, - "last_error": null - } - }, - "network": { - "network_mode": "none", - "docker_networks": [ - "none" - ], - "interfaces": [ - "lo" - ], - "published_ports": [], - "mounts": [], - "egress_refused": true - }, - "objects": [ - { - "object": "auth_mount_keycape_services", - "path": "sys/auth", - "present": true, - "source_match": true, - "source_path": null, - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "auth_mount_netkingdom", - "path": "sys/auth", - "present": true, - "source_match": true, - "source_path": null, - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "kv_mount_platform", - "path": "sys/mounts", - "present": true, - "source_match": true, - "source_path": null, - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "keycape_services_config", - "path": "auth/keycape-services/config", - "present": true, - "source_match": true, - "source_path": "openbao/auth/keycape-services-config.json", - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "secrets_engine_jwt_role", - "path": "auth/keycape-services/role/secrets-engine-openbao", - "present": true, - "source_match": true, - "source_path": "openbao/auth/secrets-engine-jwt-role.json", - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "netkingdom_oidc_config", - "path": "auth/netkingdom/config", - "present": true, - "source_match": true, - "source_path": "openbao/auth/netkingdom-oidc-config.json", - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "platform_admin_role", - "path": "auth/netkingdom/role/platform-admin", - "present": true, - "source_match": true, - "source_path": "openbao/auth/netkingdom-platform-admin-role.json", - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "policy_secrets_engine_login_self", - "path": "sys/policies/acl/secrets-engine-login-self", - "present": true, - "source_match": true, - "source_path": "openbao/policies/secrets-engine-login-self.hcl", - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - }, - { - "object": "kv_registration_metadata", - "path": "platform/metadata/workloads/keycape/secrets-engine-openbao", - "present": true, - "source_match": true, - "source_path": null, - "observed_sha256": "sha256:4444444444444444444444444444444444444444444444444444444444444444" - } - ], - "result": "restored", - "reason": "all_required_objects_match", - "teardown": { - "container_removed": true, - "container_absent_verified": true, - "plaintext_memfd_closed": true, - "custody_memfd_closed": true, - "readback_credential_revoked": true, - "docker_volumes_created": 0, - "performed_by": "session" - }, - "kv_data_read": false, - "no_secret_material_recorded": true, - "claims_not_made": [ - "consumer_continuity", - "live_jwt_login", - "phase_b_network", - "owner_acceptance", - "recurring_restore_assurance", - "production_redirect" - ] -} diff --git a/docs/openbao.md b/docs/openbao.md index 68ad343..7db097d 100644 --- a/docs/openbao.md +++ b/docs/openbao.md @@ -2,7 +2,7 @@ **Chart:** `openbao/openbao` **Chart version:** `0.28.2` -**App version:** `v2.5.4` (image tag in `helm/openbao-values.yaml`) +**App version:** `v2.5.3` **Namespace:** `openbao` **Managed by:** `railiance-platform` (S3) **Workplan:** `RAIL-PL-WP-0002` @@ -193,263 +193,6 @@ escrow owner through an out-of-band channel. The initial root token is either revoked after a non-root platform-admin token exists or stored as offline break-glass material with the same handling as unseal shares. -## Break-glass: OpenBao with SSO down - -Use this when KeyCape or Authelia is down and an OpenBao action can't wait. It -belongs to NetKingdom NK-WP-0055-T13 and is drilled against an isolated restored -OpenBao only (RPF-WP-0064-T10). Never drill it on production. The drill runs as -`scripts/openbao-isolated-restore/run-isolated-restore.sh --break-glass-drill` with its own signed -admission; the founder runbook is in the RPF-WP-0064 workplan, T10. - -**What fails with SSO down.** Operator OIDC logins through `auth/netkingdom` -fail, and so does the warden lane. ops-warden SSH certificates fail too, -because the warden backend is OpenBao. The OpenBao UI tunnel -`openbao-ui-railiance01` is only usable if its SSH credential does not come -from warden. - -**What still works.** Cluster access uses the static-key tunnel -`k3s-api-railiance01` (as `tegwick`) plus the admin kubeconfig client -certificate. Both are founder-workstation material, with an age-sealed copy on -the Storage Share (net-kingdom crisis list items 4 and 9). Through that path -you can use `kubectl exec` into `openbao-0` and -`kubectl -n openbao port-forward pod/openbao-0 8200:8200`. - -**Custody facts.** The mechanism is manual Shamir unseal: 3 shares, threshold -2. The founder (platform-root) holds all three shares in the replicated browser -password safe, under the `kingpath.credentials` entry (crisis list item 3). The -founder also holds the initial root token offline. Its validity is unconfirmed. - -**Rules for every step.** - -- Enter values only at hidden prompts: `read -rs` or OpenBao's own - `(will be hidden)` prompt. -- Never put a share, token or OTP in argv, an exported environment variable, - a file, shell history, chat, State Hub or Git. -- Keep these values in unexported shell variables in one terminal and `unset` - them at the end. -- Clear the clipboard after each paste from the password safe. -- Run with `set +o history` (or a leading space with - `HISTCONTROL=ignorespace`) as defence in depth. -- Output is reduced with `jq` to fields that are not secret. Never print a - token, `accessor`, `encoded_token`, `otp` or share. - -### 1. Reach and unseal - -```bash -kubectl --context railiance01 -n openbao get pod openbao-0 -kubectl -n openbao exec openbao-0 -- bao status -format=json | jq '{initialized, sealed, t, n, progress}' -``` - -If `sealed` is `true`, enter two shares. Each one goes into OpenBao's hidden -prompt, which needs a TTY (`-ti`): - -```bash -kubectl -n openbao exec -ti openbao-0 -- bao operator unseal # share 1 at "Unseal Key (will be hidden):" -kubectl -n openbao exec -ti openbao-0 -- bao operator unseal # share 2 -kubectl -n openbao exec openbao-0 -- bao status -format=json | jq '{sealed, progress}' -``` - -Do not pass a share as an argument (`bao operator unseal `). That puts it -in argv on the workstation and in the pod. - -### 2. API session in one terminal - -```bash -set +o history -kubectl -n openbao port-forward pod/openbao-0 8200:8200 >/dev/null & PF_PID=$! -ADDR=http://127.0.0.1:8200 -bao_api() { # bao_api METHOD PATH [TOKEN_VAR [curl args...]]; JSON body on stdin (` and - `bao operator generate-root -generate-otp`: these put the OTP in argv. -- `bao operator generate-root -nonce=`: prompts for a share, hidden. -- `bao operator generate-root -decode= -otp=`: puts both values - in argv and prints the token. -- `bao operator generate-root -cancel` -- PGP mode: `-init -pgp-key=keybase:|`, then - `gpg --decrypt` of the base64-decoded `encoded_token`. - -Because `-decode` exposes the encoded token and OTP in argv and prints the -token, run the equivalent through the API with the OTP kept in memory: - -```bash -bao_api GET sys/generate-root/attempt ROOT -openbao-break-glass.json` (or the drill receipt for a -drill) and send NetKingdom a State Hub message. Fields: - -- **What:** - - each action and API path, with the reviewed body's sha256 - - whether the pod was unsealed - - the token check result from step 3 - - whether generate-root completed - - the HTTP status of `revoke-self` - - `revoked: true` -- **Why:** the triggering outage and why the OIDC path could not be used. -- **Who:** the operator and any witness, by role (`platform-root`, `tegwick`). -- **When:** UTC start and end, and the audit-log window. The audit device - records HMAC'd tokens only. -- **Follow-ups:** - - generate a new root token (no standing root) - - a decision on the held initial root token - - re-sealing of material on the Storage Share if anything moved - -The record never contains a share, token, accessor, OTP, nonce or encoded -token. - - -### Production entry: revoke the held initial root token (NK-WP-0055-T16) - -The king-path drill (RPF-WP-0064-T10, 2026-10-28) found the founder's held initial root token valid as of the -2026-10-14 snapshot: `root`, orphan, no TTL. By founder decision it stays escrowed during builder mode and is -revoked **at production entry**, once the loopback `generate-root` listener (RAPP-OPENBAO-WP-0003) is the -break-glass path. `scripts/openbao_revoke_initial_root_token.py` reads the token at a hidden prompt and refuses -to revoke unless the break-glass listener answers. It writes a value-free 0600 receipt -(tests: `tests/test_openbao_revoke_initial_root_token.py`). - -```bash -export KUBECONFIG=~/.kube/config-railiance01 -kubectl -n openbao port-forward pod/openbao-0 18210:8210 18201:8200 >/dev/null 2>&1 & PF=$!; sleep 3 -R=~/.local/state/railiance/initial-root-token; mkdir -m 700 -p "$R" -# check any time (lookup-self only): -python3 scripts/openbao_revoke_initial_root_token.py check \ - --addr http://127.0.0.1:18201 --breakglass-addr http://127.0.0.1:18210 --receipt "$R/check-$(date -u +%Y%m%dT%H%M%SZ).json" -# at production entry only, under the founder's production-entry decision id: -python3 scripts/openbao_revoke_initial_root_token.py revoke --decision \ - --addr http://127.0.0.1:18201 --breakglass-addr http://127.0.0.1:18210 --receipt "$R/revoke-$(date -u +%Y%m%dT%H%M%SZ).json" -kill "$PF"; unset KUBECONFIG -``` - -Expected: `checked`, and at production entry `revoked` (revoke-self 204, then lookup-self 403). After a -revoke, remove the token from the password safe and keep the receipt. - ## Auto-Unseal via Transit Seal (optional, NET-WP-0020 T4) `helm/openbao-values.yaml` carries a commented `seal "transit"` stanza inside diff --git a/docs/playwright-consumer-access.md b/docs/playwright-consumer-access.md deleted file mode 100644 index 4334588..0000000 --- a/docs/playwright-consumer-access.md +++ /dev/null @@ -1,109 +0,0 @@ -# Playwright consumer access - -The release 0.1.0 pilot is running in namespace `playwright` on railiance01. -Platform owns its lifecycle and the `playwright-consumers` group binding; -rapp-playwright owns the browser implementation, package Role and client. - -The initial membership policy includes all identified platform humans and coding -agents. The group binding is installed and tested. Mapping authenticated platform -identities into that Kubernetes group is still open in RPF-WP-0056-T03; a KeyCape -group alone would not establish Kubernetes access. No administrator kubeconfig -is distributed and no broad `system:authenticated` binding is installed. - -## Prepared consumer setup — activation pending - -The helper and commands below are ready for owner integration. They are not a -claim that the registrations, JWT authenticator or general consumer transport -are active. The [owner handoff](playwright-identity-handoff.md) lists the remaining -changes and native acceptance checks. No further membership decision is needed. - -After owner activation, use a private directory outside any Git worktree and a -new token output path. `keycape` is the identity owner's native CLI: - -```sh -install -d -m 700 "$HOME/.local/state/playwright-consumer" -keycape login --issuer https://kc.coulomb.social \ - --client-id playwright-cli --audience playwright-kubernetes \ - --scope 'openid profile groups playwright:connect' \ - --redirect-uri http://127.0.0.1:18764/callback \ - --out "$HOME/.local/state/playwright-consumer/token.json" -``` - -Registered coding agents instead use `keycape service-token` with their own -Playwright client ID, audience `playwright-kubernetes`, scope `playwright:connect`, -and `--secret-env` naming the variable populated by their governed custody route. -Client registration and delivery are still pending; existing OpenBao/approval -client credentials must not be reused. Tokens expire; authenticate again into a -new private file and generate a new kubeconfig for renewal. There is no refresh -token, secret provisioning or unattended human login in this helper. - -From this repository, create the isolated consumer configuration: - -```sh -python3 scripts/playwright_consumer.py setup \ - --token-file "$HOME/.local/state/playwright-consumer/token.json" \ - --out "$HOME/.local/state/playwright-consumer/kubeconfig.json" -``` - -Setup embeds only the public CA, exec helper and token file path. It refuses -overwrites. The helper checks destination and CA before reading a mode-0600 -token, then supplies a Kubernetes ExecCredential through kubectl's pipe. Local -claim checks are misuse protection; Kubernetes must verify the JWT signature. -The public CA was read from `playwright/kube-root-ca.crt` on the pinned cluster -UID `a553c742-0115-43d4-99a4-a5ca56fe0786`; its SHA-256 is -`794d3badd7c3743fe66c3671e7f152277f2ac9adddbc5cebe534802432861bd7`. -CA rotation requires a reviewed source update. - -The owner-supported API transport must expose the TLS API on -`https://127.0.0.1:16444`. The existing local ops-bridge tunnel is -`k3s-api-railiance01`; its configured actor is workstation-specific and must not -be shared with other users or agents. Each caller needs their own supported -transport identity. With that route active, verify and forward: - -```sh -python3 scripts/playwright_consumer.py verify \ - --kubeconfig "$HOME/.local/state/playwright-consumer/kubeconfig.json" -kubectl --kubeconfig "$HOME/.local/state/playwright-consumer/kubeconfig.json" \ - --context playwright -n playwright port-forward deployment/playwright \ - 18081:8080 --address 127.0.0.1 -``` - -Verification requires the unmodified generated kubeconfig, reads the actual API -identity using SelfSubjectReview, and checks nine allowed/denied permissions. -It rejects administrator identities and added impersonation settings. Its -value-safe JSON result can be retained as an identity receipt. Run it separately -for a human and a coding agent; then run each browser trial through that caller's -forward. Existing port-forwards must be stopped separately during revocation; -token expiry or client disablement does not prove an established stream closed. - -In another terminal, from the existing rapp-playwright checkout: - -```sh -python3 client/rapp_playwright.py preflight -python3 client/rapp_playwright.py trial --out .local/first-managed-trial -``` - -Use a new output directory each time. Keep `run.json` private: it contains the -run capability. Download retained receipts/screenshots before the one-hour -artifact expiry. Current targets are the bundled fixture and public OnCo home -page; additional target admission remains separate from group membership. - -## Existing operator evidence - -Platform can recheck the native permission boundary without changing grants: - -```sh -python3 scripts/verify_playwright_access.py --output /tmp/playwright-rbac.json -``` - -The verifier uses administrator impersonation solely for authorization checks. -Its success is not consumer-login proof. It checks the cluster UID, exact Role -and group binding, allowed discovery/forwarding and denied exec, Secrets, -workload writes, foreign scope and nonmembers. - -October 13 evidence: [RBAC](evidence/2026-10-13-playwright-rbac.json), -[runtime and restart](evidence/2026-10-13-playwright-managed/runtime.json), -[responsive keyboard trial](evidence/2026-10-13-playwright-managed/receipt.json). -Native operator-driven trials passed before and after a same-image restart; -completed screenshots survived with identical hashes. Actual consumer login, -abrupt-interruption recovery and cross-version rollback remain unproven. diff --git a/docs/playwright-identity-handoff.md b/docs/playwright-identity-handoff.md deleted file mode 100644 index dd6a7cd..0000000 --- a/docs/playwright-identity-handoff.md +++ /dev/null @@ -1,136 +0,0 @@ -# Playwright consumer identity activation handoff - -Status: source prepared; owner activation and real human/agent evidence pending. -The user already admitted all identified platform humans and coding agents. -This handoff implements ADR-0006 without inventing registration acceptance, -distributing admin kubeconfigs or broadening other service credentials. - -## Custodian coordination request - -Requested outcome: **Enable self-service Playwright access for identified platform -humans and coding agents.** The user authorized sending this cross-repository -delivery request on October 13. The Custodian is asked to establish one accountable -coordinator and link the existing implementation tasks and owner requests to a -source-backed delivery task. Platform acceptance remains RPF-WP-0056-T03; the -coordination request must preserve the existing owner responsibilities and evidence. - -Sent to `the-custodian` through State Hub on 2026-10-13 at 19:31 UTC: -`1b279bad-a159-43da-8709-1167882e62f8`. Accepted at 20:04 UTC -(`f761e801-2111-41f3-af0d-a5411a37ffab`): coordinator the-custodian / -custodian-codex, source CUST-WP-0080-T05 (Hub -`ed8b909a-23bf-5e5f-b600-e477fbae136b`). RPF-WP-0056-T06 is done on that return. -Platform acceptance remains RPF-WP-0056-T03. - -Requirements: - -1. All identified platform humans and coding agents initially qualify for - `playwright-consumers`. Their existing identity owners establish identities; - no further membership policy decision is needed from the user. -2. A consumer can follow documented onboarding, authenticate, and use one command - to connect and run a UI trial without operator assistance for each use. Include - renewal, actionable failure messages and cleanup. The current setup helper is - an implementation input; a complete single-command experience remains to deliver. -3. Humans use platform login. Each coding agent uses its own registered identity - and governed credential delivery. No shared administrator kubeconfig or shared - workstation SSH identity is part of consumer onboarding. -4. Preserve the existing namespace-scoped permissions and approved target boundary. - Broad initial eligibility does not grant Secrets, pod exec, workload mutation, - other namespaces or additional browsing targets. -5. Provide supported onboarding, renewal and revocation, including how to terminate - active connections and what remains usable until token expiry. -6. A real human and a real coding agent independently complete identity verification, - permission checks and responsive/keyboard browser trials using the supported - consumer instructions. Native wrong-audience, missing-scope, expired/tampered - token and ineligible-identity attempts fail. No administrator impersonation or - synthetic token test substitutes for these acceptance receipts. -7. Platform and rapp-playwright accept the receipts, publish the consumer instructions - and update managed availability/discovery. Completion means the consumer flow - works, not merely that the coordination messages were delivered. - -First delivery should finish the prepared scoped port-forward route, including -supported per-caller API transport. Keep the user-facing requirements independent -of this transport so an existing shared platform access mechanism can replace it -later. A new access gateway is not required for this first delivery. - -Requested Custodian return: coordinating owner; source workplan/task identifiers; -linked owner tasks and their dependency order; first executable milestone; and any -specific unresolved decision. Registration, custody and transport implementation -gaps should be assigned to owners rather than returned as another membership -approval question. Do not claim owner acceptance until their responses exist. - -Existing implementation follow-ups: railiance-cluster -`0c1023ce-86ac-404a-842e-2f7e1a6d90f1`, KeyCape -`85bf4765-35cc-418f-903b-2dba64e8a06b`, NetKingdom -`c6b4dfb0-44d5-4f12-8854-2c38ab6dc454`, ops-bridge -`c367f3a2-ceba-4ec9-b241-b917c47ba11e`, rapp-playwright -`8d1de2a9-5209-4f82-a2cd-80d8c2e07001`. - -## Implementation inputs and owner responsibilities - -| Owner | Concrete change | Required return | -| --- | --- | --- | -| railiance-cluster | Review and integrate `config/playwright/authentication-config.candidate.yaml` into the K3s API-server configuration | Native config validation, rollout/recovery receipt, preserved existing authentication, real allowed/denied requests | -| key-cape / net-kingdom | Review and register `config/playwright/keycape-public-client.candidate.json`; dedicated per-agent confidential registrations | Exact claim/expiry contract, issuer rollout, registration and custody references without values | -| ops-bridge / cluster access owner | Supported per-caller TLS API route on local port 16444 | Human and agent transport instructions using their own identities | -| railiance-platform | Run `scripts/playwright_consumer.py verify` and provider trials for both identity kinds | API-returned identity, permission negatives, responsive/keyboard trial receipts | -| rapp-playwright | Review native consumer receipts and update managed availability | Accepted RAPP-PLAYWRIGHT-WP-0004-T01/T03 and declaration/registry evidence | - -Coordination requests sent October 13: cluster -`f643ce05-b71d-4492-83a3-ae142d0d3535`, KeyCape -`60b22d25-a573-4957-aaae-3a66f5713466`, NetKingdom -`94a0096c-f34c-463c-b336-b13cf629b968`. - -KeyCape source acceptance arrived 2026-10-14 (`0e65b962`): the public candidate -`playwright-cli` matches the supported authorization-code shape (PKCE S256, no -secret, redirect `http://127.0.0.1:18764/callback`, scopes `openid profile -groups playwright:connect`, access-token audience `playwright-kubernetes`). -Code-flow lifetime follows the server default of 15 minutes; a per-client -`tokenLifetime` does not change it. Human `sub` is the directory subject. -Absent a client tenant and a directory tenant, the claim is `tenant:coulomb` -with `tenant_source` default, not `tenant:platform`. A per-agent caller is a -separate confidential client. No client was registered. Live registration of -either client stays on the NetKingdom registry path. Cluster and ops-bridge -activation receipts are still absent. - -The candidate uses issuer `https://kc.coulomb.social`, dedicated audience -`playwright-kubernetes`, access scope `playwright:connect`, and a constant mapped -group `playwright-consumers`. Subjects become `keycape:human:` or -`keycape:service:`. KeyCape represents registered coding agents as service -principals with the `coding-agent` role. Other services fail this mapping; token -groups cannot inject Kubernetes privileges. Human tenants are not rewritten or -restricted to `tenant:platform`. Dedicated registrations determine who can obtain -this audience and scope. - -The public client uses exact PKCE callback `http://127.0.0.1:18764/callback`. -Native KeyCape login verifies issuer/JWKS, scope, audience, state and nonce. -Use its access token: ID tokens have client audience and no access scope. -Human lifetime inherits issuer settings; confirm it is at most one hour before -activation. Do not add per-client `tokenLifetime` to the public client: KeyCape -supports that override only for client-credentials registrations. - -Each coding agent needs a distinct new client registration with the dedicated -audience/scope, unique service subject, `coding-agent` role, its actual registered -tenant and preferably 15-minute lifetime. Its new secretRef must have an existing -governed custody/delivery contract before activation. No existing OpenBao client -secret or audience is reused. The browser-RP registration helper's accepted -HTTPS/standard-scope type does not cover this loopback/custom-scope client; its -case-acceptance requirement cannot be satisfied by fabricating a receipt. - -Cluster integration must preserve unrelated authenticators and the SSH/admin -recovery route. `AuthenticationConfiguration` is an API-server file, not a -resource accepted by `kubectl apply`. Review existing K3s flags and config -fragments, natively validate CEL against the deployed Kubernetes version, -establish rollback before activation, and coordinate any required single-node -API-server restart. The source candidate has not been compiled by Kubernetes -or applied. Source: [Kubernetes 1.35 authentication configuration and exec -plugins](https://v1-35.docs.kubernetes.io/docs/reference/access-authn-authz/authentication/). - -Before availability is published, prove wrong audience, missing scope, expired -token, tampered signature, ordinary service and nonmember refusals at the native -API. The helper's synthetic tests do not establish those server-side properties. -Prove positive human and coding-agent access via SelfSubjectReview, permission -checks and browser trials, with no `--as` impersonation. Client disablement stops -issuance; issued JWTs remain valid until expiry, and existing port-forwards need -explicit termination. Keep the provider availability tasks open until those -receipts are accepted. Existing operator-driven pilot/restart evidence remains -valid with its documented limits. diff --git a/docs/proposals/2026-10-11-forgejo-consumer-eso/CCR-2026-0042-proposed-body.yaml b/docs/proposals/2026-10-11-forgejo-consumer-eso/CCR-2026-0042-proposed-body.yaml deleted file mode 100644 index 1545a39..0000000 --- a/docs/proposals/2026-10-11-forgejo-consumer-eso/CCR-2026-0042-proposed-body.yaml +++ /dev/null @@ -1,110 +0,0 @@ -id: CCR-2026-0042 -kind: credential-change-request -schema_version: 1 -request_type: workload-kv-read -title: 'forgejo-netkingdom-oidc: proposed dedicated consumer ESO reader (oidc-browser-relying-party@1.2)' -status: proposed -created: '2026-10-09' -updated: '2026-10-11' -requester: - agent: ops-mason - reason: Source-only owner refinement of existing CCR-2026-0042. Proposed type/case1.2 and full run source require - new exact acknowledgment; original approvals do not authorize this reader role or native delivery. Historical - body retained by exact file hash and provenance below. -review: - required: true - required_approvers: - - platform-owner - comments: [] -target: - domain: infotech - tenant: platform - workload: forgejo - environment: production - purpose: Dedicated namespace-local ESO reference identity delivers CLIENT_SECRET to Forgejo; the Forgejo workload/default - SA does not authenticate directly to OpenBao. -openbao: - mount: platform - kv_path: platform/workloads/keycape/forgejo - fields: - - CLIENT_SECRET - policy_name: workload-kv-read-keycape-forgejo-forgejo - policy_file: openbao/policies/workload-kv-read-keycape-forgejo-forgejo.hcl - metadata_read: false - token_self_lifecycle: true - auth: - method: kubernetes - mount: kubernetes - role: external-secrets-keycape-forgejo-forgejo - audience: openbao - ttl: 15m - token_max_ttl: 15m - token_explicit_max_ttl: 15m - token_no_default_policy: true - policies: - - workload-kv-read-keycape-forgejo-forgejo - bound_claims: - service_account_names: - - forgejo-keycape-forgejo-eso - service_account_namespaces: - - forgejo - bound_claims_confirmed: false -access_frontdoor: - type: external-secrets - catalog_id: forgejo-keycape-client - readiness: pending-review - resolvable: false -risk: - classification: high - notes: - - Rendered from proposed SecurityCaseType/case 1.2 source, not an acknowledged successor. Retained read-only native - ESO image/CRD/RBAC and Forgejo identity observations support preparation only; bound claims remain unconfirmed - and apply blocked. The new sixth consumer role/auth path requires fresh informed-decision acknowledgment under - SecurityCaseTypes canon section 6; acknowledged type 1.1 and accepted case 1.1 do not authorize it. - - Exact read on one KV path; no metadata, list, parent or sibling access. - - Dedicated reference identity separates consumer and KeyCape role bindings, but common ESO controller already - has clusterwide TokenRequest and Secret permissions; no controller compromise isolation is claimed. - - ESO v0.16.1 TokenRequest expiry is 600 seconds and its fallback reads legacy SA token references; proposed dedicated - SA has no legacy tokens and automount false. - - Native publication requires exact1.2 acknowledgment and independent owner changes/created-only receipts; no - live grant is issued by this draft. -verification: - positive: - - policy workload-kv-read-keycape-forgejo-forgejo grants read on platform/data/workloads/keycape/forgejo only - (capabilities check) - negative: - - a default-only identity has no read capability on the path - - sibling GET and parent LIST are denied - activation_conditions: - - case acceptance present - - bound claims confirmed by the platform owner -lifecycle: - deactivate: First revert consumer reference and remove only proved-created target/delivery objects; revoke issued - reader tokens or await verified expiry, then delete created role and finally policy. Preserve shared/matched - objects. - rotate: roll-forward through the credential broker (mint-secret); never a read of the old value - compromised: owner incident process; no automatic read, reseed or cleanup -security_case: - case: forgejo-netkingdom-oidc - type: oidc-browser-relying-party@1.2 - render_digest: sha256:c0afd5576cdae0cbc807eccb2b9fa87ac668f15e32ec7cf79d295bdb23c75b35 - acceptance_record: PENDING exact proposed1.2 owner/founder acknowledgment; original1.1 acceptance is not transferred - accepted_at: null - accepted_by: [] -historical_request: - path: credential-change-requests/CCR-2026-0042-forgejo-netkingdom-oidc-consumer-reader.yaml - sha256: a36ab567839c12660c8ca468c8e4c6a67e367c92ae2ae51851b8e34c35a277f3 - security_case: - case: forgejo-netkingdom-oidc - type: oidc-browser-relying-party@1.0 - render_digest: sha256:fb8b114d465341a46bb492259cb9c6b77bc0731670edace2e13a1f58758c6e23 - acceptance_record: key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01); - type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e - accepted_at: '2026-10-08' - accepted_by: - - owner - - founder -proposed_refinement_readiness: - owner: railiance-apps - blocking_reason: Concrete identity and supported ESO design are prepared; native metadata apply/delivery remains - unapproved. Existing ORGREF-WP-0002 coordinates consumer handover and break-glass gates. diff --git a/docs/proposals/2026-10-11-forgejo-consumer-eso/reader-role.json b/docs/proposals/2026-10-11-forgejo-consumer-eso/reader-role.json deleted file mode 100644 index a393666..0000000 --- a/docs/proposals/2026-10-11-forgejo-consumer-eso/reader-role.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "bound_service_account_names": [ - "forgejo-keycape-forgejo-eso" - ], - "bound_service_account_namespaces": [ - "forgejo" - ], - "audience": "openbao", - "token_policies": [ - "workload-kv-read-keycape-forgejo-forgejo" - ], - "token_no_default_policy": true, - "token_ttl": 900, - "token_max_ttl": 900, - "token_explicit_max_ttl": 900 -} diff --git a/docs/proposals/2026-10-11-forgejo-consumer-eso/workload-kv-read-keycape-forgejo-forgejo.hcl b/docs/proposals/2026-10-11-forgejo-consumer-eso/workload-kv-read-keycape-forgejo-forgejo.hcl deleted file mode 100644 index 8a2478d..0000000 --- a/docs/proposals/2026-10-11-forgejo-consumer-eso/workload-kv-read-keycape-forgejo-forgejo.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# workload-kv-read-keycape-forgejo-forgejo: CCR-2026-0042, from ops-mason SecurityCase forgejo-netkingdom-oidc -# (oidc-browser-relying-party@1.0, render sha256:fb8b114d...). Platform-generated from the CCR: -# the case render omitted the token self-lifecycle stanzas that token_self_lifecycle requires. - -path "platform/data/workloads/keycape/forgejo" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json b/docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json deleted file mode 100644 index eee2745..0000000 --- a/docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json +++ /dev/null @@ -1,128 +0,0 @@ -{ - "schema": "platform-key39-coding-client-custody-proposal/v1", - "status": "proposed_source_only", - "formal_owner_agreement": false, - "native_authorized": false, - "credential_access_authorized": false, - "source_pins": [ - { - "path": "/home/worsch/railiance-platform/AGENTS.md", - "sha256": "8d2f1a165e3f4eecf2e6e1d6a19c4bac9e8dcb64a76650e7185d55722f233c8b" - }, - { - "path": "/home/worsch/railiance-platform/INTENT.md", - "sha256": "12c6540735af591f7a4a9fe85ff10d3cd00eec55b5b6b1ddf148abf2d3ac9a9c" - }, - { - "path": "/home/worsch/railiance-platform/openbao/auth/coding-agent-jwt-role.json", - "sha256": "c72e836ab72713a49aa8fb84faa26e824ff1263b5a184600fca0c0284720ee3b" - }, - { - "path": "/home/worsch/railiance-platform/openbao/policies/agent-high-risk-boundary.hcl", - "sha256": "ca3f27d8c099011ca44498ba1f50dea3eb6063c4a3bbbf9130e6881dd4e972f5" - }, - { - "path": "/home/worsch/railiance-platform/openbao/policies/workload-kv-read-issue-core-runtime.hcl", - "sha256": "54b9f864f3435910143a860352aac5ecb3aa2cd7afb5e161bb304c12d821c55e" - }, - { - "path": "/home/worsch/railiance-platform/docs/platform-tenant-essentials-review.md", - "sha256": "0b023c7a1bd58b1fac9b7ae746645dcf80fd3e5224c8399447fb90acbaba5c5c" - }, - { - "path": "/home/worsch/key-cape/config/service-clients.example.yaml", - "sha256": "b7e17698a0946f387c481b7e27fe9ddea22cdd9db2e34e32a50457e02a34b18e" - }, - { - "path": "/home/worsch/key-cape/src/internal/authclient/cli.go", - "sha256": "376a8e0d1d60825a8b3ef21d371e3fc353c0a34e461712a6899e264278edbe7f" - }, - { - "path": "/home/worsch/key-cape/src/internal/authclient/client.go", - "sha256": "61fb150349c5d3714450a90834c59af9a579a8a649c4149297a402cfcf6e39c5" - }, - { - "path": "/home/worsch/key-cape/docs/proposals/2026-10-11-key39-next-execution-prerequisites.json", - "sha256": "78288f6991910715b7ee8fdb2a32654418dd9becd5fccd254bab8d150420cfd3" - }, - { - "path": "/home/worsch/net-kingdom/sso-mfa/k8s/keycape/deployment.yaml", - "sha256": "b21c04841245263861273133ac8bde06653deb88be3afef9596ed2fba9db631b" - }, - { - "path": "/home/worsch/ops-warden/registry/routing/catalog.yaml", - "sha256": "09ee40df21b0ad8cad420be4613afcb1731967f68128631a94f9b96f3f31b318" - } - ], - "identity": { - "issuer": "https://kc.coulomb.social", - "client_id": "codex-railiance-platform", - "audience": "codex-railiance-platform", - "subject": "service:codex:railiance-platform", - "tenant": "tenant:platform", - "principal_type": "service", - "roles": [ - "coding-agent" - ], - "scope": "openbao:login", - "max_lifetime_seconds": 900, - "issuer_env_reference": "KEYCAPE_CODEX_RAILIANCE_PLATFORM_CLIENT_SECRET" - }, - "existing_role": { - "path": "auth/netkingdom/role/coding-agent", - "policies": [ - "agent-high-risk-boundary", - "workload-kv-read-issue-core-runtime" - ], - "ttl_seconds": 900, - "max_uses": 8, - "no_default_policy": true, - "source_is_not_native_observation": true - }, - "proposed_coordinates": { - "binding_status": "new_candidate_not_admitted_or_observed", - "kv_mount": "platform", - "kv_path": "workloads/keycape/codex-railiance-platform", - "field": "CLIENT_SECRET", - "issuer_namespace": "sso", - "issuer_secret_name": "keycape-codex-railiance-platform-client", - "issuer_secret_key": "client-secret", - "issuer_serviceaccount_candidate": "keycape-codex-client-eso", - "coding_secret_direct_delivery_to_agent": false, - "founder_delivery": "separate exact attended one-use private custody-to-service-token invocation; only verified coding JWT JSON participates in reviewed KEY39 paired handoff" - }, - "source_observations": { - "deployment_has_named_env_reference": false, - "exact_named_CCR_grant_catalog_receipt_found_in_bounded_search": false, - "current_live_client_registration_proven": false, - "current_custody_version_proven": false, - "current_issuer_matching_version_proven": false, - "current_delivery_host_profile_admitted": false, - "new_CCR_assigned": false - }, - "missing_admission_gates": [ - "Platform exact CCR/grant/catalogue and approved source revision for named client; no reuse of CCR0040 browser generation", - "Net issuer registration and exact env Secret reference; live value-free matched-version projection", - "Net dedicated namespace SecretStore/ExternalSecret serviceaccount, TokenRequest RBAC, issuer controller and Argo readiness ownership", - "Platform exact founder custody-reader role/capability denial and token budget/cleanup; no standing coding-role read grant", - "Founder host/process isolation from coding runtime, exact session/cert profile, continuous exclusive tmpfs and private environment/file custody", - "Key exact verified claim producer including all identity coordinates and finite lifetime; existing CLI alone does not bind subject/tenant/type/roles/max900", - "Fresh authenticated founder native revision and production maintenance exception when required", - "Actual node route/role/plugin and all effective leeway observation, negative refusal correlation, known-token revoke and unknown recovery" - ], - "candidate_value_free_receipt_fields": [ - "source_revision", - "CCR_id_and_digest", - "grant_id_and_digest", - "catalogue_id_and_digest", - "issuer_client_projection_digest", - "issuer_Secret_UID_resourceVersion_key_name_only", - "custody_logical_path_and_version_only", - "matched_version_attestation_no_value_or_secret_hash", - "ESO_role_ServiceAccount_namespace_UID_audience_policy_digests", - "founder_host_profile_process_isolation_attestation", - "exact_execution_envelope_digest", - "cleanup_status_and_unknown_effects" - ], - "standing_doc_correction": "tenant:coulomb -> tenant:platform only; dated historical proof unchanged" -} diff --git a/docs/proposals/2026-10-15-reuse-private-reader/README.md b/docs/proposals/2026-10-15-reuse-private-reader/README.md deleted file mode 100644 index 9c3615d..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/README.md +++ /dev/null @@ -1,83 +0,0 @@ -# Seven private-source reader preparation - -This bundle is prepared under platform's INTENT custody/Q1 remit. Nothing here -records founder credential approval or another owner's review. The existing -all-repository core-hub provisioner is precedent only and is never executed. - -The exact bounded command, after approved CCR, independent reviews and binding -confirmation, is: - -```sh -warden access openbao-platform-admin-login --exec -- python3 scripts/provision-reuse-surface-forgejo-source.py --approval /absolute/path/approved-artifact-receipt.json --receipt /absolute/path/durable-0600-receipt.jsonl -``` - -An approval receipt must contain `ccr_id: CCR-2026-0046`, `decision: approved`, -nonempty `supervisor` and `decision_ref`, and `approved_artifacts` mapping exactly -these four relative paths to SHA256 digests of their approved bytes: - -- credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml -- scripts/provision-reuse-surface-forgejo-source.py -- docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json -- openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl - -CCR itself must be approved; preparation hashes do not satisfy that gate. This -local check binds revisions and relies on attended platform lane/supervisor -custody for authenticity; it is not a signed approval authority or an autopilot -grant. Metadata apply is separately governed by existing credential-change CLI. -Bootstrap never applies policy or changes cluster delivery. - -`apps-externalsecret-data-item.draft.yaml` is a proposed insertion for apps owner, -not a deployable manifest or an apps review. Existing ESO role reads only one new -exact data path; caller writer policy is unchanged. Claim confirmation remains -false until live owner survey. Consumer delivery must wait for the FULL reviewed -image fixing redirects/HTTPS and runtime-controlled exact source URL allowlist, -with fourteen consented pinned URLs in REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST. - -Failure recovery: inspect sanitized journal and authoritative identity/team/PAT/ -KV state quietly. A missing observation is not proof a request did not apply. -The script refuses orphan PAT/KV state rather than reminting. Revoke only an -identified dedicated PAT through Forgejo owner's attended recovery; if mint -response lost, locate its exact recorded attempt-name prefix before revocation. -Never delete unrelated token or shared runtime Secret. Rotation/recovery of an -existing KV version requires a separately scoped CAS update plan; this create-only -bootstrap does not overwrite or automatically rollback ambiguous writes. - -Known execution gates: provisional IDs must be rechecked against current primary; -Forgejo owner must confirm the API contracts (team organization username, complete -sudo user inventory and token_last_eight metadata) on the deployed server; missing -or differing metadata fails closed. Excluded-private rapp-core-hub must still be -private. Native verification is a separate consumer-owned proof, not this API -bootstrap's index-only custody check. New reader provisioning/production approval, -apps config delta, fixed-image digest, live ESO survey and final70 audit all pending. - -Revision2 refines the same CCR after a confirmed server-capped pagination defect. -R1 patch and failed review evidence remain retained; no new scored trial is claimed. -All list requests now continue until an observed empty page, reject duplicate IDs, -and reconcile supplied total counts. Forgejo v11.0.3 source confirms user/repos -includes effective direct/team grants. Stable ID sort is requested for that list. -Version compatibility and live proof still precede apply. - -Forgejo11.0.3 token DELETE requires Basic authentication, unlike token inventory GET. -Admin PAT/Sudo alone is not a valid recovery implementation. The separate reviewed -recovery must reset only this dedicated user password to fresh process-memory bytes, -Basic-auth DELETE only the reviewed token ID, and verify absence. Recovery -helper approval and manual rotation due-date task remain activation gates; no -deployed quarterly expiry enforcement is claimed. - - -The separately reviewed executable credential rollback is: - -```sh -warden access openbao-platform-admin-login --exec -- python3 scripts/revoke-reuse-surface-forgejo-source.py --approval /absolute/path/approved-exact-token-recovery.json --receipt /absolute/path/durable-0600-recovery.jsonl -``` - -Its independent approval record names `action: revoke_exact_pat`, the CCR, -`decision: approved`, supervisor and decision_ref, `target: {id: , -name: }`, and SHA256s in `approved_artifacts` for -exactly recovery helper, bootstrap helper, CCR and bootstrap contract. This -receipt also relies on attended supervisor custody; it is not signed authority. -Different inventory stops before password mutation. Password stays in memory; -DELETE uses Basic authorization captured only by the quiet client. Uncertain -DELETE is followed only by read-only absence reconciliation, never another -DELETE or mint. KV is deliberately preserved, so rotation needs its separate CAS -plan. Credential rollback and apps delivery cleanup have independent receipts. diff --git a/docs/proposals/2026-10-15-reuse-private-reader/apps-externalsecret-data-item.draft.yaml b/docs/proposals/2026-10-15-reuse-private-reader/apps-externalsecret-data-item.draft.yaml deleted file mode 100644 index 12dbafc..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/apps-externalsecret-data-item.draft.yaml +++ /dev/null @@ -1,4 +0,0 @@ -secretKey: REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN -remoteRef: - key: workloads/reuse/reuse-surface/forgejo-source-read - property: FORGEJO_SOURCE_TOKEN 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 deleted file mode 100644 index 449267d..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/attended-custody-handoff-r1.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "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/bootstrap-contract.json b/docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json deleted file mode 100644 index 798797b..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json +++ /dev/null @@ -1,211 +0,0 @@ -{ - "id": "CCR-2026-0046-BOOTSTRAP-R2", - "revision": 2, - "sources": [ - { - "repo": "rapp-openbao", - "capability_ids": [ - "capability.railiance.openbao-package" - ], - "consented_commit": "35672be138036c8433a5ac5de91e068c03c2b124", - "native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-openbao/raw/registry/indexes/capabilities.yaml?ref=35672be138036c8433a5ac5de91e068c03c2b124", - "index_sha256": "044efa3d02b90e1872f2516c581159d388a8080a689f76fcd78bb4d048d6ee64", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN" - }, - { - "repo": "rapp-policy-nexus", - "capability_ids": [ - "capability.railiance.policy-publication-package" - ], - "consented_commit": "088b78ac03b0cebeec34a69aae3569eb7ec2f9c3", - "native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-policy-nexus/raw/registry/indexes/capabilities.yaml?ref=088b78ac03b0cebeec34a69aae3569eb7ec2f9c3", - "index_sha256": "e68bdedf61eee9ca2026f46a2434ce384c26234616e74f7a5d08a06c75b3bb00", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN" - }, - { - "repo": "rapp-postgres", - "capability_ids": [ - "capability.railiance.postgres-isolation-recovery" - ], - "consented_commit": "a9f569c96d74aa384ac8d3ac4aa960e4061550b5", - "native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-postgres/raw/registry/indexes/capabilities.yaml?ref=a9f569c96d74aa384ac8d3ac4aa960e4061550b5", - "index_sha256": "d939333eed9d85172e006f526c445fbe2602a721d53f7c0d14bc68286ae08776", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN" - }, - { - "repo": "rapp-sbom-nexus", - "capability_ids": [ - "capability.railiance.sbom-runtime-package" - ], - "consented_commit": "adbedd349e8df97be43a1f57db7095af45ff29d1", - "native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-sbom-nexus/raw/registry/indexes/capabilities.yaml?ref=adbedd349e8df97be43a1f57db7095af45ff29d1", - "index_sha256": "d2ee0f193d2198bf8f16ce64c90ae7664e59a2bc307ad711426af3fd47deb057", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN" - }, - { - "repo": "rapp-telemetry", - "capability_ids": [ - "capability.railiance.telemetry-package" - ], - "consented_commit": "f158307e24b86bab88a1af4830ff6b4f85afdeb5", - "native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-telemetry/raw/registry/indexes/capabilities.yaml?ref=f158307e24b86bab88a1af4830ff6b4f85afdeb5", - "index_sha256": "ee8801f143ed5cf064205710a61c2bb24efc6ea8585d5786d6b679426ec4c1d4", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN" - }, - { - "repo": "rapp-tenant-engine", - "capability_ids": [ - "capability.railiance.tenant-engine-package" - ], - "consented_commit": "9bdc86572728783a5ed94266f04f7b12d414c557", - "native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-tenant-engine/raw/registry/indexes/capabilities.yaml?ref=9bdc86572728783a5ed94266f04f7b12d414c557", - "index_sha256": "d8e9c1282350b5d2fd4f4d69712aa7a5fbcd2367bb1a7fad1b6eb16a97e5d732", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN" - }, - { - "repo": "rapp-user-engine", - "capability_ids": [ - "capability.railiance.user-engine-package" - ], - "consented_commit": "f35108f9e1b8702fb6c526fe42b2fec5ca032738", - "native_raw_url": "https://forgejo.coulomb.social/api/v1/repos/coulomb/rapp-user-engine/raw/registry/indexes/capabilities.yaml?ref=f35108f9e1b8702fb6c526fe42b2fec5ca032738", - "index_sha256": "95b4ed2d0b189f6847b1c0899f740d1da46933a8f40491e1a02f2d4e57c819fc", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN" - } - ], - "negative_private_probe": "rapp-core-hub", - "identity": { - "forgejo_user": "reuse-surface-source", - "forgejo_org": "coulomb", - "forgejo_team": "reuse-surface-source-readers", - "restricted": true, - "is_admin": false, - "visibility": "private", - "password": "random value retained only in attended process memory; never stored in evidence", - "team_permission": "read", - "includes_all_repositories": false, - "can_create_org_repo": false, - "units_map": { - "repo.code": "read", - "repo.actions": "none", - "repo.packages": "none", - "repo.issues": "none", - "repo.ext_issues": "none", - "repo.wiki": "none", - "repo.pulls": "none", - "repo.releases": "none", - "repo.projects": "none", - "repo.ext_wiki": "none" - }, - "team_repositories": [ - "coulomb/rapp-openbao", - "coulomb/rapp-policy-nexus", - "coulomb/rapp-postgres", - "coulomb/rapp-sbom-nexus", - "coulomb/rapp-telemetry", - "coulomb/rapp-tenant-engine", - "coulomb/rapp-user-engine" - ], - "token_scopes": [ - "read:repository" - ], - "token_name_prefix": "reuse-surface-source-read-", - "reuse_other_workload_token": false, - "fail_on_differing_existing_identity_or_team": true, - "enforcement": "Verify complete team membership and all effective user repository grants; no other team, collaborator or organisation grants may provide extra private read access." - }, - "delivery": { - "kv_mount": "platform", - "kv_path": "platform/workloads/reuse/reuse-surface/forgejo-source-read", - "field": "FORGEJO_SOURCE_TOKEN", - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN", - "eso_role": "reuse-surface-runtime-eso", - "eso_identity": "reuse/reuse-surface-eso", - "audience": "openbao", - "ttl": "15m", - "cluster_secret_store": "openbao-reuse", - "store_namespace_condition": [ - "reuse" - ], - "external_secret": "reuse/reuse-surface-runtime", - "target_secret": "reuse/reuse-surface-env", - "existing_consumption": "Deployment reuse/reuse-surface envFrom secretRef reuse-surface-env; environment variable available only after controlled rollout. Existing image and resource requests retained.", - "policy_delta": { - "file": "railiance-platform/openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl", - "add_exact_stanza": "path \"platform/data/workloads/reuse/reuse-surface/forgejo-source-read\" { capabilities = [\"read\"] }", - "retain_existing_paths": true, - "do_not_change": "Caller-facing workload-kv-read-reuse-surface-runtime policy or writer OIDC role; those remain unable to read this new path." - }, - "external_secret_data_delta": { - "secretKey": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN", - "remoteRef": { - "key": "workloads/reuse/reuse-surface/forgejo-source-read", - "property": "FORGEJO_SOURCE_TOKEN" - } - }, - "delivery_design_reason": "Uses the existing namespace-limited ESO store and identity plus the existing envFrom interface. Separate new KV path keeps scoped writer callers from also acquiring private-source access. No new static ESO token or new store is needed.", - "native_source_auth": { - "auth_env": "REUSE_SURFACE_FORGEJO_SOURCE_READ_TOKEN", - "auth_header": "Authorization", - "header_behavior": "Native federation _auth_headers prefixes a bare token with Bearer; owner must verify accepted Forgejo raw API route with this actual native implementation." - }, - "rollout_image": "Pending full consumer fix reviewed image digest; same-image rollout of current consumer is prohibited." - }, - "consumer_gate": "Reader must not be delivered until reuse-surface owner supplies verified image digest containing BOTH authenticated redirect rejection/HTTPS enforcement AND runtime-controlled exact URL allowlist checked before credential lookup. Apps owner must deploy REUSE_SURFACE_AUTH_SOURCE_ALLOWLIST mapping the reader environment to exactly fourteen consented pinned index/entry URLs and inventory existing authenticated sources for migration. Current image and redirect-only fix are not approved. Native owner helper must verify all seven pinned indexes and entry hashes after rollout.", - "approval": "No approval recorded. External supervisor approval receipt must bind exactly CCR, script, contract and ESO policy bytes; CCR must be approved. Additional forge/apps/reuse owner reviews and rollout approval remain required.", - "metadata_consent_sha256": "4a4602ad926e3af855d6ddba4731430d33e4a7432c507ba0a5193b9f43056b52", - "preparation_sha256": "7819eb51d249f5c7aff4df9fb520afcfb76603b05a4104439499102b6c20af99", - "recovery": "On ambiguous mutation stop. Reconcile complete Forgejo identity/team/repository/PAT metadata and exact KV field through suppressed helper before retry. Token without KV or reverse refuses automatically; revoke identified dedicated PAT through attended Forgejo owner recovery before retry. Never remint on observation timeout. KV CAS=0 prevents overwrites. Do not delete shared runtime Secret.", - "consumer_preparation": { - "owner": "reuse-surface", - "allowlist_artifact": "/tmp/cust81-reuse-reader-owner/docs/assessments/cust81-private-reader/runtime-auth-source-allowlist-r1.json", - "allowlist_sha256": "e1948c537e206b30fc72b9e4732a3f600d92e131813e6f7ecfd4bfbb18c27286", - "federation_py_sha256": "dbaa40f25cc0f090143578cd1c7068cb2cb00960fafd17d5239bf8a5bac514dd", - "integration_patch_sha256": "da9634481ce80658dfce815d3539ba023234938dc76163dfd93d7677be95ed51", - "review_state": "changes_requested for current consumer; preparation patch alone is not deployed-image evidence" - }, - "pagination_contract": "All list endpoints page until observed empty, max20pages; reject duplicate/missing IDs, reconcile X-Total-Count when supplied, reject changed/mismatched counts and bound exhaustion. Never infer completion from requestedlimit100 versus server-capped50.", - "effective_grants_contract": "Forgejo11.0.3 /user/repos?order_by=id with Sudo reader uses OwnerID=reader and Collaborate=None; SearchRepositoryCondition defaults collaborations true and includes direct UserAccessRepoCond plus UserOrgTeamRepoCond. It is not owned-only despite Swagger summary. Independent deployed-version compatibility and live inventory proof pending.", - "r1_review_failure": { - "patch_sha256": "a94547b419b87e38dc178b02d1d31af61cc11a71e3734052a190167668db8e30", - "confirmed_defect": "R1 len(batch)<100 stopped after server-capped50, omitting later grants", - "corrected_preliminary_claim": "Owned-only endpoint claim disproved by Forgejo11.0.3 model defaults; no endpoint replacement based on false premise", - "same_proposal": "CCR-2026-0046; revision2 is refinement, not a new successful trial" - }, - "api_source_evidence": [ - { - "upstream_path": "models/repo/repo/list.go", - "url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/models/repo/repo/list.go", - "sha256": "bb7833badf0fe7f6a2c800c2f301cb846b5968ce3f9e9bd29c3f1677db42acd9", - "local_cache": "/tmp/cust81-forge-contract-source/source-models_repo_repo_list.go" - }, - { - "upstream_path": "routers/api/v1/utils/page.go", - "url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/routers/api/v1/utils/page.go", - "sha256": "1d286f60b5571513fb5a26a9243c22eae65f05d7be32dfe5d0f0b15d20550954", - "local_cache": "/tmp/cust81-forge-contract-source/source-routers_api_v1_utils_page.go" - }, - { - "upstream_path": "services/convert/utils.go", - "url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/services/convert/utils.go", - "sha256": "2689ffc579ea1df69457fbe84410fef101ae4a9580f0e56dcc3113a2988626b3", - "local_cache": "/tmp/cust81-forge-contract-source/source-services_convert_utils.go" - }, - { - "upstream_path": "modules/setting/api.go", - "url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/modules/setting/api.go", - "sha256": "1f640ed884afaa6454c4bf427eccd2d6bb9a092fe37ac1aedfdc5eb85de4f7ba", - "local_cache": "/tmp/cust81-forge-contract-source/source-modules_setting_api.go" - }, - { - "upstream_path": "routers/api/v1/user/repo.go", - "url": "https://codeberg.org/forgejo/forgejo/src/tag/v11.0.3/routers/api/v1/user/repo.go", - "sha256": "adee62e6671aadd95b5013a64dbb690f2d81a0b9b4de0350c70b4153074ca6ec", - "local_cache": "/tmp/cust81-forge-contract-source/source-routers_api_v1_user_repo.go" - } - ], - "rollback_helper": { - "path": "scripts/revoke-reuse-surface-forgejo-source.py", - "scope": "Separate approval binds exact PAT id/name and artifact bytes; Basic-auth revocation after dedicated-user memory-only password reset. Preserve KV/sharedruntime; no remint. Reconcile uncertain DELETE by inventory absence." - } -} 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 deleted file mode 100644 index dd5cdb0..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/conditional-approved-ccr-state-candidate-r1.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "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 deleted file mode 100644 index cde65e0..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/platform-attended-runner-review-r1.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "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/docs/proposals/2026-10-15-reuse-private-reader/platform-owner-review-r1.json b/docs/proposals/2026-10-15-reuse-private-reader/platform-owner-review-r1.json deleted file mode 100644 index 484be01..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/platform-owner-review-r1.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "id": "RPF-WP-0058-PLATFORM-OWNER-REVIEW-R1", - "owner_repo": "railiance-platform", - "reviewer": "platform-reader-owner delegated worker", - "basis": [ - "INTENT.md shared stateful service custody, Q1 approval/delegated apply/revocation, stable delivery interface", - "AGENTS.md credential-change owner review and founder-gated attended provisioning", - "No own supervision record exists; supervised preparation only" - ], - "disposition": "prepared_with_conditions_not_execution_approval", - "scope": "Only platform custody/CCR/policy/bootstrap; other repo reviews not authored", - "findings": [ - "Separate exact KV path and exact data-read ESO delta preserve writer-only caller boundary.", - "No all-repo Forgejo team; preflight rejects different existing user/team, unexpected team repos/members, other teams/orgs/private grants, orphan PAT/KV, and unverified PAT/KV binding.", - "Quiet create-only bootstrap with per-user lane lock, durable fsynced intents/outcomes, CAS=0 KV; uncertainty is reconciled before retry, never blindly reminted.", - "Full consumer redirect/HTTPS plus authenticated URL allowlist image and exact config mandatory before credential delivery.", - "Approval receipt is locally bound revision evidence, not authenticated authority; attended founder/platform lane remains authorization source." - ], - "validation": { - "targeted_tests": 17, - "result": "passed", - "ccr_validator": "passed with expected unconfirmed ESO binding/apply blocked warning", - "diff_check": "passed" - }, - "pending": [ - "Allocation recheck against owner primary before integration", - "Independent Forgejo/apps/reuse owner reviews and actual founder credential provisioning decision", - "Live ESO claims and namespace condition confirmation; no token read or live mutations performed", - "Forgejo deployed API metadata contract support proof; sudo inventory and token_last_eight missing metadata fail closed", - "Apps fixedimage digest/config/migration/dryrun/headroom rollout approval and receipt", - "Native seven index+entry verification and final hosted70/70 audit" - ], - "execution_approved": false, - "live_changes_performed": false -} diff --git a/docs/proposals/2026-10-15-reuse-private-reader/platform-owner-review-r2.json b/docs/proposals/2026-10-15-reuse-private-reader/platform-owner-review-r2.json deleted file mode 100644 index 7c41582..0000000 --- a/docs/proposals/2026-10-15-reuse-private-reader/platform-owner-review-r2.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "id": "RPF-WP-0058-PLATFORM-OWNER-REVIEW-R2", - "owner_repo": "railiance-platform", - "reviewer": "platform-reader-owner delegated worker", - "basis": [ - "INTENT.md shared stateful service custody, Q1 approval/delegated apply/revocation, stable delivery interface", - "AGENTS.md credential-change owner review and founder-gated attended provisioning", - "No own supervision record exists; supervised preparation only" - ], - "disposition": "approved", - "scope": "Platform custody PREPARATION artifact revision2 only; not founder credential/production authority or another repo review", - "findings": [ - "Separate exact KV path and exact data-read ESO delta preserve writer-only caller boundary.", - "No all-repo Forgejo team; preflight rejects different existing user/team, unexpected team repos/members, other teams/orgs/private grants, orphan PAT/KV, and unverified PAT/KV binding.", - "Quiet create-only bootstrap with per-user lane lock, durable fsynced intents/outcomes, CAS=0 KV; uncertainty is reconciled before retry, never blindly reminted.", - "Full consumer redirect/HTTPS plus authenticated URL allowlist image and exact config mandatory before credential delivery.", - "Approval receipt is locally bound revision evidence, not authenticated authority; attended founder/platform lane remains authorization source.", - "Forgejo version source disproves preliminary owned-only /user/repos claim; unchanged endpoint with id sort includes direct and team grants. R2 pages until observed empty, refuses missing/duplicate IDs and inconsistent total counts." - ], - "validation": { - "targeted_tests": 28, - "result": "passed", - "ccr_validator": "passed with expected unconfirmed ESO binding/apply blocked warning", - "diff_check": "passed" - }, - "pending": [ - "Allocation recheck against owner primary before integration", - "Independent Forgejo/apps/reuse owner reviews and actual founder credential provisioning decision", - "Live ESO claims and namespace condition confirmation; no token read or live mutations performed", - "Forgejo deployed API metadata contract support proof; sudo inventory and token_last_eight missing metadata fail closed", - "Apps fixedimage digest/config/migration/dryrun/headroom rollout approval and receipt", - "Native seven index+entry verification and final hosted70/70 audit", - "Separate explicit exact-token recovery approval and manual rotation due-date task/receipt before activation." - ], - "execution_approved": false, - "live_changes_performed": false, - "revision": 2, - "decision": "approved", - "work_item": "RPF-WP-0058-T01", - "reviewed_artifacts": { - "credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml": "90522912fb78fed72e625382fb7114a0e9aafe76d59e36bffecab83c5bb7d643", - "scripts/provision-reuse-surface-forgejo-source.py": "abf60a754596baa1610d23b1fd367385b0222ac8aeaebba0f2cfb7ec4c62055b", - "scripts/revoke-reuse-surface-forgejo-source.py": "f78d1b26fbff93c6cc481a896d9fb9b9ce93049b8b3267fa41a64bc57dadb162", - "tests/test_reuse_surface_forgejo_source_provision.py": "0d06fd22596a0f17ecd8dd5ff6a12bb1b1782258d00126ad468661099d7d298b", - "tests/test_reuse_surface_forgejo_source_recovery.py": "08247129b220c198d8f7d1322ce63956203b9ffcaa0342e52990bb6520fd138d", - "docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json": "2bb72671e0a5f230497ab8ef2967a20ee68d1f2cf081f07c84f6db3442e18d8b", - "openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl": "6320f6c2d262d924ea16291d301d5f9eeb7efdb6ce7ea141c1a41d277d86b01b", - "docs/proposals/2026-10-15-reuse-private-reader/README.md": "dcc8879d96b85b21b52c6cac9d7a58db78169ed903490ef11561bf8d6aa507e5", - "workplans/RPF-WP-0058-reuse-private-source-reader.md": "9aeb0fbbb5359b7ba9afb4f5f7f2c55f84dade0d6d95fe98b15901e656aed5b5", - "docs/proposals/2026-10-15-reuse-private-reader/apps-externalsecret-data-item.draft.yaml": "e582a7d2eeb68668858ec19a6a32b8b816a4850b5ad1757f7686330b141578b2" - }, - "supersedes_review_artifact_sha256": "0a9746f65bb79982632accd9e20a2a632eafc5ee5410f4eddeadd625e73ca0bd", - "refinement_of_same_proposal": "CCR-2026-0046 R1 confirmed capped-page bug retained; R2 fixes pagination, not a new trial" -} diff --git a/docs/proposals/2026-10-17-key39-t02-owner-returns.md b/docs/proposals/2026-10-17-key39-t02-owner-returns.md deleted file mode 100644 index 1b0f980..0000000 --- a/docs/proposals/2026-10-17-key39-t02-owner-returns.md +++ /dev/null @@ -1,48 +0,0 @@ -# KEY-WP-0039-T02 owner returns (source-prepared, not admitted) - -Date: 2026-10-17. Status: **source-prepared, not admitted**. No live read, no -admission, custody receipt, owner review or founder go is claimed here. -Prepared from repo sources at `08c1f03` for key-cape -`docs/proposals/2026-10-11-key39-next-execution-prerequisites.json` -(`shortest_next_sequence` steps 1 and 3, Platform-owned parts). - -## 1. Coding-client admission for `codex-railiance-platform` - -- Existing record: `docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json` - (commit `9460f6f`, status `proposed_source_only`), with the bounded inspection - in `workplans/ADHOC-2026-10-11.md` and identity notes in - `docs/coding-agent-openbao-identity.md`, `openbao/auth/coding-agent-jwt-role.json`. -- It is a **proposal, not an admission/delivery record**. No admitted custody - entry, consumer host/injection binding or value-free metadata receipt for the - issuer env reference `KEYCAPE_CODEX_RAILIANCE_PLATFORM_CLIENT_SECRET` exists - in this repo. CCR-2026-0040 (create-only generate grant) does not supply - consumer delivery and is not reused. -- No new proposal drafted: the 2026-10-11 proposal is the one narrow - proposal; its `missing_admission_gates` remain the owner work. No CCR number - is assigned. - -## 2. `secrets-engine-openbao` on `auth/keycape-services` as declared in source - -- Role source: `openbao/auth/secrets-engine-jwt-role.json`; contract - `docs/contracts/secrets-engine-openbao-jwt-login.yaml` (mount `keycape-services`, - last change `c924ff2`). Declared: `bound_audiences [secrets-engine-openbao]`, - `bound_subject service:secrets-engine`, string bound claims - `principal_type=service, tenant=tenant:platform, roles=secrets-engine, - scope=openbao:login`, policy `secrets-engine-login-self`, no default policy, - `token_type service`, ttl/max/explicit-max `5m`, `token_num_uses 8`. -- **`expiration_leeway` is not declared** in role or contract (nor - `clock_skew_leeway` / `not_before_leeway`); live value is whatever the - server default or an out-of-source write set. Unknown from source. -- **Node-local Bao origin is not declared** in any Platform source checked - (contract, lane design, coding-agent identity doc). Unknown from source. - -## Needs attended sanitized owner read (live confirmation) - -1. Node-local Bao origin on the actual consumer node route, plus public - `GET /v1/sys/health` receipt with cluster-ID digest. -2. Live `auth/keycape-services/role/secrets-engine-openbao` matches the source - fields above, and its leeway values (expiration/clock-skew/not-before). -3. `auth/keycape-services/config` issuer/JWKS binding matches KeyCape's issuer. -4. For step 1: protected-custody entry/key, admitted consumer identity/host, - private injection mechanism and matched versions for the coding client — - value-free metadata only, after owner admission. diff --git a/docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml b/docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml index d7afc27..8861be7 100644 --- a/docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml +++ b/docs/provisioning-windows/2026-10-04-1000/keycape/eso.draft.yaml @@ -1,11 +1,11 @@ -# Admission approved by founder decision 2ab717e1-7881-491e-aed4-28946550c289 (A2). Apply only under the separate step-2 go, after the custody run. +# DRAFT REVIEW ARTIFACT. Do not apply. No custody/admission supplied. apiVersion: external-secrets.io/v1 kind: ClusterSecretStore metadata: name: openbao-keycape-secrets-engine-openbao annotations: railiance.io/credential-change: CCR-2026-0033 - railiance.io/admission: approved + railiance.io/admission: pending-review spec: provider: vault: @@ -16,11 +16,11 @@ spec: kubernetes: mountPath: kubernetes role: external-secrets-keycape-secrets-engine-openbao + audiences: + - openbao serviceAccountRef: name: external-secrets namespace: external-secrets - audiences: - - openbao conditions: - namespaces: - sso @@ -32,7 +32,7 @@ metadata: namespace: sso annotations: railiance.io/credential-change: CCR-2026-0033 - railiance.io/admission: approved + railiance.io/admission: pending-review spec: refreshInterval: 5m secretStoreRef: @@ -46,7 +46,7 @@ spec: metadata: annotations: railiance.io/credential-change: CCR-2026-0033 - railiance.io/admission: approved + railiance.io/admission: pending-review data: - secretKey: client-secret remoteRef: diff --git a/docs/provisioning-windows/2026-10-04-1000/keycape/founder-runbook.md b/docs/provisioning-windows/2026-10-04-1000/keycape/founder-runbook.md index 89ed2ec..8e07473 100644 --- a/docs/provisioning-windows/2026-10-04-1000/keycape/founder-runbook.md +++ b/docs/provisioning-windows/2026-10-04-1000/keycape/founder-runbook.md @@ -117,14 +117,14 @@ python3 scripts/credential-change.py applier-apply credential-change-requests/CC # apply (attended platform-admin; one phrase per CCR) warden access openbao-platform-admin-login --exec -- \ python3 scripts/credential-change.py applier-apply credential-change-requests/CCR-2026-0033.yaml \ - --confirm "DELEGATED APPLY CCR-2026-0033" --actor founder --record-state-hub --quiet + --confirm "DELEGATED APPLY CCR-2026-0033" --actor founder --record-state-hub warden access openbao-platform-admin-login --exec -- \ python3 scripts/credential-change.py applier-apply credential-change-requests/CCR-2026-0034.yaml \ - --confirm "DELEGATED APPLY CCR-2026-0034" --actor founder --record-state-hub --quiet + --confirm "DELEGATED APPLY CCR-2026-0034" --actor founder --record-state-hub ``` -Expected: exit 0 with no output (`--quiet`, which the warden wrapper requires) and a -value-free `delegated_metadata_apply` evidence entry in each CCR. +Expected: `[OK] CCR-2026-0033.yaml delegated metadata apply recorded`, the same for +0034, and a value-free evidence entry in each CCR. ESO: `eso.draft.yaml` in this directory carries `railiance.io/admission: pending-review`. Promote it to `approved` in a reviewed commit after A2 first. Then: diff --git a/docs/railiance01-coordinated-reboot.md b/docs/railiance01-coordinated-reboot.md index dc3f5c5..6a5a873 100644 --- a/docs/railiance01-coordinated-reboot.md +++ b/docs/railiance01-coordinated-reboot.md @@ -8,13 +8,11 @@ claim. This document does not authorize a reboot. | Phase | Accountable owner | Required evidence | | --- | --- | --- | -| Window, notifications, go/no-go | operator + `railiance-platform` | approval/window id; new scenario id; fresh human decision; named driver | -| Independent abort authority (RPF-WP-0017) | `railiance-infra` | named abort owner acknowledgement; may stop the window at any step without a platform vote | -| Attended login containment | `railiance-platform` | `reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json` accepting the exact ops-warden revision | -| Host access, provider console, reboot, firewall, time | `railiance-infra` | SSH; same-day `RAIL01-CONSOLE-*` receipt; host returns; UFW/NTP/tunnel checks | -| k3s/node/DNS/operators | `railiance-cluster` | node Ready; named core platform workloads Ready; failing-pod diff | +| Window, notifications, abort authority | operator + `railiance-platform` | approval/window id; named driver and abort operator | +| Host access, provider console, reboot | `railiance-infra` | SSH and independent provider-console access; host returns | +| k3s/node/DNS/operators | `railiance-cluster` | node Ready; core platform pods reconciled | | OpenBao snapshot, Shamir unseal, post-unseal checks | `railiance-platform` | current Raft snapshot receipt; 2-of-3 quorum attestation; unsealed verification | -| All CNPG clusters (`platform-pg` backup/WAL) | `rapp-postgres` (+ owning app for each cluster) | per-cluster UID, primary PVC UID, systemID, 1/1 primary; fresh `platform-pg` Barman backup | +| `platform-pg` backup, WAL, PVC, primary | `rapp-postgres` | fresh completed Barman backup; continuous archiving; 1/1 primary | | ESO stores and projections | `railiance-platform` | stores Valid; ExternalSecrets SecretSynced after unseal | | Audit custody and synthetic verification | `audit-core` | health/readiness/integrity and bounded synthetic E2 result | @@ -31,77 +29,22 @@ Run the value-safe, read-only gate: python3 scripts/audit-core-recovery-preflight.py node-reboot ``` -The automated checks (`automated_checks` in the output) require: - -- one Ready node, active k3s, and the boot id recorded; -- `timedatectl` `NTPSynchronized=yes` (receipt/backup ages are untrusted otherwise); -- UFW active with exactly the declared allow set — `OpenSSH` and Nydus - `2224/tcp` (v4/v6) — and `6443`/`8472` not open (railiance-infra - RAIL-HO-WP-0009 / Goss declaration); -- the workstation `k3s-api-railiance01` tunnel answering (`bridge check`); -- every named core platform workload Ready, plus every other Deployment and - StatefulSet in their namespaces: CoreDNS, local-path-provisioner, traefik, - metrics-server, cert-manager, the CNPG operator, external-secrets, - knative-serving (activator, controller, net-kourier-controller) and the - kourier gateway, argocd, and openbao; -- every CNPG cluster, discovered live (today `apps-pg`, `forgejo-db`, - `net-kingdom-pg`, `platform-pg`, `platform-pg-2`, `state-hub-db`, - `target-revenue-pg`, `user-engine-pg`; a vanished expected cluster fails), - 1/1 Ready, current primary on a healthy PVC, `ContinuousArchiving=True` where - backup is configured, with cluster UID, primary PVC UID, and PostgreSQL - `systemID` (from `pg_controldata`, value-safe) recorded; -- a completed `platform-pg` backup no older than 26 hours; -- every ClusterSecretStore `Valid` and every ExternalSecret `SecretSynced`; -- unsealed OpenBao and the reviewed audit-core digest in both the Deployment - spec and the running pod (`imageID`), 1/1 Ready; -- before the reboot, every Terminating PVC resolved or explicitly listed with - `--accept-terminating-pvc`. - -The output also records the all-namespace failing-pod baseline -(`reboot_evidence.failing_pods`). On 2026-10-14 it held six pre-existing pods: -three `coulomb/ihp-railiance-probe-*`, `forgejo/forgejo-gitea-*`, -`forgejo/forgejo-runner-*`, and `telemetry/telemetry-smtp-proof-20260928-*`. -Keep the pre-reboot JSON; it is the comparison input after the reboot. - -The following are human gates and make `ready_for_live_execution` remain false -until supplied: +The automated checks require one Ready node, active k3s, healthy 1/1 +`platform-pg`, continuous archiving, a successful backup no older than 26 +hours, unsealed OpenBao, Valid audit-core stores, SecretSynced projections, and +the reviewed 1/1 Ready audit-core image. The following are human gates and make +`ready_for_live_execution` remain false until supplied: - approved window id and acknowledgements from all five owners; -- a new scenario id (`--scenario-id`) and a fresh human GO decision - (`--human-decision-id`). Consumed decisions `449a697a-…` and `85724c0c-…` and - scenario `WARDEN-WP-0027-T02-DRILL-20260822-01` are not reusable and are - denied by the preflight; -- the RPF-WP-0017 re-acceptance receipt - `reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json` recording - acceptance of the exact ops-warden revision used for the attended login - (`11b2a9d…` was rejected; `6e14300…` accepted on 2026-10-14). The accepted - revision must be pushed to ops-warden `origin/main` and installed with - `uv tool install --reinstall /home/worsch/ops-warden` from that clean checkout - before the window. The preflight compares every installed `warden` module - byte-for-byte with the accepted revision and closes this gate on any mismatch; - rerun it at the hold point immediately before the attended login and abort if - it no longer matches (the 2026-09-01 WARDEN-WP-0036-T02 operation ran on - unaccepted code — do not repeat that); -- a named attended driver/abort operator and the `railiance-infra` - independent abort owner acknowledgement (`--infra-abort-owner-ack`); -- independent provider-console access, not SSH alone: a value-safe - `RAIL01-CONSOLE-` receipt recorded by railiance-infra under - its `docs/evidence/railiance01-console-access/` (procedure: - railiance-infra `docs/railiance01-console-access.md`), passed with - `--provider-console-receipt`. The preflight accepts it only when it is dated - the window day, names the approved window id, and has `console_shows_host` - and `independent_of_guest_network` true. No out-of-band console is - established today, so T03 cannot pass this gate until railiance-infra - RAIL-HO-WP-0011-T09 is done; +- a named attended driver/abort operator; +- independent provider-console access, not SSH alone; - a current non-secret OpenBao Raft snapshot receipt that validates against `docs/openbao-reboot-snapshot-receipt.example.json`, the live cluster id and Raft index, and the default 24-hour freshness bound; - explicit availability of two of three Shamir shares through their approved out-of-band custody paths. -Receipt validation failures are reported in `receipt_gate_failures` and never -abort evidence capture. Do not reuse old restore-drill templates as current -snapshot evidence. Do not +Do not reuse old restore-drill templates as current snapshot evidence. Do not put a snapshot, unseal share, token, password, or Secret response in Git or State Hub. @@ -111,66 +54,18 @@ State Hub. backup-restore, and security-engagement mutations on `railiance01`. 2. Confirm no temporary Whitehat or other expiring projection will cross the window. Clean it first if present. -3. Capture the read-only preflight result (keep the JSON as the pre-reboot - evidence: boot id, failing-pod baseline, CNPG UIDs/PVC UIDs/systemIDs) and - the audit-core chain-head attestation. +3. Capture the read-only preflight result and audit-core chain-head attestation. 4. Confirm the latest `platform-pg` Barman backup is completed, WAL archiving is healthy, and the PVC is Bound. If the daily backup is stale, take and - verify an on-demand backup before continuing. `state-hub-db` and - `user-engine-pg` have no CNPG backup configured (nor do `net-kingdom-pg` and - `target-revenue-pg`); their owners should take and verify a fresh backup - through their own lane (for `user-engine-pg`, the rapp-user-engine backup - CronJob) or record an explicit waiver before the window. -5. Resolve the stuck Terminating PVC `platform-pg-drill/platform-pg-drill-1` - (owner: `rapp-postgres`, separately approved) or list it for the window with - `--accept-terminating-pvc platform-pg-drill/platform-pg-drill-1`. -6. Create and escrow a fresh OpenBao Raft snapshot through the approved + verify an on-demand backup before continuing. +5. Create and escrow a fresh OpenBao Raft snapshot through the approved encrypted backup lane. Record only its evidence receipt and verification. Pass that receipt to the preflight with `--openbao-snapshot-evidence /path/to/receipt.json`; a merely existing, stale, placeholder, wrong-cluster, unencrypted, or on-host receipt is denied. - - The reviewed lane is `make openbao-reboot-snapshot` (offline tests: - `make openbao-reboot-snapshot-test`). It runs - `scripts/openbao-reboot-snapshot.py` as the single silent child of the - contained `openbao-platform-admin-login` (via - `scripts/openbao-attended-exec.py`): - - ```bash - make openbao-reboot-snapshot \ - SNAPSHOT_SCENARIO_ID=RPF-WP-0015-T03- \ - SNAPSHOT_RECEIPT=/abs/new/receipt.json \ - SNAPSHOT_CUSTODIAN_ESCROW_REF= \ - SNAPSHOT_CUSTODIAN_RECIPIENT=age1... # only if that offline key differs from the offsite recipient - SNAPSHOT_PRIVATE_DIR=/abs/0700/dir # optional; default is the session HOME (prefer tmpfs) - ``` - - Recoverability requires a decryption key held outside OpenBao: the escrow - reference is mandatory and the preflight rejects a receipt without one. If the - custodian's offline key is the offsite recipient, the reference alone is - recorded; otherwise the snapshot is also encrypted to - `SNAPSHOT_CUSTODIAN_RECIPIENT` (age: any one recipient key decrypts). The - receipt lists all recipients. Reading the offsite `AGE_PRIVATE_KEY` for the - decrypt-stream proof is a founder decision (in memory only), recorded in - `reviews/RPF-WP-0015-T03-founder-go-20261014.json#escrow_key_read` and cited by - the receipt. Snapshots upload to the founder-created `openbao/` folder. - - In order it reads `bao status` (initialized, unsealed, Raft), saves the - snapshot through the tunnel into a fresh 0700 temp dir outside Git, re-reads - status (same `cluster_id`, index not behind), age-encrypts to the offsite - lane recipient declared in `lib/railiance-backup-common.sh` (plus the custodian - recipient, if given), proves the - lane's escrow key derives that recipient and decrypt-streams the ciphertext - to the plaintext sha256, then uploads ciphertext only to the governed - Nextcloud lane with `If-None-Match: *`. The temp dir is always shredded. - The receipt (0600, `encrypted_location_ref` - `offhost-custody::`) is self-checked with the preflight's own - validator before it is written; on any failure the file holds a value-safe - `status: failed` record instead, which the preflight rejects. -7. Have both Shamir share custodians present. Prove provider-console access and - SSH access (same-day `RAIL01-CONSOLE-*` receipt). Record the exact restart - operator, abort operator, and the railiance-infra independent abort owner. -8. Re-run the preflight with the approval and attestation flags. The result +6. Have both Shamir share custodians present. Prove provider-console access and + SSH access. Record the exact restart and abort operators. +7. Re-run the preflight with the approval and attestation flags. The result must say `ready_for_live_execution: true`. Pause for the final operator go/no-go; this is the last reversible point. @@ -180,82 +75,35 @@ State Hub. console. Do not reinstall the OS or k3s. 2. Recover host network/SSH, then verify `k3s` active and the single node Ready. If SSH does not return, use the provider console; do not change firewall - declarations as an ad hoc recovery. `railiance-infra` verifies: the boot id - differs from the pre-reboot capture and exactly one boot occurred since it; - UFW is active with only `OpenSSH` + `2224/tcp`, `6443`/`8472` not open; the - `k3s-api-railiance01` tunnel answers; and `timedatectl` reports - `NTPSynchronized=yes`. Do not unseal, run OIDC/ESO checks, or trust any - receipt or backup age until time is synchronized. -3. `railiance-cluster` verifies these core platform workloads are Running and - reconciled: CoreDNS, local-path-provisioner, traefik, metrics-server, - cert-manager (+ cainjector/webhook), CNPG operator - (`cnpg-system/cnpg-controller-manager`), external-secrets (+ webhook/cert - controller), knative-serving (activator, controller, net-kourier-controller) - and `kourier-system/3scale-kourier-gateway`, argocd, and the OpenBao pod - (Running but sealed, so not Ready yet). Application readiness may still be - false at this point. Images not already cached on the node cannot be pulled - until in-cluster Forgejo (`forgejo.coulomb.social`) and `forgejo-db` are up; - `ImagePullBackOff` on such pods is an expected lag, not a failure, until - Forgejo is serving — escalate only if it persists after Forgejo is Ready. -4. `rapp-postgres` (with each cluster's owner) verifies for every CNPG cluster - the same cluster UID, primary PVC UID, and `systemID` as the pre-reboot - evidence, primary 1/1 Ready, `ContinuousArchiving=True` where configured, - and no re-bootstrap. A changed systemID or PVC UID is a stop condition. + declarations as an ad hoc recovery. +3. Verify CoreDNS, CNPG operator, External Secrets operator, and the OpenBao pod + are running. Application readiness may still be false at this point. +4. `rapp-postgres` verifies the same `platform-pg` PVC and cluster identity, + primary 1/1 Ready, `ContinuousArchiving=True`, and no re-bootstrap. 5. Expect OpenBao to be sealed. Two custodians unseal it through the attended, non-recorded share ceremony. Run `make openbao-status` and `make openbao-verify-post-unseal` after it reports initialized and unsealed. -6. Verify every ClusterSecretStore is `Valid` and every ExternalSecret is - `SecretSynced` (not only `openbao-audit-core`/`openbao-audit-core-database` - and the audit-core projections). If they remain in the known stale `Vault is sealed`/`InvalidProviderConfig` state, +6. Verify `openbao-audit-core` and `openbao-audit-core-database` are Valid and + the audit-core sender/database ExternalSecrets are SecretSynced. If they + remain in the known stale `Vault is sealed`/`InvalidProviderConfig` state, the platform owner may restart only Deployment `external-secrets/external-secrets`, then recheck reconciliation. 7. Do not restart audit-core to cure database timing. `/healthz` must remain the liveness boundary; wait for `/readyz` to recover naturally after DNS, PostgreSQL, OpenBao, and ESO are healthy. Record pod identity/restart count. 8. Run audit-core integrity verification against the pre-reboot chain head and - one separately approved synthetic E2. The E2 runs through the reviewed sender lane in - its own attended login: `make audit-t02-attended-window` with - `AUDIT_T02_MODE=e2` and an approval receipt whose `task_id` is - `RAILIANCE-WP-0024-T03`. It projects one synthetic sender and sends one event - twice through the driver's `expect-recovered` phase: the first answer must be a - fresh `accepted` (HTTP 200/202), the second `duplicate` for the same fixture - (accepted exactly once); it touches no lease. The harness refuses unless both - hold and the pod UID and restart count are unchanged. The driver owns no - server-side object; the synthetic event stays permanently in the append-only - chain under its trial tenant, and only the sender identity is removed. Confirm no chain - break, the prior marker remains readable, and the new event is accepted - exactly once. - Window `rpf-wp-0015-t03-window-20261014-01` does not run this E2. Founder - decision `rpf-wp-0015-t03-founder-go-20261014-01` defers it to its own - attended window and keeps tonight to the chain check. Do not mark T03 as - proving the synthetic E2 path until that follow-up window runs. -9. Re-run the read-only preflight in post-reboot mode: - - ```bash - python3 scripts/audit-core-recovery-preflight.py node-reboot \ - --pre-reboot-evidence /path/to/pre-reboot-preflight.json - ``` - - `reboot_comparison.passed` must be true: boot id changed with exactly one - boot since the pre-reboot capture, no CNPG identity change, and no failing - pod outside the pre-reboot baseline (`failing_pod_diff.new_failing` empty). - Then end the mutation freeze and notify all owners with value-safe evidence. + one separately approved synthetic E2. Confirm no chain break, the prior + marker remains readable, and the new event is accepted exactly once. +9. Re-run the read-only preflight, end the mutation freeze, and notify all + owners with value-safe evidence. ## Stop, escalation, and recovery boundaries -- Before reboot: abort for missing approval, owner, console receipt, unseal +- Before reboot: abort for missing approval, owner, console access, unseal quorum, fresh PostgreSQL backup, verified OpenBao snapshot, healthy WAL/PVC, - accepted ops-warden revision, new scenario id/fresh decision, NTP sync, or - any overlapping live mutation. `railiance-infra` may abort independently. -- Attended login: if the login emits any unexpected credential output (token, - secret id, password, key, or unrecognised value-shaped text), stop - immediately: self-revoke the issued credential, abort the window, and never - retry under the same scenario or decision. A new scenario id and a fresh - human decision are required before any further attempt. -- After reboot: retain the outage and escalate for a missing host, more than - one boot, firewall drift, unsynchronized time, node/PVC/systemID identity - change, CNPG re-bootstrap, new failing pods outside the baseline (other than - the expected pre-Forgejo image-pull lag), OpenBao initialization loss, failed + or any overlapping live mutation. +- After reboot: retain the outage and escalate for a missing host, node/PVC + identity change, CNPG re-bootstrap, OpenBao initialization loss, failed unseal, audit chain break, or any evidence of data loss. - A reboot cannot be rolled back. Never run `k3s-uninstall`, initialize OpenBao, recover PostgreSQL in place, delete a PVC, or weaken network policy as a @@ -266,9 +114,7 @@ State Hub. ## Evidence contract -Allowed: approval/window ids, scenario and decision ids, receipt ids/paths, -owner acknowledgements, CNPG cluster UIDs and systemIDs, UFW rule targets, -NTP sync boolean, failing pod names, timestamps, host boot id +Allowed: approval/window ids, owner acknowledgements, timestamps, host boot id or uptime, node/cluster/PVC names and UIDs, image digests, backup name/time/age, OpenBao initialized/sealed booleans and version, resource readiness conditions, Secret resource versions and key names, HTTP status sequence, audit chain-head diff --git a/docs/reviews/2026-10-11-access-audit-delivery-v1-platform-review.json b/docs/reviews/2026-10-11-access-audit-delivery-v1-platform-review.json deleted file mode 100644 index 40bb93e..0000000 --- a/docs/reviews/2026-10-11-access-audit-delivery-v1-platform-review.json +++ /dev/null @@ -1,75 +0,0 @@ -{ - "completed_same_owner_record_precedes_formal_review": true, - "decision": "changes_requested", - "findings": [ - { - "counterexamples": [ - "YAML numeric8000.5 (--set-json) passes int-converted range check and renders NetworkPolicy ports.port=8000.5", - "YAML numeric1.5 passes int-converted range check and renders ports.port=1.5", - "YAML numeric65535.5 truncates within range for validation and renders ports.port=65535.5" - ], - "id": "RPF-ACCESS-AUDIT-V1-F01", - "impact": "Helm succeeds but numeric NetworkPolicy port requires an integer. Current valid-port guard does not validate the value actually emitted; source setup can fail Kubernetes admission. No native apply was attempted.", - "location": "charts/flex-auth/templates/networkpolicy.yaml:24", - "required_change": "Reject noninteger receiverPort types/values before range validation and raw rendering, including numeric fractions and booleans, while preserving exact integer1..65535 values. Add independent renderer counterexamples using actual YAML/--set-json numeric values, not only --set strings. Keep default disabled and custody scope unchanged.", - "severity": "blocking_source_validation" - } - ], - "intent_basis": "Platform secure custody, durable platform delivery and stable service interfaces: exact admitted mounted sender credential and route; no independent application logic or native authority from source review.", - "native_executed": false, - "not_approved": [ - "native credential/Secret/API access, token mint or receiver registration", - "native rollout/route/ingress change or source application", - "full emission/adoption/loss completeness or originalowner task closure" - ], - "owner_record": { - "path": "workplans/ADHOC-2026-10-11.md", - "persisted_commit": "431df1d", - "sha256": "9b5f43f4a1b84fbada4f175d445a68f13ea54e47e290fe4fb11d57ac4c08cd69" - }, - "qualified_verified": [ - "Default auditDelivery.enabled=false generates no credential mount/sender flags/new receiver-DNS egress; creates no Secret or sender registration.", - "Enablement requires serve, durable single-writer outbox/Recreate, exact existingSecret/key, explicit receiver/DNS selectors and /v1/events endpoint. No payload/commit semantics code changed.", - "Exactly one projected sender key, readOnly0440 directory without subPath; fsGroup65532, no automountServiceAccountToken, existing auth reviewer mount preserved. Native Secret ownership and actual same-process reread/rotation not proved.", - "New receiver namespace+pod selectors are ANDed in one peer and exact TCPport; DNS peer ANDed with UDP/TCP53. Existing auth TCP443/6443 egress rule is retained and remains broader; do not claim whole-pod traffic is limited to receiver/DNS.", - "Actual source audit-core Service is namespace audit-core, app.kubernetes.io/name=audit-core, port8080; current receiver NetworkPolicy lacks general Access decision-pin sender ingress. No runtime route or registration admission inferred." - ], - "remaining_admission_gates": [ - "Audit owner final per-pin source/tenant/eventclass/cadence/secretpolicy/rate thresholds and writertrue/readfalse sender admission; actual inventory/renames reconciled, no new registry payload invented.", - "Platform/route owner admitted existingSecret/key, attended credential mint/delivery/rotation/revoke custody receipts; old audit-core-senders draft routing is not executable admission.", - "Exact receiver URL Service/targetport, namespace/podlabels and DNS runtime coordinates plus matching receiver ingress approved by network/receiver owners.", - "Immutable Access image with mandatory-commit and current chart provenance, renderer pins and founder rollout go; PVC/free disk/backuprestore and singlewriter Recreate window, rollback preserves pending evidence and never reverts mandatorycommit for availability.", - "Actual native commit-before-every-effect failure503 and withholding proof, same eventIDs receiver accepted202/duplicate200, restartcursor/drain and bounded backlog, private credential revoke/readrefusal/heartbeat/silence findings.", - "Compatible acceptance-time vs commit-time evidence before loss/completeness reconciliation; transport failure/503 cannot be conflated with authorization denial. Existing AccessT08/AuditT02 and reconciliation gates stay open." - ], - "review_scope": "inert Access auditDelivery chart sender mount/custody/network interface source only", - "reviewed_files": { - "/home/worsch/access-engine/charts/flex-auth/templates/deployment.yaml": "a57ad96b676110df4c307a379d13919f5039636146893ed84e256e0b0673393f", - "/home/worsch/access-engine/charts/flex-auth/templates/networkpolicy.yaml": "b42f0e1f4c56e6ced5b9f7f19ed6592df593206b4236b92d30099778527dfbe9", - "/home/worsch/access-engine/charts/flex-auth/values.yaml": "f98d3ded5702adcb648190b943c73c391b72a6334ab54d906a7505be9093ba0b", - "/home/worsch/access-engine/docs/audit-delivery-rollout-preparation.md": "1b29912ccea9e0cf2de82adfa5d460b6c4f7bd4c0de876ddc33ef01bff5f7bb0", - "/home/worsch/access-engine/tests/test_outbox_chart.py": "d405ef381ef154354e5ba1b5c09b81cfa806ecac92df883a8eb6a5448c8e2acf", - "/home/worsch/access-engine/workplans/FLEX-WP-0031-decision-record-emission.md": "9326544af33734937904108e1b3848e557fa7b22808fba98d016a0f27ca55e72" - }, - "reviewed_input_unchanged": true, - "reviewed_manifest": "/tmp/access-audit-delivery-stage/manifest.json", - "reviewed_manifest_sha256": "f4ce9aa88b517d5bfe3e97ff19c62d0bf61f5872fd9c58b6770978961083b293", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "schema": "owner-source-review/v1", - "source_only": true, - "source_pins": { - "access-engine/cmd/access-engine/main.go": "da24fa10e174f117e6cf44f1ba765d5b66078113198fa48fb911f5a91b5cec83", - "access-engine/internal/emission/delivery.go": "8d4bf346c175704b36daa07e7eef1b8deb3a8cbff22d43dc67ccf120fc9dbdc0", - "audit-core/deploy/audit-core.yaml": "227bd366d984c3bb5055303fd63f1eb9faabcf901b3be76ec31dda4fe23fb888", - "audit-core/deploy/networkpolicies.yaml": "c1ba896c9d29ac690ce2059759c46f0152a1e179d0322f505fab0c9e96c51463", - "railiance-platform/INTENT.md": "12c6540735af591f7a4a9fe85ff10d3cd00eec55b5b6b1ddf148abf2d3ac9a9c" - }, - "validation": { - "go": "go test -mod=readonly ./internal/decision ./internal/emission ./internal/readback ./cmd/access-engine ./pkg/api passed with GOCACHE/GOTMPDIR in/tmp; readback has no tests", - "helm_lint": "1chartpassed,0failed; optional icon informational", - "independent_platform_checks": 8, - "renderer": "python3 tests/test_outbox_chart.py passed against isolated exact-stage chart", - "result": "allpass including three expected fractional-port counterexamples; no native/credential action" - }, - "work_item": "RPF-WP-ADHOC-2026-10-11-T01" -} diff --git a/docs/reviews/2026-10-11-access-audit-delivery-v2-platform-review.json b/docs/reviews/2026-10-11-access-audit-delivery-v2-platform-review.json deleted file mode 100644 index c88bc5f..0000000 --- a/docs/reviews/2026-10-11-access-audit-delivery-v2-platform-review.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "completed_same_owner_record_precedes_formal_review": true, - "correction": "RPF-ACCESS-AUDIT-V1-F01 corrected: rendered decimalinteger shape validated before1..65535 bounds and rawport emission. Original8000.5/1.5/65535.5, boolean/null/named inputs refuse. Exact1/65535 render integers; no custody/default/Go payload or commit-semantics change.", - "decision": "approved", - "intent_basis": "Platform secure custody, durable platform delivery and stable service interfaces: exact admitted mounted sender credential and route; no independent application logic or native authority from source review.", - "maker_completed_record": { - "persisted_commit": "c8a3766", - "record_sha256": "be19dec8bc278e6fd3173175b864a309ac67c49b8c4be1d5b3b319b8b3e2c43a", - "repo": "access-engine", - "scope": "completed source preparation, not nativeadmission; original advancement intentional", - "task": "FLEX-WP-ADHOC-2026-10-11-T03" - }, - "native_executed": false, - "not_approved": [ - "native credential/Secret/API access, token mint or receiver registration", - "native rollout/route/ingress change or source application", - "full emission/adoption/loss completeness or originalowner task closure" - ], - "owner_record": { - "managed_identifiers_now_present": true, - "original_completed_record_sha256": "9b5f43f4a1b84fbada4f175d445a68f13ea54e47e290fe4fb11d57ac4c08cd69", - "path": "workplans/ADHOC-2026-10-11.md", - "persisted_commit": "431df1d", - "sha256": "da876fcb075c3e645dcfd6c602a3875ac98f0101b6ae57cf4c4eba70c8206e6c" - }, - "prior_review_retained_sha256": "e972a54e36b13dae05983fcd14b100551dffe9a11d14099273d5698c7353499a", - "qualified_verified": [ - "Default auditDelivery.enabled=false generates no credential mount/sender flags/new receiver-DNS egress; creates no Secret or sender registration.", - "Enablement requires serve, durable single-writer outbox/Recreate, exact existingSecret/key, explicit receiver/DNS selectors and /v1/events endpoint. No payload/commit semantics code changed.", - "Exactly one projected sender key, readOnly0440 directory without subPath; fsGroup65532, no automountServiceAccountToken, existing auth reviewer mount preserved. Native Secret ownership and actual same-process reread/rotation not proved.", - "New receiver namespace+pod selectors are ANDed in one peer and exact TCPport; DNS peer ANDed with UDP/TCP53. Existing auth TCP443/6443 egress rule is retained and remains broader; do not claim whole-pod traffic is limited to receiver/DNS.", - "Actual source audit-core Service is namespace audit-core, app.kubernetes.io/name=audit-core, port8080; current receiver NetworkPolicy lacks general Access decision-pin sender ingress. No runtime route or registration admission inferred." - ], - "remaining_admission_gates": [ - "Audit owner final per-pin source/tenant/eventclass/cadence/secretpolicy/rate thresholds and writertrue/readfalse sender admission; actual inventory/renames reconciled, no new registry payload invented.", - "Platform/route owner admitted existingSecret/key, attended credential mint/delivery/rotation/revoke custody receipts; old audit-core-senders draft routing is not executable admission.", - "Exact receiver URL Service/targetport, namespace/podlabels and DNS runtime coordinates plus matching receiver ingress approved by network/receiver owners.", - "Immutable Access image with mandatory-commit and current chart provenance, renderer pins and founder rollout go; PVC/free disk/backuprestore and singlewriter Recreate window, rollback preserves pending evidence and never reverts mandatorycommit for availability.", - "Actual native commit-before-every-effect failure503 and withholding proof, same eventIDs receiver accepted202/duplicate200, restartcursor/drain and bounded backlog, private credential revoke/readrefusal/heartbeat/silence findings.", - "Compatible acceptance-time vs commit-time evidence before loss/completeness reconciliation; transport failure/503 cannot be conflated with authorization denial. Existing AccessT08/AuditT02 and reconciliation gates stay open." - ], - "review_scope": "corrected disabled-by-default auditDelivery chart mount/custody/network preparation source only", - "reviewed_files": { - "/home/worsch/access-engine/charts/flex-auth/templates/deployment.yaml": "a57ad96b676110df4c307a379d13919f5039636146893ed84e256e0b0673393f", - "/home/worsch/access-engine/charts/flex-auth/templates/networkpolicy.yaml": "53d8ae1851b66c8c9f990fc56bc8ad9076741e2684f39470a7e1583b093d9c14", - "/home/worsch/access-engine/charts/flex-auth/values.yaml": "f98d3ded5702adcb648190b943c73c391b72a6334ab54d906a7505be9093ba0b", - "/home/worsch/access-engine/docs/audit-delivery-rollout-preparation.md": "47805cd51baceea91a45fe6c53a6cca0c58a9acb9ba42354e9c5b426ec188f4f", - "/home/worsch/access-engine/tests/test_outbox_chart.py": "f6c68dfc6f8a1a8dbf2afcab18b428111765b965a419890f52fb19eb51d3ceea", - "/home/worsch/access-engine/workplans/ADHOC-2026-10-11.md": "be19dec8bc278e6fd3173175b864a309ac67c49b8c4be1d5b3b319b8b3e2c43a", - "/home/worsch/access-engine/workplans/FLEX-WP-0031-decision-record-emission.md": "24d228f33a1a76f266ecf8ca9d403d885717df08ba08714eda928a7b1f3007ad" - }, - "reviewed_input_unchanged": true, - "reviewed_manifest": "/tmp/access-audit-delivery-v2-stage/manifest.json", - "reviewed_manifest_sha256": "90d5b7e962da046f205cdfa3267c25d22539c500c537c89033aefa568c3c5e16", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "source_only": true, - "source_pins": { - "access-engine/cmd/access-engine/main.go": "da24fa10e174f117e6cf44f1ba765d5b66078113198fa48fb911f5a91b5cec83", - "access-engine/internal/emission/delivery.go": "8d4bf346c175704b36daa07e7eef1b8deb3a8cbff22d43dc67ccf120fc9dbdc0", - "audit-core/deploy/audit-core.yaml": "227bd366d984c3bb5055303fd63f1eb9faabcf901b3be76ec31dda4fe23fb888", - "audit-core/deploy/networkpolicies.yaml": "c1ba896c9d29ac690ce2059759c46f0152a1e179d0322f505fab0c9e96c51463", - "railiance-platform/INTENT.md": "12c6540735af591f7a4a9fe85ff10d3cd00eec55b5b6b1ddf148abf2d3ac9a9c" - }, - "validation": { - "go": "previous passed ABI/commit/emission suites retained against identical main.go and delivery.go source hashes; no unnecessary rerun", - "helm_lint": "1chartpassed,0failed", - "independent_platform_checks": 15, - "renderer": "supplied frozen v2 tests pass including22 YAML/JSON refused values and integer boundaries in isolated chart", - "result": "passed offline renderer and counterexamples; no native Secret/credential/route/image/runtime proof" - }, - "work_item": "RPF-WP-ADHOC-2026-10-11-T01" -} diff --git a/docs/reviews/2026-10-11-key39-coding-client-custody-platform-source-assessment.json b/docs/reviews/2026-10-11-key39-coding-client-custody-platform-source-assessment.json deleted file mode 100644 index 7ff8789..0000000 --- a/docs/reviews/2026-10-11-key39-coding-client-custody-platform-source-assessment.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "schema": "platform-owner-source-assessment/v1", - "owner": "railiance-platform", - "role": "own custody/delivery source projection; maker qualification, not independent technical approval", - "task": "RPF-WP-ADHOC-2026-10-11-T03", - "completed_owner_record_before_formal_entry": { - "commit": "9460f6f", - "path": "workplans/ADHOC-2026-10-11.md", - "sha256": "c659956b2280bb2fa40d21a7ac6acca9514304aa5e7282b9ff61cd741881b544" - }, - "exact_input_manifest_sha256": "18c6cac097104f79ef15e4293c6c8b18bebd617289df8f168b1cfe7d5e69598c", - "exact_proposal_sha256": "959a924adc12283405c529ecd573cad5e16f4dd9c96c258c44cab8df014dc8c0", - "disposition": "qualified_public_source_projection_agreement; actual custody_and_delivery_admission_not_established", - "source_agreement": [ - "Current Key client fragment and Platform JWT role bind named codex client and tenant:platform; standing table source correction is exact", - "Current existing JWT role/policy bytes remain unchanged; no direct agent client-secret read grant or token substitution", - "Candidate coordinates are proposed, not observed or installed; no CCR assigned and browser CCR0040 is outside this case", - "Checked Deployment source lacks named env ref; bounded CCR/grant/catalogue source search finds no exact admission receipt; neither fact proves live absence" - ], - "checks": { - "passed": 15, - "kind": "offline public-source consistency and counterexamples only", - "script": "/tmp/platform-key39-coding-custody-stage/checks/check_public_source.py", - "sha256": "169a0d8e92088cbeacd4b3f63bc5b6c02169856d15e49f8ccad5757c949fdf01", - "implementation_or_native_proof": false - }, - "specific_source_counterexamples": [ - "CLI valid signed JWT with wrong coding subject/tenant/type/roles is not rejected by its generic claim verifier; exact coding producer check absent", - "CLI expiry ceiling above900 is not enforced by generic verifier; future exact producer must add it", - "Stale tenant:coulomb source table fails projection agreement", - "Existing shared issuer/ESO admission, browser generation CCR or historical AppRole proof cannot substitute for named client custody/delivery" - ], - "required_returns_before_credentials": [ - "Exact Platform reviewed CCR/grant/catalogue/custody version and one-use founder reader binding", - "Net privately projected active client registration and env reference with matched custody/issuer versions, dedicated reader and controller/Argo/readiness owners", - "Reviewed private producer binding signature-verified subject/tenant/type/roles/scope/finite900 maximum", - "Actual runtime isolation from agent, exclusive tmpfs/host/process/profile and founder private environment/file delivery", - "Fresh exact authenticated founder native decision, runtime/plugin/leeway/node health and required production maintenance exception", - "Value-free refusal correlation, verified known-token revoke and owner-private unknown recovery" - ], - "native_authorized": false, - "custody_reader_authorized": false, - "client_registration_authorized": false, - "transport_authorized": false, - "new_CCR_or_grant": false, - "standing_policy_widened": false, - "RPF55_residual_promoted": false, - "KEY39_native_complete": false -} diff --git a/docs/reviews/2026-10-11-key39-lifecycle-v4-platform-review.json b/docs/reviews/2026-10-11-key39-lifecycle-v4-platform-review.json deleted file mode 100644 index ac05bcc..0000000 --- a/docs/reviews/2026-10-11-key39-lifecycle-v4-platform-review.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "accepted_preparation_facts": [ - "Role/policy source and declared claims agree with CCR38: issuer/JWKS RS256, service subject and audience, tenant/roles/scope/principal_type; login-self has no native lane authority.", - "Six temporary registrations isolate audience/subject/tenant/role/scope and matching short-lived expiry; human and coding-agent tokens remain independently founder-held inputs, not newly authorized producers.", - "Finite exact-number expiry/observation checks reject booleans, strings, NaN/infinity and stale/future observations; approved session420..900s and positive remaining lifetime are bounded.", - "Source role omits expiration_leeway. Actual installed default/leeway must be freshly owner-observed; normalization -1=>0 and0=>150 is only a model, not native evidence.", - "Main-thread hard timers span HTTP connect/headers/body and normal close; protected Kubernetes patches/deletes use pre/post deadline checks and clipped timeouts; aggregate300s cleanup cannot reset. Late successful session is revoked privately or remains unconfirmed; partial login remains unknown.", - "UID/resourceVersion guards, baseline drift refusal, restoration readback and removed-client semantic refusal are appropriate source controls; malformed/transport or cleanup failures must remain unproved." - ], - "authority": "No source native acknowledgment granted for this revision; no helper approval authentication, runtime permission, source status/binding changes, secret access or foreign owner review is claimed.", - "completed_preparation_record_established_before_formal_review": true, - "decision": "changes_requested", - "findings": [ - { - "counterexample": "Synthetic response getcode200; read immediately raises Refused; close sleeps70ms. timeout10ms request returns after>60ms because alarm is disarmed before outer finally close. openbao_login_outcome_unknown is correctlytrue but elapsed bound is absent.", - "finding": "Exceptional response.close runs outside total_timeout, so a failed request can exceed its promised total elapsed cap.", - "id": "RPF-KEY39-V4-F01", - "location": "SessionTransport.request outer finally response.close", - "required_change": "Bound exceptional close/abort under the same remaining request budget or a separately admitted finite clipped cleanup budget; preserve/disarm/restore signal handling, suppress private failures and never turn unknown login into successful refusal. A blocking close after read failure/timeout must not escape finite authority/cleanup bounds.", - "severity": "blocking-source-review", - "tests_required": "Exceptional read, header/body timeout, oversized response, failing normal close followed by cleanup close; slow/raising close must remain finite, unknown/unproved and token cleanup truthfully distinguished." - } - ], - "frozen_inputs_changed": false, - "intent_basis": "Platform INTENT Secure Custody, stable shared service interfaces, operability/recovery and declared Staff/PEP-shaped Tooling gaps; CCR38 remains login-only identity, and bounded native refusal/session cleanup are Platform obligations.", - "native_prerequisites": [ - "Corrected exact source reviewed by deployment/issuer/Platform owners. Earlier v2/v3 failures and current v4 refusal retained.", - "Private founder token producer/handoff still unimplemented: no admitted human callback/client or coding-agent custody producer supplies this stdin contract. Do not reuse earlier native grants or create credential files to fill the gap.", - "Fresh owner-native installed role/config/effective-policy/current-leeway observation with exact receipt digest and age<=300s; envelope booleans alone prove nothing.", - "Authenticated scoped founder disposition for exact revision/target/window/cleanup obligation plus actual protected founder context; helper disposition/acknowledgment fields are procedural, not an authenticated approval system or runtime grant.", - "Actual native claim/permutation/expiry outcomes, refusal versus transport distinction, unexpected-acceptance revokes, baseline restoration/removed-client/temporary-object cleanup, and unknown-session owner resolution.", - "MASON reader/nonmember evidence and Secrets actual backend entitlement/adoption remain separate; this probe does not close them." - ], - "platform_source_pins": { - "credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml": "65b2cfb772623da59c351033ab4eb2ec7adfbd109778e787c7fe1b0e185a3bc0", - "openbao/auth/secrets-engine-jwt-role.json": "529dc23193be42cbe0d4440f902a64ca7c9158da158c9da13396aa0030480f40", - "openbao/policies/secrets-engine-login-self.hcl": "d1a2969dd2949c822972e9b32243650567ac123a669f3fc609e9bfea977bb4eb" - }, - "reviewed_entries": { - "sso-mfa/k8s/keycape/openbao-negative-session.py": "b4687c2d07819c43b45d0cddcad0488b62eb49175b2a06a43f7806a0838d3a4d", - "sso-mfa/k8s/keycape/test_openbao_negative_session.py": "ea2b5f7466c1da016f528093daf168c274238d4803eb5424d7353c66adbecf43" - }, - "reviewed_manifest": "/tmp/keycape-jwt-lifecycle-v4-stage/manifest.json", - "reviewed_manifest_sha256": "d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8", - "reviewed_runner_sha256": "bb9610babf44a959342bac700bb5712e97f400b44c4d0659d21df4426e616e19", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "schema": "owner-source-review/v1", - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-lifecycle-v4-stage/net-kingdom/sso-mfa/k8s/keycape -p test_openbao_negative_session.py -q", - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /home/worsch/key-cape/scripts -p test_openbao_negative_probe.py -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-key39-lifecycle-owner-review-stage/tests/test_key39_platform_independent.py" - ], - "independent_attachment": "/tmp/platform-key39-lifecycle-owner-review-stage/tests/test_key39_platform_independent.py", - "independent_checks": 5, - "initial_test_harness_outcome": "Independent test collection had a literal spacing syntax typo; corrected only that local test, then5passed. Reviewed lifecycle input unchanged.", - "lifecycle_tests": 41, - "result": "all passed; one deliberately asserts reproduced v4 elapsed-bound defect; no native/session values involved", - "total": 63, - "unchanged_runner_tests": 17 - }, - "work_item": "RPF-WP-0035-T13" -} diff --git a/docs/reviews/2026-10-11-key39-lifecycle-v5-platform-review.json b/docs/reviews/2026-10-11-key39-lifecycle-v5-platform-review.json deleted file mode 100644 index a2c5c7c..0000000 --- a/docs/reviews/2026-10-11-key39-lifecycle-v5-platform-review.json +++ /dev/null @@ -1,52 +0,0 @@ -{ - "completed_same_owner_preparation_record_precedes_review": true, - "correction_verified": "RPF-KEY39-V4-F01: all exceptional response close/abort paths now use one fixed20ms cap clipped to original time when remaining and to nonresettable aggregate300s cleanup. No repeated unbounded close. Aggregate expired produces zero close calls. Abort timeout/exception records unconfirmed; original unknown login remains sticky and cannot become successful refusal. Signal state is restored.", - "decision": "approved", - "frozen_source_unchanged": true, - "intent_basis": "Platform INTENT Secure Custody, Stable Interfaces and Operable/Observable recovery; CCR38 login-only identity requires truthful finite native refusal/session cleanup but grants no protected lane execution.", - "not_approved": [ - "founder disposition, authenticated runtime approval, environment/grant widening or authority transfer", - "private token producer/client/callback/coding-agent custody admission", - "actual installed role/JWKS/policy/leeway observation", - "native claim/expiry/refusal/revoke/readback/cleanup acceptance", - "MASON reader/nonmember matrices, Secrets execution entitlement or full adoption", - "KEY39/RPF35T02/SecretsT06 completion" - ], - "other_source_checks": [ - "Finite typed expiry/observation/session bounds, per-request main-thread HTTP/Kubernetes total timers, business-window post-response checks, delayed-success private revoke and partial-response unknown classification retained.", - "Current CCR38 source claims/policy are unchanged: exact role/audience/service subject/tenant/roles/scope/principal, no default policy,5m8use loginself with client-custody denies. No native grant from earlier sessions is inherited.", - "Role source does not set expiration_leeway: model0=>150 and-1=>0 retained; fresh installed role/config/effective-policy/current-leeway owner observation remains mandatory before native proof.", - "Temporary six registration permutations and real removed-client/restoration/UID/resourceVersion guards retain their separate issuer/deployment/Platform scopes. Registry cleanup cannot clear unknown native login or failed revoke.", - "Private founder human/coding-agent stdin producer/client/callback/live custody handoff is still unimplemented; source cannot authorize fetching/reusing credentials or invent that path." - ], - "prior_changes_requested_retained": true, - "prior_review": "docs/reviews/2026-10-11-key39-lifecycle-v4-platform-review.json", - "prior_review_sha256": "c0f9da4850d7b6ce8a41401bfb7cddc39a60cd7bc8767de89d76b5f93198a2d9", - "required_native_packet": "Fresh exact installed role/config/effective-policy/leeway receipt<=300s; independently admitted founder-private human and coding-agent producers; exact source/runtime/target/window pins and authenticated scoped founder go expressly including finite abort/revoke/restoration cleanup. Helper disposition/owner acknowledgment JSON fields alone authenticate none of this. Native receipts must preserve unexpected acceptance, unknown login, failed revoke and partial cleanup until actual owner resolution.", - "review_scope": "exact KEY39v5 lifecycle source acknowledgment only; no privileged runtime grant or native evidence", - "reviewed_entries": { - "sso-mfa/k8s/keycape/openbao-negative-session.py": "3bcceebc845b17492040c76223d77088257c547a2b890198e7b5c2b04a0cd360", - "sso-mfa/k8s/keycape/test_openbao_negative_session.py": "a428a4a5ea60bd6c4ad10481e133227ad696a4a08653bf39b554b47431fb9f8c" - }, - "reviewed_manifest": "/tmp/keycape-jwt-lifecycle-v5-stage/manifest.json", - "reviewed_manifest_sha256": "d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "unchanged_runner_sha256": "bb9610babf44a959342bac700bb5712e97f400b44c4d0659d21df4426e616e19", - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-lifecycle-v5-stage/net-kingdom/sso-mfa/k8s/keycape -p test_openbao_negative_session.py -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-key39-lifecycle-owner-review-stage/revision-v5/tests/test_key39_platform_v5_independent.py" - ], - "credential_access": false, - "independent_tests": 5, - "native_actions": false, - "owner_checkout_writes": false, - "result": "passing source/synthetic checks; actual corrected elapsed counterexample independently tested", - "supplied_lifecycle_tests": 46, - "total_verified_checks": 68, - "unchanged_runner_tests_previously_verified": 17 - }, - "work_item": "RPF-WP-0035-T13" -} diff --git a/docs/reviews/2026-10-11-key39-node-route-platform-review.json b/docs/reviews/2026-10-11-key39-node-route-platform-review.json deleted file mode 100644 index b25f506..0000000 --- a/docs/reviews/2026-10-11-key39-node-route-platform-review.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "completed_same_owner_refinement_precedes_formal_review": true, - "decision": "approved", - "input_unchanged": true, - "intent_basis": "Platform INTENT stable shared interfaces, secure custody and observable recovery: exact provider route prerequisites and fail-closed guarded native preparation without publichealth becoming authority.", - "mandatory_native_prerequisites": [ - "Publichealth clusterhash is unauthenticated: independently verify founder/node/Service/cluster/provider route identity, current admitted route/estate and exact source/runtime pins; no arbitrary workstation diagnostic proves node placement.", - "Actual installed OpenBao JWT plugin/version, role/config/effective-policy source equality and raw/effective expiration_leeway, clock_skew_leeway and not_before_leeway interaction. Helm2.5.4/tag and raw0=>150 model are not native facts; semantic expiry proof remains mandatory.", - "Establish issuer actual readiness_state and Argo ownership through authoritative source/owner receipt. No keycape declaration/current deployment adoption established in inspected sources; absence is not nonproduction. Unmapped production-tier rules apply. If adopted/production-approved, transient directCAS needs explicit reviewed one-use maintenanceexception, close-down entry and reconciliation before nativego.", - "Fresh authenticated scoped founder decision covers exact route, source, issuer mutation/window and finite abort/revoke/restoration/recovery obligations. Helper labels/sourceacknowledgments cannot authenticate approval or waive gates.", - "Admitted private founder human/coding-agent token producer/client/callback/custody handoff remains unimplemented. No credential request/read/reuse authorized.", - "Native correct role/claim permutation/expiry/refusal/revoke, baseline restoration/client removal/temporaryobject absence and all unknown outcome resolution. MASON and Secrets native/full-adoption obligations stay separate." - ], - "not_approved": [ - "route setup, native preflight/login/Secret access or production mutation", - "privileged authority/helperapproval authentication or default flip", - "current provider identity/plugin/leeway/readiness/Argo facts", - "private producer or whole KEY39/RPF35T02/SecretsT06 native acceptance" - ], - "optional_consumer_consistency_observation": { - "manifest_sha256": "32a419752e4a3ed7cf8f906fc40ebbaa516b9a4d6134be2df29243ce97213e40", - "notes": [ - "Explicit execute-ccr descriptor is shape/binding only; unimplemented compiler/adapter remains gate.", - "Consumer hashes use sha256: prefix; Journal Binding uses bare64hex. The future owner mediator must define and validate exact representation conversion; do not silently join mismatched identity/digests.", - "Execution request propagation does not satisfy caller/PDP/consume/plan/admission/body scope by itself." - ], - "scope": "Read design/schema for Platform interoperability only; no Secrets owner review" - }, - "ordering_caveat": "Documentation says execute checks health before protected token input, but main reads private stdin before execute invokes health. That claim is excluded from this approval. Before producer handoff/native admission, correct packet wording or implement a pre-input gate and review its exact source. Actual fail-before-native/Secret effects is verified.", - "platform_source_pins": { - "helm/openbao-ui-overlay-k8s.yaml": "7b345359906bf1ecc4cb8e61de7a9f1a121068a66bd919e606190a9217a82daf", - "helm/openbao-ui-overlay/nginx.conf": "464f7f5cecc69ea343c1e2acc5932e832b2a9c0e6a5099113634a12abc80ccb3", - "helm/openbao-values.yaml": "a10239851277889dd69dacb8636109dddd095551d85b46ca4ed2c141944e8d1e" - }, - "review_scope": "public-health fail-before-native gate and bounded node-route proposal source only; no native route/go or pre-private-input claim", - "reviewed_entries": { - "sso-mfa/k8s/keycape/openbao-negative-node-route.md": "b1184d349ad8166804253edeb7932b82d1bd30eb5fe46fcc5bbda8f88593eb9d", - "sso-mfa/k8s/keycape/openbao-negative-session.py": "11df42c873ee7ab4cf4a42fa2326ada4661703632b07745220e75a61f7c7fadf", - "sso-mfa/k8s/keycape/test_openbao_negative_session.py": "01e23f2b4fc77824a161a57acb3a52d19aad846b3b6c0fd3abd81afa1dc2985c", - "sso-mfa/k8s/keycape/test_openbao_node_route.py": "00e370b0c3892e16939b7e34ee070df7fe6183b8037741a33b7a6f2814847a9d" - }, - "reviewed_manifest": "/tmp/keycape-jwt-node-route-stage/manifest.json", - "reviewed_manifest_sha256": "23a972abc5175e121e594726d2b7f7a28c62109253f58f12623eaf2e5564a035", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-node-route-stage/net-kingdom/sso-mfa/k8s/keycape -p test_openbao*.py -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-key39-node-route-review-stage/tests/test_platform_route.py" - ], - "independent_checks": 6, - "result": "passed synthetic/offline tests; native false", - "supplied_lifecycle_and_route": 54, - "total_fresh_checks": 60 - }, - "verified": [ - "SourceService openbao/openbao-ui-gateway8080 routes to nginx /v1 proxy for OpenBao8200; proposed dedicated node-loopback Service port-forward avoids node PodDNS assumption and shared workstation tunnel alteration. No portforward/bridge/native route executed.", - "PublicGET only fixed /v1/sys/health, no credential header; strict initialized/unsealed/active booleans and bounded cluster-id hash, no raw public id retained.", - "Current-process-only route-preflight performs no Kubernetes/Secret/private-stdin access, retains native_accepted=false.", - "Execute/recovery pin health before native reads; second health check after dry-runs before first create. Missing/drift/unavailable/redirect/slow response prevents protected effects.", - "Existing no-proxy/no-redirect/byte/mainthread-total-timer/finite-exception-abort bounds apply; failed cleanup/unknown login is not cleared by healthy route." - ], - "work_item": "RPF-WP-0035-T13" -} diff --git a/docs/reviews/2026-10-11-key39-node-route-v7-platform-review.json b/docs/reviews/2026-10-11-key39-node-route-v7-platform-review.json deleted file mode 100644 index b7ae960..0000000 --- a/docs/reviews/2026-10-11-key39-node-route-v7-platform-review.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "completed_same_owner_refinement_precedes_formal_review": true, - "decision": "changes_requested", - "findings": [ - { - "counterexample": "Actual main after mocked source validation/public-health with a synthetic20ms original window and partial JSON pipe without EOF remained blocked after100ms. sys.stdin.read has no byte cap or elapsed/EOF bound. Child was terminated by test parent; no credential/native operation occurred.", - "location": "sso-mfa/k8s/keycape/openbao-negative-session.py:755", - "required_change": "Read private JSON through a strict byte cap and EOF requirement within the original unrenewed monotonic and wall execution window. Expiry/oversize/no-EOF must fail with fixed value-free receipt before protected/native effects. Independently review new immutable source; no custody completion based on v7.", - "severity": "blocking_before_private_producer_admission" - } - ], - "input_unchanged": true, - "intent_basis": "Platform INTENT stable shared interfaces, secure custody and observable recovery: exact provider route prerequisites and fail-closed guarded native preparation without publichealth becoming authority.", - "mandatory_native_prerequisites": [ - "Publichealth clusterhash is unauthenticated: independently verify founder/node/Service/cluster/provider route identity, current admitted route/estate and exact source/runtime pins; no arbitrary workstation diagnostic proves node placement.", - "Actual installed OpenBao JWT plugin/version, role/config/effective-policy source equality and raw/effective expiration_leeway, clock_skew_leeway and not_before_leeway interaction. Helm2.5.4/tag and raw0=>150 model are not native facts; semantic expiry proof remains mandatory.", - "Establish issuer actual readiness_state and Argo ownership through authoritative source/owner receipt. No keycape declaration/current deployment adoption established in inspected sources; absence is not nonproduction. Unmapped production-tier rules apply. If adopted/production-approved, transient directCAS needs explicit reviewed one-use maintenanceexception, close-down entry and reconciliation before nativego.", - "Fresh authenticated scoped founder decision covers exact route, source, issuer mutation/window and finite abort/revoke/restoration/recovery obligations. Helper labels/sourceacknowledgments cannot authenticate approval or waive gates.", - "Admitted private founder human/coding-agent token producer/client/callback/custody handoff remains unimplemented. No credential request/read/reuse authorized.", - "Native correct role/claim permutation/expiry/refusal/revoke, baseline restoration/client removal/temporaryobject absence and all unknown outcome resolution. MASON and Secrets native/full-adoption obligations stay separate." - ], - "not_approved": [ - "route setup, native preflight/login/Secret access or production mutation", - "privileged authority/helperapproval authentication or default flip", - "current provider identity/plugin/leeway/readiness/Argo facts", - "private producer or whole KEY39/RPF35T02/SecretsT06 native acceptance" - ], - "platform_source_pins": { - "helm/openbao-ui-overlay-k8s.yaml": "7b345359906bf1ecc4cb8e61de7a9f1a121068a66bd919e606190a9217a82daf", - "helm/openbao-ui-overlay/nginx.conf": "464f7f5cecc69ea343c1e2acc5932e832b2a9c0e6a5099113634a12abc80ccb3", - "helm/openbao-values.yaml": "a10239851277889dd69dacb8636109dddd095551d85b46ca4ed2c141944e8d1e" - }, - "previous_review_retained_sha256": "159c784cce600ef5ce21db98244782c73a997c209082c064a1b0ce93625c4715", - "review_scope": "v7 source/public-health and fail-before-native gate only; private input custody incomplete", - "reviewed_entries": { - "sso-mfa/k8s/keycape/openbao-negative-node-route.md": "ff3604380a616dfaa86cd9aaa1f131136ee4fc751397a1ebda3a8d737e3c5346", - "sso-mfa/k8s/keycape/openbao-negative-session.py": "4c90edb541b5ea2ff131c3e674c80197c358c2f81140187028e57de0ac8dbf81", - "sso-mfa/k8s/keycape/test_openbao_negative_session.py": "01e23f2b4fc77824a161a57acb3a52d19aad846b3b6c0fd3abd81afa1dc2985c", - "sso-mfa/k8s/keycape/test_openbao_node_route.py": "02a715dcc028d0cacbf3b0ffdf705758438717f064e290b283f8e773540dd2d7" - }, - "reviewed_manifest": "/tmp/keycape-jwt-node-route-v7-stage/manifest.json", - "reviewed_manifest_sha256": "90aabec336008d7d51e37c04b5ff96ba8ed6630821cca1a4c333d803367247f7", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-node-route-v7-stage/net-kingdom/sso-mfa/k8s/keycape -p test_openbao*.py -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-key39-node-route-v7-review-stage/tests/test_platform_route_v7.py" - ], - "independent_checks": 9, - "result": "passed offline checks including expected v7 unbounded-input counterexample", - "supplied_lifecycle_and_route": 58, - "total_fresh_checks": 67 - }, - "verified": [ - "SourceService openbao/openbao-ui-gateway8080 routes to nginx /v1 proxy for OpenBao8200; proposed dedicated node-loopback Service port-forward avoids node PodDNS assumption and shared workstation tunnel alteration. No portforward/bridge/native route executed.", - "Actual main source validation and public-health pin precede private stdin; source failure never reads private input or begins health/native work.", - "Preinput start establishes original wall/monotonic window; execute window_started=True does not renew it and expired window refuses before runner.", - "Existing public-health/no-auth and fail-before-native gates remain qualified source agreement only." - ], - "work_item": "RPF-WP-0035-T13" -} diff --git a/docs/reviews/2026-10-11-key39-node-route-v8-platform-review.json b/docs/reviews/2026-10-11-key39-node-route-v8-platform-review.json deleted file mode 100644 index 43165d6..0000000 --- a/docs/reviews/2026-10-11-key39-node-route-v8-platform-review.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "completed_same_owner_refinement_precedes_formal_review": true, - "decision": "approved", - "input_unchanged": true, - "intent_basis": "Platform INTENT stable shared interfaces, secure custody and observable recovery: exact provider route prerequisites and fail-closed guarded native preparation without publichealth becoming authority.", - "mandatory_native_prerequisites": [ - "Publichealth clusterhash is unauthenticated: independently verify founder/node/Service/cluster/provider route identity, current admitted route/estate and exact source/runtime pins; no arbitrary workstation diagnostic proves node placement.", - "Actual installed OpenBao JWT plugin/version, role/config/effective-policy source equality and raw/effective expiration_leeway, clock_skew_leeway and not_before_leeway interaction. Helm2.5.4/tag and raw0=>150 model are not native facts; semantic expiry proof remains mandatory.", - "Establish issuer actual readiness_state and Argo ownership through authoritative source/owner receipt. No keycape declaration/current deployment adoption established in inspected sources; absence is not nonproduction. Unmapped production-tier rules apply. If adopted/production-approved, transient directCAS needs explicit reviewed one-use maintenanceexception, close-down entry and reconciliation before nativego.", - "Fresh authenticated scoped founder decision covers exact route, source, issuer mutation/window and finite abort/revoke/restoration/recovery obligations. Helper labels/sourceacknowledgments cannot authenticate approval or waive gates.", - "Admitted private founder human/coding-agent token producer/client/callback/custody handoff remains unimplemented. No credential request/read/reuse authorized.", - "Native correct role/claim permutation/expiry/refusal/revoke, baseline restoration/client removal/temporaryobject absence and all unknown outcome resolution. MASON and Secrets native/full-adoption obligations stay separate.", - "Before private token production/transfer, implement and independently review authenticated/validated child-ready acknowledgment bound to exact pipe session and original window; no current handshake exists and eager piping is prohibited. Bounded child read does not admit producer custody." - ], - "not_approved": [ - "route setup, native preflight/login/Secret access or production mutation", - "privileged authority/helperapproval authentication or default flip", - "current provider identity/plugin/leeway/readiness/Argo facts", - "private producer or whole KEY39/RPF35T02/SecretsT06 native acceptance", - "Parent-ready handshake, eager producer piping, launcher/session/window custody admission or protected token production." - ], - "platform_source_pins": { - "helm/openbao-ui-overlay-k8s.yaml": "7b345359906bf1ecc4cb8e61de7a9f1a121068a66bd919e606190a9217a82daf", - "helm/openbao-ui-overlay/nginx.conf": "464f7f5cecc69ea343c1e2acc5932e832b2a9c0e6a5099113634a12abc80ccb3", - "helm/openbao-values.yaml": "a10239851277889dd69dacb8636109dddd095551d85b46ca4ed2c141944e8d1e" - }, - "previous_review_retained_sha256": "a319e5acb5af2aac242cf1f217af5abc3305ace8df80e646c995dff5a11ac039", - "review_scope": "child-side bounded private JSON receive and retained source/public-health fail-before-native gates only; no producer/custody admission", - "reviewed_entries": { - "sso-mfa/k8s/keycape/openbao-negative-node-route.md": "a04c94512efec7a524b15a3eec61d86de5a41df872e59a3114093063a11e75a0", - "sso-mfa/k8s/keycape/openbao-negative-session.py": "da4e0ab767e0c3bebb48bcf5ab578b50845d9fa7211a6771661a0e1baa4314ab", - "sso-mfa/k8s/keycape/test_openbao_negative_session.py": "01e23f2b4fc77824a161a57acb3a52d19aad846b3b6c0fd3abd81afa1dc2985c", - "sso-mfa/k8s/keycape/test_openbao_node_route.py": "9f3d8d4297c5ef68f6c6bf7ca21e981e37d7eceb836a60bc254d33b0f5129047", - "sso-mfa/k8s/keycape/test_openbao_private_input.py": "a59c2cd4ec895c1718d52fd12f64666f1be45ae423fde476755b6d77a89280c2" - }, - "reviewed_manifest": "/tmp/keycape-jwt-node-route-v8-stage/manifest.json", - "reviewed_manifest_sha256": "316fd6005cc4cb7fe14ea69a5fd408aed13e6122afef60afd13d97952b27cf4a", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-node-route-v8-stage/net-kingdom/sso-mfa/k8s/keycape -p test_openbao*.py -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-key39-node-route-v8-review-stage/tests/test_platform_route_v8.py" - ], - "independent_checks": 12, - "result": "passed synthetic/offline checks; native/custody admission false", - "supplied_lifecycle_route_private_input": 65, - "total_fresh_checks": 77 - }, - "verified": [ - "SourceService openbao/openbao-ui-gateway8080 routes to nginx /v1 proxy for OpenBao8200; proposed dedicated node-loopback Service port-forward avoids node PodDNS assumption and shared workstation tunnel alteration. No portforward/bridge/native route executed.", - "Actual main source validation and public-health pin precede private stdin; source failure never reads private input or begins health/native work.", - "Preinput start establishes original wall/monotonic window; execute window_started=True does not renew it and expired window refuses before runner.", - "Existing public-health/no-auth and fail-before-native gates remain qualified source agreement only.", - "Private binary JSON input is limited to64KiB, each token16KiB UTF8, exact two-field object and complete EOF; one hard15s maximum timer is clipped to the original remaining wall/monotonic window, with postreceive/decode deadline checks. Stalled/noEOF/oversize/malformed/expired input refuses before protected effects; alarm timer/handler restored.", - "V7 actual unbounded-input finding corrected; standard buffered pipe with completeJSON/noEOF now interrupts within original window." - ], - "work_item": "RPF-WP-0035-T13" -} diff --git a/docs/reviews/2026-10-11-key39-ready-pair-v9-v2-platform-review.json b/docs/reviews/2026-10-11-key39-ready-pair-v9-v2-platform-review.json deleted file mode 100644 index 9fd863a..0000000 --- a/docs/reviews/2026-10-11-key39-ready-pair-v9-v2-platform-review.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "completed_same_owner_refinement_precedes_formal_review": true, - "decision": "changes_requested", - "findings": [ - { - "counterexample": "handoff.deliver returns private_input_delivery_unknown=true, private_input_delivery_confirmed=false after writing begins. launch_and_deliver copies delivery then returns early, leaving consumer_outcome_unknown=false; finally terminates local SSH. Mocked exactcounterexample reproduces. Complete remote payload can have arrived before adapter timer interrupted confirmation/EOF, and local teardown provides no authenticated remote noeffects proof.", - "id": "RPF-KEY39-PAIR-V2-F01", - "location": "key-cape/scripts/openbao_negative_private_launcher.py:127", - "required_change": "Before any early return propagate private_input_delivery_unknown=true into sticky consumer_outcome_unknown=true and consumer_process_completed=false. Failed/partial delivery, transport teardown or unconfirmed remote child must retain unknown. Add exact regression; no retry/native success or remote recovery claim from local terminate.", - "severity": "blocking_before_launcher_admission" - } - ], - "input_unchanged": true, - "intent_basis": "Platform INTENT stable shared interfaces, secure custody and observable recovery: exact provider route prerequisites and fail-closed guarded native preparation without publichealth becoming authority.", - "mandatory_native_prerequisites": [ - "Publichealth clusterhash is unauthenticated: independently verify founder/node/Service/cluster/provider route identity, current admitted route/estate and exact source/runtime pins; no arbitrary workstation diagnostic proves node placement.", - "Actual installed OpenBao JWT plugin/version, role/config/effective-policy source equality and raw/effective expiration_leeway, clock_skew_leeway and not_before_leeway interaction. Helm2.5.4/tag and raw0=>150 model are not native facts; semantic expiry proof remains mandatory.", - "Establish issuer actual readiness_state and Argo ownership through authoritative source/owner receipt. No keycape declaration/current deployment adoption established in inspected sources; absence is not nonproduction. Unmapped production-tier rules apply. If adopted/production-approved, transient directCAS needs explicit reviewed one-use maintenanceexception, close-down entry and reconciliation before nativego.", - "Fresh authenticated scoped founder decision covers exact route, source, issuer mutation/window and finite abort/revoke/restoration/recovery obligations. Helper labels/sourceacknowledgments cannot authenticate approval or waive gates.", - "Admitted private founder human/coding-agent token producer/client/callback/custody handoff remains unimplemented. No credential request/read/reuse authorized.", - "Native correct role/claim permutation/expiry/refusal/revoke, baseline restoration/client removal/temporaryobject absence and all unknown outcome resolution. MASON and Secrets native/full-adoption obligations stay separate.", - "Child-ready schema now implemented as source synchronization only. Before use require independently admitted founder SSH host/session/channel, exact source/runtime/envelope/target/window/file custody and source-compatible launcher; marker/owner labels cannot authenticate authority. Warden quiet-output lane is incompatible and must not be used. No current transport/privatecoding-agent custody grant established." - ], - "not_approved": [ - "route setup, native preflight/login/Secret access or production mutation", - "privileged authority/helperapproval authentication or default flip", - "current provider identity/plugin/leeway/readiness/Argo facts", - "private producer or whole KEY39/RPF35T02/SecretsT06 native acceptance", - "Parent-ready handshake, eager producer piping, launcher/session/window custody admission or protected token production.", - "V2 launcher truthful consumer-outcome acceptance; source correction and fresh review required.", - "Founderdirect SSH use/host approval, tmpfscredential file allowance, coding-agent custody or native session admission." - ], - "platform_source_pins": { - "helm/openbao-ui-overlay-k8s.yaml": "7b345359906bf1ecc4cb8e61de7a9f1a121068a66bd919e606190a9217a82daf", - "helm/openbao-ui-overlay/nginx.conf": "464f7f5cecc69ea343c1e2acc5932e832b2a9c0e6a5099113634a12abc80ccb3", - "helm/openbao-values.yaml": "a10239851277889dd69dacb8636109dddd095551d85b46ca4ed2c141944e8d1e" - }, - "previous_review_retained_sha256": "7eb0f26cbbcb7723412a417a96311b36efb472860b7a4bdfa5d91c85cc38ad69", - "review_scope": "paired ready-child/control wrapper and founder-held directSSH launcher source only; no transport/custody admission", - "reviewed_inputs": [ - { - "entries": { - "net-kingdom/sso-mfa/k8s/keycape/openbao-negative-node-route.md": "cad3e3dc481d45295cc014e48285e81614911dfb4a10523883f3044f6085f363", - "net-kingdom/sso-mfa/k8s/keycape/openbao-negative-node-wrapper.py": "76ac013d1a676a0dfa58aea1ce87b0c3e72483ccd21f586a22c5475e9132f6c1", - "net-kingdom/sso-mfa/k8s/keycape/openbao-negative-session.py": "24b7b803e4eb7e6e7c0700a069dad0ec315ee7eb5538a88b9e56147f8fab5cdd", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_negative_session.py": "01e23f2b4fc77824a161a57acb3a52d19aad846b3b6c0fd3abd81afa1dc2985c", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_node_route.py": "1c57053be6e8472174e4034fdfbf34b3e1729f5e239b3a9af89d9e59a08d6cb6", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_private_input.py": "84277edb5bd7858885173dea802b6bdc4f0f0c7785d6e200ee9a98d8553891a1", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_private_ready.py": "8a7a033b1c509ba21397d1d74580c44f24592045b2b9de50fe87638607224277" - }, - "manifest": "/tmp/keycape-jwt-ready-v9-stage/manifest.json", - "manifest_sha256": "84542ddcff00932fc7ce22dcb2626be8942a3c006c5098a62fdd6b2478d0cd14" - }, - { - "entries": { - "key-cape/docs/openbao-negative-token-producer-decision.json": "33a00b32efc10e4751025baff1fd9b0c3f7daf34b8b1ef5d78dabf0817cb449f", - "key-cape/docs/openbao-negative-token-producer-decision.md": "0f33f026a380bf771e6cb2af3dd6ab9e3bb066c268982b36206006a65a95b783", - "key-cape/scripts/openbao_negative_private_handoff.py": "faf63a3e9504dcbb46847a114275f83d508521e095410a47f92f02b86ba82b1b", - "key-cape/scripts/openbao_negative_private_launcher.py": "7e396c80386ced69facdb019cf215b74a5735140e3584b08e13ab29c770e73d8", - "key-cape/scripts/test_openbao_negative_private_handoff.py": "e4eb600718e7d93b0e801ac3ab1427af9895a6e5d877c1134369e049e0849797", - "key-cape/scripts/test_openbao_negative_private_launcher.py": "90094c89cac8bbc0bf694aaf91f369e0891779921940bab90f8dfd3a7f2212be", - "key-cape/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md": "6d279cfc3fd1d9edf888bb9bedeaf3ecbd3ad53a4000f6793bd96dc8bbe893a3" - }, - "manifest": "/tmp/keycape-jwt-producer-handoff-v2-stage/manifest.json", - "manifest_sha256": "55a8e794af983ca2202e7a0054d0415a120d2c95018fb03fdb5f28313a109d58" - } - ], - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-ready-v9-stage/net-kingdom/sso-mfa/k8s/keycape -p test_openbao*.py -q", - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-producer-handoff-v2-stage/key-cape/scripts -p test_openbao*.py -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-key39-ready-pair-review-stage/tests/test_platform_ready_pair.py" - ], - "independent_checks": 10, - "result": "passed synthetic/offline checks including expected V2 truthfulunknown counterexample", - "supplied_key_checks": 31, - "supplied_net_checks": 74, - "total": 115 - }, - "verified": [ - "Child verifies source/window/publichealth before boundedready marker and private input. Nonce/executor/envelope/health/window bindings and dedicated nonstdio control FIFO refuse mismatches; no marker authority.", - "Launcher exactsource/envelope/window checks precede SSH spawn, private file load occurs only after matched captured childcontrol marker; wrong/missing marker preserves files and delivers zero bytes.", - "Fixed railiance01 SSHtarget, strict hostkey/batch/noagentforwarding/noportforwarding options, shellquoted constrained paths and envallowlist; malformed injection fields refuse before spawn/private reads. This does not admit transport or override SSH config/runtimecustody gates.", - "Bounded source/private input/marker parsing and timers; stdio/data/control aliases refused; wrapper captures and never forwards childstdout/stderr. Unexpected output refuses with uncertain actual native outcomes remaining owner obligations." - ], - "work_item": "RPF-WP-0035-T13" -} diff --git a/docs/reviews/2026-10-11-key39-ready-pair-v9-v4-platform-review.json b/docs/reviews/2026-10-11-key39-ready-pair-v9-v4-platform-review.json deleted file mode 100644 index f14d66e..0000000 --- a/docs/reviews/2026-10-11-key39-ready-pair-v9-v4-platform-review.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "completed_same_owner_refinement_precedes_formal_review": true, - "corrections": [ - "RPF-KEY39-PAIR-V2-F01: uncertain private delivery sets sticky consumer_outcome_unknown=true and consumer_process_completed=false before earlyreturn. Localteardown does not establish remote no-effects.", - "Retained NetV3 FIFO/unguarded contract acquisition and preparation-reset finding corrected: actual main starts original5s deadline before regular/nofollow/bytecapped stable contract read, launch/delivery clips remaining original budget. Source import/bootstrap runtime isolation remains external admission prerequisite." - ], - "decision": "approved", - "input_unchanged": true, - "intent_basis": "Platform INTENT stable shared interfaces, secure custody and observable recovery: exact provider route prerequisites and fail-closed guarded native preparation without publichealth becoming authority.", - "mandatory_native_prerequisites": [ - "Publichealth clusterhash is unauthenticated: independently verify founder/node/Service/cluster/provider route identity, current admitted route/estate and exact source/runtime pins; no arbitrary workstation diagnostic proves node placement.", - "Actual installed OpenBao JWT plugin/version, role/config/effective-policy source equality and raw/effective expiration_leeway, clock_skew_leeway and not_before_leeway interaction. Helm2.5.4/tag and raw0=>150 model are not native facts; semantic expiry proof remains mandatory.", - "Establish issuer actual readiness_state and Argo ownership through authoritative source/owner receipt. No keycape declaration/current deployment adoption established in inspected sources; absence is not nonproduction. Unmapped production-tier rules apply. If adopted/production-approved, transient directCAS needs explicit reviewed one-use maintenanceexception, close-down entry and reconciliation before nativego.", - "Fresh authenticated scoped founder decision covers exact route, source, issuer mutation/window and finite abort/revoke/restoration/recovery obligations. Helper labels/sourceacknowledgments cannot authenticate approval or waive gates.", - "Admitted private founder human/coding-agent token producer/client/callback/custody handoff remains unimplemented. No credential request/read/reuse authorized.", - "Native correct role/claim permutation/expiry/refusal/revoke, baseline restoration/client removal/temporaryobject absence and all unknown outcome resolution. MASON and Secrets native/full-adoption obligations stay separate.", - "Child-ready schema now implemented as source synchronization only. Before use require independently admitted founder SSH host/session/channel, exact source/runtime/envelope/target/window/file custody and source-compatible launcher; marker/owner labels cannot authenticate authority. Warden quiet-output lane is incompatible and must not be used. No current transport/privatecoding-agent custody grant established." - ], - "not_approved": [ - "liveSSH transport/profile/host grant or quietwarden reuse", - "credential/coding-agent custody/client/producer admission, tmpfs privatefile allowance or cleanup proof", - "authenticated founder authority or helpermarker/ownerlabel approvalauthentication", - "actual native route/OpenBao plugin/leeway/issuer readiness/Argo identity", - "native login/claims/refusal/revoke/restoration/readback/unknown resolution or full adoption/default migration" - ], - "platform_source_pins": { - "helm/openbao-ui-overlay-k8s.yaml": "7b345359906bf1ecc4cb8e61de7a9f1a121068a66bd919e606190a9217a82daf", - "helm/openbao-ui-overlay/nginx.conf": "464f7f5cecc69ea343c1e2acc5932e832b2a9c0e6a5099113634a12abc80ccb3", - "helm/openbao-values.yaml": "a10239851277889dd69dacb8636109dddd095551d85b46ca4ed2c141944e8d1e" - }, - "previous_review_retained_sha256": "8353eb8aae11be8ce874887d6a5ca10bf37c6fba73c3f85f0644a7546dd7d33f", - "review_scope": "paired child-ready/control wrapper and bounded founder-held launcher source behavior only; no transport/coding-agent custody/native admission", - "reviewed_inputs": [ - { - "entries": { - "net-kingdom/sso-mfa/k8s/keycape/openbao-negative-node-route.md": "cad3e3dc481d45295cc014e48285e81614911dfb4a10523883f3044f6085f363", - "net-kingdom/sso-mfa/k8s/keycape/openbao-negative-node-wrapper.py": "76ac013d1a676a0dfa58aea1ce87b0c3e72483ccd21f586a22c5475e9132f6c1", - "net-kingdom/sso-mfa/k8s/keycape/openbao-negative-session.py": "24b7b803e4eb7e6e7c0700a069dad0ec315ee7eb5538a88b9e56147f8fab5cdd", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_negative_session.py": "01e23f2b4fc77824a161a57acb3a52d19aad846b3b6c0fd3abd81afa1dc2985c", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_node_route.py": "1c57053be6e8472174e4034fdfbf34b3e1729f5e239b3a9af89d9e59a08d6cb6", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_private_input.py": "84277edb5bd7858885173dea802b6bdc4f0f0c7785d6e200ee9a98d8553891a1", - "net-kingdom/sso-mfa/k8s/keycape/test_openbao_private_ready.py": "8a7a033b1c509ba21397d1d74580c44f24592045b2b9de50fe87638607224277" - }, - "manifest": "/tmp/keycape-jwt-ready-v9-stage/manifest.json", - "manifest_sha256": "84542ddcff00932fc7ce22dcb2626be8942a3c006c5098a62fdd6b2478d0cd14" - }, - { - "entries": { - "key-cape/docs/openbao-negative-token-producer-decision.json": "7b3e4a53ac4af33c780f16c3f1aa9cde0154040ad2747a8d361dd79bbb7f435c", - "key-cape/docs/openbao-negative-token-producer-decision.md": "38e4c9660ca57a1fb6e5ff04f214af61dd1a58abf564a5ed22216d909e27fc73", - "key-cape/scripts/openbao_negative_private_handoff.py": "faf63a3e9504dcbb46847a114275f83d508521e095410a47f92f02b86ba82b1b", - "key-cape/scripts/openbao_negative_private_launcher.py": "46e57504b3185cc13f1656901a0ca02cc3887527cb09566e270f57b76099ab9c", - "key-cape/scripts/test_openbao_negative_private_handoff.py": "e4eb600718e7d93b0e801ac3ab1427af9895a6e5d877c1134369e049e0849797", - "key-cape/scripts/test_openbao_negative_private_launcher.py": "243426a85075b3447adeb2ca320a13711409744f71837a06699de6ad5b04ee9d", - "key-cape/scripts/test_openbao_negative_private_preparation.py": "8e4dd327c4a339a0151ecba57db5987965b5d536ca18123fb7c74a047a241548", - "key-cape/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md": "f6113d269f127976518acb48eca818fd5d6f12421f4e4c742e8a84f49a14360b" - }, - "manifest": "/tmp/keycape-jwt-producer-handoff-v4-stage/manifest.json", - "manifest_sha256": "d6f641dc1e4aae7d36658d4199d89af14d2fd7a4bd4e526856eb340234f2d33e" - } - ], - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-ready-v9-stage/net-kingdom/sso-mfa/k8s/keycape -p test_openbao*.py -q", - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/keycape-jwt-producer-handoff-v4-stage/key-cape/scripts -p test_openbao*.py -q", - "PYTHONDONTWRITEBYTECODE=1 python3 /tmp/keycape-jwt-producer-handoff-v4-stage/evidence/paired_counterexamples.py -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-key39-ready-pair-review-stage/revision-v4/tests/test_platform_ready_pair_v4.py" - ], - "independent_platform_checks": 12, - "real_local_fakeSSH_wrapper_child_regressions": 9, - "result": "passed synthetic/offline tests; no actualSSH/network/Kubernetes/credentials/native action", - "supplied_key_checks": 40, - "supplied_net_checks": 74, - "total": 135 - }, - "verified": [ - "Child verifies source/window/publichealth before boundedready marker and private input. Nonce/executor/envelope/health/window bindings and dedicated nonstdio control FIFO refuse mismatches; no marker authority.", - "Launcher exactsource/envelope/window checks precede SSH spawn, private file load occurs only after matched captured childcontrol marker; wrong/missing marker preserves files and delivers zero bytes.", - "Fixed railiance01 SSHtarget, strict hostkey/batch/noagentforwarding/noportforwarding options, shellquoted constrained paths and envallowlist; malformed injection fields refuse before spawn/private reads. This does not admit transport or override SSH config/runtimecustody gates.", - "Bounded source/private input/marker parsing and timers; stdio/data/control aliases refused; wrapper captures and never forwards childstdout/stderr. Unexpected output refuses with uncertain actual native outcomes remaining owner obligations.", - "Actual local synthetic fakeSSH invokes real wrapper/child main source/window/health/ready/privateJSON code with health/nativeexecute substituted; wrongnonce/extra-marker/healthfailure/stalls preserve files before load, childoutput suppressed and uncertainpartial4096byte delivery remains stickyunknown. This is a source test, no transport authority.", - "Launcher main FIFO/symlink/device/oversize/stable-source metadata restrictions and shared preparation timer correction tested; expired original deadline refuses before validation/spawn/private reads." - ], - "work_item": "RPF-WP-0035-T13" -} diff --git a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review-refinement.md b/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review-refinement.md deleted file mode 100644 index adb80e6..0000000 --- a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review-refinement.md +++ /dev/null @@ -1,27 +0,0 @@ -# Platform owner confirmation — narrow proposal refinement - -Disposition: **agree to the amended design**, proposal -`KEY-WP-0039-T02/coordinated-negatives-v2`, SHA-256 `23f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09`. -This refines `RPF-REVIEW-20261011-KEY0039-v2` as one review trial, not another -scored success or a live authorization. Exact source hashes are retained in -`2026-10-11-keycape-openbao-negative-session-owner-review-refinement.sources.json`. - -Independently compared original proposal SHA-256 `d7b6d9782a2a39df02a36f806836158b75ed2d4a3aafe3db73fff316b551043d` with the amended -packet. Exactly one paragraph changed: Platform's effective capability-denial -matrix now includes `sys/policies/acl/*`, `auth/*/role/*`, and -`auth/token/create`, in addition to both guarded KV data and metadata paths. -No other proposal bytes changed. Runner remains SHA-256 -`bb9610babf44a959342bac700bb5712e97f400b44c4d0659d21df4426e616e19`. -Original review and its unsuccessful condition are preserved as history; this -amendment satisfies condition 1 without rewriting the original revision. - -Platform re-agrees the explicit audience pin and temporary matching expiry -client, positive-control acceptance followed by immediate successful revoke, -complete effective-denial matrix, owner-separated no-fallback proof and verified -finally cleanup. Source design review can satisfy Platform's portion of -KEY-WP-0039-T01 after other required owner concurrence. Runtime metadata, -normalized leeway, exact founder-scoped approval, native execution and cleanup -remain preflight/execution gates; their current absence does not make this -reviewed design incomplete or prove them achieved. KEY-WP-0039-T02 and -RPF-WP-0035-T02 remain open. No probe, token, live Secret or mutation was read -or executed during this confirmation. diff --git a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review-refinement.sources.json b/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review-refinement.sources.json deleted file mode 100644 index c35839b..0000000 --- a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review-refinement.sources.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "review_id": "RPF-REVIEW-20261011-KEY0039-v2-refinement-1", - "refines": "RPF-REVIEW-20261011-KEY0039-v2", - "original_proposal_sha256": "d7b6d9782a2a39df02a36f806836158b75ed2d4a3aafe3db73fff316b551043d", - "amended_proposal_sha256": "23f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09", - "original_review_sha256": "33b0d7f22634c804d364e84f83133d50079820a8a98758ceb5186893a7c24782", - "original_review_sources_sha256": "1c030bafcc0456d690f24bb6e19b756f87e30037b94139a7646495ebb1319876", - "runner_sha256": "bb9610babf44a959342bac700bb5712e97f400b44c4d0659d21df4426e616e19", - "disposition": "agree-design", - "change_scope": "exact paragraph adds three omitted CCR-required capabilities deny paths; no other bytes changed", - "original_condition_outcome": "original proposal omitted required paths; condition1 satisfied by explicit narrow refinement, original outcome preserved", - "native_execution": false, - "human_live_approval": false, - "live_gates_remaining": [ - "actual role/config raw and effective leeway metadata", - "net-kingdom live registry custody concurrence", - "exact final scoped foundergo", - "native eight refusals, expiry acceptance+selfrevoke, effective denial and preserved nofallback", - "verified cleanup and health" - ] -} diff --git a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review.md b/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review.md deleted file mode 100644 index 2af611f..0000000 --- a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review.md +++ /dev/null @@ -1,66 +0,0 @@ -# Platform owner review — KeyCape OpenBao negative session v2 - -Disposition: **agree with conditions** for the exact source hashes retained in -`2026-10-11-keycape-openbao-negative-session-owner-review.sources.json`. -Review ID `RPF-REVIEW-20261011-KEY0039-v2`; proposal -`KEY-WP-0039-T02/coordinated-negatives-v2`. This is independent Platform owner -re-agreement, not founder approval, an executable grant, or native acceptance. -No token, live Secret, login or mutation was accessed for this review. - -The amended packet correctly makes non-audience probes pin -`secrets-engine-openbao`; KeyCape defaults audience to clientId otherwise, so -a subject/tenant/roles/scope refusal would be confounded. The runner requires -exactly the designated difference among six bound claims and issuer match. -Existing human and coding-agent tokens deliberately establish only class -refusal, not isolated principal_type enforcement; absent coding-agent custody -stays inconclusive, not silently skipped. The expiry registration matches every -bound claim, has one-minute issuance, is first accepted, and its resulting -OpenBao session must be successfully revoke-self'ed immediately. Only then is -the same JWT tested beyond expiry. This knowingly creates a temporary credential -capable of login as secrets-engine; the earlier CCR approval does not authorize -creating that registration. - -17 synthetic runner tests pass. They substantiate semantic classifications, -positive expiry control, confirmed revocation, transport/ambiguous failures, -claim isolation, redirect refusal and nonretention. They do not prove native -RS256 issuance/signature verification, current role, or cleanup. Unexpected -acceptance fails the case; failed revoke halts further presentations. A later -cleanup does not rewrite failed or uncertain outcomes. - -## Conditions before execution or task closure - -1. Restore the complete CCR-2026-0038 capability-deny acceptance matrix in the - final session packet. The reviewed proposal mentions the guarded KV data and - metadata paths but omits `sys/policies/acl/*`, `auth/*/role/*`, and - `auth/token/create`, all explicitly required by the applied CCR. Platform - retains effective-policy checks and service-session revocation separately - from KeyCape's negative runner; no KV value read is needed. -2. Obtain value-free live role/config readback immediately before mutation and - retain exact binding/policy/issuer/algorithm plus raw and normalized leeway. - The source role has no expiration_leeway field. Omitted/zero means 150 seconds and - -1 disables it according to [OpenBao JWT API](https://openbao.org/api-docs/2.5.x/auth/jwt/); - this does not establish what the live role currently carries. Normalize a - duration-string value if present; do not pass raw -1 to the runner. Stop on - unreviewed role or registry changes. -3. Obtain NetKingdom concurrence on temporary registry/envrefs and restart - custody, then separate founder approval naming the exact final source - revision/digests, six clients, unique transient Secret/envrefs, bounded - attended window and rollback. Owner re-agreement here supplies no live go. -4. Retain eight semantically valid native refusal receipts, expiry positive - acceptance/revocation, unchanged-or-repeated consumer no-fallback proof and - full effective capability denial. No-fallback is a secrets-engine consumer - obligation; the login-only runner cannot prove it. Retain finally cleanup: - six registrations/envrefs absent, invalid_client for each removed client, - transient Secret/private files removed and health/positive behavior restored. - Issued JWT residual TTL remains even after registration removal. - -The temporary secrets remain attended Kubernetes delivery only, with no OpenBao -custody or retained files beyond the stated session. Production registration, -client credential, role, policy and custody are unchanged. Concurrent source or -Deployment change requires owner reconciliation, not blanket baseline overwrite. - -RPF-WP-0035-T02 remains open until those native receipts exist. -MASON-WP-0009-T05 separately retains operator/ESO capabilities, nonmember refusal -and routing activation. Neither task is closed by this review or by 17 tests. -Any source amendment changes the reviewed hash and requires explicit narrow -review confirmation; this receipt preserves the reviewed revision. diff --git a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review.sources.json b/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review.sources.json deleted file mode 100644 index 9b2b704..0000000 --- a/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review.sources.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "reviewer_role": "railiance-platform owner", - "agent_assignment": "dedicated independent owner review", - "review_id": "RPF-REVIEW-20261011-KEY0039-v2", - "proposal_id": "KEY-WP-0039-T02/coordinated-negatives-v2", - "disposition": "agree-with-conditions", - "human_live_approval": false, - "native_execution": false, - "tests": { - "command": "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/jwt-acceptance-stage/key-cape/scripts -p test_openbao_negative_probe.py -v", - "passed": 17, - "scope": "synthetic runner mechanics, not issuer/verifier acceptance" - }, - "sources": [ - { - "path": "/home/worsch/railiance-platform/AGENTS.md", - "sha256": "8d2f1a165e3f4eecf2e6e1d6a19c4bac9e8dcb64a76650e7185d55722f233c8b", - "kind": "current-owner-source" - }, - { - "path": "/home/worsch/railiance-platform/INTENT.md", - "sha256": "12c6540735af591f7a4a9fe85ff10d3cd00eec55b5b6b1ddf148abf2d3ac9a9c", - "kind": "current-owner-source" - }, - { - "path": "/home/worsch/railiance-platform/credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml", - "sha256": "65b2cfb772623da59c351033ab4eb2ec7adfbd109778e787c7fe1b0e185a3bc0", - "kind": "current-owner-source" - }, - { - "path": "/home/worsch/railiance-platform/openbao/auth/secrets-engine-jwt-role.json", - "sha256": "529dc23193be42cbe0d4440f902a64ca7c9158da158c9da13396aa0030480f40", - "kind": "current-owner-source" - }, - { - "path": "/home/worsch/railiance-platform/openbao/auth/keycape-services-config.json", - "sha256": "a059cbf0985c9f114a67e8bb0e0a0cb0ffcab7cffc402ca477b80963a5a7b12a", - "kind": "current-owner-source" - }, - { - "path": "/home/worsch/railiance-platform/openbao/policies/secrets-engine-login-self.hcl", - "sha256": "d1a2969dd2949c822972e9b32243650567ac123a669f3fc609e9bfea977bb4eb", - "kind": "current-owner-source" - }, - { - "path": "/home/worsch/key-cape/docs/openbao-negative-probe-clients.yaml", - "sha256": "4f38badef723c08871a28a446707e71be1d3cba8b4c3bcedb2759be65e4e3ed5", - "kind": "current-owner-source" - }, - { - "path": "/home/worsch/key-cape/config/service-clients.example.yaml", - "sha256": "b7e17698a0946f387c481b7e27fe9ddea22cdd9db2e34e32a50457e02a34b18e", - "kind": "current-owner-source" - }, - { - "path": "/tmp/jwt-acceptance-stage/key-cape/docs/openbao-negative-probe-session-proposal.md", - "owner_path": "key-cape/docs/openbao-negative-probe-session-proposal.md", - "sha256": "d7b6d9782a2a39df02a36f806836158b75ed2d4a3aafe3db73fff316b551043d", - "kind": "reviewed-staged-revision" - }, - { - "path": "/tmp/jwt-acceptance-stage/key-cape/scripts/openbao_negative_probe.py", - "owner_path": "key-cape/scripts/openbao_negative_probe.py", - "sha256": "bb9610babf44a959342bac700bb5712e97f400b44c4d0659d21df4426e616e19", - "kind": "reviewed-staged-revision" - }, - { - "path": "/tmp/jwt-acceptance-stage/key-cape/scripts/test_openbao_negative_probe.py", - "owner_path": "key-cape/scripts/test_openbao_negative_probe.py", - "sha256": "08d5598357c4db72672b2c4dc21ef09711ac4f9873fb29f8a6949d34afb8d85c", - "kind": "reviewed-staged-revision" - }, - { - "path": "/tmp/jwt-acceptance-stage/key-cape/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md", - "owner_path": "key-cape/workplans/KEY-WP-0039-openbao-negative-probe-tokens.md", - "sha256": "11fe539f914bdb7036236ffc112bc701ebf864d0929b2ee88e2d66ec470d53d4", - "kind": "reviewed-staged-revision" - } - ], - "external_reference": "https://openbao.org/api-docs/2.5.x/auth/jwt/", - "conditions": [ - "Restore complete CCR0038 capabilities deny matrix: guarded KV data+metadata, sys/policies/acl/*, auth/*/role/*, auth/token/create; retain service-session revoke receipt.", - "Capture actual live role/config metadata and raw/effective expiration leeway; source omission alone is not live verification. Abort on different binding/policy/issuer/algorithm or unreviewed registry.", - "Final exact revision, six temporary clients/Secret/envrefs, attended bounded window, net-kingdom owner concurrence and separate founder scopedgo required.", - "Require all eight semantically appropriate negatives, valid expiry acceptance/selfrevoke, preserved nofallback evidence and verified finallycleanup; runner all_refused alone cannot finish owner tasks." - ] -} diff --git a/docs/reviews/2026-10-11-secrets-execution-readiness-v2-platform-review.json b/docs/reviews/2026-10-11-secrets-execution-readiness-v2-platform-review.json deleted file mode 100644 index 82595f7..0000000 --- a/docs/reviews/2026-10-11-secrets-execution-readiness-v2-platform-review.json +++ /dev/null @@ -1,53 +0,0 @@ -{ - "accepted_scope": [ - "Local fail-closed readiness guard and current protected-handler wiring; no runtime execution grant.", - "Proposed consumer/Platform metadata-only execute-ccr direction with qualified implementation waits, not implemented schema or protected operation authority." - ], - "all_frozen_hashes_verified_before_and_after_checks": true, - "checks": [ - "All ten current _open_backend handlers wire readiness before authorization/consume/backend: apply, provision, rotate, verify, handoff, wrap, exec, session revoke, native revoke and native lifecycle. Exec per-lane approval also carries auth_args.", - "Configured auto and explicit service-jwt refuse every guarded action before claim/PDP/CAS/login/backend work. A local entitlement flag/file cannot bypass the guard.", - "Named env/bootstrap selections retain approval and stance gates; service-jwt never falls back. Login/self-only service-auth probes retain service-jwt selection.", - "Local compromise/reactivate are intentionally separate local-record effects; destroy remains refused before consume. Dry-run does not perform protected backend work.", - "Existing route uses ambient OpenBaoClient.resolve independently of service-jwt selection and reads readiness/KV field-presence metadata; unchanged legacy surface, not covered by this guard. This review does not claim all CLI reads are governed or that legacy routing is adopted service execution.", - "Proposed execute-ccr admits only exact owner workload-kv-read structure bodies; cannot substitute for generic AppRole/mount apply or provision/rotate/wrap/exec/native lifecycle. Separate Access/Approval request-context/package and consume correspondence acceptance remains required." - ], - "completed_record_precedes_formal_review": true, - "completed_work_record_original_sha256": "8271e6d9e4087e2ff43b5267f5f059b4368d8bed19c842240dd16ff9deb234be", - "decision": "approved", - "intent_basis": "Platform INTENT Stable Interfaces, Secure Custody and Operable/Observable: refuse identity-only execution before irreversible approval use, keep authentication separate from exact backend authority and bound owner contracts.", - "live_handoff": "RPF35-T12 and SecretsWP13 retain missing typed entitlement compiler, trusted subject/representation correspondence, served consume verification or same-owner CAS, durable one-shot ledger, owner-held task materialization/endpoint and body enforcement, native intended effect/negative/replay/cleanup. T02/T06 retain distinct native identity and full adoption gates. No actual first unexecuted approved CCR or founder native go was selected.", - "not_approved": [ - "live service login or backend work", - "founder approval/attendance or privileged grant", - "task-execution-grant provisioning or policy widening", - "Access/Approval action/digest/package publication", - "native identity/claim/TTL/uses/revoke/MASON acceptance", - "general Lifecycle API adoption or provider default migration", - "T02/T12/SecretsT06 completion" - ], - "reviewed_input_files": { - "docs/task-scoped-openbao-execution-contract.md": "e4b72d425491530aa9ebc7c9f3cec5f4408a247cba46ae38e94721e69887c5e6", - "src/secrets_engine/cli.py": "ea616dc93aa69a00c2e18c8c282c4f2bbaa4fa20e5e8bb8cb1c536ab83bd3d61", - "src/secrets_engine/engine_auth.py": "e0479cf6d8c13233cf99dc39491143478c021597941ae8b6ba0e37957a42f330", - "tests/test_execution_readiness.py": "f825bc581a3a746397abc677aa2271fdae644050b71ab28560c04ccbb9dda52d", - "workplans/SECRETS-WP-0013-task-scoped-openbao-execution.md": "fba8691575dd89af383a64c7d1d486661e0859903fd5a3c5e88296e4173ede83" - }, - "reviewed_manifest": "/tmp/secrets-execution-contract-design-stage-v2/manifest.json", - "reviewed_manifest_sha256": "709b9dddd1c8f00a995b1ff14264323fbe080ed95df244270ce9607528878fa7", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validation": { - "command": "PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/tmp/platform-readiness-guard-review-stage/runtime:/home/worsch/secrets-engine /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/secrets-execution-contract-design-stage-v2/secrets-engine/tests/test_execution_readiness.py /home/worsch/secrets-engine/tests/test_engine_auth.py /home/worsch/secrets-engine/tests/test_approval_consume.py /home/worsch/secrets-engine/tests/test_privileged_cli_evidence.py /tmp/platform-readiness-guard-review-stage/tests/test_independent_readiness_guard.py", - "credential_access": false, - "cwd": "/home/worsch/secrets-engine", - "independent_checks": "20 provider/action refusal cases; structural enumeration of ten backend handler guards; actual session-revoke refusal before protected handle read", - "initial_harness_outcome": "Running cloned runtime from Custodian cwd failed9checks because repository-owned classification config resolved there; corrected only test cwd, then58passed. No source fix was required.", - "native_actions": false, - "owner_checkout_writes": false, - "result": "36 original guard/auth/consume/evidence checks plus22 independent checks passed in isolated /tmp runtime with exact staged CLI/auth sources", - "tests": 58 - }, - "work_item": "RPF-WP-0035-T11" -} diff --git a/docs/reviews/2026-10-11-secrets-service-jwt-v2-platform-review.json b/docs/reviews/2026-10-11-secrets-service-jwt-v2-platform-review.json deleted file mode 100644 index 84c57bf..0000000 --- a/docs/reviews/2026-10-11-secrets-service-jwt-v2-platform-review.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "schema": "owner-source-review/v1", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "decision": "changes_requested", - "work_item": "RPF-WP-0035-T11", - "completed_work_record_established_before_review": true, - "authority_scope": "Platform exact source review only; no founder approval, admission, native acceptance, Secrets owner review or MASON owner review", - "reviewed_manifest": "/tmp/secrets-service-jwt-next-stage-v2/manifest.json", - "reviewed_manifest_sha256": "c4668709154dc22fc4613aa40a947b1baca385cdd30db307b3e2a6a225d33507", - "reviewed_verifier_sha256": "51284c58f9f81b7c996322f74ce78105f5727fb4970f1bb792e86340f5b83bab", - "reviewed_bytes_verified": true, - "reviewed_inputs_preserved": true, - "intent_basis": "railiance-platform INTENT Core Principles 2 Stable Interfaces, 4 Secure Custody, 5 Operable and Observable; declared Staff/PEP-shaped Tooling gap. Platform owns scoped OpenBao substrate enforcement, not new identity/authorization authority.", - "accepted_scope": [ - "Configured-origin binding, disabled environment proxies, universal redirect refusal, HTTP200/application-json/object-data and 64KiB response bounds are appropriate improvements; original transport tests independently passed.", - "Value-free evidence whitelist suppresses unknown fields and raw private error text.", - "The packet correctly preserves CCR38 login-only identity, separates native claims/TTL/uses/MASON acceptance from functional adoption, and refuses broad standing administrative service ACL/default migration." - ], - "counterexamples": [ - { - "id": "PLATFORM-SE-V2-C01", - "finding": "verify_session sets failure when session.close raises but still returns native_verified=true if evidence advertises both revoke success booleans.", - "reproduction": "Synthetic Session.close raises RuntimeError; exact policy/matrix returned; evidence supplies operator_revocation_succeeded=true and revocation_succeeded=true; result contains service revocation failed AND native_verified=true.", - "required_change": "Require cleanup completed without exception and no failure marker in the acceptance predicate; cleanup exceptions independently invalidate result even when stale/synthetic evidence booleans claim success." - }, - { - "id": "PLATFORM-SE-V2-C02", - "finding": "native_verified=true needs neither required source/established/attempted evidence nor operator/service accessor fingerprints.", - "reproduction": "Synthetic evidence contains only the two revoke success booleans, no secret_source, session_handle, operator_session_handle, established or revocation-attempt fields; complete matrix produces native_verified=true.", - "required_change": "Require the complete typed lifecycle evidence contract, fixed in-memory source, native establishment and both attempted/confirmed revokes. Reject missing fingerprints and SHA256(empty) sentinel from source; validate actual nonempty native accessors before fingerprinting, while preserving recovered-token cleanup." - }, - { - "id": "PLATFORM-SE-V2-C03", - "finding": "request_json ignores top-level errors if HTTP200/application-json also contains an object data with apparently valid metadata.", - "reproduction": "Synthetic response {errors:[private sentinel],data:{policies:[secrets-engine-login-self]}} returns data successfully.", - "required_change": "Reject mixed success/error envelopes and error fields before consuming data; add malformed/mixed-error counterexamples with no secret text publication." - } - ], - "validation": { - "tests": 40, - "result": "passed: original 37 plus 3 independent reproducible counterexamples", - "command": "PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/home/worsch/secrets-engine/src /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /home/worsch/secrets-engine/tests/test_engine_auth.py /home/worsch/secrets-engine/tests/test_openbao_secret_source.py /tmp/secrets-service-jwt-next-stage-v2/tests/test_service_jwt_boundary.py /tmp/platform-jwt-execution-contract-stage/tests/test_frozen_secrets_v2_counterexamples.py", - "native_execution": false, - "credential_access": false - }, - "execution_design": "docs/credential-lane-designs/secrets-engine-task-execution-entitlement.md", - "live_handoff": "RPF-WP-0035-T12 retains implementation/review/native execution entitlement; T02 retains login/claims/expiry/uses/MASON acceptance; Secrets T06 retains consumer adoption/default migration. No gate is closed by this review." -} diff --git a/docs/reviews/2026-10-11-secrets-service-jwt-v3-platform-review.json b/docs/reviews/2026-10-11-secrets-service-jwt-v3-platform-review.json deleted file mode 100644 index 0aae14a..0000000 --- a/docs/reviews/2026-10-11-secrets-service-jwt-v3-platform-review.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "all_named_file_and_proof_hashes_verified": true, - "completed_work_record": "workplans/RPF-WP-0035-credential-lane-implementation.md", - "completed_work_record_original_sha256": "03af4bfd31f89d7d4df1e6673c1038ab4a64e839deac965ea8dcbfd122e890e5", - "corrections_verified": [ - "PLATFORM-SE-V2-C01: cleanup must return without exception, no failure marker, and complete typed lifecycle evidence; stale true evidence cannot override close exception.", - "PLATFORM-SE-V2-C02: native acceptance now requires fixed in-memory source, establishment, attempted/confirmed operator and service revokes, and both bounded 12-hex session fingerprints. Actual in-memory provider/login source maps absent accessors to empty handles which fail these gates.", - "PLATFORM-SE-V2-C03: any top-level errors member, including empty/null, invalidates HTTP200/object-data envelopes.", - "Configured origin is checked before token send; environment proxies disabled; all redirects refused; HTTP200/application-json/bounded object data only; raw error/evidence suppression retained.", - "Packet keeps claim negatives/expiry/use/native MASON acceptance separate, recognizes deployed login-only identity and missing protected execution entitlement, and does not change default or standing service policy." - ], - "decision": "approved", - "frozen_inputs_changed": false, - "intent_basis": "Platform INTENT Core Principles 2 Stable Interfaces, 4 Secure Custody and 5 Operable/Observable: constrained metadata interface and truthful cleanup evidence; declared Staff/PEP-shaped Tooling gaps, not new authority.", - "live_handoff": "RPF-WP-0035-T02 retains login/claims/TTL/use/MASON gates. T12 retains new entitlement implementation and native owner acceptance. Existing design is unimplemented proposal: explicit typed metadata-only execute-ccr pilot does not fulfill general AppRole/KV Lifecycle API. Secrets T06 remains open until actual intended protected action plus dispatcher/steady-state adoption.", - "not_approved": [ - "founder native attendance or production execution", - "CCR admission/status/binding changes", - "native identity/claim/expiry/use/revoke acceptance", - "MASON reader matrices or nonmember refusal", - "new task execution backend or steady-state/default migration", - "T02/T06 completion" - ], - "prior_changes_requested_retained": true, - "prior_review": "docs/reviews/2026-10-11-secrets-service-jwt-v2-platform-review.json", - "prior_review_sha256": "b5b8e6d9beeadadc22b0c2fe98da02e3dd4955d93948d336f529a3912cc2a087", - "review_scope": "corrected identity-boundary verifier and truthful source/adoption packet only; not native acceptance or execution entitlement", - "reviewed_manifest": "/tmp/secrets-service-jwt-next-stage-v3/manifest.json", - "reviewed_manifest_sha256": "6bb05bac74e4939c3e42db2ae95c5f9fb4616bb20cc285811a315c26669ebce1", - "reviewed_verifier_sha256": "f672643118aa454b35635f790d74ce9b0d9b777c2c8ef3680115d6ba0812eac4", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validation": { - "command": "PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=/home/worsch/secrets-engine/src /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /home/worsch/secrets-engine/tests/test_engine_auth.py /home/worsch/secrets-engine/tests/test_openbao_secret_source.py /tmp/secrets-service-jwt-next-stage-v3/tests/test_service_jwt_boundary.py /tmp/platform-jwt-execution-contract-stage/revision-v3/tests/test_frozen_secrets_v3_regressions.py", - "credential_requests_or_access": false, - "native_actions": false, - "result": "49 maker/current provider tests plus 3 independent regressions passed; synthetic/local only", - "tests": 52 - }, - "work_item": "RPF-WP-0035-T11" -} diff --git a/docs/reviews/2026-10-11-task-execution-journal-v2-platform-review.json b/docs/reviews/2026-10-11-task-execution-journal-v2-platform-review.json deleted file mode 100644 index ac6bb30..0000000 --- a/docs/reviews/2026-10-11-task-execution-journal-v2-platform-review.json +++ /dev/null @@ -1,48 +0,0 @@ -{ - "completed_owner_record_precedes_formal_review": true, - "decision": "changes_requested", - "finding": { - "counterexamples": [ - "after_call(binding,False,correct_digest,evidence) confirms ordinal0", - "after_call(binding,0.0,correct_digest,evidence) confirms ordinal0", - "after_call(binding,'0',correct_digest,evidence) confirms ordinal0" - ], - "finding": "after_call lacks strict ordinal and call_digest validation; SQLite coerces ill-typed ordinal into exact stored integer, allowing pending outcome to be confirmed.", - "id": "RPF-JOURNAL-V2-F01", - "required_change": "Apply before_call equivalent exact integer0..63 and typed lowercase64hex call_digest validation before transaction; malformed confirmation must leave pending state unchanged. Retain no-live-admission and exact one-time confirmation checks." - }, - "inputs_unchanged": true, - "intent_basis": "Platform INTENT Stable Interfaces/Secure Custody/Operable recovery: exact durable admission and truthful unknown external outcomes, without source prototype becoming authority.", - "reviewed_files": { - "scripts/task_execution_journal.py": "86b912e98eaedfa172de0f805ceb572b8c787565d4d9bbfc09b5836289a4dc3a", - "tests/test_task_execution_journal.py": "01075296e57592ff30702cb9252c8b10133b26afe7921a084d5314107f9346c2" - }, - "reviewed_manifest": "/tmp/platform-execution-journal-stage-v2/manifest.json", - "reviewed_manifest_sha256": "341cac1de945d2fbe8f516119909bd44fc37e85dccfb38b451981d4de2d402e7", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "schema": "owner-source-review/v1", - "scope_limits": [ - "Primitive does not authenticate caller, PDP/claim/consume or exact body plan; trusted owner mediation remains required.", - "Call ordinal/hash comes from future independent owner plan compiler; no generic backend/cleanup adapter or owner-private unknown recovery supplied.", - "Source syntax/state tests are not native grant, source application, whole execution-contract acceptance or RPF35-T12/SecretsT06 completion." - ], - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/platform-execution-journal-stage-v2/tests -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-journal-owner-review-stage/tests/test_independent_journal.py" - ], - "credential_access": false, - "independent_checks": 5, - "native": false, - "owner_checkout_writes": false, - "result": "19passed including3 deliberately reproduced malformed-confirmation defects, realfork crash and privatefile check", - "supplied_tests": 14 - }, - "verified_properties": [ - "Four actual processes contend and exactly one reserves the approval.", - "Private existing directory, database/lock modes, symlink/hardlink rejection, FULL SQLite durability and directory fsync.", - "Approval/operation uniqueness and full immutable binding; completed/unknown/reserved replay never reestablishes live admission.", - "Persisted pending call blocks next call and every success transition, including consume-confirmed, until exact confirmation; process crash after intent remains unknown/nonresumable." - ], - "work_item": "RPF-WP-0035-T11" -} diff --git a/docs/reviews/2026-10-11-task-execution-journal-v3-platform-review.json b/docs/reviews/2026-10-11-task-execution-journal-v3-platform-review.json deleted file mode 100644 index 8402423..0000000 --- a/docs/reviews/2026-10-11-task-execution-journal-v3-platform-review.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "completed_record_precedes_review": true, - "correction": "RPF-JOURNAL-V2-F01 corrected: exact integer0..63 and typed64hex call digest validated before after_call update, no SQLite bool/float/text ordinal coercion; malformed confirmations retain pending outcome then one correct confirmation passes.", - "decision": "approved", - "implementation_gates": [ - "Authenticate actual caller/representation and PDP/claim/consume; source methods do not authorize transitions.", - "Independent owner plan compiler pins exact ordinal/method/target/body hashes; call hashes alone prove no method/body scope.", - "Restricted native adapter, TTL/uses/deadline and owner-held grant/cleanup enforcement.", - "Owner-private cleanup/recovery after unknown remains unwired: business dispatch must not resume; cleanup needs a separately admitted bounded owner recovery interface.", - "Runtime private directory/credential isolation and actual native crash/refusal/revoke/readback proofs; source modes alone do not establish enforcement." - ], - "intent_basis": "Platform INTENT stable interfaces, secure custody and recovery: durable one-operation admission and truthful pending outcomes without a prototype becoming authority.", - "not_approved": [ - "native grants, production execution or source application", - "complete mediator/whole execution contract acceptance", - "RPF35-T12 or SecretsT06 closure or default migration" - ], - "prior_changes_requested_retained": true, - "prior_review": "docs/reviews/2026-10-11-task-execution-journal-v2-platform-review.json", - "prior_review_sha256": "3ed7894f0d5f7a648c61e1c15d7404786187cc40393f95f5fc4d866613da9459", - "review_scope": "unintegrated durable admission journal primitive source only", - "reviewed_files": { - "scripts/task_execution_journal.py": "866ac65af18ddabc5876cc940605d136bebfd8829fc467129d23ce2416b7716c", - "tests/test_task_execution_journal.py": "580ad0224038b9e800ac055c6650ba8b2346be1a93a32ce1a42b54ab4776bb91" - }, - "reviewed_input_unchanged": true, - "reviewed_manifest": "/tmp/platform-execution-journal-stage-v3/manifest.json", - "reviewed_manifest_sha256": "905ff6666d05df6a19d165a535579d917d7de31a43e066c2afc34a57e8cbdc9d", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validated_properties": [ - "Actual4process race admits one operation only.", - "Real child-process abrupt exit after fsynced reservation/pending intent survives reopen as unknown/nonresumable.", - "Exclusive invocation, approval/operation uniqueness, immutable binding, exact call intent/confirmation and no terminal replay.", - "Private owner directory/SQLite/lock modes and no symlink/hardlink files; FULL SQLite commits and directory sync." - ], - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 python3 -m unittest discover -s /tmp/platform-execution-journal-stage-v3/tests -q", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-journal-owner-review-stage/revision-v3/tests/test_independent_journal_v3.py" - ], - "independent_regressions": 5, - "result": "passed synthetic/local journal checks; no credentials/native action", - "supplied_tests": 15, - "total": 20 - }, - "work_item": "RPF-WP-0035-T11" -} diff --git a/docs/reviews/2026-10-11-task-execution-journal-v4-platform-review.json b/docs/reviews/2026-10-11-task-execution-journal-v4-platform-review.json deleted file mode 100644 index 2938488..0000000 --- a/docs/reviews/2026-10-11-task-execution-journal-v4-platform-review.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "completed_record_precedes_review": true, - "contract_source_pins": { - "access-engine/pkg/api/canonical.go": "21e754617c2b82d673cef31b7075f8d177e4367782b63b20b37125587bf9e300", - "approval-engine/approval_engine/binding.py": "737081b11040233f3460c32d4a9a1ecf57b5a80961f8a39b07cf879a0fa4263b", - "secrets-engine/src/secrets_engine/approval_consume.py": "2c528667d017e0d3ba18d3fc24a7856585c532b40e3c7e9364646cb629b99274", - "secrets-engine/src/secrets_engine/authorization.py": "0ec4c3f7304d68abd06a0d08dc459e4f1f7269da0e4258549662be9e57b39fd3" - }, - "correction": "Exact served PDP/Approval sha256:<64 lowercase hex> request_digest preserved and required; bare request digests refused. Plan/call/evidence content hashes stay strict bare64hex. V3 source was unintegrated/native-unused; no live journal migration accepted.", - "decision": "approved", - "implementation_gates": [ - "Authenticate actual caller/representation and PDP/claim/consume; source methods do not authorize transitions.", - "Independent owner plan compiler pins exact ordinal/method/target/body hashes; call hashes alone prove no method/body scope.", - "Restricted native adapter, TTL/uses/deadline and owner-held grant/cleanup enforcement.", - "Owner-private cleanup/recovery after unknown remains unwired: business dispatch must not resume; cleanup needs a separately admitted bounded owner recovery interface.", - "Runtime private directory/credential isolation and actual native crash/refusal/revoke/readback proofs; source modes alone do not establish enforcement." - ], - "intent_basis": "Platform INTENT stable interfaces, secure custody and recovery: durable one-operation admission and truthful pending outcomes without a prototype becoming authority.", - "not_approved": [ - "native grants, production execution or source application", - "complete mediator/whole execution contract acceptance", - "RPF35-T12 or SecretsT06 closure or default migration" - ], - "prior_changes_requested_retained": true, - "prior_review": "docs/reviews/2026-10-11-task-execution-journal-v3-platform-review.json", - "prior_review_sha256": "b9001798bdc09cad3da5a18ba7aab6a048c962ed6b4fe68fb62a78c8ef3d721b", - "review_scope": "unintegrated durable admission journal primitive source only", - "reviewed_files": { - "scripts/task_execution_journal.py": "dc9bbb4a153ce8c466f39fde1fd0104f2dd37af9ff765399dfb9415ca607e767", - "tests/test_task_execution_journal.py": "189f99ce05383b53e37cf652a9664f0e8e7eeef7199ebee97b0d55399dfe4201" - }, - "reviewed_input_unchanged": true, - "reviewed_manifest": "/tmp/platform-execution-journal-stage-v4/manifest.json", - "reviewed_manifest_sha256": "22c6d225d393b994ab7b44a43904ced1b6d7f74e0e2bebf71ffe99f87191ef4c", - "reviewer": "railiance-platform-owner (agent railiance-platform, codex)", - "same_trial": true, - "schema": "owner-source-review/v1", - "validated_properties": [ - "Actual4process race admits one operation only.", - "Real child-process abrupt exit after fsynced reservation/pending intent survives reopen as unknown/nonresumable.", - "Exclusive invocation, approval/operation uniqueness, immutable binding, exact call intent/confirmation and no terminal replay.", - "Private owner directory/SQLite/lock modes and no symlink/hardlink files; FULL SQLite commits and directory sync.", - "Exact canonical prefix persists byte-for-byte through reopen; bare/uppercase/double prefix/wrong length/whitespace refused before durable admission. Prefixed plan/call/evidence content digests refused, pending unknown preserved." - ], - "validation": { - "commands": [ - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-execution-journal-stage-v4/tests/test_task_execution_journal.py", - "PYTHONDONTWRITEBYTECODE=1 /home/worsch/secrets-engine/.venv/bin/python -m pytest -q -p no:cacheprovider /tmp/platform-journal-owner-review-stage/revision-v4/tests/test_independent_journal_v4.py" - ], - "independent_regressions": 15, - "result": "passed synthetic/local source checks including actual4process race and separate abrupt child crash; no native/credential action", - "supplied_tests": 15, - "total": 30 - }, - "work_item": "RPF-WP-0035-T11" -} diff --git a/docs/reviews/2026-10-11-task-execution-plan-v2-platform-contract-readiness.json b/docs/reviews/2026-10-11-task-execution-plan-v2-platform-contract-readiness.json deleted file mode 100644 index d19401e..0000000 --- a/docs/reviews/2026-10-11-task-execution-plan-v2-platform-contract-readiness.json +++ /dev/null @@ -1,71 +0,0 @@ -{ - "author": "railiance-platform-owner (component maker, codex)", - "completed_owner_record_precedes_receipt": { - "path": "workplans/ADHOC-2026-10-11.md", - "persisted_commit": "7adbcb4", - "sha256": "2ed8e1c1f91351f26383d10c14b29d02c0cf8332f71183bb5c773a08645ad3f0", - "task": "RPF-WP-ADHOC-2026-10-11-T02" - }, - "disposition": "qualified_source_component_ready_for_independent_integration_review", - "exact_component_manifest": "/tmp/platform-typed-plan-stage-v2/manifest.json", - "exact_component_manifest_sha256": "41cda74ab32384c789ea8a760475671aadad4e5121335c8f60567ff53d66665b", - "exact_entries": { - "docs/task-execution-plan-compiler.md": "85e00093380ef7bbee4e2239679aafe6b798189a9b754962ea4cac3f1cf14ac6", - "scripts/task_execution_plan.py": "0a96c39a71c6fcca5ea695d1a46ce20d7345b27a507851d4d46e1543fd97e656", - "tests/test_task_execution_plan.py": "432f2a316a94b586286426906504cae9368e74cc6ba7a32d788c37924cad8224", - "workplans/ADHOC-2026-10-11.md": "2ed8e1c1f91351f26383d10c14b29d02c0cf8332f71183bb5c773a08645ad3f0" - }, - "formal_owner_contract_receipt": true, - "independent_technical_review_claimed": false, - "maker_receipt_sha256": "5787c05387e8a89c0cdb10adae9867aef1cb6c6173d1617151e88916611fcbf1", - "native_executed": false, - "not_accepted": [ - "Independent technical review of maker code by its own author", - "full mediator/typed-descriptor/authority/native-readiness acceptance", - "caller-provided authority/source configuration or source-label admission", - "sourceapplication/livegrant/tokenexport or defaultflip", - "KEY39 native authority from source-founder memo or owner receipts" - ], - "owner": "railiance-platform", - "owner_intent_binding": "Platform stable interfaces and secure custody: independently owned source/body compilation keeps service-supplied bodies and alternate paths out of metadata execution. This does not create an identity/authorization engine or native authority.", - "parent_task": "RPF-WP-0035-T12", - "parent_task_closed": false, - "privileged_authority_granted": false, - "provided_contract": [ - "OwnerConfig is owner-installed, never caller-deserialized. Closed public source set fixes compiler, current CCR validator/generator, CCR, one generated policy artifact and separately pinned actual Secrets catalogue source/loader/package/errors/redactor.", - "Existing validator/readiness must accept freshlyapproved confirmed production workload-kv-read; applied/verified/active or alternate types refuse. Existing netkingdom OIDC/Kubernetes roles only; no arbitrarypath/body/argv/KV/mount/AppRole mutation.", - "Actual Secrets catalogue validator/defaults derive the thirteen-field catalog_target; CCR/catalogueid/mount/logicalpath/field/ref equality and existing management enforced. CurrentCCR42 incomplete/noeligiblecatalogue refuses; positivefixtures are synthetic only.", - "Closed local request binds execute-ccr/kind, catalogue/CCR hashes, sixorderedGETPUTGET ACL+GETPUTGET role hashes, UUID and opaque sha256-prefixed canonical requestdigest. Caller bodies/configuration cannot supply authoritativeinputs.", - "Serialized Plan content and Call digests use explicit bare64hex for Journal. GET empty body/PUT generated JSON payload hashes retainprefix. before_call recompiles sources/readiness and rederives body bytes and compares immutable Plan before returning a frozen local Call." - ], - "retained_earlier_component_manifest_sha256": "7838549f939e73981db80022dc304aed4e4b296653a466ee1c3b9b5c7f84e48e", - "review_dependency": "Access worker independent technical integration review is distinct and pending; no Platform owner formal role is attributed to that worker. Root will evaluate exact source for later integration; this qualified ownership receipt does not preapprove it.", - "schema": "owner-source-contract-readiness/v1", - "source_applied": false, - "source_only": true, - "unimplemented_enforced_integration_requirements": [ - "Exact full ExecutionContract comparison against authoritative owner grant/baseline/estate/source/deadline inputs. Local component request is not full descriptor; missing metadata cannot be filled by callerconfiguration.", - "Authenticated initiating human/PIP/representation and independently trusted service identity. No projection/member/MFA/verified/admission flags produced; current Access hard refusal and Secrets readiness refusal remain.", - "Canonical full CheckRequest/PDP/claim/approval-free correspondence and authenticated consume confirmation; digestformat correctness is not authentication.", - "Durable Journal reservation/call-intent/state integration and restricted native adapter that dispatches exactly independently derived bytes with no retry after uncertain effects.", - "Native expected-baseline/readback normalization, before-write conflict checks and source/time enforcement across revalidation-to-dispatch. Observed filesystem changes are detected; hostile sameUID swap/restore/memory mutation not covered by sourcehashes.", - "Full runtime source/config/code/dependency authenticity and private isolation; hard elapsed/remaining action budget enforcement over parser/import/filesystem/compiler work. Component has no hardtimer.", - "Owner-held task token grant validation/TTL120s/use12 and cleanupreserve enforcement, no export, real deniedscope probes/revoke/temp removal and bounded owner-private recovery afterunknown.", - "Reviewed eligible disposable pilot catalogue/approvedCCR, fresh native/go/estate proof and functional success once; full lifecycle/genericapply/defaultmigration/SecretsT06/RPF35T02/T12 closure require separate evidence." - ], - "validation": { - "actual_tested_code_sha256": "0a96c39a71c6fcca5ea695d1a46ce20d7345b27a507851d4d46e1543fd97e656", - "actual_tested_tests_sha256": "432f2a316a94b586286426906504cae9368e74cc6ba7a32d788c37924cad8224", - "maker_tests_passed": 40, - "retained_identical_v1_code_tests": true, - "scenarios": [ - "realowner K8s/OIDC generatedpayloads", - "actualconsumer UTF8catalog fingerprint equality", - "action/kind/path/body/ordinal substitution refusal", - "notapproved/applied/unconfirmed CCR refusal", - "repinnedrootpolicy/engine-managedcatalogue refusal", - "forgedPlan/source drift/duplicates/unsafealias and modes refusal" - ], - "scope": "offline synthetic private temp metadata fixtures and public owner-source reads; no native/Secret/credential operations" - } -} diff --git a/docs/rpf-wp-0015-attended-windows.md b/docs/rpf-wp-0015-attended-windows.md deleted file mode 100644 index 50cf533..0000000 --- a/docs/rpf-wp-0015-attended-windows.md +++ /dev/null @@ -1,53 +0,0 @@ -# RPF-WP-0015 attended windows — founder checklist - -Procedures approved by all owners on 2026-10-14 against review contract -`1c08ba7ae064eddde89cdc15d36f0e1c1c372e1ba5c00f09f4b665409aa337a7` -(railiance-platform `8cfbf64`). Approval covers the procedures only. Each window -below still needs its own explicit founder GO; D5 (`2fd5849c`) authorizes the -exercise class, not a specific window. The runbooks are authoritative; this page -only orders the human steps. - -## Before either window - -| # | Item | Owner | State 2026-10-14 | -|---|------|-------|------------------| -| 1 | Push ops-warden `6e14300` (login output fail-closed fix, accepted in `reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json`), then `uv tool install --reinstall /home/worsch/ops-warden` from that clean checkout | founder go, then agent | committed locally, not pushed | -| 2 | Value-safe retrospective of WARDEN-WP-0036-T02 (2026-09-01 live op ran while the relaxed check was in source) | ops-warden / railiance-platform | open | - -## T02 — database lease recovery (≤15 min window) - -Runbook: `docs/audit-core-database-lease-recovery.md`. - -1. Pick a window that is not within 20 min of 03:17 UTC (attest-chain CronJob). -2. Fill an approval receipt from `docs/audit-core-database-lease-approval.example.json`: - approval id, window start/end, named abort operator, owner acknowledgement - message ids (audit-core, rapp-postgres, railiance-platform), synthetic load - `RAILIANCE-WP-0024-T02-SYNTHETIC-01` with driver revision - `sha256:941ba251…` (audit-core `8c8bcf4`), and the `synthetic_sender` block. -3. Announce two short audit-core restart gaps (projection and cleanup). -4. In the window: one contained ops-warden login runs the whole sequence — - `make audit-t02-attended-window` (approval receipt, driver path, confirm - string, bearer file and evidence dir outside Git). Through - `scripts/openbao-attended-exec.py` it runs lane project (restart #1) → - ssh-bounded port-forward → contained T02 exercise → 0600 evidence JSON → - lane cleanup with that evidence (restart #2). No token file is used. -5. Abort: the window script stops the port-forward and runs `cleanup --abort-reason` - itself; if it could not, run `make audit-synthetic-sender-cleanup - AUDIT_SYNTHETIC_SENDER_ABORT_REASON=...` (one more login). Never restart the pod by hand. - -## T03 — coordinated railiance01 reboot - -Runbook: `docs/railiance01-coordinated-reboot.md`. Additional gates before the window: - -| # | Item | Owner | -|---|------|-------| -| 1 | Prove provider console / rescue / hard-reset access and file a `RAIL01-CONSOLE-` receipt (railiance-infra `docs/railiance01-console-access.md`, RAIL-HO-WP-0011-T09). Today no out-of-band console is established. | founder | -| 2 | Remove the stuck PVC `platform-pg-drill/platform-pg-drill-1` (pvc-protection finalizer) in a separately approved attended step, or keep it explicitly listed | rapp-postgres + founder go | -| 3 | Fresh backup or written waiver for `state-hub-db`, `user-engine-pg`, `net-kingdom-pg`, `target-revenue-pg` (no CNPG backup configured) | each DB owner | -| 4 | New scenario id and fresh human decision (08-23 decisions/scenario are consumed) | founder | -| 5 | Fresh encrypted off-host OpenBao snapshot receipt (≤24 h) | platform custody, attended | -| 6 | 2-of-3 Shamir unseal holders present; named abort operator; railiance-infra abort-owner ack | founder | -| 7 | Pre-reboot preflight run to capture the evidence file (boot id, failing-pod baseline, CNPG identities) | agent | - -Stop immediately, self-revoke and never retry on any unexpected credential output -from the attended login. diff --git a/docs/runbooks/flex-auth-envelope-signing-attended-creation.md b/docs/runbooks/flex-auth-envelope-signing-attended-creation.md deleted file mode 100644 index 3494a8f..0000000 --- a/docs/runbooks/flex-auth-envelope-signing-attended-creation.md +++ /dev/null @@ -1,144 +0,0 @@ -# Attended creation: flex-auth decision-envelope signing key (CCR-2026-0050) - -Prepared 2026-10-21 under RPF-WP-0035-T14 (founder gate) for the custody route -`flex-auth-decision-envelope-signing-key`. **Nothing in this file has been -executed.** Every step that changes OpenBao or the cluster needs the founder's -explicit go for that change, given at the time of the change. No value of the -seed is printed, stored in Git, the hub, chat or a log at any point. - -Inputs that are fixed by reviewed source: - -| Item | Value | -| --- | --- | -| Read CCR | `credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml` | -| Native action | `docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json` (`RPF35-PDP-SIGNER-NATIVE-20261016-R1`) | -| KV document | `platform/data/workloads/flex-auth/envelope-signing`, fields `ED25519_PRIVATE_KEY`, `ED25519_PUBLIC_KEY`, `kid` | -| kid | `flex-auth-lifecycle-20261016-r1` | -| Metadata contract | `cas_required=true`, `max_versions=2`, `delete_version_after=0s`; bootstrap is CAS 0 | -| Read policy | `workload-kv-read-flex-auth-envelope-signing` (`openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl`) | -| Read role | `auth/kubernetes/role/external-secrets-flex-auth-envelope-signing`, SA `flex-auth-envelope-signing-eso` in `flex-auth`, audience `openbao`, 15m, no default policy | -| ESO projection | `docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed` (seed property only) | -| Writer | `scripts/flex_auth_signer_native.py` + `scripts/flex_auth_signer_custody.py` (`sha256:f169ad12…110c`, as pinned in the native plan) | -| Route | `warden access openbao-platform-admin-login --exec -- `; routing receipt `docs/evidence/2026-10-21-flex-auth-signer-warden-route.json` (`sha256:ad7d6a1829de53678b008b23627475e7570c71cf50f9f2ad5301635022a7f467`, lane `openbao-platform-admin-login`, verdict `founder_required`, act `oidc_login`) | -| Private receipt dir | `/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing` (0700, files 0600) | - -The 2026-10-16 routing blocker (warden selected `openbao-structure-survey`) no -longer reproduces: the 2026-10-21 plan query selects the correct lane. - -## 0. Preconditions (all must hold; any miss = stop) - -1. Five dispositions on CCR-2026-0050 recorded in `review.comments`: - founder, railiance-platform-owner, access-engine-owner, secrets-engine-owner, - cluster-runtime-storage-owner (railiance-cluster). -2. Founder decision on the hub (see RPF-WP-0035-T14) resolved with - `APPROVE: CCR-2026-0050 ...`; then a platform session, not the requester, - runs `make credential-change-sync-decision CREDENTIAL_CHANGE=CCR-2026-0050`. -3. Access (FLEX-WP-0024-T06) has the retained deployment - `flex-auth/flex-auth-secrets-engine` on the immutable candidate image - `forgejo.coulomb.social/coulomb/flex-auth@sha256:09a83b27…981f` **stopped**: - replicas 0, no owned ReplicaSet replicas, no pods using the signer SA or - mounting the seed, no HPA. Protected consumers (secrets-engine) fenced. - GitOps/Helm writers for the release frozen for the window. -4. Cluster owner has reviewed the outbox PVC (1Gi local-path) and PV Retain. -5. Secrets owner is ready to admit the public receipt through - `secrets-engine/docs/founder-admission.md` (SECRETS-WP-0014-T04, - decision e4eee965). -6. Local tree is clean on the reviewed commit; tests pass: - `uv run python -m pytest -q tests/test_flex_auth_signer_native.py tests/test_flex_auth_signer_custody.py`. - -## 1. Read lane: policy and role (founder go #1) - -```bash -make credential-change-status CREDENTIAL_CHANGE=CCR-2026-0050 # expect status approved -make credential-change-applier-dry-run CREDENTIAL_CHANGE=CCR-2026-0050 -make credential-change-applier-apply-plan CREDENTIAL_CHANGE=CCR-2026-0050 -# founder go, then: -warden access openbao-platform-admin-login --exec -- \ - python3 scripts/credential-change.py applier-apply CCR-2026-0050 \ - --actor founder --record-state-hub -# the founder types the confirmation phrase the tool prompts for -``` - -Then verify native readback of the bound claims (no JWT or Secret printed) and -record it: `python3 scripts/credential-change.py confirm-binding CCR-2026-0050 ---reviewer railiance-platform-owner --comment ""`. - -Rollback: delete role `external-secrets-flex-auth-envelope-signing`, then -policy `workload-kv-read-flex-auth-envelope-signing`. No KV data exists yet. - -Negatives (record with `record-evidence`): login with a wrong SA, namespace and -audience is refused; with the right identity, sibling path read, `list`, -`metadata` read, write and delete are denied. - -## 2. Seed creation (founder go #2; single attended window) - -Do this inside one window of at most 120 seconds after building the admission. - -1. Founder builds the admission file (owner-private, 0600, outside Git) in the - schema `railiance-platform.signer-native-admission.v1` with - `action=bootstrap`, the kid above, the routing receipt digest above, the - immutable image, `caller_binding` and the five `owner_reviews`. Each review - binds `review_scope_digest(admission)`, which excludes `owner_reviews` and - `expires_at`, so reviewers can approve the exact scope ahead of the window. - Set `expires_at` to now + 90 s last. -2. Run, in the founder's terminal only: - -```bash -warden access openbao-platform-admin-login --exec -- \ - python3 scripts/flex_auth_signer_native.py \ - --admission --admission-sha256 sha256: \ - --receipt-directory /home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing -``` - -The child checks the cluster ID, unseal, non-root platform-admin entity, audit -enabled, read policy/role readback and the stop fence (rechecked before -metadata setup and before CAS). It requires metadata absence (HTTP 404), sets the -metadata contract, generates the seed in memory, writes with CAS 0, reads back -version 1, and writes a receipt that holds public fields only. It is silent; the -outcome is the receipt `status`: - -- `custody-verified-pending-independent-public-admission`: success. -- anything else, or no receipt: **unknown**. Keep everything fenced. Do not - rerun bootstrap. Use the read-only `recover` action with a fresh admission - that binds the original receipt digest. - -Rollback before CAS: abort; nothing private was written. After CAS: no restore -and no automatic retry. Forward-only recovery under a newly reviewed action. - -## 3. Public receipt for secrets-engine (no private read) - -Derive the consumer receipt from the writer receipt, never from a KV read (the -KV document also holds the seed and reads are not field-filtered). Use Access's -existing tool, which reads public fields only, together with Access's published -policy pin (`access-engine docs/native-signer-public-pin.json`: flex-auth/local, -standalone, `secrets-engine.catalog-lane.lifecycle` v2, -`sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4`): - -```bash -R=/home/worsch/.local/state/railiance-platform/flex-auth-envelope-signing/.json -python3 ~/access-engine/tools/prepare_signer_public_receipt.py --help # then run per its usage on "$R" -``` - -The result must carry kid, public key, fingerprint, KV version, writer and -source/image/deployment hashes and the pin fields (`evaluator`, `mode`, -`policy_package`, `policy_version`, `policy_package_digest`); these are the two -secrets-engine conditions recorded in the CCR. -`public_key` is unpadded base64url, 43 characters. `public_fingerprint` is -`sha256:` + hex SHA-256 of that ASCII string, not of the raw bytes. The founder -delivers the file to the secrets-engine owner over an authenticated channel and -signs the consumer trust file per `secrets-engine/docs/founder-admission.md`. - -## 4. Delivery and signer start (founder go #3, Access-owned) - -1. Access/GitOps applies the reviewed ESO projection (SA, SecretStore, - ExternalSecret). Do not use client-side apply on Secrets. -2. Check readiness with the safe helpers only: - `scripts/eso_adoption_readiness.py` and - `scripts/secret_annotation_maintenance.py` (inspect mode). Never run - go-template or jsonpath against the Secret. -3. Access restarts the single signer replica. Verify kid/source/image, a signed - non-effect lifecycle allow and deny, and independent consumer verification - before reopening protected consumers. - -Negatives: unknown or old kid, wrong policy, tampered, expired or unsigned -envelope, wrong caller and missing seed are all refused. No taskpilot enablement. diff --git a/docs/runbooks/rpf-wp-0044-t04-attended.md b/docs/runbooks/rpf-wp-0044-t04-attended.md deleted file mode 100644 index cd6d84c..0000000 --- a/docs/runbooks/rpf-wp-0044-t04-attended.md +++ /dev/null @@ -1,108 +0,0 @@ -# RPF-WP-0044-T04 — attended issue-core repository reader (founder runbook) - -Authority: D2 (hub decision `89f1058f-33bc-47a8-8a49-0b44d426c67e`) authorizes this exact -read-only repository reader and the ordered adoption. No further approval is needed. The only -founder act is the orange-lane Platform OpenBao OIDC/MFA login that wraps the fixed executor. - -## State on 2026-10-20 (agent-prepared, no secrets touched) - -| Prerequisite | State | Receipt | -| --- | --- | --- | -| Host trust `[forgejo.coulomb.social]:30022` in `argocd-ssh-known-hosts-cm` | live since 2026-10-11, re-verified | `docs/evidence/2026-10-11-issue-core-transport/2026-10-20-verify-r1.json` (original execute: the-custodian `docs/evidence/2026-10-11-issue-core-public-transport/execution.json`) | -| Provider host key = pinned key (ssh-keyscan from railiance01) | match | this runbook, 2026-10-20 | -| AppProject `railiance-tenants` admits the exact SSH URL | live + in source (`argocd/railiance01/bootstrap/01-railiance-tenants-project.yaml`) | same verify receipt | -| Sealed executor contract | valid, expires 2026-10-27T23:59:59Z | `docs/evidence/2026-10-11-issue-core-credential/contract-sealed-2026-10-20.json` | -| Contract sha256 | `1ce9d2fdc39395407a01e358bfd0179f83f35c0a448e6658eb8f5aab351d3d3a` | | -| Live known_hosts sha256 (pinned in contract) | `7b6114ff6b96c9923d3c6139fdbaec02bb4e1fd93e6c9c9ac5064beb4570c652` | | -| Wrapper source to pin | `rapp-issue-core` `186c817b67d3919a64acdddbd3889e539841f946` (Argo owner + ExternalSecret metadata guard) | | - -A 2026-10-20 dry-run returned `target_preimage_changed` because the change was already applied; -that refusal is kept as `2026-10-20-dry-run-r1.json`. It is not a failure. - -If the contract has expired, or the known_hosts hash changed, the executor refuses -(`expired_contract`, `host_pin_changed`). An agent then re-seals; the founder does not edit it. - -## Founder steps (own terminal, TTY + browser) - -0. Tunnel up (agent can do this beforehand): `bridge status` must show `openbao-ui-railiance01 connected`; - otherwise `bridge up openbao-ui-railiance01`. - -1. Run, from the repo root, exactly: - -```bash -cd ~/railiance-platform && git pull --ff-only && \ -BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \ -warden access openbao-platform-admin-login --exec -- \ - python3 scripts/provision_argocd_issue_core_repository.py \ - --contract docs/evidence/2026-10-11-issue-core-credential/contract-sealed-2026-10-20.json \ - --contract-sha256 1ce9d2fdc39395407a01e358bfd0179f83f35c0a448e6658eb8f5aab351d3d3a \ - --journal docs/evidence/2026-10-11-issue-core-credential/execution-journal-r1.json -``` - - Complete the KeyCape OIDC/MFA in the browser. The executor is silent by design (all child - output goes to /dev/null) and itself nests `warden access forgejo-admin-api-token --exec`. - -2. Read the result, not the exit code (warden exit codes are unreliable): - -```bash -python3 -c "import json;d=json.load(open('docs/evidence/2026-10-11-issue-core-credential/execution-journal-r1.json'));print(d['state'],d.get('failure'))" -``` - - - `delivered_not_adopted None` → success. Hand back to the agent. - - `preflight_refused ` → nothing was issued. Hand the reason to the agent; it fixes the - precondition and seals a new contract; you rerun with a **new** journal name (`-r2`). - - any other state (e.g. `uncertain_do_not_replay`) → **do not rerun**. Hand back; the agent - reconciles provider key ID / custody / destination first. - - The journal contains only IDs, hashes and stage names, never key material. - -## Cluster route (since 2026-10-21) - -After the CUST-WP-0073 cutover the agent SSH login has no kubeconfig. Run the commands below through -the founder admin path: prefix each remote `kubectl` with `sudo -n`, for example -`ssh railiance01 "sudo -n kubectl …"`, and export `KUBECTL_ROUTE=founder-sudo` for the bounded -scripts (`argocd_public_transport.py`, rapp-issue-core `make verify-live`, `make argocd-pin-drift`). -The JSON in a `-p` patch must be escaped as `\"` inside the single quotes when the whole remote -command is in double quotes (see the T06 runbook for the verified form). T04 is done (acd1ade); this -section is kept for replays and rollback. - -## Agent steps after `delivered_not_adopted` - -1. Commit the journal. Pin the Application: move `argocd/railiance01/drafts/issue-core.application.yaml` - to `argocd/railiance01/applications/`, set - `repoURL: ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git` and - `targetRevision: 186c817b67d3919a64acdddbd3889e539841f946`, keep no `automated` block and no - finalizer. Commit and push through the normal flow. -2. `make argocd-pin-drift` — any `DRIFT` stops the root sync. The root is currently `OutOfSync`; - if its diff holds more than the issue-core child, apply only the child - (`kubectl apply --dry-run=server -f ` then `kubectl apply -f ` on railiance01). -3. Wait for repo-server to fetch the SSH source (no `ComparisonError`), then inspect the - client-side native diff of the issue-core Application. Stop on any spec change other than - Argo tracking metadata. -4. Manual sync, apply strategy, prune off, force off: - -```bash -ssh railiance01 "kubectl -n argocd patch application issue-core --type merge -p '{\"operation\":{\"initiatedBy\":{\"username\":\"rpf-wp-0044-t04\"},\"sync\":{\"revision\":\"186c817b67d3919a64acdddbd3889e539841f946\",\"prune\":false,\"syncStrategy\":{\"apply\":{\"force\":false}}}}}'" -``` - -5. Bounded owner verifier, from `~/rapp-issue-core`: - -```bash -make verify-live VERIFY_ARGS="--expected-revision 186c817b67d3919a64acdddbd3889e539841f946 --expected-source-url ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git" -``` - - Add `--smoke` for the authenticated smoke (one controlled issue created and closed). Retain the - output as a receipt and update RPF-WP-0044-T04. - -## Rollback - -- Before Argo adoption: revoke only the journal's provider deploy key ID on - `coulomb/rapp-issue-core` (same Forgejo admin exec lane), remove only Secret - `argocd/rapp-issue-core-repository`, soft-delete only the new custody version at - `platform/operators/argocd/repositories/rapp-issue-core`. Never touch shared policy or other keys. -- After adoption: restore the previous Application source/revision in Git and manually sync. - Direct `make rollback` in rapp-issue-core is disabled by design. -- Transport prerequisites: `scripts/argocd_public_transport.py --action rollback` with the same plan - and hash (guarded exact after→before), plus revert of the owner source patches. - -Abort owner: railiance-platform (transport/AppProject with railiance-enablement). diff --git a/docs/runbooks/rpf-wp-0044-t06-eso-adoption.md b/docs/runbooks/rpf-wp-0044-t06-eso-adoption.md deleted file mode 100644 index 816d82a..0000000 --- a/docs/runbooks/rpf-wp-0044-t06-eso-adoption.md +++ /dev/null @@ -1,118 +0,0 @@ -# RPF-WP-0044-T06 — adopt external-secrets into Argo (founder runbook) - -Authority: D2 (`89f1058f-33bc-47a8-8a49-0b44d426c67e`). No further founder approval is needed. -Highest-risk adoption: ESO serves every ExternalSecret on railiance01. Prep receipt: -`docs/evidence/2026-10-21-eso-adoption-prep/prep-receipt.json`. - -## State at preparation (2026-10-21, agent, offline only) - -- Draft `argocd/railiance01/drafts/external-secrets.application.yaml`: chart `0.16.1`, - `ServerSideApply=true`, no `automated`, no finalizer, no Force/Replace, and CRD values - `crds.annotations: argocd.argoproj.io/sync-options: Prune=false,Delete=false`. The readiness gate - passes the source check. -- Offline render (render sha256 `55236645…f136f`): 39 objects, made up of 20 CRDs (all 20 - prune/delete protected), 3 Deployments, 3 ServiceAccounts, 5 ClusterRoles, 2 ClusterRoleBindings, - 1 Role, 1 RoleBinding, 1 Service, 2 ValidatingWebhookConfigurations and the empty webhook-cert - holder (metadata only). **No ClusterSecretStore or ExternalSecret is rendered**, so adoption cannot - delete or replace a store. The webhooks declare no `caBundle`, so server-side apply does not take - it from the cert-controller. `ignoreDifferences` is added only if the diff below shows drift. -- **Not done by the agent:** the fresh live diff, the store baseline and the fresh predecessor proofs. - Since the CUST-WP-0073 cutover (2026-10-20) the agent's `ssh railiance01` login (`tegwick`) cannot - read the k3s kubeconfig, and the custodian-agent observer role has no CRD, RBAC, webhook or ESO read. - Run every `ssh railiance01 "kubectl …"` below through a login that still has cluster-admin read. - -## Attended script (preferred, 2026-10-21) - -The founder runs one script in their own terminal. It covers sections 1–4 below, with go/no-go gates -and receipts under `docs/evidence/2026-10-21-eso-adoption-prep/attended-/`: - -```bash -cd ~/railiance-platform && git pull --ff-only && (cd ~/rapp-issue-core && git pull --ff-only) -scripts/rpf_wp_0044_t06_attended.sh --precheck-only # optional: pre-checks only, changes nothing -scripts/rpf_wp_0044_t06_attended.sh # pre-checks, create Application, type ADOPT, sync, post-checks -``` - -- **Route.** `KUBECTL_ROUTE=founder-sudo` is the script's default and runs `ssh railiance01 sudo -n kubectl …`. - `sudo -n` never prompts, so no `ssh -t` is needed and the pipes stay clean. The same variable selects - the route in `workload_adoption_proof.py`, `argocd_adoption_proof.py`, `argocd_public_transport.py`, - `activity_promotion_readiness.py`, `argocd_child_pin_drift.py`, `capture_service_observation.py` and - rapp-issue-core `tools/verify_live.py`. Their default stays `agent`, so an agent run never escalates. -- **Smoke write.** The pre-check runs issue-core `verify-live --smoke`, which creates and closes one - Forgejo issue. The founder allowed this on 2026-10-21. -- **Exit codes.** `2` = no-go before any change, `4` = post-check failed (rollback steps are printed, - nothing is pruned), `0` = adopted. `--dry-run` prints every command without running it. -- **Credential-kind objects.** They are split out of the render before any live read, and the - evaluator (`scripts/eso_attended_checks.py`) refuses live input that contains one. - -The manual sections below remain the reference for what the script does. - -## 1. Pre-check (founder, or agent once read access exists) - -```bash -cd ~/railiance-platform && git pull --ff-only -D=docs/evidence/2026-10-21-eso-adoption-prep -# a) fresh predecessor proofs (gate window is 300 s, so run all three back to back) -( cd ~/rapp-issue-core && make -s verify-live VERIFY_ARGS="--expected-revision 186c817b67d3919a64acdddbd3889e539841f946 --expected-source-url ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git --smoke" ) > $D/issue-core-proof.json -python3 scripts/workload_adoption_proof.py --live --source-root ~/target-revenue --observation $D/target-revenue-observation.json --receipt $D/target-revenue-proof.json -python3 scripts/eso_adoption_readiness.py --draft argocd/railiance01/drafts/external-secrets.application.yaml --issue-core-proof $D/issue-core-proof.json --target-revenue-proof $D/target-revenue-proof.json | tee $D/readiness-r2.json -# must print "ready_for_fresh_native_render_diff": true -# b) fresh render + client-side diff (T05 method); credential data in diff output is masked by kubectl -python3 -c "import yaml;print(yaml.safe_load(open('argocd/railiance01/drafts/external-secrets.application.yaml'))['spec']['source']['helm']['values'])" > /tmp/eso-values.yaml -helm template external-secrets external-secrets --repo https://charts.external-secrets.io --version 0.16.1 -n external-secrets -f /tmp/eso-values.yaml > /tmp/eso-render.yaml -ssh railiance01 "kubectl diff -f -" < /tmp/eso-render.yaml > $D/client-diff.txt; echo rc=$? -# c) store baseline -ssh railiance01 "kubectl get clustersecretstores -o custom-columns=NAME:.metadata.name,STATUS:.status.conditions[0].reason --no-headers" | tee $D/store-baseline-before.txt -``` - -Go only if all of these hold: -- the readiness gate is true; -- the diff touches CRDs in metadata only (the protective annotation) and changes no CRD `spec`; -- nothing is deleted or replaced; -- the baseline shows every store `Valid` except `openbao-activity-core` and `openbao-email-connect` (`ValidationFailed`). - -If the diff shows webhook `caBundle` drift, an agent adds `ignoreDifferences` for -`ValidatingWebhookConfiguration` `/webhooks/0/clientConfig/caBundle` and `/webhooks/1/clientConfig/caBundle` and re-runs b. - -## 2. Create the Application (an agent, after the gate is green) - -The agent moves the draft to `argocd/railiance01/applications/external-secrets.application.yaml`, -commits and pushes, runs `make argocd-pin-drift`, then creates the child only with -`ssh railiance01 "kubectl apply --dry-run=server -f -" < ` followed by the same command without `--dry-run`. -The root also carries other OutOfSync children. Creating the Application syncs nothing. - -## 3. The founder's sync command (one command) - -```bash -ssh railiance01 "sudo -n kubectl -n argocd patch application external-secrets --type merge -p '{\"operation\":{\"initiatedBy\":{\"username\":\"rpf-wp-0044-t06\"},\"sync\":{\"revision\":\"0.16.1\",\"prune\":false,\"syncOptions\":[\"ServerSideApply=true\"],\"syncStrategy\":{\"apply\":{\"force\":false}}}}}'" -``` - -(Local double quotes; the remote shell receives the JSON inside single quotes. Prune off, force off, SSA.) - -## 4. Post-sync checks - -```bash -ssh railiance01 "kubectl -n argocd get application external-secrets -o custom-columns=SYNC:.status.sync.status,HEALTH:.status.health.status,PHASE:.status.operationState.phase,REV:.status.sync.revision" -# expect Synced Healthy Succeeded 0.16.1 -ssh railiance01 "kubectl -n external-secrets get deploy" # 3/3 available, no restarts beyond rollout -ssh railiance01 "kubectl get crd -o name | grep -c external-secrets.io" # still 20 -ssh railiance01 "kubectl get clustersecretstores -o custom-columns=NAME:.metadata.name,STATUS:.status.conditions[0].reason --no-headers" > $D/store-baseline-after.txt -diff $D/store-baseline-before.txt $D/store-baseline-after.txt && echo baseline-unchanged -# ExternalSecret refresh sample (issue-core): force a refresh and confirm Ready -ssh railiance01 "kubectl -n issue-core annotate externalsecret issue-core-runtime force-sync=\$(date +%s) --overwrite && sleep 20 && kubectl -n issue-core get externalsecret issue-core-runtime -o custom-columns=READY:.status.conditions[0].status,REASON:.status.conditions[0].reason" -# expect True SecretSynced -``` - -The agent records these as `docs/evidence/-eso-argocd-adoption.json`, re-runs issue-core -`verify-live`, and marks T06 done after the proving period. - -## Rollback (abort owner: railiance-platform) - -Never prune and never delete the Application with cascade. Revert the Application to the previously -reviewed chart `0.16.1` values in Git (`git revert `), re-apply the child, then -run the same sync command as above. To stop Argo management without touching workloads, delete only -the Application object. It has no finalizer, so no cascade happens, and the CRDs are protected anyway. - -## Separate later step (not part of adoption) - -Retire the stale `pending-upgrade` Helm release record (`sh.helm.release.v1.external-secrets.v*`) -under its own reviewed cleanup. ArgoCD does not track it, and it must not be replaced silently. diff --git a/docs/task-execution-cleanup.md b/docs/task-execution-cleanup.md deleted file mode 100644 index c21050b..0000000 --- a/docs/task-execution-cleanup.md +++ /dev/null @@ -1,66 +0,0 @@ -# Durable cleanup obligations for the metadata execution pilot - -RPF-WP-0035-T12 source preparation, 2026-10-16. This source is not an installed -mediator, native execution entitlement, founder approval or owner-native proof. - -Platform's existing admission journal made `unknown` and `failed` terminal and -blocked all further calls. Its single business-state cleanup transition could not -express a timed-out write with confirmed token revocation, or a verified write -with unconfirmed cleanup. A future mediator using that interface could suppress -cleanup precisely when needed. The journal now retains the original business -outcome and records cleanup in a separate durable table. - -At fresh reservation the trusted owner supplies at most one commitment each for -`task-token-revoke`, `identity-token-revoke`, and `grant-policy-delete`. Each is a -kind and exact native-call digest; no token/accessor, payload, path override or -shell command is accepted. Obligations and admission are inserted in one SQLite -transaction with FULL synchronous commits and directory fsync. Existing journal -files gain the separate table; old admissions gain no obligations or authority. -Empty cleanup evidence cannot produce a confirmed result or legacy success state. - -Only the same live exclusive invocation that transitioned this binding through -`consume-confirmed` can record cleanup intent. Neither a caller string nor journal -observation establishes consume authority: the trusted mediator must independently -validate the real served approval/PDP/consume before that transition. Cleanup can -proceed when the business call is pending or state is unknown/failed. Starting it -permanently closes business dispatch for that admission. An exact obligation moves -`owed -> pending -> confirmed|unconfirmed`; no retry transition exists. A crash -leaves pending, and reopening never restores admission. Pending business-call -uncertainty remains even if every cleanup succeeds. - -`task_execution_cleanup.run_cleanup` is the fixed caller of those journal methods. -It is intended for the owner mediator's `finally` block inside the original -`Journal.locked()` context. It dispatches only the three named owner cleanup -methods, passing their admitted digest for independent native-call comparison. -Ordinary exceptions/malformed evidence become closed unconfirmed reason evidence -without logging or hashing raw backend text. Remaining independent obligations -continue. A process death or BaseException leaves pending intent and requires -owner reconciliation. A journal write failure prevents the corresponding effect. -The component has no HTTP client, CLI command execution or native credential opener. - -The owner adapter must retain the exact handles in private memory, implement -bounded native calls, compare each reconstructed cleanup call against the admitted -commitment, and return confirmed only after the appropriate native revoke/absence -verification. A timeout, policy deletion alone, or eventual TTL expiry cannot prove -issued-token revocation. Cleanup has the original separate obligation rather than -the expired business deadline; this component does not extend any business grant. -No automatic postcrash recovery is implemented: owner-private recovery requires -fresh scoped authority and must never retry a business effect. - -The current Compiler Plan does not yet contain these cleanup commitments. A -production mediator must bind them into the exact reviewed grant/full canonical -request/owner plan before reservation. The journal's digest parameter does not -verify that correspondence. It must also integrate authenticated initiating actor, -served claim/PDP and independently verified atomic consume, exact source/body and -baseline/readback validation, ephemeral owner-held token materialization, per-call -trusted-time/use enforcement, and native audit/recovery. There is no production -executor in this repository to wire to this driver today; existing production -readiness remains refused. Approval CAS alone cannot provide exactly-once target -effects across process/network failure. T12 and SECRETS-WP-0013 remain open. - -Validation uses actual SQLite/process locks and abrupt `os._exit` before and after -a separately fsynced synthetic fixture effect. Reopened processes cannot resume -cleanup or business work; a four-process race admits one driver. Other tests cover -pending business calls, revoke timeout without suppressing later cleanup, changed -commitments and bindings, malformed confirmations and empty-evidence refusal. -These are disposable local fixtures, not OpenBao or production acceptance. diff --git a/docs/task-execution-grant-projection.md b/docs/task-execution-grant-projection.md deleted file mode 100644 index b5c7647..0000000 --- a/docs/task-execution-grant-projection.md +++ /dev/null @@ -1,85 +0,0 @@ -# Proposed task-execution-grant immutable projection - -RPF-WP-0035-T12 source preparation, 2026-10-16. This proposes and implements the -owner's pure projection/binding code; it approves no grant and registers no native -or legacy delegated applier. Existing production readiness remains refused. - -Use the existing `task-execution-grant` CCR type proposed by Platform and -SECRETS-WP-0013. Use the existing execution descriptor's -`execution_grant_ccr_id` and `grant_immutable_sha256` join. No second consumer -execution descriptor, authorization token or broker is introduced. - -`scripts/task_execution_grant.py` defines a closed CCR root with existing identity, -status/date/requester/review/target fields, effectful `execution` and `openbao` -objects, and an explicitly typed evidence event list. `immutable_digest` uses -SHA256 over sorted compact ASCII JSON of the complete validated projection. -It includes status, updated, title, requester, review requirements/approvers, -target coordinates, all execution inputs, generated HCL, token body and cleanup. -It excludes exactly `review.comments` and `evidence.events` by replacing those -lists with empty lists. No generic evidence-prefix stripping or caller-selected -exclusion exists. Unknown root/review/evidence fields refuse. Fields nested in -execution/openbao remain fully hashed and must exactly match independently -reconstructed effects when bound. Projection alone is not semantic admission. - -Reviews have exactly at/reviewer/decision/comment; evidence events have exactly -at/kind/result/reference_sha256. These append-only annotations cannot supply a -runtime approval or become policy inputs. `require_append_only` independently -checks that each old list is an exact prefix of the new list and that the -immutable projection is unchanged. Removal, replacement and reordering refuse. -The owner must use known history when checking an update; a current file cannot -prove its own history. Updating `updated` or status changes the digest and requires -a new exact join. There is no guessed exclusion for convenience. - -The compiler's optional independently installed `OwnerConfig.grant_filename` -selects one different CCR file under credential-change-requests. It adds exactly -that record and the pure validator source to the closed owner source-pin set. -The compiler reads verified bytes and executes the pinned validator source directly, -without a module-cache substitute. It reconstructs the entire effectful grant: - -- The existing descriptor, excluding only its own `grant_immutable_sha256` to avoid - self-reference, plus action/catalogue and the actual owner source pins. The - record's own raw file hash is omitted from its own source-pin projection; all - other source hashes, including the validator, remain included. -- Deterministic `cleanup_specs(operation_id)`, its fixed private credential slots, - exact revoke/verification and delete/verification paths/methods, declared - no-issue/no-create branches, one reserved cleanup use and the cleanup obligation - after business expiry. -- Exact operation-specific task policy HCL on the two independently compiled - metadata endpoints plus revoke-self, and the exact parented service token body: - nonrenewable, no default policies, no identity policies, 30–120s TTL and twelve - uses. No caller HCL, body, handle, wildcard or mount administration is accepted. - -It compares those structures with the owner record and compares the resulting -immutable SHA and grant ID with the existing descriptor fields before returning -a bound Plan. Unknown or changed effectful fields fail equality even if the caller -re-pins the record and recomputes its digest. The complete descriptor must still -match independently installed owner descriptor bytes. Raw source file hashes and -immutable hashes serve different purposes: even an annotation-only file edit -requires owner source-pin refresh and Plan reconstruction; it does not silently -change the grant immutable digest or restore an admission. - -The disposable native adapter now refuses missing/unbound or proposed grants, -rebuilds the exact bound Plan from pinned owner sources at construction, checks -all source pins immediately before each native request, reconstructs each actual -metadata body via the compiler and compares actual token/ACL bodies with the bound -projection before issuing them. It still requires the same live postconsume -journal admission and the live owner-spawned disposable OpenBao process handle. -Native fault tests explicitly use synthetic approved copies in temporary directories; -no real owner CCR status, policy or grant is created or changed. - -The normal credential-change CLI deliberately continues to reject this type for -execution. Adding it to LANE_MODULES would expose generic applier methods and is -not part of this source preparation. The pure validator is actually called by -the compiler/native fixture path; it is not a paper-only schema. An owner can -review this projection and later integrate source validation/recording into the -CCR tooling without enabling execution. - -Remaining production work is now specific: approve/adopt this owner CCR projection; -verify owner source/grant admission and immutable target CCR semantics; integrate -authenticated principal, served exact claim/PDP/package and independent signed -consume; finish total native baseline/readback normalization/conflict control; -install bounded isolated owner runtime, trusted served deadlines/use accounting, -audit and protected recovery; then run the exact founder-scoped attended trial. -Neither an approved string nor this projection authenticates human approval. -Approval replay prevention and uncertain backend-effect reconciliation remain -separate. T12 and Secrets WP13 stay open. diff --git a/docs/task-execution-mediator-seam.md b/docs/task-execution-mediator-seam.md deleted file mode 100644 index 463c056..0000000 --- a/docs/task-execution-mediator-seam.md +++ /dev/null @@ -1,115 +0,0 @@ -# Strict task handoff to independent consumption confirmation - -RPF-WP-0035-T12 source preparation, 2026-10-16. This seam calls only the existing -fixed disposable native adapter. It installs no production origin, identity, -permission, real grant, generic broker or backend default. - -## Actual starting gaps and reused owner interfaces - -At Platform source base d042d468 and Secrets c80a944 the native fixture used a -synthetic direct `consume-confirmed` journal transition. No actual strict Secrets -task handoff existed. Generic Secrets consume accepted a substituted approval ID, -coerced idempotent values and did not require the original exact decision. Those -are Secrets-owned defects; Platform did not copy its authorization policy or edit -that repository. The Secrets owner prepared a separate strict module under -SECRETS-WP-0013, referenced by exact hashes in this package's manifest. - -Approval Engine already provides `ExecutionConfirmationClient.consume_exact` and -`confirm_consumption`. The latter performs an independently authenticated GET of -the durable original approval object and filters it to the original consumption -triplet/time. It is historical PIP evidence, not authorization. It neither validates -an Access decision nor makes a supplied receipt authoritative. Platform's adapter -previously assumed its first native call was journal ordinal zero, conflicting -with a preceding durable CAS/confirmation sequence. - -`scripts/task_execution_mediator.py` now performs the narrow owner join: - -1. Require the actual `secrets_engine.task_execution_handoff` dependency; absence - refuses before provider credentials, journal reservation or native effects. - The mediator itself calls `authorize_task_execution`. It accepts neither an - inbound authorization object nor a caller-selected authorizer/transport callback. -2. Independently compile the exact local catalogue/descriptor/grant and compare the - strict owner's detached handoff descriptor byte-for-byte. Only authorization_id - may be added to approval reference metadata; purpose and source semantics cannot - be substituted through an inbound catalogue object. Secrets owns signed Check, - claim/native-act/full and approval-free digest joins, caller/subject correspondence - and the strict prepared object. Platform does not render or duplicate that decision. -3. Bind the actual returned full canonical digest into the owner Plan. Reserve the - approval/request/operation/Plan tuple and the precommitted cleanup obligations - once while holding the same invocation lock through cleanup. -4. Commit a CAS call intent, invoke Secrets `consume_task_execution` once and require - its actual typed result with exact original approval/request/decision/time and - `idempotent is False`. A historical idempotent response cannot open native work. -5. Commit a separate read intent and invoke the actual Approval owner's default - isolated transport, with independently installed read credential and exact same - owner-admitted origin. Require `confirm_consumption` to match the native act, - approval-free digest and original exact triplet/time. Raw approval entries and - provider credentials never enter journal evidence or receipts. -6. Recheck original approval/decision/operation bounds using the existing Secrets - time adapter, then persist `consume-confirmed`. Any CAS/read failure, timeout, - denied read or mismatch records unknown and causes zero native execution. A - subsequent invocation cannot retry the consumed approval or persisted admission. -7. Only then enter the fixed native grant/metadata adapter. Its next business-call - ordinal comes from the same live journal, so CAS and GET occupy ordinals0/1 and - native calls continue at2. Pending/unknown/cleanup-closed state cannot supply a - next ordinal. Each native request retains its own durable intent and outcome. -8. Cleanup stays under the original distinct obligations even if business work - fails. Wave 5 requires exact committed before/after snapshots before reporting - native apply verified; six HTTP successes alone remain insufficient. This is - sequential fixture acceptance, without atomic CAS, service-login provenance or - native production owner acceptance. - -The owner configuration selects provider route/read credential and the existing -Secrets configuration. These are independently installed runtime inputs, never -part of an HTTP body or authority derived from a caller string. Both providers use -real existing transports. Python import/file checks do not establish runtime -credential isolation; production custody/enforcement remains an external gate. - -## Whole disposable source-server proof - -Tests run the actual Access `serve` binary built from the owner source, with its -real Ed25519 signing implementation and enforce-mode TokenReview client. A local -synthetic reviewer independently admits only the explicitly fixed service token, -principal/audience and expiry. This is not actual Kubernetes identity proof. -Signing keys are generated in private temporary files; the public key and real -policy digest are independently installed in the Secrets trust file. The policy -digest comes from an owner-local actual source CLI before any network response. - -The positive policy is a separately named local synthetic exact-descriptor Rego -fixture with a real deny fixture and Rego test. It never edits or enables the -actual task policy. The actual proposed service task package is exercised unchanged -and returns genuine signed DENY. There is no fabricated DecisionEnvelope or -model-only approval. Real Approval WSGI/SQLite plus distinct independently admitted -synthetic service/human identities create, approve, consume and serve the object. -The strict Secrets module uses its actual mandatory producer-type signature verifier, -retained request/claim/envelope and one-shot strict CAS, followed by the independent -Approval GET and real disposable OpenBao grant/token/metadata/cleanup requests. - -Tests also prove actual independent read-scope denial after successful consume: -the durable approval is consumed, Platform remains unknown with pending read intent, -no target native objects exist and reinvocation cannot consume again. The native -journal is checked for known synthetic token leakage. A source purpose substitution -refuses before authorization/CAS, and a missing strict module refuses before any -credentials. Actual-policy denial leaves the approval approved and journal empty. -All identity, policy/registry admission, source-bundle/baseline digests and grant -approval in these tests are synthetic fixture inputs. These tests do not admit an -estate caller, real grant, production transport or actual protected custody. - -## Remaining production work - -Adopt/publish the exact strict Secrets dependency and this owner seam together; -admit the existing task-execution-grant source projection and actual identity, -policy package, provider origin/custody and complete source revisions. The current -actual policy remains hard-closed. Finish target CCR immutable semantics, total -native expected-state normalization/conflict control, CCR38 provenance, actual -per-call served-time/use enforcement, audit/outbox and protected uncertain/crash -recovery. Then obtain and execute the exact founder-scoped attended trial. -Approval replay prevention and uncertain native-effect reconciliation remain -separate guarantees. T12, Secrets WP13 and native adoption stay open. - -Wave 5 adds owner-installed exact metadata snapshots committed through the -existing expected_before/expected_after descriptor fields, per-target baseline -and complete role/policy readback comparison, durable readback/verified evidence, -and a separate cleanup-confirmed transition. The sequence remains sequential, -without native CAS or independently recoverable cleanup handles. See -[fixture acceptance](task-execution-native-fixture.md#exact-disposable-metadata-acceptance-2026-10-16-wave-5). diff --git a/docs/task-execution-native-fixture.md b/docs/task-execution-native-fixture.md deleted file mode 100644 index 255c7e2..0000000 --- a/docs/task-execution-native-fixture.md +++ /dev/null @@ -1,274 +0,0 @@ -# Executable native metadata/grant/cleanup preparation - -RPF-WP-0035-T12, 2026-10-16, second source package after the cleanup journal. - -The fixed `NativeFixtureAdapter` actually dispatches to disposable loopback OpenBao. -It requires a live owner-spawned OpenBao dev-process handle with the exact loopback -port and dev/no-store-token arguments, and refuses every non-loopback origin. -Production is deliberately not implemented. This is a guard against accidental -misrouting inside the trusted fixture runtime, not a Python security sandbox. -No estate credentials, grants, approvals or policy packages are read or changed. -The tests start in-memory dev servers with synthetic root/session credentials, -synthetic owner admission, grant/state/source digests and a synthetic postconsume -journal transition. This proves native primitives, not authenticated admission. - -The owner Compiler now deterministically includes the operation-specific temporary -policy name and cleanup call commitments in each serialized Plan. Commitments -include identity/task credential slots, exact native revoke/verify or delete/verify -method/path pairs, and the narrow no-issue/no-create skip branches. The full -execution descriptor can be bound into that Plan only by byte equality with an -independently installed owner descriptor, with operation/actor/CCR/catalogue/six -operations correspondence. Compiler revalidation preserves both descriptor and -cleanup content. The descriptor's SHA is preparation provenance; no opaque digest -or local file becomes approval or verified canonical request correspondence. - -The adapter needs the exact compiler/Plan/binding and separately installed owner -fixture inputs. All handles stay in owner memory with hidden representations. It -requires the same live consumed journal admission and exact grant-issuing state. -It refuses an existing per-operation grant policy, creates only the two compiler -metadata endpoints plus revoke-self, verifies HCL, mints one parented service token -with only this policy, no default/identity policies, nonrenewable TTL <=120 seconds -and twelve uses, then verifies native token bounds through the owner. Six ordered -metadata calls use independently recompiled source/method/path/body. A task use is -reserved for revoke; native token limits also enforce the ceiling. Business calls -check wall and monotonic deadlines before effect, with a five-second cleanup reserve. -No retry/remint, caller HCL/JSON/path, mount/AppRole/KV/SecretID or shell API exists. -Wave 5 below adds exact sequential snapshot acceptance; native baseline CAS remains absent. - -Every business/grant request records durable intent and closed status evidence. -Native cleanup has its own durable per-request intents beneath the original -committed obligation; accessor verification cannot erase an uncertain revoke. -The native adapter compares the exact committed cleanup sequence before request. -Self-revoke success must be followed by owner lookup-accessor's specific invalid -accessor response; generic HTTP400/403, timeout and policy deletion do not prove -revocation. The two explicitly accepted error encodings cover OpenBao's plain and -wrapped single-invalid-accessor forms. Native error text is never logged or hashed. -Cleanup bypasses expired business deadlines under the retained explicit obligation, -never source-driven business repair. Independent cleanup continues after failure; -retirement never overwrites the original unknown/failed business outcome. - -Current actual native tests cover policy/body and K8s-role application/readback, -owner-parented TTL/use policy bounds, native sibling/KV/token/mount/SecretID capability -denial, both token accessor-invalid proofs, policy absence, deadline-expired cleanup, -source drift, no preconsume materialization, changed descriptor/cleanup commitments, -nonloopback refusal, actual write followed by simulated lost response, and actual -policy retirement with failed task revocation. The lost response is fault injection -after a successful real native write; it does not pretend to be an actual network -partition. The failed-revoke fixture independently confirms the still-existing -native token, then performs explicit synthetic owner recovery. No blind retry occurs. - -## Minimal remaining decisions and integration - -1. Define/admit `task-execution-grant` in the owner CCR validator. Bind the exact - cleanup commitments, temporary policy/token bodies and source revisions into its - immutable projection, referenced by the existing descriptor grant SHA. The - descriptor currently has no cleanup field; no engine schema extension is needed - if this owner grant binding is made explicit. The immutable projection/excluded - append-only fields are not implemented in the current owner validator. This is - the concrete owner schema/admission decision, not a need for a new service. -2. Have the existing authoritative engines supply authenticated initiating principal, - exact canonical request + served claim/PDP/package and independent signed CAS - consume verification to the owner mediator before its journal transition. - The native fixture's local transition supplies none of that evidence. -3. Install exact origin/cluster/source/grant/baseline/readback inputs under the - attended owner runtime. The current binder compares independently installed - descriptor bytes, but does not authenticate source-bundle paths, grant digest or - compute immutable CCR projection. The six-operation plan still comes from pinned - actual owner source. Production origin authentication and deadline bounds against - served approval/PDP remain separate. -4. Finish expected-state normalization and conflict handling for native K8s/OIDC - defaults; add per-policy/role approved baseline comparison and exact readback - verification. The fixture checks known fields, not total production equality. - OpenBao metadata PUT has no native atomic CAS; the owner must choose exclusive - admitted target custody or an honest conflict/recovery protocol. -5. Wire the fixed adapter to that mediator with bounded runtime isolation, - owner-private uncertain-issuance recovery, external audit outbox and exact - founder-scoped attended trial. No production caller or protected recovery channel - exists here. The existing production readiness gate remains refused. - -No umbrella broker, general executor, new credential catalogue or standing service -admin entitlement is introduced. Approval CAS prevents approval replay; it cannot -make uncertain native effects exactly-once. RPF T12 and Secrets WP13 remain open. - -### Exact disposable metadata acceptance (2026-10-16, wave 5) - -The owner-installed `FixtureAdmission` now carries canonical JSON before/after -snapshot bytes. Their SHA-256 values must equal the existing descriptor's -`expected_before` and `expected_after`, and both snapshots must name exactly the -two compiled metadata paths. Explicit JSON null means absence. Only the actual -OpenBao empty-errors 404 is accepted as absence; denied/malformed reads fail. -These inputs contain metadata, never KV values or private token handles. - -Policy comparison retains name, HCL, version and cas_required. Only the -modification timestamp is excluded, after requiring an aware timestamp and the -closed observed policy field set. Role comparison retains every returned field, -including aliases, token defaults and unexpected additions, using canonical JSON -bytes (therefore false cannot equal zero). The disposable Kubernetes fixture pins -the complete OpenBao 2.5.5 response shape. Another plugin/version/default requires -an independently reviewed owner snapshot; nothing infers new accepted defaults. - -Each baseline gates its corresponding write. Policy readback occurs before role -baseline/write/readback. A role conflict can therefore leave the policy applied; -that operation remains unknown. This sequence has no native CAS, aggregate -baseline gate, concurrent-writer exclusion, or simultaneous final-state proof. -After all six calls match the committed snapshots, the same-live adapter records -readback and verified with a value-free evidence digest. The mediator reports -native_apply verified; cleanup-confirmed requires all separate cleanup receipts. -Readback success with unconfirmed cleanup cannot reach cleanup-confirmed. - -Reopening SQLite cannot dispatch, reconsume, restore token/accessor handles or -retry cleanup. Native effects can survive process loss. Cleanup handles remain -memory-only: no independently authorized recovery mechanism is implemented here. -This closes disposable sequential apply acceptance only, not TSA03 or native -production recovery/admission. - - -## Independent cleanup recovery source successor - -`task_execution_recovery.py` adds separately installed fixture-only owner custody -and fresh recovery permits. Optional `recovery_custody` on NativeFixtureAdapter -persists the original binding/cleanup commitments, exact operation policy, backend -port and identity accessor before any grant effect; it commits mint intent before -token creation and task accessor immediately after a valid issued response. -Bearer credentials are never persisted. The custody directory is separate from -the public journal, owner-only 0700/0600, bounded at 64KiB, no-follow, with atomic -replacement and fsync. It is real process-independent persistence for synthetic -fixtures. It does not isolate same-UID hostile execution, encrypt at rest, select -production storage or establish a production secret policy. Those are explicit -native custody/deployment gates, not supplied by Python types or file modes. - -The independent owner reads its private custody revision and installs a fresh -bounded permit, current owner credential and retained live disposable Popen. -Permit revocation is durably owner-recorded. The recovery runner has no mint, -consume, policy-apply or business method. It dispatches only fixed exact-accessor -revoke/readback and exact `task-exec-` policy read/delete/readback. -A differently recreated policy is never deleted. Backend admission remains the -same loopback-only owner-spawned dev process; no estate backend can be selected. -Accessor-based recovery is an independently admitted alternate cleanup projection, -not a reinterpretation of the original revoke-self call digest. Production needs -exact owner-reviewed recovery scope; original business grant does not imply it. - -Fresh recovery holds the journal process lock but never sets business admission -or consumed-admission fields. Separate recovery attempt/call/result tables retain -original cleanup statuses before reconciliation, write native intent before calls, -and preserve failed/pending outcomes. Proven cleanup alone updates the exact -original cleanup obligation to confirmed. Business state and original pending -call uncertainty remain unchanged. Reusing the original approval, continuing -metadata calls and reusing a recovery permit refuse. Failure needs another -independent owner permit; there is no automatic retry. Expired/revoked permits -or changed private custody revision refuse before calls. - -A token-create response lost before accessor custody remains unresolved token -cleanup debt: policy deletion/identity revocation can be independently confirmed, -but cannot imply task-token revocation. Neither expiry guessed from wall clock -nor policy absence fabricates token absence. A production successor must admit -an independent exact ambiguous-issuance reconciliation mechanism or prove bounded -expiry from owner backend evidence. No such new grant is implemented here. - -`test_task_execution_recovery.py` forks a real executor and terminates it without -finally cleanup after issue, business effect/lost response and revoke/lost response. -The independent parent, holding the backend and separately installed custody, -recovers specific obligations and verifies actual token/policy absence. Additional -cases cover lost-mint response, revoked credential, permit revocation/expiry, -revision drift, namespace policy-name reuse and private-file/binding refusals. -This is source fixture proof only. Native custody, isolation, exact recovery-role -permissions, retention/encryption, clock admission, audit delivery and human scope -approval remain missing. No generic broker, production reader, grant or CLI is added. - - -### Recovery R2 exact absence and authenticated capsule refinement - -R1 overclaimed policy absence for arbitrary 404 bodies. R2 requires exactly -`{"errors":[]}` for both initial GET and post-DELETE GET. Other 404 bodies, -permission denials, extra data, malformed/duplicate-key/nonfinite JSON and failed -readback cannot confirm policy cleanup. A successful DELETE status alone cannot -confirm absence. Recovery native policy comparison admits precisely one optional -terminal LF, matching the retained OpenBao HCL source/readback convention; spaces, -tabs, CR, extra terminal LF and any other byte change are refused. This is a -narrow declared normalization, not full whitespace or semantic equivalence. - -Private fixture capsules now carry HMAC-SHA256 authentication under an exactly -32-byte integrity key supplied independently by the recovery owner. The key is -never stored in the capsule or public journal, and is not a bearer credential. -The fixture owner retains this key independently across executor death; a fresh -owner custody object must supply the same key. Wrong-key or damaged accessor/HCL -bytes refuse before admission. This is integrity protection, not encryption or -same-UID isolation; production key custody, rotation/retention and recovery actor -selection remain explicitly unadmitted. - -After authentication the reader enforces exact top-level and nested binding/ -cleanup key sets and types, validated Binding/Cleanup formats and the ordered -source-derived cleanup_specs operation digests. Duplicate JSON keys, nonfinite -numbers, malformed phase/revision combinations and type-coercible values refuse. -The reader bounds bytes, rejects indirect/nonprivate files and detects stat/size -changes during its single read. A valid older prepared capsule still cannot -prove a task token absent when durable journal state/calls reached issuance: -that remains unknown cleanup debt. Authentication alone is not anti-rollback. -No business or consume admission is restored and no production scope is granted. - - -## Absolute budgets, effective credential expiry and exact source bytes - -This source successor tightens the disposable native owner adapter; it does not -establish actual v2 whole-stack ALLOW/native acceptance. The retained-registry -Approval correspondence failure is preserved in the coordinator artifact. The -new real provider fixture is retained separately as unfinished follow-up, while -the existing ec215-based fixture evidence remains its original scoped proof. - -Every native business/grant method now runs inside the earlier wall/monotonic -operation deadline minus the five-second reserve. After exact owner token lookup, -the adapter requires aware expire_time and retains the earlier actual expiry and -conservative mint-start + declared TTL bound, in wall and monotonic coordinates. -Every subsequent business call uses the minimum of global and credential bounds. -The descriptor and signed global deadline are never extended or rewritten. -Missing/expired credential evidence refuses issuance acceptance. Cleanup continues -under its original separate obligation after either business bound expires. - -The fixture owner main-thread execution profile uses absolute SIGALRM budgets, -not socket inactivity timeouts alone. Each native transport and cleanup native -call has a two-second execution deadline, including intent/source checking and -send/header/body/close work. Larger compiler reconstruction remains within the -earlier operation/credential window. A 50ms alarm backstop keeps blocked teardown -interruptible after timeout; it cannot authorize another business effect. Nested -timers retain the shorter outer deadline rather than refreshing it. Unsupported -threads, invalid budgets and conflicting ambient timers refuse. This fixed Unix -fixture profile does not select a production process supervisor or provide a -shared arbitrary-thread execution service. Native rollout still needs exact owner -runtime/admission, trusted clock and independent recovery/custody enforcement. - -The actual v2 package/version selects additional mediator checks over the exact -independently installed descriptor source_bundle before authorization and each -freshness gate, including pre-native grant entry. It reads bounded regular, -files without group/world write, non-indirect paths, compares exact source hashes and stat stability, -and applies an absolute read budget. The existing compiler's own pin set does not -implicitly authenticate other source_bundle roles; these explicit checks fill -that gap. No requester-selected path, approval flag or source boolean can enable -this path. Legacy synthetic fixture proof remains separately scoped and is not -used to satisfy v2 acceptance. - -Native policy/grant absence now requires exact empty-errors404 and policy HCL -comparison admits only the already reviewed single optional terminal LF, reusing -frozen recovery R2 helpers. Native response JSON rejects duplicate/nonfinite data. - -The independent slice tests cover actual header/body drips, stalled close after -timeout, slow compiler and shorter actual credential expiry before any HTTP -effect, nested/conflicting/foreign-thread budgets, private source hash drift, -unsafe modes/indirection/FIFO/oversize refusal, and bounded cleanup after expiry. -Actual native fixed grant/metadata/readback/cleanup remains a separate positive -compatibility check. There is no successful actual normalized provider-chain -claim in this slice; source-managed pre-approval normalization and immediate CAS -precision must pass the follow-up full-stack proof first. - -2026-10-16 T12 actual v2 successor source proof: retained registry signed DENY -normalization from the real Secrets prepare_task_approval builder precedes actual -Approval issue/human approval. Actual Access v2 then signs ALLOW; strict Secrets -CAS and independent confirmation GET precede six disposable native calls, exact -readback and confirmed token/policy cleanup. Approval precision source permits -immediate CAS without sleeps/backdating. The 60-second immutable deadline, -30-second backend TTL, 120-second synthetic observation and existing cleanup -reserve remain bound; issued credential expiry narrows business time. Source, -caller, revoke, source-reader, registry drift and expiry refusals cause no native -effects; revoke between ALLOW/CAS retains unknown without business dispatch. -This owner fixture uses separately installed synthetic identity/key/trust and -loopback OpenBao, grants no production authority and keeps T12 progress. Prior -failed temporal/normalization proofs remain retained. diff --git a/docs/task-execution-plan-compiler.md b/docs/task-execution-plan-compiler.md deleted file mode 100644 index 9bb49f7..0000000 --- a/docs/task-execution-plan-compiler.md +++ /dev/null @@ -1,112 +0,0 @@ -# Owner-derived typed metadata plan compiler - -Prepared source component for RPF-WP-0035-T12, with completed bounded source -preparation RPF-WP-ADHOC-2026-10-11-T02. This component does not authorize, -dispatch, authenticate or mint anything. CCR38 stays identity/self-only. - -`OwnerConfig` is independently installed by the Platform owner. It is never -parsed from an HTTP body, CheckRequest or a service-supplied configuration. -It fixes the Platform repository, Secrets source repository, owner-admitted local -catalogue directory and exact CCR/catalogue filenames and reviewed source hashes. -The closed source set contains this compiler, existing credential-change validator, -CCR, its one reader HCL artifact, catalogue entry and the actual Secrets catalogue -loader/package/errors/redactor files. Arbitrary additional files are refused. -Paths/hashes are public provenance, not credentials or authority. Source authenticity -and admitted runtime/continuous exclusive source custody remain external gates; -before/after filesystem hashes detect observed changes, not adversarial same-UID -swap-and-restore or arbitrary mutation of Python memory. - -The compiler reuses `validate_ccr`, `applier_readiness_blockers`, `apply_blockers`, -`generated_policy_hcl` and `auth_payload`. It only accepts freshly approved, -confirmed production `workload-kv-read` CCRs whose reviewed HCL matches generated -read-only policy, with existing netkingdom OIDC or Kubernetes auth mount. Already -applied/verified/active CCRs refuse rather than silently become replay authority. -No other lane module, mount enable/config, KV read/write, token/SecretID delivery, -AppRole mutation, caller HCL/JSON body, paths or command argv can be compiled. - -The catalogue comes from a separately owner-pinned local Secrets catalogue file, -loaded by `secrets_engine.catalog.validate_entry` executed directly from the -verified source snapshot, together with its pinned package/errors/redactor. -Each compilation uses temporary private module names and removes them on success -or failure. Canonical application imports remain untouched. Cached modules and -timestamp-based bytecode cannot substitute different code for the verified bytes; -new Secrets Engine imports outside the closed source set refuse. This is source -consistency within the admitted Python runtime, not a Python security sandbox. -Its catalogue id, existing mount, logical path, fields, production stage and -approval CCR reference must match the owner CCR. Engine-managed mounts and auth -are refused: existing delivery-auth metadata can be carried, but is never applied -or authenticated by this action. This is not a new credential-grants catalogue. -The exact thirteen-field `CatalogEntry` projection matches -`build_action_request.context.catalog_target`; its digest uses sorted compact UTF8 -JSON with `ensure_ascii=False`, matching the actual ExecutionContract helper. -Actual Forgejo CCR42 is in_flight and no matching Secrets catalogue exists. Neither -is invented. Positive tests only make explicitly synthetic approved/pilot copies -of existing public owner fixtures in temporary directories; they grant nothing. - -## Concrete interfaces - -`Compiler.proposal()` locally derives metadata: action `execute-ccr`, kind -`workload-kv-read`, catalogue id, CCR bytes digest, exact catalogue-target projection -and digest, and six ordered operation method/path/payload hashes. It is preparation, -not an admission or a complete ExecutionContract. Order matches the proposed Access -package: GET/PUT/GET ACL, then GET/PUT/GET OIDC/Kubernetes role. GET payload is empty; -PUT JSON bytes are deterministic, sorted compact ASCII JSON containing generated -HCL or `auth_payload`, with no caller-selected body. - -`Compiler.compile(request)` accepts exactly that metadata, operation UUID and the -opaque served canonical PDP request digest `sha256:<64 lowercase hex>`. -It compares the exact owner-derived proposal and returns a frozen serialized Plan. -It does not recompute Access canonicalization, strip the prefix, validate a PDP or -accept an unsigned digest as authority. The authenticated mediator must establish -correspondence with exact full CheckRequest/claim/consume bytes independently. -Plan content digest is explicitly bare64hex for Journal.Binding.plan_digest; -request and catalogue/payload hashes retain their required sha256 prefix. - -`Compiler.before_call(plan, ordinal)` accepts exact integer0..5, recompiles the -immutable plan and source bundle, re-derives fresh owner payload bytes and compares -again before returning one frozen local Call. A forged Plan constructor, extra -field, body hash/path/method/ordinal substitution, source drift or readiness change -refuses. Call.call_digest is the bare hash of exact ordinal/kind/method/path/payload -hash for the durable journal. The future mediator must validate this call immediately -before persisting its journal intent and dispatching these same bytes; this API -has no native callback, no backend opener and no credential. - -## Enforced integration gates still missing - -The compiler only binds its closed local component request. It does not construct -or approve the complete `SECRETS-EXEC-20261011-v1` descriptor. The mediator must -compare the exact owner-derived CCR/catalogue/six operations and full independently -pinned source bundle, authoritative grant CCR/hash, origin/cluster, approved native -before/after state expectations, deadline and TTL/use ceilings against locally -admitted inputs. Caller-selected grant/baseline/estate/configuration cannot supply -these. Missing authoritative inputs or whole-descriptor correspondence must refuse -before protected backend effects. Expected native baseline/readback normalization, -conflict handling and source/time enforcement spanning revalidation-to-dispatch -remain unimplemented; source comparison alone is not native atomic CAS. - -The proposed Access package still hard-refuses owner admission. No initiator -projection or verified/member/MFA flags are produced here; existing readiness -refusal remains. Actual identity/PIP/representation/service/PDP/claim/consume, -Journal admission/replay/unknown state handling, independent provider confirmation, -restricted native adapter and whole-request/source/deadline enforcement must be -integrated. The proposed task token ceilings are120s/12uses, with at least one -cleanup use; this component records limits, it does not enforce native expiry/uses -or provide a hard elapsed timer. Trusted owner runtime must bound parser/import/ -filesystem/compiler work by remaining action budget before any actual call. -Token custody/revoke, temporary object removal, owner-private recovery after unknown -and truthful native readback remain gates. The 2026-10-16 source cleanup journal -and fixed driver are documented in task-execution-cleanup.md; they preserve unknown -outcomes but are not yet bound into this compiler or a production mediator. General lifecycle/apply/exec/default -migration and SecretsT06/RPF35T02/T12 cannot close from this source component. - -Validation:45 compiler tests and 15 journal tests pass, covering real owner K8s/OIDC body generation, -exact catalogue unicode digest interoperability, refusal of alternate action/kind/ -path/body/ordinal, re-pinned root policy, nonapproved/unconfirmed/replayed CCR, -engine-managed catalogue, forged immutable plan, source drift/duplicates and -symlink/hardlink/writable source. No native action, credential/Secret access or owner checkout writes. - -October 13 correction: a regression first demonstrated that a cached validator -could replace the hashed source. Additional cases cover cached dependencies, -invalid catalogue acceptance through a substituted validator, and private-module -cleanup on both success and refusal. The October 11 v2 review remains evidence -for its original bytes only; it does not attest this corrected revision. diff --git a/helm/external-secrets-resources.values.yaml b/helm/external-secrets-resources.values.yaml deleted file mode 100644 index d18e12a..0000000 --- a/helm/external-secrets-resources.values.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Wave-2 resource requests for the ESO Helm release on railiance01 -# (RPF-WP-0063-T01, node budget 2026-10-21). Requests only: there are no -# requests or limits today and no limits are added. Applied on top of the -# release's stored values with --reuse-values. -resources: - requests: - cpu: 10m - memory: 64Mi -certController: - resources: - requests: - cpu: 10m - memory: 64Mi -webhook: - resources: - requests: - cpu: 10m - memory: 80Mi diff --git a/helm/forgejo-db-cluster.yaml b/helm/forgejo-db-cluster.yaml index a22be17..5caade1 100644 --- a/helm/forgejo-db-cluster.yaml +++ b/helm/forgejo-db-cluster.yaml @@ -26,8 +26,8 @@ spec: # timeouts on :8000 under node CPU pressure). Sized like net-kingdom-pg on ~4Gi node. resources: requests: - cpu: 105m - memory: 288Mi + cpu: 200m + memory: 512Mi limits: cpu: "1" memory: 1Gi diff --git a/helm/gitea-db-cluster.yaml b/helm/gitea-db-cluster.yaml new file mode 100644 index 0000000..62472bd --- /dev/null +++ b/helm/gitea-db-cluster.yaml @@ -0,0 +1,46 @@ +--- +# cnpg Cluster for Gitea database +# Managed by railiance-platform (S3). Operator lives in cnpg-system namespace. +# +# Apply: kubectl apply -f helm/gitea-db-cluster.yaml +# Status: kubectl cnpg status gitea-db -n databases +# +# Pre-condition: gitea-db-credentials Secret must exist in databases namespace. +# Create it (one-time, do NOT commit plaintext): +# kubectl create secret generic gitea-db-credentials \ +# --namespace databases \ +# --from-literal=username=gitea \ +# --from-literal=password= +# Then encrypt with SOPS and commit helm/gitea-db-secret.sops.yaml. +apiVersion: postgresql.cnpg.io/v1 +kind: Cluster +metadata: + name: gitea-db + namespace: databases + labels: + app.kubernetes.io/name: gitea-db + app.kubernetes.io/component: database + app.kubernetes.io/managed-by: manual + railiance.io/layer: s3-platform +spec: + instances: 1 # bump to 3 when node RAM > 8GB + # spec.postgresql.version is not a real CNPG v1 field; use imageName. + # Live cluster was upgraded to PG 18.1; match the live state so + # `make db-deploy` (kubectl apply) is a no-op rather than a rejection. + imageName: ghcr.io/cloudnative-pg/postgresql:18.1-system-trixie + storage: + size: 10Gi + bootstrap: + initdb: + database: gitea + owner: gitea + secret: + name: gitea-db-credentials + # Connection pooler can be added later: + # managed: + # services: + # additional: + # - selectorType: rw + # serviceTemplate: + # metadata: + # name: gitea-db-pooler-rw diff --git a/helm/openbao-ui-overlay-k8s.yaml b/helm/openbao-ui-overlay-k8s.yaml index 651cbd2..a80606a 100644 --- a/helm/openbao-ui-overlay-k8s.yaml +++ b/helm/openbao-ui-overlay-k8s.yaml @@ -48,7 +48,7 @@ spec: periodSeconds: 20 resources: requests: - cpu: 10m + cpu: 25m memory: 32Mi limits: cpu: 200m diff --git a/helm/openbao-ui-overlay/README.md b/helm/openbao-ui-overlay/README.md index a645611..5d8cd68 100644 --- a/helm/openbao-ui-overlay/README.md +++ b/helm/openbao-ui-overlay/README.md @@ -5,13 +5,6 @@ Streamlines the browser login mask reached through the named single **Sign in with KeyCape** action. Namespace, auth method, mount path, and role are preset in `presets.json` and hidden by `overlay.css` / `overlay.js`. -The main button always uses role `platform-admin`. Below it, "Sign in with a -workload role" offers only the roles listed in `presets.json` `workloadRoles` -(no free-text role), for example `reuse-surface-hub-writer-workload-kv-read` -(NK-WP-0051-T06). Each listed role must allow the loopback UI callback. Use a -private window for a workload role so the platform-admin KeyCape session is -not reused. - ## Mechanism (T01 decision) OpenBao ships UI assets inside the container image. There is no supported API @@ -37,7 +30,7 @@ behaviour. | File | Purpose | | --- | --- | | `VERSION` | OpenBao image tag this overlay targets (`openbao-values.yaml`) | -| `presets.json` | Hidden login defaults (`netkingdom`, `platform-admin`, …) and the `workloadRoles` allow-list | +| `presets.json` | Hidden login defaults (`netkingdom`, `platform-admin`, …) | | `overlay.css` | Hide raw OpenBao login fields | | `overlay.js` | Apply presets, branding on post-login Ember pages | | `login.html` / `login.js` / `login.css` | Standalone KeyCape login at `/ui/vault/auth` | diff --git a/helm/openbao-ui-overlay/login.css b/helm/openbao-ui-overlay/login.css index 8f17c9a..d9880ca 100644 --- a/helm/openbao-ui-overlay/login.css +++ b/helm/openbao-ui-overlay/login.css @@ -82,41 +82,4 @@ body { .login-error.is-visible { display: block; -} -.login-workload { - margin-top: 1.25rem; - border-top: 1px solid var(--border); - padding-top: 1rem; - font-size: 0.9rem; -} - -.login-workload summary { - cursor: pointer; - color: var(--accent); - font-weight: 600; -} - -.login-workload p { - margin: 0.75rem 0; -} - -.login-workload label { - display: block; - margin-bottom: 0.35rem; - color: var(--muted); -} - -.login-workload select { - width: 100%; - padding: 0.55rem 0.6rem; - margin-bottom: 0.9rem; - border: 1px solid var(--border); - border-radius: 6px; - font: inherit; - background: var(--card); - color: var(--text); -} - -.login-button-secondary { - background: var(--muted); -} +} \ No newline at end of file diff --git a/helm/openbao-ui-overlay/login.html b/helm/openbao-ui-overlay/login.html index 15ccd89..54a4a09 100644 --- a/helm/openbao-ui-overlay/login.html +++ b/helm/openbao-ui-overlay/login.html @@ -16,15 +16,6 @@ - diff --git a/helm/openbao-ui-overlay/login.js b/helm/openbao-ui-overlay/login.js index 37ed7ab..d629ad1 100644 --- a/helm/openbao-ui-overlay/login.js +++ b/helm/openbao-ui-overlay/login.js @@ -21,21 +21,9 @@ } } - // Only roles listed in presets.workloadRoles can be chosen; there is no - // free-text role. The main button always uses presets.role (platform-admin). - function workloadRoles(presets) { - const roles = Array.isArray(presets.workloadRoles) ? presets.workloadRoles : []; - return roles.filter( - (entry) => - entry && - typeof entry.role === "string" && - /^[a-z0-9][a-z0-9-]*$/.test(entry.role) && - entry.role !== (presets.role || "platform-admin") - ); - } - - async function redirectToKeyCape(presets, role) { + async function redirectToKeyCape(presets) { const mount = presets.mount || "netkingdom"; + const role = presets.role || "platform-admin"; const redirectUri = `${window.location.origin}/ui/vault/auth/${mount}/oidc/callback`; const response = await fetch(`/v1/auth/${mount}/oidc/auth_url`, { @@ -79,45 +67,18 @@ if (!button) return; - function bind(control, chooseRole) { - control.addEventListener("click", async () => { - control.disabled = true; - try { - await redirectToKeyCape(presets, chooseRole()); - } catch (error) { - control.disabled = false; - showError( - error instanceof Error - ? error.message - : "Sign-in failed. Contact your administrator." - ); - } - }); - } - - bind(button, () => presets.role || "platform-admin"); - - const roles = workloadRoles(presets); - const workload = document.getElementById("login-workload"); - const select = document.getElementById("login-workload-role"); - const workloadButton = document.getElementById("login-workload-submit"); - if (!roles.length || !workload || !select || !workloadButton) return; - - const summary = document.getElementById("login-workload-summary"); - const hint = document.getElementById("login-workload-hint"); - if (summary && presets.workloadLabel) summary.textContent = presets.workloadLabel; - if (hint && presets.workloadHint) hint.textContent = presets.workloadHint; - for (const entry of roles) { - const option = document.createElement("option"); - option.value = entry.role; - option.textContent = entry.label || entry.role; - select.appendChild(option); - } - workload.hidden = false; - bind(workloadButton, () => { - const chosen = roles.find((entry) => entry.role === select.value); - if (!chosen) throw new Error("Choose a listed workload role."); - return chosen.role; + button.addEventListener("click", async () => { + button.disabled = true; + try { + await redirectToKeyCape(presets); + } catch (error) { + button.disabled = false; + showError( + error instanceof Error + ? error.message + : "Sign-in failed. Contact your administrator." + ); + } }); } diff --git a/helm/openbao-ui-overlay/presets.json b/helm/openbao-ui-overlay/presets.json index 59f82d7..9aa265c 100644 --- a/helm/openbao-ui-overlay/presets.json +++ b/helm/openbao-ui-overlay/presets.json @@ -5,13 +5,5 @@ "role": "platform-admin", "title": "Sign in with KeyCape", "signInLabel": "Sign in with KeyCape", - "banner": "Platform operators authenticate through KeyCape at kc.coulomb.social.", - "workloadRoles": [ - { - "role": "reuse-surface-hub-writer-workload-kv-read", - "label": "reuse-surface hub writer (workload read)" - } - ], - "workloadLabel": "Sign in with a workload role", - "workloadHint": "A workload role needs its own person. Open this page in a private window, or sign out of KeyCape first, so the platform-admin session is not reused." -} + "banner": "Platform operators authenticate through KeyCape at kc.coulomb.social." +} \ No newline at end of file diff --git a/helm/playwright-consumer-access.yaml b/helm/playwright-consumer-access.yaml deleted file mode 100644 index fbadbf5..0000000 --- a/helm/playwright-consumer-access.yaml +++ /dev/null @@ -1,18 +0,0 @@ -# Group membership and authentication are managed by the identity/cluster owners. -# This binding does not issue credentials or make anyone a member. -apiVersion: rbac.authorization.k8s.io/v1 -kind: RoleBinding -metadata: - name: playwright-consumers - namespace: playwright - labels: - app.kubernetes.io/part-of: rapp-playwright - app.kubernetes.io/managed-by: railiance-platform -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: Role - name: playwright-consumer -subjects: - - apiGroup: rbac.authorization.k8s.io - kind: Group - name: playwright-consumers diff --git a/intakes/RPF-IN-0005-infd-purpose-release-owner-review.md b/intakes/RPF-IN-0005-infd-purpose-release-owner-review.md deleted file mode 100644 index 00e4739..0000000 --- a/intakes/RPF-IN-0005-infd-purpose-release-owner-review.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -id: RPF-IN-0005 -kind: intake -type: intake -title: "Independent platform owner review of INFD purpose image release" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -Review the exact prepared image-only release, SQLite custody/consistent backup, -actual-data restore gate, cutover writer boundary, manual Argo scope and database -retaining rollback. Initial source manifest SHA256: -7c6738b39ea8e54e61c8ed625e94ad0b2755e672e1576efcdf84a1d665ac3300. - -This owner record precedes the review evidence. Its scope is independent source -review and local synthetic verification. It does not authorize native backup, -export, restore, credentials or rollout. Final disposition and exact hashes will -be attached after review; existing approval boundaries remain in force. - -## Initial exact review - -Changes required against frozen manifest `7c6738b39ea8e54e61c8ed625e94ad0b2755e672e1576efcdf84a1d665ac3300`: pre-open schema refusal, optimization-safe admission, actual runtime ConfigMap/PVC identity sealing, and live rollback-count clarification. Review receipt: `docs/evidence/2026-10-11-infd-purpose-release-owner-review/conditional-review.json`. Independent synthetic backup and fake-transport probes are mechanism evidence only; no actual private data or native operation occurred. Intake remains vetted until the refined source receives exact final review. - -## Final owner review - -Absorbed into final technical review `RPF-REVIEW-20261011-INFD-PURPOSE-R1-FINAL`, receipt `docs/evidence/2026-10-11-infd-purpose-release-owner-review/final-review.json`. Technical agreement applies only to refined exact 78-path manifest `b1e3e66bc4023dc80c6ea59564c3557c7653fb3c307f46a049997b76198a9033`. All four initial defects are resolved; original conditional failure/probe evidence remains. Eleven packet methods plus four additional independent methods pass. Owner review is complete; human disposition, actual protected-data drill, rollout and native verification remain gated in INFD-WP-0009-T04. No native action or approval is inferred. diff --git a/intakes/RPF-IN-0006-forgejo-consumer-eso-review.md b/intakes/RPF-IN-0006-forgejo-consumer-eso-review.md deleted file mode 100644 index 081adce..0000000 --- a/intakes/RPF-IN-0006-forgejo-consumer-eso-review.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -id: RPF-IN-0006 -kind: intake -type: intake -title: "Platform review of proposed Forgejo ESO consumer reader" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: CCR-2026-0042 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -Owner review of exact source manifest `271afd04512b3dac642b3385013cac03d28afde22ff97bcca8d0e262ab3d9279`, proposed dedicated ESO consumer-reader CCR/body, custody, binding, installed-controller compatibility, revocation and exact new type/case acknowledgment boundary. This source work record precedes the formal review. No other owner role, native apply, token mint, secret read or human acknowledgment is asserted. Final review disposition will close this record; native dependencies remain in the existing case/CCR driver. - -## Completed source review - -Absorbed into platform technical review `RPF-REVIEW-20261011-FORGEJO-ESO-R1`: exact17-path source reviewed; generated consumer CCR must identify Forgejo workload and preserve issued-token revocation/expiry plus created-only rollback terms. Conditional review complete; implementation refinement remains with ORGREF-WP-0002/CCR-2026-0042, and fresh proposed1.2 acknowledgment/native owner gates are unfulfilled. The completed source work record precedes the formal review entry. - -## Final exact review - -Final technical agreement `RPF-REVIEW-20261011-FORGEJO-ESO-R1-FINAL` covers only refined19-path manifest `5dbd04cf96d73a2604cbf113073d618c03434fc87508ba614cbe3820a1a0e431`. Both generated-CCR defects resolved;44 independently rerun focused tests pass. Initial conditional review and counterexample remain historical evidence. This completed source review is not new type/case1.2 acknowledgment, native apply, credential mint, structural materialization, consumer Helm handover or first-case completion. Existing ORGREF-WP-0002/CCR42 carry those remaining gates. diff --git a/intakes/RPF-IN-0007-infd-purpose-actuator-review.md b/intakes/RPF-IN-0007-infd-purpose-actuator-review.md deleted file mode 100644 index 8e05065..0000000 --- a/intakes/RPF-IN-0007-infd-purpose-actuator-review.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -id: RPF-IN-0007 -kind: intake -type: intake -title: "Platform review of INFD protected custody and image release actuator" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -Review exact11-path execution source manifest `653b68c3891c066f40d104bfc67a35da5e436605cd99afd52fbac4b47a9553fe` and bundle `0a79eda40fe0954fd3828bf06fd6ccf149e294ac194bc6fe605e585b878fa8d3`, approval gate, private real-data custody, offline clone isolation, single-writer cutover, uncertain recovery, manual Argo scope and current-database rollback. Record precedes formal technical review. Synthetic verification only; no native backup/export/restore, mutation, credential read or founder approval is authorized. - -## Completed initial source review - -Absorbed into technical review `RPF-REVIEW-20261011-INFD-ACTUATOR-R1-CONDITIONAL`. Changes required: unhealthy-candidate rollback must not depend on candidate readiness/exec; offline restore must repeat integrity/schema/count proof after admin. Deadline/recovery semantics require explicit amended-packet agreement. Initial12tests passed but independent failure probe reproduced zero rollback dispatch for an unready candidate. Native work remains in INFD-WP-0009-T04; no native action/approval occurred. Completed record precedes formal review entry. - -## Final exact helper review - -Final technical agreement `RPF-REVIEW-20261011-INFD-ACTUATOR-R1-FINAL` covers helper manifest `f2fad6c251fbb4420e07c1329802f521b513bbf66e51f64c5d673ef04f16284c`,23paths, execution bundle `27d6d6d3133c963da1dde92d498d636617387c16e850a17c598148abc35c775d`. Sixteen synthetic tests independently pass; additional recovery probes reject wrong operation/source/UID/prune/resource scope and confirm unhealthy-candidate recovery request without Pod/exec. Original conditional evidence remains. Final founder packet must hash-bind this executor/four render inputs and explicitly disclose the all-phase deadline before submission; source agreement is not approval or native custody/provider evidence. - -## Snapshot failure source review work - -Same Platform owner preparation trial, following successful native backup and retained UNCERTAIN drill-copy reported by the coordinator. Completed independent source-mechanism check reproduces WAL0/SHM32768 on a synthetic Store backup after the initial checker. No native database was opened or copied, no retry/cleanup/source HEAD change occurred. The exact recovery-source technical review entry follows this owner work record when maker source is frozen; original failed journal and approval remain evidence, not backfilled success. Fresh exact supervisor disposition on the recovery delta is required before any action. - -## Completed existing-snapshot rescue source review - -Same Platform role final technical agreement `RPF-REVIEW-20261011-INFD-SNAPSHOT-RESCUE-R2` covers exact66-path stage `0867e27a6754dddff3eedbb333ab4ca4faa98ee31d383ebe940b8e054c6cabf7`. Twenty-one executor and five mocked reader tests independently pass; additional ordinary-file/mock and journal crash probes refuse unsafe custody/replay without altering the original. This work note precedes its formal entry. Fresh exact source-version2 founder disposition, newly committed source and authenticated fixed-origin readback are required before any recovery phase. Original COMPLETE backup/UNCERTAIN copy and approval remain historical unchanged; no native recovery, copy or deletion occurred in this review. - -## Independent read-only outcome reconciliation review - -Completed same Platform source judgement after coordinator-reported approved R2 release and original verify UNCERTAIN caused by serialized prune omission. Independent read-only evidence may verify the already-approved target while retaining the original failure/journal unchanged; this does not authorize native mutation or helper retry. Source judgement precedes the formal review entry. Actual collected evidence remains subject to exact owner review and is not claimed complete here. - -## Final actual target outcome assessment - -Same Platform owner ACK `RPF-REVIEW-20261011-INFD-R2-ACTUAL-TARGET-ACK` verifies coordinator-collected read-only target receipt c3fc0740, additional exact Pod/UID/GID receipt078fff22 and separate post-rollout native provider allow-path c534f5fa. Matching installed Argo v3.5.3 primary source substantiates false omission; original explicit prune=false and exact succeeded scoped operation correlate to the release. Original verify remains UNCERTAIN; independent evidence refines the same trial outcome without helper/source/journal replay or backfilled success. Product closure and live unpromoted future-helper residual remain owner follow-through, with inherited MEDIUM/T05 retained. Completed cumulative owner record precedes final ACK. diff --git a/intakes/RPF-IN-0008-infd-zlib-candidate-review.md b/intakes/RPF-IN-0008-infd-zlib-candidate-review.md deleted file mode 100644 index 7a93b0d..0000000 --- a/intakes/RPF-IN-0008-infd-zlib-candidate-review.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -id: RPF-IN-0008 -kind: intake -type: intake -title: "Platform source review of separate INFD zlib candidate publication" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009-T05 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -Bounded new Platform review of source candidate manifest `8d345bb2bde3efce4d0cc99dfc2f883a64f79c42d2cd4abb288229a995e9ad66`: official zlib package/index provenance, exact current763c base and minimal layer/runtime changes, local scan/SBOM proof, source-pinned ownerCI publication/credential boundaries. Distinct from completed purpose-actuator RPF-IN-0007 and its approvedR2 trial. No native rollout/data action, owner source write, registry publish or carried-forward human approval. Native763c still has disclosed inheritedMEDIUM/T05 dueOct18; source candidate is unpublished. This owner work record precedes formal review. - -## Completed initial source review - -Initial technical review `RPF-REVIEW-20261011-INFD-ZLIB-CANDIDATE-R1` validates exact27-path8d345 source/localproofs and publication owner boundary. Source proof helper must refuse optimized Python before artifact IO; directory coverage verified independently for current artifacts and required on actual published readback. Four source methods pass; raw scans confirm base1MED/candidate0 on stated DB. Current local candidate remains unpublished; no native authorization transferred. Review refinement remains in promoted INFD-WP-0009-T05, separate from unpromoted verifier residual and completed IN7. Completed owner work record precedes formal entry. - -## Completed amended-source review preparation - -Same bounded review completed against the amended source. Exact initial conditional source review and four-file workrecord/probe snapshot are archived under `initial-380c6172`; initial failed optimized-proof boundary remains historical. Seven maker tests and independent optimized/deletion boundary probes reviewed. This completed workrecord is staged before the final formal review. Published-image readback and separate native authorization remain outside this source review. - -## Completed final exact-source review - -Final frozen66-path source `3e41ab391cda26f09a556fecef8a17c2759ef2899e43eb136b25ef4b3fde23a0` completed after optimization, directory metadata and whiteout-boundary refinements. Eight maker tests and five independent synthetic boundary probes pass. All66 hashes match. Actual existing local candidate remains unpublished with unchanged two-file delta,313 directory records and stated scan snapshot. This workrecord update precedes the formal narrow-source agreement; original380c conditional and645 failed proof-boundary evidence remain archived. No native result, approval, publication or T05 completion is recorded. - -## Completed lower-layer-only whiteout refinement review - -Same IN8 preparation trial completed against frozen74-path source `724aa4777eb1c8976d868e5f504473f4b6d58f13d750d87aefa5c8d5ca0747ec`. Whiteout markers now mask prior-layer state before merging current additions. Nine maker tests and thirteen independent CLI probes pass, including eight root/nested regular/opaque whiteout order variants that check the actual emitted replacement-file hash. All74 hashes match; existing local candidate remains unchanged/unpublished. Exact preceding16-file agreement snapshot and manifest/map are archived at `accepted-66-6af44c10` before supersession. This completed anchor precedes formal review and does not create a new success or native authority. diff --git a/intakes/RPF-IN-0009-infd-zlib-native-release-review.md b/intakes/RPF-IN-0009-infd-zlib-native-release-review.md deleted file mode 100644 index b0398d5..0000000 --- a/intakes/RPF-IN-0009-infd-zlib-native-release-review.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -id: RPF-IN-0009 -kind: intake -type: intake -title: "Platform technical review of distinct INFD zlib native release" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009-T05 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, review] ---- - -New bounded Platform technical review of immutable published zlib artifact and separately proposed image-only native release/drill/cutover/recovery. Distinct from completed RPF-IN-0008 source candidate review and purpose R2 authorization. No native action, private database access, credentials, owner source writes, carried-forward approval or T05 closure. - -## Completed initial changes-requested review - -Initial exact67 manifest1e4 was observed at review start, then shared stage changed to68 manifest81adb9e1749d6bd9f19755a2bbd7b501589ea782445537bbbfe925e1d9f8fa47 before snapshot preservation. Review stopped rather than treating that as unchanged frozen source. Observed68 bytes and manifest archived;29 published proof6ba2 hashes verified. Remote keyword dispatch and recorded backup snapshot identity are not plumbed; freshscan helper permits unbound incomplete zero reports. Fresh scan age and DBNextUpdate controls exist but artifact and full inventory coverage must be checked. Completed owner workrecord precedes conditional entry. Refinement remains this same review trial; no technical agreement or native approval issued. - -## Completed refined-source changes-requested review - -Same owner preparation trial reviewed immutable143 manifest `f170b34d8846919b18520264e7ba96e2ac11a3bce7899bc5710fcb0d22f4af29`. Original wiring and incomplete scan proof defects fixed; all143 source hashes/plan file bindings verified,25 synthetic methods pass. Independent remote payload/partial scan repro confirms fixes; separate fixture shows unrelated privileged ephemeral container passes current Pod gate. Post-ready freeform output and recovery authority availability require explicit refinement. Exact previous74 owner-review files plus manifest/map archived before supersession. Source-only review is completed before conditional entry; no technical ACK, approval or native evidence inferred. - -Root selected a NEW human-proposal recovery alternative for further source preparation: independently root-confirmed same accepted receipt retained before release may support manual supervised763/c96 Deployment-only latestDB recovery for at most30minutes after release and before deadline, only exact approved custody/journal/source pins and demonstrable transport-unavailable/502/503/504 plus failed candidate placement. No401/403/revoked/invalidsignature/conflict fallback, no backup/release fallback; inability to query later revocation while unavailable must be disclosed. This is proposed scope awaiting exact code review and NEW founder disposition, not existing authority. - -## Completed final exact-source technical review - -Final immutable481 manifest `3f1ce7ed678a457f73ab6486c7e81ef3360daf4f7ec1196f3031c3ffaace57ec` reviewed in this same IN9 preparation trial. All481 hashes and plan executor/case pins match;36 synthetic tests and12 additional public-artifact/synthetic checks pass. Ephemeral Pod custody, private metadata filtering, original payload wiring and full fresh scan coverage now refuse challenged defects. Bounded supervised outage recovery is expressly NEW proposed founder consent, not old approval: rollback only failed exactcandidate/source, retained authenticated pre-release exact acceptance,30minutes/globaldeadline/UID/config/PVC/journal/source/image pins, no401/403/revocation/conflict/healthy candidate/backup or release fallback. Later revocation unobservable during outage is disclosed; recovered verification proves separate rollback placement, not provider acceptance/taskclosure. Exact prior123 owner files/manifest/map preserved before entry; original67 historical absence remains honest. Completed workrecord precedes formal source agreement. No native action or new approval inferred. - -## Final481 actual API compatibility finding; no ACK issued - -Before returning any agreement, Product identified actual provider shape incompatibility. Owner source lifecycle_review.outcomes returns only review/dispositions, optional reviewNone/not_submitted and optional submissionstate; lifecycle_service wraps it in current PDP authorization. The staged sanitizer instead requires invented topmemo_id and restrictive inventedstates. Independent read-only owner-source inspection confirms it. Prior unissued draft retained explicitly as superseded, not formal acceptance. Additional source correction must use fixed-request/HTTP200 correlation and bounded shape/count-only proof without detailed freeform dispositions or founder-authority inference. Completed sameIN9 changes-requested workrecord precedes entry. - -## Completed postcontract final source review - -Same completedIN9 preparation trial reviewed immutable509 manifest `767c6e93adfa1a844b1e25947f33e782b087a747452575f056b3df25bc88873c`. All509 hashes plus closed executor/case pins match;38 synthetic methods and18 additional public-report/shape/negative checks pass. Actual lifecycle review/dispositions shape now supported without invented identity/state/actor fields. Provider proof emits fixed authenticated request context/status/time/shape/counts only and explicitly never infers approval. Prior130 fullfiles and exact9d0ff679 manifest retained losslessly with content-addressed82blobs and130path map. Source/render/freshscan/recovery guards remain unchanged; no native request/data access/action or approval. This completed anchor precedes formal technical agreement for NEW human proposal; exact final ACK/checklist-only rebinding remains before posting. diff --git a/intakes/RPF-IN-0010-infd-zlib-native-post-acceptance.md b/intakes/RPF-IN-0010-infd-zlib-native-post-acceptance.md deleted file mode 100644 index 54ff851..0000000 --- a/intakes/RPF-IN-0010-infd-zlib-native-post-acceptance.md +++ /dev/null @@ -1,23 +0,0 @@ ---- -id: RPF-IN-0010 -kind: intake -type: intake -title: "Platform native post-acceptance of exact INFD zlib release" -repo: railiance-platform -lane: supervised -status: closed -owner: railiance-platform -requested_by: the-custodian -origin: handoff -origin_ref: INFD-WP-0009-T05 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, acceptance] ---- - -New small Platform owner native post-acceptance record, separate from closed RPF-IN-0009 source preparation. Scope only actual accepted source312ab97/imagef558 completion, retained metadata proof, and no expanded estate/customer/runtime authority. Reviewer reads safe retained root evidence, never private snapshots/database/credentials/native APIs. - -## Completed actual-evidence assessment - -Root immutable20-file manifest0e4799f709e097312c18ef0124afc153003efef0627902591939a218dbd66d4d checked before/after and full unchanged safe metadata/source history retained. Eight native phase receipts pass; matching frozen backup/copy and isolated clone pre/post counts, created-only cleanup, image-only manual falseprune release/current database retention, installed zlibr1/policy9d/schema5/health and postReady fixed-route current-PDPHTTP200 bracket exact same Pod/source/image. Full exact44-package fresh unsuppressed report checked against approved scan contract at actual verification time. Root three prephase refusals and negative1.1806second clock diagnosis retained; only root bookkeeping verified_at moved to workstation observation while native_observed_at retained. Approved source/helper/render/plan unchanged. Previous sandbox hypothesis was disproven, no custody/durable phase/native effect in those refusals. No recovery used, no automaticgrant inferred. Product closes foreign task/risk/WP records separately; no C07 alias/tombstone determination or deletion. This completed owner workrecord precedes formal native post-acceptance. diff --git a/intakes/RPF-IN-0011-activity-core-release-custody.md b/intakes/RPF-IN-0011-activity-core-release-custody.md deleted file mode 100644 index 957d650..0000000 --- a/intakes/RPF-IN-0011-activity-core-release-custody.md +++ /dev/null @@ -1,48 +0,0 @@ ---- -id: RPF-IN-0011 -kind: intake -type: intake -title: "Prepare dedicated activity-core image-only release principal and custody" -repo: railiance-platform -lane: supervised -status: open -owner: railiance-platform -requested_by: activity-core -origin: handoff -origin_ref: ACTIVITY-WP-0041-T03 -priority: high -created: "2026-10-11" -updated: "2026-10-11" -tags: [security, cross-owner, release, credential-custody] ---- - -Prepare and return an exact owner workplan/task binding for the already D2-authorized -`ACTIVITY-WP-0041-image-only-v1` release scope. This intake is independent of the -RPF48 gate that awaits ACT41 acceptance. It requests owner implementation and native -acceptance, not another founder approval or a secret value. RPF-WP-0036-T03 is -backup freshness work and does not supply this release identity/custody commitment. - -Fixed intended Kubernetes identity is -`system:serviceaccount:activity-core:activity-core-release-broker` on estate -92.205.62.239, cluster UID a553c742-0115-43d4-99a4-a5ca56fe0786. Native read-only -metadata found no dedicated account, only default/activity-core-eso, with no matching -namespace/ArgoCD role bindings. ESO projection custody cannot be borrowed for release. -Platform must choose and independently attest the fixed Git publication identity -and exclusive broker custody, plus distinct build/review/health/retention issuer -principals and Ed25519 key custody/rotation; no local fixture key is acceptance. - -Activity-core source supplies bounded health GET, 60-second durable observer, -private config/credential drift checks, actual Kubernetes username and eleven -allow/deny checks, dedicated Temporal worker, exact one-field Git publication, -targeted non-pruning Argo operations, durable rollback and sanitized audit sink. -See activity-core docs/release-runtime-owner-handoff.md and this repo's - docs/activity-core-release-admission.md for exact boundaries. Keep privileged -transport behind this reviewed broker because Application RBAC and repository-write -credentials alone cannot enforce the one-field scope. - -Return source-bound owner acceptance and actual Git/Kubernetes allow/deny, custody, -revocation, independent signed receipts, continuous observer/ledger, Temporal -registration, normal release and failed-health rollback proof. No native activation -is claimed. If preparation requires attended admin authentication, stage the exact -reviewed custody command first, then place that login in the orange checklist. -Only a genuinely expanded action/access/custody scope goes to the red daily batch. diff --git a/intakes/RPF-IN-0012-task-execution-and-wrapped-delivery-review.md b/intakes/RPF-IN-0012-task-execution-and-wrapped-delivery-review.md deleted file mode 100644 index 04f2772..0000000 --- a/intakes/RPF-IN-0012-task-execution-and-wrapped-delivery-review.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -id: RPF-IN-0012 -kind: intake -type: intake -title: "Return independent task-execution contract and wrapped-delivery owner review" -repo: railiance-platform -status: open -owner: railiance-platform -requested_by: secrets-engine -origin: handoff -origin_ref: SECRETS-WP-0013-T01 -priority: high -created: "2026-10-11" -updated: "2026-10-11" ---- - -Review the proposed SECRETS-EXEC-20261011-v1 metadata-only mediated execution -contract and return the exact Platform owner workplan/task binding and reviewed -revision. Identity/approval/PDP owners still owe independent returns. This intake -records no foreign owner assent and requests no credential value or native action. - -D3 P3=b separately selects response-wrapped issuer delivery. The existing issuer -primitive is hardened in Secrets Engine: native KV-v2/AppRole creation path and -TTL/accessor are verified, a fresh private recipient file is exclusively reserved -and fsynced before mint, and uncertain attempts cannot silently overwrite/remint. -Disposable local OpenBao proves a first unwrap and specific consumed-token denial; -it does not prove estate custody or delivery. Review the exact actor/path/audience, -deadline, authorization/consume, destination, audit, replay/crash reconciliation and -rollback requirements for a separate delivery operation. Metadata execute-ccr -explicitly excludes wrap/KV-value delivery and cannot lend its grant to this lane. - -Source: secrets-engine/docs/task-scoped-openbao-execution-contract.md and -src/secrets_engine/{openbao,wrap}.py; tests/test_wrapped_issuer_native.py is an -isolated test, not a production proof. The already D2-approved repository issuer -has its own prepared bounded command and attended Platform login requirement; -no second repository scope approval is requested and its waiting task is not -reopened by this source review. Owner-prepared execution must precede any new -attended item. Genuinely expanded scope belongs in the single daily red batch. diff --git a/interfaces/RAILIANCE-WP-0024-owner-reviews.json b/interfaces/RAILIANCE-WP-0024-owner-reviews.json index a19c06e..0b7712c 100644 --- a/interfaces/RAILIANCE-WP-0024-owner-reviews.json +++ b/interfaces/RAILIANCE-WP-0024-owner-reviews.json @@ -33,9 +33,7 @@ "docs/audit-core-database-lease-recovery.md", "docs/railiance01-coordinated-reboot.md", "scripts/audit-core-recovery-preflight.py", - "scripts/audit-core-database-lease-recovery.py", - "scripts/audit_core_synthetic_sender_lane.py", - "scripts/t02-attended-window.py" + "scripts/audit-core-database-lease-recovery.py" ], "assertions": [ "The database-lease exercise uses only synthetic load and proves health 200, readiness and requests fail retryably with 503, then recover without changing the audit-core pod UID or restart count.", @@ -58,8 +56,7 @@ "docs/railiance01-coordinated-reboot.md", "scripts/audit-core-recovery-preflight.py", "scripts/audit-core-database-lease-recovery.py", - "docs/audit-core-database-lease-approval.example.json", - "scripts/t02-attended-window.py" + "docs/audit-core-database-lease-approval.example.json" ], "assertions": [ "Lease selection aborts unless exactly one live handle exists below database/creds/audit-core-runtime and its issue time coheres with the ExternalSecret refresh.", @@ -123,10 +120,7 @@ "scripts/audit-core-recovery-preflight.py", "scripts/audit-core-database-lease-recovery.py", "docs/audit-core-database-lease-approval.example.json", - "docs/openbao-reboot-snapshot-receipt.example.json", - "scripts/audit_core_synthetic_sender_lane.py", - "scripts/t02-attended-window.py", - "scripts/openbao-reboot-snapshot.py" + "docs/openbao-reboot-snapshot-receipt.example.json" ], "assertions": [ "T02 remains gated on a named window, abort operator, exact owner receipts and a revision-pinned synthetic-load driver; repository access alone cannot revoke a lease.", @@ -142,14 +136,11 @@ } }, "artifact_sha256": { - "docs/audit-core-database-lease-approval.example.json": "87e57e3290074508c6517938acf8c7fc1a75d977b8ff630f247a4ff5d2ee45c6", - "docs/audit-core-database-lease-recovery.md": "8dfd3fb59c551df0346d2569674227bb2f4e763c3bf058453035591073e20794", + "docs/audit-core-database-lease-approval.example.json": "360d49d0bea7ca376405974403036bff080533728f133d237f30f4ee3bb943e0", + "docs/audit-core-database-lease-recovery.md": "5aa8f2723d7216d775990e0b2cd770290284b62d185526a9dffe7d6a4f925d95", "docs/openbao-reboot-snapshot-receipt.example.json": "b95903a3e1aacb607eecc3f0440f08e62480d09da47bd7ab96c3eb8814f2eaee", - "docs/railiance01-coordinated-reboot.md": "975bd3167cd1c3da152fafc8bcf882fb15dade92a89f60e82495cf34201fc81b", - "scripts/audit-core-database-lease-recovery.py": "332405cd6fce36d3df5588e7622065e3655eea0e4256766eb07c743349f29b17", - "scripts/audit-core-recovery-preflight.py": "d870d7763fabb9d01f72c157f82419257fd1bf9c2705e1661a98022b1caa29ba", - "scripts/audit_core_synthetic_sender_lane.py": "8a9c40cfdfd5cfe276bbf3ab79ecdf8e5f18cb85a2811423f54fa3808a4c57a4", - "scripts/t02-attended-window.py": "c4531d2daf6b2658cf8e94c9761d9cfe6333cca646b1d1692103945144fd775d", - "scripts/openbao-reboot-snapshot.py": "1370a40b75cfd89fe9cc60a9ba064414e6baa57349738fbb8bb26bc8804d96d8" + "docs/railiance01-coordinated-reboot.md": "9969c2f498587710154e8d9a650fb8a13159e9e7b99918291dbdf577b131b7e7", + "scripts/audit-core-database-lease-recovery.py": "c87fb026c527fc17116067c5742d324340707ce4aecf440d0d4e618db9b872a1", + "scripts/audit-core-recovery-preflight.py": "802f73fa4352f64bc0972d813e3dd874f3ec2f428e162b23158c9d8d6fd8e710" } } diff --git a/manifests/rapp-qonto-audit-sender.yaml b/manifests/rapp-qonto-audit-sender.yaml deleted file mode 100644 index c69b5e1..0000000 --- a/manifests/rapp-qonto-audit-sender.yaml +++ /dev/null @@ -1,57 +0,0 @@ -# CCR-2026-0047 approved explicitly 2026-10-15; native gates still apply. -apiVersion: external-secrets.io/v1 -kind: ClusterSecretStore -metadata: - name: openbao-rapp-qonto-audit - annotations: - railiance.io/credential-change: CCR-2026-0047 - railiance.io/admission: approved -spec: - provider: - vault: - server: http://openbao.openbao.svc:8200 - path: platform - version: v2 - auth: - kubernetes: - mountPath: kubernetes - role: external-secrets-rapp-qonto-audit - serviceAccountRef: - name: external-secrets - namespace: external-secrets - conditions: - - namespaces: - - rapp-qonto ---- -apiVersion: external-secrets.io/v1 -kind: ExternalSecret -metadata: - name: rapp-qonto-audit-sender - namespace: rapp-qonto - annotations: - railiance.io/credential-change: CCR-2026-0047 - railiance.io/admission: approved -spec: - refreshInterval: 5m - secretStoreRef: - kind: ClusterSecretStore - name: openbao-rapp-qonto-audit - target: - template: - metadata: - annotations: - railiance.io/credential-change: CCR-2026-0047 - railiance.io/admission: approved - engineVersion: v2 - mergePolicy: Replace - name: rapp-qonto-audit-sender - creationPolicy: Owner - deletionPolicy: Retain - data: - - secretKey: bearer - remoteRef: - key: workloads/rapp-qonto/audit-sender - property: AUDIT_TOKEN - conversionStrategy: Default - decodingStrategy: None - metadataPolicy: None diff --git a/manifests/templates/keycape-issuer-eso.yaml b/manifests/templates/keycape-issuer-eso.yaml deleted file mode 100644 index 406a29c..0000000 --- a/manifests/templates/keycape-issuer-eso.yaml +++ /dev/null @@ -1,46 +0,0 @@ -# PROPOSED SOURCE ONLY — RPF-1.3-T4 / NK48-R5-F2; not admitted or applied. -# Platform controls this template. Only admitted client_id is substituted. -# Provision the store through Platform's separate reviewed owner path; the -# platform-secret-delivery lane applies ONLY the ExternalSecret document. -apiVersion: external-secrets.io/v1 -kind: ClusterSecretStore -metadata: - name: openbao-keycape-{client_id}-issuer -spec: - provider: - vault: - server: http://openbao.openbao.svc:8200 - path: platform - version: v2 - auth: - kubernetes: - mountPath: kubernetes - role: external-secrets-keycape-{client_id} - serviceAccountRef: - name: external-secrets - namespace: external-secrets - audiences: - - openbao - conditions: - - namespaces: - - sso ---- -apiVersion: external-secrets.io/v1 -kind: ExternalSecret -metadata: - name: keycape-{client_id}-client - namespace: sso -spec: - refreshInterval: 5m - secretStoreRef: - kind: ClusterSecretStore - name: openbao-keycape-{client_id}-issuer - target: - name: keycape-{client_id}-client - creationPolicy: Owner - deletionPolicy: Retain - data: - - secretKey: client-secret - remoteRef: - key: workloads/keycape/{client_id} - property: CLIENT_SECRET diff --git a/openbao/auth/coding-agent-jwt-role.json b/openbao/auth/coding-agent-jwt-role.json index f4343c5..8fa6147 100644 --- a/openbao/auth/coding-agent-jwt-role.json +++ b/openbao/auth/coding-agent-jwt-role.json @@ -7,7 +7,7 @@ "bound_claims_type": "string", "bound_claims": { "sub": "service:codex:railiance-platform", - "principal_type": ["service", "agent"], + "principal_type": "service", "tenant": "tenant:platform", "roles": "coding-agent" }, diff --git a/openbao/auth/credential-broker-keycape-client-secret-generate-oidc-role.json b/openbao/auth/credential-broker-keycape-client-secret-generate-oidc-role.json deleted file mode 100644 index 1e53aa4..0000000 --- a/openbao/auth/credential-broker-keycape-client-secret-generate-oidc-role.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "role_type": "oidc", - "allowed_redirect_uris": [ - "http://localhost:8250/oidc/callback", - "http://127.0.0.1:8250/oidc/callback" - ], - "oidc_scopes": ["openid", "profile", "email", "groups"], - "user_claim": "sub", - "groups_claim": "groups", - "bound_claims_type": "string", - "bound_claims": { - "groups": ["net-kingdom-admins"], - "/assurance/level": "aal2" - }, - "token_policies": ["credential-broker-keycape-client-secret-generate-issuer"], - "token_no_default_policy": true, - "token_type": "service", - "token_ttl": "15m", - "token_max_ttl": "15m", - "token_explicit_max_ttl": "15m", - "token_num_uses": 0, - "verbose_oidc_logging": false -} diff --git a/openbao/auth/fluid-telegram-hall-of-helix-oidc-role.json b/openbao/auth/fluid-telegram-hall-of-helix-oidc-role.json deleted file mode 100644 index b9d301b..0000000 --- a/openbao/auth/fluid-telegram-hall-of-helix-oidc-role.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "role_type": "oidc", - "allowed_redirect_uris": [ - "http://localhost:8250/oidc/callback", - "http://127.0.0.1:8250/oidc/callback", - "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback" - ], - "oidc_scopes": ["openid", "profile", "email", "groups"], - "user_claim": "sub", - "groups_claim": "groups", - "bound_claims_type": "string", - "bound_claims": { - "groups": ["fluid-telegram-operators"], - "/assurance/level": "aal2" - }, - "token_policies": ["workload-kv-fluid-telegram-hall-of-helix-operator"], - "token_no_default_policy": true, - "token_type": "service", - "token_ttl": "15m", - "token_max_ttl": "30m", - "token_explicit_max_ttl": "30m", - "token_num_uses": 0, - "verbose_oidc_logging": false -} diff --git a/openbao/auth/netkingdom-platform-admin-role.json b/openbao/auth/netkingdom-platform-admin-role.json index 54e9fc2..103061a 100644 --- a/openbao/auth/netkingdom-platform-admin-role.json +++ b/openbao/auth/netkingdom-platform-admin-role.json @@ -8,13 +8,11 @@ "bound_claims": { "groups": [ "net-kingdom-admins" - ], - "/assurance/level": "aal2" + ] }, "bound_claims_type": "string", "bound_subject": "", "claim_mappings": { - "/assurance/level": "assurance_level", "email": "email", "preferred_username": "username" }, diff --git a/openbao/backup-primary-serviceaccounts.yaml b/openbao/backup-primary-serviceaccounts.yaml deleted file mode 100644 index 3e45c2c..0000000 --- a/openbao/backup-primary-serviceaccounts.yaml +++ /dev/null @@ -1,30 +0,0 @@ -{ - "apiVersion": "v1", - "kind": "List", - "items": [ - { - "apiVersion": "v1", - "kind": "ServiceAccount", - "metadata": { - "name": "backup-primary-delivery", - "namespace": "coulomb-social", - "labels": { - "app.kubernetes.io/managed-by": "railiance-platform" - } - }, - "automountServiceAccountToken": false - }, - { - "apiVersion": "v1", - "kind": "ServiceAccount", - "metadata": { - "name": "backup-primary-delivery", - "namespace": "email-connect", - "labels": { - "app.kubernetes.io/managed-by": "railiance-platform" - } - }, - "automountServiceAccountToken": false - } - ] -} diff --git a/openbao/email-connect-platform-smtp/foundation.json b/openbao/email-connect-platform-smtp/foundation.json deleted file mode 100644 index d5ad112..0000000 --- a/openbao/email-connect-platform-smtp/foundation.json +++ /dev/null @@ -1,84 +0,0 @@ -{ - "schema": "platform.email-connect-smtp-foundation.v1", - "cluster_uid": "a553c742-0115-43d4-99a4-a5ca56fe0786", - "namespace_uid": "5678c5d7-e4f5-4a62-8f24-1c6e1d906319", - "deployment_uid": "2e98e52a-a2d1-4c52-9ac8-6efbc0a18e72", - "deployment_generation": 74, - "resources": [ - { - "apiVersion": "v1", - "automountServiceAccountToken": false, - "kind": "ServiceAccount", - "metadata": { - "name": "email-connect-platform-smtp-eso", - "namespace": "email-connect" - } - }, - { - "apiVersion": "external-secrets.io/v1", - "kind": "SecretStore", - "metadata": { - "name": "openbao-email-connect-platform-smtp", - "namespace": "email-connect" - }, - "spec": { - "provider": { - "vault": { - "auth": { - "kubernetes": { - "mountPath": "kubernetes", - "role": "email-connect-platform-smtp-eso", - "serviceAccountRef": { - "audiences": [ - "openbao" - ], - "name": "email-connect-platform-smtp-eso" - } - } - }, - "path": "platform", - "server": "http://openbao.openbao.svc:8200", - "version": "v2" - } - } - } - }, - { - "apiVersion": "external-secrets.io/v1", - "kind": "ExternalSecret", - "metadata": { - "name": "email-connect-platform-smtp", - "namespace": "email-connect" - }, - "spec": { - "data": [ - { - "remoteRef": { - "conversionStrategy": "Default", - "decodingStrategy": "None", - "key": "workloads/railiance-telemetry/smtp", - "metadataPolicy": "None", - "property": "SMTP_PASSWORD" - }, - "secretKey": "password" - } - ], - "refreshInterval": "1h", - "secretStoreRef": { - "kind": "SecretStore", - "name": "openbao-email-connect-platform-smtp" - }, - "target": { - "creationPolicy": "Owner", - "deletionPolicy": "Retain", - "name": "email-connect-platform-smtp", - "template": { - "engineVersion": "v2", - "mergePolicy": "Replace", - "type": "Opaque" - } - } - } - } - ] -} diff --git a/openbao/fluid-telegram/agent-deny-overlay.hcl b/openbao/fluid-telegram/agent-deny-overlay.hcl deleted file mode 100644 index 42095ee..0000000 --- a/openbao/fluid-telegram/agent-deny-overlay.hcl +++ /dev/null @@ -1,9 +0,0 @@ -# CCR-2026-0039: coding-agent denies for platform/workloads/coulomb/fluid-telegram/hall-of-helix (data and metadata). -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-app" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/operator-app" { capabilities = ["deny"] } -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session" { capabilities = ["deny"] } -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/bot-token" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/bot-token" { capabilities = ["deny"] } -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/redaction-salt" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/redaction-salt" { capabilities = ["deny"] } diff --git a/openbao/policies/agent-high-risk-boundary.hcl b/openbao/policies/agent-high-risk-boundary.hcl index 42251e7..eb3eb85 100644 --- a/openbao/policies/agent-high-risk-boundary.hcl +++ b/openbao/policies/agent-high-risk-boundary.hcl @@ -143,19 +143,3 @@ path "platform/data/workloads/approval-engine/audit-sender" { capabilities = ["d path "platform/metadata/workloads/approval-engine/audit-sender" { capabilities = ["deny"] } path "platform/data/workloads/informed-decision/audit-sender" { capabilities = ["deny"] } path "platform/metadata/workloads/informed-decision/audit-sender" { capabilities = ["deny"] } - -# CCR-2026-0039: coding-agent denies for platform/workloads/coulomb/fluid-telegram/hall-of-helix (data and metadata). -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-app" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/operator-app" { capabilities = ["deny"] } -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session" { capabilities = ["deny"] } -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/bot-token" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/bot-token" { capabilities = ["deny"] } -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/redaction-salt" { capabilities = ["deny"] } -path "platform/metadata/workloads/coulomb/fluid-telegram/hall-of-helix/redaction-salt" { capabilities = ["deny"] } - -# NK-WP-0049 / CCR-2026-0048 and0049: owner-only backup publisher/retention. -path "platform/data/workloads/net-kingdom/identity-backup/publisher" { capabilities = ["deny"] } -path "platform/metadata/workloads/net-kingdom/identity-backup/publisher" { capabilities = ["deny"] } -path "platform/data/workloads/net-kingdom/identity-backup/retention" { capabilities = ["deny"] } -path "platform/metadata/workloads/net-kingdom/identity-backup/retention" { capabilities = ["deny"] } diff --git a/openbao/policies/backup-pair-writer.hcl b/openbao/policies/backup-pair-writer.hcl deleted file mode 100644 index 381c849..0000000 --- a/openbao/policies/backup-pair-writer.hcl +++ /dev/null @@ -1,28 +0,0 @@ -# Attended writer for the two application-archive publisher pairs. -# Founder admission 2026-10-08. CAS=0 and one-write behavior are enforced by -# scripts/application_backup_pair_custody.py. This policy cannot delete, -# list, or read secret values. - -path "platform/data/workloads/coulomb-social/backup-primary" { - capabilities = ["create", "update"] -} - -path "platform/data/workloads/email-connect/backup-primary" { - capabilities = ["create", "update"] -} - -path "platform/metadata/workloads/coulomb-social/backup-primary" { - capabilities = ["read"] -} - -path "platform/metadata/workloads/email-connect/backup-primary" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/credential-broker-keycape-client-secret-generate-issuer.hcl b/openbao/policies/credential-broker-keycape-client-secret-generate-issuer.hcl deleted file mode 100644 index 9a18506..0000000 --- a/openbao/policies/credential-broker-keycape-client-secret-generate-issuer.hcl +++ /dev/null @@ -1,20 +0,0 @@ -# CCR-2026-0040 (RPF-WP-0054): the credential broker may mint keycape/client-secret-generate -# tokens only through this token role. Bind to the reviewed caller identity only. -path "auth/token/create/keycape-client-secret-generate" { - capabilities = ["create", "update"] -} -path "auth/token/lookup-accessor" { - capabilities = ["update"] -} -path "auth/token/revoke-accessor" { - capabilities = ["update"] -} -path "auth/token/lookup-self" { - capabilities = ["read"] -} -path "auth/token/revoke-self" { - capabilities = ["update"] -} -path "sys/capabilities-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/email-connect-platform-smtp-eso.hcl b/openbao/policies/email-connect-platform-smtp-eso.hcl deleted file mode 100644 index 8b72607..0000000 --- a/openbao/policies/email-connect-platform-smtp-eso.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/railiance-telemetry/smtp" { capabilities = ["read"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } diff --git a/openbao/policies/keycape-client-secret-generate.hcl b/openbao/policies/keycape-client-secret-generate.hcl deleted file mode 100644 index 6dbb5be..0000000 --- a/openbao/policies/keycape-client-secret-generate.hcl +++ /dev/null @@ -1,17 +0,0 @@ -# CCR-2026-0040 (RPF-WP-0054): grant keycape/client-secret-generate. Create-only: no update, -# data read, metadata write, list or delete. Exact deny beats the + glob. -path "platform/data/workloads/keycape/+" { - capabilities = ["create"] -} -path "platform/metadata/workloads/keycape/+" { - capabilities = ["read"] -} -path "platform/data/workloads/keycape/secrets-engine-openbao" { - capabilities = ["deny"] -} -path "platform/metadata/workloads/keycape/secrets-engine-openbao" { - capabilities = ["deny"] -} -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/net-kingdom-backup-publisher-eso.hcl b/openbao/policies/net-kingdom-backup-publisher-eso.hcl deleted file mode 100644 index 3c01c2a..0000000 --- a/openbao/policies/net-kingdom-backup-publisher-eso.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/net-kingdom/identity-backup/publisher" { capabilities = ["read"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } diff --git a/openbao/policies/net-kingdom-backup-retention-eso.hcl b/openbao/policies/net-kingdom-backup-retention-eso.hcl deleted file mode 100644 index 9c348e3..0000000 --- a/openbao/policies/net-kingdom-backup-retention-eso.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/net-kingdom/identity-backup/retention" { capabilities = ["read"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } diff --git a/openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl b/openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl deleted file mode 100644 index 4c55677..0000000 --- a/openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl +++ /dev/null @@ -1,15 +0,0 @@ -path "platform/data/workloads/net-kingdom/directory-binds/svc-authelia" { - capabilities = ["read", "update"] - allowed_parameters = { - "data" = [] - "options" = [] - } -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/net-kingdom-bind-executor-syn-journey-read.hcl b/openbao/policies/net-kingdom-bind-executor-syn-journey-read.hcl deleted file mode 100644 index 173eaa9..0000000 --- a/openbao/policies/net-kingdom-bind-executor-syn-journey-read.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/net-kingdom/synthetic-journey/syn-journey" { capabilities = ["read"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } diff --git a/openbao/policies/net-kingdom-svc-authelia-bind-eso.hcl b/openbao/policies/net-kingdom-svc-authelia-bind-eso.hcl deleted file mode 100644 index 538a860..0000000 --- a/openbao/policies/net-kingdom-svc-authelia-bind-eso.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/net-kingdom/directory-binds/svc-authelia" { capabilities = ["read"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } diff --git a/openbao/policies/net-kingdom-svc-keycape-bind-eso.hcl b/openbao/policies/net-kingdom-svc-keycape-bind-eso.hcl deleted file mode 100644 index 801fd32..0000000 --- a/openbao/policies/net-kingdom-svc-keycape-bind-eso.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/net-kingdom/directory-binds/svc-keycape" { capabilities = ["read"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } diff --git a/openbao/policies/net-kingdom-svc-provisioner-bind-eso.hcl b/openbao/policies/net-kingdom-svc-provisioner-bind-eso.hcl deleted file mode 100644 index 5a07c17..0000000 --- a/openbao/policies/net-kingdom-svc-provisioner-bind-eso.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/net-kingdom/directory-binds/svc-provisioner" { capabilities = ["read"] } -path "auth/token/lookup-self" { capabilities = ["read"] } -path "auth/token/revoke-self" { capabilities = ["update"] } diff --git a/openbao/policies/secrets-engine-login-self.hcl b/openbao/policies/secrets-engine-login-self.hcl index 3ed212c..c9df710 100644 --- a/openbao/policies/secrets-engine-login-self.hcl +++ b/openbao/policies/secrets-engine-login-self.hcl @@ -1,4 +1,4 @@ -# CCR-2026-0038 (RPF-WP-0035-T02): login-only service policy; not a lane applier. +# RPF-WP-0035-T02: proposed login-only service policy; not a lane applier. path "auth/token/lookup-self" { capabilities = ["read"] } @@ -8,9 +8,3 @@ path "sys/capabilities-self" { path "auth/token/revoke-self" { capabilities = ["update"] } -path "platform/data/workloads/keycape/secrets-engine-openbao" { - capabilities = ["deny"] -} -path "platform/metadata/workloads/keycape/secrets-engine-openbao" { - capabilities = ["deny"] -} diff --git a/openbao/policies/workload-kv-fluid-telegram-hall-of-helix-operator.hcl b/openbao/policies/workload-kv-fluid-telegram-hall-of-helix-operator.hcl deleted file mode 100644 index 19db1a7..0000000 --- a/openbao/policies/workload-kv-fluid-telegram-hall-of-helix-operator.hcl +++ /dev/null @@ -1,16 +0,0 @@ -# CCR-2026-0039 (RPF-WP-0035-T03): exact per-entry operator matrix; no metadata, list or delete. -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-app" { - capabilities = ["read"] -} -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session" { - capabilities = ["create", "read", "update"] -} -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/bot-token" { - capabilities = ["create", "read", "update"] -} -path "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/redaction-salt" { - capabilities = ["create", "read"] -} -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-coulomb-social-backup-primary.hcl b/openbao/policies/workload-kv-read-coulomb-social-backup-primary.hcl deleted file mode 100644 index eba7155..0000000 --- a/openbao/policies/workload-kv-read-coulomb-social-backup-primary.hcl +++ /dev/null @@ -1,11 +0,0 @@ -path "platform/data/workloads/coulomb-social/backup-primary" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-email-connect-backup-primary.hcl b/openbao/policies/workload-kv-read-email-connect-backup-primary.hcl deleted file mode 100644 index 383c2af..0000000 --- a/openbao/policies/workload-kv-read-email-connect-backup-primary.hcl +++ /dev/null @@ -1,11 +0,0 @@ -path "platform/data/workloads/email-connect/backup-primary" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl b/openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl deleted file mode 100644 index 57425e7..0000000 --- a/openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl +++ /dev/null @@ -1,11 +0,0 @@ -path "platform/data/workloads/flex-auth/envelope-signing" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-keycape-forgejo-eso.hcl b/openbao/policies/workload-kv-read-keycape-forgejo-eso.hcl deleted file mode 100644 index a02a426..0000000 --- a/openbao/policies/workload-kv-read-keycape-forgejo-eso.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# workload-kv-read-keycape-forgejo-eso: CCR-2026-0041, from ops-mason SecurityCase forgejo-netkingdom-oidc -# (oidc-browser-relying-party@1.0, render sha256:fb8b114d...). Platform-generated from the CCR: -# the case render omitted the token self-lifecycle stanzas that token_self_lifecycle requires. - -path "platform/data/workloads/keycape/forgejo" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-keycape-forgejo-forgejo.hcl b/openbao/policies/workload-kv-read-keycape-forgejo-forgejo.hcl deleted file mode 100644 index 8a2478d..0000000 --- a/openbao/policies/workload-kv-read-keycape-forgejo-forgejo.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# workload-kv-read-keycape-forgejo-forgejo: CCR-2026-0042, from ops-mason SecurityCase forgejo-netkingdom-oidc -# (oidc-browser-relying-party@1.0, render sha256:fb8b114d...). Platform-generated from the CCR: -# the case render omitted the token self-lifecycle stanzas that token_self_lifecycle requires. - -path "platform/data/workloads/keycape/forgejo" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor.hcl b/openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor.hcl deleted file mode 100644 index 870d909..0000000 --- a/openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# workload-kv-read-keycape-nk-bind-executor-approval-bounded-executor: CCR-2026-0061 (proposed), -# SecurityCase nk-bind-executor-approval (oidc-service-client@0.1, proposed), consumer reader for -# sso/nk-bind-executor. Platform-generated from the CCR. Source only; not applied. - -path "platform/data/workloads/keycape/nk-bind-executor-approval" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-eso.hcl b/openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-eso.hcl deleted file mode 100644 index a1c80ff..0000000 --- a/openbao/policies/workload-kv-read-keycape-nk-bind-executor-approval-eso.hcl +++ /dev/null @@ -1,15 +0,0 @@ -# workload-kv-read-keycape-nk-bind-executor-approval-eso: CCR-2026-0060 (proposed), SecurityCase -# nk-bind-executor-approval (oidc-service-client@0.1, proposed). Platform-generated from the CCR -# (token_self_lifecycle true, as CCR-2026-0041). Source only; not applied. - -path "platform/data/workloads/keycape/nk-bind-executor-approval" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-eso.hcl b/openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-eso.hcl index 64df36f..b07a283 100644 --- a/openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-eso.hcl +++ b/openbao/policies/workload-kv-read-keycape-secrets-engine-openbao-eso.hcl @@ -1,12 +1,3 @@ path "platform/data/workloads/keycape/secrets-engine-openbao" { capabilities = ["read"] } - -# ESO validates its own short-lived workload session. -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-mas-tem-relay.hcl b/openbao/policies/workload-kv-read-mas-tem-relay.hcl deleted file mode 100644 index bc3fcbe..0000000 --- a/openbao/policies/workload-kv-read-mas-tem-relay.hcl +++ /dev/null @@ -1,11 +0,0 @@ -path "platform/data/suppliers/scaleway/mas-tem-relay" { - capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} diff --git a/openbao/policies/workload-kv-read-rapp-qonto-audit.hcl b/openbao/policies/workload-kv-read-rapp-qonto-audit.hcl deleted file mode 100644 index f440116..0000000 --- a/openbao/policies/workload-kv-read-rapp-qonto-audit.hcl +++ /dev/null @@ -1,3 +0,0 @@ -path "platform/data/workloads/rapp-qonto/audit-sender" { - capabilities = ["read"] -} diff --git a/openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl b/openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl index ebbb1fc..93739c6 100644 --- a/openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl +++ b/openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl @@ -9,8 +9,3 @@ path "auth/token/lookup-self" { path "auth/token/revoke-self" { capabilities = ["update"] } - -# CCR-2026-0046 proposed; exact extra data path, no metadata/list grants. -path "platform/data/workloads/reuse/reuse-surface/forgejo-source-read" { - capabilities = ["read"] -} diff --git a/openbao/policies/workload-kv-read-reuse-surface-runtime.hcl b/openbao/policies/workload-kv-read-reuse-surface-runtime.hcl index c042392..1427c76 100644 --- a/openbao/policies/workload-kv-read-reuse-surface-runtime.hcl +++ b/openbao/policies/workload-kv-read-reuse-surface-runtime.hcl @@ -1,24 +1,7 @@ -# CCR-2026-0044 adds lookup-self and revoke-self for the dedicated caller. -# CCR-2026-0045 (NK-WP-0051-T06) adds capabilities-self so the caller proves -# its own reach without an operator or root token. -# Do not attach this policy to entity 4a1f663c-0621-0483-edd0-f852274f4b6b. - path "platform/data/workloads/reuse/reuse-surface/runtime-secrets" { capabilities = ["read"] } path "platform/metadata/workloads/reuse/reuse-surface/runtime-secrets" { capabilities = ["read"] -} - -path "auth/token/lookup-self" { - capabilities = ["read"] -} - -path "auth/token/revoke-self" { - capabilities = ["update"] -} - -path "sys/capabilities-self" { - capabilities = ["update"] -} +} \ No newline at end of file diff --git a/openbao/ssh/roles-spec.yaml b/openbao/ssh/roles-spec.yaml index 0523143..1ccb34f 100644 --- a/openbao/ssh/roles-spec.yaml +++ b/openbao/ssh/roles-spec.yaml @@ -18,8 +18,6 @@ roles: allow_user_certificates: true allow_user_key_ids: true default_user: agt - # CUST-WP-0073 / WARDEN-WP-0045: agt-custodian requests permit-pty explicitly. - allowed_extensions: "permit-pty" ttl: 24h max_ttl: 24h atm-role: diff --git a/registry/capabilities/capability.railiance.platform-services.md b/registry/capabilities/capability.railiance.platform-services.md index f0d04ad..905bdbe 100644 --- a/registry/capabilities/capability.railiance.platform-services.md +++ b/registry/capabilities/capability.railiance.platform-services.md @@ -1,8 +1,8 @@ --- id: capability.railiance.platform-services -name: Secret custody and platform data services -summary: OpenBao policy/custody, consumer grants, secret delivery and database/backup - contracts. +name: Railiance Platform Services +summary: 'S3 Platform Services layer of the Railiance OAS Stack — shared cluster services: PostgreSQL + HA, Valkey cache, secret management, identity integration, and object storage.' owner: railiance-platform status: draft domain: financials @@ -11,25 +11,19 @@ tags: - platform - secrets - postgres -- security -- netkingdom -- framework-reuse maturity: discovery: current: D3 target: D4 confidence: medium - rationale: SCOPE.md places this repo precisely at S3 in the Railiance OAS Stack, - scoped to shared cluster services (PostgreSQL HA, Valkey, secrets, identity - integration, object storage). + rationale: SCOPE.md places this repo precisely at S3 in the Railiance OAS Stack, scoped to shared + cluster services (PostgreSQL HA, Valkey, secrets, identity integration, object storage). availability: current: A2 target: A3 confidence: medium - rationale: 'Source interface assessed 2026-10-14: OpenBao policy/custody, consumer - grants, secret delivery and database/backup contracts. Availability describes - the delivery form, not production acceptance. Package ownership is split to - rapp repos; caches and object services are demand-led.' + rationale: No top-level package manifest, but ships real scripts/ (OpenBao/Vault apply/verify, credential-change + Python tooling) and tools/; has `.forgejo/` CI already in place. external_evidence: completeness: level: C1 @@ -47,46 +41,35 @@ external_evidence: known_reliability_risks: - no top-level packaging; scripts/tools have no unified install path discovery: - intent: OpenBao policy/custody, consumer grants, secret delivery and database/backup - contracts + intent: Own the shared platform services layer (Postgres HA, Valkey, secrets, identity integration, + object storage) that application and enablement layers depend on, without owning the underlying host/cluster + substrate. includes: - - OpenBao policy/custody, consumer grants, secret delivery and database/backup contracts + - PostgreSQL HA and Valkey cache service management + - secret management (OpenBao/Vault) apply/verify tooling + - identity integration and object storage excludes: - - Package ownership is split to rapp repos; caches and object services are demand-led + - host/cluster substrate provisioning (see railiance-infra, railiance-cluster) + - application workload deployment (see railiance-apps) assumptions: [] - use_cases: - - 'U04: Protected operation obtains and consumes exact human approval before its - side effect' - - 'U05: Operator provisions and delivers a scoped workload credential through the - approved owner lane' - - 'U07: Owner inventories, rotates, revokes and detects exposure of credentials' - - 'U10: Operator recovers identity, custody or data with independently held recovery - material' - - 'U13: Platform owner deploys and rolls back a workload with explicit substrate - and custody bindings' + use_cases: [] research_memos: [] availability: current_level: A2 target_level: A3 current_artifacts: - - SCOPE.md - - docs/openbao.md + - scripts/ (OpenBao/Vault, credential-change tooling) + - tools/ target_artifacts: [] consumption_modes: - - CLI - - source + - cli (shell/python scripts) relations: depends_on: [] supports: [] - related_to: - - capability.railiance.openbao-package - - capability.railiance.postgres-isolation-recovery - - capability.iam.key-cape - - capability.security.credential-lifecycle + related_to: [] evidence: documentation: - SCOPE.md - - docs/openbao.md tests: - tests/ consumer_feedback: [] @@ -94,42 +77,46 @@ evidence: incidents: [] consumer_guidance: recommended_for: - - Protected operation obtains and consumes exact human approval before its side - effect - - Operator provisions and delivers a scoped workload credential through the approved - owner lane - - Owner inventories, rotates, revokes and detects exposure of credentials - - Operator recovers identity, custody or data with independently held recovery material - - Platform owner deploys and rolls back a workload with explicit substrate and custody - bindings + - Railiance operators managing shared platform services (Postgres, Valkey, secrets, identity, storage) not_recommended_for: - - Package ownership is split to rapp repos; caches and object services are demand-led + - needs at the host/cluster substrate layer (see railiance-infra/-cluster) or application layer (see + railiance-apps) known_limitations: - - Package ownership is split to rapp repos; caches and object services are demand-led - - 2026-10-14 architecture review estimates operational maturity separately; a registry - entry does not authorize execution or establish deployment. + - no unified top-level packaging across scripts/tools promotion_history: [] --- -# Secret custody and platform data services +# Railiance Platform Services -OpenBao policy/custody, consumer grants, secret delivery and database/backup contracts. +## Overview -## Reuse boundary +`railiance-platform` is the S3 Platform Services layer of the Railiance OAS Stack: shared cluster services including PostgreSQL HA, Valkey cache, OpenBao/Vault secret management, identity integration, and object storage, consumed by application and enablement layers above it. -Package ownership is split to rapp repos; caches and object services are demand-led. +## Assessment notes -## Supported use cases +### Discovery -- U04: Protected operation obtains and consumes exact human approval before its side effect -- U05: Operator provisions and delivers a scoped workload credential through the approved owner lane -- U07: Owner inventories, rotates, revokes and detects exposure of credentials -- U10: Operator recovers identity, custody or data with independently held recovery material -- U13: Platform owner deploys and rolls back a workload with explicit substrate and custody bindings +SCOPE.md places this repo precisely at S3 in the Railiance OAS Stack, scoped to shared cluster services (PostgreSQL HA, Valkey, secrets, identity integration, object storage). -## Evidence and integration +### Availability -- [SCOPE.md](../../SCOPE.md) -- [docs/openbao.md](../../docs/openbao.md) +No top-level package manifest, but ships real scripts/ (OpenBao/Vault apply/verify, credential-change Python tooling) and tools/; has `.forgejo/` CI already in place. -Assessment: CUST-WP-0081, 2026-10-14. Relations identify interfaces to compose; they do not certify native integration. Existing external evidence levels are retained where present. New entries use conservative C1/R0 until consumer assessment exists. +### Completeness + +First-pass honest assessment from the REUSE-WP-0017 coverage campaign +(reuse-surface). No external consumer feedback exists yet; levels reflect +scope-vs-intent documentation quality, not internal code quality. + +### Reliability + +No production consumer telemetry exists yet; reliability level is +intentionally conservative pending REUSE-WP-0019 reuse-telemetry evidence. + +## Promotion checklist + +- [x] ID follows `capability..` pattern +- [x] Maturity enums match `specs/CapabilityMaturityStandard.md` +- [x] `external_evidence` is populated separately from `maturity` +- [ ] Relations reference valid capability IDs (none yet) +- [x] Index entry added in `registry/indexes/capabilities.yaml` diff --git a/registry/ccr-standard-profile.yaml b/registry/ccr-standard-profile.yaml deleted file mode 100644 index 7b9edd7..0000000 --- a/registry/ccr-standard-profile.yaml +++ /dev/null @@ -1,76 +0,0 @@ -# Standard lane profile for self-service CCR intake (RPF-WP-0061-T01). -# -# A CCR that satisfies every rule below receives the railiance-platform-owner -# review from `credential-change.py review-standard` without a platform worker -# session. Anything else is routed to the railiance-platform inbox. Changes to -# this file are platform-owner review changes: the profile digest is recorded -# in every review comment it produces. -# -# The profile never grants approval. Founder approval, confirm-binding against -# native state and the applier boundary are unchanged. -profile: ccr-standard-lane -version: 1 - -# Identities allowed to file CCRs with `credential-change.py submit`. -submitters: -- ops-mason - -request_type: workload-kv-read -mount: platform -kv_path_patterns: -- '^platform/workloads/[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*(/[a-z0-9][a-z0-9-]*)?$' -- '^platform/suppliers/[a-z0-9][a-z0-9-]*/[a-z0-9][a-z0-9-]*$' -policy_name_prefix: workload-kv-read- -# The policy file must equal the generated HCL: data read, plus -# lookup-self/revoke-self when token_self_lifecycle is set. -metadata_read: false -token_capabilities_self: false - -auth: - method: kubernetes - mount: kubernetes - role_prefix: external-secrets- - audience: openbao - max_ttl_seconds: 900 - token_no_default_policy: true - service_accounts: 1 - namespaces: 1 - forbidden_namespaces: - - argocd - - cert-manager - - default - - external-secrets - - kube-public - - kube-system - - openbao - -allowed_risk_classifications: -- low -- medium - -# A second reader of an existing path is a platform judgement. -reader_conflict_statuses: -- draft -- in_flight -- proposed -- needs_changes -- approved -- apply_pending -- applied -- verified -- active -- rotated - -# Calibration against all 51 CCRs on main at 2026-10-18 (version 1, status -# ignored): only CCR-2026-0051 passes. First failing rule for the others: -# metadata_read set ............ 0001-0004, 0006, 0010-0014, 0017-0020, -# 0023-0027, 0029-0031, 0035, 0044-0046 -# non-standard role/policy name 0015, 0016, 0032, 0036, 0037, 0048, 0049 -# audience not openbao ......... 0021, 0022, 0047 -# central ESO SA namespace ..... 0033, 0041 (external-secrets/external-secrets) -# not kubernetes / not platform 0007-0009, 0034, 0042 -# policy file drift ............ 0005 -# risk high .................... 0050 -# other request types .......... 0028, 0038-0040, 0043 -# The profile is deliberately stricter than past practice: older shapes need a -# platform review instead of silently passing. diff --git a/registry/indexes/capabilities.yaml b/registry/indexes/capabilities.yaml index c0c1c73..3c28091 100644 --- a/registry/indexes/capabilities.yaml +++ b/registry/indexes/capabilities.yaml @@ -1,24 +1,20 @@ version: 1 -updated: '2026-10-14' +updated: '2026-07-06' domain: helix_forge capabilities: - id: capability.railiance.platform-services - name: Secret custody and platform data services - summary: OpenBao policy/custody, consumer grants, secret delivery and database/backup - contracts. + name: Railiance Platform Services + summary: 'S3 Platform Services layer of the Railiance OAS Stack — shared cluster services: PostgreSQL + HA, Valkey cache, secret management, identity integration, and object storage.' + vector: D3 / A2 / C1 / R0 domain: financials status: draft owner: railiance-platform + path: registry/capabilities/capability.railiance.platform-services.md tags: - railiance - platform - secrets - postgres - - security - - netkingdom - - framework-reuse - vector: D3 / A2 / C1 / R0 - path: registry/capabilities/capability.railiance.platform-services.md consumption_modes: - - CLI - - source + - cli (shell/python scripts) diff --git a/requirements/README.md b/requirements/README.md deleted file mode 100644 index 5b31855..0000000 --- a/requirements/README.md +++ /dev/null @@ -1,27 +0,0 @@ -# Primary application archive runtime - -Use a dedicated platform-managed backup virtual environment. The app image does -not need boto3. backup-primary.in pins the boto3/botocore pair needed for conditional -multipart completion; backup-primary.lock also freezes all transitive dependencies -and accepted package hashes. Reviewed pair: boto3 1.40.18 / botocore 1.40.76. - -After admission, prepare the runtime before a capture window: - -```bash -uv venv /opt/railiance-platform/backup-venv -uv pip sync --python /opt/railiance-platform/backup-venv/bin/python \ - --require-hashes requirements/backup-primary.lock -``` - -These are installation instructions, not an instruction to alter a running -workload or enable a schedule. Do not resolve dependencies during a scheduled -backup. Node/tool/actor admission remains RPF-WP-0053. Deploy these requirements -with the reviewed scripts and record installed hashes. Credentials are delivered -separately by the owner; no values belong in this directory. - -An offline check with synthetic environment fields on 2026-10-07 proved the exact -installed botocore model exposes CompleteMultipartUpload.IfNoneMatch, without -provider contact. A presence check does not prove actual custody or provider -permissions. Requirements were compiled in /tmp; the comments preserve that -command's provenance. Regenerate from this repository's requirements/*.in with -uv pip compile --generate-hashes and review the new lock before installation. diff --git a/requirements/backup-primary.in b/requirements/backup-primary.in deleted file mode 100644 index 915fb0e..0000000 --- a/requirements/backup-primary.in +++ /dev/null @@ -1,2 +0,0 @@ -boto3==1.40.18 -botocore==1.40.76 diff --git a/requirements/backup-primary.lock b/requirements/backup-primary.lock deleted file mode 100644 index ff0e6ad..0000000 --- a/requirements/backup-primary.lock +++ /dev/null @@ -1,35 +0,0 @@ -# This file was autogenerated by uv via the following command: -# uv pip compile --generate-hashes --output-file /tmp/backup-primary.lock /tmp/backup-primary.in -boto3==1.40.18 \ - --hash=sha256:64301d39adecc154e3e595eaf0d4f28998ef0a5551f1d033aeac51a9e1a688e5 \ - --hash=sha256:daa776ba1251a7458c9d6c7627873d0c2460c8e8272d35759065580e9193700a - # via -r /tmp/backup-primary.in -botocore==1.40.76 \ - --hash=sha256:2b16024d68b29b973005adfb5039adfe9099ebe772d40a90ca89f2e165c495dc \ - --hash=sha256:fe425d386e48ac64c81cbb4a7181688d813df2e2b4c78b95ebe833c9e868c6f4 - # via - # -r /tmp/backup-primary.in - # boto3 - # s3transfer -jmespath==1.1.0 \ - --hash=sha256:472c87d80f36026ae83c6ddd0f1d05d4e510134ed462851fd5f754c8c3cbb88d \ - --hash=sha256:a5663118de4908c91729bea0acadca56526eb2698e83de10cd116ae0f4e97c64 - # via - # boto3 - # botocore -python-dateutil==2.9.0.post0 \ - --hash=sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3 \ - --hash=sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427 - # via botocore -s3transfer==0.13.1 \ - --hash=sha256:a981aa7429be23fe6dfc13e80e4020057cbab622b08c0315288758d67cabc724 \ - --hash=sha256:c3fdba22ba1bd367922f27ec8032d6a1cf5f10c934fb5d68cf60fd5a23d936cf - # via boto3 -six==1.17.0 \ - --hash=sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274 \ - --hash=sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81 - # via python-dateutil -urllib3==2.8.0 \ - --hash=sha256:0cf3cae568d36aa9576b28dfb35f11328f1cb974ca7647d9475ebb86c75ac6e3 \ - --hash=sha256:63bf2ead4c879426ebf22ef2a781eeb4aa3b4ae798a0435506f8687fd5bb9b63 - # via botocore diff --git a/reviews/RPF-WP-0015-T03-founder-go-20261014.json b/reviews/RPF-WP-0015-T03-founder-go-20261014.json deleted file mode 100644 index 30a2639..0000000 --- a/reviews/RPF-WP-0015-T03-founder-go-20261014.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "decision_id": "rpf-wp-0015-t03-founder-go-20261014-01", - "scenario_id": "RPF-WP-0015-T03-REBOOT-20261014-01", - "approved_window_id": "rpf-wp-0015-t03-window-20261014-01", - "task_id": "RAILIANCE-WP-0024-T03", - "decided_by_role": "founder", - "decided_in": "attended Claude Code session 2026-10-14 (answer: 'Today, I have 2 unseal holders')", - "recorded_at": "2026-10-14T17:24:57Z", - "authority": "D5 2fd5849c (outage exercise authorized 2026-10-11) + this per-window founder GO", - "abort_operator": "founder (platform-root), attended", - "independent_abort_owner": "railiance-infra (procedure receipt a234288a)", - "owner_procedure_approvals_contract": "3817c1df04a4ae3a4870a63e9abc24d5ea27f57d4e499cbdff582f1146bc8298", - "owner_procedure_approvals": { - "audit-core": "7c4019f2-348d-475e-9c96-e1ff6d0d27f5", - "rapp-postgres": "02f285e0-4285-4b0b-a50b-85665da4e9a2", - "railiance-platform": "b9ea904d-2670-498d-9fc5-a243f216914a", - "railiance-cluster": "2087b9ac-c980-4881-89f1-6b1088132f5b", - "railiance-infra": "a234288a-d084-4f76-850b-9094f51c4a0f" - }, - "conditions": "Window proceeds only after the hold-point preflight reports ready_for_live_execution true and a final founder go/no-go. Consumed decisions 449a697a/85724c0c and scenario WARDEN-WP-0027-T02-DRILL-20260822-01 are not reused.", - "secret_values_observed": false, - "escrow_key_read": { - "decision": "Allow, in memory only", - "question": "Snapshot lane verifies the encrypted copy by decrypting it in memory with the offsite escrow key (AGE_PRIVATE_KEY at platform/workloads/railiance/backup/offsite-lane, normally restore-drills only). Allow that read during the attended snapshot?", - "decided_by_role": "founder", - "decided_at": "2026-10-14 (attended Claude Code session, before the snapshot lane review)", - "scope": "RPF-WP-0015-T03 attended snapshot lane only; key passed over stdin and held in process memory, never written to disk, logged, or put in Git/hub" - }, - "upload_folder": { - "decision": "railiance-backups/openbao/ created by the founder on the offsite share", - "decided_by_role": "founder" - }, - "synthetic_e2_deferral": { - "decision": "Chain check only tonight; synthetic E2 deferred to its own attended window", - "decided_by_role": "founder", - "decided_at": "2026-10-14 (attended session)", - "audit_core_conditions": [ - "pre-reboot chain-head attestation and hash recorded", - "post-reboot /healthz 200, /readyz recovers without restarting audit-core; pod UID and restart count recorded", - "verify-chain reports no break and the pre-reboot head is present", - "attest-chain succeeds with a new attestation", - "T03 is not marked as proving the synthetic E2 path until the follow-up E2 window runs through the reviewed sender lane" - ], - "audit_core_acceptance": "7c4019f2-348d-475e-9c96-e1ff6d0d27f5", - "follow_up": { - "target_date": "2026-10-15 (proposed; founder to confirm)", - "lane": "reviewed audit-core synthetic sender lane in its own attended window", - "records_as": "T03 completion evidence for the synthetic E2 path" - } - }, - "custodian_key_check": { - "result": "founder's offline age identity railiance-backup.key derives age1zvryunvjhvpkmasskauga2heeg0ztnte9ymgppvjge36ekumk50syr3tsz (the offsite snapshot recipient)", - "checked_by_role": "founder", - "checked_at": "2026-10-14 (attended session, public key only)" - }, - "window_console_receipt": "railiance-infra RAIL01-CONSOLE-20261014T175624Z" -} diff --git a/reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json b/reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json deleted file mode 100644 index bdf12bd..0000000 --- a/reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json +++ /dev/null @@ -1,298 +0,0 @@ -{ - "interface": "railiance.attended-login-containment-reacceptance-review", - "workplan_id": "RPF-WP-0017", - "source_repo": "ops-warden", - "current_decision": "accept", - "current_reviewed_revision": "6e1430073c64ed79204cdf676f18bfa41812d5d2", - "reviews": [ - { - "interface": "railiance.attended-login-containment-reacceptance-review", - "version": 1, - "workplan_id": "RPF-WP-0017", - "task_id": "RPF-WP-0017-T01", - "owner": "railiance-platform", - "source_repo": "ops-warden", - "baseline_revision": "0fae0904ce8d8694338dd53a8a79abec5fec788d", - "baseline_owner_receipt": "ops-warden@bc1966da825fbeaa4d83947a4f942b5d5f085408:docs/evidence/RAILIANCE-WP-0026-T01-ops-warden-receipt.json", - "reviewed_revision": "11b2a9d7bc97f5fe8606927c17fe8acc59b79cd0", - "reviewed_paths": [ - "src/warden/cli.py", - "src/warden/proxy.py", - "tests/test_proxy.py" - ], - "intervening_commits": [ - { - "revision": "b4c1d3900a528bbb9f2714d59403757426185d63", - "date": "2026-09-01", - "summary": "Removed `login_output.strip()` from the attended-login fail-closed condition (WARDEN-WP-0036-T01). A login with returncode 0 and a populated helper now proceeds to child handoff even when the client emitted stdout/stderr." - }, - { - "revision": "8f01eefb1e958d060adfb78ea214464b730edd4d", - "date": "2026-09-01", - "summary": "Propagates the caller's ~/.config/warden/warden.yaml path as WARDEN_CONFIG into the isolated child env. Path only, not a credential; no criterion impact." - }, - { - "revision": "4c3a0f4d3a0b1b70c791ed03148205da6eebcfc2", - "date": "2026-09-28", - "summary": "Introduced AttendedLoginError with distinct exit codes 10-14 and phase outcomes in both audit files; child output now fails closed on any byte including whitespace, checked before returncode. Containment-strengthening for the child phase; no regression." - } - ], - "criteria": [ - { - "id": 1, - "title": "Isolated writable token-helper location created before authentication, mode 0600 with private parents, deterministic cleanup", - "result": "pass", - "evidence": "_prepare_attended_login_home unchanged since baseline: HOME probe, 0700 root and session dirs, O_EXCL|O_NOFOLLOW 0600 helper with preflight write/fsync/truncate, ownership/mode asserted; finally-block rmtree + rmdir. 4c3a0f4 only reclassifies cleanup failure as AttendedLoginError(exit 14, cleanup_failed). Tests: test_attended_login_persistence_failure_revokes_and_cleans, test_cli_attended_result_and_both_audits." - }, - { - "id": 2, - "title": "Read-only or unusable default home fails before OIDC begins", - "result": "pass", - "evidence": "Home preflight runs before any subprocess and is unchanged since baseline. test_attended_login_refuses_read_only_home_before_auth asserts subprocess.run is never invoked." - }, - { - "id": 3, - "title": "Adapter-owned stdout/stderr containment; no inherited agent-visible stdio", - "result": "pass", - "evidence": "_contained_run still uses PIPE for stdout and stderr for login, child, and revoke. Login output is held only for revocation and is never printed, returned, or audited; error messages are value-free. test_attended_login_contained_success_output_never_escapes and the parametrized unexpected-output test assert the sentinel is absent from capsys, the exception text, argv, and both audit logs." - }, - { - "id": 4, - "title": "Unexpected credential-bearing or otherwise non-allowlisted output fails closed and triggers immediate self-revocation", - "result": "fail", - "evidence": "b4c1d39 changed proxy.py's login check from `if login.returncode != 0 or login_output.strip() or not helper_valid` to `if login.returncode != 0 or not helper_valid`. Login-phase output is no longer an independent fail-closed trigger, and there is no allowlist. A login that exits 0 with a populated helper but emits arbitrary or credential-bearing stdout/stderr now proceeds to the reviewed child instead of failing closed. The default `-format=json` makes credential-bearing login output the expected case. The output is still contained (criterion 3 holds), but this criterion requires fail-closed on non-allowlisted output, and the baseline receipt outcome `unexpected_stdout_and_stderr_fail_closed` is no longer true for the login phase. The child phase still meets the criterion (4c3a0f4: any child byte gives exit 12 child_output with revocation)." - }, - { - "id": 5, - "title": "Focused tests cover success handoff, unwritable home refusal, helper persistence failure, unexpected stdout, unexpected stderr, immediate revocation, cleanup, and value absence", - "result": "fail", - "evidence": "The focused suite passes and covers success, home refusal, persistence failure, revocation, cleanup, value absence, and the child-output/exit-code phases. However, test_attended_login_unexpected_output_is_contained_revoked_and_cleaned (stdout/stderr) now passes only because its fake login leaves the helper empty, so it exercises the persistence-failure path, not an unexpected-output trigger. The new test_attended_login_contained_success_output_never_escapes asserts that credential-bearing login output with a populated helper succeeds (rc 0, child runs). No test proves that unexpected login stdout or stderr fails closed on its own, so the coverage required by the criterion is absent." - } - ], - "tests": [ - { - "command": "uv run pytest -q tests/test_proxy.py tests/test_plan.py", - "passed": true, - "passed_count": 68, - "failed_count": 0 - }, - { - "command": "uv run pytest -q", - "passed": false, - "passed_count": 610, - "failed_count": 1, - "deselected_count": 4, - "note": "Single failure tests/test_agent_read_boundary_check.py::TestGeneratedArtifact::test_artifact_is_current (registry/generated/high-risk-data-paths.yaml stale vs catalog). It is unrelated to attended-login containment but means the repository is not green at the reviewed revision." - }, - { - "command": "uv run ruff check src/warden/proxy.py src/warden/cli.py tests/test_proxy.py", - "passed": true - } - ], - "decision": "reject", - "decision_rationale": "Criteria 1-3 still hold. Criterion 4 regressed in b4c1d39 (WARDEN-WP-0036): login-phase output is no longer a fail-closed trigger, so the behaviour railiance-platform accepted at 0fae0904 has been changed without a railiance-platform re-review. Criterion 5's unexpected-output coverage is now satisfied only incidentally through the persistence-failure path. The relaxation may be a deliberate design choice, since output is still never forwarded. If so, RPF-WP-0017 acceptance criterion 4 must be formally amended by railiance-platform, with an explicit allowlist for contained `bao login -format=json` output and railiance-infra concurrence, before this revision can be accepted. Otherwise ops-warden must restore the login-output fail-closed check and add a populated-helper unexpected-output test.", - "observations": [ - "WARDEN-WP-0036-T02 records a live governed OpenBao platform-admin operation on 2026-09-01 using the post-b4c1d39 code. That revision had not been re-accepted under RPF-WP-0017. This review did not verify the operation; it is recorded for railiance-infra follow-up.", - "8f01eef forwards a non-secret config path into the isolated child; acceptable, but note that the child now reads caller-home configuration despite HOME isolation." - ], - "live_oidc_performed": false, - "openbao_calls_performed": false, - "live_drill_authorized": false, - "secret_values_observed": false, - "sensitive_material_recorded": false, - "reviewer": "claude/railiance-platform-ops-warden-review", - "reviewed_at": "2026-10-14T10:29:14Z" - }, - { - "interface": "railiance.attended-login-containment-reacceptance-review", - "version": 1, - "workplan_id": "RPF-WP-0017", - "task_id": "RPF-WP-0017-T01", - "owner": "railiance-platform", - "source_repo": "ops-warden", - "baseline_revision": "0fae0904ce8d8694338dd53a8a79abec5fec788d", - "supersedes_review_of": "11b2a9d7bc97f5fe8606927c17fe8acc59b79cd0", - "reviewed_revision": "8a426fba696c024a24430e8d3bcb239463d51f51", - "fix_revision": "244f1ae2ca538d36b0dc43032b26f5eca85c6c66", - "reviewed_paths": [ - "src/warden/proxy.py", - "tests/test_proxy.py", - "registry/generated/high-risk-data-paths.yaml" - ], - "intervening_commits": [ - { - "revision": "244f1ae2ca538d36b0dc43032b26f5eca85c6c66", - "date": "2026-10-14", - "summary": "WARDEN-WP-0036: restores login output as an independent fail-closed trigger. Login proceeds only if returncode 0, helper populated (0600, owned, non-empty) AND output allowlisted (_login_output_allowed); otherwise single self-revocation and AttendedLoginError(exit 10, login_failed), no retry." - }, - { - "revision": "8a426fba696c024a24430e8d3bcb239463d51f51", - "date": "2026-10-14", - "summary": "Regenerates registry/generated/high-risk-data-paths.yaml; diff is generated_at and catalog_revision/catalog_revision_date only. Restores test_artifact_is_current. No criterion impact." - } - ], - "criteria": [ - { - "id": 1, - "title": "Isolated writable token-helper location created before authentication, mode 0600 with private parents, deterministic cleanup", - "result": "pass", - "evidence": "_prepare_attended_login_home and the finally-block cleanup (rmtree + rmdir, exit 14 cleanup_failed) are unchanged by 244f1ae. New tests assert the .warden-attended-login root is absent after non-allowlisted fail-closed." - }, - { - "id": 2, - "title": "Read-only or unusable default home fails before OIDC begins", - "result": "pass", - "evidence": "Home preflight unchanged; test_attended_login_refuses_read_only_home_before_auth still passes." - }, - { - "id": 3, - "title": "Adapter-owned stdout/stderr containment; no inherited agent-visible stdio", - "result": "pass", - "evidence": "_contained_run still PIPEs stdout/stderr for login, child, revoke. The allowlist functions return bool only; the new error suffix ' (login client produced non-allowlisted output)' is a fixed string with no captured bytes. Parametrized tests assert the sentinel is absent from the exception text and capsys out/err." - }, - { - "id": 4, - "title": "Unexpected credential-bearing or otherwise non-allowlisted output fails closed and triggers immediate self-revocation", - "result": "pass", - "evidence": "proxy.py: `if login.returncode != 0 or not helper_valid or not output_allowed` -> _revoke_contained -> AttendedLoginError(exit 10, login_failed) before child handoff; no retry loop. stdout allowlist: empty, or exactly one JSON object (json.loads full-document, duplicate keys rejected, Unicode/ValueError/RecursionError caught) whose top-level keys are within a fixed 9-key typed set, with `auth` required as an object containing `client_token` (str) and only a fixed 12-key typed auth set; bool is not accepted where int is expected; NaN/floats rejected. stderr allowlist: empty, or a fullmatch of the OpenBao OIDC prompt (browser-launch or open-link variant), exactly one `https://\\S+` URL on its own line, optional 'Waiting for OIDC authentication to complete...' line, whitespace only otherwise. Reviewer probes: two prompts, CRLF, two URLs on one line, trailing text after the wait line, and non-ASCII whitespace inside the URL are all rejected." - }, - { - "id": 5, - "title": "Focused tests cover success handoff, unwritable home refusal, helper persistence failure, unexpected stdout, unexpected stderr, immediate revocation, cleanup, and value absence", - "result": "pass", - "evidence": "New test_attended_login_non_allowlisted_output_with_populated_helper_fails_closed (13 cases: stdout text, stderr text, stderr appended to valid prompt, JSON + trailing text, two JSON objects, extra top-level key, extra auth key, wrong type, no auth, JSON array, duplicate key, malformed JSON, non-UTF-8) uses a populated helper so only the output trigger can fire. It asserts exit 10/login_failed, sentinel absence, call sequence exactly [login, token revoke -self] (no retry, child never runs), and removal of the private root. test_attended_login_allowlisted_output_hands_off covers the 5 allowlisted shapes. The earlier persistence-failure and CLI dual-audit tests still pass (the audit test now uses JSON-shaped login output)." - } - ], - "allowlist_relaxation_note": "Criterion 4 as baselined at 0fae0904 rejected any login output. This revision accepts two allowlisted shapes: one `bao login -format=json` object on stdout, and the OpenBao OIDC CLI prompt on stderr. Both are an explicit, structure-only relaxation of 'reject all login output'. They are accepted because the output stays captured and is never forwarded, logged, or audited (criterion 3), and because a successful attended login is required to return the issued token in contained output. The stderr URL group (`https://[^\\s]+`) can carry arbitrary non-whitespace data, including a token-shaped string. railiance-platform accepts this: the URL is the server-issued OIDC auth_url and is printed before any client token exists, and the stderr is contained. The relaxation does not allow output to reach the agent.", - "tests": [ - { - "command": "uv run pytest -q tests/test_proxy.py tests/test_plan.py", - "passed": true, - "passed_count": 86, - "failed_count": 0 - }, - { - "command": "uv run pytest -q", - "passed": true, - "passed_count": 629, - "failed_count": 0, - "deselected_count": 4 - }, - { - "command": "uv run ruff check src/warden/proxy.py tests/test_proxy.py", - "passed": true - } - ], - "decision": "accept", - "decision_rationale": "244f1ae fixes both failures from the 11b2a9d review. Non-allowlisted login stdout or stderr with a populated helper now independently fails closed, with one self-revocation, no retry, no child handoff, cleanup, and value-free errors (criterion 4). Populated-helper tests prove the output trigger directly, not through the persistence-failure path (criterion 5). 8a426fb restores a green full suite. The allowlist is tight on key names, types, and document framing. The remaining concerns below are non-blocking because the output stays contained.", - "concerns_non_blocking": [ - "The stdout allowlist constrains key names and top-level types only. Nested contents of data, metadata, warnings, wrap_info, and mfa_requirement are unconstrained, and client_token may be empty. A non-null wrap_info next to auth would be accepted, and a wrapping token in it would not be covered by `token revoke -self`. Recommended hardening: require wrap_info to be null, and data to be null or empty.", - "The stderr URL group accepts arbitrary non-whitespace data (accepted above). Leading whitespace before the prompt is also accepted.", - "Because stderr is captured, the operator never sees the open-link variant's URL. In headless use the login can only finish via the browser-launch path. This is usability, not containment.", - "The new parametrized tests assert value absence in the exception and stdio, but not in the two audit files. Audit absence for login_failed is covered only by test_cli_attended_result_and_both_audits.", - "Observations from the first review still stand: the WARDEN-WP-0036-T02 live operation on 2026-09-01 ran on unaccepted code and needs railiance-infra follow-up, and the WARDEN_CONFIG path is forwarded to the child." - ], - "live_oidc_performed": false, - "openbao_calls_performed": false, - "live_drill_authorized": false, - "secret_values_observed": false, - "sensitive_material_recorded": false, - "reviewer": "claude/railiance-platform-ops-warden-review-2", - "reviewed_at": "2026-10-14T10:39:09Z" - }, - { - "interface": "railiance.attended-login-containment-reacceptance-review", - "version": 1, - "workplan_id": "RPF-WP-0017", - "task_id": "RPF-WP-0017-T01", - "owner": "railiance-platform", - "source_repo": "ops-warden", - "baseline_revision": "0fae0904ce8d8694338dd53a8a79abec5fec788d", - "supersedes_review_of": "8a426fba696c024a24430e8d3bcb239463d51f51", - "reviewed_revision": "6e1430073c64ed79204cdf676f18bfa41812d5d2", - "reviewed_paths": [ - "src/warden/proxy.py", - "tests/test_proxy.py" - ], - "intervening_commits": [ - { - "revision": "6e1430073c64ed79204cdf676f18bfa41812d5d2", - "date": "2026-10-14", - "summary": "WARDEN-WP-0036: tightens the attended-login allowlist. wrap_info must be null or absent, data must be null, absent or {}, auth.client_token must be a non-empty str, and leading whitespace before the stderr OIDC prompt is rejected. Populated-helper tests now run through the CLI and assert that sentinel values are absent from both audit files. Addresses non-blocking concerns 1, 2 and 4 of the second review." - } - ], - "criteria": [ - { - "id": 1, - "title": "Isolated writable token-helper location created before authentication, mode 0600 with private parents, deterministic cleanup", - "result": "pass", - "evidence": "Helper home preparation and cleanup are unchanged since 8a426fb. CLI-level tests now assert the private root is removed on both the fail-closed and the hand-off paths." - }, - { - "id": 2, - "title": "Read-only or unusable default home fails before OIDC begins", - "result": "pass", - "evidence": "Unchanged; test_attended_login_refuses_read_only_home_before_auth passes." - }, - { - "id": 3, - "title": "Adapter-owned stdout/stderr containment; no inherited agent-visible stdio", - "result": "pass", - "evidence": "_contained_run is unchanged. New _assert_login_audits reads access-audit.log and audit.jsonl and asserts the unexpected-login, OK and helper sentinels are absent, with login exit_code/outcome recorded (10/login_failed or 0/ok). CLI output, capsys and every subprocess argv are asserted sentinel-free." - }, - { - "id": 4, - "title": "Unexpected credential-bearing or otherwise non-allowlisted output fails closed and triggers immediate self-revocation", - "result": "pass", - "evidence": "The fail-closed gate is unchanged from 244f1ae (returncode, helper_valid and output_allowed, then a single self-revocation and exit 10, with no retry). The allowlist is now tighter. The type of wrap_info is (NoneType,) only, so a wrapping token that would survive `token revoke -self` is rejected. `parsed.get(\"data\")` truthy is rejected, and given the (dict, None) typing that means only null, absent or {} pass. client_token must be a non-empty str. _LOGIN_OIDC_STDERR no longer starts with `\\s*`, so fullmatch rejects any leading byte. Reviewer probes: a list-typed data, a tab-prefixed prompt and a space-prefixed prompt are rejected; a null wrap_info and trailing whitespace after the prompt are accepted, as intended." - }, - { - "id": 5, - "title": "Focused tests cover success handoff, unwritable home refusal, helper persistence failure, unexpected stdout, unexpected stderr, immediate revocation, cleanup, and value absence", - "result": "pass", - "evidence": "The non-allowlisted parametrized test grows to 20 cases through the CLI. New cases: wrap_info carrying a token, an empty wrap_info, non-empty data, empty and null client_token, and stderr with a leading space or newline. It asserts exit 10, the error reason text, sentinel absence (output, capsys, argv, both audit files), exactly [login, revoke -self] with no retry and no child, and cleanup. A direct-call test keeps the value-free exception and exact argv assertions. The allowlisted hand-off test (6 cases, adding data-empty) now runs through the CLI and asserts the child runs, the final call is revoke -self, the sentinel is absent, cleanup happens, and the audit records 0/ok." - } - ], - "allowlist_relaxation_note": "Unchanged from the second review. Accepting one `bao login -format=json` object on stdout and the OpenBao OIDC prompt on stderr is an explicit, structure-only relaxation of 'reject all login output'. It is accepted because both stay captured, are never forwarded, logged or audited, and cannot reach the agent. The stderr URL group (`https://[^\\s]+`) can still carry arbitrary non-whitespace data. That is acceptable because it is the server-issued OIDC auth_url, printed before any client token exists, and it is contained.", - "tests": [ - { - "command": "uv run pytest -q tests/test_proxy.py tests/test_plan.py", - "passed": true, - "passed_count": 95, - "failed_count": 0 - }, - { - "command": "uv run pytest -q", - "passed": true, - "passed_count": 638, - "failed_count": 0, - "deselected_count": 4 - }, - { - "command": "uv run ruff check src/warden/proxy.py tests/test_proxy.py", - "passed": true - } - ], - "decision": "accept", - "decision_rationale": "6e14300 only tightens the allowlist that the second review accepted at 8a426fb, and adds audit-file value-absence coverage. It loosens nothing: the fail-closed gate, revocation, no-retry and cleanup paths are unchanged. Second-review concerns 1, 2 and 4 are resolved. All five criteria pass and the full suite is green.", - "resolved_concerns": [ - "Concern 1 is resolved: wrap_info must be null or absent, data null, absent or {}, and client_token non-empty.", - "Concern 2 is partially resolved: leading whitespace before the prompt is rejected, but the URL group stays permissive by design (see allowlist_relaxation_note).", - "Concern 4 is resolved: audit-file sentinel absence is now asserted on both the fail-closed and the hand-off paths." - ], - "concerns_non_blocking": [ - "A whitespace-only client_token (e.g. \" \") passes, because the check is `token != \"\"` rather than `token.strip()`. This is cosmetic: the populated 0600 helper check is the real persistence gate.", - "The nested contents of auth.metadata, warnings and mfa_requirement are still unconstrained. They are contained, and the self-token is what gets revoked.", - "Concern 3 from the second review is unchanged. Because stderr is captured, the operator never sees the open-link URL, so headless login cannot complete. This is usability, not containment.", - "Observations from the earlier reviews still stand: the WARDEN-WP-0036-T02 live operation on 2026-09-01 ran on unaccepted code and needs railiance-infra follow-up, and the WARDEN_CONFIG path is forwarded to the child." - ], - "live_oidc_performed": false, - "openbao_calls_performed": false, - "live_drill_authorized": false, - "secret_values_observed": false, - "sensitive_material_recorded": false, - "reviewer": "claude/railiance-platform-ops-warden-review-2", - "reviewed_at": "2026-10-14T10:43:47Z" - } - ] -} diff --git a/scripts/activity_promotion_readiness.py b/scripts/activity_promotion_readiness.py deleted file mode 100644 index 2f83bdf..0000000 --- a/scripts/activity_promotion_readiness.py +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only activity-core promotion gate; signed owner acceptance is mandatory. - -D2 grants scoped activation after proof, never substitutes for release-principal -custody, authenticated issuers or rollback. This checker cannot activate a worker, -issue credentials, change Argo policy or admit its own caller as a trusted signer. -""" -from __future__ import annotations -import argparse -import base64 -from datetime import datetime, timezone, timedelta -import hashlib -import json -from pathlib import Path -import re -import subprocess - -PIN = 'a86a14332d76d92a7ed176ea34dca4429902c396' -OWNER_TASK = 'ACTIVITY-WP-0041-T03' -AUTHORITY = 'ACTIVITY-WP-0041-image-only-v1' -APPLICATION_PATH = 'argocd/railiance01/applications/activity-core.application.yaml' -RESOURCES = { - ('', 'ConfigMap', 'activity-core', name) for name in - ('actcore-runtime-config', 'actcore-external-activity-definitions', 'actcore-report-schemas', 'actcore-ops-service-inventory') -} | {('', 'Service', 'activity-core', name) for name in ('actcore-api', 'actcore-worker-metrics')} | { - ('apps', 'Deployment', 'activity-core', name) for name in ('actcore-api', 'actcore-worker', 'actcore-event-router') -} -CHECKS = { - 'principal_scope_enforced', 'credential_custody_verified', 'revocation_denial_proven', - 'independent_receipt_issuers_verified', 'authenticated_ci_digest_binding_proven', - 'continuous_health_observer_active', 'live_and_rollback_retention_verified', - 'image_only_admission_enforced', 'source_cas_and_single_flight_proven', - 'restart_and_lost_response_recovery_proven', 'out_of_scope_denials_proven', - 'forged_stale_mismatched_receipt_denials_proven', 'failed_health_rollback_proven', - 'temporal_dispatch_registered', 'durable_audit_sink_verified', 'report_schedule_invariants_verified', -} -TARGETS = { - 'platform_repository': 'coulomb/railiance-platform', 'branch': 'main', - 'child_path': APPLICATION_PATH, 'child_field': '/spec/source/targetRevision', - 'root_application': 'railiance-apps-root', 'root_sync_selects_only': 'activity-core', - 'child_application': 'activity-core', 'prune': False, -} - - -def canonical(value): - return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False).encode() - - -def stamp(value): - result = datetime.fromisoformat(value.replace('Z', '+00:00')) - if result.tzinfo is None: - raise ValueError('timezone required') - return result - - -def run(argv, *, input=None): - try: - result = subprocess.run(argv, input=input, capture_output=True, timeout=100) - if result.returncode: - raise ValueError('metadata read unverified; protected output suppressed') - return result.stdout - except (OSError, subprocess.SubprocessError): - raise ValueError('metadata read unverified; protected output suppressed') from None - - -def collect(): - def get(args): - return json.loads(run(['kubectl', *args, '-o', 'json'])) - nodes = get(['get', 'nodes']) - if '92.205.62.239' not in [a['address'] for n in nodes['items'] for a in n['status']['addresses']]: - raise ValueError('estate identity mismatch') - namespace = get(['get', 'namespace', 'kube-system']) - obj = get(['-n', 'argocd', 'get', 'application', 'activity-core']) - spec, status = obj['spec'], obj.get('status', {}) - expected_source = {'repoURL': 'https://forgejo.coulomb.social/coulomb/activity-core.git', - 'path': 'k8s/gitops', 'targetRevision': PIN} - if spec.get('source') != expected_source: - raise ValueError('exact activity source unverified') - op = status.get('operationState', {}) - deployments = [] - for name in ('actcore-api', 'actcore-worker', 'actcore-event-router'): - deployment = get(['-n', 'activity-core', 'get', 'deployment', name]) - generation = deployment['metadata']['generation']; ds = deployment.get('status', {}) - desired = deployment['spec'].get('replicas', 1) - deployments.append({'name': name, 'generation_observed': ds.get('observedGeneration') == generation, - 'replicas': desired, 'ready': ds.get('readyReplicas', 0), - 'updated': ds.get('updatedReplicas', 0), 'available': ds.get('availableReplicas', 0)}) - project = get(['-n', 'argocd', 'get', 'appproject', 'activity-core'])['spec'] - return {'captured_at': datetime.now(timezone.utc).isoformat(), 'estate_ip': '92.205.62.239', - 'cluster_uid': namespace['metadata']['uid'], 'application_uid': obj['metadata']['uid'], - 'source': expected_source, 'project': spec.get('project'), - 'destination': spec.get('destination'), 'automated_present': 'automated' in spec.get('syncPolicy', {}), - 'sync_options': spec.get('syncPolicy', {}).get('syncOptions', []), - 'pending_operation': bool(obj.get('operation')), 'sync': status.get('sync', {}).get('status'), - 'revision': status.get('sync', {}).get('revision'), 'health': status.get('health', {}).get('status'), - 'operation': {k: op.get(k) for k in ('phase', 'finishedAt')}, - 'condition_types': [c.get('type') for c in status.get('conditions', [])], - 'resources': [{k: r.get(k) for k in ('group', 'kind', 'namespace', 'name', 'status')} for r in status.get('resources', [])], - 'project_scope_matches': (project.get('sourceRepos') == ['https://forgejo.coulomb.social/coulomb/activity-core.git'] - and project.get('destinations') == [{'server': 'https://kubernetes.default.svc', 'namespace': 'activity-core'}] - and project.get('clusterResourceWhitelist', []) == [] - and {(r.get('group') or '',r['kind']) for r in project.get('namespaceResourceWhitelist', [])} - == {('', 'ConfigMap'),('', 'Service'),('apps','Deployment')}), - 'deployments': deployments} - - -def adoption_checks(observation, now): - if (not 0 <= (now-stamp(observation['captured_at'])).total_seconds() <= 300 - or observation['estate_ip'] != '92.205.62.239' or not observation['cluster_uid'] - or not observation['application_uid']): - raise ValueError('fresh exact estate observation required') - if (observation['source'] != {'repoURL':'https://forgejo.coulomb.social/coulomb/activity-core.git','path':'k8s/gitops','targetRevision':PIN} - or observation['project'] != 'activity-core' or observation['revision'] != PIN - or observation['destination'] != {'server':'https://kubernetes.default.svc','namespace':'activity-core'} - or observation['automated_present'] is not False or observation['pending_operation'] is not False - or set(observation['sync_options']) != {'CreateNamespace=false','ApplyOutOfSyncOnly=true','PruneLast=true','FailOnSharedResource=true'} - or observation['sync'] != 'Synced' or observation['health'] != 'Healthy' - or observation['condition_types'] or observation['project_scope_matches'] is not True): - raise ValueError('bounded manual adoption scope unverified') - op = observation['operation'] - if op['phase'] != 'Succeeded' or (stamp(observation['captured_at'])-stamp(op['finishedAt'])).total_seconds() < 86400: - raise ValueError('24-hour adoption eligibility unverified') - resources = observation['resources'] - if (len(resources) != 9 or {tuple(r.get(k) or '' for k in ('group','kind','namespace','name')) for r in resources} != RESOURCES - or any(r['status'] != 'Synced' for r in resources)): - raise ValueError('nine-resource runtime inventory unverified') - deployments = observation['deployments'] - if (len(deployments) != 3 or {d['name'] for d in deployments} != {'actcore-api','actcore-worker','actcore-event-router'} - or any(d['generation_observed'] is not True or type(d['replicas']) is not int or d['replicas'] < 1 - or any(d[k] != d['replicas'] for k in ('ready','updated','available')) for d in deployments)): - raise ValueError('deployment generation/readiness unverified') - - -def owner_acceptance(envelope, trusted_key, trusted_principal, observation, now): - from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey - if set(envelope) != {'payload','signature'} or not trusted_principal or len(trusted_key) != 32: - raise ValueError('trusted owner acceptance configuration required') - payload = envelope['payload'] - Ed25519PublicKey.from_public_bytes(trusted_key).verify(base64.b64decode(envelope['signature'],validate=True), canonical(payload)) - if (payload.get('schema_version') != 'activity-core-release-acceptance/v1' - or payload.get('owner_task') != OWNER_TASK or payload.get('issuer') != trusted_principal - or payload.get('authority') != AUTHORITY or payload.get('application') != 'activity-core' - or payload.get('cluster_uid') != observation['cluster_uid'] or payload.get('adoption_revision') != PIN - or payload.get('targets') != TARGETS or not payload.get('release_principal') - or not re.fullmatch('[a-f0-9]{40}',payload.get('broker_revision','')) - or set(payload.get('checks',{})) != CHECKS - or any(value is not True for value in payload['checks'].values())): - raise ValueError('owner acceptance scope or machine proof incomplete') - issued, expires = stamp(payload['issued_at']), stamp(payload['expires_at']) - if not 0 <= (now-issued).total_seconds() <= 300 or not now < expires <= issued + timedelta(minutes=5): - raise ValueError('owner acceptance stale or expired') - health = payload['continuous_health'] - if (health.get('revision') != PIN or health.get('continuous') is not True - or type(health.get('max_gap_seconds')) is not int or not 0 < health['max_gap_seconds'] <= 90 - or not 0 <= (now-stamp(health['observed_at'])).total_seconds() <= 90 - or (stamp(health['observed_at'])-stamp(health['healthy_since'])).total_seconds() < 86400): - raise ValueError('owner continuous observation proof incomplete') - return payload - - -def readiness(observation, *, envelope=None, trusted_key=None, trusted_principal=None, now=None): - now = now or datetime.now(timezone.utc) - base = {'schema_version':'activity-promotion-readiness/v1','task':'RPF-WP-0048-T02', - 'owner_dependency':OWNER_TASK,'native_mutation':False,'automatic_sync_candidate':False, - 'observed_at':now.isoformat(),'checker_sha256':hashlib.sha256(Path(__file__).read_bytes()).hexdigest()} - try: - adoption_checks(observation,now) - except Exception: - return base | {'status':'unverified','reason':'adoption_metadata_unverified'} - base |= {'adoption_24h_eligible':True,'adoption_snapshot_is_not_continuous_observation':True, - 'observation_sha256':hashlib.sha256(canonical(observation)).hexdigest()} - if envelope is None or trusted_key is None or trusted_principal is None: - return base | {'status':'waiting','reason':'authenticated_owner_activation_acceptance_missing', - 'depends_on':[OWNER_TASK],'needs_human':False} - try: - payload = owner_acceptance(envelope,trusted_key,trusted_principal,observation,now) - except Exception: - return base | {'status':'unverified','reason':'owner_activation_acceptance_unverified'} - return base | {'status':'ready','owner_acceptance_sha256':hashlib.sha256(canonical(envelope)).hexdigest(), - 'release_principal':payload['release_principal'],'broker_revision':payload['broker_revision'], - 'scope':AUTHORITY,'rollback':'disable dedicated release dispatcher admission; retain manual child policy and prior source pin', - 'abort_owner':'railiance-platform'} - - -def main(): - parser=argparse.ArgumentParser(description=__doc__) - parser.add_argument('--live',action='store_true');parser.add_argument('--remote',action='store_true',help=argparse.SUPPRESS) - parser.add_argument('--observation',type=Path);parser.add_argument('--receipt',type=Path) - parser.add_argument('--owner-acceptance',type=Path) - parser.add_argument('--trusted-owner-key',type=Path,help='operator-owned raw Ed25519 public key; never receipt-provided') - parser.add_argument('--trusted-owner-principal',help='operator-owned issuer binding') - args=parser.parse_args() - try: - if args.remote: - print(json.dumps(collect(),sort_keys=True));return 0 - if args.live: - import sys;sys.path.insert(0,str(Path(__file__).resolve().parent));import kube_route - observation=json.loads(run(kube_route.ssh_argv(['python3','-','--remote']),input=Path(__file__).read_bytes())) - observation['remote_captured_at']=observation['captured_at'] - observation['captured_at']=datetime.now(timezone.utc).isoformat() - if args.observation:args.observation.write_text(json.dumps(observation,indent=2)+'\n') - else:observation=json.loads(args.observation.read_text()) - envelope=json.loads(args.owner_acceptance.read_text()) if args.owner_acceptance else None - key=args.trusted_owner_key.read_bytes() if args.trusted_owner_key else None - result=readiness(observation,envelope=envelope,trusted_key=key,trusted_principal=args.trusted_owner_principal) - except Exception:result={'schema_version':'activity-promotion-readiness/v1','status':'unverified','reason':'protected_read_or_inputs_unverified','native_mutation':False} - if args.receipt:args.receipt.write_text(json.dumps(result,indent=2)+'\n') - print(json.dumps(result,sort_keys=True));return 0 if result['status']=='ready' else 1 - - -if __name__=='__main__':raise SystemExit(main()) diff --git a/scripts/application_archive_retention_plan.py b/scripts/application_archive_retention_plan.py deleted file mode 100644 index ac84281..0000000 --- a/scripts/application_archive_retention_plan.py +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env python3 -"""Review primary archive generations; no provider or local deletion capability.""" - -from __future__ import annotations - -import argparse -import json -import re -from datetime import UTC, datetime -from pathlib import Path - -from primary_application_archive import PROFILES, require_version, validate_key - - -def identity(receipt): - return { - "object_key": receipt["object_key"], - "version_id": receipt["version_id"], - "ciphertext_sha256": receipt["ciphertext_sha256"], - "ciphertext_bytes": receipt["ciphertext_bytes"], - } - - -def plan(application, inventory, recovery, now=None): - now = now or datetime.now(UTC) - if ( - recovery.get("status") != "application_restored_verified" - or recovery.get("application") != application - or recovery.get("application_restore_verified") is not True - ): - raise ValueError("attended_application_recovery_proof_required") - protected = recovery.get("object_key", "") - validate_key(application, protected) - require_version(recovery.get("version_id")) - items, ignored = {}, [] - for receipt in inventory: - key = receipt.get("object_key", "") - if ( - receipt.get("application") != application - or receipt.get("status") != "offsite_fetched_verified" - or receipt.get("remote_retrieval_verified") is not True - or not isinstance(key, str) - or not key.startswith(PROFILES[application]) - ): - ignored.append(key) - continue - validate_key(application, key) - require_version(receipt.get("version_id")) - captured = datetime.fromisoformat(receipt["captured_at"].replace("Z", "+00:00")) - if ( - captured.tzinfo is None - or captured > now - or key in items - or not re.fullmatch(r"[a-f0-9]{64}", receipt.get("ciphertext_sha256", "")) - or not isinstance(receipt.get("ciphertext_bytes"), int) - or receipt["ciphertext_bytes"] <= 0 - ): - raise ValueError("unverified_archive_inventory") - items[key] = (captured.astimezone(UTC), receipt) - if ( - protected not in items - or items[protected][1]["version_id"] != recovery["version_id"] - or items[protected][1]["ciphertext_sha256"] != recovery.get("ciphertext_sha256") - ): - raise ValueError("protected_recovery_point_missing_or_changed") - ordered = sorted(items, key=lambda key: (items[key][0], key), reverse=True) - daily, weekly = {}, {} - for key in ordered: - stamp = items[key][0] - daily.setdefault(stamp.date(), key) - weekly.setdefault(stamp.isocalendar()[:2], key) - keep = {protected, ordered[0]} | set(list(daily.values())[:14]) | set(list(weekly.values())[:4]) - return { - "schema": "platform.application-retention-review.v1", - "status": "reviewed", - "application": application, - "daily_generations": 14, - "weekly_generations": 4, - "protected": identity(items[protected][1]), - "keep": [identity(items[key][1]) for key in ordered if key in keep], - "expiration_candidates": [identity(items[key][1]) for key in ordered if key not in keep], - "ignored": ignored, - "provider_mutations": False, - "local_mutations": False, - "activation_gate": "Separate owner must admit lifecycle/capacity, revalidate protected " - "recovery and exact versions before any expiration.", - } - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--application", choices=PROFILES, required=True) - parser.add_argument("--inventory", type=Path, required=True) - parser.add_argument("--recovery-receipt", type=Path, required=True) - args = parser.parse_args() - print( - json.dumps( - plan( - args.application, - json.loads(args.inventory.read_text()), - json.loads(args.recovery_receipt.read_text()), - ), - indent=2, - ) - ) - return 0 - - -if __name__ == "__main__": - try: - raise SystemExit(main()) - except Exception as error: - print(json.dumps({"status": "failed", "failure_type": type(error).__name__})) - raise SystemExit(1) diff --git a/scripts/application_backup_credentials.py b/scripts/application_backup_credentials.py deleted file mode 100644 index fc94b37..0000000 --- a/scripts/application_backup_credentials.py +++ /dev/null @@ -1,444 +0,0 @@ -#!/usr/bin/env python3 -"""Preview or deliver one admitted backup reader's credentials on railiance01. - -Stdlib only. No ambient OpenBao credential, Secret read, subprocess secret argv, -provider call, rotation, backup capture or service activation. -""" - -from __future__ import annotations - -import argparse -import base64 -import datetime as dt -import fcntl -import hashlib -import ipaddress -import json -import os -import re -import signal -import stat -import subprocess -import sys -import time -import urllib.error -import urllib.request -from pathlib import Path - -CLUSTER_UID = "a553c742-0115-43d4-99a4-a5ca56fe0786" -FIELDS = ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"] -SA = "backup-primary-delivery" -PROFILES = { - "coulomb-social": {"ccr": "CCR-2026-0036", "directory": "/etc/coulomb-social-backup"}, - "email-connect": {"ccr": "CCR-2026-0037", "directory": "/etc/email-connect-backup"}, -} - - -class DeliveryError(Exception): - """Only fixed diagnostics cross the containment boundary.""" - - -def guard(condition, code): - if not condition: - raise DeliveryError(code) - - -def expected_openbao(application): - policy = f"workload-kv-read-{application}-backup-primary" - return { - "mount": "platform", - "kv_path": f"platform/workloads/{application}/backup-primary", - "fields": FIELDS, - "policy_name": policy, - "policy_file": f"openbao/policies/{policy}.hcl", - "metadata_read": False, - "token_self_lifecycle": True, - "auth": { - "method": "kubernetes", - "mount": "kubernetes", - "role": f"{application}-backup-primary-workload-kv-read", - "audience": "openbao", - "ttl": "5m", - "token_max_ttl": "5m", - "token_explicit_max_ttl": "5m", - "token_no_default_policy": True, - "token_num_uses": 4, - "token_type": "service", - "policies": [policy], - "bound_claims": { - "service_account_names": [SA], - "service_account_namespaces": [application], - }, - }, - } - - -def contract(raw, application, execute=False, expected_hash=None): - guard(len(raw) <= 65536, "contract_too_large") - ccr = json.loads(raw) - expected = expected_openbao(application) - actual = dict(ccr.get("openbao", {})) - auth = dict(actual.get("auth", {})) - confirmed = auth.pop("bound_claims_confirmed", None) - actual["auth"] = auth - guard( - actual == expected - and ccr.get("id") == PROFILES[application]["ccr"] - and ccr.get("request_type") == "workload-kv-read", - "exact_contract_required", - ) - delivery = ccr.get("delivery", {}) - guard( - delivery.get("target") == PROFILES[application]["directory"] + "/primary.env" - and delivery.get("cluster_uid") == CLUSTER_UID - and delivery.get("owner") == "root:root" - and delivery.get("mode") == "0600" - and delivery.get("directory_mode") == "0700", - "exact_delivery_required", - ) - digest = hashlib.sha256(raw).hexdigest() - if execute: - guard(expected_hash == digest, "reviewed_source_hash_required") - guard( - ccr.get("status") in {"applied", "verified", "active"} and confirmed is True, - "applied_confirmed_reader_required", - ) - reviews = ccr.get("review", {}).get("comments", []) - for owner in ("platform-owner", f"{application}-owner"): - guard( - any( - r.get("reviewer") == f"User ({owner})" - and r.get("decision") == "approved" - and r.get("source_ref") - for r in reviews - ), - "owner_review_required", - ) - admission = delivery.get("admission", {}) - guard( - all( - admission.get(key) is True - for key in ( - "node_executor_approved", - "provider_scope_verified", - "reader_boundary_verified", - ) - ) - and isinstance(admission.get("source_ref"), str) - and admission["source_ref"], - "delivery_admission_required", - ) - return ccr, digest - - -def command(argv): - try: - result = subprocess.run( - argv, - capture_output=True, - check=False, - timeout=30, - env={ - "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "KUBECONFIG": "/etc/rancher/k3s/k3s.yaml", - }, - ) - guard(result.returncode == 0 and len(result.stdout) <= 1048576, "kubernetes_command_failed") - return result.stdout - except (OSError, subprocess.SubprocessError): - raise DeliveryError("kubernetes_command_failed") from None - - -class NoRedirect(urllib.request.HTTPRedirectHandler): - def redirect_request(self, *args, **kwargs): - return None - - -class Native: - def __init__(self): - self.address = None - self.opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect()) - - def identity(self, application): - kube = ["kubectl", "--request-timeout=20s"] - ns = json.loads(command(kube + ["get", "namespace", "kube-system", "-o", "json"])) - guard(ns.get("metadata", {}).get("uid") == CLUSTER_UID, "cluster_identity_mismatch") - svc = json.loads( - command(kube + ["-n", "openbao", "get", "service", "openbao", "-o", "json"]) - ) - addr = ipaddress.ip_address(svc.get("spec", {}).get("clusterIP", "")) - guard( - addr in ipaddress.ip_network("10.43.0.0/16") - and any( - p.get("port") == 8200 and p.get("protocol", "TCP") == "TCP" - for p in svc["spec"].get("ports", []) - ), - "openbao_service_mismatch", - ) - self.address = f"http://{addr}:8200/v1/" - sa = json.loads( - command(kube + ["-n", application, "get", "serviceaccount", SA, "-o", "json"]) - ) - guard( - sa.get("automountServiceAccountToken") is False, "non_mounting_service_account_required" - ) - jwt = ( - command( - kube - + ["-n", application, "create", "token", SA, "--audience=openbao", "--duration=10m"] - ) - .decode() - .strip() - ) - pieces = jwt.split(".") - guard(len(pieces) == 3 and len(jwt) < 16384, "bounded_jwt_required") - claims = json.loads(base64.urlsafe_b64decode(pieces[1] + "=" * (-len(pieces[1]) % 4))) - bound = claims.get("kubernetes.io", {}).get("serviceaccount", {}) - guard( - claims.get("sub") == f"system:serviceaccount:{application}:{SA}" - and claims.get("aud") == ["openbao"] - and bound.get("uid") == sa.get("metadata", {}).get("uid") - and bound.get("uid") - and 0 < claims.get("exp", 0) - time.time() <= 660, - "jwt_binding_mismatch", - ) - return jwt - - def request(self, method, path, payload=None, token=None): - headers = {"Content-Type": "application/json"} - if token is not None: - headers["X-Vault-Token"] = token - body = None if payload is None else json.dumps(payload).encode() - request = urllib.request.Request( - self.address + path, data=body, headers=headers, method=method - ) - try: - with self.opener.open(request, timeout=20) as response: - raw = response.read(1048577) - guard(len(raw) <= 1048576, "openbao_response_too_large") - return {} if not raw else json.loads(raw) - except (urllib.error.URLError, OSError): - raise DeliveryError("openbao_request_failed") from None - - -def retrieve(native, application, expected_version): - role = expected_openbao(application)["auth"]["role"] - policy = expected_openbao(application)["policy_name"] - jwt = native.identity(application) - response = native.request("POST", "auth/kubernetes/login", {"role": role, "jwt": jwt}) - auth = response.get("auth", {}) - token = auth.get("client_token") - guard(isinstance(token, str) and token, "scoped_token_required") - try: - guard( - auth.get("policies") == [policy] and 0 < auth.get("lease_duration", 0) <= 300, - "login_scope_mismatch", - ) - info = native.request("GET", "auth/token/lookup-self", token=token).get("data", {}) - guard( - info.get("policies") == [policy] - and not info.get("identity_policies") - and 0 < info.get("ttl", 0) <= 300 - and (info.get("period") or 0) == 0 - and 0 < info.get("explicit_max_ttl", 0) <= 300 - and 0 < info.get("num_uses", 0) <= 4, - "reader_scope_mismatch", - ) - record = native.request( - "GET", f"platform/data/workloads/{application}/backup-primary", token=token - ).get("data", {}) - metadata = record.get("metadata", {}) - guard( - type(expected_version) is int - and expected_version > 0 - and metadata.get("version") == expected_version - and metadata.get("destroyed") is False - and not metadata.get("deletion_time"), - "custody_version_mismatch", - ) - values = record.get("data", {}) - guard(set(values) == set(FIELDS), "credential_fields_mismatch") - # Scaleway/AWS key characters; prevents EnvironmentFile or shell injection. - for name in FIELDS: - guard( - isinstance(values[name], str) - and re.fullmatch(r"[A-Za-z0-9/+=_.-]{8,256}", values[name]), - "credential_encoding_rejected", - ) - return "".join(f"{name}={values[name]}\n" for name in FIELDS).encode() - finally: - # Revoke BEFORE publishing; failed revocation never admits a delivery. - try: - native.request("POST", "auth/token/revoke-self", {}, token=token) - except Exception: # noqa: BLE001 - contain all backend failures without disclosing values - raise DeliveryError("token_revocation_unconfirmed") from None - - -def private_directory(path, uid=0): - # O_NOFOLLOW checks every ancestor, including /etc and the private leaf. - fd = os.open("/", os.O_RDONLY | os.O_DIRECTORY) - try: - for part in Path(path).parts[1:]: - new = os.open(part, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW, dir_fd=fd) - os.close(fd) - fd = new - info = os.fstat(fd) - guard( - info.st_uid == uid and info.st_gid == uid and stat.S_IMODE(info.st_mode) == 0o700, - "private_directory_required", - ) - return fd - except BaseException: - os.close(fd) - raise - - -def private_read(directory_fd, name, uid=0): - fd = os.open(name, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK, dir_fd=directory_fd) - with os.fdopen(fd, "rb") as stream: - info = os.fstat(stream.fileno()) - guard( - stat.S_ISREG(info.st_mode) - and info.st_uid == uid - and info.st_gid == uid - and stat.S_IMODE(info.st_mode) == 0o600 - and info.st_nlink == 1, - "private_file_required", - ) - body = stream.read(65537) - guard(len(body) <= 65536, "private_file_too_large") - return body - - -def publish(directory_fd, name, body, uid=0): - try: - existing = private_read(directory_fd, name, uid) - except FileNotFoundError: - existing = None - if existing is not None: - guard(existing == body, "existing_delivery_differs_owner_rotation_required") - return "unchanged" - temporary = f".{name}.{os.getpid()}.{os.urandom(8).hex()}" - fd = os.open( - temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=directory_fd - ) - try: - with os.fdopen(fd, "wb") as stream: - os.fchmod(stream.fileno(), 0o600) - stream.write(body) - stream.flush() - os.fsync(stream.fileno()) - os.link( - temporary, name, src_dir_fd=directory_fd, dst_dir_fd=directory_fd, follow_symlinks=False - ) - finally: - os.unlink(temporary, dir_fd=directory_fd) - os.fsync(directory_fd) - guard(private_read(directory_fd, name, uid) == body, "delivery_readback_failed") - return "created" - - -def deliver(ccr, digest, application, version, native, directory_fd, uid=0): - guard(type(version) is int and version > 0, "explicit_custody_version_required") - lock = os.open( - "delivery.lock", - os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW | os.O_NONBLOCK, - 0o600, - dir_fd=directory_fd, - ) - try: - info = os.fstat(lock) - guard( - stat.S_ISREG(info.st_mode) - and info.st_uid == uid - and info.st_gid == uid - and info.st_nlink == 1 - and stat.S_IMODE(info.st_mode) == 0o600, - "private_lock_required", - ) - fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - payload = retrieve(native, application, version) - outcome = publish(directory_fd, "primary.env", payload, uid) - receipt = { - "schema": "platform.application-backup-credential-delivery.v1", - "status": "delivered", - "application": application, - "ccr": ccr["id"], - "ccr_sha256": digest, - "cluster_uid": CLUSTER_UID, - "kv_path": ccr["openbao"]["kv_path"], - "kv_version": version, - "target": ccr["delivery"]["target"], - "owner": "root:root", - "mode": "0600", - "directory_mode": "0700", - "token_revoked": True, - "readback_equal": True, - "outcome": outcome, - "delivered_at": dt.datetime.now(dt.timezone.utc).isoformat(), - } - # One immutable metadata receipt per run. Crash after file publication is - # recoverable by exact equality on a fresh scoped read, never overwrite. - name = f"delivery-v{version}-{time.time_ns()}.json" - publish(directory_fd, name, (json.dumps(receipt, sort_keys=True) + "\n").encode(), uid) - return receipt - finally: - os.close(lock) - - -def terminate(signum, frame): - raise DeliveryError("delivery_interrupted") - - -def main(argv=None): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--application", choices=PROFILES, required=True) - parser.add_argument("--ccr", type=Path, required=True) - parser.add_argument("--expected-ccr-sha256") - parser.add_argument("--expected-kv-version", type=int) - parser.add_argument("--execute", action="store_true") - args = parser.parse_args(argv) - signal.signal(signal.SIGTERM, terminate) - try: - ccr, digest = contract( - args.ccr.read_bytes(), args.application, args.execute, args.expected_ccr_sha256 - ) - if not args.execute: - print( - json.dumps( - { - "status": "preview", - "ccr": ccr["id"], - "ccr_sha256": digest, - "application": args.application, - "target": ccr["delivery"]["target"], - "effects": [], - }, - sort_keys=True, - ) - ) - return 0 - guard(os.geteuid() == 0, "admitted_node_root_required") - guard( - type(args.expected_kv_version) is int and args.expected_kv_version > 0, - "explicit_custody_version_required", - ) - directory = private_directory(PROFILES[args.application]["directory"]) - try: - receipt = deliver( - ccr, digest, args.application, args.expected_kv_version, Native(), directory - ) - finally: - os.close(directory) - print(json.dumps(receipt, sort_keys=True)) - return 0 - except DeliveryError as exc: - print(json.dumps({"status": "failed", "code": str(exc)}), file=sys.stderr) - except Exception: # noqa: BLE001 - CLI is the final secret-containment boundary - # Never stringify backend exceptions, response bodies or private paths. - print('{"status":"failed","code":"contained_delivery_failure"}', file=sys.stderr) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/application_backup_pair_custody.py b/scripts/application_backup_pair_custody.py deleted file mode 100644 index 412c6c0..0000000 --- a/scripts/application_backup_pair_custody.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Preview custody for the two backup publisher pairs. - -A later attended act may store both AWS fields in one CAS=0 write. This -command does not generate provider keys, contact OpenBao, or perform that -write. The delivery reader in application_backup_credentials.py cannot -create or rotate these values. -""" - -from __future__ import annotations - -import argparse -import json -import re -import sys -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -FIELDS = ("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY") -WRITER_POLICY = "backup-pair-writer" -# Founder admission 2026-10-08. The installed OpenBao policy matches -# openbao/policies/backup-pair-writer.hcl. The CLI still does not write. -WRITER_ADMITTED = True -PROFILES = { - "coulomb-social": "CCR-2026-0036", - "email-connect": "CCR-2026-0037", -} -VALUE = re.compile(r"[!-~]{16,128}\Z") - - -class CustodyError(Exception): - """Fixed diagnostic. It never carries key material.""" - - -def guard(condition, code): - if not condition: - raise CustodyError(code) - - -def kv_path(application): - guard(application in PROFILES, "unknown_application") - return f"platform/workloads/{application}/backup-primary" - - -def ccr_path(application): - prefix = PROFILES[application] - return ROOT / "credential-change-requests" / f"{prefix}-{application}-backup-primary.yaml" - - -def preview(application): - raw = ccr_path(application).read_text(encoding="utf-8") - document = json.loads(raw) - guard(document.get("id") == PROFILES[application], "ccr_mismatch") - guard(document.get("openbao", {}).get("fields") == list(FIELDS), "field_mismatch") - guard(document.get("openbao", {}).get("kv_path") == kv_path(application), "path_mismatch") - return { - "status": "preview", - "application": application, - "ccr": document["id"], - "ccr_status": document.get("status"), - "kv_path": kv_path(application), - "fields": list(FIELDS), - "cas": 0, - "writes": 1, - "writer_policy": WRITER_POLICY, - "writer_admitted": WRITER_ADMITTED, - "provider_keys": "not_created", - "openbao_write": False, - "effects": [], - } - - -def validate_pair(values): - guard(isinstance(values, dict) and set(values) == set(FIELDS), "exact_pair_required") - stored = [] - for name in FIELDS: - value = values[name] - guard(isinstance(value, str) and VALUE.fullmatch(value), "invalid_pair_value") - stored.append(value) - guard(stored[0] != stored[1], "pair_values_must_differ") - return {name: values[name] for name in FIELDS} - - -def assert_writer(identity, application): - """Refuse the delivery reader, a generic grant, and any unreviewed writer.""" - guard(WRITER_ADMITTED, "writer_identity_unreviewed") - policies = identity.get("policies") - reader = f"workload-kv-read-{application}-backup-primary" - guard( - policies == [WRITER_POLICY] - and identity.get("identity_policies") == [] - and reader not in policies - and "default" not in policies, - "writer_identity_unreviewed", - ) - ttl = identity.get("ttl") - uses = identity.get("num_uses") - guard(type(ttl) is int and 0 < ttl <= 300, "writer_ttl_unbounded") - guard(type(uses) is int and 0 < uses <= 2, "writer_uses_unbounded") - - -def provision(application, values, identity, transport): - """One metadata check and one CAS=0 write. Tests inject transport. - - Live use stays disconnected while WRITER_ADMITTED is false. - """ - path = kv_path(application) - pair = validate_pair(values) - assert_writer(identity, application) - metadata = transport.metadata(f"platform/metadata/workloads/{application}/backup-primary") - guard(metadata is None, "existing_custody_requires_rotation_review") - written = transport.put_cas0( - f"platform/data/workloads/{application}/backup-primary", - {"options": {"cas": 0}, "data": pair}, - ) - try: - guard(written.get("version") == 1 and set(written) <= {"version", "request_id"}, "unexpected_version") - return { - "application": application, - "kv_path": path, - "cas": 0, - "kv_version": 1, - "field_names": list(FIELDS), - "request_id": written.get("request_id"), - "secret_material": False, - } - finally: - transport.revoke() - - -def main(argv=None): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--application", required=True, choices=sorted(PROFILES)) - args = parser.parse_args(argv) - json.dump(preview(args.application), sys.stdout, indent=2) - sys.stdout.write("\n") - return 0 - - -if __name__ == "__main__": - try: - raise SystemExit(main()) - except CustodyError as exc: - print(str(exc), file=sys.stderr) - raise SystemExit(2) from None diff --git a/scripts/application_recovery_access.py b/scripts/application_recovery_access.py deleted file mode 100644 index 4130540..0000000 --- a/scripts/application_recovery_access.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Routine Coulomb recovery access; scoped custodian API, no Console session.""" -from __future__ import annotations - -import argparse -import base64 -import json -import os -import sys -from pathlib import Path - -from application_recovery_identities import Refused, private_directory, private_json -from application_recovery_resources import API, SECRET_BASE, public_recipient, status - -CONTRACT = Path(__file__).resolve().parents[1] / "docs/credential-lane-designs/coulomb-recovery-api.json" -PROJECT = "06d107bc-9d84-4672-b2a0-95701cc0c07f" - - -def retrieve(api, contract, material): - if (contract.get("schema") != "platform.coulomb-recovery-api.v1" - or contract.get("project_id") != PROJECT): - raise Refused("exact_coulomb_contract_required") - reference = contract["materials"][material] - path = SECRET_BASE + "/" + reference["id"] - metadata = api("GET", path) - if (metadata.get("id") != reference["id"] or metadata.get("name") != reference["name"] - or metadata.get("project_id") != PROJECT or metadata.get("protected") is not True - or metadata.get("type") != "opaque" or metadata.get("ephemeral_policy") is not None - or metadata.get("deletion_requested_at") is not None): - raise Refused("protected_exact_escrow_required") - version = api("GET", path + "/versions/" + str(reference["revision"])) - properties = version.get("ephemeral_properties") or {} - if (version.get("status") != "enabled" or properties.get("expires_at") - or properties.get("expires_once_accessed")): - raise Refused("enabled_nonexpiring_exact_version_required") - response = api("GET", path + "/versions/" + str(reference["revision"]) + "/access") - if response.get("secret_id") != reference["id"] or response.get("revision") != reference["revision"]: - raise Refused("escrow_response_binding_refused") - payload = base64.b64decode(response["data"], validate=True) - if material == "age": - if public_recipient(payload) != contract["public_recipient"]: - raise Refused("public_recipient_differs") - else: - pair = json.loads(payload) - if set(pair) != {"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"} or any( - not isinstance(v, str) or not v or "\n" in v for v in pair.values() - ): - raise Refused("exact_api_pair_required") - return payload - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--execute", action="store_true") - parser.add_argument("--directory", type=Path, default=Path.home() / ".config/railiance-platform/coulomb-social-recovery") - parser.add_argument("--material", choices=("age", "reader-pair", "custodian-pair"), default="age") - parser.add_argument("--output", type=Path) - args = parser.parse_args() - if not args.execute: - print(json.dumps({"status": "preview", "material": args.material, "provider_contacted": False, - "personal_login_required": False})) - return - private_directory(args.directory) - if args.output: - if not args.output.is_absolute(): - raise Refused("absolute_private_output_required") - private_directory(args.output.parent) - if args.output.exists() or args.output.is_symlink(): - raise Refused("fresh_private_output_required") - contract = json.loads(CONTRACT.read_text()) - pair = private_json(args.directory / "custodian-pair.json") - payload = retrieve(API(pair), contract, args.material) - if args.output: - fd = os.open(args.output, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "wb") as stream: - stream.write(payload) - stream.flush() - os.fsync(stream.fileno()) - print(json.dumps({"status": "scoped_api_retrieval_verified", "material": args.material, - "private_output_created": bool(args.output), "credential_values_printed": False, - "personal_session_used": False})) - - -if __name__ == "__main__": - try: - main() - except Exception as error: # noqa: BLE001 -- suppress secret-bearing provider diagnostics - print(json.dumps({"status": "refused", "failure_type": type(error).__name__, - "native_status": status(error)})) - sys.exit(1) diff --git a/scripts/application_recovery_drill.py b/scripts/application_recovery_drill.py deleted file mode 100644 index f4bc0c2..0000000 --- a/scripts/application_recovery_drill.py +++ /dev/null @@ -1,181 +0,0 @@ -"""Timed Coulomb recovery using only the provider kit, escrow and held API callers.""" -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import re -import subprocess -import sys -import tarfile -import time -from datetime import UTC, datetime -from pathlib import Path - -from application_recovery_access import retrieve -from application_recovery_identities import ( - Refused, - checkpoint, - private_directory, - private_json, -) -from application_recovery_kit import PROJECT, download, protect_receipt -from application_recovery_resources import API, s3, status - - -def extract_verified(archive, destination): - if destination.exists() or destination.is_symlink(): - raise Refused("fresh_kit_extraction_required") - with tarfile.open(archive, "r:") as bundle: - members = bundle.getmembers() - names = [m.name for m in members] - if (not 1 < len(members) < 10000 or len(set(names)) != len(names) - or any(not m.isfile() or m.sparse is not None or Path(m.name).is_absolute() - or ".." in Path(m.name).parts for m in members) - or sum(m.size for m in members) > 5 * 1024**3): - raise Refused("bounded_regular_unique_kit_members_required") - metadata = bundle.getmember("manifest.json") - if not 0 < metadata.size <= 1024**2: - raise Refused("bounded_kit_manifest_required") - manifest = json.loads(bundle.extractfile(metadata).read()) - if manifest.get("schema") != "platform.coulomb-recovery-kit-manifest.v1": - raise Refused("exact_kit_manifest_required") - files = manifest["files"] - if set(files) | {"manifest.json"} != set(names): - raise Refused("complete_manifest_members_required") - if manifest.get("private_identity_included") is not False or manifest.get("api_pairs_included") is not False: - raise Refused("separate_credential_custody_required") - for member in members: - if member.name == "manifest.json": - continue - expected = files[member.name] - if (member.size != expected["bytes"] or not re.fullmatch(r"[a-f0-9]{64}", expected["sha256"])): - raise Refused("kit_member_binding_differs") - with bundle.extractfile(member) as stream: - if hashlib.file_digest(stream, "sha256").hexdigest() != expected["sha256"]: - raise Refused("kit_member_hash_differs") - # No executable or image is used until every member is verified. - destination.mkdir(mode=0o700) - for member in members: - target = destination / member.name - target.parent.mkdir(parents=True, exist_ok=True, mode=0o700) - fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with bundle.extractfile(member) as source, os.fdopen(fd, "wb") as output: - while block := source.read(8 * 1024**2): - output.write(block) - return manifest - - -class Drill: - def __init__(self, directory, destination, reference): - private_directory(directory) - private_directory(destination.parent) - if destination.exists() or destination.is_symlink(): - raise Refused("fresh_private_drill_destination_required") - destination.mkdir(mode=0o700) - self.directory, self.root, self.reference = directory, destination, reference - self.stages = {} - - def stage(self, name, call): - began = time.monotonic() - value = call() - self.stages[name] = round(time.monotonic() - began, 3) - checkpoint(self.root / "stage-receipt.json", {"stages": self.stages, "last_completed": name}) - return value - - def command(self, name, arguments): - result = subprocess.run(arguments, capture_output=True, timeout=300, check=False) - if result.returncode: - fd = os.open(self.root / (name + "-private-diagnostics.txt"), - os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "wb") as stream: - stream.write(result.stdout + result.stderr) - raise Refused("private_drill_child_refused") - return result.stdout - - def run(self): - began, started = time.monotonic(), datetime.now(UTC).isoformat() - kit = self.root / "retrieved-kit.tar" - reader = s3(private_json(self.directory / "reader-pair.json"), PROJECT) - self.stage("provider_kit_retrieval", lambda: download(reader, self.reference, kit)) - contents = self.root / "kit" - manifest = self.stage("all_kit_member_hashes_and_extraction", lambda: extract_verified(kit, contents)) - contract = json.loads((contents / "tools/docs/credential-lane-designs/coulomb-recovery-api.json").read_text()) - custodian = API(private_json(self.directory / "custodian-pair.json")) - identity = self.stage("provider_age_escrow_retrieval", lambda: retrieve(custodian, contract, "age")) - identity_path = self.root / "identity.agekey" - fd = os.open(identity_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "wb") as stream: - stream.write(identity) - del identity - for kind in ("application", "postgres"): - self.stage(kind + "_image_load", lambda kind=kind: self.command(kind + "-image-load", - ["docker", "load", "-i", str(contents / (kind + "-image.tar"))])) - image_id = manifest["application_image_id" if kind == "application" else "postgres_image_id"] - actual = self.command(kind + "-image-inspect", ["docker", "image", "inspect", image_id, - "--format", "{{.Id}}"]) - if actual.decode().strip() != image_id: - raise Refused("retained_image_id_differs") - tools = contents / "tools" - pair = self.root / "pair" - unpacked = self.stage("decrypt_and_validate_pair", lambda: self.command("decrypt", [sys.executable, - str(tools / "recover-paired-backup.py"), "--execute", "--archive", str(contents / "packet.tar.age"), - "--transfer-receipt", str(contents / "primary-receipt.json"), "--identity", str(identity_path), - "--destination", str(pair)])) - unpacked = json.loads(unpacked) - restored = self.stage("isolated_database_content_restore", lambda: self.command("restore", [sys.executable, - str(tools / "restore-paired-snapshot.py"), str(pair), "--keep-instance", "--postgres-image", manifest["postgres_image_id"]])) - restored = json.loads(restored) - app = self.stage("packaged_app_views_and_permissions", lambda: self.command("application", [sys.executable, - str(tools / "verify-restored-application.py"), "--snapshot", str(pair), "--restore-receipt", - str(Path(restored["restored_directory"]) / "restore-receipt.json"), "--kit-manifest", str(contents / "manifest.json"), - "--output", str(self.root / "application-receipt.json")])) - app = json.loads(app) - baseline = json.loads((contents / "application-recovery.json").read_text()) - point = json.loads((contents / "primary-receipt.json").read_text()) - for key in ("actual_pages_rendered_through_view", "same_tenant_ungranted_page_denials", "content_files_unchanged"): - if app.get(key) != baseline.get(key): - raise Refused("kit_application_baseline_differs") - if app["status"] != "isolated_application_recovery_verified": - raise Refused("actual_application_recovery_required") - result = {"schema": "platform.coulomb-provider-kit-recovery.v1", "status": "application_restored_verified", - "started_at": started, "completed_at": datetime.now(UTC).isoformat(), "stages_seconds": self.stages, - "recovery_seconds": round(time.monotonic() - began, 3), "protected_kit": self.reference, - "kit_members_verified": len(manifest["files"]) + 1, "application": "coulomb-social", - "captured_at": manifest["captured_at"], "application_restore_verified": True, - "application_data_images_and_recovery_tools_from_provider_kit": True, - "bootstrap_api_client_preinstalled": True, - "age_identity_from_separate_provider_escrow": True, "original_node_or_plaintext_used": False, - "personal_session_used": False, "registry_pulls_performed": False, "fresh_docker_daemon": False, - "operator_account_recovery_time_included": False, "real_browser_journey_verified": False, - "unpack": unpacked, "restore": restored, "application_checks": app, "timers_activated": False} - result.update({k: point[k] for k in ("object_key", "version_id", "ciphertext_sha256", "ciphertext_bytes", - "image", "age_recipient", "database_sha256", "content_sha256")}) - result["protected_recovery_proof"] = protect_receipt( - s3(private_json(self.directory / "custodian-pair.json"), PROJECT), reader, result) - checkpoint(self.root / "recovery-receipt.json", result) - return result - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--execute", action="store_true") - parser.add_argument("--directory", type=Path, default=Path.home() / ".config/railiance-platform/coulomb-social-recovery") - parser.add_argument("--kit-reference", type=Path, required=True) - parser.add_argument("--destination", type=Path, required=True) - args = parser.parse_args() - if not args.execute: - print(json.dumps({"status": "preview", "provider_contacted": False, "production_restore": False})) - return - result = Drill(args.directory, args.destination, private_json(args.kit_reference)).run() - print(json.dumps({k: result[k] for k in ("status", "recovery_seconds", "kit_members_verified", "stages_seconds")})) - - -if __name__ == "__main__": - try: - main() - except Exception as error: # noqa: BLE001 -- child diagnostics stay owner-private - print(json.dumps({"status": "refused", "failure_type": type(error).__name__, "native_status": status(error), - "reason": str(error) if isinstance(error, Refused) else "private_review_required"})) - sys.exit(1) diff --git a/scripts/application_recovery_identities.py b/scripts/application_recovery_identities.py deleted file mode 100644 index 5c7507c..0000000 --- a/scripts/application_recovery_identities.py +++ /dev/null @@ -1,400 +0,0 @@ -"""Coulomb-only identity bootstrap; stdin auth is temporary, API pairs stay private. - -No personal Console session is a routine credential. Default preview has no -effects. Native S3/escrow boundary checks remain mandatory after this IAM stage. -""" - -from __future__ import annotations - -import argparse -import fcntl -import json -import os -import stat -import sys -import tempfile -from datetime import UTC, datetime, timedelta -from pathlib import Path -from urllib.error import HTTPError -from urllib.parse import urlencode -from urllib.request import HTTPRedirectHandler, Request, build_opener - -PUBLISHER = "681d00bf-b965-4323-b657-97bfde423511" -PROJECT = "coulomb-social-recovery" -DECISION = "27bbc1ac-e275-42be-ba34-6cf16eae4e78" -PROGRAMMATIC_DIRECTION = "6139e02b-3fb7-4eff-be8a-efe127c45ce5" -ROLES = { - "bootstrap": {"name": "coulomb-social-recovery-bootstrap", "permissions": [ - "ObjectStorageFullAccess", "SecretManagerFullAccess"]}, - "reader": {"name": "coulomb-social-recovery-reader", "permissions": [ - "ObjectStorageReadOnly"]}, - "custodian": {"name": "coulomb-social-recovery-custodian", "permissions": [ - "ObjectStorageReadOnly", "ObjectStorageObjectsWrite", "SecretManagerReadOnly", - "SecretManagerSecretAccess"]}, - "publisher": {"name": "coulomb-social-backup-primary", "permissions": [ - "ObjectStorageObjectsRead", "ObjectStorageObjectsWrite", "ObjectStorageObjectsDelete", - "ObjectStorageBucketsRead"]}, -} - - -class Refused(RuntimeError): - pass - - -class ProviderError(Refused): - def __init__(self, status): - super().__init__("provider_request_refused") - self.status = status - - -def inside_git(path): - return any((parent / ".git").exists() for parent in [path.resolve(), *path.resolve().parents]) - - -def private_directory(path): - info = path.lstat() - if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.geteuid() or info.st_mode & 0o077: - raise Refused("private_owner_directory_required") - if inside_git(path): - raise Refused("credential_directory_must_be_outside_git") - - -def fsync_directory(path): - fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY) - try: - os.fsync(fd) - finally: - os.close(fd) - - -def private_json(path): - info = path.lstat() - if not stat.S_ISREG(info.st_mode) or info.st_uid != os.geteuid() or info.st_mode & 0o077: - raise Refused("private_owner_file_required") - return json.loads(path.read_text()) - - -def checkpoint(path, value): - fd, name = tempfile.mkstemp(prefix=".checkpoint-", dir=path.parent) - try: - with os.fdopen(fd, "w") as stream: - json.dump(value, stream, sort_keys=True, indent=2) - stream.write("\n") - stream.flush() - os.fsync(stream.fileno()) - os.replace(name, path) - fsync_directory(path.parent) - finally: - if os.path.exists(name): - os.unlink(name) - - -class Transport: - def __init__(self, auth): - if set(auth) != {"session_token", "user_agent"} or any( - not isinstance(v, str) or not v or "\n" in v or "\r" in v for v in auth.values() - ): - raise Refused("temporary_stdin_auth_required") - self.headers = {"X-Session-Token": auth["session_token"], - "User-Agent": auth["user_agent"], "Content-Type": "application/json"} - - class NoRedirect(HTTPRedirectHandler): - def redirect_request(self, *args, **kwargs): - raise Refused("provider_redirect_refused") - - self.opener = build_opener(NoRedirect()) - - def __call__(self, method, path, data=None): - if not path.startswith(("/iam/v1alpha1/", "/account/v3/")) or ".." in path: - raise Refused("provider_api_scope_refused") - request = Request("https://api.scaleway.com" + path, method=method, - headers=self.headers, - data=None if data is None else json.dumps(data).encode()) - try: - with self.opener.open(request, timeout=20) as response: - raw = response.read(8 * 1024**2) - return json.loads(raw) if raw else {} - except HTTPError as error: - raise ProviderError(error.code) from None - - -class Bootstrap: - def __init__(self, api, directory, now=None): - self.api, self.directory = api, directory - private_directory(directory) - self.now = now or datetime.now(UTC) - self.path = directory / "identity-state.json" - self.state = private_json(self.path) if self.path.exists() else { - "schema": "platform.coulomb-recovery-identities.v1", "decision": DECISION, - "access_decision": PROGRAMMATIC_DIRECTION, "apps": {}, "policies": {}, "keys": {}, - } - if self.state.get("decision") != DECISION or self.state.get("access_decision") != ( - PROGRAMMATIC_DIRECTION - ) or self.state.get("schema") != "platform.coulomb-recovery-identities.v1": - raise Refused("journal_scope_refused") - if self.state.get("pending_key_issue"): - raise Refused("uncertain_key_issue_needs_reconciliation") - - def save(self): - checkpoint(self.path, self.state) - - def inventory(self, path, key, **filters): - rows = [] - for page in range(1, 101): - value = self.api("GET", path + "?" + urlencode({**filters, "page_size": 100, - "page": page})) - batch = value[key] - if not isinstance(batch, list): - raise Refused("invalid_provider_inventory") - rows.extend(batch) - count = int(value["total_count"]) - if len(rows) == count: - return rows - if not batch or len(rows) > count: - break - raise Refused("complete_inventory_required") - - def project(self, organization): - if self.state.get("project"): - project = self.api("GET", "/account/v3/projects/" + self.state["project"]["id"]) - else: - found = [v for v in self.inventory("/account/v3/projects", "projects", - organization_id=organization) if v["name"] == PROJECT] - if found: - raise Refused("untracked_existing_project_requires_review") - project = self.api("POST", "/account/v3/projects", { - "name": PROJECT, "organization_id": organization, - "description": "Coulomb-only recovery custody; decision " + DECISION, - }) - self.state["project"] = project - self.save() - if project["name"] != PROJECT or project["organization_id"] != organization: - raise Refused("project_binding_refused") - return project - - def application(self, role, organization, applications): - spec = ROLES[role] - if role == "publisher": - app = self.api("GET", "/iam/v1alpha1/applications/" + PUBLISHER) - elif role in self.state["apps"]: - app = self.api("GET", "/iam/v1alpha1/applications/" + self.state["apps"][role]["id"]) - else: - if any(v["name"] == spec["name"] for v in applications): - raise Refused("untracked_existing_application_requires_review") - app = self.api("POST", "/iam/v1alpha1/applications", { - "name": spec["name"], "organization_id": organization, - "description": "Coulomb-only " + role + "; " + DECISION, - }) - self.state["apps"][role] = app - self.save() - if app["name"] != spec["name"] or app["organization_id"] != organization: - raise Refused("application_binding_refused") - return app - - def policy(self, role, app, project, organization): - name = "coulomb-recovery-project-" + role - if role in self.state["policies"]: - policy = self.api("GET", "/iam/v1alpha1/policies/" + self.state["policies"][role]["id"]) - else: - policies = self.inventory("/iam/v1alpha1/policies", "policies", - organization_id=organization) - if any(p["name"] == name for p in policies): - raise Refused("untracked_existing_policy_requires_review") - policy = self.api("POST", "/iam/v1alpha1/policies", { - "name": name, "description": "Coulomb recovery project only; " + DECISION, - "organization_id": organization, "application_id": app["id"], - "rules": [{"project_ids": [project["id"]], - "permission_set_names": ROLES[role]["permissions"]}], - }) - self.state["policies"][role] = policy - self.save() - if (policy["application_id"] != app["id"] or policy["name"] != name - or policy["organization_id"] != organization): - raise Refused("policy_binding_refused") - rules = self.api("GET", "/iam/v1alpha1/rules?" + urlencode({"policy_id": policy["id"]})) - if len(rules["rules"]) != 1: - raise Refused("single_project_rule_required") - rule = rules["rules"][0] - if (rule.get("organization_id") or rule.get("project_ids") != [project["id"]] - or set(rule.get("permission_set_names", [])) != set(ROLES[role]["permissions"]) - or rule.get("condition") or rule.get("deny")): - raise Refused("effective_project_rule_refused") - - def key(self, role, app, project): - path = self.directory / (role + "-pair.json") - # A paused attended window can outlast the bounded construction key. - # Renew only this role, after exact own-key revocation/readback. Never - # replace reader/custodian/publisher credentials as a bootstrap retry. - prior = self.state["keys"].get(role, {}) - if role == "bootstrap" and prior: - expired = datetime.fromisoformat(prior["expires_at"].replace("Z", "+00:00")) <= self.now - if expired: - if path.exists(): - old = private_json(path) - actual = self.api("GET", "/iam/v1alpha1/api-keys/" + old["AWS_ACCESS_KEY_ID"]) - native_expiry = datetime.fromisoformat(actual["expires_at"].replace("Z", "+00:00")) - if native_expiry > self.now: - raise Refused("native_bootstrap_expiry_not_reached") - self.retire_bootstrap() - self.state.setdefault("retired_bootstrap_keys", []).append(dict(prior)) - self.state["keys"].pop(role) - self.save() - if role in self.state["keys"]: - pair = private_json(path) - if set(pair) != {"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"}: - raise Refused("exact_pair_fields_required") - current = self.api("GET", "/iam/v1alpha1/api-keys/" + pair["AWS_ACCESS_KEY_ID"]) - if (current["application_id"] != app["id"] - or current["default_project_id"] != project["id"]): - raise Refused("existing_key_binding_refused") - return - if path.exists() or path.is_symlink(): - raise Refused("untracked_pair_requires_review") - self.state["pending_key_issue"] = {"role": role, "application_id": app["id"]} - self.save() - payload = {"application_id": app["id"], "default_project_id": project["id"], - "description": "Coulomb recovery " + role + "; " + DECISION} - if role == "bootstrap": - payload["expires_at"] = (self.now + timedelta(hours=2)).isoformat() - try: - issued = self.api("POST", "/iam/v1alpha1/api-keys", payload) - except ProviderError as error: - if error.status in {400, 401, 403, 404, 422, 429}: - self.state.pop("pending_key_issue") - self.save() - raise - try: - if issued.get("application_id") != app["id"]: - raise Refused("issued_key_binding_refused") - if role == "bootstrap": - expires = datetime.fromisoformat( - (issued.get("expires_at") or "").replace("Z", "+00:00")) - if not self.now < expires <= self.now + timedelta(hours=2): - raise Refused("bounded_bootstrap_expiry_required") - pair = {"AWS_ACCESS_KEY_ID": issued["access_key"], - "AWS_SECRET_ACCESS_KEY": issued["secret_key"]} - if any(not isinstance(v, str) or not v for v in pair.values()): - raise Refused("complete_pair_required") - fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "w") as stream: - json.dump(pair, stream) - stream.write("\n") - stream.flush() - os.fsync(stream.fileno()) - fsync_directory(path.parent) - except (Refused, OSError, KeyError, ValueError, TypeError): - if issued.get("access_key") and issued.get("application_id") == app["id"]: - try: - self.api("DELETE", "/iam/v1alpha1/api-keys/" + issued["access_key"]) - self.state.pop("pending_key_issue") - self.save() - except (Refused, OSError, KeyError, ValueError, TypeError): - raise Refused("pair_persistence_and_revocation_failed_review_required") from None - raise Refused("pair_persistence_failed_review_required") from None - self.state["keys"][role] = {"application_id": app["id"], "file": path.name, - "expires_at": issued.get("expires_at")} - self.state.pop("pending_key_issue") - self.save() - - def run(self): - publisher = self.api("GET", "/iam/v1alpha1/applications/" + PUBLISHER) - if publisher["id"] != PUBLISHER or publisher["name"] != ROLES["publisher"]["name"]: - raise Refused("publisher_binding_refused") - organization = publisher["organization_id"] - permissions = self.inventory("/iam/v1alpha1/permission-sets", "permission_sets", - organization_id=organization) - names = {v["name"] for v in permissions} - if any(set(role["permissions"]) - names for role in ROLES.values()): - raise Refused("required_permission_sets_unavailable") - applications = self.inventory("/iam/v1alpha1/applications", "applications", - organization_id=organization) - project = self.project(organization) - for role in ROLES: - app = self.application(role, organization, applications) - self.policy(role, app, project, organization) - if role != "publisher": - self.key(role, app, project) - self.state["status"] = "identity_bootstrap_complete_native_boundaries_pending" - self.save() - return {"status": self.state["status"], "new_project": PROJECT, - "managed_recovery_applications": 3, "project_policies_verified": 4, - "private_pairs": 3, - "publisher_pair_replaced": False, "organization_wide_grants": False, - "bootstrap_key_max_hours": 2, "native_s3_escrow_boundaries_verified": False, - "credential_values_printed": False} - - def retire_bootstrap(self): - if self.state.get("status") != "identity_bootstrap_complete_native_boundaries_pending": - raise Refused("completed_identity_stage_required") - app = self.state["apps"]["bootstrap"] - actual = self.api("GET", "/iam/v1alpha1/applications/" + app["id"]) - if actual["name"] != ROLES["bootstrap"]["name"]: - raise Refused("bootstrap_application_binding_refused") - path = self.directory / "bootstrap-pair.json" - if self.state["keys"]["bootstrap"].get("revoked_verified") and not path.exists(): - return {"status": "bootstrap_api_key_retired", "credential_values_printed": False} - pair = private_json(path) - key_path = "/iam/v1alpha1/api-keys/" + pair["AWS_ACCESS_KEY_ID"] - try: - current = self.api("GET", key_path) - except ProviderError as error: - if error.status != 404: - raise - else: - if (current["application_id"] != app["id"] or current["default_project_id"] != ( - self.state["project"]["id"] - )): - raise Refused("retirement_key_binding_refused") - self.api("DELETE", key_path) - try: - self.api("GET", key_path) - except ProviderError as error: - if error.status != 404: - raise - else: - raise Refused("bootstrap_revocation_unconfirmed") - self.state["keys"]["bootstrap"]["revoked_verified"] = True - self.save() - path.unlink() # Only this just-revoked temporary pair, never retained archives/pairs. - fsync_directory(path.parent) - return {"status": "bootstrap_api_key_retired", "own_temporary_pair_removed": True, - "credential_values_printed": False} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--execute", action="store_true") - parser.add_argument("--directory", type=Path) - parser.add_argument("--retire-bootstrap", action="store_true") - args = parser.parse_args() - if not args.execute: - print(json.dumps({"status": "preview", "project": PROJECT, - "applications": [v["name"] for k, v in ROLES.items() if k != "publisher"], - "provider_contacted": False, "credential_values_printed": False})) - return 0 - if not args.directory or not args.directory.is_absolute(): - raise Refused("private_absolute_directory_required") - private_directory(args.directory) - fd = os.open(args.directory / "identity-bootstrap.lock", os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, - 0o600) - with os.fdopen(fd, "w") as lock: - fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - raw = sys.stdin.read(65537) - if len(raw) > 65536: - raise Refused("bounded_temporary_auth_required") - auth = json.loads(raw) - bootstrap = Bootstrap(Transport(auth), args.directory) - print(json.dumps(bootstrap.retire_bootstrap() if args.retire_bootstrap else bootstrap.run())) - return 0 - - -if __name__ == "__main__": - try: - raise SystemExit(main()) - except (Refused, OSError, KeyError, ValueError, TypeError) as error: - print(json.dumps({"status": "failed", "failure_type": type(error).__name__, - "http_status": getattr(error, "status", None), - "refusal": str(error) if isinstance(error, Refused) and all( - c.islower() or c == "_" for c in str(error)) else None, - "credential_values_printed": False})) - raise SystemExit(1) from None diff --git a/scripts/application_recovery_kit.py b/scripts/application_recovery_kit.py deleted file mode 100644 index 5a33487..0000000 --- a/scripts/application_recovery_kit.py +++ /dev/null @@ -1,151 +0,0 @@ -"""Immutable, nonexpiring Coulomb kit publication and independent retrieval.""" -from __future__ import annotations - -import argparse -import hashlib -import json -import re -import sys -from pathlib import Path - -import primary_application_archive as archive -from application_recovery_identities import ( - Refused, - checkpoint, - private_directory, - private_json, -) -from application_recovery_resources import s3, status - -PROJECT = "06d107bc-9d84-4672-b2a0-95701cc0c07f" -BUCKET = "railiance-coulomb-social-recovery" -MAX_BYTES = 5 * 1024**3 - - -def download(client, ref, output): - private_directory(output.parent) - archive.require_version(ref.get("version_id")) - if (ref.get("project_id") != PROJECT or ref.get("bucket") != BUCKET - or not re.fullmatch(r"kits/[a-f0-9]{64}/recovery-kit.tar", ref.get("object_key", "")) - or not re.fullmatch(r"[a-f0-9]{64}", ref.get("sha256", "")) - or type(ref.get("bytes")) is not int or not 0 < ref["bytes"] <= MAX_BYTES - or output.exists() or output.is_symlink()): - raise Refused("exact_kit_reference_and_fresh_output_required") - result = client.get_object(Bucket=BUCKET, Key=ref["object_key"], VersionId=ref["version_id"]) - if result.get("VersionId") != ref["version_id"] or result["ContentLength"] != ref["bytes"]: - result["Body"].close() - raise Refused("kit_version_or_length_differs") - size, hashed = 0, hashlib.sha256() - with result["Body"] as body, output.open("xb") as stream: - output.chmod(0o600) - while block := body.read(8 * 1024**2): - size += len(block) - if size > ref["bytes"]: - raise Refused("kit_readback_exceeds_size") - stream.write(block) - hashed.update(block) - if size != ref["bytes"] or hashed.hexdigest() != ref["sha256"]: - raise Refused("kit_readback_hash_differs") - return dict(ref, independently_retrieved_hash_verified=True, automatic_expiry=False) - - -def require_protected_bucket(client): - if client.get_bucket_versioning(Bucket=BUCKET).get("Status") != "Enabled": - raise Refused("protected_bucket_versioning_required") - try: - lifecycle = client.get_bucket_lifecycle_configuration(Bucket=BUCKET).get("Rules", []) - except Exception as error: - if status(error) != 404: - raise - lifecycle = [] - if lifecycle: - raise Refused("protected_bucket_must_have_no_lifecycle_expiry") - - -def protect_receipt(writer, reader, receipt): - """Retain the recovery proof independently, without a self-hash cycle.""" - require_protected_bucket(writer) - payload = json.dumps(receipt, sort_keys=True, separators=(",", ":")).encode() - if len(payload) > 1024**2 or receipt.get("status") != "application_restored_verified": - raise Refused("bounded_successful_recovery_proof_required") - expected = hashlib.sha256(payload).hexdigest() - key = "kits/receipts/" + expected + "/recovery-proof.json" - try: - head = writer.head_object(Bucket=BUCKET, Key=key) - except Exception as error: - if status(error) != 404: - raise - response = writer.put_object(Bucket=BUCKET, Key=key, Body=payload, - IfNoneMatch="*", Metadata={"sha256": expected}, ContentType="application/json") - version = archive.require_version(response.get("VersionId")) - else: - if head["ContentLength"] != len(payload) or head.get("Metadata", {}).get("sha256") != expected: - raise Refused("existing_proof_binding_differs") - version = archive.require_version(head.get("VersionId")) - response = reader.get_object(Bucket=BUCKET, Key=key, VersionId=version) - with response["Body"] as stream: - if response.get("VersionId") != version or stream.read(len(payload) + 1) != payload: - raise Refused("independent_proof_readback_differs") - return {"project_id": PROJECT, "bucket": BUCKET, "object_key": key, "version_id": version, - "sha256": expected, "bytes": len(payload), "independent_readback_verified": True, - "automatic_expiry": False} - - -def publish(client, source): - archive.regular_file(source) - require_protected_bucket(client) - size = source.stat().st_size - if not 0 < size <= MAX_BYTES: - raise Refused("bounded_kit_required") - expected = archive.digest(source) - key = "kits/" + expected + "/recovery-kit.tar" - try: - head = client.head_object(Bucket=BUCKET, Key=key) - except Exception as error: - if status(error) != 404: - raise - with source.open("rb") as stream: - created = client.put_object(Bucket=BUCKET, Key=key, Body=stream, - ContentLength=size, IfNoneMatch="*", Metadata={"sha256": expected}) - version = archive.require_version(created.get("VersionId")) - else: - if head["ContentLength"] != size or head.get("Metadata", {}).get("sha256") != expected: - raise Refused("existing_kit_binding_differs") - version = archive.require_version(head.get("VersionId")) - return {"schema": "platform.coulomb-protected-recovery-kit.v1", "project_id": PROJECT, - "bucket": BUCKET, "object_key": key, "version_id": version, - "sha256": expected, "bytes": size, "automatic_expiry": False} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("operation", choices=("publish", "retrieve")) - parser.add_argument("--execute", action="store_true") - parser.add_argument("--directory", type=Path, default=Path.home() / ".config/railiance-platform/coulomb-social-recovery") - parser.add_argument("--source", type=Path) - parser.add_argument("--receipt", type=Path, required=True) - parser.add_argument("--output", type=Path, required=True) - args = parser.parse_args() - if not args.execute: - print(json.dumps({"status": "preview", "provider_contacted": False, "automatic_expiry": False})) - return - private_directory(args.directory) - private_directory(args.receipt.parent) - if args.operation == "publish": - if not args.source or args.receipt.exists() or args.receipt.is_symlink(): - raise Refused("source_and_fresh_receipt_required") - ref = publish(s3(private_json(args.directory / "custodian-pair.json"), PROJECT), args.source) - checkpoint(args.receipt, ref) - else: - ref = private_json(args.receipt) - result = download(s3(private_json(args.directory / "reader-pair.json"), PROJECT), ref, args.output) - print(json.dumps({"status": "protected_kit_independently_retrieved_verified", "kit": result, - "application_recovery_from_kit_verified": False, "private_values_printed": False})) - - -if __name__ == "__main__": - try: - main() - except Exception as error: # noqa: BLE001 -- never print SDK content/credential diagnostics - print(json.dumps({"status": "refused", "failure_type": type(error).__name__, "native_status": status(error)})) - sys.exit(1) diff --git a/scripts/application_recovery_mirror.py b/scripts/application_recovery_mirror.py deleted file mode 100644 index 168dbc2..0000000 --- a/scripts/application_recovery_mirror.py +++ /dev/null @@ -1,141 +0,0 @@ -"""Coulomb immutable recovery mirror using the proven primary multipart engine. - -The adapter pins every logical request to Coulomb and remaps only to its own -isolated recovery project/bucket/prefix. Independent fetch uses the reader. -""" -from __future__ import annotations - -import argparse -import json -import re -import sys -from pathlib import Path - -import primary_application_archive as primary -from application_recovery_identities import ( - Refused, - checkpoint, - private_directory, - private_json, -) -from application_recovery_resources import s3, status - -PROJECT = "06d107bc-9d84-4672-b2a0-95701cc0c07f" -BUCKET = "railiance-coulomb-social-archives" -OPERATIONS = {"head_object", "get_object", "create_multipart_upload", "upload_part", - "complete_multipart_upload", "abort_multipart_upload"} - - -class Mirror: - def __init__(self, client): - self.client = client - - def __getattr__(self, operation): - if operation not in OPERATIONS: - raise Refused("mirror_operation_outside_contract") - def invoke(**arguments): - if arguments.get("Bucket") != primary.BUCKET: - raise Refused("mirror_logical_bucket_refused") - key = arguments.get("Key", "") - primary.validate_key("coulomb-social", key) - return getattr(self.client, operation)(**dict(arguments, Bucket=BUCKET, Key="archives/" + key)) - return invoke - - -def validate_receipt(receipt): - primary.validate_key("coulomb-social", receipt.get("object_key", "")) - primary.require_version(receipt.get("version_id")) - if (receipt.get("application") != "coulomb-social" - or receipt.get("schema") != "platform.application-primary-archive.v1" - or receipt.get("status") != "offsite_fetched_verified" - or receipt.get("remote_retrieval_verified") is not True - or receipt.get("destination") != "s3://" + primary.BUCKET + "/" + receipt["object_key"] - or receipt.get("immutable_create") is not True - or not re.fullmatch(r"[a-f0-9]{64}", receipt.get("ciphertext_sha256", "")) - or type(receipt.get("ciphertext_bytes")) is not int - or not 0 < receipt["ciphertext_bytes"] <= primary.MAX_BYTES): - raise Refused("verified_coulomb_primary_receipt_required") - - -def mirror(client, source, readback, receipt, journal, save): - validate_receipt(receipt) - binding = {k: receipt[k] for k in ("object_key", "version_id", "ciphertext_sha256", "ciphertext_bytes", - "captured_at", "image", "age_recipient", "database_sha256", "content_sha256")} - if journal: - if journal.get("primary_binding") != binding or journal.get("recovery_project_id") != PROJECT: - raise Refused("mirror_resume_binding_differs") - else: - journal.update(schema="platform.coulomb-recovery-mirror.v1", primary_binding=binding, - recovery_project_id=PROJECT, object_key=receipt["object_key"], - ciphertext_sha256=receipt["ciphertext_sha256"]) - primary.regular_file(source) - if primary.digest(source) != receipt["ciphertext_sha256"] or source.stat().st_size != receipt["ciphertext_bytes"]: - raise Refused("mirror_source_differs") - primary.transfer(Mirror(client), "coulomb-social", source, readback, journal, save) - reference = {"project_id": PROJECT, "bucket": BUCKET, - "object_key": "archives/" + receipt["object_key"], - "version_id": journal["version_id"], "ciphertext_sha256": receipt["ciphertext_sha256"], - "ciphertext_bytes": receipt["ciphertext_bytes"], "hash_readback_verified": True} - journal["recovery_mirror"] = reference - save() - return dict(receipt, recovery_mirror=reference) - - -def retrieve(client, output, receipt): - validate_receipt(receipt) - ref = receipt.get("recovery_mirror", {}) - if (ref.get("project_id") != PROJECT or ref.get("bucket") != BUCKET - or ref.get("object_key") != "archives/" + receipt["object_key"] - or ref.get("ciphertext_sha256") != receipt["ciphertext_sha256"] - or ref.get("ciphertext_bytes") != receipt["ciphertext_bytes"] - or ref.get("hash_readback_verified") is not True): - raise Refused("exact_recovery_mirror_reference_required") - primary.require_version(ref.get("version_id")) - primary.download(Mirror(client), receipt["object_key"], ref["version_id"], - receipt["ciphertext_bytes"], receipt["ciphertext_sha256"], output) - return {"status": "independent_mirror_retrieved_verified", "provider_mutations": False, - "recovery_mirror": ref, "application_restore_verified": False} - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("operation", choices=("publish", "retrieve")) - parser.add_argument("--execute", action="store_true") - parser.add_argument("--directory", type=Path, default=Path.home() / ".config/railiance-platform/coulomb-social-recovery") - parser.add_argument("--receipt", type=Path, required=True) - parser.add_argument("--source", type=Path) - parser.add_argument("--output", type=Path, required=True) - parser.add_argument("--journal", type=Path) - parser.add_argument("--result", type=Path) - args = parser.parse_args() - if not args.execute: - print(json.dumps({"status": "preview", "provider_contacted": False, "operation": args.operation})) - return - private_directory(args.directory) - private_directory(args.output.parent) - receipt = private_json(args.receipt) - if args.result: - private_directory(args.result.parent) - if args.result.exists() or args.result.is_symlink(): - raise Refused("fresh_result_required") - if args.operation == "retrieve": - result = retrieve(s3(private_json(args.directory / "reader-pair.json"), PROJECT), args.output, receipt) - else: - if not args.journal or not args.source or not args.result: - raise Refused("publish_source_journal_result_required") - private_directory(args.journal.parent) - journal = private_json(args.journal) if args.journal.exists() else {} - result = mirror(s3(private_json(args.directory / "publisher-pair.json"), PROJECT), args.source, - args.output, receipt, journal, lambda: checkpoint(args.journal, journal)) - if args.result: - checkpoint(args.result, result) - print(json.dumps({"status": "mirror_publish_verified" if args.operation == "publish" else result["status"], - "application_restore_verified": False, "private_values_printed": False})) - - -if __name__ == "__main__": - try: - main() - except Exception as error: # noqa: BLE001 -- suppress private provider diagnostics - print(json.dumps({"status": "refused", "failure_type": type(error).__name__, "native_status": status(error)})) - sys.exit(1) diff --git a/scripts/application_recovery_resources.py b/scripts/application_recovery_resources.py deleted file mode 100644 index 90e444e..0000000 --- a/scripts/application_recovery_resources.py +++ /dev/null @@ -1,452 +0,0 @@ -"""Coulomb-only protected resources, exercised through scoped API pairs. - -Preview is offline. Execute never prints secret values or reads sibling custody. -Current Scaleway bucket policy version is an allowlist intersected with IAM. -""" -from __future__ import annotations - -import argparse -import base64 -import fcntl -import hashlib -import hmac -import json -import os -import subprocess -import sys -from datetime import UTC, datetime -from pathlib import Path -from urllib.error import HTTPError -from urllib.parse import urlencode -from urllib.request import HTTPRedirectHandler, Request, build_opener - -from application_recovery_identities import ( - DECISION, - PUBLISHER, - Refused, - checkpoint, - private_directory, - private_json, -) - -REGION = "nl-ams" -BUCKETS = {"kits": "railiance-coulomb-social-recovery", - "archives": "railiance-coulomb-social-archives"} -SECRET_BASE = "/secret-manager/v1beta1/regions/nl-ams/secrets" - - -class NativeError(Refused): - def __init__(self, status): - self.status = status - super().__init__("native_request_refused") - - -class API: - def __init__(self, pair): - self.key = pair["AWS_SECRET_ACCESS_KEY"] - - class NoRedirect(HTTPRedirectHandler): - def redirect_request(self, *args, **kwargs): - raise Refused("provider_redirect_refused") - - self.opener = build_opener(NoRedirect()) - - def __call__(self, method, path, value=None): - if not path.startswith((SECRET_BASE, "/iam/v1alpha1/")) or ".." in path: - raise Refused("exact_api_scope_required") - req = Request("https://api.scaleway.com" + path, method=method, - headers={"X-Auth-Token": self.key, "Content-Type": "application/json"}, - data=None if value is None else json.dumps(value).encode()) - try: - with self.opener.open(req, timeout=25) as response: - data = response.read(1048577) - if len(data) > 1048576: - raise Refused("bounded_response_required") - return json.loads(data) if data else {} - except HTTPError as error: - raise NativeError(error.code) from None - - -def s3(pair, project): - import boto3 - from botocore.config import Config - return boto3.client("s3", endpoint_url="https://s3.nl-ams.scw.cloud", region_name=REGION, - aws_access_key_id=pair["AWS_ACCESS_KEY_ID"].split("@")[0] + "@" + project, - aws_secret_access_key=pair["AWS_SECRET_ACCESS_KEY"], - config=Config(signature_version="s3v4", connect_timeout=15, read_timeout=30, - retries={"max_attempts": 0}, - request_checksum_calculation="when_required", - response_checksum_validation="when_required", - s3={"addressing_style": "path"})) - - -def status(error): - if isinstance(error, NativeError): - return error.status - return getattr(error, "response", {}).get("ResponseMetadata", {}).get("HTTPStatusCode") - - -def denied(call): - try: - result = call() - if isinstance(result, dict) and "Body" in result: - result["Body"].close() - except Exception as error: # noqa: BLE001 -- normalize SDK and HTTP denial evidence - if status(error) == 403: - return {"http_status": 403, "denied": True} - raise Refused("native_denial_must_be_403") from None - raise Refused("native_boundary_unexpectedly_allowed") - - -def canonical_policy(value): - # Policy array ordering is not significant. Scaleway sorts actions and - # principal arrays on write. Preserve every value (including duplicates) - # and compare all fields; never discard unknown permission/condition data. - if isinstance(value, dict): - return {k: canonical_policy(v) for k, v in value.items()} - if isinstance(value, list): - return sorted((canonical_policy(v) for v in value), key=lambda v: json.dumps(v, sort_keys=True)) - return value - - -def policy(bucket, prefix, apps, owner): - def statement(sid, principal, actions, resources, condition=None): - value = {"Sid": sid, "Effect": "Allow", "Principal": {"SCW": principal}, - "Action": actions, "Resource": resources} - if condition: - value["Condition"] = condition - return value - # Preserve an explicit platform operator recovery route, even after the - # construction key is revoked. Owner is recorded from existing owned policy. - statements = [statement("PlatformOwner", "user_id:" + owner, "s3:*", [bucket, bucket + "/*"]), - statement("TemporaryConstruction", "application_id:" + apps["bootstrap"], - "s3:*", [bucket, bucket + "/*"])] - for role in ("reader", "custodian", *( ["publisher"] if prefix == "archives" else [])): - principal = "application_id:" + apps[role] - actions = ["s3:GetObject", "s3:GetObjectVersion"] - if role in {"custodian", "publisher"}: - actions += ["s3:PutObject"] - if role == "publisher": - actions += ["s3:AbortMultipartUpload", "s3:ListMultipartUploadParts"] - statements.append(statement(role + "Objects", principal, actions, bucket + "/" + prefix + "/*")) - statements.append(statement(role + "List", principal, ["s3:ListBucket", "s3:ListBucketVersions"], - bucket, {"StringLike": {"s3:prefix": [prefix + "/", prefix + "/*"]}})) - if prefix != "archives": - statements.append(statement(role + "Metadata", principal, - ["s3:GetBucketLocation", "s3:GetBucketVersioning", "s3:GetLifecycleConfiguration"], bucket)) - if prefix == "archives": - # Scaleway accepts at most ten statements per policy. These metadata - # rights are identical for all three callers; sharing their statement - # changes no effective permission or prefix restriction. - statements.append(statement("ArchiveMetadata", ["application_id:" + apps[r] - for r in ("reader", "custodian", "publisher")], - ["s3:GetBucketLocation", "s3:GetBucketVersioning", "s3:GetLifecycleConfiguration"], bucket)) - return {"Version": "2023-04-17", "Id": "coulomb-independent-recovery", "Statement": statements} - - -def public_recipient(identity): - proc = subprocess.run(["age-keygen", "-y", "/dev/stdin"], input=identity, - capture_output=True, timeout=15, check=False) - value = proc.stdout.decode().strip() - if proc.returncode or not value.startswith("age1"): - raise Refused("dedicated_age_identity_required") - return value - - -class Resources: - def __init__(self, directory, owner): - private_directory(directory) - self.directory, self.owner = directory, owner - self.ids = private_json(directory / "identity-state.json") - if self.ids.get("decision") != DECISION or self.ids.get("status") != ( - "identity_bootstrap_complete_native_boundaries_pending" - ): - raise Refused("verified_identity_stage_required") - self.project = self.ids["project"]["id"] - self.apps = {r: v["id"] for r, v in self.ids["apps"].items()} | {"publisher": PUBLISHER} - self.pairs = {r: private_json(directory / (r + "-pair.json")) - for r in ("bootstrap", "reader", "custodian", "publisher")} - self.api = {r: API(p) for r, p in self.pairs.items()} - self.clients = {r: s3(p, self.project) for r, p in self.pairs.items()} - self.path = directory / "resource-state.json" - self.state = private_json(self.path) if self.path.exists() else { - "schema": "platform.coulomb-recovery-resources.v1", "decision": DECISION, - "project_id": self.project, "owner_user_id": owner, "buckets": {}, "secrets": {}} - if (self.state.get("project_id") != self.project - or self.state.get("owner_user_id") != owner or self.state.get("decision") != DECISION): - raise Refused("resource_journal_binding_refused") - if self.state.get("pending"): - raise Refused("uncertain_resource_mutation_requires_reconciliation") - - def save(self): - checkpoint(self.path, self.state) - - def deny_s3(self, role, operation, **arguments): - # Some denied PUTs leave a provider keep-alive connection unusable for - # the next request (observed HTTP400 with an empty error). Isolate each - # negative probe so it cannot corrupt subsequent positive read proof. - client = s3(self.pairs[role], self.project) - try: - return denied(lambda: getattr(client, operation)(**arguments)) - finally: - client.close() - - def mutate(self, label, call): - self.state["pending"] = label - self.save() - try: - result = call() - except Exception as error: - if status(error) in {400, 401, 403, 404, 409, 412, 422, 429}: - self.state.pop("pending") - self.save() - raise - self.state.pop("pending") - return result - - def bucket(self, prefix, bucket): - client = self.clients["bootstrap"] - if bucket not in self.state["buckets"]: - inventory = client.list_buckets()["Buckets"] - if any(v["Name"] == bucket for v in inventory): - raise Refused("untracked_bucket_requires_review") - self.mutate("create_bucket:" + bucket, lambda: client.create_bucket( - Bucket=bucket, ACL="private", CreateBucketConfiguration={"LocationConstraint": REGION})) - self.state["buckets"][bucket] = {"created": True} - self.save() - # No payload is ever placed before allowlist installation/readback. - intended = policy(bucket, prefix, self.apps, self.owner) - try: - live = json.loads(client.get_bucket_policy(Bucket=bucket)["Policy"]) - except Exception as error: - if status(error) != 404: - raise - live = None - if live is not None and canonical_policy(live) != canonical_policy(intended): - raise Refused("existing_policy_drift_requires_review") - if live is None: - self.mutate("put_policy:" + bucket, lambda: client.put_bucket_policy( - Bucket=bucket, Policy=json.dumps(intended))) - self.save() - if canonical_policy(json.loads(client.get_bucket_policy(Bucket=bucket)["Policy"])) != canonical_policy(intended): - raise Refused("policy_readback_differs") - if client.get_bucket_versioning(Bucket=bucket).get("Status") != "Enabled": - self.mutate("versioning:" + bucket, lambda: client.put_bucket_versioning( - Bucket=bucket, VersioningConfiguration={"Status": "Enabled"})) - self.save() - acl = client.get_bucket_acl(Bucket=bucket) - if any(g.get("Grantee", {}).get("URI") for g in acl.get("Grants", [])): - raise Refused("public_acl_refused") - try: - lifecycle = client.get_bucket_lifecycle_configuration(Bucket=bucket)["Rules"] - except Exception as error: - if status(error) != 404: - raise - lifecycle = [] - # Retention compatibility has not yet been proved. Do not activate - # expiry even on rolling bucket until protected recovery exists. - if lifecycle: - raise Refused("unexpected_expiry_requires_review") - if client.get_bucket_versioning(Bucket=bucket).get("Status") != "Enabled": - raise Refused("versioning_readback_required") - self.state["buckets"][bucket] = {"created": True, "versioning": "Enabled", - "public_acl_absent": True, "lifecycle_rules": [], "policy": intended} - self.save() - - def secret(self, name): - api = self.api["bootstrap"] - if name not in self.state["secrets"]: - found = api("GET", SECRET_BASE + "?" + urlencode({"project_id": self.project, "page_size": 100})) - if int(found["total_count"]) != len(found["secrets"]): - raise Refused("complete_secret_inventory_required") - if any(s["name"] == name for s in found["secrets"]): - raise Refused("untracked_secret_requires_review") - created = self.mutate("create_secret:" + name, lambda: api("POST", SECRET_BASE, - {"name": name, "project_id": self.project, "type": "opaque", "protected": True, - "description": "Coulomb-only independent recovery; " + DECISION})) - self.state["secrets"][name] = {"id": created["id"]} - self.save() - ref = self.state["secrets"][name] - metadata = api("GET", SECRET_BASE + "/" + ref["id"]) - if (metadata.get("name") != name or metadata.get("project_id") != self.project - or metadata.get("type") != "opaque" or metadata.get("protected") is not True - or metadata.get("ephemeral_policy") is not None - or metadata.get("deletion_requested_at") is not None): - raise Refused("protected_nonexpiring_secret_required") - return ref - - def version(self, name, value): - ref = self.secret(name) - path = SECRET_BASE + "/" + ref["id"] - if not ref.get("revision"): - versions = self.api["bootstrap"]("GET", path + "/versions?page_size=100") - if int(versions["total_count"]) != 0: - raise Refused("untracked_secret_revision_requires_review") - created = self.mutate("create_version:" + name, lambda: self.api["bootstrap"]( - "POST", path + "/versions", {"data": base64.b64encode(value).decode(), - "description": "Protected Coulomb custody", "disable_previous": False})) - ref["revision"] = created["revision"] - self.save() - revision = str(ref["revision"]) - current = self.api["custodian"]("GET", path + "/versions/" + revision) - props = current.get("ephemeral_properties") or {} - if current.get("status") != "enabled" or props.get("expires_at") or props.get("expires_once_accessed"): - raise Refused("enabled_nonexpiring_revision_required") - actual = self.api["custodian"]("GET", path + "/versions/" + revision + "/access") - if not hmac.compare_digest(base64.b64decode(actual["data"], validate=True), value): - raise Refused("independent_secret_readback_differs") - return ref - - def run(self): - for prefix, bucket in BUCKETS.items(): - self.bucket(prefix, bucket) - # Canary is deliberately synthetic: denial checks cannot expose an age - # identity or a real API pair, even if IAM were accidentally too broad. - canary_name = "coulomb-social-recovery-access-canary" - canary = self.version(canary_name, b"synthetic-coulomb-recovery-canary-v1") - probe_path = SECRET_BASE + "/" + canary["id"] + "/versions/" + str(canary["revision"]) + "/access" - boundaries = {r + "_escrow": denied(lambda r=r: self.api[r]("GET", probe_path)) - for r in ("reader", "publisher")} - identity_path = self.directory / "coulomb.agekey" - if not identity_path.exists(): - proc = subprocess.run(["age-keygen"], capture_output=True, timeout=15, check=False) - if proc.returncode: - raise Refused("age_generation_failed") - fd = os.open(identity_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "wb") as stream: - stream.write(proc.stdout) - stream.flush() - os.fsync(stream.fileno()) - # Validate mode/ownership without JSON parsing. - info = identity_path.lstat() - if not identity_path.is_file() or identity_path.is_symlink() or info.st_uid != os.geteuid() or info.st_mode & 0o077: - raise Refused("private_age_identity_required") - identity = identity_path.read_bytes() - recipient = public_recipient(identity) - self.version("coulomb-social-age-recovery", identity) - for role in ("reader", "custodian"): - self.version("coulomb-social-recovery-" + role + "-api", json.dumps( - self.pairs[role], sort_keys=True).encode()) - boundaries.update(self.object_boundaries()) - self.state.update(status="scoped_api_custody_and_native_boundaries_verified", - public_recipient=recipient, secret_boundaries=boundaries, - completed_at=datetime.now(UTC).isoformat(), - native_archive_recovery_verified=False, timers_activated=False) - self.save() - return {"status": self.state["status"], "buckets": len(self.state["buckets"]), - "protected_secrets": len(self.state["secrets"]), "public_recipient": recipient, - "routine_pairs_escrow_readback_equal": True, "secret_boundaries": boundaries, - "private_values_printed": False, "timers_activated": False} - - def fixture(self, prefix): - bucket = BUCKETS[prefix] - key = prefix + "/bootstrap-boundary-v1.txt" - content = b"synthetic Coulomb recovery boundary fixture v1\n" - ref = self.state.setdefault("fixtures", {}).get(prefix) - if not ref: - response = self.mutate("put_fixture:" + prefix, lambda: self.clients["custodian"].put_object( - Bucket=bucket, Key=key, Body=content, IfNoneMatch="*")) - ref = {"bucket": bucket, "key": key, "version_id": response.get("VersionId")} - self.state["fixtures"][prefix] = ref - self.save() - if not ref["version_id"] or ref["version_id"] == "null": - raise Refused("versioned_fixture_required") - response = self.clients["reader"].get_object(Bucket=bucket, Key=key, VersionId=ref["version_id"]) - with response["Body"] as stream: - actual = stream.read(len(content) + 1) - if (actual != content or response.get("VersionId") != ref["version_id"]): - raise Refused("independent_version_readback_differs") - ref["sha256"] = hashlib.sha256(content).hexdigest() - self.save() - return ref - - def object_boundaries(self): - proofs = {} - for prefix, bucket in BUCKETS.items(): - ref = self.fixture(prefix) - key, version = ref["key"], ref["version_id"] - reader = self.clients["reader"] - listing = reader.list_object_versions(Bucket=bucket, Prefix=prefix + "/") - if not any(v["Key"] == key and v["VersionId"] == version for v in listing.get("Versions", [])): - raise Refused("independent_version_listing_required") - proofs[prefix + "_reader_version_get_hash"] = {"verified": True, "version_id": version} - for role in ("reader", "custodian", "publisher"): - # All deletion targets are newly made synthetic canaries. No - # actual retained backup is ever a destructive negative probe. - proofs[prefix + "_" + role + "_delete"] = self.deny_s3(role, "delete_object", - Bucket=bucket, Key=key, VersionId=version) - intended = self.state["buckets"][bucket]["policy"] - proofs[prefix + "_" + role + "_policy_write"] = self.deny_s3(role, "put_bucket_policy", - Bucket=bucket, Policy=json.dumps(intended)) - proofs[prefix + "_" + role + "_outside_prefix"] = self.deny_s3(role, "get_object", - Bucket=bucket, Key="outside-approved-prefix/synthetic-absent") - proofs[prefix + "_reader_put"] = self.deny_s3("reader", "put_object", - Bucket=bucket, Key=prefix + "/reader-write-denial.txt", Body=b"synthetic-only") - kit = self.state["fixtures"]["kits"] - proofs["publisher_protected_kit_get"] = self.deny_s3("publisher", "get_object", - Bucket=kit["bucket"], Key=kit["key"], VersionId=kit["version_id"]) - publisher = self.clients["publisher"] - # Verify ordinary worker upload/readback in addition to its multipart - # abort. No content is captured from the application by this helper. - key = "archives/publisher-boundary-v1.txt" - payload = b"synthetic publisher fixture v1\n" - published = self.state.get("publisher_fixture") - if not published: - response = self.mutate("publisher_fixture", lambda: publisher.put_object( - Bucket=BUCKETS["archives"], Key=key, Body=payload, IfNoneMatch="*")) - published = {"version_id": response.get("VersionId")} - self.state["publisher_fixture"] = published - self.save() - if not published["version_id"] or published["version_id"] == "null": - raise Refused("versioned_publisher_fixture_required") - response = publisher.get_object(Bucket=BUCKETS["archives"], Key=key, VersionId=published["version_id"]) - with response["Body"] as stream: - if stream.read(len(payload) + 1) != payload: - raise Refused("publisher_version_readback_differs") - proofs["publisher_create_readback"] = {"verified": True} - upload = self.state.get("multipart_fixture") - if not upload: - upload = self.mutate("multipart_fixture", lambda: publisher.create_multipart_upload( - Bucket=BUCKETS["archives"], Key="archives/multipart-abort-boundary-v1")) - self.state["multipart_fixture"] = {"upload_id": upload["UploadId"], "aborted": False} - self.save() - multipart = self.state["multipart_fixture"] - if not multipart["aborted"]: - self.mutate("multipart_abort", lambda: publisher.abort_multipart_upload( - Bucket=BUCKETS["archives"], Key="archives/multipart-abort-boundary-v1", UploadId=multipart["upload_id"])) - multipart["aborted"] = True - self.save() - proofs["publisher_own_multipart_abort"] = {"verified": True} - for role in ("publisher", "reader", "custodian"): - proofs[role + "_iam_admin"] = denied(lambda role=role: self.api[role]( - "GET", "/iam/v1alpha1/applications?organization_id=" + self.ids["project"]["organization_id"])) - return proofs - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--execute", action="store_true") - parser.add_argument("--directory", type=Path) - parser.add_argument("--owner-user-id") - args = parser.parse_args() - if not args.execute: - print(json.dumps({"status": "preview", "provider_contacted": False, - "buckets": BUCKETS, "automatic_expiry": False})) - return - import uuid - uuid.UUID(args.owner_user_id) - private_directory(args.directory) - fd = os.open(args.directory / "resource-bootstrap.lock", os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "w") as lock: - fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - print(json.dumps(Resources(args.directory, args.owner_user_id).run())) - - -if __name__ == "__main__": - try: - main() - except Exception as error: # noqa: BLE001 -- never expose SDK credential-bearing diagnostics - print(json.dumps({"status": "refused", "failure_type": type(error).__name__, - "native_status": status(error), - "reason": str(error) if isinstance(error, Refused) else "private_review_required"})) - sys.exit(1) diff --git a/scripts/apply-backup-publisher-bucket-policy.py b/scripts/apply-backup-publisher-bucket-policy.py deleted file mode 100644 index 05d01ec..0000000 --- a/scripts/apply-backup-publisher-bucket-policy.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -"""Add the two publisher statements to the live backup bucket policy. - -Existing statement IDs are kept. The bootstrap key writes the merged policy. -The receipt contains statement IDs and status codes only. -""" - -from __future__ import annotations - -import json -import subprocess -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError - -ROOT = Path("/home/worsch/railiance-platform") -DESIRED = ROOT / "docs/object-storage/railiance-platform-pg-backup-policy.json" -RECEIPT = Path("/home/worsch/.local/state/railiance/receipts/backup-bucket-policy-apply.json") -BUCKET = "railiance-platform-pg-backup" -ENDPOINT = "https://s3.nl-ams.scw.cloud" - - -def kv(path): - proc = subprocess.run(["bao", "kv", "get", "-format=json", path], capture_output=True) - if proc.returncode: - raise SystemExit(71) - return json.loads(proc.stdout)["data"]["data"] - - -def client(access_key, secret_key): - return boto3.client( - "s3", - region_name="nl-ams", - endpoint_url=ENDPOINT, - aws_access_key_id=access_key, - aws_secret_access_key=secret_key, - ) - - -def sids(document): - return [item.get("Sid") for item in document.get("Statement", [])] - - -def merged(live, desired): - statements = list(live.get("Statement", [])) - present = {item.get("Sid") for item in statements} - for item in desired.get("Statement", []): - if item.get("Sid") not in present: - statements.append(item) - present.add(item.get("Sid")) - return { - "Version": live.get("Version") or desired.get("Version"), - "Id": live.get("Id") or desired.get("Id"), - "Statement": statements, - } - - -def main(): - backup = kv("platform/workloads/railiance/backup/object-storage") - bootstrap = kv("platform/workloads/railiance/scaleway/bootstrap") - desired = json.loads(DESIRED.read_text(encoding="utf-8")) - reader = client(backup["ACCESS_KEY"], backup["SECRET_KEY"]) - live = json.loads(reader.get_bucket_policy(Bucket=BUCKET)["Policy"]) - policy = merged(live, desired) - receipt = {"before": sids(live), "intended": sids(policy)} - if sids(live) == sids(policy): - receipt["put"] = "unchanged" - else: - writer = client(bootstrap["ACCESS_KEY"], bootstrap["SECRET_KEY"]) - try: - writer.put_bucket_policy(Bucket=BUCKET, Policy=json.dumps(policy)) - receipt["put"] = "ok" - except ClientError as exc: - receipt["put"] = exc.response.get("Error", {}).get("Code", "error") - try: - after = json.loads(reader.get_bucket_policy(Bucket=BUCKET)["Policy"]) - receipt["after"] = sids(after) - except ClientError as exc: - receipt["after_error"] = exc.response.get("Error", {}).get("Code", "error") - RECEIPT.parent.mkdir(mode=0o700, exist_ok=True) - RECEIPT.write_text(json.dumps(receipt, indent=2) + "\n") - RECEIPT.chmod(0o600) - - -if __name__ == "__main__": - try: - main() - except SystemExit: - raise - except Exception: - raise SystemExit(72) from None diff --git a/scripts/apply-reuse-surface-reader-custody.py b/scripts/apply-reuse-surface-reader-custody.py deleted file mode 100755 index cc0ad52..0000000 --- a/scripts/apply-reuse-surface-reader-custody.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/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/scripts/argocd_adoption_proof.py b/scripts/argocd_adoption_proof.py deleted file mode 100644 index 163fb26..0000000 --- a/scripts/argocd_adoption_proof.py +++ /dev/null @@ -1,365 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only Argo adoption proof. Capture/discard raw subprocess output on errors. - -Use --live on the workstation; SSH collects only allowlisted metadata on the -verified estate. The explicit contract binds source, revision and resource set. -A receipt is proof of observation, never authority for a subsequent mutation. -""" -from __future__ import annotations -import argparse -import base64 -from datetime import datetime, timezone -import hashlib -import json -from pathlib import Path -import re -import subprocess -import tempfile - -PIN = 'd2dbc19c254247652c49fda8721c80d53bca206a' -CONTRACT = { - 'estate_ip': '92.205.62.239', 'application': 'openbao-secretstore', - 'project': 'railiance-platform-addons', - 'destination': {'server': 'https://kubernetes.default.svc', 'namespace': 'external-secrets'}, - 'repo_url': 'https://forgejo.coulomb.social/coulomb/railiance-platform.git', - 'source_path': 'argocd/platform-addons/openbao-secretstore', 'revision': PIN, - 'resources': [{'group': 'external-secrets.io', 'kind': 'ClusterSecretStore', - 'namespace': '', 'name': 'openbao'}], - 'ready_checks': [{'kind': 'ClusterSecretStore', 'namespace': '', 'name': 'openbao', - 'tracking_id': 'openbao-secretstore:external-secrets.io/ClusterSecretStore:external-secrets/openbao'}, - {'kind': 'ExternalSecret', 'namespace': 'issue-core', - 'name': 'issue-core-runtime'}], - 'tracking_method': 'annotation', 'controller_version': 'v3.5.3', - 'minimum_proving_seconds': 86400, 'maximum_observation_age_seconds': 300, -} - - -def _kube_route(): - """Local-only import; this module's source may also run remotely without it.""" - import sys - here = str(Path(__file__).resolve().parent) - if here not in sys.path: - sys.path.insert(0, here) - import kube_route - return kube_route - - -def checked_run(command, *, input=None, timeout=30): - try: - result = subprocess.run(command, input=input, capture_output=True, timeout=timeout) - if result.returncode: - raise RuntimeError('operation unverified; protected output suppressed') - return result.stdout - except (OSError, subprocess.SubprocessError): - raise RuntimeError('operation unverified; protected output suppressed') from None - - -def identity(resource): - return tuple(resource.get(k) or '' for k in ('group', 'kind', 'namespace', 'name')) - - -def timestamp(value): - if not isinstance(value, str): - raise ValueError('invalid proof timestamp') - stamp = datetime.fromisoformat(value.replace('Z', '+00:00')) - if stamp.tzinfo is None: - raise ValueError('proof timestamp requires timezone') - return stamp - - -def validate_contract(contract): - if (not re.fullmatch(r'[a-f0-9]{40}', contract['revision']) - or not re.fullmatch(r'[a-z0-9][a-z0-9.-]*', contract['application']) - or not re.fullmatch(r'[a-z0-9/-]+', contract['source_path']) - or '..' in contract['source_path'] or contract['source_path'].startswith('/')): - raise ValueError('invalid adoption contract') - for check in contract['ready_checks']: - if check['kind'] not in ('ClusterSecretStore', 'ExternalSecret'): - raise ValueError('secret objects are excluded from the metadata contract') - for key in ('namespace', 'name'): - if not re.fullmatch(r'[a-z0-9.-]*', check[key]) or (key == 'name' and not check[key]): - raise ValueError('invalid metadata identity') - if (not contract['resources'] or len({identity(r) for r in contract['resources']}) != len(contract['resources']) - or contract['minimum_proving_seconds'] < 86400 - or not 0 < contract['maximum_observation_age_seconds'] <= 300): - raise ValueError('invalid proof bounds') - - -def tracking_settings(contract): - cm = json.loads(checked_run(['kubectl', '-n', 'argocd', 'get', 'configmap', 'argocd-cm', '-o', 'json'])) - controller = json.loads(checked_run(['kubectl', '-n', 'argocd', 'get', 'statefulset', 'argocd-application-controller', '-o', 'json'])) - containers = controller['spec']['template']['spec']['containers'] - images = [c['image'] for c in containers if c['name'] == 'argocd-application-controller'] - if len(images) != 1 or not images[0].endswith(':' + contract['controller_version']): - raise ValueError('expected Argo controller version unverified') - data = cm.get('data', {}) - # Argo 3.0 changed the default from label to annotation. Bind that default to - # an observed pinned 3.x controller; never infer it for an unknown runtime. - method = data.get('application.resourceTrackingMethod', 'annotation') - label_key = data.get('application.instanceLabelKey', 'app.kubernetes.io/instance') - if method != contract['tracking_method'] or data.get('installationID'): - raise ValueError('tracking semantics differ from this adoption contract') - return {'method': method, 'instance_label_key': label_key, - 'controller_version': contract['controller_version'], 'installation_id_absent': True} - - -def argo_desired(source, contract, settings): - """Inject only Argo-owned tracking metadata, never copy live fields into source.""" - import copy - desired = copy.deepcopy(source) - if (desired.get('apiVersion') != 'external-secrets.io/v1beta1' - or desired.get('kind') != 'ClusterSecretStore' - or desired.get('metadata', {}).get('name') != 'openbao' - or desired['metadata'].get('namespace')): - raise ValueError('pinned source identity differs from admitted store') - if (settings['method'] != contract['tracking_method'] - or settings['controller_version'] != contract['controller_version'] - or settings['installation_id_absent'] is not True): - raise ValueError('unsupported Argo render semantics') - tracking = f"{contract['application']}:external-secrets.io/ClusterSecretStore:{contract['destination']['namespace']}/openbao" - annotations = desired['metadata'].setdefault('annotations', {}) - if 'argocd.argoproj.io/tracking-id' in annotations and annotations['argocd.argoproj.io/tracking-id'] != tracking: - raise ValueError('source tracking annotation conflicts with Argo identity') - annotations['argocd.argoproj.io/tracking-id'] = tracking - return desired - - -def changed_paths(left, right, path=''): - """JSON pointers only: never return old/new values or annotation contents.""" - if type(left) is not type(right): - return [path] - if isinstance(left, dict): - result = [] - for key in sorted(set(left) | set(right)): - child = path + '/' + key.replace('~', '~0').replace('/', '~1') - result.extend([child] if key not in left or key not in right else changed_paths(left[key], right[key], child)) - return result - return [] if left == right else [path] - - -def stable_object(value): - import copy - value = copy.deepcopy(value) - for field in ('creationTimestamp', 'managedFields', 'resourceVersion', 'uid', 'generation'): - value['metadata'].pop(field, None) - value.pop('status', None) - return value - - -def remote_diff(rendered, contract): - def get(args, input=None): - return json.loads(checked_run(['kubectl', *args], input=input)) - nodes = get(['get', 'nodes', '-o', 'json']) - if contract['estate_ip'] not in [a['address'] for n in nodes['items'] for a in n['status']['addresses']]: - raise ValueError('estate identity unverified') - settings = tracking_settings(contract) - source = get(['create', '--dry-run=client', '--validate=false', '-f', '-', '-o', 'json'], rendered) - normalized = json.dumps(argo_desired(source, contract, settings), sort_keys=True).encode() - live = get(['get', '--raw', '/apis/external-secrets.io/v1beta1/clustersecretstores/openbao']) - if live.get('apiVersion') != source['apiVersion']: - raise ValueError('same source API-version read unverified') - def diff(payload, server): - command = ['kubectl', 'diff', *(['--server-side'] if server else []), '-f', '-'] - result = subprocess.run(command, input=payload, capture_output=True, timeout=30) - if result.returncode not in (0, 1): - raise ValueError('native diff failed; output suppressed') - return result.returncode - raw_client = get(['apply', '--dry-run=server', '-f', '-', '-o', 'json'], rendered) - raw_server = get(['apply', '--server-side', '--dry-run=server', '-f', '-', '-o', 'json'], rendered) - normalized_client = get(['apply', '--dry-run=server', '-f', '-', '-o', 'json'], normalized) - normalized_server = get(['apply', '--server-side', '--dry-run=server', '-f', '-', '-o', 'json'], normalized) - return {'revision': contract['revision'], 'path': contract['source_path'], - 'render_sha256': hashlib.sha256(rendered).hexdigest(), 'render_resource_count': 1, - 'normalized_render_sha256': hashlib.sha256(normalized).hexdigest(), - 'normalization': 'argocd-v3-annotation-tracking-destination-namespace', - 'tracking_settings': settings, 'same_api_version': True, - 'raw_server_exit_code': diff(rendered, True), 'raw_client_exit_code': diff(rendered, False), - 'raw_client_changed_paths': changed_paths(stable_object(live), stable_object(raw_client)), - 'raw_server_changed_paths': changed_paths(stable_object(live), stable_object(raw_server)), - 'exit_code': diff(normalized, True), 'client_side_exit_code': diff(normalized, False), - 'normalized_client_changed_paths': changed_paths(stable_object(live), stable_object(normalized_client)), - 'normalized_server_changed_paths': changed_paths(stable_object(live), stable_object(normalized_server)), - 'output_suppressed': True} - - -def collect(contract): - validate_contract(contract) - def get(args): - return json.loads(checked_run(['kubectl', *args, '-o', 'json'])) - nodes = get(['get', 'nodes']) - addresses = [a.get('address') for n in nodes['items'] for a in n.get('status', {}).get('addresses', [])] - if contract['estate_ip'] not in addresses: - raise ValueError('estate identity unverified') - obj = get(['-n', 'argocd', 'get', 'application', contract['application']]) - spec, status = obj['spec'], obj.get('status', {}) - op = status.get('operationState', {}) - sync_operation = op.get('operation', {}).get('sync', {}) - app = {'name': obj['metadata']['name'], 'uid': obj['metadata']['uid'], - 'project': spec.get('project'), 'destination': spec.get('destination'), 'source': {k: spec.get('source', {}).get(k) - for k in ('repoURL', 'path', 'targetRevision')}, - 'automated_present': 'automated' in spec.get('syncPolicy', {}), - 'sync': status.get('sync', {}).get('status'), 'health': status.get('health', {}).get('status'), - 'revision': status.get('sync', {}).get('revision'), - 'pending_operation': bool(obj.get('operation')), - 'condition_types': [c.get('type') for c in status.get('conditions', [])], - 'operation': {k: op.get(k) for k in ('phase', 'startedAt', 'finishedAt')}, - 'sync_prune': sync_operation.get('prune', False), - 'sync_strategy_apply': 'apply' in sync_operation.get('syncStrategy', {}), - 'operation_revision': sync_operation.get('revision'), - 'resources': [{k: r.get(k) for k in ('group', 'kind', 'namespace', 'name', 'status')} - for r in status.get('resources', [])]} - checks = [] - for check in contract['ready_checks']: - args = ['-n', check['namespace']] if check['namespace'] else [] - obj = get([*args, 'get', check['kind'], check['name']]) - ready = [c for c in obj.get('status', {}).get('conditions', []) if c.get('type') == 'Ready'] - checks.append({'kind': check['kind'], 'namespace': check['namespace'], 'name': check['name'], - 'uid': obj['metadata']['uid'], - 'ready': [{k: c.get(k) for k in ('status', 'lastTransitionTime')} for c in ready], - 'tracking_matches': obj['metadata'].get('annotations', {}).get('argocd.argoproj.io/tracking-id') == check['tracking_id'] - if 'tracking_id' in check else None}) - return {'tracking_settings': tracking_settings(contract), - 'captured_at': datetime.now(timezone.utc).isoformat(), - 'estate_ip': contract['estate_ip'], 'application': app, 'ready_checks': checks} - - -def verify(observation, contract, *, now=None): - validate_contract(contract) - now = now or datetime.now(timezone.utc) - age = (now - timestamp(observation['captured_at'])).total_seconds() - if not 0 <= age <= contract['maximum_observation_age_seconds']: - raise ValueError('observation stale or from future') - if observation['estate_ip'] != contract['estate_ip']: - raise ValueError('estate identity mismatch') - settings = observation.get('tracking_settings', {}) - if (settings.get('method') != contract['tracking_method'] or settings.get('controller_version') != contract['controller_version'] - or settings.get('installation_id_absent') is not True): - raise ValueError('observed Argo tracking semantics unverified') - app = observation['application'] - if (app['name'] != contract['application'] or not app['uid'] or app['project'] != contract['project'] - or app['source'] != {'repoURL': contract['repo_url'], 'path': contract['source_path'], 'targetRevision': contract['revision']} - or app['revision'] != contract['revision'] or app.get('destination') != contract['destination']): - raise ValueError('application source or identity mismatch') - if app['automated_present'] is not False or app['pending_operation'] is not False: - raise ValueError('manual idle policy unverified') - if app['sync'] != 'Synced' or app['health'] != 'Healthy': - raise ValueError('application sync or health unverified') - if any(t != 'OrphanedResourceWarning' for t in app['condition_types']): - raise ValueError('application condition prevents proof') - op = app['operation'] - if (op['phase'] != 'Succeeded' or app['sync_prune'] is not False - or app['sync_strategy_apply'] is not True or app['operation_revision'] != contract['revision']): - raise ValueError('successful pinned manual apply unverified') - start, finish = timestamp(op['startedAt']), timestamp(op['finishedAt']) - proof_age = (timestamp(observation['captured_at']) - finish).total_seconds() - if finish < start or proof_age < contract['minimum_proving_seconds']: - raise ValueError('completed adoption proving period unverified') - resources = app['resources'] - expected = {identity(r) for r in contract['resources']} - if (len(resources) != len(expected) or {identity(r) for r in resources} != expected - or any(r['status'] != 'Synced' for r in resources)): - raise ValueError('exact tracked resource set unverified') - checks = observation['ready_checks'] - expected_checks = {(c['kind'], c['namespace'], c['name']): c for c in contract['ready_checks']} - if len(checks) != len(expected_checks) or {(c['kind'], c['namespace'], c['name']) for c in checks} != set(expected_checks): - raise ValueError('readiness checks incomplete') - for check in checks: - key = (check['kind'], check['namespace'], check['name']) - ready = check['ready'] - if not check['uid'] or len(ready) != 1 or ready[0]['status'] != 'True': - raise ValueError('resource readiness unverified') - if timestamp(ready[0]['lastTransitionTime']) > start: - raise ValueError('resource readiness changed during adoption proving period') - if 'tracking_id' in expected_checks[key] and check['tracking_matches'] is not True: - raise ValueError('resource tracking identity unverified') - diff = observation.get('pinned_render_diff', {}) - allowed_raw_paths = { - '/metadata/annotations/argocd.argoproj.io~1tracking-id', - '/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration', - } - for mode in ('client', 'server'): - paths = diff.get(f'raw_{mode}_changed_paths') - code = diff.get(f'raw_{mode}_exit_code') - if (not isinstance(paths, list) or any(not isinstance(path, str) for path in paths) - or not set(paths) <= allowed_raw_paths or type(code) is not int - or code not in (0, 1) or bool(paths) != bool(code)): - raise ValueError('raw source drift contains unknown or substantive fields') - if (diff.get('revision') != contract['revision'] or diff.get('path') != contract['source_path'] - or type(diff.get('exit_code')) is not int or diff['exit_code'] != 0 - or type(diff.get('client_side_exit_code')) is not int or diff['client_side_exit_code'] != 0 - or diff.get('normalization') != 'argocd-v3-annotation-tracking-destination-namespace' - or diff.get('tracking_settings') != settings or diff.get('same_api_version') is not True - or diff.get('normalized_client_changed_paths') != [] or diff.get('normalized_server_changed_paths') != [] - or not re.fullmatch(r'[a-f0-9]{64}', diff.get('normalized_render_sha256', '')) - or diff.get('render_resource_count') != len(expected) - or not re.fullmatch(r'[a-f0-9]{64}', diff.get('render_sha256', ''))): - raise ValueError('exact pinned render zero-diff unverified') - return {'schema_version': 'argocd-adoption-proof/v1', 'status': 'verified', - 'application': contract['application'], 'revision': contract['revision'], - 'captured_at': observation['captured_at'], 'verified_at': now.isoformat(), - 'proving_seconds': proof_age, 'resource_count': len(expected), - 'ready_count': len(checks), 'manual_apply_prune_off': True, - 'warnings': sorted(set(app['condition_types'])), 'native_mutation': False, - 'verifier_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), - 'contract_sha256': hashlib.sha256(json.dumps(contract,sort_keys=True).encode()).hexdigest(), - 'observation_sha256': hashlib.sha256(json.dumps(observation,sort_keys=True).encode()).hexdigest()} - - -def render_diff(source_root, contract): - """Render a pinned tracked source into a disposable local directory; no checkout.""" - with tempfile.TemporaryDirectory(prefix='adoption-pin-') as temporary: - archive = checked_run(['git', '-C', str(source_root), 'archive', contract['revision'], contract['source_path']]) - checked_run(['tar', '-x', '-C', temporary], input=archive) - rendered = checked_run(['kubectl', 'kustomize', str(Path(temporary) / contract['source_path'])]) - # Fixed admission for this phase: exactly one public ClusterSecretStore, - # never forward a Secret or extra document to a diff operation. - if re.findall(rb'^kind: (.+)$', rendered, re.M) != [b'ClusterSecretStore']: - raise ValueError('pinned render resource kind unverified') - encoded = base64.b64encode(rendered).decode() - return json.loads(checked_run(_kube_route().ssh_argv(['python3', '-', '--remote-diff', '--render-base64', encoded]), - input=Path(__file__).read_bytes(), timeout=150)) - - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--live', action='store_true') - parser.add_argument('--remote-diff', action='store_true', help=argparse.SUPPRESS) - parser.add_argument('--render-base64', help=argparse.SUPPRESS) - parser.add_argument('--remote-collect', action='store_true', help=argparse.SUPPRESS) - parser.add_argument('--observation', type=Path) - parser.add_argument('--source-root', type=Path) - parser.add_argument('--receipt', type=Path) - args = parser.parse_args() - try: - if args.remote_diff: - print(json.dumps(remote_diff(base64.b64decode(args.render_base64, validate=True), CONTRACT), sort_keys=True)) - return 0 - if args.remote_collect: - print(json.dumps(collect(CONTRACT), sort_keys=True)) - return 0 - if args.live: - if not args.source_root: - raise ValueError('source root required for pinned rendering') - raw = checked_run(_kube_route().ssh_argv(['python3', '-', '--remote-collect']), - input=Path(__file__).read_bytes(), timeout=120) - observation = json.loads(raw) - observation['pinned_render_diff'] = render_diff(args.source_root, CONTRACT) - if args.observation: - args.observation.write_text(json.dumps(observation,indent=2)+'\n') - elif args.observation: - observation = json.loads(args.observation.read_text()) - else: - raise ValueError('live read or observation required') - receipt = verify(observation, CONTRACT) - except Exception: - receipt = {'schema_version':'argocd-adoption-proof/v1', 'status':'unverified', - 'error':'adoption proof incomplete; raw output suppressed', 'native_mutation':False} - if args.receipt: - args.receipt.write_text(json.dumps(receipt,indent=2)+'\n') - print(json.dumps(receipt,sort_keys=True)) - return 0 if receipt.get('status') == 'verified' else 1 - - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/scripts/argocd_child_pin_drift.py b/scripts/argocd_child_pin_drift.py deleted file mode 100644 index a3263dc..0000000 --- a/scripts/argocd_child_pin_drift.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only drift check between child Application pins in Git and on railiance01. - -`railiance-apps-root` syncs by hand from `argocd/railiance01/applications`. If a -release patches a child's live targetRevision without committing the same pin -here, the next root sync silently rolls that child back. Run this before any -root sync and after every release. Exit 1 on drift, 2 on a read failure. -""" -from __future__ import annotations -import json -from pathlib import Path -import subprocess -import sys - -import yaml - -APPS_DIR = Path(__file__).resolve().parent.parent / "argocd/railiance01/applications" - - -def declared() -> dict[str, str]: - pins = {} - for path in sorted(APPS_DIR.glob("*.y*ml")): - for doc in yaml.safe_load_all(path.read_text()): - if doc and doc.get("kind") == "Application": - pins[doc["metadata"]["name"]] = doc["spec"]["source"]["targetRevision"] - return pins - - -def live() -> dict[str, str]: - sys.path.insert(0, str(Path(__file__).resolve().parent)) - import kube_route # railiance01 SSH lane; never the workstation kubeconfig - - out = subprocess.run( - kube_route.ssh_argv(["kubectl", "-n", "argocd", "get", "applications.argoproj.io", "-o", "json"]), - capture_output=True, text=True, check=True, - ).stdout - return { - item["metadata"]["name"]: item["spec"].get("source", {}).get("targetRevision", "") - for item in json.loads(out)["items"] - } - - -def main() -> int: - try: - running = live() - except (subprocess.CalledProcessError, FileNotFoundError, ValueError) as exc: - print(f"ERROR: cannot read live Applications: {exc}", file=sys.stderr) - return 2 - drift = 0 - for name, pin in declared().items(): - current = running.get(name) - if current == pin: - print(f"ok {name} {pin[:12]}") - else: - drift += 1 - print(f"DRIFT {name} git={pin[:12]} live={(current or 'absent')[:12]}") - if drift: - print(f"{drift} child pin(s) differ: commit the live pin here (or revert live) " - "before syncing railiance-apps-root.", file=sys.stderr) - return 1 if drift else 0 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/scripts/argocd_public_transport.py b/scripts/argocd_public_transport.py deleted file mode 100644 index 01fb0f7..0000000 --- a/scripts/argocd_public_transport.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python3 -"""Guarded public-only Argo transport prerequisite; no Secret or credential calls.""" -import argparse -import copy -from datetime import datetime,timezone -import hashlib -import json -import os -from pathlib import Path -import re -import shlex -import subprocess - -URL='ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git' -D2='89f1058f-33bc-47a8-8a49-0b44d426c67e' -TARGETS=[('configmap','argocd-ssh-known-hosts-cm','/data/ssh_known_hosts','railiance-enablement'),('appproject','railiance-tenants','/spec/sourceRepos','railiance-platform')] - -class Refused(Exception):pass - -def _kube_route(): - """Local-only import; this module's source may also run remotely without it.""" - import sys - here = str(Path(__file__).resolve().parent) - if here not in sys.path: - sys.path.insert(0, here) - import kube_route - return kube_route - -def require(ok,reason): - if not ok:raise Refused(reason) - -def digest(value):return hashlib.sha256(json.dumps(value,sort_keys=True).encode()).hexdigest() - -def run(args,body=None): - try: - result=subprocess.run(_kube_route().ssh_argv(['kubectl','--request-timeout=15s',*args]),input=body,text=True,capture_output=True,timeout=60) - except Exception:raise Refused('command_outcome_unverified') from None - require(result.returncode==0,'command_outcome_unverified') - return result.stdout - -def value(obj,path): - for key in path.strip('/').split('/'):obj=obj[key] - return obj - -def replace(obj,path,wanted): - keys=path.strip('/').split('/');parent=obj - for key in keys[:-1]:parent=parent[key] - parent[keys[-1]]=wanted - -def normalized(obj): - obj=copy.deepcopy(obj) - for field in ('managedFields','resourceVersion','generation'):obj.get('metadata',{}).pop(field,None) - return obj - -def validate(plan): - require(plan.get('schema')=='argocd-public-transport/v1' and plan.get('task')=='RPF-WP-0044-T04' and plan.get('approval_ref')==D2,'plan_authority') - require(plan.get('target')=='railiance01' and plan.get('namespace')=='argocd' and plan.get('repo_server_tcp_30022_verified') is True,'plan_target') - require(all(isinstance(plan.get(k),str) and plan[k] for k in ('diff_ref','rollback','abort_owner','baseline_sha256','public_authority_observation_sha256')),'plan_receipt_contract') - require(len(plan.get('resources',[]))==2,'target_count') - for item,fixed in zip(plan['resources'],TARGETS): - require(tuple(item.get(k) for k in ('kind','name','path','owner'))==fixed,'fixed_target') - require(bool(re.fullmatch(r'[a-f0-9-]{36}',item.get('uid',''))),'target_uid') - cm,project=plan['resources'] - require(isinstance(cm['before'],str) and cm['before'].endswith('\n') and isinstance(cm['after'],str),'host_field_type') - added=cm['after'][len(cm['before']):] - require(cm['after'].startswith(cm['before']) and bool(re.fullmatch(r'\[forgejo\.coulomb\.social\]:30022 (ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256) [A-Za-z0-9+/=]+\n',added)),'host_addition_scope') - require('[forgejo.coulomb.social]:30022 ' not in cm['before'],'host_preimage_already_admitted') - require(isinstance(project['before'],list) and all(isinstance(v,str) for v in project['before']) and URL not in project['before'] and project['after']==project['before']+[URL],'repository_addition_scope') - -def patch(item,live,rollback=False): - before,after=(item['after'],item['before']) if rollback else (item['before'],item['after']) - require(live['metadata']['uid']==item['uid'],'target_uid_changed') - require(value(live,item['path'])==before,'target_preimage_changed') - require(isinstance(live['metadata'].get('resourceVersion'),str),'resource_version_required') - return [{'op':'test','path':'/metadata/uid','value':item['uid']},{'op':'test','path':'/metadata/resourceVersion','value':live['metadata']['resourceVersion']},{'op':'test','path':item['path'],'value':before},{'op':'replace','path':item['path'],'value':after}] - -def perform(plan,action,receipt,checkpoint=lambda:None): - validate(plan) - nodes=json.loads(run(['get','nodes','-o','json']))['items'] - require(len(nodes)==1 and any(a.get('address')=='92.205.62.239' for a in nodes[0]['status']['addresses']),'wrong_estate') - read=lambda item:json.loads(run(['-n','argocd','get',item['kind'],item['name'],'-o','json'])) - rollback=action=='rollback' - # Validate both targets before the first write, avoiding an avoidable partial change. - live=[read(item) for item in plan['resources']] - for item,obj in zip(plan['resources'],live): - require(obj['metadata']['uid']==item['uid'],'target_uid_changed') - if action=='verify':require(value(obj,item['path'])==item['after'],'verification_field_changed') - elif rollback and value(obj,item['path'])==item['before']:continue - else:patch(item,obj,rollback) - for item,obj in zip(plan['resources'],live): - record={'kind':item['kind'],'name':item['name'],'uid':item['uid'],'field':item['path'],'before_sha256':digest(item['before']),'after_sha256':digest(item['after'])};receipt['resources'].append(record) - if action=='verify':record['verified']=True;continue - if rollback and value(obj,item['path'])==item['before']:record['already_restored']=True;continue - body=json.dumps(patch(item,obj,rollback));args=['-n','argocd','patch',item['kind'],item['name'],'--type=json','--patch-file=/dev/stdin'] - admitted=json.loads(run(args+['--dry-run=server','-o','json'],body)) - wanted=copy.deepcopy(obj);replace(wanted,item['path'],item['before'] if rollback else item['after']) - require(normalized(admitted)==normalized(wanted),'dry_run_modified_other_fields');record['server_dry_run']=True - if action=='dry-run':continue - receipt['pending_target']=item['kind']+'/'+item['name'];receipt['mutation_attempted']=True;checkpoint() - run(args+['-o','name'],body) - checked=read(item) - require(checked['metadata']['uid']==item['uid'] and normalized(checked)==normalized(wanted),'postchange_projection_unverified') - record['changed_verified']=True;receipt['pending_target']=None;checkpoint() - receipt['passed']=True;return receipt - -def main(): - parser=argparse.ArgumentParser(description=__doc__) - parser.add_argument('--plan',type=Path,required=True);parser.add_argument('--plan-sha256',required=True) - parser.add_argument('--action',choices=('dry-run','execute','verify','rollback'),default='dry-run') - parser.add_argument('--receipt',type=Path,required=True) - args=parser.parse_args() - receipt={'schema':'argocd-public-transport-receipt/v1','task':'RPF-WP-0044-T04','approval_ref':D2,'action':args.action,'passed':False,'mutation_attempted':False,'resources':[],'secret_or_credential_calls':False,'captured_at':datetime.now(timezone.utc).isoformat()} - # Exclusive receipt is a replay guard. Never overwrite another attempt. - fd=None - try: - require(not args.receipt.exists(),'receipt_exists_do_not_replay') - fd=os.open(args.receipt,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) - raw=args.plan.read_bytes();require(hashlib.sha256(raw).hexdigest()==args.plan_sha256,'plan_changed') - def checkpoint(): - os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0);os.write(fd,(json.dumps(receipt,sort_keys=True)+'\n').encode());os.fsync(fd) - receipt['plan_sha256']=args.plan_sha256;checkpoint();perform(json.loads(raw),args.action,receipt,checkpoint) - except Refused as error:receipt['failure']=str(error) - except Exception:receipt['failure']='internal_outcome_unverified' - finally: - if fd is not None: - os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0);os.write(fd,(json.dumps(receipt,sort_keys=True)+'\n').encode());os.fsync(fd);os.close(fd) - # Only whitelist receipt fields are emitted. Every raw child error stays captured. - print(json.dumps(receipt,sort_keys=True));return 0 if receipt['passed'] else 1 - -if __name__=='__main__':raise SystemExit(main()) diff --git a/scripts/attended_service_delivery.py b/scripts/attended_service_delivery.py deleted file mode 100644 index 3643bf0..0000000 --- a/scripts/attended_service_delivery.py +++ /dev/null @@ -1,70 +0,0 @@ -#!/usr/bin/env python3 -"""attended-service-credential-delivery CCRs: a value in platform custody that no workload reads. - -NetKingdom NK-WP-0056-T04 (svc-privacyidea). Some services keep a credential in -their own encrypted store and accept it only through an administrator API -(privacyIDEA's LDAP resolver bind) that a person operates. The value is held in -OpenBao, written once with CAS=0 from the attended platform-admin session, and -delivered by an attended procedure that reads it in memory and writes it into -the service. No Kubernetes or workload auth role, policy or ESO object exists, -and the runtime frontdoor stays disabled. Modelled on attended-host-key-delivery -(CCR-2026-0028). Nothing here reads, prints or accepts a secret value. -""" -from __future__ import annotations - -import re -from typing import Any - -REQUEST_TYPE = "attended-service-credential-delivery" -APPROVED = {"applied", "verified", "active"} # approve sets "approved" after the first reviewer -KV_RE = re.compile(r"^platform/workloads/[a-z0-9][a-z0-9-]*(/[a-z0-9][a-z0-9-]*)+$") -FIELD_RE = re.compile(r"^[A-Z][A-Z0-9_]*$") -AUTH = {"method": "attended-oidc", "mount": "netkingdom", "role": "platform-admin"} -DELIVERY_KEYS = ("surface", "service", "target", "procedure", "gate") -DOC = "docs/credential-lane-designs/attended-service-credential-delivery.md" - - -def validate_contract(ccr: dict[str, Any]) -> list[str]: - errors: list[str] = [] - if ccr.get("request_type") != REQUEST_TYPE: - errors.append("unsupported attended service contract") - openbao = ccr.get("openbao") - if not isinstance(openbao, dict): - return errors + ["openbao must be an object"] - if openbao.get("mount") != "platform" or not KV_RE.match(str(openbao.get("kv_path", ""))): - errors.append("openbao.kv_path must be a platform/workloads/... path on mount platform") - fields = openbao.get("fields") - if not isinstance(fields, list) or not fields or not all(isinstance(f, str) and FIELD_RE.match(f) for f in fields): - errors.append("openbao.fields must list upper-case field names") - if openbao.get("auth") != AUTH: - errors.append("openbao.auth must be the attended platform-admin OIDC session only") - if openbao.get("runtime_access") is not False or openbao.get("initial_cas") != 0: - errors.append("runtime_access must be false and initial_cas 0") - extra = set(openbao) - {"mount", "kv_path", "fields", "auth", "runtime_access", "initial_cas"} - if extra: - errors.append("openbao must not declare policies, roles or other access: " + ", ".join(sorted(extra))) - if (ccr.get("access_frontdoor") or {}).get("resolvable") is not False: - errors.append("runtime credential frontdoor must remain disabled") - delivery = ccr.get("delivery") - if not isinstance(delivery, dict) or any(not isinstance(delivery.get(k), str) or not delivery[k] for k in DELIVERY_KEYS): - errors.append("delivery needs surface, service, target, procedure and gate") - elif delivery["surface"] != "attended-service-config": - errors.append("delivery.surface must be attended-service-config") - if ccr.get("status") in APPROVED: - comments = (ccr.get("review") or {}).get("comments") or [] - approved = {str(c.get("reviewer", "")).removeprefix("User (").removesuffix(")") - for c in comments if c.get("decision") == "approved"} - for role in (ccr.get("review") or {}).get("required_approvers") or []: - if role not in approved: - errors.append("explicit role approval missing: " + role) - for section, keys in {"verification": ["positive", "negative", "activation_conditions"], - "lifecycle": ["deactivate", "rotate", "compromised"]}.items(): - for key in keys: - if not (ccr.get(section) or {}).get(key): - errors.append(f"{section}.{key} required") - return errors - - -def procedure_note(ccr: dict[str, Any]) -> str: - return (f"Attended delivery into {ccr['delivery']['service']} through {ccr['delivery']['procedure']} " - f"({DOC}). No workload policy, auth role or ESO object is created.") diff --git a/scripts/audit-core-database-lease-recovery.py b/scripts/audit-core-database-lease-recovery.py index 7dc405f..08b1ff8 100755 --- a/scripts/audit-core-database-lease-recovery.py +++ b/scripts/audit-core-database-lease-recovery.py @@ -14,6 +14,7 @@ import hashlib import importlib.util import json import os +import stat import subprocess import sys import time @@ -24,9 +25,6 @@ from typing import Any, Callable ROOT = Path(__file__).resolve().parents[1] TASK_ID = "RAILIANCE-WP-0024-T02" -# The post-reboot synthetic E2 (T03 runbook step 8) reuses this receipt shape and lane. -E2_TASK_ID = "RAILIANCE-WP-0024-T03" -E2_CONFIRM = f"{E2_TASK_ID}:synthetic-e2:attended" PROCEDURE = "audit-core-database-lease-recovery" LEASE_PREFIX = "database/creds/audit-core-runtime" EXTERNAL_SECRET = "audit-core-database" @@ -73,16 +71,14 @@ def parse_time(value: str) -> datetime: return parsed.astimezone(UTC) -def load_approval( - path: Path, now: datetime, *, require_open_window: bool, task_id: str = TASK_ID -) -> dict[str, Any]: +def load_approval(path: Path, now: datetime, *, require_open_window: bool) -> dict[str, Any]: try: document = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError) as exc: raise ProcedureError("approval receipt is unavailable or invalid JSON") from exc if not isinstance(document, dict): raise ProcedureError("approval receipt must be an object") - if document.get("procedure") != PROCEDURE or document.get("task_id") != task_id: + if document.get("procedure") != PROCEDURE or document.get("task_id") != TASK_ID: raise ProcedureError("approval receipt has the wrong procedure or task") if document.get("status") != "approved" or not document.get("approval_id"): raise ProcedureError("approval receipt is not approved") @@ -104,12 +100,6 @@ def load_approval( load = document.get("synthetic_load") or {} if not load.get("contract_id") or not load.get("driver_revision"): raise ProcedureError("approval receipt lacks the synthetic-load contract") - sender = document.get("synthetic_sender") - # Strict naming/scope/expiry rules live in audit_core_synthetic_sender_lane.py. - if not isinstance(sender, dict) or set(sender) != {"identity", "source", "tenant", "expires_at"} or not all( - isinstance(value, str) and value for value in sender.values() - ): - raise ProcedureError("approval receipt lacks the projected synthetic sender identity") return document @@ -126,46 +116,33 @@ def safe_run( return completed -# Operator OpenBao access is the SSH tunnel; the public endpoint was retracted -# 2026-09-15 (RMASTER-WP-0020-T09). -TUNNEL_ADDR = "http://127.0.0.1:18200" -CONTAINMENT_MARKER = "WARDEN_ROUTING_CATALOG" -TOKEN_ENV = ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN") - - -def contained_bao_env(environ: dict[str, str] | None = None) -> dict[str, str]: - """Environment for ``bao`` inside the contained attended login only. - - ``scripts/openbao-attended-exec.py`` runs ``warden access - openbao-platform-admin-login --exec`` with BAO_ADDR pinned to the tunnel, - no token variable, and the Warden routing marker; ``bao`` then - authenticates through Warden's isolated token helper. Anything else -- - a token file, a token variable, or another address -- is refused. - """ - env = dict(os.environ if environ is None else environ) - if not env.get(CONTAINMENT_MARKER): - raise ProcedureError( - "live OpenBao access requires the contained attended login " - "(scripts/openbao-attended-exec.py)" - ) - if env.get("BAO_ADDR", TUNNEL_ADDR) != TUNNEL_ADDR: - raise ProcedureError(f"BAO_ADDR must be the operator tunnel {TUNNEL_ADDR}") - if any(env.get(name) for name in TOKEN_ENV): - raise ProcedureError("token variables must be unset inside the contained login") - for name in TOKEN_ENV: - env.pop(name, None) - env["BAO_ADDR"] = TUNNEL_ADDR - return env - - class Authority: - def __init__(self) -> None: - self.env = contained_bao_env() + def __init__(self, token_file: Path) -> None: + if not token_file.is_file() or stat.S_IMODE(token_file.stat().st_mode) != 0o600: + raise ProcedureError("OpenBao token file must exist with mode 0600") + token = token_file.read_text(encoding="utf-8").splitlines()[0].strip() + if not token: + raise ProcedureError("OpenBao token file is empty") + self.env = dict(os.environ, BAO_ADDR="https://bao.coulomb.social", BAO_TOKEN=token) def bao(self, args: list[str], *, label: str) -> str: return safe_run(["bao", *args], label=label, env=self.env).stdout.strip() - def _lookup(self, lease_id: str) -> dict[str, Any]: + def single_runtime_lease(self) -> dict[str, Any]: + raw = self.bao( + ["list", "-format=json", f"sys/leases/lookup/{LEASE_PREFIX}"], + label="list exact runtime lease handles", + ) + try: + handles = json.loads(raw) + except json.JSONDecodeError as exc: + raise ProcedureError("runtime lease list is invalid JSON") from exc + if not isinstance(handles, list) or len(handles) != 1: + count = len(handles) if isinstance(handles, list) else "unknown" + raise ProcedureError( + f"exact runtime prefix must contain one live handle (observed {count})" + ) + lease_id = f"{LEASE_PREFIX}/{handles[0]}" lookup_raw = self.bao( ["write", "-format=json", "sys/leases/lookup", f"lease_id={lease_id}"], label="lookup exact runtime lease metadata", @@ -185,28 +162,6 @@ class Authority: "ttl": ttl, } - def runtime_lease_for(self, secret: dict[str, Any]) -> dict[str, Any]: - """The one handle issued for the mounted Secret's refresh. - - ESO refreshes every 15m while the role's TTL is 15m (max 1h), so the - predecessor of the mounted credential can still be live. Select only the - handle coherent with the Secret refresh; every other handle must be an - older predecessor, which is never revoked. - """ - raw = self.bao( - ["list", "-format=json", f"sys/leases/lookup/{LEASE_PREFIX}"], - label="list exact runtime lease handles", - ) - try: - handles = json.loads(raw) - except json.JSONDecodeError as exc: - raise ProcedureError("runtime lease list is invalid JSON") from exc - if not isinstance(handles, list) or not 1 <= len(handles) <= MAX_RUNTIME_HANDLES: - count = len(handles) if isinstance(handles, list) else "unknown" - raise ProcedureError(f"exact runtime prefix has an unexpected handle count ({count})") - leases = [self._lookup(f"{LEASE_PREFIX}/{handle}") for handle in handles] - return select_runtime_lease(leases, secret) - def revoke(self, lease_id: str) -> None: self.bao(["lease", "revoke", lease_id], label="revoke exact runtime lease") @@ -265,52 +220,11 @@ def secret_state(remote: Any) -> dict[str, Any]: } -ATTEST_CRONJOB = "audit-core-attest-chain" -ATTEST_GUARD = timedelta(minutes=20) - - -def next_daily_run(schedule: str, time_zone: str | None, now: datetime) -> datetime: - """Next run of a fixed daily "M H * * *" UTC schedule; anything else fails closed.""" - fields = schedule.split() - if ( - time_zone not in ("Etc/UTC", "UTC") - or len(fields) != 5 - or fields[2:] != ["*", "*", "*"] - or not fields[0].isdigit() - or not fields[1].isdigit() - ): - raise ProcedureError("attest-chain schedule is not a fixed daily UTC time; choose the window manually") - run = now.replace(hour=int(fields[1]), minute=int(fields[0]), second=0, microsecond=0) - return run if run >= now - ATTEST_GUARD else run + timedelta(days=1) - - -def assert_attest_chain_quiet(cronjob: dict[str, Any], now: datetime) -> None: - """The attest CronJob mounts the same database Secret; never revoke under it.""" - spec = cronjob.get("spec", {}) - if cronjob.get("status", {}).get("active"): - raise ProcedureError("an attest-chain Job is active; wait for it to finish") - if spec.get("suspend"): - return - run = next_daily_run(str(spec.get("schedule", "")), spec.get("timeZone"), now) - if abs(run - now) < ATTEST_GUARD: - raise ProcedureError("window is within 20 minutes of the attest-chain schedule") - - -def attest_chain_state(remote: Any) -> None: - cronjob = remote.kubectl_json( - ["-n", "audit-core", "get", "cronjob", ATTEST_CRONJOB], - label="read attest-chain schedule", - ) - assert_attest_chain_quiet(cronjob, datetime.now(UTC)) - - def pod_state(remote: Any) -> dict[str, Any]: - pods = PREFLIGHT.serving_pods( - remote.kubectl_json( - ["-n", "audit-core", "get", "pods", "-l", "app.kubernetes.io/name=audit-core"], - label="read audit-core pod metadata", - ) - ) + pods = remote.kubectl_json( + ["-n", "audit-core", "get", "pods", "-l", "app.kubernetes.io/name=audit-core"], + label="read audit-core pod metadata", + ).get("items", []) if len(pods) != 1: raise ProcedureError(f"expected one audit-core pod, observed {len(pods)}") statuses = pods[0].get("status", {}).get("containerStatuses", []) @@ -320,24 +234,6 @@ def pod_state(remote: Any) -> dict[str, Any]: } -MAX_RUNTIME_HANDLES = 8 - - -def select_runtime_lease(leases: list[dict[str, Any]], secret: dict[str, Any]) -> dict[str, Any]: - coherent = [ - lease for lease in leases - if abs((lease["issue_time"] - secret["refresh_time"]).total_seconds()) <= 5 - ] - if len(coherent) != 1: - raise ProcedureError( - f"exactly one runtime lease must match the mounted Secret refresh (observed {len(coherent)})" - ) - selected = coherent[0] - if any(lease["issue_time"] >= selected["issue_time"] for lease in leases if lease is not selected): - raise ProcedureError("a runtime lease newer than the mounted credential exists") - return {**selected, "predecessor_handles": len(leases) - 1} - - def assert_lease_matches_refresh(lease: dict[str, Any], secret: dict[str, Any]) -> None: delta = abs((lease["issue_time"] - secret["refresh_time"]).total_seconds()) if ( @@ -385,68 +281,12 @@ def force_refresh(remote: Any, baseline: dict[str, Any]) -> dict[str, Any]: return latest -def synthetic_e2(args: argparse.Namespace) -> dict[str, Any]: - """Post-reboot E2: one synthetic event accepted exactly once, no lease touched. - - baseline must be accepted; resending the same fixture must return duplicate - (exactly-once); the pod must not change while the event is written. - """ - approval = load_approval(args.approval, datetime.now(UTC), require_open_window=True, task_id=E2_TASK_ID) - if args.confirm != E2_CONFIRM: - raise ProcedureError(f"synthetic E2 requires --confirm {E2_CONFIRM}") - remote = PREFLIGHT.Remote(args.remote) - driver = LoadDriver( - args.load_driver, - approval["synthetic_load"]["contract_id"], - approval["synthetic_load"]["driver_revision"], - ) - before_pod = pod_state(remote) - if PREFLIGHT.endpoint_status(remote, "/readyz") != 200: - raise ProcedureError("audit-core is not ready for the synthetic E2") - # The driver's baseline phase reports "ready" for accepted *or* duplicate, so it - # cannot prove a write. Send twice: the first answer must be a fresh accept, - # the second a duplicate of the same fixture (accepted exactly once). - first = driver.run("expect-recovered") - if first.get("status") != "accepted" or first.get("http_status") not in (200, 202): - raise ProcedureError("synthetic E2 first send was not a fresh accept (fixture already present?)") - replay = driver.run("expect-recovered") - if replay.get("status") != "duplicate" or replay.get("fixture_id") != first.get("fixture_id"): - raise ProcedureError("synthetic E2 replay was not recognised as a duplicate") - # The driver owns no server-side object; its cleanup only confirms local state. - # The synthetic event stays in the append-only chain under the trial tenant. - load_cleanup = driver.run("cleanup") - if load_cleanup.get("status") != "clean" or load_cleanup.get("fixture_id") != first.get("fixture_id"): - raise ProcedureError("synthetic E2 driver cleanup did not report clean for this fixture") - after_pod = pod_state(remote) - if after_pod["uid"] != before_pod["uid"] or after_pod["restart_count"] != before_pod["restart_count"]: - raise ProcedureError("audit-core pod identity or restart count changed during the synthetic E2") - return { - "procedure": PROCEDURE, - "mode": "post-reboot-synthetic-e2", - "task_id": E2_TASK_ID, - "approval_id": approval["approval_id"], - "completed_at": datetime.now(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z"), - "synthetic_event_accepted": True, - "first_send_status": first["status"], - "first_send_http_status": first["http_status"], - "synthetic_event_retained": "append-only chain, trial tenant (driver owns no server-side object)", - "replay_status": replay["status"], - "accepted_exactly_once": True, - "same_pod_uid": after_pod["uid"] == before_pod["uid"], - "restart_count_unchanged": after_pod["restart_count"] == before_pod["restart_count"], - "driver_cleanup_status": load_cleanup.get("status"), - "synthetic_load_contract": approval["synthetic_load"]["contract_id"], - "synthetic_load_driver_revision": approval["synthetic_load"]["driver_revision"], - "secret_values_observed": False, - } - - def exercise(args: argparse.Namespace) -> dict[str, Any]: approval = load_approval(args.approval, datetime.now(UTC), require_open_window=True) if args.confirm != CONFIRM: raise ProcedureError(f"live exercise requires --confirm {CONFIRM}") remote = PREFLIGHT.Remote(args.remote) - authority = Authority() + authority = Authority(args.token_file) driver = LoadDriver( args.load_driver, approval["synthetic_load"]["contract_id"], @@ -464,10 +304,9 @@ def exercise(args: argparse.Namespace) -> dict[str, Any]: if not preflight["ready_for_live_execution"]: raise ProcedureError("database lease recovery preflight is not ready") - attest_chain_state(remote) before_pod = pod_state(remote) before_secret = secret_state(remote) - lease = authority.runtime_lease_for(before_secret) + lease = authority.single_runtime_lease() assert_lease_matches_refresh(lease, before_secret) baseline_load = driver.run("baseline") if baseline_load.get("status") != "ready": @@ -482,7 +321,7 @@ def exercise(args: argparse.Namespace) -> dict[str, Any]: # Close the race with ESO's ordinary refresh before the destructive step. if secret_state(remote) != before_secret or pod_state(remote) != before_pod: raise ProcedureError("baseline changed before revocation") - current = authority.runtime_lease_for(before_secret) + current = authority.single_runtime_lease() if current["fingerprint"] != lease["fingerprint"]: raise ProcedureError("runtime lease changed before revocation") authority.revoke(lease["id"]) @@ -563,30 +402,18 @@ def exercise(args: argparse.Namespace) -> dict[str, Any]: } - -def emit_result(result: dict, result_file: "Path | None") -> None: - """Under the contained login the child must stay silent: write, never print.""" - if result_file is None: - print(json.dumps(result, indent=2, sort_keys=True)) - return - fd = os.open(result_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "w", encoding="utf-8") as handle: - json.dump(result, handle, indent=2, sort_keys=True) - handle.write("\n") - -def main(argv: list[str] | None = None) -> int: +def main() -> int: parser = argparse.ArgumentParser() - parser.add_argument("command", choices=["validate-approval", "exercise", "synthetic-e2"]) + parser.add_argument("command", choices=["validate-approval", "exercise"]) parser.add_argument("--approval", type=Path, required=True) parser.add_argument("--remote", default="railiance01") parser.add_argument( - "--contained", action="store_true", - help="required for exercise: run inside the contained attended OpenBao login", + "--token-file", type=Path, + default=Path.home() / ".local/openbao/platform-admin.token", ) parser.add_argument("--load-driver", type=Path) parser.add_argument("--confirm") - parser.add_argument("--result-file", type=Path, help="write the JSON result here (0600, new) and print nothing") - args = parser.parse_args(argv) + args = parser.parse_args() try: if args.command == "validate-approval": approval = load_approval(args.approval, datetime.now(UTC), require_open_window=False) @@ -597,18 +424,13 @@ def main(argv: list[str] | None = None) -> int: "secret_values_observed": False, } else: - if not args.contained: - raise ProcedureError("live exercise requires --contained (scripts/openbao-attended-exec.py)") if args.load_driver is None: raise ProcedureError("live exercise requires --load-driver") - result = synthetic_e2(args) if args.command == "synthetic-e2" else exercise(args) + result = exercise(args) except (OSError, IndexError, ProcedureError, PREFLIGHT.PreflightError) as exc: - if args.result_file is None: - print(f"database lease recovery failed: {exc}", file=sys.stderr) - else: - emit_result({"status": "failed", "error": str(exc), "secret_values_observed": False}, args.result_file) + print(f"database lease recovery failed: {exc}", file=sys.stderr) return 1 - emit_result(result, args.result_file) + print(json.dumps(result, indent=2, sort_keys=True)) return 0 diff --git a/scripts/audit-core-recovery-preflight.py b/scripts/audit-core-recovery-preflight.py index ba752e0..b2cbb5b 100755 --- a/scripts/audit-core-recovery-preflight.py +++ b/scripts/audit-core-recovery-preflight.py @@ -21,7 +21,7 @@ from typing import Any EXPECTED_IMAGE = ( "forgejo.coulomb.social/coulomb/audit-core@" - "sha256:ec15f63d49226bfe507af2bc38ffbd5e83f549ba2c6d1ae7a338e7e053f34615" + "sha256:c2fe39a0185b99be3fc0cb14d2de69772b8e66e20490097c9d11d90cc39719a6" ) EXPECTED_DB_KEYS = {"username", "password", "host", "port", "dbname"} @@ -30,18 +30,6 @@ class PreflightError(RuntimeError): pass -def serving_pods(pod_list: dict[str, Any]) -> list[dict[str, Any]]: - """Deployment pods only: audit-core attest-chain Job pods share the app label.""" - return [ - pod - for pod in pod_list.get("items", []) - if not any( - owner.get("kind") == "Job" - for owner in pod.get("metadata", {}).get("ownerReferences", []) - ) - ] - - class Remote: def __init__(self, host: str) -> None: self.host = host @@ -104,23 +92,6 @@ def latest_completed_backup(backups: dict[str, Any], now: datetime) -> dict[str, } -def running_pod_image(pod: dict[str, Any]) -> dict[str, Any]: - """Image actually running in the serving pod, not only the Deployment spec. - - ``status.containerStatuses[].imageID`` carries the digest the kubelet - resolved; a spec that matches while an older ReplicaSet pod still runs must - fail closed. - """ - containers = pod.get("spec", {}).get("containers", []) - statuses = pod.get("status", {}).get("containerStatuses", []) - name = containers[0].get("name") if containers else None - spec_image = containers[0].get("image") if containers else None - status = next((item for item in statuses if item.get("name") == name), None) - image_id = (status or {}).get("imageID") or "" - match = re.search(r"@(sha256:[0-9a-f]{64})$", image_id) - return {"image": spec_image, "digest": match.group(1) if match else None} - - def resource_condition(resource: dict[str, Any]) -> dict[str, Any]: conditions = resource.get("status", {}).get("conditions", []) current = conditions[0] if conditions else {} @@ -169,10 +140,6 @@ def validate_snapshot_receipt( for key in required_true: if receipt.get(key) is not True: raise PreflightError(f"OpenBao snapshot receipt requires {key}=true") - # A snapshot whose only decryption key lives inside OpenBao cannot restore OpenBao. - escrow = receipt.get("custodian_escrow_ref") - if not isinstance(escrow, str) or not escrow.strip() or "replace" in escrow.lower(): - raise PreflightError("OpenBao snapshot receipt names no decryption key held outside OpenBao") if ( receipt["source_cluster"] != "railiance01" or receipt["source_namespace"] != "openbao" @@ -237,7 +204,7 @@ def common_state(remote: Remote, now: datetime) -> dict[str, Any]: ["-n", "audit-core", "get", "pods", "-l", "app.kubernetes.io/name=audit-core"], label="read audit-core pod metadata", ) - pods = serving_pods(pod_list) + pods = pod_list.get("items", []) if len(pods) != 1: raise PreflightError(f"expected one audit-core pod, observed {len(pods)}") pod = pods[0] @@ -268,7 +235,6 @@ def common_state(remote: Remote, now: datetime) -> dict[str, Any]: except json.JSONDecodeError as exc: raise PreflightError("OpenBao seal state returned invalid JSON") from exc image = deployment["spec"]["template"]["spec"]["containers"][0]["image"] - running = running_pod_image(pod) state = { "captured_at": now.replace(microsecond=0).isoformat().replace("+00:00", "Z"), "node": { @@ -302,12 +268,6 @@ def common_state(remote: Remote, now: datetime) -> dict[str, Any]: "audit_core": { "image": image, "image_matches_reviewed_digest": image == EXPECTED_IMAGE, - "running_pod_image": running["image"], - "running_pod_image_digest": running["digest"], - "running_pod_image_matches_reviewed_digest": ( - running["image"] == EXPECTED_IMAGE - and running["digest"] == EXPECTED_IMAGE.rsplit("@", 1)[1] - ), "ready_replicas": deployment.get("status", {}).get("readyReplicas", 0), "replicas": deployment.get("spec", {}).get("replicas", 0), "pod_uid": pod["metadata"]["uid"], @@ -331,7 +291,6 @@ def automated_common_pass(state: dict[str, Any]) -> bool: all(item["ready"] for item in state["external_secret_stores"].values()), all(item["ready"] for item in state["external_secrets"].values()), state["audit_core"]["image_matches_reviewed_digest"], - state["audit_core"]["running_pod_image_matches_reviewed_digest"], state["audit_core"]["ready_replicas"] == state["audit_core"]["replicas"] == 1, ) ) @@ -395,474 +354,14 @@ def database_lease_preflight(remote: Remote, args: argparse.Namespace) -> dict[s } -# --- node-reboot specific checks ------------------------------------------- - -# Named core platform workloads (namespace, kind, name) that must be fully -# Ready. Every other Deployment/StatefulSet in these namespaces must be Ready -# as well, so a renamed or newly added operator component cannot hide. -CORE_PLATFORM_WORKLOADS = ( - ("kube-system", "Deployment", "coredns"), - ("kube-system", "Deployment", "local-path-provisioner"), - ("kube-system", "Deployment", "traefik"), - ("kube-system", "Deployment", "metrics-server"), - ("cert-manager", "Deployment", "cert-manager"), - ("cnpg-system", "Deployment", "cnpg-controller-manager"), - ("external-secrets", "Deployment", "external-secrets"), - ("knative-serving", "Deployment", "activator"), - ("knative-serving", "Deployment", "controller"), - ("knative-serving", "Deployment", "net-kourier-controller"), - ("kourier-system", "Deployment", "3scale-kourier-gateway"), - ("argocd", "Deployment", "argocd-repo-server"), - ("argocd", "StatefulSet", "argocd-application-controller"), - ("openbao", "StatefulSet", "openbao"), -) -CORE_PLATFORM_NAMESPACES = frozenset(ns for ns, _, _ in CORE_PLATFORM_WORKLOADS) - -# CNPG clusters observed on 2026-10-14. The live list is discovered; this set -# only makes a vanished cluster fail closed instead of silently shrinking scope. -EXPECTED_CNPG_CLUSTERS = frozenset({ - "databases/apps-pg", "databases/forgejo-db", "databases/net-kingdom-pg", - "databases/platform-pg", "databases/platform-pg-2", "databases/state-hub-db", - "target-revenue/target-revenue-pg", "user-engine/user-engine-pg", -}) -FRESH_BACKUP_REQUIRED = frozenset({"databases/platform-pg"}) -FRESH_BACKUP_RECOMMENDED = frozenset({"databases/state-hub-db", "user-engine/user-engine-pg"}) - -# Declared host firewall (railiance-infra RAIL-HO-WP-0009 / Goss). -UFW_ALLOWED = frozenset({"OpenSSH", "2224/tcp"}) -UFW_FORBIDDEN_PORTS = ("6443", "8472") -K3S_API_TUNNEL = "k3s-api-railiance01" - -OPS_WARDEN_RECEIPT = "reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json" -CONSUMED_DECISION_PREFIXES = ("449a697a", "85724c0c") -CONSUMED_SCENARIO_IDS = frozenset({"WARDEN-WP-0027-T02-DRILL-20260822-01"}) -SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{5,127}$") -FORBIDDEN_RECEIPT_MARKERS = ( - "BEGIN PRIVATE KEY", "BEGIN OPENSSH PRIVATE KEY", "AGE-SECRET-KEY-1", - "OPENBAO_ROOT_TOKEN", "VAULT_TOKEN", "hvs.", "password=", "://", -) - - -def workload_ready(resource: dict[str, Any]) -> bool: - wanted = resource.get("spec", {}).get("replicas", 1) - return wanted >= 1 and resource.get("status", {}).get("readyReplicas", 0) == wanted - - -def core_platform_workloads(workloads: dict[str, Any]) -> dict[str, Any]: - """Readiness of named core workloads plus every workload in core namespaces.""" - observed: dict[tuple[str, str, str], bool] = {} - for item in workloads.get("items", []): - meta = item.get("metadata", {}) - if meta.get("namespace") in CORE_PLATFORM_NAMESPACES: - key = (meta["namespace"], item.get("kind"), meta.get("name")) - observed[key] = workload_ready(item) - missing = sorted("/".join(key) for key in CORE_PLATFORM_WORKLOADS if key not in observed) - unready = sorted("/".join(key) for key, ready in observed.items() if not ready) - return { - "required": ["/".join(key) for key in CORE_PLATFORM_WORKLOADS], - "missing": missing, - "unready": unready, - "ready": not missing and not unready, - } - - -def failing_pods(pod_list: dict[str, Any]) -> list[str]: - """``namespace/name`` of every pod that is neither Succeeded nor Running+Ready.""" - failing = [] - for pod in pod_list.get("items", []): - status = pod.get("status", {}) - phase = status.get("phase") - if phase == "Succeeded": - continue - statuses = status.get("containerStatuses", []) - if phase == "Running" and statuses and all(item.get("ready") for item in statuses): - continue - meta = pod.get("metadata", {}) - failing.append(f"{meta.get('namespace')}/{meta.get('name')}") - return sorted(failing) - - -def failing_pod_diff(baseline: list[str], current: list[str]) -> dict[str, Any]: - new = sorted(set(current) - set(baseline)) - return { - "baseline_count": len(baseline), - "current_count": len(current), - "new_failing": new, - "resolved": sorted(set(baseline) - set(current)), - "no_new_failures": not new, - } - - -def system_identifier(controldata: str) -> str | None: - match = re.search(r"^Database system identifier:\s+(\d+)\s*$", controldata, re.MULTILINE) - return match.group(1) if match else None - - -def cnpg_identity(cluster: dict[str, Any], pvc_uids: dict[str, str]) -> dict[str, Any]: - """Value-safe identity/health of one CNPG cluster (no credentials, no DSNs).""" - meta = cluster.get("metadata", {}) - status = cluster.get("status", {}) - primary = status.get("currentPrimary") - backup_configured = bool(cluster.get("spec", {}).get("backup")) - archiving = condition(cluster, "ContinuousArchiving") - return { - "uid": meta.get("uid"), - "instances": cluster.get("spec", {}).get("instances", 0), - "ready_instances": status.get("readyInstances", 0), - "phase": status.get("phase"), - "current_primary": primary, - "primary_pvc_uid": pvc_uids.get(primary) if primary else None, - "healthy_pvc": sorted(status.get("healthyPVC", [])), - "backup_configured": backup_configured, - "continuous_archiving": archiving == "True" if backup_configured else None, - "system_id": None, - } - - -def cnpg_healthy(identity: dict[str, Any]) -> bool: - return ( - identity["ready_instances"] == identity["instances"] == 1 - and bool(identity["uid"]) - and bool(identity["primary_pvc_uid"]) - and bool(identity["system_id"]) - and identity["current_primary"] in identity["healthy_pvc"] - and identity["continuous_archiving"] is not False - ) - - -def latest_backups_by_cluster(backups: dict[str, Any], now: datetime) -> dict[str, Any]: - grouped: dict[str, list[dict[str, Any]]] = {} - for item in backups.get("items", []): - meta = item.get("metadata", {}) - cluster = item.get("spec", {}).get("cluster", {}).get("name") - if cluster: - grouped.setdefault(f"{meta.get('namespace')}/{cluster}", []).append(item) - result = {} - for key, items in grouped.items(): - try: - result[key] = latest_completed_backup({"items": items}, now) - except PreflightError: - result[key] = None - return result - - -def eso_inventory(stores: dict[str, Any], external_secrets: dict[str, Any]) -> dict[str, Any]: - """Every ClusterSecretStore Valid and every ExternalSecret SecretSynced.""" - def bad(items: list[dict[str, Any]], reason: str, namespaced: bool) -> list[str]: - out = [] - for item in items: - ready = next( - (c for c in item.get("status", {}).get("conditions", []) if c.get("type") == "Ready"), - {}, - ) - if ready.get("status") != "True" or ready.get("reason") != reason: - meta = item.get("metadata", {}) - out.append(f"{meta.get('namespace')}/{meta.get('name')}" if namespaced else meta.get("name")) - return sorted(out) - - store_items = stores.get("items", []) - es_items = external_secrets.get("items", []) - invalid_stores = bad(store_items, "Valid", False) - unsynced = bad(es_items, "SecretSynced", True) - return { - "cluster_store_count": len(store_items), - "invalid_cluster_stores": invalid_stores, - "external_secret_count": len(es_items), - "unsynced_external_secrets": unsynced, - "ready": bool(store_items) and bool(es_items) and not invalid_stores and not unsynced, - } - - -def ufw_state(status_text: str) -> dict[str, Any]: - """Compare ``ufw status`` with the declared OpenSSH + Nydus 2224 allow set.""" - active = bool(re.search(r"^Status:\s+active\s*$", status_text, re.MULTILINE)) - targets: set[str] = set() - in_rules = False - for line in status_text.splitlines(): - if line.startswith("--"): - in_rules = True - continue - if in_rules and line.strip(): - target = re.split(r"\s{2,}", line.strip())[0].replace(" (v6)", "") - targets.add(target) - forbidden = sorted( - target for target in targets - if any(re.match(rf"^{port}(/|:|$)", target) for port in UFW_FORBIDDEN_PORTS) - ) - return { - "active": active, - "allowed_targets": sorted(targets), - "forbidden_ports_open": forbidden, - "matches_declaration": active and targets == UFW_ALLOWED and not forbidden, - } - - -def boots_since(list_boots_json: str, since: datetime) -> int: - """Count boots whose first entry is after ``since`` (journalctl --list-boots -o json).""" - try: - boots = json.loads(list_boots_json) - except json.JSONDecodeError as exc: - raise PreflightError("boot list returned invalid JSON") from exc - count = 0 - for boot in boots: - first = boot.get("first_entry") - if isinstance(first, int) and datetime.fromtimestamp(first / 1_000_000, UTC) > since: - count += 1 - return count - - -def validate_ops_warden_receipt(path: Path) -> dict[str, Any]: - """RPF-WP-0017 re-acceptance of the ops-warden revision for the attended login.""" - try: - raw = path.read_text(encoding="utf-8") - receipt = json.loads(raw) - except (OSError, json.JSONDecodeError) as exc: - raise PreflightError("ops-warden acceptance receipt is unavailable or invalid JSON") from exc - if not isinstance(receipt, dict) or receipt.get("workplan_id", "RPF-WP-0017") != "RPF-WP-0017" or "RPF-WP-0017" not in raw: - raise PreflightError("ops-warden acceptance receipt is not an RPF-WP-0017 receipt") - if any(marker in raw for marker in FORBIDDEN_RECEIPT_MARKERS[:-1]): - raise PreflightError("ops-warden acceptance receipt contains forbidden material") - if receipt.get("secret_values_observed") is True or receipt.get("sensitive_material_recorded") is True: - raise PreflightError("ops-warden acceptance receipt records sensitive material") - disposition = next( - (str(receipt[key]).lower() for key in ("current_decision", "disposition", "decision", "status", "result") if key in receipt), - "", - ) - if disposition not in {"accept", "accepted", "approve", "approved"}: - raise PreflightError("ops-warden acceptance receipt does not record acceptance") - revision = next( - ( - receipt[key] for key in ("current_reviewed_revision", "reviewed_revision", "accepted_revision", "ops_warden_revision", "revision") - if isinstance(receipt.get(key), str) and re.fullmatch(r"[0-9a-f]{40}", receipt[key]) - ), - None, - ) - if not revision: - raise PreflightError("ops-warden acceptance receipt names no exact 40-hex revision") - return {"path": str(path), "accepted_revision": revision, "verified": True} - - -OPS_WARDEN_REPO = Path("/home/worsch/ops-warden") - - -def installed_warden_package(tool_python: Path) -> Path: - """Package directory of the warden actually on PATH (uv tool environment).""" - completed = subprocess.run( - [str(tool_python), "-I", "-c", "import os, warden; print(os.path.dirname(warden.__file__))"], - text=True, capture_output=True, check=False, - ) - if completed.returncode != 0 or not completed.stdout.strip(): - raise PreflightError("installed warden package could not be located") - return Path(completed.stdout.strip()) - - -def verify_installed_warden(package: Path, repo: Path, revision: str) -> dict[str, Any]: - """Prove the installed warden equals the accepted, published ops-warden revision. - - The uv tool is a non-editable directory install that records no revision, so - compare every installed module byte-for-byte with that revision's tree. - """ - def git(*args: str) -> subprocess.CompletedProcess[bytes]: - return subprocess.run(["git", "-C", str(repo), *args], capture_output=True, check=False) - - if git("merge-base", "--is-ancestor", revision, "origin/main").returncode != 0: - raise PreflightError("accepted ops-warden revision is not published on origin/main") - listed = git("ls-tree", "-r", "--name-only", revision, "src/warden/") - if listed.returncode != 0: - raise PreflightError("accepted ops-warden revision is not available locally") - expected = { - name.removeprefix("src/warden/") - for name in listed.stdout.decode().splitlines() - if name.endswith(".py") - } - installed = {path.relative_to(package).as_posix() for path in package.rglob("*.py")} - if installed != expected: - raise PreflightError("installed warden module set differs from the accepted revision") - for name in sorted(expected): - blob = git("show", f"{revision}:src/warden/{name}") - if blob.returncode != 0 or blob.stdout != (package / name).read_bytes(): - raise PreflightError("installed warden differs from the accepted revision") - return {"installed_revision_matches": True, "modules_compared": len(expected)} - - -CONSOLE_RECEIPT_ID = re.compile(r"^RAIL01-CONSOLE-(\d{8})T\d{6}Z$") - - -def validate_console_receipt(path: Path, *, window_id: str | None, now: datetime) -> dict[str, Any]: - """railiance-infra console attestation (docs/railiance01-console-access.md). - - Accept only a same-day receipt for this window that proves the console - shows the host and is independent of the guest network. Role-only fields; - any credential-looking or placeholder content is denied. - """ - try: - import yaml # type: ignore[import-untyped] - - raw = path.read_text(encoding="utf-8") - receipt = yaml.safe_load(raw) - except Exception as exc: # noqa: BLE001 - any parse/import failure is fail-closed - raise PreflightError("provider-console receipt is unavailable or invalid YAML") from exc - if not isinstance(receipt, dict): - raise PreflightError("provider-console receipt has an unsupported shape") - if any(marker in raw for marker in FORBIDDEN_RECEIPT_MARKERS) or "<" in raw: - raise PreflightError("provider-console receipt contains forbidden material/placeholder") - match = CONSOLE_RECEIPT_ID.fullmatch(str(receipt.get("receipt_id", ""))) - if not match or path.stem != receipt["receipt_id"]: - raise PreflightError("provider-console receipt id is not RAIL01-CONSOLE-") - try: - attested = rfc3339(str(receipt["attested_at"])) - except (KeyError, ValueError) as exc: - raise PreflightError("provider-console receipt has invalid attested_at") from exc - today = now.astimezone(UTC).strftime("%Y%m%d") - if match.group(1) != today or attested.strftime("%Y%m%d") != today or attested > now: - raise PreflightError("provider-console receipt is not from the window day") - if receipt.get("host") != "railiance01" or receipt.get("panel_login") != "ok": - raise PreflightError("provider-console receipt does not prove panel access to railiance01") - if receipt.get("console_shows_host") is not True or receipt.get("independent_of_guest_network") is not True: - raise PreflightError("provider-console receipt does not prove an independent host console") - if not window_id or str(receipt.get("reboot_window_id")) != window_id: - raise PreflightError("provider-console receipt does not name the approved window id") - return { - "receipt_id": receipt["receipt_id"], - "attested_at": attested.isoformat().replace("+00:00", "Z"), - "attested_by_role": receipt.get("attested_by_role"), - "console_shows_host": True, - "independent_of_guest_network": True, - "window_id_matches": True, - "verified": True, - } - - -def fresh_decision_gates(scenario_id: str | None, decision_id: str | None) -> dict[str, bool]: - """Consumed drill scenario/decisions can never authorize this window.""" - return { - "new_scenario_id_recorded": bool(scenario_id) - and bool(SAFE_ID.fullmatch(scenario_id)) - and scenario_id not in CONSUMED_SCENARIO_IDS, - "fresh_human_decision_recorded": bool(decision_id) - and bool(SAFE_ID.fullmatch(decision_id)) - and not decision_id.lower().startswith(CONSUMED_DECISION_PREFIXES), - } - - -def reboot_comparison(pre: dict[str, Any], post: dict[str, Any], boots: int) -> dict[str, Any]: - """Post-reboot proof: exactly one reboot, same database identities, no new failures.""" - pre_host, post_host = pre.get("host", {}), post["host"] - identity_changes = [] - for key, before in pre.get("cnpg_clusters", {}).items(): - after = post["cnpg_clusters"].get(key) - for field in ("uid", "primary_pvc_uid", "system_id"): - if after is None or after.get(field) != before.get(field): - identity_changes.append(f"{key}:{field}") - pod_diff = failing_pod_diff(pre.get("failing_pods", []), post["failing_pods"]) - return { - "boot_id_changed": bool(pre_host.get("boot_id")) - and pre_host.get("boot_id") != post_host["boot_id"], - "boots_since_pre_capture": boots, - "exactly_one_reboot": boots == 1 and pre_host.get("boot_id") != post_host["boot_id"], - "database_identity_changes": sorted(identity_changes), - "failing_pod_diff": pod_diff, - "passed": boots == 1 - and pre_host.get("boot_id") != post_host["boot_id"] - and not identity_changes - and pod_diff["no_new_failures"], - } - - -def tunnel_answers(name: str) -> bool: - try: - completed = subprocess.run( - ["bridge", "check", name], text=True, capture_output=True, check=False, timeout=30 - ) - except (OSError, subprocess.TimeoutExpired): - return False - rows = [line.split() for line in completed.stdout.splitlines() if line.startswith(name)] - return completed.returncode == 0 and bool(rows) and rows[0][-1] == "yes" - - -def reboot_state(remote: Remote, now: datetime) -> dict[str, Any]: - workloads = remote.kubectl_json( - ["get", "deployments,statefulsets", "-A"], label="read core workload state" - ) - pods = remote.kubectl_json(["get", "pods", "-A"], label="read all-namespace pod phases") - clusters = remote.kubectl_json( - ["get", "clusters.postgresql.cnpg.io", "-A"], label="discover CNPG clusters" - ) - pvcs = remote.kubectl_json(["get", "pvc", "-A"], label="read PVC identities") - pvc_uids = { - f"{item['metadata']['namespace']}/{item['metadata']['name']}": item["metadata"]["uid"] - for item in pvcs.get("items", []) - } - terminating_pvcs = sorted( - f"{item['metadata']['namespace']}/{item['metadata']['name']}" - for item in pvcs.get("items", []) - if item["metadata"].get("deletionTimestamp") - ) - cnpg: dict[str, Any] = {} - for cluster in clusters.get("items", []): - ns, name = cluster["metadata"]["namespace"], cluster["metadata"]["name"] - ns_pvcs = { - key.split("/", 1)[1]: uid for key, uid in pvc_uids.items() if key.startswith(f"{ns}/") - } - identity = cnpg_identity(cluster, ns_pvcs) - if identity["current_primary"]: - controldata = remote.kubectl( - [ - "-n", ns, "exec", identity["current_primary"], "-c", "postgres", "--", - "pg_controldata", "/var/lib/postgresql/data/pgdata", - ], - label=f"read {ns}/{name} system identifier", - ) - identity["system_id"] = system_identifier(controldata) - cnpg[f"{ns}/{name}"] = identity - backups = remote.kubectl_json(["get", "backups.postgresql.cnpg.io", "-A"], label="read CNPG backup metadata") - stores = remote.kubectl_json(["get", "clustersecretstores"], label="read ESO store conditions") - projections = remote.kubectl_json(["get", "externalsecrets", "-A"], label="read ESO projection conditions") - ufw = remote.run(["sudo", "-n", "ufw", "status"], label="read host firewall state") - ntp = remote.run( - ["timedatectl", "show", "-p", "NTPSynchronized", "--value"], label="read time sync state" - ) - return { - "host": { - "boot_id": remote.run(["cat", "/proc/sys/kernel/random/boot_id"], label="read boot id"), - "ntp_synchronized": ntp.strip() == "yes", - "firewall": ufw_state(ufw), - }, - "k3s_api_tunnel_answers": tunnel_answers(K3S_API_TUNNEL), - "core_platform_workloads": core_platform_workloads(workloads), - "failing_pods": failing_pods(pods), - "terminating_pvcs": terminating_pvcs, - "cnpg_clusters": cnpg, - "missing_expected_cnpg_clusters": sorted(EXPECTED_CNPG_CLUSTERS - set(cnpg)), - "cnpg_backups": latest_backups_by_cluster(backups, now), - "eso": eso_inventory(stores, projections), - } - - -def reboot_automated_pass(extra: dict[str, Any], max_backup_age_hours: float) -> dict[str, bool]: - backups = extra["cnpg_backups"] - return { - "ntp_synchronized": extra["host"]["ntp_synchronized"], - "firewall_matches_declaration": extra["host"]["firewall"]["matches_declaration"], - "k3s_api_tunnel_answers": extra["k3s_api_tunnel_answers"], - "core_platform_workloads_ready": extra["core_platform_workloads"]["ready"], - "all_cnpg_clusters_healthy": not extra["missing_expected_cnpg_clusters"] - and all(cnpg_healthy(item) for item in extra["cnpg_clusters"].values()), - "required_backups_fresh": all( - backups.get(key) is not None and backups[key]["age_hours"] <= max_backup_age_hours - for key in FRESH_BACKUP_REQUIRED - ), - "all_eso_stores_valid_and_synced": extra["eso"]["ready"], - } - - def reboot_preflight(remote: Remote, args: argparse.Namespace) -> dict[str, Any]: now = datetime.now(UTC) state = common_state(remote, now) - extra = reboot_state(remote, now) - latest = extra["cnpg_backups"].get("databases/platform-pg") - if latest is None: - raise PreflightError("no completed platform-pg backup is visible") + backups = remote.kubectl_json( + ["-n", "databases", "get", "backup", "-l", "cnpg.io/cluster=platform-pg"], + label="read platform-pg backup metadata", + ) + latest = latest_completed_backup(backups, now) snapshot_evidence = Path(args.openbao_snapshot_evidence).resolve() if args.openbao_snapshot_evidence else None snapshot_receipt = None if snapshot_evidence: @@ -872,82 +371,25 @@ def reboot_preflight(remote: Remote, args: argparse.Namespace) -> dict[str, Any] now=now, max_age_hours=args.max_snapshot_age_hours, ) - # Gate failures stay value-safe gate results; they never abort evidence capture. - receipt_errors: dict[str, str] = {} - warden_receipt = None - try: - warden_receipt = validate_ops_warden_receipt(Path(args.ops_warden_acceptance_receipt)) - warden_receipt.update( - verify_installed_warden( - installed_warden_package(Path(args.warden_tool_python)), - OPS_WARDEN_REPO, - warden_receipt["accepted_revision"], - ) - ) - except PreflightError as exc: - receipt_errors["ops_warden_acceptance_receipt"] = str(exc) - console_receipt = None - if args.provider_console_receipt: - try: - console_receipt = validate_console_receipt( - Path(args.provider_console_receipt), window_id=args.approved_window_id, now=now - ) - except PreflightError as exc: - receipt_errors["provider_console_receipt"] = str(exc) - else: - receipt_errors["provider_console_receipt"] = "not supplied (RAIL-HO-WP-0011-T09 pending)" - checks = reboot_automated_pass(extra, args.max_backup_age_hours) - comparison = None - if args.pre_reboot_evidence: - try: - pre = json.loads(Path(args.pre_reboot_evidence).read_text(encoding="utf-8"))["reboot_evidence"] - except (OSError, json.JSONDecodeError, KeyError, TypeError) as exc: - raise PreflightError("pre-reboot evidence is unavailable or has no reboot_evidence") from exc - boot_list = remote.run( - ["sudo", "-n", "journalctl", "--list-boots", "-o", "json", "--no-pager"], - label="read boot list", - ) - comparison = reboot_comparison(pre, extra, boots_since(boot_list, rfc3339(pre["captured_at"]))) - checks["post_reboot_comparison_passed"] = comparison["passed"] - else: - # Pre-reboot: stuck Terminating PVCs must be resolved or explicitly listed. - listed = set(args.accept_terminating_pvc or []) - checks["terminating_pvcs_resolved_or_listed"] = set(extra["terminating_pvcs"]) <= listed - decision = fresh_decision_gates(args.scenario_id, args.human_decision_id) gates = { "approved_window_id_recorded": bool(args.approved_window_id), - **decision, - "ops_warden_revision_accepted_for_attended_login": bool(warden_receipt), "openbao_snapshot_evidence_present": bool(snapshot_receipt), "unseal_quorum_attested": args.unseal_quorum_attested, - "provider_console_receipt_verified": bool(console_receipt), + "provider_console_access_attested": args.provider_console_attested, "host_cluster_platform_database_audit_owners_acknowledged": args.all_owners_ack, - "railiance_infra_independent_abort_owner_acknowledged": args.infra_abort_owner_ack, "attended_abort_operator_named": bool(args.abort_operator), } - automated = ( - automated_common_pass(state) - and latest["age_hours"] <= args.max_backup_age_hours - and all(checks.values()) - ) + automated = automated_common_pass(state) and latest["age_hours"] <= args.max_backup_age_hours return { "procedure": "railiance01-coordinated-reboot", - "phase": "post-reboot" if comparison else "pre-reboot", "preflight_only": True, "automated_checks_passed": automated, - "automated_checks": checks, - "ready_for_live_execution": automated and all(gates.values()) and not comparison, + "ready_for_live_execution": automated and all(gates.values()), "operator_gates": gates, "latest_platform_pg_backup": latest, "max_backup_age_hours": args.max_backup_age_hours, - "fresh_backup_recommended_for": sorted(FRESH_BACKUP_RECOMMENDED), "openbao_snapshot_receipt": snapshot_receipt, - "ops_warden_acceptance_receipt": warden_receipt, - "provider_console_receipt": console_receipt, - "receipt_gate_failures": receipt_errors, "max_snapshot_age_hours": args.max_snapshot_age_hours, - "reboot_evidence": {"captured_at": state["captured_at"], **extra}, - "reboot_comparison": comparison, "state": state, "secret_values_observed": False, } @@ -964,27 +406,7 @@ def main() -> int: parser.add_argument("--synthetic-load-id") parser.add_argument("--openbao-snapshot-evidence") parser.add_argument("--unseal-quorum-attested", action="store_true") - parser.add_argument( - "--provider-console-receipt", - help="railiance-infra RAIL01-CONSOLE-.yaml receipt for this window", - ) - parser.add_argument("--infra-abort-owner-ack", action="store_true") - parser.add_argument("--scenario-id", help="new scenario id; consumed drill ids are denied") - parser.add_argument("--human-decision-id", help="fresh human GO decision id") - parser.add_argument("--ops-warden-acceptance-receipt", default=OPS_WARDEN_RECEIPT) - parser.add_argument( - "--warden-tool-python", - default=str(Path.home() / ".local/share/uv/tools/ops-warden/bin/python"), - help="Python of the uv tool environment that provides `warden` on PATH", - ) - parser.add_argument( - "--accept-terminating-pvc", action="append", - help="namespace/name of a pre-existing Terminating PVC accepted for the window", - ) - parser.add_argument( - "--pre-reboot-evidence", - help="pre-reboot preflight JSON; switches node-reboot to post-reboot comparison", - ) + parser.add_argument("--provider-console-attested", action="store_true") parser.add_argument("--all-owners-ack", action="store_true") parser.add_argument("--max-backup-age-hours", type=float, default=26.0) parser.add_argument("--max-snapshot-age-hours", type=float, default=24.0) diff --git a/scripts/audit_core_synthetic_sender_lane.py b/scripts/audit_core_synthetic_sender_lane.py deleted file mode 100755 index 56182b9..0000000 --- a/scripts/audit_core_synthetic_sender_lane.py +++ /dev/null @@ -1,487 +0,0 @@ -#!/usr/bin/env python3 -"""Attended, value-safe synthetic sender lane for audit-core recovery exercises. - -One temporary audit-core sender identity is added to the registry at -``platform/workloads/audit-core/senders`` for exactly one approved window and -removed again afterwards. The identity is parameterized by the approved window -receipt (``synthetic_sender`` block), not by constants in this file, so the -dated Whitehat ``-01``/``-02`` procedures are never copied. - -Ordering contract (RAILIANCE-WP-0024-T02): - - project -> audit-core restart -> T02 exercise -> evidence -> cleanup -> restart - -* ``preflight`` is read-only. It never reads the sender registry values unless - ``--inspect-registry`` is given (attended; values stay in memory only). -* ``project`` is time-gated to the opening minutes of the approved window and - writes the bearer only to a new mode-0600 file outside any Git work tree. -* ``cleanup`` is idempotent and refuses to run until the T02 exercise evidence - is recorded, unless an explicit abort reason is given. - -No command prints a bearer, the registry, OpenBao bodies, or Kubernetes Secret -data. -""" - -from __future__ import annotations - -import argparse -import importlib.util -import json -import os -import re -import secrets -import stat -import sys -import time -from datetime import UTC, datetime, timedelta -from pathlib import Path -from typing import Any - - -ROOT = Path(__file__).resolve().parents[1] -LANE = "audit-core-synthetic-sender" -REGISTRY_PATH = "platform/workloads/audit-core/senders" -DRIVER_INTERFACE = ROOT / "interfaces" / "RAILIANCE-WP-0024-T02-synthetic-load-driver.json" -PROJECT_CUTOFF = timedelta(minutes=3) -MIN_REMAINING = timedelta(minutes=10) -NAME_RE = re.compile(r"^[a-z][a-z0-9-]{2,62}$") -TENANT_RE = re.compile(r"^tenant:trial:[a-z0-9][a-z0-9-]{1,62}$") -RFC3339_Z_RE = re.compile(r"^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$") - - -class LaneError(RuntimeError): - pass - - -def _load(name: str, filename: str) -> Any: - spec = importlib.util.spec_from_file_location(name, ROOT / "scripts" / filename) - if not spec or not spec.loader: - raise LaneError(f"cannot load {filename}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -# Reuse, do not copy: the live-proven registry/reload helpers from the -02 lane -# (Operator, safe_run, secure_unlink, force_sender_sync_and_restart) and the -# reviewed T02 receipt/attest-chain/preflight gates from the lease harness. -CREDS = _load("audit_core_whitehat_e2_credentials_02", "audit-core-whitehat-e2-credentials-02.py") -LEASE = _load("audit_core_database_lease_recovery", "audit-core-database-lease-recovery.py") -PREFLIGHT = LEASE.PREFLIGHT -ERRORS = (OSError, IndexError, KeyError, LaneError, CREDS.ProcedureError, LEASE.ProcedureError, PREFLIGHT.PreflightError) - - -def utc_now() -> datetime: - return datetime.now(UTC) - - -def stamp(value: datetime) -> str: - return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def confirm_token(approval: dict[str, Any]) -> str: - return f"{LANE}:{approval['approval_id']}:attended" - - -def driver_contract_id() -> str: - try: - return json.loads(DRIVER_INTERFACE.read_text(encoding="utf-8"))["contract_id"] - except (OSError, json.JSONDecodeError, KeyError) as exc: - raise LaneError("synthetic-load driver interface is unavailable") from exc - - -def sender_spec(approval: dict[str, Any]) -> dict[str, Any]: - """Validate the receipt's single synthetic identity; return it normalized.""" - block = approval.get("synthetic_sender") - if not isinstance(block, dict): - raise LaneError("approval receipt has no synthetic_sender block") - if set(block) != {"identity", "source", "tenant", "expires_at"}: - raise LaneError("synthetic_sender must name exactly identity, source, tenant and expires_at") - name, source, tenant, expires_text = ( - block["identity"], block["source"], block["tenant"], block["expires_at"] - ) - if not isinstance(name, str) or not NAME_RE.match(name) or "synthetic" not in name: - raise LaneError("synthetic_sender.identity must be a lowercase name containing 'synthetic'") - if not isinstance(source, str) or not NAME_RE.match(source) or "synthetic" not in source: - raise LaneError("synthetic_sender.source must be a named synthetic source, never '*'") - if not isinstance(tenant, str) or not TENANT_RE.match(tenant): - raise LaneError("synthetic_sender.tenant must be exactly one tenant:trial:") - if not isinstance(expires_text, str) or not RFC3339_Z_RE.match(expires_text): - raise LaneError("synthetic_sender.expires_at must be RFC3339 UTC (YYYY-MM-DDTHH:MM:SSZ)") - expires = LEASE.parse_time(expires_text) - window_end = LEASE.parse_time(approval["window"]["end"]) - if expires > window_end: - raise LaneError("synthetic_sender.expires_at must not be later than the window end") - load = approval.get("synthetic_load") or {} - if load.get("contract_id") != driver_contract_id(): - raise LaneError("synthetic_load.contract_id does not match the registered driver interface") - return {"name": name, "source": source, "tenant": tenant, "expires_at": expires_text, "expires": expires} - - -def load_receipt(path: Path, now: datetime, *, require_open_window: bool) -> tuple[dict[str, Any], dict[str, Any]]: - # The same lane serves the T02 exercise and the T03 post-reboot synthetic E2. - try: - approval = LEASE.load_approval(path, now, require_open_window=require_open_window) - except LEASE.ProcedureError as exc: - if "wrong procedure or task" not in str(exc): - raise - approval = LEASE.load_approval( - path, now, require_open_window=require_open_window, task_id=LEASE.E2_TASK_ID - ) - return approval, sender_spec(approval) - - -def assert_projection_time(approval: dict[str, Any], spec: dict[str, Any], now: datetime) -> None: - start = LEASE.parse_time(approval["window"]["start"]) - if not start <= now <= start + PROJECT_CUTOFF: - raise LaneError( - f"projection permitted only {stamp(start)} through {stamp(start + PROJECT_CUTOFF)}; now {stamp(now)}" - ) - if spec["expires"] - now < MIN_REMAINING: - raise LaneError("identity would expire before a full T02 exercise could complete") - - -def registry_entry(spec: dict[str, Any], token: str) -> dict[str, Any]: - return { - "name": spec["name"], - "tokens": [token], - "sources": [spec["source"]], - "tenants": [spec["tenant"]], - "may_write": True, - "may_read": True, - "expires_at": spec["expires_at"], - } - - -def registry_conflicts(registry: list[Any], spec: dict[str, Any]) -> list[str]: - """Name, source, or tenant overlap with any existing identity is a conflict.""" - conflicts: list[str] = [] - for entry in registry: - if not isinstance(entry, dict): - continue - label = str(entry.get("name")) - if entry.get("name") == spec["name"]: - conflicts.append(f"identity {label} already exists") - if spec["source"] in (entry.get("sources") or []): - conflicts.append(f"source is already granted to {label}") - if spec["tenant"] in (entry.get("tenants") or []): - conflicts.append(f"tenant is already granted to {label}") - return conflicts - - -def add_identity(registry: list[Any], spec: dict[str, Any], token: str) -> list[Any]: - conflicts = registry_conflicts(registry, spec) - if conflicts: - raise LaneError("registry conflict: " + "; ".join(conflicts)) - return [*registry, registry_entry(spec, token)] - - -def remove_identity(registry: list[Any], spec: dict[str, Any]) -> tuple[list[Any], int]: - """Remove only the exact named identity, and only if it is ours.""" - updated: list[Any] = [] - removed = 0 - for entry in registry: - if isinstance(entry, dict) and entry.get("name") == spec["name"]: - if entry.get("sources") != [spec["source"]] or entry.get("tenants") != [spec["tenant"]]: - raise LaneError("named identity exists with a different scope; refusing to remove it") - removed += 1 - continue - updated.append(entry) - return updated, removed - - -def _inside_git_tree(path: Path) -> bool: - return any((parent / ".git").exists() for parent in path.parents) - - -def check_bearer_path(path: Path, *, must_exist: bool) -> None: - if not path.is_absolute(): - raise LaneError("bearer file must be an absolute path") - parent = path.parent.resolve() - target = parent / path.name - if target == ROOT or ROOT in target.parents or _inside_git_tree(target): - raise LaneError("bearer file must be outside every Git work tree") - info = parent.stat() - if info.st_uid != os.getuid() or stat.S_IMODE(info.st_mode) & 0o077: - raise LaneError("bearer file directory must be owned by the operator with mode 0700") - if must_exist: - details = target.lstat() - if not stat.S_ISREG(details.st_mode) or stat.S_IMODE(details.st_mode) != 0o600: - raise LaneError("bearer file must be a regular mode-0600 file") - elif target.exists() or target.is_symlink(): - raise LaneError("bearer file already exists; run cleanup first") - - -def write_bearer(path: Path, token: str) -> None: - check_bearer_path(path, must_exist=False) - fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "w", encoding="utf-8") as handle: - handle.write(token) - handle.flush() - os.fsync(handle.fileno()) - os.chmod(path, 0o600) - - -def assert_evidence(path: Path | None, abort_reason: str | None, approval: dict[str, Any]) -> dict[str, Any]: - """Cleanup restarts audit-core, so it must not precede the recorded evidence.""" - if abort_reason: - return {"mode": "abort", "reason": abort_reason.strip()[:200]} - if path is None: - raise LaneError("cleanup requires --exercise-evidence (recorded T02 result) or --abort-reason") - try: - evidence = json.loads(path.read_text(encoding="utf-8")) - except (OSError, json.JSONDecodeError) as exc: - raise LaneError("exercise evidence is unavailable or invalid JSON") from exc - if ( - not isinstance(evidence, dict) - or evidence.get("procedure") != LEASE.PROCEDURE - or evidence.get("approval_id") != approval["approval_id"] - or evidence.get("secret_values_observed") is not False - or not evidence.get("completed_at") - ): - raise LaneError("exercise evidence does not belong to this approved T02 window") - return {"mode": "evidence-recorded", "completed_at": evidence["completed_at"]} - - -def cluster_state(remote: Any) -> dict[str, Any]: - state = PREFLIGHT.common_state(remote, utc_now()) - return { - "automated_checks_passed": PREFLIGHT.automated_common_pass(state), - "openbao_unsealed": state["openbao"]["initialized"] and not state["openbao"]["sealed"], - "senders_external_secret_ready": state["external_secrets"]["audit-core-senders"]["ready"], - "image_matches_reviewed_digest": state["audit_core"]["image_matches_reviewed_digest"], - "ready": f"{state['audit_core']['ready_replicas']}/{state['audit_core']['replicas']}", - "pod_uid": state["audit_core"]["pod_uid"], - } - - -def settled_state(remote: Any, *, previous_uid: str | None, timeout: float = 90) -> dict[str, Any]: - """Wait until exactly one Ready serving pod remains (old pod terminated).""" - deadline = time.monotonic() + timeout - while True: - try: - state = cluster_state(remote) - if state["ready"] == "1/1" and state["pod_uid"] != previous_uid: - return state - except PREFLIGHT.PreflightError: - pass - if time.monotonic() >= deadline: - raise LaneError("audit-core did not settle on one Ready restarted pod") - time.sleep(3) - - -class ContainedOperator(CREDS.Operator): - """The -02 registry/kubectl helpers, authenticated only by the contained login. - - No token file and no token variable: ``bao`` runs with the tunnel address - and Warden's isolated token helper (see ``LEASE.contained_bao_env``). - """ - - def __init__(self, remote: str) -> None: # noqa: D107 - deliberately no super().__init__ - self.remote = remote - self.bao_env = LEASE.contained_bao_env() - - -def contained_operator(args: argparse.Namespace) -> Any: - if not args.contained: - raise LaneError(f"{args.command} requires --contained (scripts/openbao-attended-exec.py)") - return ContainedOperator(args.remote) - - -def registry_metadata(operator: Any) -> dict[str, Any]: - raw = operator.bao(["kv", "metadata", "get", "-format=json", REGISTRY_PATH], label="read sender registry metadata").stdout - try: - data = json.loads(raw)["data"] - return {"current_version": data["current_version"], "updated_time": data["updated_time"]} - except (json.JSONDecodeError, KeyError, TypeError) as exc: - raise LaneError("sender registry metadata is incomplete") from exc - - -def time_state(approval: dict[str, Any], now: datetime) -> str: - start = LEASE.parse_time(approval["window"]["start"]) - end = LEASE.parse_time(approval["window"]["end"]) - if now < start: - return "before-window" - if now <= start + PROJECT_CUTOFF: - return "projection-window-open" - return "projection-cutoff-passed" if now <= end else "window-closed" - - -def identity_view(spec: dict[str, Any]) -> dict[str, Any]: - return {key: spec[key] for key in ("name", "source", "tenant", "expires_at")} - - -def preflight(args: argparse.Namespace) -> dict[str, Any]: - now = utc_now() - approval, spec = load_receipt(args.approval, now, require_open_window=False) - result: dict[str, Any] = { - "lane": LANE, - "approval_id": approval["approval_id"], - "time_state": time_state(approval, now), - "identity": identity_view(spec), - "confirm": confirm_token(approval), - "registry_inspected": False, - "secret_values_observed": False, - } - if args.bearer_file is not None: - check_bearer_path(args.bearer_file, must_exist=False) - result["bearer_file_path_ok"] = True - if args.offline: - result["live_checks"] = "skipped" - return result - if args.inspect_registry and not args.contained: - raise LaneError("--inspect-registry requires --contained") - remote = PREFLIGHT.Remote(args.remote) - result["audit_core"] = cluster_state(remote) - result["automated_checks_passed"] = result["audit_core"]["automated_checks_passed"] - if not args.contained: - # Outside the attended login there is no OpenBao credential at all. - result["sender_registry_metadata"] = "skipped-not-contained" - return result - operator = contained_operator(args) - result["sender_registry_metadata"] = registry_metadata(operator) - if args.inspect_registry: - # Attended only: values are held in memory and never emitted. - registry = operator.registry() - result["registry_inspected"] = True - result["identity_present"] = any( - isinstance(entry, dict) and entry.get("name") == spec["name"] for entry in registry - ) - result["registry_conflicts"] = registry_conflicts(registry, spec) - registry.clear() - return result - - -def _cleanup_registry(operator: Any, spec: dict[str, Any]) -> int: - updated, removed = remove_identity(operator.registry(), spec) - if removed: - operator.write_registry(updated) - CREDS.force_sender_sync_and_restart(operator) - updated.clear() - return removed - - -def project(args: argparse.Namespace) -> dict[str, Any]: - now = utc_now() - approval, spec = load_receipt(args.approval, now, require_open_window=True) - if args.confirm != confirm_token(approval): - raise LaneError(f"project requires --confirm {confirm_token(approval)}") - operator = contained_operator(args) - assert_projection_time(approval, spec, now) - if args.bearer_file is None: - raise LaneError("project requires --bearer-file outside Git") - check_bearer_path(args.bearer_file, must_exist=False) - remote = PREFLIGHT.Remote(args.remote) - before = cluster_state(remote) - if not before["automated_checks_passed"]: - raise LaneError("audit-core recovery preflight is not green; refusing to project") - LEASE.attest_chain_state(remote) - registry = operator.registry() - conflicts = registry_conflicts(registry, spec) - if conflicts: - raise LaneError("registry conflict: " + "; ".join(conflicts)) - token = secrets.token_urlsafe(48) - bearer_written = registry_written = False - try: - write_bearer(args.bearer_file, token) - bearer_written = True - operator.write_registry(add_identity(registry, spec, token)) - registry_written = True - CREDS.force_sender_sync_and_restart(operator) - after = settled_state(remote, previous_uid=before["pod_uid"]) - except BaseException: - if registry_written: - _cleanup_registry(operator, spec) - if bearer_written: - CREDS.secure_unlink(args.bearer_file) - raise - finally: - token = "" - registry.clear() - return { - "lane": LANE, - "approval_id": approval["approval_id"], - "projected_at": stamp(utc_now()), - "identity": identity_view(spec), - "bearer_file": str(args.bearer_file), - "bearer_file_mode": "0600", - "audit_core_restarted": True, - "audit_core_pod_uid_after_projection": after["pod_uid"], - "next_step": "run the T02 exercise; its baseline must capture this pod UID", - "secret_values_observed": False, - } - - -def cleanup(args: argparse.Namespace) -> dict[str, Any]: - approval, spec = load_receipt(args.approval, utc_now(), require_open_window=False) - if args.confirm != confirm_token(approval): - raise LaneError(f"cleanup requires --confirm {confirm_token(approval)}") - operator = contained_operator(args) - gate = assert_evidence(args.exercise_evidence, args.abort_reason, approval) - removed = _cleanup_registry(operator, spec) - remaining = any( - isinstance(entry, dict) and entry.get("name") == spec["name"] for entry in operator.registry() - ) - if remaining: - raise LaneError("temporary identity remains after cleanup") - bearer_removed = False - if args.bearer_file is not None and (args.bearer_file.exists() or args.bearer_file.is_symlink()): - check_bearer_path(args.bearer_file, must_exist=True) - CREDS.secure_unlink(args.bearer_file) - bearer_removed = True - after = settled_state(PREFLIGHT.Remote(args.remote), previous_uid=None) - return { - "lane": LANE, - "approval_id": approval["approval_id"], - "cleanup_gate": gate, - "cleanup_completed_at": stamp(utc_now()), - "identity": spec["name"], - "identities_removed": removed, - "audit_core_restarted": bool(removed), - "bearer_file_removed": bearer_removed, - "audit_core_ready": after["ready"], - "secret_values_observed": False, - } - - - -def emit_result(result: dict, result_file: "Path | None") -> None: - """Under the contained login the child must stay silent: write, never print.""" - if result_file is None: - print(json.dumps(result, indent=2, sort_keys=True)) - return - fd = os.open(result_file, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "w", encoding="utf-8") as handle: - json.dump(result, handle, indent=2, sort_keys=True) - handle.write("\n") - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - parser.add_argument("command", choices=["preflight", "project", "cleanup"]) - parser.add_argument("--approval", type=Path, required=True, help="approved T02 window receipt") - parser.add_argument("--bearer-file", type=Path, help="absolute path outside Git (mode-0700 directory)") - parser.add_argument("--remote", default="railiance01") - parser.add_argument("--contained", action="store_true", - help="run inside the contained attended OpenBao login (required for project/cleanup)") - parser.add_argument("--confirm") - parser.add_argument("--offline", action="store_true", help="preflight: validate receipt/path only") - parser.add_argument("--inspect-registry", action="store_true", help="preflight: attended registry conflict check") - parser.add_argument("--exercise-evidence", type=Path, help="cleanup: recorded T02 exercise result JSON") - parser.add_argument("--abort-reason", help="cleanup: abort path when no exercise evidence exists") - parser.add_argument("--result-file", type=Path, help="write the JSON result here (0600, new) and print nothing") - args = parser.parse_args(argv) - try: - handler = {"preflight": preflight, "project": project, "cleanup": cleanup}[args.command] - result = handler(args) - except ERRORS as exc: - if args.result_file is None: - print(f"synthetic sender lane failed: {exc}", file=sys.stderr) - else: - emit_result({"status": "failed", "error": str(exc), "secret_values_observed": False}, args.result_file) - return 1 - emit_result(result, args.result_file) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/bootstrap_coulomb_recovery_session.mjs b/scripts/bootstrap_coulomb_recovery_session.mjs deleted file mode 100644 index 48eb6cc..0000000 --- a/scripts/bootstrap_coulomb_recovery_session.mjs +++ /dev/null @@ -1,100 +0,0 @@ -#!/usr/bin/env node -/** One attended bootstrap; committed helper creates scoped routine API identities. */ -import {createHash} from 'node:crypto'; -import {execFileSync,spawn} from 'node:child_process'; -import {readFileSync,mkdirSync,lstatSync} from 'node:fs'; -import {homedir} from 'node:os'; -import {dirname,join,resolve} from 'node:path'; -import {fileURLToPath,pathToFileURL} from 'node:url'; -import {parseArgs} from 'node:util'; -import {createInterface} from 'node:readline/promises'; - -const {values}=parseArgs({options:{'playwright-module':{type:'string'},'preflight-only':{type:'boolean'}}}); -const root=resolve(dirname(fileURLToPath(import.meta.url)),'..'); -const factory=join(root,'scripts','application_recovery_identities.py'); -const directory=join(homedir(),'.config','railiance-platform','coulomb-social-recovery'); -const digest=data=>createHash('sha256').update(data).digest('hex'); -let browser,reader,child; - -function sourcePreflight(){ - const committed=execFileSync('git',['show','HEAD:scripts/application_recovery_identities.py'],{cwd:root}); - if(digest(committed)!==digest(readFileSync(factory)))throw new Error('committed_factory_required'); - mkdirSync(directory,{recursive:true,mode:0o700}); - const info=lstatSync(directory); - if(!info.isDirectory()||info.uid!==process.getuid()||(info.mode&0o077))throw new Error('private_directory_required'); - const validation="import importlib.util,sys;from pathlib import Path;"+ - "s=importlib.util.spec_from_file_location('recovery_bootstrap',sys.argv[1]);"+ - "m=importlib.util.module_from_spec(s);s.loader.exec_module(m);m.private_directory(Path(sys.argv[2]))"; - execFileSync('python3',['-c',validation,factory,directory],{stdio:['ignore','pipe','pipe']}); - return digest(committed); -} - -async function runFactory(page,retire=false){ - const sourceHash=sourcePreflight(); - if(new URL(page.url()).origin!=='https://console.scaleway.com')throw new Error('console_page_required'); - const auth=await page.evaluate(()=>{ - const names=Object.keys(localStorage).filter(n=>n.startsWith('_scw_session_console_scaleway_com_')); - if(names.length!==1)throw new Error('unambiguous_session_required'); - const session=JSON.parse(localStorage.getItem(names[0])); - if(new Date(session.jwt.expiresAt).getTime(){ - child=spawn('python3',[factory,'--execute','--directory',directory, - ...(retire?['--retire-bootstrap']:[])],{stdio:['pipe','pipe','pipe']}); - let output=''; - child.stdout.on('data',data=>{if(output.length<65536)output+=data.toString();}); - child.stderr.on('data',()=>{}); // Provider/credential diagnostics never enter chat. - child.on('error',()=>reject(new Error('bootstrap_child_failed'))); - child.on('close',code=>{ - child=null; - try {resolveResult({exit_code:code,result:JSON.parse(output.trim()),factory_source_sha256:sourceHash});} - catch {reject(new Error('redacted_bootstrap_result_required'));} - }); - child.stdin.end(JSON.stringify(auth)); - }); - return result; -} - -try{ - const sourceHash=sourcePreflight(); - const modulePath=resolve(values['playwright-module']||''); - const pkg=JSON.parse(readFileSync(join(dirname(modulePath),'package.json'),'utf8')); - if(pkg.name!=='playwright'||pkg.version!=='1.62.1')throw new Error('locked_playwright_required'); - if(values['preflight-only']){ - console.log(JSON.stringify({status:'preflight_passed',factory_source_sha256:sourceHash, - private_directory_validated:true,locked_playwright_verified:true,provider_contacted:false})); - process.exit(0); - } - const {chromium}=await import(pathToFileURL(modulePath).href); - browser=await chromium.launch({headless:false}); - const context=await browser.newContext();const page=await context.newPage(); - await page.goto('https://console.scaleway.com/',{waitUntil:'domcontentloaded',timeout:60000}); - reader=createInterface({input:process.stdin,output:process.stdout}); - console.log('One-time bootstrap browser opened. Sign in privately; no personal state is saved.'); - for(;;){ - const answer=await reader.question('After Console login: Enter/run = bootstrap; finish = retire temporary key and close; stop = close. '); - if(answer.trim()==='stop')break; - try{ - const retire=answer.trim()==='finish'; - const outcome=await runFactory(page,retire); - console.log(JSON.stringify(outcome)); - if(outcome.exit_code===0&&retire){ - try{await page.goto('https://account.scaleway.com/logout',{waitUntil:'domcontentloaded',timeout:30000});}catch{} - await context.clearCookies(); - break; - } - if(outcome.exit_code===0){ - console.log('Identity stage complete. Use scoped APIs for resource/boundary proofs, then finish. Keep this window for bootstrap only.'); - continue; - } - }catch{console.error('Bootstrap stopped safely. No credential values printed. Keep this window for review.');} - console.log('No automatic retry. Review the private journal; keep this browser open until the next reviewed attempt.'); - } -}catch{ - console.error('Bootstrap preflight/session failed; no credential values printed.');process.exitCode=1; -}finally{ - if(child)child.kill('SIGTERM'); - if(reader)reader.close(); - if(browser)await browser.close(); -} diff --git a/scripts/broker_grant_lane.py b/scripts/broker_grant_lane.py deleted file mode 100644 index b54ee06..0000000 --- a/scripts/broker_grant_lane.py +++ /dev/null @@ -1,269 +0,0 @@ -"""custody-generate-grant CCRs: a create-only KV grant the credential broker mints. - -RPF-WP-0054-T04. Loaded by scripts/credential-change.py for request_type -custody-generate-grant. The lane writes three reviewed objects and nothing else: - -* the grant policy: ``create`` on ``/+`` data, ``read`` on ``/+`` - metadata, explicit ``deny`` on reserved entries, ``revoke-self``. It has no - ``update`` anywhere (an existing entry can never be overwritten), no data - read, no metadata write (custom_metadata writes would also let the token - change ``delete_version_after``/``max_versions`` of other entries), no list - and no delete; -* the broker issuer policy: create tokens only through this grant's token role; -* the token role: exactly the grant policy, orphan, non-renewable, no default - policy, at most 15 minutes. - -Binding the issuer policy to the calling identity is a separate review once the -caller is known (RPF-WP-0054-T01); until then nothing can mint the token, so -the apply is inert. Nothing here reads, prints or accepts a secret value. -""" -from __future__ import annotations - -import importlib.util -import re -from pathlib import Path -from typing import Any - -import yaml - -REQUEST_TYPE = "custody-generate-grant" -PREFIX_RE = re.compile(r"^platform/workloads/[a-z0-9-]+$") -ENTRY_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") -NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") -CATALOG = "credential-grants/catalog.yaml" -MAX_TTL_SECONDS = 15 * 60 -# Same list scripts/openbao-apply-token-grants.py writes; default is excluded by -# token_no_default_policy. -DISALLOWED_POLICIES = ["root", "platform-admin"] - - -def _service_jwt_lane(): - spec = importlib.util.spec_from_file_location( - "service_jwt_lane", Path(__file__).resolve().parent / "service_jwt_lane.py" - ) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -_shared = _service_jwt_lane() -ttl_seconds = _shared.ttl_seconds -normalized = _shared.normalized -BaoError = _shared.BaoError -_run = _shared._run -_read_json = _shared._read_json - - -def _paths(prefix: str) -> tuple[str, str]: - mount, rest = prefix.split("/", 1) - return f"{mount}/data/{rest}", f"{mount}/metadata/{rest}" - - -def generated_policy_hcl(ccr: dict[str, Any]) -> str: - openbao = ccr["openbao"] - data, meta = _paths(openbao["kv_prefix"]) - lines = [ - f"# {ccr['id']} (RPF-WP-0054): grant {openbao['grant_id']}. Create-only: no update,", - "# data read, metadata write, list or delete. Exact deny beats the + glob.", - f'path "{data}/+" {{', ' capabilities = ["create"]', "}", - f'path "{meta}/+" {{', ' capabilities = ["read"]', "}", - ] - for entry in openbao["reserved_entries"]: - for base in (data, meta): - lines += [f'path "{base}/{entry}" {{', ' capabilities = ["deny"]', "}"] - lines += ['path "auth/token/revoke-self" {', ' capabilities = ["update"]', "}"] - return "\n".join(lines) + "\n" - - -def generated_issuer_hcl(ccr: dict[str, Any]) -> str: - openbao = ccr["openbao"] - lines = [ - f"# {ccr['id']} (RPF-WP-0054): the credential broker may mint {openbao['grant_id']}", - "# tokens only through this token role. Bind to the reviewed caller identity only.", - ] - for path, caps in ( - (f"auth/token/create/{openbao['token_role']}", '"create", "update"'), - ("auth/token/lookup-accessor", '"update"'), - ("auth/token/revoke-accessor", '"update"'), - ("auth/token/lookup-self", '"read"'), - ("auth/token/revoke-self", '"update"'), - ("sys/capabilities-self", '"update"'), - ): - lines += [f'path "{path}" {{', f" capabilities = [{caps}]", "}"] - return "\n".join(lines) + "\n" - - -def role_payload(ccr: dict[str, Any]) -> dict[str, Any]: - openbao = ccr["openbao"] - return { - "allowed_policies": [openbao["policy_name"]], - "disallowed_policies": DISALLOWED_POLICIES, - "orphan": True, - "renewable": False, - "token_explicit_max_ttl": openbao["token_ttl"], - "token_no_default_policy": True, - "token_type": "service", - } - - -def _catalog_grant(repo_dir: Path, grant_id: str) -> dict[str, Any] | None: - catalog = yaml.safe_load((repo_dir / CATALOG).read_text(encoding="utf-8")) or {} - for grant in catalog.get("grants") or []: - if grant.get("id") == grant_id: - return grant - return None - - -def _check_file(repo_dir: Path, rel: str, expected: str, field: str, errors: list[str]) -> None: - path = repo_dir / rel - if not path.exists(): - errors.append(f"{field} does not exist: {rel}") - elif normalized(path.read_text(encoding="utf-8")) != normalized(expected): - errors.append(f"{field} does not match the generated policy") - - -def validate(ccr: dict[str, Any], repo_dir: Path) -> list[str]: - errors: list[str] = [] - openbao = ccr.get("openbao") - if not isinstance(openbao, dict): - return ["openbao must be an object"] - if (ccr.get("target") or {}).get("environment") != "production": - errors.append("target.environment must be production") - review = ccr.get("review") or {} - if review.get("required") is not True or not review.get("required_approvers"): - errors.append("review.required must be true with required_approvers") - prefix = str(openbao.get("kv_prefix") or "") - if not PREFIX_RE.match(prefix): - errors.append("openbao.kv_prefix must be platform/workloads/") - reserved = openbao.get("reserved_entries") - if not isinstance(reserved, list) or not all(ENTRY_RE.match(str(e)) for e in reserved): - errors.append("openbao.reserved_entries must list exact entry names") - for key in ("policy_name", "issuer_policy", "token_role"): - if not NAME_RE.match(str(openbao.get(key) or "")): - errors.append(f"openbao.{key} contains unsafe characters") - seconds = ttl_seconds(openbao.get("token_ttl")) - if seconds is None or not 0 < seconds <= MAX_TTL_SECONDS: - errors.append("openbao.token_ttl must be set and at most 15m") - if errors: - return errors - - for key, file_key, expected in ( - ("policy_name", "policy_file", generated_policy_hcl(ccr)), - ("issuer_policy", "issuer_policy_file", generated_issuer_hcl(ccr)), - ): - rel = str(openbao.get(file_key) or "") - if rel != f"openbao/policies/{openbao[key]}.hcl": - errors.append(f"openbao.{file_key} must be openbao/policies/<{key}>.hcl") - else: - _check_file(repo_dir, rel, expected, f"openbao.{file_key}", errors) - - grant = _catalog_grant(repo_dir, str(openbao.get("grant_id") or "")) - if grant is None: - errors.append(f"openbao.grant_id not in {CATALOG}") - else: - gb = grant.get("openbao") or {} - if gb.get("policies") != [openbao["policy_name"]]: - errors.append("catalog grant policies must be exactly [openbao.policy_name]") - if gb.get("token_role") != openbao["token_role"] or gb.get("issuer_policy") != openbao["issuer_policy"]: - errors.append("catalog grant token_role/issuer_policy must match the CCR") - if ttl_seconds((grant.get("ttl") or {}).get("max")) != seconds: - errors.append("catalog grant ttl.max must equal openbao.token_ttl") - return errors - - -def readiness_blockers(ccr: dict[str, Any], repo_dir: Path, allowed_statuses: set[str]) -> list[str]: - blockers: list[str] = [] - if ccr.get("status") not in allowed_statuses: - blockers.append( - "applier requires approved, applied, verified, or active CCR status, " - f"got {ccr.get('status')}" - ) - blockers.extend(validate(ccr, repo_dir)) - return blockers - - -def dry_run_payload(ccr: dict[str, Any], repo_dir: Path, warnings: list[str]) -> dict[str, Any]: - openbao = ccr["openbao"] - return { - "id": ccr["id"], - "title": ccr["title"], - "status": ccr["status"], - "environment": ccr["target"]["environment"], - "warnings": warnings, - "source_artifacts": {}, - "mutations": [ - {"kind": "policy_write", "openbao_path": f"sys/policies/acl/{openbao['policy_name']}", - "policy_name": openbao["policy_name"], "source": openbao["policy_file"], - "body": generated_policy_hcl(ccr).rstrip()}, - {"kind": "policy_write", "openbao_path": f"sys/policies/acl/{openbao['issuer_policy']}", - "policy_name": openbao["issuer_policy"], "source": openbao["issuer_policy_file"], - "body": generated_issuer_hcl(ccr).rstrip()}, - {"kind": "token_role_write", "openbao_path": f"auth/token/roles/{openbao['token_role']}", - "payload": role_payload(ccr)}, - {"kind": "readback_compare", "openbao_path": "both policies, token role", - "payload": {"prints": "booleans only"}}, - ], - "out_of_scope": [ - "secret value reads or writes", - "binding the issuer policy to any identity (separate review after RPF-WP-0054-T01)", - "token creation", - "changes to any other policy or role", - ], - "required_evidence": [ - "CCR id and approval reference", - "readback equality for both policies and the token role", - "disposable probe (RPF-WP-0054-T05): create once, second create and data read denied", - ], - } - - -def apply_details(ccr: dict[str, Any], actor: str) -> list[str]: - openbao = ccr["openbao"] - return [ - f"Delegated metadata applier ran as {actor} using local bao CLI ambient authority.", - f"Policy metadata write: sys/policies/acl/{openbao['policy_name']}", - f"Issuer policy metadata write: sys/policies/acl/{openbao['issuer_policy']}", - f"Token role metadata write: auth/token/roles/{openbao['token_role']}", - "Readback matched source for both policies and the token role.", - "Issuer policy is not bound to any identity by this apply.", - "No secret values were read, written, printed, or accepted in argv.", - ] - - -def readback_mismatches(ccr: dict[str, Any], repo_dir: Path, bao_bin: str) -> list[str]: - openbao = ccr["openbao"] - mismatches: list[str] = [] - for name, expected in ( - (openbao["policy_name"], generated_policy_hcl(ccr)), - (openbao["issuer_policy"], generated_issuer_hcl(ccr)), - ): - text = _run(bao_bin, ["policy", "read", name], repo_dir, quiet=True, capture=True) - if normalized(text) != normalized(expected): - mismatches.append(f"policy {name} differs from source") - role = _read_json(bao_bin, ["read", f"auth/token/roles/{openbao['token_role']}"], repo_dir).get("data", {}) - for key, want in role_payload(ccr).items(): - got = role.get(key) - if key == "token_explicit_max_ttl": - same = ttl_seconds(got) == ttl_seconds(want) - elif key == "disallowed_policies": - same = sorted(got or []) == sorted(want) - else: - same = got == want - if not same: - mismatches.append(f"token role {key} differs") - return mismatches - - -def run_apply(ccr: dict[str, Any], repo_dir: Path, bao_bin: str, quiet: bool = False) -> None: - openbao = ccr["openbao"] - _run(bao_bin, ["policy", "write", openbao["policy_name"], openbao["policy_file"]], repo_dir, quiet) - _run(bao_bin, ["policy", "write", openbao["issuer_policy"], openbao["issuer_policy_file"]], repo_dir, quiet) - role = role_payload(ccr) - args = ["write", f"auth/token/roles/{openbao['token_role']}"] - for key, value in role.items(): - rendered = ",".join(value) if isinstance(value, list) else str(value).lower() if isinstance(value, bool) else value - args.append(f"{key}={rendered}") - _run(bao_bin, args, repo_dir, quiet) - mismatches = readback_mismatches(ccr, repo_dir, bao_bin) - if mismatches: - raise BaoError("readback mismatch: " + "; ".join(mismatches)) diff --git a/scripts/cadence_observer_readback.py b/scripts/cadence_observer_readback.py deleted file mode 100644 index 1f72060..0000000 --- a/scripts/cadence_observer_readback.py +++ /dev/null @@ -1,333 +0,0 @@ -#!/usr/bin/env python3 -"""Bounded independent operator readback for NK-WP-0035; no credential output.""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import re -import socket -import subprocess -import time -from datetime import datetime, timezone -from pathlib import Path -from urllib.error import HTTPError -from urllib.parse import urlencode -from urllib.request import ProxyHandler, Request, build_opener - -from cadence_receiver_custody import compatible -from factory_audit_custody import require, snapshot -from native_factory_acceptance import NoRedirect, operator_identity -from state_hub_preflight_lane import LaneError, bao, data - -SCOPES = { - "qonto-assistant": "tenant:friendly:binky", - "approval-engine": "tenant:platform", -} - - -def stamp(value): - parsed = datetime.fromisoformat(value.replace("Z", "+00:00")) - require(parsed.tzinfo is not None, "timezone_required") - return parsed.astimezone(timezone.utc) - - -def paths(source, tenant, instance, since, until, event_ids): - require(SCOPES.get(source) == tenant, "exact_source_tenant_required") - if source == "qonto-assistant": - require( - isinstance(instance, str) - and 1 <= len(instance) <= 256 - and instance.strip() == instance, - "exact_instance_required", - ) - else: - require(instance is None, "approval_has_no_declared_instance_filter") - require( - stamp(since) <= stamp(until) <= datetime.now(timezone.utc), - "invalid_acceptance_window", - ) - require( - 1 <= len(event_ids) <= 256 and len(set(event_ids)) == len(event_ids), - "bounded_unique_event_ids_required", - ) - require( - all( - isinstance(x, str) and re.fullmatch(r"[A-Za-z0-9_.:-]{1,200}", x) - for x in event_ids - ), - "invalid_event_id", - ) - query_values = dict(source=source, tenant=tenant, since=since, until=until) - if instance is not None: - query_values["stream_instance_id"] = instance - query = urlencode(query_values) - return ["/v1/reconciliation?" + query, "/v1/stream-findings"] + [ - "/v1/events/" + x for x in event_ids - ] - - -def read_http(origin, token, path, allowed): - require(path in allowed, "read_path_not_in_exact_packet") - request = Request(origin + path, headers={"Authorization": "Bearer " + token}) - try: - response = build_opener(ProxyHandler({}), NoRedirect()).open( - request, timeout=10 - ) - except HTTPError as exc: - response = exc - with response: - raw = response.read(1048577) - require(len(raw) <= 1048576, "readback_too_large") - require(response.status == 200, "independent_readback_failed") - return json.loads(raw) - - -def sanitize_record(record, source, tenant, instance, event_id, since, until): - require( - record.get("event_id") == event_id - and record.get("source") == source - and record.get("tenant") == tenant, - "record_source_tenant_id_mismatch", - ) - detail = record["details"]["data"] - if source == "qonto-assistant": - require( - detail.get("stream_instance_id") == instance, "record_instance_mismatch" - ) - require( - type(detail.get("stream_sequence")) is int - and detail["stream_sequence"] > 0, - "record_sequence_missing", - ) - accepted = record["accepted_at"] - require( - stamp(since) <= stamp(accepted) < stamp(until), - "record_outside_acceptance_window", - ) - action = record["action"] - coverage = ( - [detail["class"]] - if action == "audit-core.heartbeat" and "class" in detail - else [] - ) - if source == "qonto-assistant": - require( - action in {"audit.allow", "audit.deny", "audit-core.heartbeat"}, - "record_undeclared_class", - ) - if action == "audit-core.heartbeat": - require(coverage == ["audit.deny"], "record_heartbeat_scope_mismatch") - if source == "approval-engine": - names = {"issuance", "use", "supersession", "revocation"} - require( - action in {"approval." + name for name in names} | {"audit-core.heartbeat"}, - "record_undeclared_class", - ) - if action == "audit-core.heartbeat": - require( - len(coverage) == 1 and coverage[0] in names, - "record_heartbeat_scope_mismatch", - ) - return { - "event_id": event_id, - "source": source, - "tenant": tenant, - "stream_instance_id": instance, - "stream_sequence": detail.get("stream_sequence") - if instance is not None - else None, - "type": action, - "accepted_at": accepted, - "observed_at": record["observed_at"], - "covered_event_classes": coverage, - "independent_custody_readback": True, - } - - -def readback(origin, token, source, tenant, instance, since, until, event_ids): - allowed = paths(source, tenant, instance, since, until, event_ids) - reconciliation = read_http(origin, token, allowed[0], allowed) - require( - reconciliation.get("source") == source - and reconciliation.get("tenant") == tenant - and reconciliation.get("stream_instance_id") == instance - and stamp(reconciliation["since"]) == stamp(since) - and stamp(reconciliation["until"]) == stamp(until), - "reconciliation_scope_window_mismatch", - ) - raw_findings = read_http(origin, token, allowed[1], allowed) - findings = raw_findings.get("stream_findings", raw_findings.get("findings")) - require(isinstance(findings, list), "unexpected_findings_shape") - safe_findings = [ - { - key: row.get(key) - for key in ( - "kind", - "sender", - "source", - "class", - "expected_within_seconds", - "last_seen", - "overdue_seconds", - ) - } - for row in findings - if row.get("source") == source - ] - records = [ - sanitize_record( - read_http(origin, token, path, allowed), - source, - tenant, - instance, - event_id, - since, - until, - ) - for path, event_id in zip(allowed[2:], event_ids, strict=True) - ] - return { - "reconciliation": reconciliation, - "stream_findings": safe_findings, - "events": records, - "scope": ( - "bounded same-instance accepted custody; source-global findings require active-instance interpretation" - if instance is not None - else "bounded source lifecycle acceptance window; no process-instance completeness claim" - ), - } - - -def main(): - p = argparse.ArgumentParser(description=__doc__) - p.add_argument("--source", choices=SCOPES, required=True) - for key in ( - "tenant", - "since", - "until", - "kubeconfig", - "server", - "expected-receiver-image", - ): - p.add_argument("--" + key, required=True) - p.add_argument( - "--instance", - help="Required only for Qonto; Approval has no declared instance field", - ) - p.add_argument("--event-id", action="append", required=True) - p.add_argument("--receipt", type=Path, required=True) - a = p.parse_args() - receipt = { - "schema_version": "platform.independent-cadence-readback/v1", - "status": "refused", - "credential_values_emitted": False, - "observed_at": datetime.now(timezone.utc).isoformat(), - } - fd = None - forward = None - try: - paths(a.source, a.tenant, a.instance, a.since, a.until, a.event_id) - require( - Path.home().parent.name == ".warden-attended-login" - and not any( - os.getenv(x) for x in ("BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN") - ), - "fresh_attended_warden_envelope_required", - ) - fd = os.open( - a.receipt, os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600 - ) - - def save(): - os.lseek(fd, 0, os.SEEK_SET) - os.ftruncate(fd, 0) - os.write(fd, (json.dumps(receipt, indent=2) + "\n").encode()) - os.fsync(fd) - - kube = [ - "kubectl", - "--kubeconfig", - a.kubeconfig, - "--server", - a.server, - "--request-timeout=20s", - ] - receipt["receiver"] = compatible(kube, a.expected_receiver_image) - save() - identity = data(bao(["token", "lookup", "-format=json"]))["data"] - require( - "platform-admin" in identity["policies"] - and "root" not in identity["policies"], - "attended_platform_admin_required", - ) - version, _, rows, _ = snapshot() - operator = operator_identity(rows) - token = (operator.get("tokens") or [operator["token"]])[0] - receipt.update(reader_name=operator["name"], registry_version=version) - with socket.socket() as sock: - sock.bind(("127.0.0.1", 0)) - port = sock.getsockname()[1] - forward = subprocess.Popen( - kube - + [ - "-n", - "audit-core", - "port-forward", - "--address=127.0.0.1", - "service/audit-core", - str(port) + ":8080", - ], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - for _ in range(40): - require(forward.poll() is None, "port_forward_failed") - try: - with socket.create_connection(("127.0.0.1", port), timeout=0.2): - break - except OSError: - time.sleep(0.2) - else: - raise LaneError("port_forward_not_ready") - receipt.update( - readback( - "http://127.0.0.1:" + str(port), - token, - a.source, - a.tenant, - a.instance, - a.since, - a.until, - a.event_id, - ) - ) - receipt.update( - status="independent_custody_readback_complete", - helper_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), - ) - save() - return 0 - except Exception as exc: - receipt["error"] = ( - str(exc) if isinstance(exc, LaneError) else "contained_readback_failed" - ) - if fd is not None: - save() - return 1 - finally: - if forward is not None: - forward.terminate() - try: - forward.wait(timeout=5) - except subprocess.TimeoutExpired: - forward.kill() - forward.wait(timeout=5) - if fd is not None: - os.close(fd) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/cadence_receiver_custody.py b/scripts/cadence_receiver_custody.py deleted file mode 100644 index 43304b2..0000000 --- a/scripts/cadence_receiver_custody.py +++ /dev/null @@ -1,798 +0,0 @@ -#!/usr/bin/env python3 -"""Proposed NK-WP-0035 attended custody+cadence delivery; no values in receipts. - -Plan is offline. Apply requires CCR0047 exact owner approval and the SHA256 of -current complete packet, immutable compatible receiver and attended platform -admin. A failed attempt retains KV version1 and must resume explicitly. -""" - -from __future__ import annotations - -import argparse -import base64 -import copy -import hashlib -import json -import os -import secrets -import sys -import time -from datetime import datetime, timezone -from pathlib import Path - -import yaml -from factory_audit_custody import ( - REGISTRY, - credential_module, - prepare_metadata, - receiver_check, - require, - snapshot, -) -from factory_audit_delivery import ( - custody_value, - projection_matches, -) -from keycape_approval_custody import read_optional, require_denied, role_matches -from state_hub_preflight_lane import ROOT, LaneError, bao, command, data, revoke - -CCR = ROOT / "credential-change-requests/CCR-2026-0047-rapp-qonto-audit-sender.yaml" -MANIFEST = ROOT / "manifests/rapp-qonto-audit-sender.yaml" -WORKSPACE = ROOT.parent -APPROVAL = WORKSPACE / "approval-engine/cadence.yaml" -QONTO = WORKSPACE / "qonto-assistant/specs/audit-emission-cadence.yaml" -PROPOSAL = ( - WORKSPACE / "net-kingdom/generated/coordination-frame/approval-engine-receiver.json" -) -BUILDER = WORKSPACE / "audit-core/audit_core/cadence_registration.py" - - -def contracts(approved=False): - module = credential_module() - ccr, errors, _ = module.validate_ccr(CCR) - require(not errors, "invalid_credential_request") - if approved: - require( - ccr["status"] in {"approved", "applied", "verified", "active"}, - "approved_ccr_required", - ) - for owner in ( - "platform-operator", - "audit-core-owner", - "rapp-qonto-owner", - "qonto-assistant-owner", - ): - require( - any( - x.get("decision") == "approved" and owner in x.get("reviewer", "") - for x in ccr["review"]["comments"] - ), - "named_owner_approval_required", - ) - policy = "workload-kv-read-rapp-qonto-audit" - role = "external-secrets-rapp-qonto-audit" - payload = { - "bound_service_account_names": ["external-secrets"], - "bound_service_account_namespaces": ["external-secrets"], - "policies": policy, - "ttl": "15m", - } - o = ccr["openbao"] - require( - o["kv_path"] == "platform/workloads/rapp-qonto/audit-sender" - and o["fields"] == ["AUDIT_TOKEN", "CUSTODY_REQUEST"] - and o["policy_name"] == policy - and o["metadata_read"] is False - and o["auth"]["mount"] == "kubernetes" - and o["auth"]["role"] == role - and o["auth"]["bound_claims_confirmed"] is True - and module.auth_payload(ccr) == payload, - "exact_contract_required", - ) - hcl = module.generated_policy_hcl(ccr) - require((ROOT / o["policy_file"]).read_text() == hcl, "policy_source_drift") - return { - "ccr": "CCR-2026-0047", - "name": "rapp-qonto-collector", - "namespace": "rapp-qonto", - "kv": "platform/data/workloads/rapp-qonto/audit-sender", - "metadata": "platform/metadata/workloads/rapp-qonto/audit-sender", - "policy": policy, - "hcl": hcl, - "role": role, - "role_payload": payload, - "store": "openbao-rapp-qonto-audit", - "secret": "rapp-qonto-audit-sender", - "secret_key": "bearer", - } - - -def reviewed_objects(): - objects = list(yaml.safe_load_all(MANIFEST.read_text())) - require(len(objects) == 2, "exact_projection_objects_required") - store, external = objects - require( - store["kind"] == "ClusterSecretStore" - and store["metadata"]["name"] == "openbao-rapp-qonto-audit" - and store["spec"]["conditions"] == [{"namespaces": ["rapp-qonto"]}], - "exact_store_scope_required", - ) - vault = store["spec"]["provider"]["vault"] - require( - vault - == { - "server": "http://openbao.openbao.svc:8200", - "path": "platform", - "version": "v2", - "auth": { - "kubernetes": { - "mountPath": "kubernetes", - "role": "external-secrets-rapp-qonto-audit", - "serviceAccountRef": { - "name": "external-secrets", - "namespace": "external-secrets", - }, - } - }, - }, - "exact_store_auth_required", - ) - require( - external["kind"] == "ExternalSecret" - and external["metadata"]["name"] == "rapp-qonto-audit-sender" - and external["metadata"]["namespace"] == "rapp-qonto" - and external["spec"]["secretStoreRef"] - == {"kind": "ClusterSecretStore", "name": "openbao-rapp-qonto-audit"}, - "exact_projection_target_required", - ) - spec = external["spec"] - require( - set(spec) == {"refreshInterval", "secretStoreRef", "target", "data"}, - "unexpected_projection_fields", - ) - require( - spec["data"] - == [ - { - "secretKey": "bearer", - "remoteRef": { - "key": "workloads/rapp-qonto/audit-sender", - "property": "AUDIT_TOKEN", - "conversionStrategy": "Default", - "decodingStrategy": "None", - "metadataPolicy": "None", - }, - } - ], - "exact_projection_field_required", - ) - target = spec["target"] - require( - set(target) == {"name", "creationPolicy", "deletionPolicy", "template"} - and target["name"] == "rapp-qonto-audit-sender" - and target["creationPolicy"] == "Owner" - and target["deletionPolicy"] == "Retain", - "exact_projection_ownership_required", - ) - template = target["template"] - require( - set(template) == {"metadata", "engineVersion", "mergePolicy"} - and template["engineVersion"] == "v2" - and template["mergePolicy"] == "Replace" - and set(template["metadata"]) == {"annotations"}, - "metadata_only_template_required", - ) - return objects - - -def packet(): - contracts() - reviewed_objects() - paths = [ - CCR, - MANIFEST, - ROOT / "openbao/policies/workload-kv-read-rapp-qonto-audit.hcl", - Path(__file__), - ROOT / "scripts/factory_audit_custody.py", - ROOT / "scripts/factory_audit_delivery.py", - ROOT / "scripts/keycape_approval_custody.py", - ROOT / "scripts/state_hub_preflight_lane.py", - ROOT / "scripts/credential-change.py", - APPROVAL, - QONTO, - PROPOSAL, - BUILDER, - WORKSPACE / "audit-core/audit_core/senders.py", - WORKSPACE / "audit-core/audit_core/emission_cadence.py", - ] - hashes = { - str(p.relative_to(WORKSPACE)): hashlib.sha256(p.read_bytes()).hexdigest() - for p in paths - } - digest = hashlib.sha256(json.dumps(hashes, sort_keys=True).encode()).hexdigest() - return { - "schema_version": "platform.cadence-custody-packet/v1", - "sha256": digest, - "source_sha256": hashes, - "status": "proposed", - "credential_values_emitted": False, - } - - -def builder(): - sys.path.insert(0, str(WORKSPACE / "audit-core")) - from audit_core.cadence_registration import prepare, qonto_metadata - - return prepare, qonto_metadata - - -def proposal_metadata(): - _, qonto_metadata = builder() - raw = QONTO.read_bytes() - return qonto_metadata(yaml.safe_load(raw), hashlib.sha256(raw).hexdigest()) - - -def preflight_custody(lane, *, resume=False): - """Refuse existing custody/scope drift before creating any reader grant.""" - _, _, rows, used = snapshot() - existing = [row for row in rows if row["name"] == lane["name"]] - old = read_optional(lane["metadata"]) - if old is None: - require(not existing, "registry_sender_without_custody") - return - require( - resume - and old["current_version"] == 1 - and not old["versions"]["1"].get("destroyed") - and not old["versions"]["1"].get("deletion_time"), - "reviewed_version1_resume_required", - ) - raw = data(bao(["read", "-format=json", lane["kv"]]))["data"] - token = custody_value(lane, raw) - wanted = dict(proposal_metadata(), tokens=[token]) - if existing: - require(existing == [wanted], "registered_sender_drift") - else: - require(token not in used, "sender_token_collision") - - -def merge_and_seed(lane, receipt, save, *, resume=False): - """CAS all registry metadata once; only new exact KV uses CAS0. - - All private values are in memory. A CAS conflict does not retry against a - new registry silently: operator re-preflights and resumes durable version1. - """ - version, body, rows, used = snapshot() - originals = copy.deepcopy(rows) - prepare, _ = builder() - updated, approval_receipt = prepare( - body, APPROVAL.read_bytes(), json.loads(PROPOSAL.read_text()) - ) - wanted = proposal_metadata() - existing = [row for row in rows if row["name"] == lane["name"]] - old = read_optional(lane["metadata"]) - if old is None: - require(not existing, "registry_sender_without_custody") - token = secrets.token_urlsafe(48) - require(token not in used, "sender_token_collision") - receipt.update(phase="custody_cas0", registry_version_before=version) - save() - result = data( - bao( - ["write", "-format=json", lane["kv"], "-"], - payload={ - "options": {"cas": 0}, - "data": {"AUDIT_TOKEN": token, "CUSTODY_REQUEST": lane["ccr"]}, - }, - ) - ) - require(result["data"]["version"] == 1, "initial_version_mismatch") - receipt.update(custody_version=1, custody_request_id=result.get("request_id")) - save() - else: - require( - resume - and old["current_version"] == 1 - and not old["versions"]["1"].get("destroyed") - and not old["versions"]["1"].get("deletion_time"), - "reviewed_version1_resume_required", - ) - raw = data(bao(["read", "-format=json", lane["kv"]]))["data"] - token = custody_value(lane, raw) - receipt["custody_version_reused"] = 1 - wanted["tokens"] = [token] - if existing: - require(existing == [wanted], "registered_sender_drift") - else: - require(token not in used, "sender_token_collision") - merged = json.loads(updated["senders.json"]) - if not existing: - merged.append(wanted) - updated["senders.json"] = json.dumps(merged, separators=(",", ":")) - # The real receiver parser validates the complete candidate before CAS. - from audit_core.senders import SenderRegistry - - SenderRegistry.from_env({"AUDIT_CORE_SENDERS": updated["senders.json"]}) - receipt.update(phase="registry_cas", approval=approval_receipt) - save() - if updated != body: - result = data( - bao( - ["write", "-format=json", REGISTRY, "-"], - payload={"options": {"cas": version}, "data": updated}, - ) - ) - receipt["registry_request_id"] = result.get("request_id") - after_version, after_body, after_rows, _ = snapshot() - require(after_body == updated, "registry_readback_mismatch") - require( - all(row in after_rows for row in originals if row["name"] != "approval-engine"), - "other_sender_changed", - ) - before_approval = next(row for row in originals if row["name"] == "approval-engine") - after_approval = next(row for row in after_rows if row["name"] == "approval-engine") - require( - { - k: v - for k, v in before_approval.items() - if k not in ("heartbeat_classes", "emission_cadence") - } - == { - k: v - for k, v in after_approval.items() - if k not in ("heartbeat_classes", "emission_cadence") - }, - "approval_credentials_or_scope_changed", - ) - receipt.update( - registry_version_after=after_version, - registry_readback_matches=True, - other_senders_preserved=True, - approval_credentials_preserved=True, - ) - save() - return token, updated - - -COMPATIBILITY = """import inspect,json,sys -from audit_core.sqlite_backend import SQLiteAuditBackend -from audit_core.postgres_backend import PostgresAuditBackend -from audit_core.senders import SenderRegistry -from audit_core.cadence_registration import qonto_metadata -entry=json.load(sys.stdin) -entry['tokens']=['fixture-only-never-native'] -r=SenderRegistry.from_env({'AUDIT_CORE_SENDERS':json.dumps([entry])}) -a=r.authenticate('Bearer fixture-only-never-native') -proof={'cadence_registration':callable(qonto_metadata), -'sqlite_instance_filter':'stream_instance_id' in inspect.signature(SQLiteAuditBackend.event_counts).parameters, -'postgres_instance_filter':'stream_instance_id' in inspect.signature(PostgresAuditBackend.event_counts).parameters, -'synthetic_cadence_parse':a.emission_cadence is not None and a.emission_cadence.source=='qonto-assistant', -'synthetic_heartbeat_parse':dict(a.heartbeat_classes)==entry['heartbeat_classes'], -'synthetic_exact_scope':a.sources==frozenset(entry['sources']) and a.tenants==frozenset(entry['tenants']) and a.may_write and not a.may_read} -print(json.dumps(proof)) -""" - - -def compatible(kube, image): - result = receiver_check(kube, image) - pod = data( - command( - kube - + [ - "-n", - "audit-core", - "get", - "pods", - "-l", - "app.kubernetes.io/name=audit-core,app.kubernetes.io/component=receiver", - "-o", - "json", - ] - ) - )["items"][0] - proof = data( - command( - kube - + [ - "-n", - "audit-core", - "exec", - "-i", - pod["metadata"]["name"], - "--", - "python", - "-c", - COMPATIBILITY, - ], - payload=proposal_metadata(), - ) - ) - require( - set(proof) - == { - "cadence_registration", - "sqlite_instance_filter", - "postgres_instance_filter", - "synthetic_cadence_parse", - "synthetic_heartbeat_parse", - "synthetic_exact_scope", - } - and all(v is True for v in proof.values()), - "receiver_cadence_instance_support_required", - ) - result["cadence_instance_capabilities"] = proof - return result - - -def reader_checks(kube, lane, token, receipt, save): - require( - role_matches(read_optional("auth/kubernetes/role/" + lane["role"]), lane), - "reader_role_drift", - ) - jwt = ( - command( - kube - + [ - "-n", - "external-secrets", - "create", - "token", - "external-secrets", - "--duration=10m", - ] - ) - .stdout.decode() - .strip() - ) - auth = data( - bao( - ["write", "-format=json", "auth/kubernetes/login", "-"], - payload={"role": lane["role"], "jwt": jwt}, - ) - )["auth"] - reader = auth["client_token"] - try: - require( - set(auth["token_policies"]) == {lane["policy"], "default"} - and 0 < auth["lease_duration"] <= 900, - "reader_scope_ttl_mismatch", - ) - raw = data(bao(["read", "-format=json", lane["kv"]], token=reader))["data"] - require(custody_value(lane, raw) == token, "reader_value_mismatch") - for path in ( - REGISTRY, - lane["metadata"], - "platform/data/workloads/approval-engine/audit-sender", - ): - require_denied( - bao(["read", "-format=json", path], token=reader, allow_failure=True) - ) - require_denied( - bao( - ["list", "-format=json", "platform/metadata/workloads"], - token=reader, - allow_failure=True, - ) - ) - finally: - revoke(reader) - require_denied( - bao(["token", "lookup", "-format=json"], token=reader, allow_failure=True) - ) - receipt["reader_exact_path_negatives_and_revocation"] = True - save() - # Exercise incorrect namespace and service account without creating workloads. - for namespace, account in ( - ("rapp-qonto", "default"), - ("external-secrets", "default"), - ): - jwt = ( - command( - kube + ["-n", namespace, "create", "token", account, "--duration=10m"] - ) - .stdout.decode() - .strip() - ) - bad = bao( - ["write", "-format=json", "auth/kubernetes/login", "-"], - payload={"role": lane["role"], "jwt": jwt}, - allow_failure=True, - ) - if bad.returncode == 0: - revoke(data(bad)["auth"]["client_token"]) - raise LaneError("wrong_service_account_or_namespace_authenticated") - require(b"403" in bad.stderr, "wrong_identity_denial_inconclusive") - child = data( - bao( - [ - "token", - "create", - "-format=json", - "-policy=" + lane["policy"], - "-policy=agent-high-risk-boundary", - "-no-default-policy", - "-ttl=60s", - ] - ) - )["auth"]["client_token"] - try: - for path in (lane["kv"], lane["metadata"]): - require_denied( - bao(["read", "-format=json", path], token=child, allow_failure=True) - ) - finally: - revoke(child) - receipt.update(wrong_identity_denied=True, coding_agent_deny_wins=True) - save() - - -def preflight_projection(kube, objects): - """Exact reviewed metadata template allowed; arbitrary template data refused.""" - for obj in objects: - meta = obj["metadata"] - prefix = kube + (["-n", meta["namespace"]] if "namespace" in meta else []) - result = command( - prefix + ["get", obj["kind"], meta["name"], "-o", "json"], - allow_failure=True, - ) - if result.returncode: - require(b"NotFound" in result.stderr, "projection_presence_unknown") - continue - live = data(result) - require( - all(live["spec"].get(k) == v for k, v in obj["spec"].items()), - "existing_projection_drift", - ) - require(not live["spec"].get("dataFrom"), "unexpected_projection_datafrom") - template = live["spec"].get("target", {}).get("template") - wanted = obj["spec"].get("target", {}).get("template") - require(template == wanted, "unexpected_projection_template") - - -def deliver(kube, lane, token, body, receipt, save, image): - objects = reviewed_objects() - preflight_projection(kube, objects) - command(kube + ["apply", "-f", str(MANIFEST)]) - command( - kube - + [ - "wait", - "--for=condition=Ready", - "clustersecretstore/" + lane["store"], - "--timeout=45s", - ] - ) - command( - kube - + [ - "-n", - lane["namespace"], - "wait", - "--for=condition=Ready", - "externalsecret/" + lane["secret"], - "--timeout=45s", - ] - ) - es = data( - command( - kube - + [ - "-n", - lane["namespace"], - "get", - "externalsecret", - lane["secret"], - "-o", - "json", - ] - ) - ) - secret = data( - command( - kube - + ["-n", lane["namespace"], "get", "secret", lane["secret"], "-o", "json"] - ) - ) - projection_matches(secret, es, lane, token) - receipt["projection_matches"] = True - save() - command( - kube - + [ - "-n", - "audit-core", - "annotate", - "externalsecret", - "audit-core-senders", - "force-sync=" + str(time.time_ns()), - "--overwrite", - ] - ) - for _ in range(25): - projected = data( - command( - kube - + [ - "-n", - "audit-core", - "get", - "secret", - "audit-core-senders", - "-o", - "json", - ] - ) - ) - if json.loads( - base64.b64decode(projected["data"]["senders.json"]) - ) == json.loads(body["senders.json"]): - break - time.sleep(2) - else: - raise LaneError("receiver_registry_projection_mismatch") - receipt["receiver_registry_projection_matches"] = True - save() - command(kube + ["-n", "audit-core", "rollout", "restart", "deployment/audit-core"]) - command( - kube - + [ - "-n", - "audit-core", - "rollout", - "status", - "deployment/audit-core", - "--timeout=50s", - ] - ) - receipt["receiver_after"] = compatible(kube, image) - # Environment inherited by exec must equal the private projected registry. - pod = data( - command( - kube - + [ - "-n", - "audit-core", - "get", - "pods", - "-l", - "app.kubernetes.io/name=audit-core,app.kubernetes.io/component=receiver", - "-o", - "json", - ] - ) - )["items"][0] - expected_hash = hashlib.sha256( - json.dumps(json.loads(body["senders.json"]), sort_keys=True).encode() - ).hexdigest() - probe = 'import os,json,hashlib;print(json.dumps({"sha256":hashlib.sha256(json.dumps(json.loads(os.environ["AUDIT_CORE_SENDERS"]),sort_keys=True).encode()).hexdigest()}))' - proof = data( - command( - kube - + [ - "-n", - "audit-core", - "exec", - pod["metadata"]["name"], - "--", - "python", - "-c", - probe, - ] - ) - ) - require(proof == {"sha256": expected_hash}, "receiver_runtime_registry_mismatch") - # Do not publish a low-entropy token hash or full registry hash in receipts. - receipt.update(receiver_reloaded=True, receiver_runtime_registry_matches=True) - save() - - -def main(): - p = argparse.ArgumentParser(description=__doc__) - p.add_argument("action", choices=["plan", "apply"]) - p.add_argument("--kubeconfig") - p.add_argument("--server") - p.add_argument("--expected-receiver-image") - p.add_argument("--confirm-packet-sha256") - p.add_argument("--receipt", type=Path) - p.add_argument("--resume", action="store_true") - a = p.parse_args() - receipt = { - "schema_version": "platform.cadence-custody/v1", - "status": "refused", - "credential_values_emitted": False, - "started_at": datetime.now(timezone.utc).isoformat(), - } - fd = None - try: - current = packet() - if a.action == "plan": - current["qonto_sender_metadata"] = proposal_metadata() - print(json.dumps(current, indent=2)) - return 0 - lane = contracts(approved=True) - require( - a.confirm_packet_sha256 == current["sha256"], - "exact_reviewed_packet_required", - ) - require( - a.kubeconfig and a.server and a.expected_receiver_image and a.receipt, - "explicit_runtime_inputs_required", - ) - require( - Path.home().parent.name == ".warden-attended-login" - and not os.getenv("BAO_TOKEN") - and not os.getenv("VAULT_TOKEN") - and not os.getenv("OPENBAO_TOKEN"), - "attended_warden_envelope_required", - ) - fd = os.open( - a.receipt, os.O_RDWR | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600 - ) - - def save(): - os.lseek(fd, 0, os.SEEK_SET) - os.ftruncate(fd, 0) - os.write(fd, (json.dumps(receipt, indent=2) + "\n").encode()) - os.fsync(fd) - - receipt["packet_sha256"] = current["sha256"] - save() - kube = [ - "kubectl", - "--kubeconfig", - a.kubeconfig, - "--server", - a.server, - "--request-timeout=20s", - ] - receipt["receiver_before"] = compatible(kube, a.expected_receiver_image) - save() - identity = data(bao(["token", "lookup", "-format=json"]))["data"] - require( - "platform-admin" in identity["policies"] - and "root" not in identity["policies"], - "attended_platform_admin_required", - ) - command(kube + ["get", "namespace", "rapp-qonto", "-o", "name"]) - # Validate registry, declarations, lane and projection before metadata writes. - _, body, _, _ = snapshot() - prepare, _ = builder() - prepare(body, APPROVAL.read_bytes(), json.loads(PROPOSAL.read_text())) - proposal_metadata() - preflight_projection(kube, reviewed_objects()) - preflight_custody(lane, resume=a.resume) - receipt["phase"] = "metadata" - save() - prepare_metadata([lane]) - token, updated = merge_and_seed(lane, receipt, save, resume=a.resume) - receipt["phase"] = "reader_verification" - save() - reader_checks(kube, lane, token, receipt, save) - receipt["phase"] = "delivery" - save() - deliver(kube, lane, token, updated, receipt, save, a.expected_receiver_image) - receipt.update( - status="delivered_pending_native_producer_and_independent_acceptance", - completed_at=datetime.now(timezone.utc).isoformat(), - ) - save() - return 0 - except Exception as exc: - receipt.update( - status="refused", - error=str(exc) - if isinstance(exc, LaneError) - else "contained_operation_failed", - ) - if fd is not None: - save() - elif a.action == "plan": - print(json.dumps(receipt, indent=2)) - return 1 - finally: - if fd is not None: - os.close(fd) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/capture_service_observation.py b/scripts/capture_service_observation.py index 1e9c25c..e17f0b0 100644 --- a/scripts/capture_service_observation.py +++ b/scripts/capture_service_observation.py @@ -18,8 +18,8 @@ from recovery_evidence import recovery_signals def query(args, allowed_codes=(0,)): - import kube_route - result = subprocess.run(kube_route.ssh_argv(['kubectl', '--request-timeout=10s', *args]), + result = subprocess.run(['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', + 'railiance01', shlex.join(['kubectl', '--request-timeout=10s', *args])], capture_output=True, text=True, timeout=25) if result.returncode not in allowed_codes: raise ValueError('query unavailable') diff --git a/scripts/ccr_intake.py b/scripts/ccr_intake.py deleted file mode 100644 index bf26783..0000000 --- a/scripts/ccr_intake.py +++ /dev/null @@ -1,474 +0,0 @@ -"""Self-service CCR intake: submit, standard-lane review and id checks. - -RPF-WP-0061-T02. Lets a client file a CCR and obtain the platform-owner review -for in-profile lanes without a railiance-platform worker session. The rules -live in registry/ccr-standard-profile.yaml; this module only evaluates them. -Nothing here touches OpenBao or grants approval: the CCR stays `proposed` -until the founder decision is synced. -""" -from __future__ import annotations - -import argparse -import hashlib -import json -import re -import shutil -import subprocess -import sys -import urllib.request -from collections import defaultdict -from datetime import datetime, timezone -from pathlib import Path -from typing import Any - -import yaml - -REPO_DIR = Path(__file__).resolve().parents[1] -DEFAULT_PROFILE = REPO_DIR / "registry/ccr-standard-profile.yaml" -CCR_ID_RE = re.compile(r"^CCR-(\d{4})-(\d{4})$") -CCR_FILE_RE = re.compile(r"^(CCR-\d{4}-\d{4})(-[a-z0-9-]+)?\.ya?ml$") -SLUG_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") -TTL_UNITS = {"s": 1, "m": 60, "h": 3600, "d": 86400} -PLATFORM_OWNER = "railiance-platform-owner" -PLATFORM_TOPIC_ID = "ca369340-a64e-442e-98f1-a4fa7dc74a38" - - -def load_profile(path: Path = DEFAULT_PROFILE) -> tuple[dict[str, Any], str]: - raw = path.read_bytes() - return yaml.safe_load(raw), "sha256:" + hashlib.sha256(raw).hexdigest() - - -def ttl_seconds(value: Any) -> int | None: - if not isinstance(value, str) or not re.match(r"^[1-9][0-9]*[smhd]$", value): - return None - return int(value[:-1]) * TTL_UNITS[value[-1]] - - -def bound_list(auth: dict[str, Any], key: str) -> list[Any]: - claims = auth.get("bound_claims") or {} - return list(claims.get(key) or auth.get(f"bound_{key}") or []) - - -def ccr_files(directory: Path) -> list[Path]: - return sorted(p for p in directory.glob("CCR-*.y*ml") if CCR_FILE_RE.match(p.name)) - - -def reader_conflicts( - ccr: dict[str, Any], directory: Path, profile: dict[str, Any], cc: Any -) -> list[str]: - kv_path = (ccr.get("openbao") or {}).get("kv_path") - statuses = set(profile["reader_conflict_statuses"]) - conflicts = [] - for path in ccr_files(directory): - other = cc.load_yaml(path) - if other.get("id") == ccr.get("id"): - continue - if (other.get("openbao") or {}).get("kv_path") == kv_path and other.get("status") in statuses: - conflicts.append(f"{other.get('id')} ({other.get('status')})") - return conflicts - - -def standard_violations( - ccr: dict[str, Any], profile: dict[str, Any], directory: Path, cc: Any -) -> list[str]: - """Return every reason the CCR falls outside the standard lane profile.""" - v: list[str] = [] - if ccr.get("request_type") != profile["request_type"]: - return [f"request_type must be {profile['request_type']}"] - openbao = ccr.get("openbao") or {} - auth = openbao.get("auth") or {} - want = profile["auth"] - kv_path = openbao.get("kv_path") or "" - if openbao.get("mount") != profile["mount"]: - v.append(f"mount must be {profile['mount']}") - if not any(re.match(p, kv_path) for p in profile["kv_path_patterns"]): - v.append(f"kv_path {kv_path!r} is outside the standard workload/supplier prefixes") - if openbao.get("metadata_read", True) is not profile["metadata_read"]: - v.append("metadata_read must be false") - if bool(openbao.get("token_capabilities_self", False)) is not profile["token_capabilities_self"]: - v.append("token_capabilities_self is not standard") - policy_name = openbao.get("policy_name") or "" - if not policy_name.startswith(profile["policy_name_prefix"]): - v.append(f"policy_name must start with {profile['policy_name_prefix']}") - policy_file = openbao.get("policy_file") - if not policy_file: - v.append("policy_file is required") - else: - policy_path = REPO_DIR / policy_file - if not policy_path.exists(): - v.append(f"policy_file {policy_file} does not exist") - elif cc.normalized_policy_body(policy_path.read_text()) != cc.normalized_policy_body( - cc.generated_policy_hcl(ccr) - ): - v.append("policy_file differs from the generated policy") - if auth.get("method") != want["method"] or auth.get("mount") != want["mount"]: - v.append(f"auth must be {want['method']} on mount {want['mount']}") - if not str(auth.get("role") or "").startswith(want["role_prefix"]): - v.append(f"auth role must start with {want['role_prefix']}") - if auth.get("audience") != want["audience"]: - v.append(f"audience must be {want['audience']}") - for key in ("ttl", "token_max_ttl", "token_explicit_max_ttl"): - seconds = ttl_seconds(auth.get(key)) - if seconds is None or seconds > want["max_ttl_seconds"]: - v.append(f"{key} must be set and at most {want['max_ttl_seconds']}s") - if auth.get("token_no_default_policy") is not want["token_no_default_policy"]: - v.append("token_no_default_policy must be true") - if auth.get("policies") != [policy_name]: - v.append("auth policies must be exactly [policy_name]") - accounts = bound_list(auth, "service_account_names") - namespaces = bound_list(auth, "service_account_namespaces") - if len(accounts) != want["service_accounts"] or "*" in accounts: - v.append(f"exactly {want['service_accounts']} exact service account required") - if len(namespaces) != want["namespaces"] or "*" in namespaces: - v.append(f"exactly {want['namespaces']} exact namespace required") - forbidden = sorted(set(namespaces) & set(want["forbidden_namespaces"])) - if forbidden: - v.append(f"namespace {forbidden} is reserved for platform components") - risk = (ccr.get("risk") or {}).get("classification") - if risk not in profile["allowed_risk_classifications"]: - v.append(f"risk classification {risk!r} needs a platform review") - plan_ref = ccr.get("plan_ref") - if plan_ref is not None and not ( - isinstance(plan_ref, dict) - and all(isinstance(plan_ref.get(k), str) and plan_ref.get(k) for k in ("repo", "path", "commit")) - and re.match(r"^[0-9a-f]{7,40}$", plan_ref["commit"]) - ): - v.append("plan_ref must have non-empty repo, path and a hex commit") - conflicts = reader_conflicts(ccr, directory, profile, cc) - if conflicts: - v.append(f"kv_path already has a reader: {', '.join(conflicts)}") - return v - - -def duplicate_ids(directory: Path, cc: Any) -> dict[str, list[str]]: - seen: dict[str, list[str]] = defaultdict(list) - for path in ccr_files(directory): - seen[str(cc.load_yaml(path).get("id"))].append(path.name) - prefix = CCR_FILE_RE.match(path.name).group(1) - if prefix != cc.load_yaml(path).get("id"): - seen[f"{prefix} (file name vs id)"].append(path.name) - return {k: v for k, v in seen.items() if len(v) > 1 or "file name" in k} - - -def next_ccr_id(directory: Path, year: int) -> str: - numbers = [0] - for path in ccr_files(directory): - match = CCR_ID_RE.match(CCR_FILE_RE.match(path.name).group(1)) - if match and int(match.group(1)) == year: - numbers.append(int(match.group(2))) - return f"CCR-{year}-{max(numbers) + 1:04d}" - - -def record_review(path: Path, ccr: dict[str, Any], violations: list[str], profile: dict[str, Any], digest: str, cc: Any) -> None: - label = f"{profile['profile']} v{profile['version']} {digest}" - if violations: - decision = "needs_platform_review" - comment = f"Outside {label}: " + "; ".join(violations) - else: - decision = "approved" - comment = (f"Standard lane: passes {label}. Platform-owner source review only; " - "founder approval and confirm-binding still required.") - comments = ccr.setdefault("review", {}).setdefault("comments", []) - comments.append({ - "at": cc.utc_now(), - "reviewer": f"{PLATFORM_OWNER} (policy-as-code {profile['profile']} v{profile['version']})", - "decision": decision, - "comment": comment, - }) - ccr["updated"] = datetime.now(timezone.utc).date().isoformat() - cc.dump_yaml(path, ccr) - - -def notify_platform(ccr: dict[str, Any], violations: list[str], base_url: str) -> None: - payload = { - "from_agent": (ccr.get("requester") or {}).get("agent", "ccr-intake"), - "to_agent": "railiance-platform", - "subject": f"{ccr['id']} needs platform review (outside standard lane profile)", - "body": f"{ccr['id']} {ccr.get('title')}: " + "; ".join(violations), - } - request = urllib.request.Request( - f"{base_url.rstrip('/')}/messages/", data=json.dumps(payload).encode(), - headers={"Content-Type": "application/json"}, method="POST") - with urllib.request.urlopen(request, timeout=15): - pass - - -def review_standard(path: Path, cc: Any, profile_path: Path = DEFAULT_PROFILE, - write: bool = True) -> list[str]: - ccr, _warnings = cc.validate_or_exit(path) - if ccr.get("status") != "proposed": - cc.fail(f"review-standard requires status proposed, got {ccr.get('status')}") - profile, digest = load_profile(profile_path) - violations = standard_violations(ccr, profile, path.parent, cc) - if write: - record_review(path, ccr, violations, profile, digest, cc) - return violations - - -def command_review_standard(args: argparse.Namespace, cc: Any) -> int: - path = cc.resolve_ccr(args.ref) - violations = review_standard(path, cc, Path(args.profile), write=not args.check) - if not violations: - print(f"[OK] {path.name}: standard lane; platform-owner review recorded" - if not args.check else f"[OK] {path.name}: standard lane") - return 0 - for item in violations: - print(f"[NEEDS-PLATFORM] {path.name}: {item}") - if args.notify and not args.check: - notify_platform(cc.load_yaml(path), violations, args.state_hub_url) - print("[OK] railiance-platform notified") - return 2 - - -def command_check_ids(args: argparse.Namespace, cc: Any) -> int: - duplicates = duplicate_ids(cc.ccr_dir(), cc) - for key, names in duplicates.items(): - print(f"[ERROR] {key}: {names}") - if not duplicates: - print("[OK] CCR ids unique and matching file names") - return 1 if duplicates else 0 - - -def tree_digests(root: Path) -> dict[str, str]: - return { - str(path.relative_to(root)): hashlib.sha256(path.read_bytes()).hexdigest() - for path in root.rglob("*") - if path.is_file() and ".git" not in path.relative_to(root).parts - and "__pycache__" not in path.parts - } - - -def branch_gate(main_root: Path, stem: str, cc: Any, profile_path: Path = DEFAULT_PROFILE) -> list[str]: - """CI gate for a ccr/ branch checked out at REPO_DIR (RPF-WP-0061-T03). - - The branch may add exactly one new CCR and its new policy file, nothing - else; the CCR must be an in-profile submission from a listed submitter with - a policy-as-code review against the current profile digest. - """ - errors: list[str] = [] - ccr_rel = f"credential-change-requests/{stem}.yaml" - path = REPO_DIR / ccr_rel - if not CCR_FILE_RE.match(path.name) or not path.exists(): - return [f"branch ccr/{stem} must add {ccr_rel}"] - if (main_root / ccr_rel).exists(): - errors.append(f"{ccr_rel} already exists on main") - ccr, validation_errors, _warnings = cc.validate_ccr(path) - errors.extend(f"validate: {item}" for item in validation_errors) - policy_rel = (ccr.get("openbao") or {}).get("policy_file") - allowed = {ccr_rel} | ({policy_rel} if policy_rel else set()) - branch, main = tree_digests(REPO_DIR), tree_digests(main_root) - changed = {k for k in branch.keys() | main.keys() if branch.get(k) != main.get(k)} - for extra in sorted(changed - allowed): - errors.append(f"ccr branch may not change {extra}") - if policy_rel and policy_rel in main and main[policy_rel] != branch.get(policy_rel): - errors.append(f"{policy_rel} exists on main; a submission may not modify it") - if ccr.get("status") != "proposed": - errors.append(f"status must be proposed, got {ccr.get('status')}") - profile, digest = load_profile(profile_path) - if (ccr.get("requester") or {}).get("agent") not in profile["submitters"]: - errors.append(f"requester is not a listed submitter {profile['submitters']}") - errors.extend(standard_violations(ccr, profile, path.parent, cc)) - comments = (ccr.get("review") or {}).get("comments") or [] - if len(comments) != 1 or comments[0].get("decision") != "approved" or digest not in str( - comments[0].get("comment") - ): - errors.append("review must hold exactly the policy-as-code approval for the current profile") - errors.extend(f"duplicate id: {k} {v}" for k, v in duplicate_ids(path.parent, cc).items()) - return errors - - -def command_check_branch(args: argparse.Namespace, cc: Any) -> int: - stem = args.branch.removeprefix("refs/heads/").removeprefix("ccr/") - errors = branch_gate(Path(args.main_root).resolve(), stem, cc) - for item in errors: - print(f"[FAIL] ccr/{stem}: {item}") - if not errors: - print(f"[OK] ccr/{stem}: one in-profile CCR, nothing else changed") - return 1 if errors else 0 - - -def decision_payload(ccr: dict[str, Any], cc: Any) -> dict[str, Any]: - openbao = ccr["openbao"] - auth = openbao["auth"] - review = ccr["review"]["comments"][-1] - description = "\n".join([ - f"Founder decision for {ccr['id']}: {ccr.get('title')}.", - "", - "Non-secret request summary:", - f"- Requester: {(ccr.get('requester') or {}).get('agent')}", - f"- OpenBao path: {openbao.get('kv_path')} fields {openbao.get('fields')}", - f"- Policy: {openbao.get('policy_name')} ({openbao.get('policy_file')})", - f"- Auth: {auth.get('method')} role {auth.get('role')}, SA {bound_list(auth, 'service_account_names')}" - f" in {bound_list(auth, 'service_account_namespaces')}, ttl {auth.get('ttl')}", - f"- Risk: {(ccr.get('risk') or {}).get('classification')}", - f"- Platform review: {review.get('reviewer')}: {review.get('comment')}", - "", - # The link line would be a placeholder: the decision does not exist yet. - *[line for line in cc.render_decision_templates(ccr).splitlines() - if not line.startswith("Decision link:")], - "", - "Approval permits apply after confirm-binding; it exposes and stores no value.", - ]) - return { - "topic_id": PLATFORM_TOPIC_ID, - "title": f"Review {ccr['id']} {ccr.get('title')}", - "description": description, - "escalation_note": "Founder decision required before OpenBao credential lane apply.", - } - - -def request_decision(path: Path, cc: Any, base_url: str) -> dict[str, Any]: - """Open the founder's State Hub decision for a platform-reviewed CCR (RPF-WP-0061-T04).""" - ccr, _warnings = cc.validate_or_exit(path) - if ccr.get("status") != "proposed": - cc.fail(f"request-decision requires status proposed, got {ccr.get('status')}") - if (ccr.get("state_hub") or {}).get("decision_id"): - cc.fail(f"{ccr['id']} already has State Hub decision {ccr['state_hub']['decision_id']}") - comments = (ccr.get("review") or {}).get("comments") or [] - last = comments[-1] if comments else {} - if last.get("decision") != "approved" or not str(last.get("reviewer", "")).startswith(PLATFORM_OWNER): - cc.fail("request-decision requires a platform-owner approval as the latest review comment") - decision = cc.state_hub_post_json(base_url, "/decisions/", decision_payload(ccr, cc)) - if not decision.get("id") or decision.get("status") not in (None, "open"): - cc.fail(f"State Hub returned an unexpected decision: {decision.get('id')} {decision.get('status')}") - state_hub = ccr.setdefault("state_hub", {}) - state_hub["decision_id"] = decision["id"] - state_hub["decision_api_url"] = f"{base_url.rstrip('/')}/decisions/{decision['id']}" - ccr["updated"] = datetime.now(timezone.utc).date().isoformat() - cc.dump_yaml(path, ccr) - return decision - - -def command_request_decision(args: argparse.Namespace, cc: Any) -> int: - path = cc.resolve_ccr(args.ref) - decision = request_decision(path, cc, args.state_hub_url) - print(f"[OK] {path.name}: State Hub decision {decision['id']} opened; commit the CCR file") - return 0 - - -def standard_policy_hcl(kv_path: str, cc: Any) -> str: - """The exact HCL review-standard compares against, for a standard-lane path.""" - return cc.generated_policy_hcl({ - "request_type": "workload-kv-read", - "openbao": {"mount": kv_path.split("/", 1)[0], "kv_path": kv_path, - "metadata_read": False, "token_self_lifecycle": True}, - }) - - -def command_render_policy(args: argparse.Namespace, cc: Any) -> int: - profile, _digest = load_profile(Path(args.profile)) - if not any(re.match(p, args.kv_path) for p in profile["kv_path_patterns"]): - cc.fail(f"kv_path {args.kv_path!r} is outside the standard workload/supplier prefixes") - sys.stdout.write(standard_policy_hcl(args.kv_path, cc)) - return 0 - - -def git(*argv: str) -> None: - subprocess.run(["git", "-C", str(REPO_DIR), *argv], check=True) - - -def submit(args: argparse.Namespace, cc: Any) -> tuple[Path, list[str]]: - profile, _digest = load_profile(Path(args.profile)) - if args.requester not in profile["submitters"]: - cc.fail(f"requester {args.requester!r} may not self-submit; allowed: {profile['submitters']}") - if not SLUG_RE.match(args.slug): - cc.fail("slug must be lowercase letters, digits and dashes") - source = cc.resolve_repo_path(args.ccr) - errors: list[str] = [] - cc.scan_for_secrets(source, errors) - if errors: - cc.fail("; ".join(errors)) - ccr = cc.load_yaml(source) - directory = cc.ccr_dir() - today = datetime.now(timezone.utc).date() - ccr_id = next_ccr_id(directory, today.year) - ccr.update(id=ccr_id, status="proposed", created=today.isoformat(), updated=today.isoformat()) - ccr.setdefault("requester", {})["agent"] = args.requester - ccr.setdefault("review", {})["comments"] = [] - openbao = ccr.get("openbao") or {} - if args.policy: - policy_name = openbao.get("policy_name") or cc.fail("openbao.policy_name is required") - target = REPO_DIR / "openbao/policies" / f"{policy_name}.hcl" - policy_text = cc.resolve_repo_path(args.policy).read_text() - cc.reject_secret_text(policy_text, "policy file") - if target.exists() and target.read_text() != policy_text: - cc.fail(f"{target.relative_to(REPO_DIR)} exists with different content") - target.write_text(policy_text) - openbao["policy_file"] = str(target.relative_to(REPO_DIR)) - destination = directory / f"{ccr_id}-{args.slug}.yaml" - cc.dump_yaml(destination, ccr) - violations = review_standard(destination, cc, Path(args.profile)) - return destination, violations - - -def command_submit(args: argparse.Namespace, cc: Any) -> int: - destination, violations = submit(args, cc) - ccr = cc.load_yaml(destination) - print(f"[OK] filed {ccr['id']} -> {destination.relative_to(REPO_DIR)}") - for item in violations: - print(f"[NEEDS-PLATFORM] {item}") - if args.commit or args.push: - branch = f"ccr/{destination.stem}" - git("switch", "-c", branch) - paths = [str(destination.relative_to(REPO_DIR))] - if ccr["openbao"].get("policy_file"): - paths.append(ccr["openbao"]["policy_file"]) - git("add", *paths) - git("commit", "-m", f"File {ccr['id']}: {ccr.get('title')}", "--", *paths) - if args.push: - git("push", "-u", "origin", branch) - if violations and args.notify: - notify_platform(ccr, violations, args.state_hub_url) - return 0 - - -def add_parsers(sub: Any, cc: Any, state_hub_url: str) -> None: - review = sub.add_parser( - "review-standard", - help="Check a proposed CCR against the standard lane profile; record the platform-owner review", - ) - review.add_argument("ref") - review.add_argument("--profile", default=str(DEFAULT_PROFILE)) - review.add_argument("--check", action="store_true", help="report only; do not write the review") - review.add_argument("--notify", action="store_true", help="message railiance-platform when out of profile") - review.add_argument("--state-hub-url", default=state_hub_url) - review.set_defaults(func=lambda args: command_review_standard(args, cc)) - - check_ids = sub.add_parser("check-ids", help="Fail on duplicate CCR ids or id/file name mismatch") - check_ids.set_defaults(func=lambda args: command_check_ids(args, cc)) - - check_branch = sub.add_parser( - "check-branch", help="CI gate: a ccr/* branch adds exactly one in-profile CCR and its policy" - ) - check_branch.add_argument("--branch", required=True, help="ccr/- or refs/heads/ccr/...") - check_branch.add_argument("--main-root", required=True, help="tree of main to compare against") - check_branch.set_defaults(func=lambda args: command_check_branch(args, cc)) - - decision_p = sub.add_parser( - "request-decision", help="Open the founder State Hub decision for a platform-reviewed proposed CCR" - ) - decision_p.add_argument("ref") - decision_p.add_argument("--state-hub-url", default=state_hub_url) - decision_p.set_defaults(func=lambda args: command_request_decision(args, cc)) - - render_policy = sub.add_parser( - "render-policy", help="Print the standard-lane policy HCL for a KV path (what review-standard expects)" - ) - render_policy.add_argument("--kv-path", required=True) - render_policy.add_argument("--profile", default=str(DEFAULT_PROFILE)) - render_policy.set_defaults(func=lambda args: command_render_policy(args, cc)) - - submit_p = sub.add_parser( - "submit", - help="File a client CCR draft under the next CCR id and run the standard review", - ) - submit_p.add_argument("--ccr", required=True, help="client CCR draft (non-secret YAML)") - submit_p.add_argument("--policy", help="client policy HCL; copied to openbao/policies/.hcl") - submit_p.add_argument("--slug", required=True) - submit_p.add_argument("--requester", required=True) - submit_p.add_argument("--profile", default=str(DEFAULT_PROFILE)) - submit_p.add_argument("--commit", action="store_true", help="commit on a new ccr/- branch") - submit_p.add_argument("--push", action="store_true", help="commit and push the ccr/* branch") - submit_p.add_argument("--notify", action="store_true") - submit_p.add_argument("--state-hub-url", default=state_hub_url) - submit_p.set_defaults(func=lambda args: command_submit(args, cc)) diff --git a/scripts/credential-change.py b/scripts/credential-change.py index 92cec1d..1fe9c85 100755 --- a/scripts/credential-change.py +++ b/scripts/credential-change.py @@ -253,8 +253,6 @@ def validate_workload_kv_read(ccr: dict[str, Any], errors: list[str], warnings: errors.append("openbao.token_self_lifecycle must be boolean") if "token_revoke_self" in openbao and not isinstance(openbao["token_revoke_self"], bool): errors.append("openbao.token_revoke_self must be boolean") - if "token_capabilities_self" in openbao and not isinstance(openbao["token_capabilities_self"], bool): - errors.append("openbao.token_capabilities_self must be boolean") if "kv_metadata" in openbao: validate_kv_metadata(openbao["kv_metadata"], errors) fields = [str(field) for field in require_list(openbao.get("fields"), "openbao.fields", errors)] @@ -518,12 +516,6 @@ def validate_ccr(path: Path) -> tuple[dict[str, Any], list[str], list[str]]: owner = importlib.util.module_from_spec(spec) spec.loader.exec_module(owner) errors.extend(owner.validate_contract(ccr)) - elif request_type == "attended-service-credential-delivery": - errors.extend(attended_service_module().validate_contract(ccr)) - elif request_type == "workload-kv-write-next-version": - errors.extend(write_next_version_module().validate_contract(ccr)) - elif request_type in LANE_MODULES: - errors.extend(lane_module(ccr).validate(ccr, REPO_DIR)) else: errors.append("unsupported request_type") return ccr, errors, warnings @@ -535,14 +527,14 @@ def render_summary(ccr: dict[str, Any], warnings: list[str]) -> str: frontdoor = ccr["access_frontdoor"] risk = ccr["risk"] verification = ccr["verification"] - fields = ", ".join(openbao.get("fields", [])) + fields = ", ".join(openbao["fields"]) lines = [ f"Request: {ccr['title']}", f"CCR: {ccr['id']} ({ccr['status']})", f"Type: {ccr['request_type']}", f"Target: {ccr['target']['tenant']}/{ccr['target']['workload']} ({ccr['target']['environment']})", "Mount/path/field:", - f" {openbao.get('kv_path', '-')}", + f" {openbao['kv_path']}", f" {fields}", "Policy:", f" {openbao.get('policy_name', 'none (attended host delivery)')}", @@ -627,11 +619,6 @@ def generated_policy_hcl(ccr: dict[str, Any]) -> str: '\npath "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n') elif openbao.get("token_revoke_self", False): body += '\npath "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n' - if openbao.get("token_capabilities_self", False): - # NK-WP-0051-T06: the caller proves its own reach. capabilities-self is a - # POST, so the grant is "update"; it reveals capabilities, never values. - body += ('\npath "sys/capabilities-self" {\n' - ' capabilities = ["update"]\n}\n') return body @@ -728,10 +715,6 @@ def auth_payload(ccr: dict[str, Any]) -> dict[str, Any]: def render_plan(ccr: dict[str, Any]) -> str: if ccr.get("request_type") == "attended-host-key-delivery": return "Use docs/credential-lane-designs/railiance-clock-host-delivery.md through the attended owner procedure. No workload policy or auth role is created." - if ccr.get("request_type") == "attended-service-credential-delivery": - return attended_service_module().procedure_note(ccr) - if ccr.get("request_type") == "workload-kv-write-next-version": - return write_next_version_module().procedure_note(ccr) openbao = ccr["openbao"] auth = openbao["auth"] payload = auth_payload(ccr) @@ -763,10 +746,6 @@ def render_plan(ccr: dict[str, Any]) -> str: def render_operator_commands(ccr: dict[str, Any]) -> str: if ccr.get("request_type") == "attended-host-key-delivery": return "Use docs/credential-lane-designs/railiance-clock-host-delivery.md through the attended owner procedure. No workload policy or auth role is created." - if ccr.get("request_type") == "attended-service-credential-delivery": - return attended_service_module().procedure_note(ccr) - if ccr.get("request_type") == "workload-kv-write-next-version": - return write_next_version_module().procedure_note(ccr) openbao = ccr["openbao"] auth = openbao["auth"] auth_path = f"auth/{auth['mount']}/role/{auth['role']}" @@ -825,58 +804,6 @@ def string_values(value: Any) -> list[str]: return [] -def service_jwt_lane(): - """Lazy-load scripts/service_jwt_lane.py (request_type service-jwt-login).""" - import importlib.util - spec = importlib.util.spec_from_file_location("service_jwt_lane", REPO_DIR / "scripts/service_jwt_lane.py") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def is_service_jwt(ccr: dict[str, Any]) -> bool: - return ccr.get("request_type") == "service-jwt-login" - - -# Request types whose delegated applier lives in its own reviewed module. -LANE_MODULES = { - "service-jwt-login": "service_jwt_lane", - "workload-kv-operator-matrix": "operator_matrix_lane", - "custody-generate-grant": "broker_grant_lane", - "broker-issuer-binding": "issuer_binding_lane", -} - - -def attended_service_module(): - """Owner contract for attended-service-credential-delivery (no workload access).""" - import importlib.util - spec = importlib.util.spec_from_file_location("attended_service_delivery", REPO_DIR / "scripts/attended_service_delivery.py") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def write_next_version_module(): - """Owner contract for workload-kv-write-next-version (bounded executor, NK-WP-0056).""" - import importlib.util - spec = importlib.util.spec_from_file_location("workload_kv_write_next_version", REPO_DIR / "scripts/workload_kv_write_next_version.py") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -def lane_module(ccr: dict[str, Any]): - """Lazy-load the applier module for a lane request type, or None.""" - name = LANE_MODULES.get(ccr.get("request_type")) - if name is None: - return None - import importlib.util - spec = importlib.util.spec_from_file_location(name, REPO_DIR / f"scripts/{name}.py") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - def applier_policy_violations(ccr: dict[str, Any]) -> list[str]: violations: list[str] = [] if ccr.get("request_type") != "workload-kv-read": @@ -896,30 +823,20 @@ def applier_policy_violations(ccr: dict[str, Any]) -> list[str]: # WARDEN-WP-0028: platform fleet lanes stay under platform/workloads/; # client/tenant commercial secrets live on mount tenants/ (exact-path policies). - # WARDEN-WP-0043: supplier account credentials live at - # platform/suppliers// (exactly two segments). allowed_mount_prefixes = { - "platform": ("platform/workloads/", "platform/suppliers/"), - "tenants": ("tenants/",), + "platform": "platform/workloads/", + "tenants": "tenants/", } if mount not in allowed_mount_prefixes: violations.append( f"openbao.mount must be one of {sorted(allowed_mount_prefixes)}, got {mount}" ) else: - prefixes = allowed_mount_prefixes[mount] - if not kv_path.startswith(prefixes): + prefix = allowed_mount_prefixes[mount] + if not kv_path.startswith(prefix): violations.append( - f"openbao.kv_path for mount {mount!r} must stay under " - + " or ".join(prefixes) + f"openbao.kv_path for mount {mount!r} must stay under {prefix}" ) - elif kv_path.startswith("platform/suppliers/"): - segments = kv_path[len("platform/suppliers/"):].split("/") - if len(segments) != 2 or not all(segments): - violations.append( - "openbao.kv_path under platform/suppliers/ must be exactly " - "/" - ) if any(fragment in kv_path for fragment in ("*", "..", "//")): violations.append("openbao.kv_path must not contain wildcard, parent, or empty segments") if "/data/" in kv_path or "/metadata/" in kv_path: @@ -1004,9 +921,6 @@ def applier_policy_violations(ccr: dict[str, Any]) -> list[str]: def applier_readiness_blockers(ccr: dict[str, Any]) -> list[str]: - lane = lane_module(ccr) - if lane is not None: - return lane.readiness_blockers(ccr, REPO_DIR, APPLIER_DRY_RUN_ALLOWED_STATUSES) blockers: list[str] = [] status = ccr.get("status") if status not in APPLIER_DRY_RUN_ALLOWED_STATUSES: @@ -1021,9 +935,6 @@ def applier_readiness_blockers(ccr: dict[str, Any]) -> list[str]: def applier_dry_run_payload(ccr: dict[str, Any], warnings: list[str]) -> dict[str, Any]: - lane = lane_module(ccr) - if lane is not None: - return lane.dry_run_payload(ccr, REPO_DIR, warnings) openbao = ccr["openbao"] auth = openbao["auth"] auth_path = f"auth/{auth['mount']}/role/{auth['role']}" @@ -1112,9 +1023,6 @@ def applier_confirmation_phrase(ccr: dict[str, Any]) -> str: def delegated_apply_details(ccr: dict[str, Any], actor: str) -> list[str]: - lane = lane_module(ccr) - if lane is not None: - return lane.apply_details(ccr, actor) openbao = ccr["openbao"] auth = openbao["auth"] return [ @@ -1145,10 +1053,6 @@ def render_applier_apply_plan(ccr: dict[str, Any], warnings: list[str]) -> str: def runbook_readiness_blockers(ccr: dict[str, Any]) -> list[str]: if ccr.get("request_type") == "attended-host-key-delivery": return ["use the attended owner procedure in docs/credential-lane-designs/railiance-clock-host-delivery.md"] - if ccr.get("request_type") == "attended-service-credential-delivery": - return ["use the attended procedure: " + attended_service_module().procedure_note(ccr)] - if ccr.get("request_type") == "workload-kv-write-next-version": - return ["use the attended procedure: " + write_next_version_module().procedure_note(ccr)] blockers: list[str] = [] status = ccr.get("status") if status not in RUNBOOK_ALLOWED_STATUSES: @@ -1278,14 +1182,7 @@ def render_runbook(payload: dict[str, Any]) -> str: return "\n".join(lines) -def run_bao_metadata_apply(ccr: dict[str, Any], bao_bin: str, quiet: bool = False) -> None: - lane = lane_module(ccr) - if lane is not None: - try: - lane.run_apply(ccr, REPO_DIR, bao_bin, quiet=quiet) - except (lane.BaoError, subprocess.CalledProcessError) as exc: - fail(str(exc)) - return +def run_bao_metadata_apply(ccr: dict[str, Any], bao_bin: str) -> None: openbao = ccr["openbao"] auth = openbao["auth"] auth_path = f"auth/{auth['mount']}/role/{auth['role']}" @@ -1299,14 +1196,7 @@ def run_bao_metadata_apply(ccr: dict[str, Any], bao_bin: str, quiet: bool = Fals [bao_bin, "write", auth_path, f"@{role_path}"], ] for command in commands: - if not quiet: - subprocess.run(command, cwd=REPO_DIR, check=True) - continue - # Silent mode for attended wrappers that refuse any output. bao's own - # messages are discarded; a failure reports only the step and exit code. - result = subprocess.run(command, cwd=REPO_DIR, capture_output=True) - if result.returncode != 0: - fail(f"bao {command[1]} failed with exit code {result.returncode}") + subprocess.run(command, cwd=REPO_DIR, check=True) finally: try: role_path.unlink() @@ -1373,19 +1263,10 @@ def record_evidence_state_hub( ccr: dict[str, Any], base_url: str, actor: str, kind: str, result: str, details: list[str] ) -> dict[str, Any]: openbao = ccr["openbao"] - # Lane request types differ: workload-kv-read has kv_path, auth lanes have - # auth.mount, custody-generate-grant has kv_prefix. Never fail after an apply. - auth_mount = (openbao.get("auth") or {}).get("mount") - target = ( - openbao.get("kv_path") - or openbao.get("kv_prefix") - or openbao.get("campaign_path") - or (f"auth/{auth_mount}" if auth_mount else "unknown") - ) summary = ( f"CCR {ccr['id']} evidence {kind}/{result} by {actor}: " - f"status={ccr['status']} path={target} " - f"policy={openbao.get('policy_name', 'unknown')}; " + f"status={ccr['status']} path={openbao['kv_path']} " + f"policy={openbao['policy_name']}; " + "; ".join(details) ) return state_hub_post_json( @@ -1563,7 +1444,7 @@ def record_lifecycle_state_hub( summary = ( f"CCR {ccr['id']} lifecycle {action} by {actor}: " f"status={ccr['status']} readiness={frontdoor.get('readiness')} " - f"resolvable={frontdoor.get('resolvable') is True} path={openbao.get('kv_path', '-')} " + f"resolvable={frontdoor.get('resolvable') is True} path={openbao['kv_path']} " f"policy={openbao['policy_name']}; reason={reason}; " + "; ".join(details) ) @@ -1613,8 +1494,7 @@ def inventory_ccr_from_args(args: argparse.Namespace) -> dict[str, Any]: allowed_redirect_uris = list(getattr(args, "redirect_uri", None) or []) if args.auth_method == "oidc" and not allowed_redirect_uris: allowed_redirect_uris = [ - # bao.coulomb.social was retired on 2026-09-15; the UI goes through the operator tunnel. - "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback", + "https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback", "http://localhost:8250/oidc/callback", "http://127.0.0.1:8250/oidc/callback", ] @@ -1799,9 +1679,9 @@ def status_payload(ccr: dict[str, Any], warnings: list[str]) -> dict[str, Any]: "frontdoor_blockers": frontdoor_blocked_by, "warnings": warnings, "openbao": { - "mount": openbao.get("mount"), - "kv_path": openbao.get("kv_path"), - "fields": openbao.get("fields", []), + "mount": openbao["mount"], + "kv_path": openbao["kv_path"], + "fields": openbao["fields"], "policy_name": openbao.get("policy_name"), "auth_mount": auth.get("mount"), "auth_method": auth.get("method"), @@ -1957,7 +1837,7 @@ def decision_template_context(ccr: dict[str, Any]) -> dict[str, str]: state_hub = ccr.get("state_hub", {}) return { "id": ccr["id"], - "kv_path": openbao.get("kv_path") or f"auth/{auth['mount']}", + "kv_path": openbao["kv_path"], "policy_name": openbao.get("policy_name"), "auth_role_path": f"auth/{auth['mount']}/role/{auth['role']}", "decision_link": state_hub.get("decision_api_url") @@ -1972,7 +1852,7 @@ def decision_templates(ccr: dict[str, Any] | None = None) -> dict[str, str]: else: context = { "id": "", - "kv_path": ", /> or /...>", + "kv_path": " or /...>", "policy_name": "", "auth_role_path": "auth//role/", "decision_link": "", @@ -2173,7 +2053,7 @@ def command_applier_apply(args: argparse.Namespace) -> int: phrase = args.confirm or input("Type delegated apply confirmation phrase: ") if phrase != expected: fail(f"confirmation phrase mismatch; expected {expected!r}") - run_bao_metadata_apply(ccr, args.bao_bin, quiet=args.quiet) + run_bao_metadata_apply(ccr, args.bao_bin) set_status = "applied" if ccr.get("status") == "approved" else None details = delegated_apply_details(ccr, args.actor) ccr = append_evidence( @@ -2184,8 +2064,7 @@ def command_applier_apply(args: argparse.Namespace) -> int: details, set_status=set_status, ) - if not args.quiet: - print(f"[OK] {path.name} delegated metadata apply recorded") + print(f"[OK] {path.name} delegated metadata apply recorded") if args.record_state_hub: event = record_evidence_state_hub( ccr, @@ -2195,8 +2074,7 @@ def command_applier_apply(args: argparse.Namespace) -> int: "passed", details, ) - if not args.quiet: - print(f"[OK] State Hub progress event {event.get('id', '')}") + print(f"[OK] State Hub progress event {event.get('id', '')}") return 0 @@ -2271,7 +2149,7 @@ def command_decision(args: argparse.Namespace, status: str) -> int: { "summary": ( f"CCR {ccr['id']} decision {status} by {args.reviewer}: " - f"path={openbao.get('kv_path', '-')} policy={openbao['policy_name']} " + f"path={openbao['kv_path']} policy={openbao['policy_name']} " f"fields={','.join(openbao['fields'])} " f"auth_role=auth/{auth['mount']}/role/{auth['role']}; " f"{args.comment}" @@ -2418,9 +2296,6 @@ def build_parser() -> argparse.ArgumentParser: scaffold.add_argument("--catalog-manifest") scaffold.set_defaults(func=lambda args: command_scaffold(args, generated_policy_hcl, auth_payload)) - from ccr_intake import add_parsers as add_intake_parsers - add_intake_parsers(sub, sys.modules[__name__], os.environ.get("STATE_HUB_URL", "http://127.0.0.1:8000")) - from provisioning_window import command_window window = sub.add_parser("window-checklist", help="Render an offline attended-window preparation checklist; never execute") window.add_argument("--window-file", required=True) diff --git a/scripts/credential.py b/scripts/credential.py index 7c94d29..0886349 100755 --- a/scripts/credential.py +++ b/scripts/credential.py @@ -1177,12 +1177,6 @@ def build_parser() -> argparse.ArgumentParser: def main() -> int: guard_verbose_env() - if sys.argv[1:2] == ["generate"]: - # RPF-WP-0054: generate-in-custody runs on the caller's own session and - # must stay silent, so it bypasses the lease runner and issuer prompt. - sys.path.insert(0, str(Path(__file__).resolve().parent)) - from keycape_client_secret_generate import main as generate_main - return generate_main(sys.argv[2:]) parser = build_parser() args = parser.parse_args() args.lease_dir = resolve_repo_path(args.lease_dir) diff --git a/scripts/email_connect_smtp_delivery.py b/scripts/email_connect_smtp_delivery.py deleted file mode 100644 index 7101d17..0000000 --- a/scripts/email_connect_smtp_delivery.py +++ /dev/null @@ -1,235 +0,0 @@ -#!/usr/bin/env python3 -"""Fixed, silent email-connect platform-mail reader admission. - -Default is offline proposal output. Execution requires the approved CCR, native -binding confirmation and reviewed source digest inside the attended owner lane. -It neither creates Kubernetes resources nor deploys the application sender. -""" -from __future__ import annotations - -import argparse -import copy -import hashlib -import importlib.util -import json -import os -from pathlib import Path -import re -import sys -from datetime import datetime, timezone - -ROOT = Path(__file__).resolve().parents[1] -OPENBAO_CLUSTER_ID = 'fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf' -CCR = ROOT / 'credential-change-requests/CCR-2026-0056-email-connect-platform-smtp.yaml' -POLICY_FILE = ROOT / 'openbao/policies/email-connect-platform-smtp-eso.hcl' -LANE = dict(service_account='email-connect-platform-smtp-eso', namespace='email-connect', - role='email-connect-platform-smtp-eso', policy='email-connect-platform-smtp-eso', - kv_path='platform/data/workloads/railiance-telemetry/smtp') -SOURCES = ('scripts/email_connect_smtp_delivery.py', 'scripts/telemetry_smtp_delivery.py', - 'scripts/state_hub_preflight_lane.py', 'scripts/repair_eso_kubernetes_auth.py', - 'scripts/telemetry_smtp_entry.py', 'scripts/credential-change.py', - 'openbao/policies/email-connect-platform-smtp-eso.hcl') -PROTECTED = ('sys/policies/acl/agent-high-risk-boundary', - 'sys/policies/acl/telemetry-smtp-eso', 'auth/kubernetes/role/telemetry-smtp-eso', - 'sys/policies/acl/authelia-smtp-eso', 'auth/kubernetes/role/authelia-smtp-eso') - - -class Refused(Exception): - pass - - -def require(ok, reason): - if not ok: - raise Refused(reason) - - -def module(name, path): - spec = importlib.util.spec_from_file_location(name, path) - result = importlib.util.module_from_spec(spec) - spec.loader.exec_module(result) - return result - - -def lane_module(): - sys.path.insert(0, str(ROOT / 'scripts')) - lane = module('email_connect_smtp_fixed_lane', ROOT / 'scripts/telemetry_smtp_delivery.py') - lane.LANE = dict(LANE) - lane.POLICY = POLICY_FILE.read_text() - return lane - - -def prepare(): - if not __debug__: - raise Refused('assertions_must_be_enabled') - validator = module('email_connect_smtp_ccr', ROOT / 'scripts/credential-change.py') - ccr, errors, _ = validator.validate_ccr(CCR) - require(not errors, 'invalid_ccr') - lane = lane_module() - expected_auth = dict(method='kubernetes', mount='kubernetes', role=LANE['role'], - bound_claims={'service_account_names': [LANE['service_account']], - 'service_account_namespaces': [LANE['namespace']]}, - policies=[LANE['policy']], ttl='15m', audience='openbao', - token_max_ttl='15m', token_explicit_max_ttl='15m', - token_no_default_policy=True) - actual = dict(ccr['openbao']['auth']); actual.pop('bound_claims_confirmed', None) - require(actual == expected_auth, 'auth_contract_drift') - expected_policy = ('path "'+LANE['kv_path']+'" { capabilities = ["read"] }\n' - 'path "auth/token/lookup-self" { capabilities = ["read"] }\n' - 'path "auth/token/revoke-self" { capabilities = ["update"] }\n') - require(lane.POLICY == expected_policy, 'reader_policy_drift') - o = ccr['openbao'] - require(o['kv_path'] == 'platform/workloads/railiance-telemetry/smtp' - and o['mount'] == 'platform' and o['fields'] == ['SMTP_PASSWORD'] - and o['metadata_read'] is False and o['token_self_lifecycle'] is True - and o['policy_name'] == LANE['policy'] - and o['policy_file'] == str(POLICY_FILE.relative_to(ROOT)), 'path_contract_drift') - delivery = ccr['delivery'] - require(all(delivery.get(k) == v for k, v in { - 'namespace': 'email-connect', 'service_account': LANE['service_account'], - 'secret_store': 'openbao-email-connect-platform-smtp', - 'external_secret': 'email-connect-platform-smtp', - 'target_secret': 'email-connect-platform-smtp', 'target_key': 'password', - }.items()), 'projection_contract_drift') - scope = copy.deepcopy(ccr) - for key in ('status', 'review', 'updated', 'state_hub'): - scope.pop(key, None) - scope['openbao']['auth'].pop('bound_claims_confirmed', None) - scope['access_frontdoor'].pop('readiness', None) - proposal = {'schema': 'email-connect.smtp-admission-proposal.v1', 'scope': scope, - 'openbao_cluster_id': OPENBAO_CLUSTER_ID, - 'role': lane.role_payload(LANE), 'policy': lane.POLICY, - 'source_sha256': {p: hashlib.sha256((ROOT / p).read_bytes()).hexdigest() - for p in SOURCES}} - raw = json.dumps(proposal, sort_keys=True, separators=(',', ':')).encode() - return ccr, proposal, 'sha256:' + hashlib.sha256(raw).hexdigest() - - -def execution_guard(ccr, expected, actual): - require(expected == actual, 'reviewed_digest_required') - require(ccr['status'] == 'approved', 'approved_ccr_required') - require(ccr['openbao']['auth'].get('bound_claims_confirmed') is True, - 'native_binding_confirmation_required') - - -def protected_state(lane): - return {p: lane.data(lane.bao(['read', '-format=json', p]))['data'] for p in PROTECTED} - - -def admit(receipt, checkpoint): - lane = lane_module() - status = lane.data(lane.bao(['status', '-format=json'])) - require(status.get('cluster_id') == OPENBAO_CLUSTER_ID - and status.get('initialized') is True and status.get('sealed') is False, - 'openbao_instance_mismatch') - ident = lane.data(lane.bao(['token', 'lookup', '-format=json']))['data']['policies'] - require('platform-admin' in ident and 'root' not in ident, 'attended_platform_admin_required') - before = protected_state(lane) - boundary = before[PROTECTED[0]]['policy'] - for path in (LANE['kv_path'], LANE['kv_path'].replace('/data/', '/metadata/', 1)): - require(re.search(r'path\s+"'+re.escape(path)+r'"\s*\{\s*capabilities\s*=\s*\["deny"\]\s*\}', boundary), - 'existing_agent_boundary_required') - # Check both existing targets before the shared lane can create either one. - for path, verify in ( - ('sys/policies/acl/'+LANE['policy'], lambda v: require(v['policy'] == lane.POLICY, 'reader_policy_drift')), - ('auth/kubernetes/role/'+LANE['role'], lambda v: lane.check_role(v, LANE)), - ): - old = lane.bao(['read', '-format=json', path], allow_failure=True) - if old.returncode == 0: - verify(lane.data(old)['data']) - else: - require(lane.missing(old), 'metadata_absence_unproven') - mailbox_before = lane.data(lane.bao(['read', '-format=json', LANE['kv_path']]))['data'] - lane.validate_entry(mailbox_before) - receipt.update(phase='admitting', mutation_outcome='unconfirmed') - checkpoint(receipt) - lane.run(receipt) - require(protected_state(lane) == before, 'existing_consumer_metadata_changed') - mailbox_after = lane.data(lane.bao(['read', '-format=json', LANE['kv_path']]))['data'] - require(mailbox_after == mailbox_before, 'mailbox_changed_during_admission') - receipt.update(status='reader_admitted_verified', phase='completed', mutation_outcome='verified', - existing_consumers_preserved=True, agent_boundary_preserved=True, - mailbox_unchanged=True, kubernetes_resources_installed=False, - native_consumer_auth_verified=False, projection_ready=False, - application_sender_changed=False) - checkpoint(receipt) - - -def execute(ccr, proposal, digest, expected, path, runner=admit): - execution_guard(ccr, expected, digest) - parent = path.parent - info = parent.lstat() - require(parent.is_dir() and not parent.is_symlink() and info.st_uid == os.getuid() - and not info.st_mode & 0o077, 'private_receipt_parent_required') - fd = os.open(path, os.O_WRONLY | os.O_APPEND | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - # This unique receipt is durable before any owner operation. An incomplete - # receipt requires inspection; never overwrite it or automatically retry. - receipt = {'schema': 'email-connect.smtp-admission.v1', 'status': 'started', - 'phase': 'preflight', 'proposal_digest': digest, - 'source_sha256': proposal['source_sha256'], - 'started_at': datetime.now(timezone.utc).isoformat(), 'email_sent': False, - 'password_modified': False, 'mutation_outcome': 'not_started'} - def checkpoint(value): - raw = (json.dumps(value, sort_keys=True, separators=(',', ':'))+'\n').encode() - while raw: - written = os.write(fd, raw) - if written <= 0: - raise OSError('receipt_write_failed') - raw = raw[written:] - os.fsync(fd) - try: - checkpoint(receipt) - directory = os.open(parent, os.O_RDONLY | os.O_DIRECTORY) - try: - os.fsync(directory) - finally: - os.close(directory) - runner(receipt, checkpoint) - require(receipt.get('status') == 'reader_admitted_verified', 'success_receipt_required') - result = 0 - except Exception as error: - # Exceptions may carry provider text. Only this wrapper's fixed guards - # are permitted diagnostics; dependencies and arbitrary errors stay opaque. - safe = {'openbao_instance_mismatch', 'attended_platform_admin_required', 'existing_agent_boundary_required', - 'reader_policy_drift', 'metadata_absence_unproven', - 'existing_consumer_metadata_changed', 'mailbox_changed_during_admission', - 'success_receipt_required'} - receipt.update(status='failed_requires_inspection', reason=(str(error) - if isinstance(error, Refused) and str(error) in safe - else 'contained_admission_failure')) - result = 1 - finally: - time_key = ('completed_at' if receipt['status'] in - {'reader_admitted_verified', 'failed_requires_inspection'} else 'interrupted_at') - receipt[time_key] = datetime.now(timezone.utc).isoformat() - checkpoint(receipt) - os.close(fd) - return result - - -def main(argv=None): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--execute', action='store_true') - parser.add_argument('--expect-digest') - parser.add_argument('--receipt', type=Path) - args = parser.parse_args(argv) - ccr, proposal, digest = prepare() - if not args.execute: - print(json.dumps({'status': 'prepared', 'proposal_digest': digest, - 'ccr_status': ccr['status'], - 'binding_confirmed': ccr['openbao']['auth']['bound_claims_confirmed'], - 'proposal': proposal}, indent=2)) - return 0 - require(args.receipt is not None, 'private_receipt_required') - return execute(ccr, proposal, digest, args.expect_digest, args.receipt) - - -if __name__ == '__main__': - if '--execute' in sys.argv: - with open(os.devnull, 'w') as sink: - os.dup2(sink.fileno(), 1); os.dup2(sink.fileno(), 2) - try: - code = main() - except Exception: - code = 1 - sys.exit(code) - sys.exit(main()) diff --git a/scripts/email_connect_smtp_foundation.py b/scripts/email_connect_smtp_foundation.py deleted file mode 100644 index 2feb2f2..0000000 --- a/scripts/email_connect_smtp_foundation.py +++ /dev/null @@ -1,314 +0,0 @@ -#!/usr/bin/env python3 -"""Silent attended CCR-2026-0056 foundation, admission and projection. - -Default output is an offline proposal. Never changes the application sender. -An incomplete attempt requires inspection, not an automatic retry or rollback. -""" -from __future__ import annotations - -import argparse -import hashlib -import http.client -import json -import os -from pathlib import Path -import socket -import subprocess -import sys -import tempfile -import time -from datetime import datetime, timezone - -import email_connect_smtp_delivery as admission -from state_hub_preflight_lane import command, data, bao, capabilities - -ROOT = admission.ROOT -BUNDLE = ROOT / 'openbao/email-connect-platform-smtp/foundation.json' -NS = 'email-connect' -SA = admission.LANE['service_account'] -KUBECONFIG = '/home/worsch/.kube/config-railiance01' -require = admission.require - - -def digest(value): - return 'sha256:' + hashlib.sha256(json.dumps(value, sort_keys=True, separators=(',', ':')).encode()).hexdigest() - - -def prepare(): - ccr, owner, owner_digest = admission.prepare() - bundle = json.loads(BUNDLE.read_text()) - proposal = dict(schema='platform.email-connect-smtp-foundation-proposal.v1', - foundation=bundle, admission_digest=owner_digest, - kubeconfig=KUBECONFIG, - controller_sha256=hashlib.sha256(Path(__file__).read_bytes()).hexdigest()) - return ccr, proposal, digest(proposal) - - -class Native: - def __init__(self): - self.kube = ['kubectl', '--kubeconfig', KUBECONFIG] - - def get(self, kind, name, namespace=NS): - args = self.kube + ['get', kind, name, '--ignore-not-found', '-o', 'json'] - if namespace: - args += ['-n', namespace] - result = command(args) - return data(result) if result.stdout.strip() else None - - def target_metadata(self, name): - # Kubernetes content negotiation excludes Secret values at the server. - # Use a private local socket, one fixed GET path and no full-object fallback. - require(name == 'email-connect-platform-smtp', 'target_secret_scope_drift') - path = '/api/v1/namespaces/email-connect/secrets/'+name - with tempfile.TemporaryDirectory(prefix='email-connect-metadata-') as directory: - sockpath = str(Path(directory)/'api.sock') - proc = subprocess.Popen(self.kube + ['proxy', '--unix-socket='+sockpath, - '--accept-paths=^'+path+'$', '--reject-methods=^(POST|PUT|PATCH|DELETE)$'], - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - conn = None - try: - for _ in range(50): - if Path(sockpath).exists(): - break - require(proc.poll() is None, 'metadata_proxy_failed') - time.sleep(0.1) - sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - sock.settimeout(10) - try: - sock.connect(sockpath) - except BaseException: - sock.close() - raise - conn = http.client.HTTPConnection('localhost', timeout=10) - conn.sock = sock - conn.request('GET', path, headers={'Accept': 'application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1'}) - response = conn.getresponse() - if response.status == 404: - return None - require(response.status == 200, 'metadata_query_failed') - value = json.load(response) - require(value.get('kind') == 'PartialObjectMetadata' and - value.get('apiVersion') == 'meta.k8s.io/v1' and 'data' not in value, - 'metadata_only_response_required') - return value['metadata'] - finally: - if conn: - conn.close() - proc.terminate() - try: - proc.wait(timeout=5) - except subprocess.TimeoutExpired: - proc.kill(); proc.wait(timeout=5) - - def create(self, resource): - return data(command(self.kube + ['create', '-f', '-', '-o', 'json'], payload=resource)) - - def jwt(self, service_account=SA, audience='openbao'): - return command(self.kube + ['-n', NS, 'create', 'token', service_account, - '--audience='+audience, '--duration=10m']).stdout.decode().strip() - - def token_review(self, jwt): - return data(command(self.kube + ['create', '-f', '-', '-o', 'json'], payload={ - 'apiVersion': 'authentication.k8s.io/v1', 'kind': 'TokenReview', - 'spec': {'token': jwt, 'audiences': ['openbao']}}))['status'] - - def wait(self, resource): - command(self.kube + ['-n', NS, 'wait', '--for=condition=Ready', - resource['kind']+'/'+resource['metadata']['name'], '--timeout=45s']) - - -def exact_resource(actual, wanted): - require(actual is not None, 'resource_missing') - require(actual['apiVersion'] == wanted['apiVersion'] and actual['kind'] == wanted['kind'], 'resource_drift') - require(all(actual['metadata'].get(k) == v for k, v in wanted['metadata'].items()), 'resource_identity_drift') - if wanted['kind'] == 'ServiceAccount': - require(actual.get('automountServiceAccountToken') is False - and not actual.get('secrets') and not actual.get('imagePullSecrets'), 'service_account_drift') - else: - require(actual['spec'] == wanted['spec'], 'resource_spec_drift') - require(not actual['metadata'].get('deletionTimestamp'), 'resource_deleting') - - -def protected_application(native, bundle): - deployment = native.get('deployment', 'email-connect') - require(deployment['metadata']['uid'] == bundle['deployment_uid'] - and deployment['metadata']['generation'] == bundle['deployment_generation'], 'application_changed') - values = [] - for kind, name in [('deployment', 'email-connect'), ('configmap', 'email-connect-config'), - ('externalsecret', 'email-connect-runtime')]: - obj = deployment if kind == 'deployment' else native.get(kind, name) - values.append({'uid': obj['metadata']['uid'], 'spec': obj.get('spec'), 'data': obj.get('data')}) - return digest(values) - - -def verify_login(native): - def login(sa=SA, audience='openbao', negative=False): - result = bao(['write', '-format=json', 'auth/kubernetes/login', '-'], - payload={'role': SA, 'jwt': native.jwt(sa, audience)}, allow_failure=negative) - if negative: - if result.returncode == 0: - revoke(data(result)['auth']['client_token']) - raise admission.Refused('negative_login_succeeded') - require(b'400' in result.stderr or b'403' in result.stderr, 'negative_login_inconclusive') - return None - return data(result)['auth'] - - def revoke(token): - bao(['write', '-f', 'auth/token/revoke-self'], token=token) - check = bao(['token', 'lookup', '-format=json'], token=token, allow_failure=True) - require(check.returncode != 0 and b'403' in check.stderr, 'revocation_unconfirmed') - - auth = login() - token = auth['client_token'] - try: - require(auth['token_policies'] == [SA] and auth.get('policies', [SA]) == [SA] - and 0 < auth['lease_duration'] <= 900, 'effective_policy_or_ttl_drift') - path = admission.LANE['kv_path'] - paths = [path, path.replace('/data/', '/metadata/', 1), path+'-unrelated', - 'auth/token/create', 'auth/token/lookup-self', 'auth/token/revoke-self'] - expected = [['read'], ['deny'], ['deny'], ['deny'], ['read'], ['update']] - require(capabilities(token, paths) == dict(zip(paths, expected)), 'capability_drift') - mailbox = data(bao(['read', '-format=json', path], token=token))['data'] - admission.lane_module().validate_entry(mailbox) - finally: - revoke(token) - login('default', negative=True) - login(audience='wrong-audience', negative=True) - return dict(native_consumer_auth_verified=True, exact_read_verified=True, - mutation_sibling_metadata_denied=True, bounded_ttl=True, - other_service_account_denied=True, wrong_audience_denied=True, - workload_token_revocation_verified=True) - - -def run(proposal, receipt, checkpoint, native=None): - native = native or Native() - bundle = proposal['foundation'] - status = data(bao(['status', '-format=json'])) - require(status.get('cluster_id') == admission.OPENBAO_CLUSTER_ID and - status.get('initialized') is True and status.get('sealed') is False, 'openbao_instance_mismatch') - policies = data(bao(['token', 'lookup', '-format=json']))['data']['policies'] - require('platform-admin' in policies and 'root' not in policies, 'attended_platform_admin_required') - for name, uid in [('kube-system', bundle['cluster_uid']), (NS, bundle['namespace_uid'])]: - obj = native.get('namespace', name, namespace=None) - require(obj and obj['metadata']['uid'] == uid and not obj['metadata'].get('deletionTimestamp'), - 'cluster_or_namespace_identity_mismatch') - before = protected_application(native, bundle) - resources = bundle['resources'] - require([x['kind'] for x in resources] == ['ServiceAccount', 'SecretStore', 'ExternalSecret'], 'foundation_contract_drift') - for x in resources: - require(native.get(x['kind'], x['metadata']['name']) is None, 'target_exists_requires_inspection') - target = resources[-1]['spec']['target']['name'] - require(native.target_metadata(target) is None, 'target_secret_exists_requires_inspection') - receipt.update(phase='creating_service_account', mutation_outcome='unconfirmed') - checkpoint(receipt) - account = native.create(resources[0]) - exact_resource(account, resources[0]) - review = native.token_review(native.jwt()) - require(review.get('authenticated') is True and review.get('audiences') == ['openbao'] - and review['user']['username'] == 'system:serviceaccount:'+NS+':'+SA - and review['user']['uid'] == account['metadata']['uid'], 'native_binding_unproven') - receipt.update(phase='binding_verified', service_account_uid=account['metadata']['uid'], - native_binding_verified=True) - checkpoint(receipt) - validator = admission.module('email_connect_foundation_ccr', ROOT/'scripts/credential-change.py') - validator.confirm_binding(admission.CCR, reviewer='attended-platform-operator', - comment='CCR-2026-0056 foundation: pinned cluster and namespace, exact new service account and native audience TokenReview verified; receipt '+receipt['proposal_digest']) - ccr, _, owner_digest = admission.prepare() - admission.execution_guard(ccr, proposal['admission_digest'], owner_digest) - owner_receipt = {} - def owner_checkpoint(value): - receipt.update(phase='owner_admission', owner_admission=dict(value)) - checkpoint(receipt) - admission.admit(owner_receipt, owner_checkpoint) - receipt.update(phase='native_login_verification') - checkpoint(receipt) - receipt.update(verify_login(native)) - for wanted in resources[1:]: - receipt.update(phase='creating_'+wanted['kind'].lower()) - checkpoint(receipt) - created = native.create(wanted) - exact_resource(created, wanted) - native.wait(wanted) - live = native.get(wanted['kind'], wanted['metadata']['name']) - exact_resource(live, wanted) - require(live['metadata']['uid'] == created['metadata']['uid'] and - any(c.get('type') == 'Ready' and c.get('status') == 'True' - for c in live.get('status', {}).get('conditions', [])), 'projection_not_ready') - if wanted['kind'] == 'ExternalSecret': - require(bool(live['status'].get('refreshTime')), 'projection_refresh_unproven') - metadata = native.target_metadata(target) - require(metadata and any(o.get('uid') == live['metadata']['uid'] - and o.get('controller') is True for o in metadata.get('ownerReferences', [])), - 'projection_ownership_unproven') - receipt['projection_refresh_time'] = live['status']['refreshTime'] - require(protected_application(native, bundle) == before, 'application_changed') - receipt.update(status='foundation_admitted_projection_ready', phase='completed', - mutation_outcome='verified', projection_ready=True, application_sender_changed=False) - checkpoint(receipt) - - -def execute(ccr, proposal, actual, expected, path, runner=run): - require(actual == expected, 'reviewed_digest_required') - require(ccr['status'] == 'approved', 'approved_ccr_required') - info = path.parent.lstat() - require(path.is_absolute() and path.parent.is_dir() and not path.parent.is_symlink() - and info.st_uid == os.getuid() and not info.st_mode & 0o077, 'private_receipt_parent_required') - fd = os.open(path, os.O_WRONLY | os.O_APPEND | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - receipt = dict(schema='platform.email-connect-smtp-foundation.v1', status='started', - phase='preflight', proposal_digest=actual, started_at=datetime.now(timezone.utc).isoformat(), - mutation_outcome='not_started', email_sent=False, application_sender_changed=False) - def checkpoint(value): - raw = (json.dumps(value, sort_keys=True)+'\n').encode() - while raw: - n = os.write(fd, raw) - if n <= 0: - raise OSError('receipt_write_failed') - raw = raw[n:] - os.fsync(fd) - try: - checkpoint(receipt) - parent = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY) - try: - os.fsync(parent) - finally: - os.close(parent) - runner(proposal, receipt, checkpoint) - require(receipt['status'] == 'foundation_admitted_projection_ready', 'success_receipt_required') - result = 0 - except Exception: - receipt.update(status='failed_requires_inspection', reason='contained_foundation_failure') - result = 1 - finally: - key = 'interrupted_at' if receipt['status'] == 'started' else 'completed_at' - receipt[key] = datetime.now(timezone.utc).isoformat() - try: - checkpoint(receipt) - finally: - os.close(fd) - return result - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--execute', action='store_true') - parser.add_argument('--expect-digest') - parser.add_argument('--receipt', type=Path) - args = parser.parse_args() - ccr, proposal, value = prepare() - if not args.execute: - print(json.dumps(dict(proposal_digest=value, proposal=proposal, ccr_status=ccr['status']), indent=2)) - return 0 - require(args.receipt is not None, 'private_receipt_required') - return execute(ccr, proposal, value, args.expect_digest, args.receipt) - - -if __name__ == '__main__': - if '--execute' in sys.argv: - with open(os.devnull, 'w') as sink: - os.dup2(sink.fileno(), 1); os.dup2(sink.fileno(), 2) - try: - code = main() - except Exception: - code = 1 - sys.exit(code) - sys.exit(main()) diff --git a/scripts/eso_adoption_readiness.py b/scripts/eso_adoption_readiness.py deleted file mode 100644 index 6585ea6..0000000 --- a/scripts/eso_adoption_readiness.py +++ /dev/null @@ -1,61 +0,0 @@ -#!/usr/bin/env python3 -"""Local, non-executing ESO adoption readiness gate. No credential or live calls.""" -import argparse -from datetime import datetime,timezone -import hashlib -import json -from pathlib import Path -import re -import yaml - -D2='89f1058f-33bc-47a8-8a49-0b44d426c67e' - -def require(ok,reason): - if not ok:raise ValueError(reason) - -def fresh(receipt,now): - stamp=datetime.fromisoformat(receipt['captured_at'].replace('Z','+00:00')) - require(stamp.tzinfo is not None and 0<=(now-stamp).total_seconds()<=300,'predecessor_proof_stale') - -def source_check(app): - require(app.get('kind')=='Application' and app.get('metadata',{}).get('name')=='external-secrets' and app['metadata'].get('namespace')=='argocd','source_identity') - spec=app['spec'];source=spec['source'];require(spec.get('project')=='railiance-platform-addons' and spec.get('destination')=={'server':'https://kubernetes.default.svc','namespace':'external-secrets'},'source_destination') - require(source.get('repoURL')=='https://charts.external-secrets.io' and source.get('chart')=='external-secrets' and source.get('targetRevision')=='0.16.1' and source['helm'].get('releaseName')=='external-secrets','source_chart_pin') - values=yaml.safe_load(source['helm']['values']);require(values.get('installCRDs') is True,'install_crds') - options=values.get('crds',{}).get('annotations',{}).get('argocd.argoproj.io/sync-options','') - require(set(options.split(','))=={'Prune=false','Delete=false'},'crd_prune_delete_protection') - sync=spec.get('syncPolicy',{});require('automated' not in sync and not app['metadata'].get('finalizers'),'manual_no_finalizer') - require('ServerSideApply=true' in sync.get('syncOptions',[]),'server_side_apply') - require(not any(x in sync.get('syncOptions',[]) for x in ('Force=true','Replace=true')),'no_force_replace') - return {'chart_pin':'0.16.1','crd_prune_delete_protected':True,'manual_sync_only':True,'server_side_apply':True} - -def issue_check(receipt,now): - require(receipt.get('schema_version')=='rapp-issue-core-adoption/v2' and receipt.get('passed') is True,'issue_core_adoption_missing') - fresh(receipt,now);checks=receipt['checks'];require(checks.get('estate_node_ip') is True and checks.get('argo_synced_healthy') is True and checks.get('argo_tracking') is True and checks.get('external_secret_ready') is True and checks.get('private_service') is True,'issue_core_adoption_incomplete') - require(bool(re.fullmatch(r'[0-9a-f]{40}',checks.get('source_revision',''))),'issue_core_immutable_revision') - require(all(checks.get(k)==v for k,v in {'healthz_status':200,'unauthenticated_ingest_status':401,'authenticated_ingest_status':201,'authenticated_close_status':200}.items()),'issue_core_controlled_smoke_missing') - -def target_check(receipt,now): - require(receipt.get('schema_version')=='argocd-workload-adoption-proof/v1' and receipt.get('status')=='verified' and receipt.get('application')=='target-revenue','target_revenue_adoption_missing') - fresh(receipt,now);require(receipt.get('manual_apply_prune_off') is True and receipt.get('child_automation_enabled') is False,'target_revenue_adoption_incomplete') - -def assess(app,issue,target,now=None): - now=now or datetime.now(timezone.utc);missing=[];checks={} - for name,fn,value in [('source',source_check,app),('RPF-WP-0044-T04',issue_check,issue),('RPF-WP-0044-T05',target_check,target)]: - try: - result=fn(value) if name=='source' else fn(value,now) - checks[name]=result if name=='source' else True - except Exception:missing.append(name) - return {'schema':'eso-adoption-readiness/v1','task':'RPF-WP-0044-T06','approval_ref':D2,'source_ready':bool(checks.get('source')),'ready_for_fresh_native_render_diff':not missing,'missing_or_invalid':missing,'checks':checks,'next_action':'fresh_native_render_diff' if not missing else 'wait_for_predecessor_automated_proof','founder_approval_required':False,'native_mutation':False,'live_or_credential_calls':False} - -def main(): - parser=argparse.ArgumentParser(description=__doc__);parser.add_argument('--draft',required=True,type=Path);parser.add_argument('--issue-core-proof',type=Path);parser.add_argument('--target-revenue-proof',type=Path) - args=parser.parse_args() - def load(path): - try:return json.loads(path.read_text()) if path else {} - except Exception:return {} - try:app=yaml.safe_load(args.draft.read_text());receipt=assess(app,load(args.issue_core_proof),load(args.target_revenue_proof)) - except Exception:receipt={'ready_for_fresh_native_render_diff':False,'failure':'source_unverified','native_mutation':False,'live_or_credential_calls':False} - print(json.dumps(receipt,sort_keys=True));return 0 if receipt['ready_for_fresh_native_render_diff'] else 2 - -if __name__=='__main__':raise SystemExit(main()) diff --git a/scripts/eso_attended_checks.py b/scripts/eso_attended_checks.py deleted file mode 100644 index 6b84e34..0000000 --- a/scripts/eso_attended_checks.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env python3 -"""Evaluation helpers for the attended RPF-WP-0044-T06 ESO adoption script. - -Pure functions over files the attended script captured. No cluster calls, no -credential reads. Credential-kind objects are never compared or printed: the -render is split before anything live is fetched, and live input that still -contains such an object is refused. -""" -import argparse -import json -from pathlib import Path -import sys - -import yaml - -CREDENTIAL_KIND = 'Sec' + 'ret' -EXPECTED_CRDS = 20 -EXPECTED_FAILED_STORES = {'openbao-activity-core', 'openbao-email-connect'} -REVISION = '0.16.1' - - -def ident(obj): - meta = obj.get('metadata', {}) - group = obj.get('apiVersion', '').rpartition('/')[0] - return (group, obj.get('kind'), meta.get('namespace') or '', meta.get('name')) - - -def split_render(text): - """Return (comparable objects, credential-kind identities) from a helm render.""" - keep, held = [], [] - for doc in yaml.safe_load_all(text): - if not doc: - continue - if doc.get('kind') == CREDENTIAL_KIND: - held.append(ident(doc)) - else: - keep.append(doc) - return keep, held - - -def subset_diff(want, have, path=''): - """Paths where declared ``want`` differs from live ``have`` (live-only fields ignored).""" - if isinstance(want, dict): - if not isinstance(have, dict): - return [path or '/'] - out = [] - for key, value in want.items(): - if key not in have: - out.append(f'{path}/{key}') - else: - out.extend(subset_diff(value, have[key], f'{path}/{key}')) - return out - if isinstance(want, list): - if not isinstance(have, list) or len(want) != len(have): - return [path or '/'] - out = [] - for index, (a, b) in enumerate(zip(want, have)): - out.extend(subset_diff(a, b, f'{path}/{index}')) - return out - if want != have and not (want in (None, '', {}, []) and have in (None, '', {}, [])): - return [path or '/'] - return [] - - -def classify(render_objects, live_objects): - if any(o.get('kind') == CREDENTIAL_KIND for o in live_objects): - raise ValueError('credential-kind object in live input refused') - live = {ident(o): o for o in live_objects} - rows, problems = [], [] - for obj in render_objects: - key = ident(obj) - current = live.get(key) - if current is None: - rows.append({'object': '/'.join(key), 'state': 'create'}) - if obj['kind'] == 'CustomResourceDefinition': - problems.append('crd_missing_live:' + key[3]) - continue - paths = [p for p in subset_diff({k: v for k, v in obj.items() if k not in ('apiVersion', 'kind')}, - current) if not p.startswith('/status')] - metadata_only = all(p.startswith('/metadata/') for p in paths) - state = 'unchanged' if not paths else ('metadata-only' if metadata_only else 'spec-change') - rows.append({'object': '/'.join(key), 'state': state, 'paths': paths[:20]}) - if obj['kind'] == 'CustomResourceDefinition' and not metadata_only: - problems.append('crd_spec_change:' + key[3]) - elif state == 'spec-change': - problems.append('spec_change_needs_review:' + '/'.join(key[1:])) - crds = sum(1 for o in render_objects if o['kind'] == 'CustomResourceDefinition') - if crds != EXPECTED_CRDS: - problems.append(f'render_crd_count:{crds}') - stores = [o for o in render_objects if o['kind'] in ('ClusterSecretStore', 'SecretStore', 'ExternalSecret')] - if stores: - problems.append('render_contains_store_or_externalsecret') - return {'objects': rows, 'counts': {s: sum(r['state'] == s for r in rows) - for s in ('create', 'unchanged', 'metadata-only', 'spec-change')}, - 'problems': problems, 'go': not problems} - - -def store_baseline(store_list): - rows = {} - for item in store_list.get('items', []): - ready = [c for c in item.get('status', {}).get('conditions', []) if c.get('type') == 'Ready'] - cond = ready[0] if ready else {} - rows[item['metadata']['name']] = {'ready': cond.get('status'), 'reason': cond.get('reason')} - failing = {n for n, r in rows.items() if r['ready'] != 'True'} - unexpected = sorted(failing - EXPECTED_FAILED_STORES) - return {'stores': rows, 'count': len(rows), 'not_ready': sorted(failing), - 'unexpected_not_ready': unexpected, 'go': bool(rows) and not unexpected} - - -def app_state(app): - status = app.get('status', {}) - op = status.get('operationState', {}) - return {'sync': status.get('sync', {}).get('status'), 'health': status.get('health', {}).get('status'), - 'revision': status.get('sync', {}).get('revision'), 'phase': op.get('phase'), - 'pending_operation': bool(app.get('operation')), - 'conditions': [c.get('type') for c in status.get('conditions', [])], - 'automated': 'automated' in app.get('spec', {}).get('syncPolicy', {}), - 'resources': len(status.get('resources', []))} - - -def precheck_app(app, expected_resources): - state = app_state(app) - problems = [] - if state['automated']: - problems.append('automated_sync_present') - if any(c.endswith('Error') for c in state['conditions']): - problems.append('argo_error_condition') - if state['pending_operation']: - problems.append('pending_operation') - if state['resources'] != expected_resources: - problems.append(f"argo_resource_count:{state['resources']}") - return {**state, 'problems': problems, 'go': not problems} - - -def postcheck(app, crd_names, before, after): - state = app_state(app) - problems = [] - if state['phase'] != 'Succeeded': - problems.append(f"operation_phase:{state['phase']}") - if state['sync'] != 'Synced' or state['health'] != 'Healthy': - problems.append(f"argo_state:{state['sync']}/{state['health']}") - if state['revision'] != REVISION: - problems.append(f"revision:{state['revision']}") - count = sum(1 for n in crd_names if n.endswith('external-secrets.io')) - if count != EXPECTED_CRDS: - problems.append(f'crd_count:{count}') - if before['stores'] != after['stores']: - problems.append('store_baseline_changed') - return {**state, 'crd_count': count, 'store_baseline_unchanged': before['stores'] == after['stores'], - 'problems': problems, 'go': not problems} - - -def refresh_sample(before, after): - def ready(obj): - return [c.get('status') for c in obj.get('status', {}).get('conditions', []) if c.get('type') == 'Ready'] - refreshed = after.get('status', {}).get('refreshTime') != before.get('status', {}).get('refreshTime') - ok = refreshed and ready(after) == ['True'] - return {'refreshed': refreshed, 'ready': ready(after), 'go': ok} - - -def main(argv=None): - parser = argparse.ArgumentParser(description=__doc__) - sub = parser.add_subparsers(dest='cmd', required=True) - s = sub.add_parser('split'); s.add_argument('--render', type=Path, required=True); s.add_argument('--out', type=Path, required=True) - c = sub.add_parser('classify'); c.add_argument('--render', type=Path, required=True); c.add_argument('--live', type=Path, required=True) - b = sub.add_parser('stores'); b.add_argument('--live', type=Path, required=True) - a = sub.add_parser('precheck-app'); a.add_argument('--live', type=Path, required=True); a.add_argument('--expected-resources', type=int, required=True) - p = sub.add_parser('postcheck'); p.add_argument('--app', type=Path, required=True); p.add_argument('--crds', type=Path, required=True) - p.add_argument('--before', type=Path, required=True); p.add_argument('--after', type=Path, required=True) - r = sub.add_parser('refresh'); r.add_argument('--before', type=Path, required=True); r.add_argument('--after', type=Path, required=True) - g = sub.add_parser('gate'); g.add_argument('--readiness', type=Path, required=True) - args = parser.parse_args(argv) - load = lambda path: json.loads(path.read_text()) - if args.cmd == 'split': - keep, held = split_render(args.render.read_text()) - args.out.write_text(yaml.safe_dump_all(keep, sort_keys=False)) - result = {'comparable': len(keep), 'credential_kind_excluded': ['/'.join(h) for h in held], 'go': True, - 'total': len(keep) + len(held)} - elif args.cmd == 'classify': - keep, _ = split_render(args.render.read_text()) - live = load(args.live) - result = classify(keep, live.get('items', live if isinstance(live, list) else [])) - elif args.cmd == 'stores': - result = store_baseline(load(args.live)) - elif args.cmd == 'precheck-app': - result = precheck_app(load(args.live), args.expected_resources) - elif args.cmd == 'postcheck': - crds = [line.strip().split('/')[-1] for line in args.crds.read_text().splitlines() if line.strip()] - result = postcheck(load(args.app), crds, load(args.before), load(args.after)) - elif args.cmd == 'refresh': - result = refresh_sample(load(args.before), load(args.after)) - else: - gate = load(args.readiness) - result = {'go': gate.get('ready_for_fresh_native_render_diff') is True, - 'missing_or_invalid': gate.get('missing_or_invalid')} - print(json.dumps(result, sort_keys=True)) - return 0 if result['go'] else 3 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/flex_auth_signer_custody.py b/scripts/flex_auth_signer_custody.py deleted file mode 100644 index 1fd1197..0000000 --- a/scripts/flex_auth_signer_custody.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -"""Protected signer CAS primitive. Disposable owner fixture only; no live entry. - -This module deliberately cannot admit production custody. A reviewed native CCR, -protected executor, fencing and founder scope are prerequisites for that adapter. -""" -from __future__ import annotations -from dataclasses import dataclass,field -from datetime import datetime,timezone -import base64 -import hashlib -import hmac -import http.client -import json -import os -from pathlib import Path -import re -import secrets -import subprocess - -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from task_execution_journal import Journal,Refused -from task_execution_native_fixture import _absolute_budget -from task_execution_recovery import _json_unique,_invalid_constant - -PATH='platform/data/workloads/flex-auth/envelope-signing' -FIELDS={'ED25519_PRIVATE_KEY','ED25519_PUBLIC_KEY','kid'} -BINDINGS={'proposal_sha256','executed_revision_sha256','deployment_sha256','image_sha256','policy_sha256','trust_sha256'} - - -def encoded(value):return json.dumps(value,sort_keys=True,separators=(',',':'),allow_nan=False).encode() -def b64(value):return base64.urlsafe_b64encode(value).decode().rstrip('=') - - -@dataclass(frozen=True) -class FixtureWriterAdmission: - port:int - owner_token:str=field(repr=False) - server_process:subprocess.Popen=field(repr=False) - fenced:bool=False - - -class SignerWriter: - """Single attempted operation; failed/unknown writes never retry or remint. - - File permissions are accidental-disclosure guards, not same-UID isolation. - The receipt directory must be independent owner custody in a native adapter. - Python cannot promise seed zeroization; process lifetime is the boundary. - """ - def __init__(self,admission:FixtureWriterAdmission,directory:Path,operation_id:str,*,kid:str,expected_version:int,bindings:dict): - if (type(admission) is not FixtureWriterAdmission or type(admission.port) is not int - or not 1<=admission.port<=65535 or type(admission.owner_token) is not str - or not admission.owner_token or type(admission.fenced) is not bool or not admission.fenced): - raise Refused('exact fenced fixture custody required') - process=admission.server_process - if (type(process) is not subprocess.Popen or process.poll() is not None or type(process.args) is not list - or '-dev' not in process.args or '-dev-no-store-token' not in process.args - or '-dev-listen-address=127.0.0.1:'+str(admission.port) not in process.args): - raise Refused('live owner-spawned disposable dev process required') - self.admission=admission - self._initialize_receipt(directory,operation_id,kid=kid,expected_version=expected_version,bindings=bindings) - - def _initialize_receipt(self,directory,operation_id,*,kid,expected_version,bindings): - if (type(operation_id) is not str or not re.fullmatch(r'[a-zA-Z0-9_-]{1,80}',operation_id) - or type(kid) is not str or not re.fullmatch(r'[a-zA-Z0-9_-]{1,80}',kid) - or type(expected_version) is not int or not 0<=expected_version<2**31 - or type(bindings) is not dict or set(bindings)!=BINDINGS - or any(type(v) is not str or not re.fullmatch(r'sha256:[0-9a-f]{64}',v) for v in bindings.values())): - raise Refused('exact signer operation contract required') - if directory.absolute()!=directory.resolve() or directory.is_symlink():raise Refused('indirect receipt custody refused') - Journal._private(directory.stat(),directory=True) - self.directory=directory;self.path=directory/(operation_id+'.json') - self.expected_version=expected_version;self.kid=kid;self.attempted=False - self.receipt={'schema':'railiance-platform.signer-cas-receipt.v1','operation_id':operation_id, - 'bindings':dict(bindings),'writer_sha256':'sha256:'+hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), - 'actor':'synthetic-owner-fixture','created_at':datetime.now(timezone.utc).isoformat(),'path':PATH,'expected_version':expected_version,'kid':kid, - 'status':'prepared','production_admission':False,'rollback':'forward-recovery-only', - 'public_key':None,'public_fingerprint':None,'kv_version':None,'exact_readback':False} - self._save(initial=True) - - def _save(self,*,initial=False): - raw=encoded(self.receipt) - if len(raw)>65536:raise Refused('receipt bound exceeded') - if initial: - target=self.path - else: - fd=os.open(self.path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK) - try:Journal._private(os.fstat(fd)) - finally:os.close(fd) - target=self.directory/('.pending-'+secrets.token_hex(16)) - fd=os.open(target,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600) - try: - with os.fdopen(fd,'wb',closefd=False) as stream:stream.write(raw);stream.flush();os.fsync(fd) - finally:os.close(fd) - if not initial:os.replace(target,self.path) - fd=os.open(self.directory,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW) - try:os.fsync(fd) - finally:os.close(fd) - - def _http(self,method,path,body=None): - # Fixed loopback owner process and fixed data path; never arbitrary URL. - if self.admission.server_process.poll() is not None:raise Refused('fixture custody backend unavailable') - with _absolute_budget(2.0): - conn=http.client.HTTPConnection('127.0.0.1',self.admission.port,timeout=2) - try: - conn.request(method,'/v1/'+path,body=None if body is None else encoded(body),headers={ - 'X-Vault-Token':self.admission.owner_token,'Content-Type':'application/json'}) - response=conn.getresponse();raw=response.read(1048577) - if len(raw)>1048576:raise Refused('backend response bound exceeded') - value=json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant) if raw else {} - return response.status,value - finally:conn.close() - - def write(self): - if self.attempted:raise Refused('signer operation already attempted') - self.attempted=True - # All source/receipt checks precede generation; no seed import, argv, - # environment, temporary file or public output interface exists. - private=Ed25519PrivateKey.generate() - seed=b64(private.private_bytes(serialization.Encoding.Raw,serialization.PrivateFormat.Raw,serialization.NoEncryption())) - public=b64(private.public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw)) - document={'ED25519_PRIVATE_KEY':seed,'ED25519_PUBLIC_KEY':public,'kid':self.kid} - self.receipt.update(status='write-intent-unknown',public_key=public, - public_fingerprint='sha256:'+hashlib.sha256(public.encode()).hexdigest()) - self._save() # A durable intent must exist before any possible effect. - try: - status,result=self._http('POST',PATH,{'options':{'cas':self.expected_version},'data':document}) - if status!=200 or type(result) is not dict:raise Refused('signer write unconfirmed') - version=result.get('data',{}).get('version') - if type(version) is not int or version!=self.expected_version+1:raise Refused('signer write version unconfirmed') - self.receipt['kv_version']=version - status,result=self._http('GET',PATH+'?version='+str(version)) - if status!=200 or type(result) is not dict:raise Refused('signer readback unconfirmed') - data=result.get('data',{});stored=data.get('data') if type(data) is dict else None - metadata=data.get('metadata') if type(data) is dict else None - if (type(stored) is not dict or set(stored)!=FIELDS or any(type(v) is not str for v in stored.values()) - or type(metadata) is not dict or type(metadata.get('version')) is not int or metadata['version']!=version - or any(not hmac.compare_digest(stored[name],document[name]) for name in FIELDS)): - raise Refused('signer readback mismatch') - self.receipt.update(status='custody-verified-pending-independent-public-admission',exact_readback=True) - self._save() - return dict(self.receipt) - except BaseException: - # Persist only closed metadata; unknown never implies absence or retry. - # If receipt storage fails, the durable earlier intent remains unknown. - self.receipt.update(status='write-intent-unknown',exact_readback=False) - self._save() - raise Refused('signer custody outcome unconfirmed') from None - - -if __name__=='__main__': - raise SystemExit('production signer custody unadmitted; no live entry point') diff --git a/scripts/flex_auth_signer_native.py b/scripts/flex_auth_signer_native.py deleted file mode 100644 index 4fc8de6..0000000 --- a/scripts/flex_auth_signer_native.py +++ /dev/null @@ -1,425 +0,0 @@ -#!/usr/bin/env python3 -"""Silent attended retained-lifecycle signer custody; never a credential front door. - -Invoke only through the reviewed warden platform-admin envelope. The externally -reviewed admission SHA is an exact-command input, not approval by JSON shape. -No private value crosses stdout/stderr, argv, receipt or persistent local storage. -""" -from __future__ import annotations -import argparse -import base64 -from datetime import datetime,timezone -import fcntl -import hashlib -import hmac -import http.client -import importlib.util -import json -import os -from pathlib import Path -import re -import selectors -import shlex -import subprocess -import sys -import time - -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from flex_auth_signer_custody import SignerWriter,PATH,FIELDS,BINDINGS,encoded,b64 -from task_execution_journal import Journal,Refused -from task_execution_native_fixture import _absolute_budget -from task_execution_recovery import _json_unique,_invalid_constant,_policy_absent,_same_policy - -ROOT=Path(__file__).resolve().parents[1] -META='platform/metadata/workloads/flex-auth/envelope-signing' -CCR='credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml' -PLAN='docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json' -CLUSTER='a553c742-0115-43d4-99a4-a5ca56fe0786' -BAO_CLUSTER='fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf' -NAME='flex-auth-secrets-engine' -SECRET='flex-auth-secrets-engine-envelope-signing' -ROLE='external-secrets-flex-auth-envelope-signing' -LIFECYCLE='sha256:bd11c5fe77ce6439c65fea225ad6b71d2110efc5e7b5bc9b499c59cd0a53b8b4' -REVIEWS={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-runtime-storage-owner'} -SOURCE_FILES={'scripts/flex_auth_signer_native.py','scripts/flex_auth_signer_custody.py','scripts/task_execution_native_fixture.py','scripts/task_execution_journal.py','scripts/task_execution_recovery.py','scripts/credential-change.py'} - - -def require(ok,reason): - if not ok:raise Refused(reason) - - -def digest(raw):return 'sha256:'+hashlib.sha256(raw).hexdigest() -def is_digest(value):return type(value) is str and re.fullmatch(r'sha256:[0-9a-f]{64}',value) is not None - -def private_read(path,limit=65536): - path=Path(path) - require(path.is_absolute() and path.absolute()==path.resolve() and not path.is_symlink(),'indirect private input') - Journal._private(path.parent.stat(),directory=True) - fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK) - try: - before=os.fstat(fd);Journal._private(before) - raw=os.read(fd,limit+1);after=os.fstat(fd) - require((before.st_dev,before.st_ino,before.st_size,before.st_mtime_ns,before.st_ctime_ns)== - (after.st_dev,after.st_ino,after.st_size,after.st_mtime_ns,after.st_ctime_ns) - and len(raw)==before.st_size and len(raw)<=limit,'private input changed or oversized') - return raw - finally:os.close(fd) - - -def parsed(raw): - try:return json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant) - except Exception:raise Refused('invalid closed JSON input') from None - - -def review_scope_digest(value): - """Owner reviews bind the exact action scope minus the reviews themselves and the - invocation-time expiry. The 5..120s expiry stays enforced separately; binding it - into the reviewed digest made five asynchronous reviews impossible to collect - inside the attended window (2026-10-21 build-mode fix, close-down checklist).""" - return digest(encoded({k:v for k,v in value.items() if k not in {'owner_reviews','expires_at'}})) - - -def validate_admission(value): - fields={'schema','action','operation_id','kid','expires_at','founder_entity_id','ccr_sha256','source_bundle','bindings', - 'producer_image','caller_binding','deployment_projection_sha256','authority','routing','owner_reviews', - 'exclusive_window_sha256','consumer_trust_sha256','original_receipt_sha256','original_execution'} - require(type(value) is dict and set(value)==fields,'exact admission schema required') - require(value['schema']=='railiance-platform.signer-native-admission.v1' and value['action'] in {'bootstrap','recover'},'native admission action refused') - require(type(value['operation_id']) is str and re.fullmatch(r'[A-Za-z0-9_-]{1,80}',value['operation_id']) and - type(value['kid']) is str and re.fullmatch(r'[A-Za-z0-9_-]{1,80}',value['kid']),'native operation identity required') - require(type(value['founder_entity_id']) is str and re.fullmatch(r'[a-f0-9-]{36}',value['founder_entity_id']),'explicit attended identity required') - require(all(is_digest(value[x]) for x in ['ccr_sha256','deployment_projection_sha256','exclusive_window_sha256','consumer_trust_sha256']),'explicit custody/image/trust selections required') - require(type(value['producer_image']) is str and re.fullmatch(r'forgejo\.coulomb\.social/coulomb/flex-auth@sha256:[a-f0-9]{64}',value['producer_image']),'immutable producer image required') - require(value['caller_binding']=='secrets-engine=system:serviceaccount:secrets-engine:secrets-engine','retained caller binding required') - require(value['authority']=='founder-attended-exact-source-scope; no standing grant','independent attended authority required') - route=value['routing'];require(type(route) is dict and set(route)=={'lane_id','verdict','founder_act','receipt_sha256'} and - route['lane_id']=='openbao-platform-admin-login' and route['verdict']=='founder_required' and route['founder_act']=='oidc_login' and is_digest(route['receipt_sha256']),'routing mismatch refused') - reviews=value['owner_reviews'];require(type(reviews) is dict and set(reviews)==REVIEWS,'distinct owner reviews required') - for role,review in reviews.items(): - require(type(review) is dict and set(review)=={'approved_admission_sha256','approval_ref'} and - is_digest(review['approved_admission_sha256']) and type(review['approval_ref']) is str and - 00,'absolute custody command timeout') - for item,_ in sel.select(remaining): - chunk=os.read(item.fileobj.fileno(),65536) - if not chunk:sel.unregister(item.fileobj) - else: - data[item.fileobj].extend(chunk) - require(sum(map(len,data.values()))<=1048576,'custody command output bound') - code=proc.wait(timeout=max(.001,deadline-time.monotonic())) - require(code in allowed,'custody command unconfirmed') - return code,bytes(data[proc.stdout]) - finally:sel.close() - except BaseException: - proc.kill();proc.wait(timeout=2);raise Refused('custody command unconfirmed') from None - finally: - proc.stdout.close();proc.stderr.close() - def bao(self,args,payload=None,allowed=(0,)): - with _absolute_budget(2): - code,raw=self.captured(['bao',*args],payload,allowed=allowed) - return code,parsed(raw) - def kube(self,resource,name=None): - args=['kubectl','--request-timeout=2s'] - if resource!='namespace':args+=['-n','flex-auth'] - args+=['get',resource] - if name:args.append(name) - args+=['-o','json'] - with _absolute_budget(2): - _,raw=self.captured(['ssh','-o','BatchMode=yes','-o','StrictHostKeyChecking=yes','-o','ConnectTimeout=2','railiance01',shlex.join(args)]) - return parsed(raw) - def http(self,method,path,body=None): - require(method in {'GET','POST'} and path in {META,PATH,PATH+'?version=1'},'fixed custody path required') - require(type(getattr(self,'_token',None)) is str and self._token,'contained attended session required') - with _absolute_budget(2): - conn=http.client.HTTPConnection('127.0.0.1',18200,timeout=2) - try: - conn.request(method,'/v1/'+path,body=None if body is None else encoded(body), - headers={'X-Vault-Token':self._token,'Content-Type':'application/json'}) - response=conn.getresponse();raw=response.read(1048577) - require(len(raw)<=1048576,'custody response bound') - return response.status,parsed(raw) if raw else {} - finally:conn.close() - - -def deployment_projection(deployment): - spec=deployment['spec']['template']['spec'];containers=spec.get('containers',[]) - require(type(containers) is list and len(containers)==1 and not spec.get('initContainers'),'single fixed signer container required') - item=containers[0] - # Never hash arbitrary env values or an entire Secret/spec response. - return {'service_account':spec.get('serviceAccountName'),'image':item.get('image'), - 'args':item.get('args'),'volume_mounts':item.get('volumeMounts',[]), - 'volumes':spec.get('volumes',[]),'security_context':spec.get('securityContext')} - - -def fence(io,contract): - namespace=io.kube('namespace','kube-system') - require(namespace.get('metadata',{}).get('uid')==CLUSTER,'wrong target cluster') - deployment=io.kube('deployment',NAME);spec=deployment.get('spec',{});meta=deployment.get('metadata',{}) - require(type(spec.get('replicas')) is int and spec['replicas']==0 and - deployment.get('status',{}).get('observedGeneration',-1)==meta.get('generation') and - not deployment.get('status',{}).get('replicas',0),'all signer replicas must be stopped') - projection=deployment_projection(deployment) - require(digest(encoded(projection))==contract['deployment_projection_sha256'] and - projection['service_account']==NAME and projection['image']==contract['producer_image'],'fenced deployment source/image mismatch') - args=projection['args'];require(type(args) is list and all(type(x) is str for x in args),'exact signer args required') - def option(flag): - require(args.count(flag)==1 and args.index(flag)+1``, writes it to -``platform/workloads/coulomb/fluid-telegram/hall-of-helix/`` with CAS 0 -and verifies the readback. Only after all four are verified, and no source -changed during the run, are the infotech entries metadata-deleted. Values only -travel through this process's memory and the ``bao`` child's stdin. - -Run from a railiance-platform checkout at the reviewed commit: - - BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \\ - warden access openbao-platform-admin-login --exec -- \\ - python3 scripts/fluid_telegram_custody_move.py \\ - --confirm 'MOVE fluid-telegram hall-of-helix to coulomb' \\ - --receipt "$HOME/ft-custody-move-receipt.json" - -``--dry-run`` touches only KV metadata. ``--keep-source`` copies and verifies -but does not delete. A rerun after a completed move reports ``already_moved``. - -The process prints nothing (stdout/stderr go to /dev/null). The optional -receipt (mode 0600, refused if it exists) holds versions, booleans and an -outcome code only. - -Exit codes: - 0 moved (or already moved / already identical); dry-run: a move would run - 1 unexpected internal error (contained) - 2 precondition refused before any write (confirmation, envelope, identity, - an entry absent at both prefixes, absence not provable, receipt exists) - 3 a coulomb entry exists and differs, or a current version is not live; - never overwritten - 4 write or delete failed - 5 verify failed: readback differs, or a source changed during the run - 6 dry-run only: some coulomb entries already exist -""" -from __future__ import annotations - -import argparse -import json -import os -from pathlib import Path -import sys - -sys.path.insert(0, str(Path(__file__).resolve().parent)) -from tenant_engine_audit_core_sender_seed import Exit, bao, live_version, read_json # noqa: E402 - -SOURCE = 'workloads/infotech/fluid-telegram/hall-of-helix' -TARGET = 'workloads/coulomb/fluid-telegram/hall-of-helix' -ENTRIES = ('bot-token', 'operator-app', 'operator-session', 'redaction-salt') -CONFIRM = 'MOVE fluid-telegram hall-of-helix to coulomb' -SCHEMA = 'platform.fluid-telegram-custody-move.v1' - - -def meta(prefix, entry): - return read_json(f'platform/metadata/{prefix}/{entry}', absent_ok=True) - - -def data(prefix, entry, code=2): - raw = read_json(f'platform/data/{prefix}/{entry}', absent_ok=False, code=code) - try: - return raw['data'], raw['metadata']['version'] - except (KeyError, TypeError): - raise Exit(code, 'unparseable_read') from None - - -def run(args, receipt): - if args.confirm != CONFIRM: - raise Exit(2, 'exact_confirmation_required') - if (Path.home().parent.name != '.warden-attended-login' - or os.environ.get('BAO_TOKEN') or os.environ.get('VAULT_TOKEN')): - raise Exit(2, 'attended_warden_envelope_required') - ident = read_json('auth/token/lookup-self', absent_ok=False) - policies = ident.get('policies') or [] - if 'platform-admin' not in policies or 'root' in policies: - raise Exit(2, 'attended_platform_admin_required') - - rows = receipt['entries'] - plan = [] - for entry in ENTRIES: - src, dst = meta(SOURCE, entry), meta(TARGET, entry) - row = rows[entry] = {'source_present': src is not None, 'target_present': dst is not None} - if src is None and dst is None: - raise Exit(2, 'entry_absent_at_both_prefixes') - if src is not None: - if live_version(src) is None: - raise Exit(3, 'source_current_version_not_live') - row['source_version'] = src['current_version'] - if dst is not None: - if live_version(dst) is None: - raise Exit(3, 'target_current_version_not_live') - row['target_version'] = dst['current_version'] - plan.append((entry, src, dst)) - - if args.dry_run: - receipt['value_reads'] = 0 - if any(dst is not None for _, src, dst in plan if src is not None): - raise Exit(6, 'dry_run_target_present') - return 'dry_run_would_move' if any(src for _, src, _ in plan) else 'dry_run_already_moved' - - if all(src is None for _, src, _ in plan): - return 'already_moved' - - # Phase 1: copy and verify every entry before anything is deleted. - for entry, src, dst in plan: - if src is None: - continue - value, version = data(SOURCE, entry) - if version != src['current_version']: - raise Exit(5, 'source_changed_during_run') - row = rows[entry] - if dst is not None: - current, _ = data(TARGET, entry, code=3) - if current != value: - raise Exit(3, 'target_differs') - row['outcome'] = 'already_identical' - continue - result = bao(['write', '-format=json', f'platform/data/{TARGET}/{entry}', '-'], - payload={'options': {'cas': 0}, 'data': value}) - if result.returncode != 0: - raise Exit(4, 'write_failed') - back, back_version = data(TARGET, entry, code=5) - if back != value or back_version != 1: - raise Exit(5, 'readback_mismatch') - row['target_version'] = 1 - row['outcome'] = 'copied' - - for entry, src, _ in plan: - if src is None: - continue - again = meta(SOURCE, entry) - if again is None or again.get('current_version') != src['current_version']: - raise Exit(5, 'source_changed_during_run') - - if args.keep_source: - return 'copied_source_kept' - - # Phase 2: remove the undocumented infotech entries (all versions). - for entry, src, _ in plan: - if src is None: - continue - if bao(['delete', f'platform/metadata/{SOURCE}/{entry}']).returncode != 0: - raise Exit(4, 'source_delete_failed') - if meta(SOURCE, entry) is not None: - raise Exit(5, 'source_still_present') - rows[entry]['source_deleted'] = True - return 'moved' - - -def main(argv=None): - devnull = os.open(os.devnull, os.O_WRONLY) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--confirm', default='') - parser.add_argument('--receipt', type=Path) - parser.add_argument('--dry-run', action='store_true') - parser.add_argument('--keep-source', action='store_true') - try: - args = parser.parse_args(argv) - except SystemExit: - return 2 - receipt = {'schema': SCHEMA, 'mode': 'dry-run' if args.dry_run else 'move', - 'source': 'platform/' + SOURCE, 'target': 'platform/' + TARGET, - 'entries': {}, 'credential_values_emitted': False} - fd = None - if args.receipt is not None: - try: - fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - except OSError: - return 2 - try: - receipt['outcome'] = run(args, receipt) - code = 0 - except Exit as exc: - receipt['outcome'] = exc.reason - code = exc.code - except Exception: - receipt['outcome'] = 'contained_internal_error' - code = 1 - receipt['exit_code'] = code - if fd is not None: - os.write(fd, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode()) - os.close(fd) - return code - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/install-backup-pair-writer.sh b/scripts/install-backup-pair-writer.sh deleted file mode 100755 index 25ebacc..0000000 --- a/scripts/install-backup-pair-writer.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh -# Install backup-pair-writer. Silent: warden access rejects any child output. -set -eu -policy=/home/worsch/railiance-platform/openbao/policies/backup-pair-writer.hcl -receipt_dir=/home/worsch/.local/state/railiance/receipts -umask 077 -mkdir -p "$receipt_dir" -bao policy write backup-pair-writer "$policy" >/dev/null 2>&1 || exit 31 -bao policy read backup-pair-writer >"$receipt_dir/backup-pair-writer.policy" 2>/dev/null || exit 32 -sha256sum "$receipt_dir/backup-pair-writer.policy" >"$receipt_dir/backup-pair-writer.sha256" || exit 33 -exit 0 diff --git a/scripts/install-backup-primary-readers.sh b/scripts/install-backup-primary-readers.sh deleted file mode 100644 index aad2d62..0000000 --- a/scripts/install-backup-primary-readers.sh +++ /dev/null @@ -1,134 +0,0 @@ -#!/bin/sh -# Install both backup-primary reader policies and Kubernetes auth roles. -# Silent: warden access rejects any child output. Receipts stay on absolute paths. -set -eu -export BAO_ADDR="${BAO_ADDR:-http://127.0.0.1:18200}" -export VAULT_ADDR="${VAULT_ADDR:-http://127.0.0.1:18200}" -receipt_dir=/home/worsch/.local/state/railiance/receipts -umask 077 -mkdir -p "$receipt_dir" -stage="$receipt_dir/backup-primary-readers.stage" -: >"$stage" -python3 - "$stage" <<'PY' -import importlib.util -import json -import subprocess -import sys -from pathlib import Path - -stage = Path(sys.argv[1]) -root = Path("/home/worsch/railiance-platform") -spec = importlib.util.spec_from_file_location( - "credential_change", root / "scripts/credential-change.py" -) -cc = importlib.util.module_from_spec(spec) -spec.loader.exec_module(cc) - -refs = [ - "CCR-2026-0036-coulomb-social-backup-primary.yaml", - "CCR-2026-0037-email-connect-backup-primary.yaml", -] -safe_role_keys = ( - "audience", - "bound_service_account_names", - "bound_service_account_namespaces", - "policies", - "token_explicit_max_ttl", - "token_max_ttl", - "token_no_default_policy", - "token_num_uses", - "token_policies", - "token_type", - "ttl", -) - -def run(argv): - result = subprocess.run(argv, cwd=root, capture_output=True) - if result.returncode != 0: - stage.write_text(f"exit {result.returncode} step {argv[1]}\n", encoding="utf-8") - raise SystemExit(result.returncode or 1) - return result.stdout.decode() - -receipts = [] -for name in refs: - ccr = json.loads((root / "credential-change-requests" / name).read_text(encoding="utf-8")) - if ccr.get("status") != "approved" or ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True: - stage.write_text(f"{ccr.get('id')} not approved for apply\n", encoding="utf-8") - raise SystemExit(44) - blockers = cc.applier_readiness_blockers(ccr) - if blockers: - stage.write_text(ccr["id"] + " blocked\n", encoding="utf-8") - raise SystemExit(45) - openbao = ccr["openbao"] - auth = openbao["auth"] - policy_path = root / openbao["policy_file"] - run(["bao", "policy", "write", openbao["policy_name"], str(policy_path)]) - installed = run(["bao", "policy", "read", openbao["policy_name"]]) - if cc.normalized_policy_body(installed) != cc.normalized_policy_body(policy_path.read_text(encoding="utf-8")): - stage.write_text(openbao["policy_name"] + " readback differs\n", encoding="utf-8") - raise SystemExit(46) - payload = cc.auth_payload(ccr) - role_file = Path(f"/home/worsch/.local/state/railiance/receipts/{auth['role']}.payload.json") - role_file.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") - role_file.chmod(0o600) - run(["bao", "write", f"auth/{auth['mount']}/role/{auth['role']}", f"@{role_file}"]) - raw = run(["bao", "read", "-format=json", f"auth/{auth['mount']}/role/{auth['role']}"]) - data = json.loads(raw).get("data", {}) - observed = {key: data.get(key) for key in safe_role_keys if key in data} - names = data.get("bound_service_account_names") - namespaces = data.get("bound_service_account_namespaces") - policies = data.get("token_policies") or data.get("policies") - audience = data.get("audience") or data.get("audiences") - problems = [] - if names != payload["bound_service_account_names"]: - problems.append("names") - if namespaces != payload["bound_service_account_namespaces"]: - problems.append("namespaces") - if policies != payload["policies"].split(","): - problems.append("policies") - if audience not in (payload.get("audience"), [payload.get("audience")]): - problems.append("audience") - if data.get("token_no_default_policy") is not True: - problems.append("no_default") - if data.get("token_num_uses") != 4: - problems.append("uses") - if data.get("token_type") != "service": - problems.append("type") - if data.get("ttl") not in (300, "5m", "300s"): - problems.append("ttl") - if data.get("token_max_ttl") not in (300, "5m", "300s"): - problems.append("max_ttl") - if data.get("token_explicit_max_ttl") not in (300, "5m", "300s"): - problems.append("explicit_max_ttl") - if problems: - stage.write_text( - json.dumps({"role": auth["role"], "problems": problems, "observed": observed}, indent=2) - + "\n", - encoding="utf-8", - ) - raise SystemExit(47) - receipts.append( - { - "id": ccr["id"], - "policy": openbao["policy_name"], - "role": auth["role"], - "policies": policies, - "bound_service_account_names": names, - "bound_service_account_namespaces": namespaces, - "ttl": data.get("ttl"), - "token_max_ttl": data.get("token_max_ttl"), - "token_explicit_max_ttl": data.get("token_explicit_max_ttl"), - "token_num_uses": data.get("token_num_uses"), - "token_no_default_policy": data.get("token_no_default_policy"), - "token_type": data.get("token_type"), - } - ) - -out = Path("/home/worsch/.local/state/railiance/receipts/backup-primary-readers.json") -out.write_text(json.dumps({"status": "installed", "readers": receipts}, indent=2) + "\n", encoding="utf-8") -out.chmod(0o600) -PY -if [ -s "$stage" ]; then - exit 48 -fi -exit 0 diff --git a/scripts/issuer_binding_lane.py b/scripts/issuer_binding_lane.py deleted file mode 100644 index 2ba3194..0000000 --- a/scripts/issuer_binding_lane.py +++ /dev/null @@ -1,169 +0,0 @@ -"""broker-issuer-binding CCRs: an attended OIDC role that carries only a broker issuer policy. - -RPF-WP-0054-T05. Loaded by scripts/credential-change.py for request_type -broker-issuer-binding. The role lets a human operator (group + aal2 on the -existing netkingdom OIDC mount) hold exactly one issuer policy, so their own -session can mint the grant token that ``credential.py generate`` uses. The role -rules (callbacks, scopes, claims, TTLs, no default policy) are the operator-matrix -lane's; the issuer policy must already be live from its own CCR. Nothing here -reads, prints or accepts a secret value; the mount is never enabled here. -""" -from __future__ import annotations - -import importlib.util -import json -import re -import tempfile -from pathlib import Path -from typing import Any - -REQUEST_TYPE = "broker-issuer-binding" -ISSUER_POLICY_RE = re.compile(r"^credential-broker-[a-z0-9-]+-issuer$") -ROLE_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") - - -def _load(name: str): - spec = importlib.util.spec_from_file_location(name, Path(__file__).resolve().parent / f"{name}.py") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -_matrix = _load("operator_matrix_lane") -BaoError = _matrix.BaoError -normalized = _matrix.normalized -_run = _matrix._run -_read_json = _matrix._read_json -ALLOWED_AUTH_MOUNTS = _matrix.ALLOWED_AUTH_MOUNTS -DURATION_KEYS = _matrix.DURATION_KEYS - - -def role_payload(ccr: dict[str, Any], repo_dir: Path) -> dict[str, Any]: - return _matrix.role_payload(ccr, repo_dir) - - -def validate(ccr: dict[str, Any], repo_dir: Path) -> list[str]: - errors: list[str] = [] - openbao = ccr.get("openbao") - if not isinstance(openbao, dict) or not isinstance(openbao.get("auth"), dict): - return ["openbao.auth must be an object"] - auth = openbao["auth"] - if (ccr.get("target") or {}).get("environment") != "production": - errors.append("target.environment must be production") - review = ccr.get("review") or {} - if review.get("required") is not True or not review.get("required_approvers"): - errors.append("review.required must be true with required_approvers") - issuer = str(openbao.get("issuer_policy") or "") - if not ISSUER_POLICY_RE.match(issuer): - errors.append("openbao.issuer_policy must be credential-broker-<...>-issuer") - elif not (repo_dir / f"openbao/policies/{issuer}.hcl").exists(): - errors.append(f"openbao.issuer_policy has no source file: openbao/policies/{issuer}.hcl") - if auth.get("method") != "oidc": - errors.append("openbao.auth.method must be oidc") - if auth.get("mount") not in ALLOWED_AUTH_MOUNTS: - errors.append(f"openbao.auth.mount must be one of {sorted(ALLOWED_AUTH_MOUNTS)}") - if not ROLE_RE.match(str(auth.get("role") or "")): - errors.append("openbao.auth.role contains unsafe characters") - if not isinstance(auth.get("bound_claims_confirmed"), bool): - errors.append("openbao.auth.bound_claims_confirmed must be a boolean") - role = _matrix._shared._load_json(repo_dir, str(auth.get("role_file") or ""), "openbao.auth.role_file", errors) - if role: - _matrix._validate_role(role, issuer, errors) - # OpenBao refuses child-token creation from a limited-use token, and - # minting the grant token is this role's whole purpose (probe 2026-10-10). - if role.get("token_num_uses") != 0: - errors.append("role token_num_uses must be 0: limited-use tokens cannot create child tokens") - return errors - - -def readiness_blockers(ccr: dict[str, Any], repo_dir: Path, allowed_statuses: set[str]) -> list[str]: - blockers: list[str] = [] - if ccr.get("status") not in allowed_statuses: - blockers.append( - "applier requires approved, applied, verified, or active CCR status, " - f"got {ccr.get('status')}" - ) - if ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True: - blockers.append("applier requires confirmed OpenBao auth binding") - blockers.extend(validate(ccr, repo_dir)) - return blockers - - -def dry_run_payload(ccr: dict[str, Any], repo_dir: Path, warnings: list[str]) -> dict[str, Any]: - openbao = ccr["openbao"] - auth = openbao["auth"] - return { - "id": ccr["id"], - "title": ccr["title"], - "status": ccr["status"], - "environment": ccr["target"]["environment"], - "warnings": warnings, - "source_artifacts": {}, - "mutations": [ - {"kind": "issuer_policy_present", "openbao_path": f"sys/policies/acl/{openbao['issuer_policy']}", - "payload": {"live_text_must_match": f"openbao/policies/{openbao['issuer_policy']}.hcl"}}, - {"kind": "auth_role_write", "openbao_path": f"auth/{auth['mount']}/role/{auth['role']}", - "payload": role_payload(ccr, repo_dir), - "precondition": f"auth/{auth['mount']} exists with type oidc (never enabled here)"}, - {"kind": "readback_compare", "openbao_path": "role", "payload": {"prints": "booleans only"}}, - ], - "out_of_scope": [ - "policy writes (the issuer policy belongs to its grant CCR)", - "secret value reads or writes", - "token creation or login", - ], - "required_evidence": [ - "CCR id and approval reference", - "readback equality for the role", - "attended login with this role, then the RPF-WP-0054-T05 probe", - ], - } - - -def apply_details(ccr: dict[str, Any], actor: str) -> list[str]: - openbao = ccr["openbao"] - auth = openbao["auth"] - return [ - f"Delegated metadata applier ran as {actor} using local bao CLI ambient authority.", - f"Issuer policy confirmed live and matching source: sys/policies/acl/{openbao['issuer_policy']}", - f"OIDC role metadata write: auth/{auth['mount']}/role/{auth['role']}", - "Readback matched source for the role.", - "No secret values were read, written, printed, or accepted in argv.", - ] - - -def readback_mismatches(ccr: dict[str, Any], repo_dir: Path, bao_bin: str) -> list[str]: - auth = ccr["openbao"]["auth"] - mismatches: list[str] = [] - role = _read_json(bao_bin, ["read", f"auth/{auth['mount']}/role/{auth['role']}"], repo_dir).get("data", {}) - for key, want in role_payload(ccr, repo_dir).items(): - got = role.get(key) - if key in DURATION_KEYS: - same = _matrix.ttl_seconds(got) == _matrix.ttl_seconds(want) - elif key in ("allowed_redirect_uris", "oidc_scopes"): - same = sorted(got or []) == sorted(want) - else: - same = got == want - if not same: - mismatches.append(f"role {key} differs") - return mismatches - - -def run_apply(ccr: dict[str, Any], repo_dir: Path, bao_bin: str, quiet: bool = False) -> None: - openbao = ccr["openbao"] - auth = openbao["auth"] - mounts = _read_json(bao_bin, ["auth", "list"], repo_dir) - mounts = mounts.get("data", mounts) - if (mounts.get(f"{auth['mount']}/") or {}).get("type") != "oidc": - raise BaoError(f"auth mount {auth['mount']}/ is absent or not oidc; this lane never enables it") - live = _run(bao_bin, ["policy", "read", openbao["issuer_policy"]], repo_dir, quiet=True, capture=True) - source = (repo_dir / f"openbao/policies/{openbao['issuer_policy']}.hcl").read_text(encoding="utf-8") - if normalized(live) != normalized(source): - raise BaoError("issuer policy is absent or differs from source; apply its grant CCR first") - with tempfile.TemporaryDirectory() as tmp: - role_path = Path(tmp) / "role.json" - role_path.write_text(json.dumps(role_payload(ccr, repo_dir), sort_keys=True) + "\n") - _run(bao_bin, ["write", f"auth/{auth['mount']}/role/{auth['role']}", f"@{role_path}"], repo_dir, quiet) - mismatches = readback_mismatches(ccr, repo_dir, bao_bin) - if mismatches: - raise BaoError("readback mismatch: " + "; ".join(mismatches)) diff --git a/scripts/keycape_client_secret_generate.py b/scripts/keycape_client_secret_generate.py deleted file mode 100644 index 96bdedc..0000000 --- a/scripts/keycape_client_secret_generate.py +++ /dev/null @@ -1,327 +0,0 @@ -#!/usr/bin/env python3 -"""RPF-WP-0054-T03: generate a KeyCape confidential-client secret inside custody. - -Entry point for the oidc-browser-relying-party ``mint-secret`` step (lane -``keycape-client-secret-generate``). Normally reached as -``credential.py generate ...``; ops-warden runs it as a child in the caller's -own environment and holds no token. - - credential.py generate --grant keycape/client-secret-generate \\ - --client-id forgejo --case-file \\ - --case-ref forgejo-netkingdom-oidc@sha256: --receipt - -Steps (every one fails closed): - -1. Refuse agents (``WARDEN_AGENT_ID``) and root. The caller's session must hold - the broker issuer policy for this grant. -2. Re-derive the case reference from ``--case-file`` (sha256 over its exact - bytes) and require: it equals ``--case-ref``; type - an exactly source-pinned acknowledged browser revision; status ``materialized``; client_type - ``confidential``; ``parameters.client_id`` equals ``--client-id``, which - matches the type's pattern and is not a reserved entry. -3. Mint a 5-minute grant token through token role - ``keycape-client-secret-generate``. All KV calls below use only that token. -4. Prove absence by metadata (404). An existing entry is never touched. -5. Generate the value in this process and write it with CAS 0 through the - ``bao`` child's stdin. -6. Verify by metadata only: current version 1, live. The data path is never read. -7. Revoke the grant token (revoke-self). - -The process prints nothing. The receipt (mode 0600, refused if it exists) and -the optional State Hub event hold the path, version, case reference, booleans and -an outcome code only. - -Exit codes: 0 generated (dry-run: would generate); 1 contained internal error; -2 precondition refused; 3 entry already exists (never overwritten, including a -lost CAS race); 4 token or write failure; 5 verification failure. -""" -from __future__ import annotations - -import argparse -import hashlib -import json -import os -from pathlib import Path -import re -import secrets -import subprocess -import sys -import tempfile -import urllib.request - -import yaml - -GRANT = 'keycape/client-secret-generate' -TOKEN_ROLE = 'keycape-client-secret-generate' -GRANT_POLICY = 'keycape-client-secret-generate' -ISSUER_POLICY = 'credential-broker-keycape-client-secret-generate-issuer' -PREFIX = 'workloads/keycape' -RESERVED = {'secrets-engine-openbao'} -FIELD = 'CLIENT_SECRET' -# Owner configuration, never a caller-supplied root or a version search/fallback. -# The sibling source checkout must contain these exact reviewed bytes. -NETKINGDOM_ROOT = Path(__file__).resolve().parents[2] / 'net-kingdom' -ACKNOWLEDGED_TYPES = { - 'oidc-browser-relying-party@1.1': ( - 'security-case-types/oidc-browser-relying-party/type.yaml', - 'f705c64e4875e9b8e18a912fb51c135e77898926d6a451f5cb74ab3c0cbe4d1c'), -} -# Same authenticated producer and parameter projection as ops-warden case_inputs -# at b593c879, restricted to this broker's currently admitted 1.1. Source-only -# acknowledgment (including 1.2) never expands broker admission. A future 1.3 -# entry requires real acknowledgment and a separately reviewed admission binding. -# Neither source status nor caller-supplied YAML expands this set. -SOURCE_PINS = { - 'tools/security-case-types/security_case_validator.py': - '1bcc62e9bc374c711363295549f3cd5bb0b32952671ed4b08fddc7f74742959b', - 'tools/security-scenario-composer/security_scenario_composer.py': - '9f0cc69930aa8ee2cc3fb92236e6a99728df17b8c0fe36e1250241c38fc01c4f', - 'tools/playbook-capability-contract/playbook_contract_validator.py': - '2e1e0c809d4bc9d64c2d974cc23fecd1c5636785154c2554edd9191852fd926d', -} -PARAMETER_PINS = { - '1.1': '9ec2d6113bf70dbd35cfc247412363ff187e792bc82e24159a6f78e090aa3e4c', -} -_VALIDATE = """ -import json, sys -from pathlib import Path -sys.path.insert(0, str(Path(sys.argv[1]) / 'tools/security-case-types')) -import security_case_validator as v -case, typ = json.load(sys.stdin) -issues = v.validate_type(typ) + v.validate_case(case, typ, require_implemented=False) -print('valid' if not v.has_errors(issues) else 'invalid') -""" -# oidc-browser-relying-party@1.1 parameter client_id. -CLIENT_ID_RE = re.compile(r'^[a-z0-9][a-z0-9-]{1,62}$') -CASE_REF_RE = re.compile(r'^[a-z0-9][a-z0-9-]*@sha256:[0-9a-f]{64}$') -# 48 random bytes -> 64 URL-safe characters, confirmed by key-cape 2026-10-10 -# (message 4e0213b3; opaque constant-time comparison, no alphabet limit). -SECRET_BYTES = 48 -TOKEN_TTL = '5m' -SCHEMA = 'platform.keycape-client-secret-generate.v1' - - -class Exit(Exception): - def __init__(self, code: int, reason: str): - super().__init__(reason) - self.code = code - self.reason = reason - - -def bao(args, token=None, payload=None): - env = dict(os.environ) - if token is not None: - env['BAO_TOKEN'] = env['VAULT_TOKEN'] = token - return subprocess.run(['bao', *args], env=env, - input=None if payload is None else json.dumps(payload).encode(), - capture_output=True, timeout=60) - - -def absent(result): - return result.returncode != 0 and (b'404' in result.stderr or b'No value found' in result.stderr) - - -def read_json(result, code): - try: - return json.loads(result.stdout) - except ValueError: - raise Exit(code, 'unparseable_bao_output') from None - - -def authenticated_source(relative, expected): - path = NETKINGDOM_ROOT / relative - # Refuse symlinks at every selected component, including the configured root. - if any(part.is_symlink() for part in (path, *path.parents)): - raise ValueError - raw = path.read_bytes() - if hashlib.sha256(raw).hexdigest() != expected: - raise ValueError - return raw - - -def check_acknowledged_case(doc): - # Authenticates the selected TYPE/producer bytes and validates case schema. - # The caller's self-hashed case reference is not authenticated case admission - # or owner acceptance. Those require independent Warden/native evidence; - # this source-only hardening does not complete the real admission binding. - binding = ACKNOWLEDGED_TYPES.get(doc['spec'].get('type')) - if binding is None: - raise Exit(2, 'case_type_unacknowledged') - try: - typ = yaml.safe_load(authenticated_source(*binding)) - spec = typ['spec'] - version = str(spec['version']) - fingerprint = hashlib.sha256(json.dumps( - spec['parameters'], sort_keys=True, separators=(',', ':')).encode()).hexdigest() - if (spec['status'] not in ('acknowledged', 'implemented') - or f"{spec['id']}@{version}" != doc['spec']['type'] - or PARAMETER_PINS.get(version) != fingerprint): - raise ValueError - with tempfile.TemporaryDirectory(prefix='platform-case-validator-') as directory: - for relative, expected in SOURCE_PINS.items(): - raw = authenticated_source(relative, expected) - dest = Path(directory) / relative - dest.parent.mkdir(parents=True, exist_ok=True) - dest.write_bytes(raw) - result = subprocess.run( - [sys.executable, '-I', '-c', _VALIDATE, directory], - input=json.dumps([doc, typ]), text=True, - stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, - env={}, timeout=10, check=True) - if result.stdout != 'valid\n': - raise ValueError - except (OSError, ValueError, TypeError, KeyError, yaml.YAMLError, - subprocess.SubprocessError): - raise Exit(2, 'case_source_validation_failed') from None - - -def check_case(args, receipt): - if not CLIENT_ID_RE.match(args.client_id) or args.client_id in RESERVED: - raise Exit(2, 'client_id_invalid_or_reserved') - if not CASE_REF_RE.match(args.case_ref): - raise Exit(2, 'case_ref_malformed') - try: - raw = Path(args.case_file).read_bytes() - doc = yaml.safe_load(raw) - except (OSError, yaml.YAMLError): - raise Exit(2, 'case_file_unreadable') from None - spec = (doc or {}).get('spec') if isinstance(doc, dict) else None - if not isinstance(spec, dict): - raise Exit(2, 'case_file_shape') - params = spec.get('parameters') or {} - derived = f"{spec.get('id')}@sha256:{hashlib.sha256(raw).hexdigest()}" - if derived != args.case_ref: - raise Exit(2, 'case_ref_mismatch') - if not isinstance(spec.get('type'), str): - raise Exit(2, 'case_type_unacknowledged') - if spec.get('status') != 'materialized': - raise Exit(2, 'case_not_materialized') - if params.get('client_type') != 'confidential': - raise Exit(2, 'case_client_not_confidential') - if params.get('client_id') != args.client_id: - raise Exit(2, 'case_client_id_mismatch') - check_acknowledged_case(doc) - receipt['case_verified'] = True - - -def run(args, receipt, state): - if args.grant != GRANT: - raise Exit(2, 'unknown_grant') - if os.environ.get('WARDEN_AGENT_ID'): - raise Exit(2, 'agent_callers_refused') - check_case(args, receipt) - - ident = bao(['token', 'lookup', '-format=json']) - if ident.returncode != 0: - raise Exit(2, 'caller_session_missing') - policies = (read_json(ident, 2).get('data') or {}).get('policies') or [] - if 'root' in policies: - raise Exit(2, 'root_refused') - if ISSUER_POLICY not in policies: - raise Exit(2, 'caller_lacks_issuer_policy') - - created = bao(['token', 'create', f'-role={TOKEN_ROLE}', f'-policy={GRANT_POLICY}', - f'-ttl={TOKEN_TTL}', '-format=json']) - if created.returncode != 0: - raise Exit(4, 'grant_token_create_failed') - try: - state['token'] = read_json(created, 4)['auth']['client_token'] - except (KeyError, TypeError): - raise Exit(4, 'grant_token_create_failed') from None - token = state['token'] - - meta_path = f'platform/metadata/{PREFIX}/{args.client_id}' - meta = bao(['read', '-format=json', meta_path], token=token) - if meta.returncode == 0: - raise Exit(3, 'already_present') - if not absent(meta): - raise Exit(2, 'absence_not_proven') - if args.dry_run: - return 'dry_run_would_generate' - - value = secrets.token_urlsafe(SECRET_BYTES) - written = bao(['write', '-format=json', f'platform/data/{PREFIX}/{args.client_id}', '-'], - token=token, payload={'options': {'cas': 0}, 'data': {FIELD: value}}) - del value - if written.returncode != 0: - if bao(['read', '-format=json', meta_path], token=token).returncode == 0: - raise Exit(3, 'already_present_lost_race') - raise Exit(4, 'write_failed') - - meta = bao(['read', '-format=json', meta_path], token=token) - if meta.returncode != 0: - raise Exit(5, 'verify_metadata_unreadable') - data = read_json(meta, 5).get('data') or {} - version = (data.get('versions') or {}).get('1') or {} - if data.get('current_version') != 1 or version.get('deletion_time') or version.get('destroyed'): - raise Exit(5, 'verify_version_mismatch') - receipt['kv_version'] = 1 - return 'generated' - - -def record_state_hub(url, receipt): - body = { - 'summary': (f"keycape-client-secret-generate {receipt['outcome']}: {receipt['path']} " - f"v{receipt.get('kv_version', '-')} case {receipt['case_ref']}"), - 'event_type': 'credential_change_evidence', - 'author': 'railiance-platform-credential-broker', - } - request = urllib.request.Request(url.rstrip('/') + '/progress/', data=json.dumps(body).encode(), - headers={'Content-Type': 'application/json'}, method='POST') - with urllib.request.urlopen(request, timeout=10) as response: - return 200 <= response.status < 300 - - -def main(argv=None): - devnull = os.open(os.devnull, os.O_WRONLY) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--grant', required=True) - parser.add_argument('--client-id', required=True) - parser.add_argument('--case-ref', required=True) - parser.add_argument('--case-file', required=True) - parser.add_argument('--receipt', type=Path) - parser.add_argument('--dry-run', action='store_true') - parser.add_argument('--record-state-hub', action='store_true') - parser.add_argument('--state-hub-url', default=os.environ.get('STATE_HUB_URL', 'http://127.0.0.1:8000')) - try: - args = parser.parse_args(argv) - except SystemExit: - return 2 - receipt = {'schema': SCHEMA, 'mode': 'dry-run' if args.dry_run else 'generate', - 'grant': args.grant, 'client_id': args.client_id, 'case_ref': args.case_ref, - 'path': f'platform/{PREFIX}/{args.client_id}', 'value_reads': 0, - 'credential_values_emitted': False} - fd = None - if args.receipt is not None: - try: - fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - except OSError: - return 2 - state = {} - try: - receipt['outcome'] = run(args, receipt, state) - code = 0 - except Exit as exc: - receipt['outcome'] = exc.reason - code = exc.code - except Exception: - receipt['outcome'] = 'contained_internal_error' - code = 1 - if state.get('token'): - receipt['grant_token_revoked'] = bao(['token', 'revoke', '-self'], token=state['token']).returncode == 0 - receipt['exit_code'] = code - if args.record_state_hub: - try: - receipt['state_hub_recorded'] = record_state_hub(args.state_hub_url, receipt) - except Exception: - receipt['state_hub_recorded'] = False - if fd is not None: - os.write(fd, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode()) - os.close(fd) - return code - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/keycape_issuer_eso.py b/scripts/keycape_issuer_eso.py deleted file mode 100644 index 8acace3..0000000 --- a/scripts/keycape_issuer_eso.py +++ /dev/null @@ -1,70 +0,0 @@ -"""Proposed fixed issuer ESO source contract for RPF-1.3-T4 / NK48-R5-F2. - -Pure public metadata only. No API/credential/Secret access or apply path. This -new declaration requires independent Platform review before type integration. -The delivery lane may use only external_secret(); store provisioning is a -separate owner action and is never delegated by this module. -""" -from __future__ import annotations - -from copy import deepcopy -from pathlib import Path -import re - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -TEMPLATE = ROOT / 'manifests/templates/keycape-issuer-eso.yaml' - - -class Refused(ValueError): - """Fixed issuer metadata differs; never return an owner delivery receipt.""" - - -def documents(client_id: str) -> tuple[dict, dict]: - # Match the frozen type client_id pattern and existing adapter's derived bound. - # No caller can supply a store name/kind/namespace or an OpenBao path. - if not isinstance(client_id, str) or not re.fullmatch(r'[a-z0-9][a-z0-9-]{1,62}', client_id): - raise Refused('bounded admitted client_id required') - if len('keycape-' + client_id + '-client') > 63: - raise Refused('derived issuer reference exceeds existing adapter bound') - store, external = yaml.safe_load_all(TEMPLATE.read_text().format(client_id=client_id)) - return store, external - - -def external_secret(client_id: str) -> dict: - """The ONLY document the proposed delivery capability may apply.""" - return deepcopy(documents(client_id)[1]) - - -def _matching_public_resource(observed: dict, expected: dict) -> None: - if not isinstance(observed, dict): - raise Refused('public resource observation required') - metadata = observed.get('metadata') - if not isinstance(metadata, dict): - raise Refused('resource metadata required') - expected_namespace = expected['metadata'].get('namespace') - namespace_matches = (metadata.get('namespace') == expected_namespace - if expected_namespace else metadata.get('namespace') in (None, '')) - if (observed.get('apiVersion') != expected['apiVersion'] - or observed.get('kind') != expected['kind'] - or metadata.get('name') != expected['metadata']['name'] - or not namespace_matches - or observed.get('spec') != expected['spec']): - raise Refused('fixed issuer resource differs') - status = observed.get('status') - conditions = status.get('conditions', []) if isinstance(status, dict) else [] - if not isinstance(conditions, list) or not any( - isinstance(c, dict) and c.get('type') == 'Ready' and c.get('status') == 'True' - for c in conditions): - raise Refused('fixed issuer resource not ready') - - -def require_fixed_store(observed: dict, client_id: str) -> None: - """Refuse a missing/drifted/not-ready store; never create it through delivery.""" - _matching_public_resource(observed, documents(client_id)[0]) - - -def require_ready_delivery(observed: dict, client_id: str) -> None: - """Spec equality and Ready precede a created/NO_CHANGE owner receipt.""" - _matching_public_resource(observed, documents(client_id)[1]) diff --git a/scripts/kube_route.py b/scripts/kube_route.py deleted file mode 100644 index 801e28c..0000000 --- a/scripts/kube_route.py +++ /dev/null @@ -1,38 +0,0 @@ -"""One configurable cluster route for bounded railiance01 proof scripts. - -KUBECTL_ROUTE selects how a remote command reaches the railiance01 API: - -* ``agent`` (default): ``ssh railiance01 `` as the SSH login itself. - Since the CUST-WP-0073 cutover the agent login has no kubeconfig, so this - route fails closed for agents instead of silently escalating. -* ``founder-sudo``: ``ssh railiance01 sudo -n ``, the founder's - attended admin path (root reads the k3s kubeconfig). ``-n`` never prompts, - so stdin/stdout stay byte-clean pipes (no ``ssh -t``, which would merge - stderr into stdout and rewrite line endings). - -Only these named routes exist; arbitrary shell is never accepted. -""" -import os -import shlex - -SSH = ['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', 'railiance01'] -ROUTES = {'agent': [], 'founder-sudo': ['sudo', '-n']} - - -def route_name(environ=None): - name = (os.environ if environ is None else environ).get('KUBECTL_ROUTE', 'agent') - if name not in ROUTES: - raise ValueError('unknown KUBECTL_ROUTE') - return name - - -def remote_prefix(environ=None): - return list(ROUTES[route_name(environ)]) - - -def ssh_argv(remote_args, environ=None): - """Local argv that runs ``remote_args`` on railiance01 through the selected route.""" - remote_args = [str(a) for a in remote_args] - if not remote_args: - raise ValueError('remote command required') - return SSH + [shlex.join(remote_prefix(environ) + remote_args)] diff --git a/scripts/kv_duplicate_retire.py b/scripts/kv_duplicate_retire.py deleted file mode 100644 index 1dd56cd..0000000 --- a/scripts/kv_duplicate_retire.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python3 -"""Attended, silent retirement of a duplicate KV entry nothing may still read. - -Founder ruling 2026-10-09 (ops-mason message b72ff77d): -``platform/workloads/activity-core/default/llm-connect-provider-secrets`` (v1, -2026-06-18) duplicates the live -``platform/workloads/activity-core/llm-connect/llm-connect-provider-secrets`` and -no source references it. Before the metadata delete this process proves: - -* the canonical entry exists with a live current version; -* the duplicate exists with exactly the expected version count; -* no live ACL policy other than platform-admin grants a non-deny capability on - the duplicate's data path (exact, ``+`` and trailing ``*`` globs). Grants on - the metadata path only (e.g. mount-wide ``platform/metadata/*`` survey and - read-only inventory policies) cannot read the value, so they are recorded in - the receipt as ``metadata_only_policies`` but do not block. - -No value is read. Run from a railiance-platform checkout: - - BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \\ - warden access openbao-platform-admin-login --exec -- \\ - python3 scripts/kv_duplicate_retire.py \\ - --confirm 'RETIRE activity-core/default/llm-connect-provider-secrets' \\ - --receipt "$HOME/kv-duplicate-retire-receipt.json" - -``--dry-run`` runs every check and deletes nothing. The process prints nothing; -the receipt (mode 0600, refused if it exists) holds versions, booleans, the -names of granting policies and an outcome code. - -Exit codes: 0 retired / dry-run clean / already absent; 1 contained internal -error; 2 precondition refused; 3 a live policy still grants the duplicate; -4 delete failed; 5 duplicate still present after delete. -""" -from __future__ import annotations - -import argparse -import json -import os -from pathlib import Path -import re -import sys - -sys.path.insert(0, str(Path(__file__).resolve().parent)) -from tenant_engine_audit_core_sender_seed import Exit, bao, live_version, read_json # noqa: E402 - -DUPLICATE = 'workloads/activity-core/default/llm-connect-provider-secrets' -CANONICAL = 'workloads/activity-core/llm-connect/llm-connect-provider-secrets' -EXPECTED_VERSIONS = 1 -CONFIRM = 'RETIRE activity-core/default/llm-connect-provider-secrets' -SCHEMA = 'platform.kv-duplicate-retire.v1' -EXEMPT_POLICIES = {'platform-admin', 'root'} -STANZA_RE = re.compile(r'path\s+"([^"]+)"\s*\{(.*?)\}', re.S) -CAPS_RE = re.compile(r'capabilities\s*=\s*\[([^\]]*)\]') - - -def glob_matches(pattern, path): - regex = ''.join('[^/]+' if part == '+' else re.escape(part) - for part in re.split(r'(\+)', pattern.rstrip('*'))) - return re.fullmatch(regex + ('.*' if pattern.endswith('*') else ''), path) is not None - - -def granting_stanzas(policy_text, paths): - """Patterns in ``policy_text`` that grant anything but deny on one of ``paths``.""" - hits = [] - for pattern, body in STANZA_RE.findall(policy_text): - caps = CAPS_RE.search(body) - names = {c.strip().strip('"') for c in caps.group(1).split(',')} if caps else set() - if names - {'deny', ''} and 'deny' not in names and any(glob_matches(pattern, p) for p in paths): - hits.append(pattern) - return hits - - -def policy_names(): - result = bao(['policy', 'list', '-format=json']) - if result.returncode != 0: - raise Exit(2, 'policy_list_failed') - try: - names = json.loads(result.stdout) - except ValueError: - raise Exit(2, 'policy_list_unparseable') from None - if not isinstance(names, list) or not names: - raise Exit(2, 'policy_list_unparseable') - return names - - -def run(args, receipt): - if args.confirm != CONFIRM: - raise Exit(2, 'exact_confirmation_required') - if (Path.home().parent.name != '.warden-attended-login' - or os.environ.get('BAO_TOKEN') or os.environ.get('VAULT_TOKEN')): - raise Exit(2, 'attended_warden_envelope_required') - ident = read_json('auth/token/lookup-self', absent_ok=False) - policies = ident.get('policies') or [] - if 'platform-admin' not in policies or 'root' in policies: - raise Exit(2, 'attended_platform_admin_required') - - canonical = read_json('platform/metadata/' + CANONICAL, absent_ok=True) - receipt['canonical_live'] = canonical is not None and live_version(canonical) is not None - if not receipt['canonical_live']: - raise Exit(2, 'canonical_not_live') - duplicate = read_json('platform/metadata/' + DUPLICATE, absent_ok=True) - receipt['duplicate_present_before'] = duplicate is not None - if duplicate is None: - return 'already_absent' - receipt['duplicate_versions'] = len(duplicate.get('versions') or {}) - if duplicate.get('current_version') != EXPECTED_VERSIONS or receipt['duplicate_versions'] != EXPECTED_VERSIONS: - raise Exit(2, 'duplicate_version_unexpected') - - data_path, metadata_path = 'platform/data/' + DUPLICATE, 'platform/metadata/' + DUPLICATE - granting, metadata_only = {}, {} - for name in policy_names(): - if name in EXEMPT_POLICIES: - continue - result = bao(['policy', 'read', name]) - if result.returncode != 0: - raise Exit(2, 'policy_read_failed') - text = result.stdout.decode() - hits = granting_stanzas(text, [data_path]) - if hits: - granting[name] = hits - else: - meta_hits = granting_stanzas(text, [metadata_path]) - if meta_hits: - metadata_only[name] = meta_hits - receipt['granting_policies'] = granting - receipt['metadata_only_policies'] = metadata_only - if granting: - raise Exit(3, 'live_policy_grants_duplicate') - if args.dry_run: - return 'dry_run_would_retire' - - if bao(['delete', 'platform/metadata/' + DUPLICATE]).returncode != 0: - raise Exit(4, 'delete_failed') - if read_json('platform/metadata/' + DUPLICATE, absent_ok=True) is not None: - raise Exit(5, 'duplicate_still_present') - receipt['duplicate_deleted'] = True - return 'retired' - - -def main(argv=None): - devnull = os.open(os.devnull, os.O_WRONLY) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--confirm', default='') - parser.add_argument('--receipt', type=Path) - parser.add_argument('--dry-run', action='store_true') - try: - args = parser.parse_args(argv) - except SystemExit: - return 2 - receipt = {'schema': SCHEMA, 'mode': 'dry-run' if args.dry_run else 'retire', - 'duplicate': 'platform/' + DUPLICATE, 'canonical': 'platform/' + CANONICAL, - 'value_reads': 0} - fd = None - if args.receipt is not None: - try: - fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - except OSError: - return 2 - try: - receipt['outcome'] = run(args, receipt) - code = 0 - except Exit as exc: - receipt['outcome'] = exc.reason - code = exc.code - except Exception: - receipt['outcome'] = 'contained_internal_error' - code = 1 - receipt['exit_code'] = code - if fd is not None: - os.write(fd, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode()) - os.close(fd) - return code - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/openbao-apply-ssh-engine.sh b/scripts/openbao-apply-ssh-engine.sh index db9f2bc..1b78cc4 100755 --- a/scripts/openbao-apply-ssh-engine.sh +++ b/scripts/openbao-apply-ssh-engine.sh @@ -189,8 +189,7 @@ spec = yaml.safe_load(open(spec_path)) roles = spec.get("roles") or {} for role_name, params in roles.items(): args = [f"{k}={v}" for k, v in params.items()] - # The exec wrapper below already runs `bao`; pass only its arguments. - cmd = ["write", f"{mount}/roles/{role_name}"] + args + cmd = ["bao", "write", f"{mount}/roles/{role_name}"] + args proc = subprocess.run( kubectl_parts + ["exec", "-i", "-n", namespace, pod, "--", "sh", "-c", "read -r BAO_TOKEN; export BAO_TOKEN; exec bao \"$@\"", "sh"] + cmd, diff --git a/scripts/openbao-isolated-restore/preflight.py b/scripts/openbao-isolated-restore/preflight.py deleted file mode 100644 index 98b125d..0000000 --- a/scripts/openbao-isolated-restore/preflight.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/usr/bin/env python3 -"""Metadata-only preflight for the isolated original-OpenBao restore (RPF-WP-0064-T02). - -Runs before any custody prompt. Never asks for, opens or decrypts custody -material. Checks: signed admission (exact bytes, sha256, fixed SSH namespace, -protected allowed_signers anchor, window), exact committed source closure, -pinned image digest present locally (``--pull=never``), ciphertext size and -sha256, required tools, ``/dev/shm`` tmpfs and that no prior container of this -adapter exists. Prints only OK/ERROR, never values. -""" -from __future__ import annotations - -import argparse -import importlib.util -import json -import shutil -import subprocess -import sys -from pathlib import Path - -HERE = Path(__file__).resolve().parent -_spec = importlib.util.spec_from_file_location("restore_adapter", HERE / "restore_adapter.py") -adapter = importlib.util.module_from_spec(_spec) -_spec.loader.exec_module(adapter) -require, Refused = adapter.require, adapter.Refused - - -def check_local(adm: dict, ciphertext: Path, docker: str = "docker") -> None: - for tool in ("age", "age-keygen", "ssh-keygen", "git", docker): - require(shutil.which(tool), "preflight_refused") - mounts = Path("/proc/mounts").read_text().splitlines() - require(any(line.split()[1:3] == ["/dev/shm", "tmpfs"] for line in mounts), "preflight_refused") - inspect = subprocess.run([docker, "image", "inspect", adm["image"], "--format", "{{json .RepoDigests}}"], - capture_output=True, timeout=30) - require(inspect.returncode == 0 and adm["image"] in json.loads(inspect.stdout or b"[]"), "preflight_refused") - stale = subprocess.run([docker, "ps", "-a", "-q", "--filter", "name=rpf64-restore-"], - capture_output=True, timeout=30) - require(stale.returncode == 0 and not stale.stdout.strip(), "preflight_refused") - raw = adapter.read_owner_file(ciphertext, 512 * 1024 * 1024) - snap = adm["snapshot"] - require(len(raw) == snap["ciphertext_bytes"] and adapter.sha256_bytes(raw) == snap["ciphertext_sha256"], - "ciphertext_mismatch") - require(raw.startswith(b"age-encryption.org/v1\n"), "ciphertext_mismatch") - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0]) - for name in ("admission", "admission-sig", "admission-sha256", "ciphertext", "receipt"): - parser.add_argument("--" + name, required=True) - parser.add_argument("--mode", choices=(adapter.PHASE_A, adapter.DRILL_MODE), default=adapter.PHASE_A) - opts = parser.parse_args(argv) - try: - adm, _raw, _signer, _protected = adapter.load_admission( - Path(opts.admission), Path(opts.admission_sig), opts.admission_sha256, adapter.anchor_path(), opts.mode) - require(adm["material"] == "original", "admission_refused") - adapter.check_window(adm) - adapter.check_source_closure(adm, adapter.ROOT) - check_local(adm, Path(opts.ciphertext)) - receipt = Path(opts.receipt) - require(receipt.is_absolute() and not receipt.exists() and receipt.parent.is_dir(), "preflight_refused") - except Refused as exc: - print(f"ERROR: preflight refused ({exc.reason}); no custody input requested.") - return 1 - except Exception: - print("ERROR: preflight refused (internal_error); no custody input requested.") - return 1 - print("OK: signed admission, source closure, pinned image, ciphertext and local target verified.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/openbao-isolated-restore/restore_adapter.py b/scripts/openbao-isolated-restore/restore_adapter.py deleted file mode 100644 index 815addf..0000000 --- a/scripts/openbao-isolated-restore/restore_adapter.py +++ /dev/null @@ -1,1165 +0,0 @@ -#!/usr/bin/env python3 -"""Isolated original-OpenBao restore adapter (RPF-WP-0064-T02 phase A; T10 break-glass drill). - -Restores one age-encrypted Raft snapshot into a disposable, networkless Docker -container on the founder workstation, unseals it with the original Shamir -shares, reads back configuration metadata, and tears everything down. It never -runs on railiance01, never uses a kubeconfig, never reads KV ``data`` and never -prints or records a secret value. - -Custody inputs (founder-attended, hidden prompts only, never argv/env/files): -the backup age identity (held in a sealed memfd for ``age -d``) and two -original unseal shares (process memory only). The decrypted snapshot lives in -a sealed memfd and is streamed to the container's stdin; the container's Raft -directory is tmpfs. A throwaway 1-of-1 init makes the empty target writable; -the restore invalidates it. Readback uses a root credential generated in the -restored target from the same two shares, revoked before teardown. - -A reaper process watches the session: on parent loss or the hard deadline it -kills the session, removes the container and writes an ``aborted`` receipt. - -Outcomes: ``restored`` | ``refused`` (required object missing or not matching -its source; never repaired) | ``aborted`` (any other failed check, timeout, -operator abort). Only the launcher ``run-isolated-restore.sh`` is the attended -entry point; ``Session`` is importable for the synthetic rehearsal tests. - -``--mode break-glass-drill`` (RPF-WP-0064-T10, NK-WP-0055-T13) needs its own -signed admission (``drill: "break-glass"``, attempt id ``RPF-WP-0064-T10-*``). -It runs the same restore and unseal, forgets the unseal shares, then: checks a -held root token with ``lookup-self`` only (never revoked or otherwise used); -runs a fresh generate-root ceremony with a server OTP and two re-entered -shares; does one read-only action (``sys/auth``); revokes the generated token -(204) and confirms ``lookup-self`` is refused (403); starts and cancels one -more attempt. Outcomes: ``drilled`` | ``aborted``. No object readback. -""" -from __future__ import annotations - -import base64 -import fcntl -import getpass -import hashlib -import json -import os -import re -import secrets -import select -import signal -import stat -import subprocess -import sys -import time -from datetime import UTC, datetime -from pathlib import Path -from typing import Any, Callable - -ROOT = Path(__file__).resolve().parents[2] -HERE = "scripts/openbao-isolated-restore/" -NAMESPACE = "railiance-openbao-restore-attempt" -ADMISSION_SCHEMA = "railiance-openbao-restore-admission/v1" -RECEIPT_SCHEMA = "railiance-openbao-restore-receipt/v2" -IMAGE_REPOSITORY = "quay.io/openbao/openbao" -# RPF-WP-0064-T06: production digest, observed 2026-10-24 as the imageID of the live -# openbao-0 container (spec image quay.io/openbao/openbao:2.5.4); equal to the T04 -# rehearsal pull. Original-material runs refuse any other image. -PINNED_IMAGE_DIGEST: str | None = "sha256:436eaf9778cad75507ff70ea26ace30dcbe15606e619ac3823495663d7f7c115" -MAX_DURATION_SECONDS = 2700 -LEADER_WAIT_SECONDS = 180 -LEADER_FIELDS = {"ha_enabled": bool, "is_self": bool, "leader_address": str, "leader_cluster_address": str, - "raft_committed_index": int, "raft_applied_index": int, "active_time": str} -SEAL_FIELDS = {"sealed": bool, "initialized": bool, "storage_type": str, "version": str} -_SECRETISH = re.compile(r"\b(?:hvs|hvb|hvr|s|b|r)\.[A-Za-z0-9_-]{8,}|[A-Za-z0-9+/=_-]{32,}") - - -def typed(value, kind): - """Keep a status value only if it has the expected type; bound strings and redact token-like text.""" - if kind is int: - return value if type(value) is int else None - if kind is bool: - return value if type(value) is bool else None - if isinstance(value, str): - return _SECRETISH.sub("[redacted]", value)[:200] - return None - - -def sanitize_status_line(raw: bytes) -> str | None: - """First non-empty stderr line, bounded, with token-like text redacted.""" - for line in raw.decode("utf-8", "replace").splitlines(): - line = line.strip() - if line: - return _SECRETISH.sub("[redacted]", line)[:200] - return None -REAPER_GRACE_SECONDS = 60 -DEFAULT_ANCHOR = Path("/etc/railiance-openbao-restore/allowed_signers") -ANCHOR_ENV = "RAILIANCE_OPENBAO_RESTORE_ANCHOR" -SSH_ENV = {"PATH": "/usr/bin:/bin", "LC_ALL": "C"} -CLAIMS_NOT_MADE = ( - "consumer_continuity", "live_jwt_login", "phase_b_network", - "owner_acceptance", "recurring_restore_assurance", "production_redirect", -) -ADAPTER_PATHS = tuple(HERE + n for n in ( - "restore_adapter.py", "preflight.py", "run-isolated-restore.sh", "verify-restore-receipt.py")) -OBJECT_SOURCE_PATHS = ( - "openbao/auth/keycape-services-config.json", - "openbao/auth/secrets-engine-jwt-role.json", - "openbao/auth/netkingdom-oidc-config.json", - "openbao/auth/netkingdom-platform-admin-role.json", - "openbao/policies/secrets-engine-login-self.hcl", - "docs/contracts/secrets-engine-openbao-jwt-login.yaml", -) -SOURCE_PATHS = tuple(sorted(ADAPTER_PATHS + OBJECT_SOURCE_PATHS)) -KV_METADATA_PATH = "platform/metadata/workloads/keycape/secrets-engine-openbao" - -# Required objects. ``source`` objects compare the declared fields of the -# source file (minus ``ignore``) with the restored readback after normalising. -OBJECTS: tuple[dict[str, Any], ...] = ( - {"object": "auth_mount_keycape_services", "kind": "mount", "path": "sys/auth", - "mount": "keycape-services/", "types": ("jwt",)}, - {"object": "auth_mount_netkingdom", "kind": "mount", "path": "sys/auth", - "mount": "netkingdom/", "types": ("oidc", "jwt")}, - {"object": "kv_mount_platform", "kind": "mount", "path": "sys/mounts", - "mount": "platform/", "types": ("kv",), "kv_version": "2"}, - {"object": "keycape_services_config", "kind": "source", "path": "auth/keycape-services/config", - "source": "openbao/auth/keycape-services-config.json", "ignore": ()}, - {"object": "secrets_engine_jwt_role", "kind": "source", - "path": "auth/keycape-services/role/secrets-engine-openbao", - "source": "openbao/auth/secrets-engine-jwt-role.json", "ignore": ()}, - {"object": "netkingdom_oidc_config", "kind": "source", "path": "auth/netkingdom/config", - "source": "openbao/auth/netkingdom-oidc-config.json", "ignore": ("_note",)}, - {"object": "platform_admin_role", "kind": "source", "path": "auth/netkingdom/role/platform-admin", - "source": "openbao/auth/netkingdom-platform-admin-role.json", "ignore": ()}, - {"object": "policy_secrets_engine_login_self", "kind": "policy", - "path": "sys/policies/acl/secrets-engine-login-self", - "source": "openbao/policies/secrets-engine-login-self.hcl"}, - {"object": "kv_registration_metadata", "kind": "kv_metadata", "path": KV_METADATA_PATH}, -) -ALLOWED_READS = frozenset({"sys/seal-status", "sys/leader", "auth/token/lookup-self", - *(o["path"] for o in OBJECTS)}) -ALLOWED_WRITES = frozenset({"sys/unseal", "sys/generate-root/attempt", "sys/generate-root/update", - "auth/token/revoke-self"}) -# --------------------------------------------------------------------------- T10 break-glass drill -PHASE_A, DRILL_MODE = "phase-a", "break-glass-drill" -DRILL = "break-glass" -GENERATE_ROOT_ATTEMPT = "sys/generate-root/attempt" -MINIMAL_ACTION_PATH = "sys/auth" -# T10 extensions: generate-root status (GET) and cancel (DELETE); sys/auth and -# auth/token/lookup-self are already allowed reads, sys/generate-root/update an allowed write. -ALLOWED_READS = ALLOWED_READS | {GENERATE_ROOT_ATTEMPT} -ALLOWED_DELETES = frozenset({GENERATE_ROOT_ATTEMPT}) -DRILL_READBACK = "generate-root-from-reentered-shares" -DRILL_CLAIMS_NOT_MADE = ("production_root_token_validity", "production_generate_root_availability", - "sso_down_access_path") -DRILL_ABORT_REASONS = frozenset({"held_token_refused", "generate_root_failed", "minimal_action_failed", - "revocation_failed", "cancel_drill_failed"}) -ABORT_REASONS = frozenset({ - "admission_refused", "preflight_refused", "custody_identity_refused", "ciphertext_mismatch", - "decrypt_failed", "plaintext_mismatch", "target_start_failed", "network_containment_failed", - "throwaway_init_failed", "restore_failed", "unseal_refused", "leader_not_elected", "cluster_mismatch", - "applied_index_behind", "readback_credential_failed", "readback_failed", "deadline_exceeded", - "operator_abort", "parent_lost", "internal_error", -}) | DRILL_ABORT_REASONS -# Production runs the chart with setNodeId, so the snapshot's voter is the pod -# name. The target takes the same id; a native rehearsal showed a different id -# still elects a leader after a forced restore, so this is alignment, not the -# T07 attempt -01 cause. -RESTORE_NODE_ID = "openbao-0" -SERVER_CONFIG = json.dumps({ - "storage": {"raft": {"path": "/openbao/data", "node_id": RESTORE_NODE_ID}}, - # OpenBao 2.x disables unauthenticated generate-root by default; the - # readback credential needs it, on a loopback-only listener without a network. - "listener": {"tcp": {"address": "127.0.0.1:8200", "tls_disable": True, - "disable_unauthed_generate_root_endpoints": False}}, - "api_addr": "http://127.0.0.1:8200", "cluster_addr": "http://127.0.0.1:8201", - "disable_mlock": True, "ui": False, -}, separators=(",", ":")) -TMPFS = { - "/openbao/data": "rw,nosuid,nodev,noexec,size=536870912,uid=100,gid=1000,mode=0700", - "/openbao/config": "rw,nosuid,nodev,noexec,size=1048576,uid=100,gid=1000,mode=0700", - "/tmp": "rw,nosuid,nodev,noexec,size=16777216,uid=100,gid=1000,mode=0700", - # The image declares VOLUMEs here; tmpfs overlays stop anonymous volumes. - "/openbao/file": "rw,nosuid,nodev,noexec,size=1048576,uid=100,gid=1000,mode=0700", - "/openbao/logs": "rw,nosuid,nodev,noexec,size=1048576,uid=100,gid=1000,mode=0700", - # Production declares a file audit device at /openbao/audit/openbao-audit.log - # (rapp-openbao values). The restored audit table re-enables it on unseal; if - # the path is unwritable OpenBao seals itself again (T07 attempts -01/-02). - # The log holds HMAC'd entries only and dies with the container. - "/openbao/audit": "rw,nosuid,nodev,noexec,size=16777216,uid=100,gid=1000,mode=0700", -} -DIGEST_RE = re.compile(r"^sha256:[a-f0-9]{64}$") -IMAGE_RE = re.compile(r"^" + re.escape(IMAGE_REPOSITORY) + r"@(sha256:[a-f0-9]{64})$") -AGE_IDENTITY_RE = re.compile(r"^AGE-SECRET-KEY-1[0-9A-Z]{58}$") -AGE_RECIPIENT_RE = re.compile(r"^age1[023456789acdefghjklmnpqrstuvwxyz]{58}$") -SHARE_RE = re.compile(r"^(?:[A-Za-z0-9+/]{40,100}={0,2}|[a-f0-9]{60,130})$") -ATTEMPT_RE = re.compile(r"^RPF-WP-0064-(?:T07|T04)-[A-Za-z0-9-]{3,64}$") -DRILL_ATTEMPT_RE = re.compile(r"^RPF-WP-0064-T10-[A-Za-z0-9-]{3,64}$") -# Snapshot name for the claim limit, e.g. openbao-raft-20261014T1532Z.snap.age. -SNAPSHOT_LABEL_RE = re.compile(r"^(?=.*20\d{6})[a-z0-9][a-z0-9._-]{2,95}$") -HELD_TOKEN_RE = re.compile(r"^[A-Za-z0-9._-]{16,256}$") -POLICY_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9_.-]{0,63}$") -HTTP_CODE_RE = re.compile(rb"Code: (\d{3})\.") -COMMIT_RE = re.compile(r"^[a-f0-9]{40}$") -DURATION_RE = re.compile(r"^(\d+)([smh]?)$") - - -class Refused(Exception): - """A fail-closed check; ``reason`` is an ABORT_REASONS code or ``object_*``.""" - - def __init__(self, reason: str): - super().__init__(reason) - self.reason = reason - - -def require(condition: Any, reason: str) -> None: - if not condition: - raise Refused(reason) - - -def now_utc() -> datetime: - return datetime.now(UTC).replace(microsecond=0) - - -def iso(value: datetime) -> str: - return value.astimezone(UTC).strftime("%Y-%m-%dT%H:%M:%SZ") - - -def parse_iso(value: Any) -> datetime: - require(isinstance(value, str) and re.fullmatch(r"\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ", value), "admission_refused") - return datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC) - - -def sha256_bytes(data: bytes) -> str: - return "sha256:" + hashlib.sha256(data).hexdigest() - - -def canonical(value: Any) -> bytes: - return json.dumps(value, sort_keys=True, separators=(",", ":")).encode() - - -def strict_json(raw: bytes) -> Any: - def unique(pairs: list[tuple[str, Any]]) -> dict[str, Any]: - out: dict[str, Any] = {} - for key, value in pairs: - require(key not in out, "admission_refused") - out[key] = value - return out - return json.loads(raw, object_pairs_hook=unique) - - -def placeholder_digest(value: str) -> bool: - hexpart = value.split(":", 1)[-1] - return len(set(hexpart)) <= 2 - - -# --------------------------------------------------------------------------- admission - -ADMISSION_FIELDS = {"schema", "namespace", "attempt_id", "phase", "phase_b_admitted", "material", - "not_before", "not_after", "max_duration_seconds", "abort_operator", "image", - "adapter_commit", "snapshot", "unseal", "readback_credential", "source_sha256"} -SNAPSHOT_FIELDS = {"ciphertext_sha256", "ciphertext_bytes", "plaintext_sha256", "age_recipient", - "source_cluster_id", "source_applied_index", "source_receipt_sha256"} - - -def validate_admission(adm: Any, mode: str = PHASE_A) -> dict[str, Any]: - """Shape-only validation of the signed admission (no I/O); phase-A and drill admissions are disjoint.""" - require(mode in (PHASE_A, DRILL_MODE), "admission_refused") - drill = mode == DRILL_MODE - fields = ADMISSION_FIELDS | {"drill", "snapshot_label"} if drill else ADMISSION_FIELDS - require(isinstance(adm, dict) and set(adm) == fields, "admission_refused") - require(adm["schema"] == ADMISSION_SCHEMA and adm["namespace"] == NAMESPACE, "admission_refused") - attempt_re = DRILL_ATTEMPT_RE if drill else ATTEMPT_RE - require(isinstance(adm["attempt_id"], str) and attempt_re.fullmatch(adm["attempt_id"]), "admission_refused") - require(adm["phase"] == "A" and adm["phase_b_admitted"] is False, "admission_refused") - require(adm["material"] in ("original", "synthetic"), "admission_refused") - require(adm["readback_credential"] == (DRILL_READBACK if drill else "generate-root-from-entered-shares"), - "admission_refused") - if drill: - require(adm["drill"] == DRILL, "admission_refused") - require(isinstance(adm["snapshot_label"], str) and SNAPSHOT_LABEL_RE.fullmatch(adm["snapshot_label"]), - "admission_refused") - nb, na = parse_iso(adm["not_before"]), parse_iso(adm["not_after"]) - require(nb < na and (na - nb).total_seconds() <= 24 * 3600, "admission_refused") - dur = adm["max_duration_seconds"] - require(type(dur) is int and 60 <= dur <= MAX_DURATION_SECONDS, "admission_refused") - if adm["material"] == "original": - require(dur == MAX_DURATION_SECONDS, "admission_refused") - require(isinstance(adm["abort_operator"], str) and re.fullmatch(r"[a-z][a-z0-9-]{1,63}", adm["abort_operator"]), - "admission_refused") - match = IMAGE_RE.fullmatch(adm["image"]) if isinstance(adm["image"], str) else None - require(match and not placeholder_digest(match.group(1)), "admission_refused") - require(isinstance(adm["adapter_commit"], str) and COMMIT_RE.fullmatch(adm["adapter_commit"]), "admission_refused") - snap = adm["snapshot"] - require(isinstance(snap, dict) and set(snap) == SNAPSHOT_FIELDS, "admission_refused") - for field in ("ciphertext_sha256", "plaintext_sha256", "source_receipt_sha256"): - require(isinstance(snap[field], str) and DIGEST_RE.fullmatch(snap[field]) - and not placeholder_digest(snap[field]), "admission_refused") - require(type(snap["ciphertext_bytes"]) is int and 0 < snap["ciphertext_bytes"] <= 512 * 1024 * 1024, - "admission_refused") - require(isinstance(snap["age_recipient"], str) and AGE_RECIPIENT_RE.fullmatch(snap["age_recipient"]), - "admission_refused") - require(isinstance(snap["source_cluster_id"], str) - and re.fullmatch(r"[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}", snap["source_cluster_id"]), - "admission_refused") - require(type(snap["source_applied_index"]) is int and snap["source_applied_index"] > 0, "admission_refused") - require(adm["unseal"] == {"key_threshold": 2, "key_shares": 3}, "admission_refused") - sources = adm["source_sha256"] - require(isinstance(sources, dict) and set(sources) == set(SOURCE_PATHS), "admission_refused") - for value in sources.values(): - require(isinstance(value, str) and DIGEST_RE.fullmatch(value), "admission_refused") - return adm - - -def source_digests(root: Path) -> dict[str, str]: - out = {} - for name in SOURCE_PATHS: - path = root / name - require(not any(p.is_symlink() for p in (path, *path.parents)) and path.is_file(), "preflight_refused") - out[name] = sha256_bytes(path.read_bytes()) - return out - - -def check_source_closure(adm: dict[str, Any], root: Path) -> None: - """Admission names the exact committed adapter and source files on disk.""" - require(source_digests(root) == adm["source_sha256"], "preflight_refused") - head = subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], capture_output=True, timeout=10) - require(head.returncode == 0 and head.stdout.decode().strip() == adm["adapter_commit"], "preflight_refused") - dirty = subprocess.run(["git", "-C", str(root), "status", "--porcelain", "--", *SOURCE_PATHS], - capture_output=True, timeout=10) - require(dirty.returncode == 0 and not dirty.stdout.strip(), "preflight_refused") - - -def anchor_path() -> Path: - return Path(os.environ.get(ANCHOR_ENV) or DEFAULT_ANCHOR) - - -def anchor_protected(signers: Path) -> bool: - """True if the running account cannot rewrite the trust anchor.""" - euid = os.geteuid() - for path in (signers.parent, signers): - info = os.stat(path, follow_symlinks=False) - if info.st_mode & 0o022 or (info.st_uid == euid and euid != 0): - return False - return True - - -def verify_signature(data: bytes, signature: bytes, signers: Path) -> dict[str, str]: - """``ssh-keygen -Y`` over exact admission bytes with the fixed namespace.""" - import tempfile - require(signers.is_absolute() and not signers.is_symlink() and not signers.parent.is_symlink() - and signers.is_file(), "admission_refused") - require(signature.startswith(b"-----BEGIN SSH SIGNATURE-----") and len(signature) <= 8192, "admission_refused") - with tempfile.TemporaryDirectory(prefix="rpf64-adm-") as tmp: - sig = Path(tmp) / "admission.sig" - sig.write_bytes(signature) - found = subprocess.run(["ssh-keygen", "-Y", "find-principals", "-f", str(signers), "-n", NAMESPACE, - "-s", str(sig)], capture_output=True, env=SSH_ENV, timeout=10) - principals = found.stdout.decode("utf-8", "replace").split() - require(found.returncode == 0 and len(principals) == 1 - and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._@-]{0,127}", principals[0]), "admission_refused") - checked = subprocess.run(["ssh-keygen", "-Y", "verify", "-f", str(signers), "-I", principals[0], - "-n", NAMESPACE, "-s", str(sig)], input=data, capture_output=True, - env=SSH_ENV, timeout=10) - match = re.search(r"key (SHA256:[A-Za-z0-9+/=]+)", checked.stdout.decode("utf-8", "replace")) - require(checked.returncode == 0 and match, "admission_refused") - return {"principal": principals[0], "fingerprint": match.group(1)} - - -def read_owner_file(path: Path, limit: int) -> bytes: - """Regular, owner-only, single-link file read without following symlinks.""" - path = Path(os.path.abspath(path)) - require(not any(p.is_symlink() for p in (path, *path.parents)), "preflight_refused") - fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) - with os.fdopen(fd, "rb") as handle: - info = os.fstat(handle.fileno()) - require(stat.S_ISREG(info.st_mode) and info.st_uid == os.getuid() and info.st_nlink == 1 - and not info.st_mode & 0o077 and 0 < info.st_size <= limit, "preflight_refused") - raw = handle.read(limit + 1) - require(0 < len(raw) <= limit, "preflight_refused") - return raw - - -def load_admission(admission: Path, signature: Path, admission_sha256: str, signers: Path, - mode: str = PHASE_A) -> tuple[dict[str, Any], bytes, dict[str, str], bool]: - raw = read_owner_file(admission, 64 * 1024) - require(DIGEST_RE.fullmatch(admission_sha256) and sha256_bytes(raw) == admission_sha256, "admission_refused") - adm = validate_admission(strict_json(raw), mode) - signer = verify_signature(raw, read_owner_file(signature, 8192), signers) - protected = anchor_protected(signers) - if adm["material"] == "original": - require(protected, "admission_refused") - require(PINNED_IMAGE_DIGEST and adm["image"] == f"{IMAGE_REPOSITORY}@{PINNED_IMAGE_DIGEST}", - "admission_refused") - return adm, raw, signer, protected - - -def check_window(adm: dict[str, Any], at: datetime | None = None) -> None: - at = at or now_utc() - require(parse_iso(adm["not_before"]) <= at < parse_iso(adm["not_after"]), "admission_refused") - - -# --------------------------------------------------------------------------- normalisation - -def _seconds(value: Any) -> Any: - if isinstance(value, str): - match = DURATION_RE.fullmatch(value) - if match: - return int(match.group(1)) * {"": 1, "s": 1, "m": 60, "h": 3600}[match.group(2)] - return value - - -def normalise(value: Any) -> Any: - if isinstance(value, dict): - return {k: normalise(v) for k, v in sorted(value.items())} - if isinstance(value, list): - items = [normalise(v) for v in value] - return sorted(items) if all(isinstance(v, str) for v in items) else items - return _seconds(value) - - -def project(source: dict[str, Any], observed: dict[str, Any], ignore: tuple[str, ...]) -> tuple[bool, dict]: - fields = sorted(k for k in source if k not in ignore) - projected = {k: normalise(observed.get(k)) for k in fields} - expected = {k: normalise(source[k]) for k in fields} - return projected == expected, projected - - -# --------------------------------------------------------------------------- memfd custody - -def sealed_memfd(name: str, data: bytes | bytearray) -> int: - fd = os.memfd_create(name, os.MFD_CLOEXEC | os.MFD_ALLOW_SEALING) - view = memoryview(data) - while view: - view = view[os.write(fd, view):] - fcntl.fcntl(fd, fcntl.F_ADD_SEALS, fcntl.F_SEAL_SHRINK | fcntl.F_SEAL_GROW | fcntl.F_SEAL_WRITE | fcntl.F_SEAL_SEAL) - os.lseek(fd, 0, os.SEEK_SET) - return fd - - -def wipe(buf: bytearray | None) -> None: - if buf is not None: - for i in range(len(buf)): - buf[i] = 0 - - -def http_code(stderr: bytes) -> int | None: - """HTTP status from the bao CLI error text (``Code: 403.``); None if absent.""" - match = HTTP_CODE_RE.search(stderr) - return int(match.group(1)) if match else None - - -def policy_names(value: Any) -> list[str]: - """Policy names only, sorted; anything not shaped like a policy name is dropped.""" - return sorted(p for p in (value if isinstance(value, list) else []) - if isinstance(p, str) and POLICY_NAME_RE.fullmatch(p))[:32] - - -def tty_prompt(label: str) -> bytearray: - """Hidden prompt on the controlling terminal; the value never touches argv/env/disk.""" - require(sys.stdin.isatty(), "preflight_refused") - return bytearray(getpass.getpass(f"{label} (hidden): ").strip().encode()) - - -# --------------------------------------------------------------------------- session - -class Session: - """One phase-A restore attempt. Inject ``prompt``/``docker``/``say`` in tests only.""" - - def __init__(self, *, admission: dict[str, Any], admission_sha256: str, signer: dict[str, str], - anchor_is_protected: bool, ciphertext: Path, receipt_path: Path, root: Path = ROOT, - prompt: Callable[[str], bytearray] = tty_prompt, docker: str = "docker", - say: Callable[[str], None] = lambda m: print(m, flush=True), start_reaper: bool = True, - mode: str = PHASE_A): - require(mode in (PHASE_A, DRILL_MODE) and (mode == DRILL_MODE) == ("drill" in admission), "admission_refused") - self.mode = mode - self.adm, self.root, self.prompt, self.docker, self.say = admission, root, prompt, docker, say - self.ciphertext, self.receipt_path, self.start_reaper = ciphertext, receipt_path, start_reaper - self.name = "rpf64-restore-" + secrets.token_hex(6) - self.deadline = time.monotonic() + admission["max_duration_seconds"] - self.not_after = parse_iso(admission["not_after"]) - self.started = now_utc() - self.container_started = False - self.identity_fd: int | None = None - self.plaintext_fd: int | None = None - self.shares: list[bytearray] = [] - self.credential: str | None = None - self.reaper: subprocess.Popen | None = None - snap = admission["snapshot"] - self.receipt: dict[str, Any] = { - "schema": RECEIPT_SCHEMA, - "extends": "docs/openbao-restore-drill-evidence.example.json", - "attempt_id": admission["attempt_id"], "material": admission["material"], "phase": "A", - "admission_sha256": admission_sha256, "admission_namespace": NAMESPACE, - "signer_principal": signer["principal"], "signer_fingerprint": signer["fingerprint"], - "anchor_protected": anchor_is_protected, "abort_operator": admission["abort_operator"], - "adapter_commit": admission["adapter_commit"], "source_sha256": dict(admission["source_sha256"]), - "started_at": iso(self.started), "finished_at": None, "not_after": admission["not_after"], - "deadline_seconds": admission["max_duration_seconds"], "elapsed_seconds": None, - "image": admission["image"], "image_id": None, - "snapshot": {"ciphertext_sha256": snap["ciphertext_sha256"], "ciphertext_bytes": snap["ciphertext_bytes"], - "plaintext_sha256": snap["plaintext_sha256"], "source_cluster_id": snap["source_cluster_id"], - "source_applied_index": snap["source_applied_index"], - "source_receipt_sha256": snap["source_receipt_sha256"], - "ciphertext_verified": False, "plaintext_verified": False}, - "restored": {"cluster_id": None, "applied_index": None, "sealed_after_restore": None, - "unsealed_with_original_shares": False, "cluster_id_match": False, - "applied_index_not_behind": False, "throwaway_init_invalidated": False, - "leader_wait": None}, - "network": {"network_mode": None, "docker_networks": [], "interfaces": [], "published_ports": [], - "mounts": [], "egress_refused": False}, - "objects": [], "result": None, "reason": None, - "teardown": {"container_removed": False, "container_absent_verified": False, - "plaintext_memfd_closed": True, "custody_memfd_closed": True, - "readback_credential_revoked": None, "docker_volumes_created": 0, "performed_by": "session"}, - "kv_data_read": False, "no_secret_material_recorded": True, - "claims_not_made": list(CLAIMS_NOT_MADE), - } - if mode == DRILL_MODE: - self.receipt["claims_not_made"] += list(DRILL_CLAIMS_NOT_MADE) - self.receipt.update({ - "drill": DRILL, - "held_root_token": {"valid": None, "policies": [], "ttl_zero": None, "expire_time_null": None, - "orphan": None}, - "generate_root": {"started": False, "shares_entered": 0, "complete": False, "otp_decode_ok": False, - "policies": [], "minimal_action_path": MINIMAL_ACTION_PATH, - "minimal_action_ok": False}, - "revocation": {"revoke_status": None, "post_revoke_lookup": None}, - "cancel_drill": {"started_then_cancelled": False}, - # The held-token result is valid only as of this snapshot, never for production now. - "claim_limits": {"production_root_token_validity": { - "as_of_snapshot": admission["snapshot_label"], - "snapshot_plaintext_sha256": snap["plaintext_sha256"]}}, - }) - - # ---- infrastructure - def remaining(self) -> float: - left = min(self.deadline - time.monotonic(), (self.not_after - now_utc()).total_seconds()) - require(left > 0, "deadline_exceeded") - return left - - def run(self, argv: list[str], *, data: bytes = b"", limit: float = 60, check: bool = True, - reason: str = "internal_error") -> subprocess.CompletedProcess: - try: - done = subprocess.run(argv, input=data, capture_output=True, timeout=min(limit, self.remaining())) - except subprocess.TimeoutExpired as exc: - raise Refused("deadline_exceeded" if self.remaining_or_zero() <= 1 else reason) from exc - require(not check or done.returncode == 0, reason) - return done - - def remaining_or_zero(self) -> float: - return max(0.0, min(self.deadline - time.monotonic(), (self.not_after - now_utc()).total_seconds())) - - def bao(self, args: list[str], *, credential: str | None = None, body: bytes = b"", limit: float = 30, - check: bool = True, reason: str = "internal_error") -> subprocess.CompletedProcess: - script = 'IFS= read -r t; BAO_TOKEN="$t"; export BAO_TOKEN; exec "$@"' if credential else 'exec "$@"' - data = (credential.encode() + b"\n" + body) if credential else body - return self.run([self.docker, "exec", "-i", self.name, "sh", "-c", script, "sh", "bao", *args], - data=data, limit=limit, check=check, reason=reason) - - def read(self, path: str, credential: str | None = None, reason: str = "readback_failed") -> dict | None: - require(path in ALLOWED_READS and "/data/" not in path, "internal_error") - done = self.bao(["read", "-format=json", path], credential=credential, check=False, reason=reason) - if done.returncode != 0: - if b"No value found" in done.stderr or done.returncode == 2: - return None - raise Refused(reason) - body = json.loads(done.stdout) - return body.get("data", body) if isinstance(body, dict) else None - - def write(self, path: str, payload: dict, credential: str | None = None, reason: str = "internal_error") -> dict: - require(path in ALLOWED_WRITES, "internal_error") - done = self.bao(["write", "-format=json", path, "-"], credential=credential, body=json.dumps(payload).encode(), - reason=reason) - body = json.loads(done.stdout or b"{}") - return body.get("data", body) if isinstance(body, dict) and isinstance(body.get("data"), dict) else body - - def delete(self, path: str, reason: str = "internal_error") -> None: - require(path in ALLOWED_DELETES, "internal_error") - self.bao(["delete", path], reason=reason) - - def tell_reaper(self, line: str) -> None: - if self.reaper and self.reaper.stdin: - try: - self.reaper.stdin.write((line + "\n").encode()) - self.reaper.stdin.flush() - except OSError: - pass - - def spawn_reaper(self) -> None: - deadline_epoch = time.time() + self.remaining_or_zero() + REAPER_GRACE_SECONDS - header = {"parent_pid": os.getpid(), "deadline_epoch": deadline_epoch, "docker": self.docker, - "receipt_path": str(self.receipt_path), "base_receipt": self.receipt} - self.reaper = subprocess.Popen([sys.executable, str(Path(__file__).resolve()), "--reaper"], - stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, - start_new_session=True, close_fds=True) - self.tell_reaper(json.dumps(header)) - - # ---- steps - def custody_identity(self) -> None: - self.say("Step 1/7: custody identity (hidden prompt).") - identity = self.prompt("Backup age identity") - try: - require(AGE_IDENTITY_RE.fullmatch(identity.decode("ascii", "replace")), "custody_identity_refused") - self.identity_fd = sealed_memfd("rpf64-age-identity", identity + b"\n") - self.receipt["teardown"]["custody_memfd_closed"] = False - finally: - wipe(identity) - done = subprocess.run(["age-keygen", "-y", f"/dev/fd/{self.identity_fd}"], pass_fds=(self.identity_fd,), - capture_output=True, timeout=min(10, self.remaining())) - require(done.returncode == 0 and done.stdout.decode().strip() == self.adm["snapshot"]["age_recipient"], - "custody_identity_refused") - - def decrypt(self) -> None: - self.say("Step 2/7: verify ciphertext and decrypt into a sealed memfd.") - snap = self.adm["snapshot"] - raw = self.ciphertext.read_bytes() - require(len(raw) == snap["ciphertext_bytes"] and sha256_bytes(raw) == snap["ciphertext_sha256"], - "ciphertext_mismatch") - self.receipt["snapshot"]["ciphertext_verified"] = True - os.lseek(self.identity_fd, 0, os.SEEK_SET) - try: - done = subprocess.run(["age", "-d", "-i", f"/dev/fd/{self.identity_fd}"], input=raw, - pass_fds=(self.identity_fd,), capture_output=True, - timeout=min(300, self.remaining())) - except subprocess.TimeoutExpired as exc: - raise Refused("deadline_exceeded") from exc - require(done.returncode == 0 and done.stdout, "decrypt_failed") - plain = bytearray(done.stdout) - del done - try: - require(sha256_bytes(bytes(plain)) == snap["plaintext_sha256"], "plaintext_mismatch") - self.plaintext_fd = sealed_memfd("rpf64-snapshot", plain) - self.receipt["teardown"]["plaintext_memfd_closed"] = False - finally: - wipe(plain) - self.receipt["snapshot"]["plaintext_verified"] = True - self.close_identity() - - def start_target(self) -> None: - self.say("Step 3/7: start networkless disposable target.") - image = self.adm["image"] - inspect = self.run([self.docker, "image", "inspect", image, "--format", "{{json .RepoDigests}}|{{.Id}}"], - reason="target_start_failed") - digests, image_id = inspect.stdout.decode().strip().split("|", 1) - require(image in json.loads(digests) and DIGEST_RE.fullmatch(image_id), "target_start_failed") - self.receipt["image_id"] = image_id - argv = [self.docker, "run", "--detach", "--pull=never", "--name", self.name, "--network", "none", - "--user", "100:1000", "--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges", - "--pids-limit", "256", "--memory", "1g", "--label", "railiance.restore.attempt=" + self.adm["attempt_id"]] - for target, options in TMPFS.items(): - argv += ["--tmpfs", f"{target}:{options}"] - argv += ["-e", "BAO_LOCAL_CONFIG=" + SERVER_CONFIG, "-e", "BAO_ADDR=http://127.0.0.1:8200", - "-e", "SKIP_CHOWN=1", image, "server"] - self.tell_reaper("C " + self.name) - self.container_started = True - self.run(argv, reason="target_start_failed") - self.check_network() - for _ in range(60): - status = self.read("sys/seal-status", reason="target_start_failed") - if status and status.get("initialized") is False: - return - time.sleep(0.5) - self.remaining() - raise Refused("target_start_failed") - - def check_network(self) -> None: - done = self.run([self.docker, "container", "inspect", self.name], reason="network_containment_failed") - info = json.loads(done.stdout)[0] - host, net = info["HostConfig"], info["NetworkSettings"] - record = self.receipt["network"] - record["network_mode"] = host.get("NetworkMode") - record["docker_networks"] = sorted((net.get("Networks") or {}).keys()) - record["published_ports"] = sorted((net.get("Ports") or {}).keys()) - record["mounts"] = sorted(m.get("Type", "?") for m in info.get("Mounts") or []) - require(record["network_mode"] == "none" and record["docker_networks"] == ["none"] - and not record["published_ports"] and not record["mounts"] and not host.get("PortBindings") - and not host.get("Binds") and host.get("Privileged") is False and host.get("ReadonlyRootfs") is True - and sorted((host.get("Tmpfs") or {}).keys()) == sorted(TMPFS), "network_containment_failed") - dev = self.run([self.docker, "exec", self.name, "cat", "/proc/net/dev"], reason="network_containment_failed") - names = sorted(line.split(":", 1)[0].strip() for line in dev.stdout.decode().splitlines()[2:] if ":" in line) - record["interfaces"] = names - require(names == ["lo"], "network_containment_failed") - probe = self.run([self.docker, "exec", self.name, "sh", "-c", - "wget -q -T 3 -O /dev/null http://192.0.2.1/ || exit 7; " - "wget -q -T 3 -O /dev/null https://kc.coulomb.social/ || exit 7; exit 0"], - check=False, limit=20, reason="network_containment_failed") - record["egress_refused"] = probe.returncode == 7 - require(record["egress_refused"], "network_containment_failed") - - def restore(self) -> None: - self.say("Step 4/7: throwaway init, then forced Raft restore from the sealed memfd.") - init = self.bao(["operator", "init", "-key-shares=1", "-key-threshold=1", "-format=json"], - reason="throwaway_init_failed") - throwaway = json.loads(init.stdout) - del init - unseal = self.write("sys/unseal", {"key": throwaway["unseal_keys_b64"][0]}, reason="throwaway_init_failed") - require(unseal.get("sealed") is False, "throwaway_init_failed") - self.wait_active("throwaway_init_failed") - throwaway_credential = throwaway["root_token"] - throwaway.clear() - script = 'IFS= read -r t; BAO_TOKEN="$t"; export BAO_TOKEN; exec bao operator raft snapshot restore -force /dev/stdin' - proc = subprocess.Popen([self.docker, "exec", "-i", self.name, "sh", "-c", script], stdin=subprocess.PIPE, - stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) - try: - proc.stdin.write(throwaway_credential.encode() + b"\n") - os.lseek(self.plaintext_fd, 0, os.SEEK_SET) - while chunk := os.read(self.plaintext_fd, 1 << 20): - proc.stdin.write(chunk) - proc.stdin.close() - code = proc.wait(timeout=min(600, self.remaining())) - except subprocess.TimeoutExpired as exc: - proc.kill() - raise Refused("deadline_exceeded" if self.remaining_or_zero() <= 1 else "restore_failed") from exc - except BrokenPipeError as exc: - raise Refused("restore_failed") from exc - require(code == 0, "restore_failed") - self.close_plaintext() - self.throwaway_credential = throwaway_credential - - def unseal_original(self) -> None: - self.say("Step 5/7: unseal with two original shares (hidden prompts).") - sealed = None - for _ in range(60): - status = self.read("sys/seal-status", reason="unseal_refused") - sealed = bool(status and status.get("sealed")) - if sealed: - break - time.sleep(0.5) - self.remaining() - self.receipt["restored"]["sealed_after_restore"] = sealed - require(sealed, "unseal_refused") - for index in (1, 2): - share = self.prompt(f"Original unseal share {index} of 2") - if not SHARE_RE.fullmatch(share.decode("ascii", "replace")): - wipe(share) - raise Refused("unseal_refused") - self.shares.append(share) - result = self.write("sys/unseal", {"key": share.decode()}, reason="unseal_refused") - require(result.get("sealed") is False, "unseal_refused") - self.receipt["restored"]["unsealed_with_original_shares"] = True - leader = self.wait_active("leader_not_elected") - status = self.read("sys/seal-status", reason="unseal_refused") or {} - snap, restored = self.adm["snapshot"], self.receipt["restored"] - restored["cluster_id"] = status.get("cluster_id") - restored["applied_index"] = leader.get("raft_applied_index") - restored["cluster_id_match"] = restored["cluster_id"] == snap["source_cluster_id"] - restored["applied_index_not_behind"] = (isinstance(restored["applied_index"], int) - and restored["applied_index"] >= snap["source_applied_index"]) - lookup = self.bao(["read", "-format=json", "auth/token/lookup-self"], credential=self.throwaway_credential, - check=False) - restored["throwaway_init_invalidated"] = lookup.returncode != 0 - self.throwaway_credential = None - require(restored["throwaway_init_invalidated"], "restore_failed") - require(restored["cluster_id_match"], "cluster_mismatch") - require(restored["applied_index_not_behind"], "applied_index_behind") - - def wait_active(self, reason: str) -> dict: - # Bounded, non-secret diagnostics: T07 attempt -01 timed out here with no - # trace. Only typed status fields and a sanitized first error line are kept. - started, polls, leader, seal, error = time.monotonic(), 0, {}, {}, None - wait = self.receipt["restored"] - try: - while time.monotonic() - started < LEADER_WAIT_SECONDS: - polls += 1 - done = self.bao(["read", "-format=json", "sys/leader"], check=False, reason=reason) - if done.returncode == 0: - body = json.loads(done.stdout) - leader = body.get("data", body) if isinstance(body, dict) else {} - error = None - if leader.get("is_self") and isinstance(leader.get("raft_applied_index"), int) \ - and leader["raft_applied_index"] > 0: - return leader - else: - error = sanitize_status_line(done.stderr) - time.sleep(1) - self.remaining() - status = self.bao(["read", "-format=json", "sys/seal-status"], check=False, reason=reason) - if status.returncode == 0: - body = json.loads(status.stdout) - seal = body.get("data", body) if isinstance(body, dict) else {} - raise Refused(reason) - finally: - wait["leader_wait"] = { - "waited_seconds": int(time.monotonic() - started), "polls": polls, - "elected": bool(leader.get("is_self")) and isinstance(leader.get("raft_applied_index"), int) - and leader.get("raft_applied_index", 0) > 0, - "last_leader": {k: typed(leader.get(k), t) for k, t in LEADER_FIELDS.items()}, - "last_seal": {k: typed(seal.get(k), t) for k, t in SEAL_FIELDS.items()}, - "last_error": error, - } - - def generate_credential(self) -> None: - self.say("Step 6/7: generate an in-memory readback credential from the entered shares.") - attempt = self.write("sys/generate-root/attempt", {}, reason="readback_credential_failed") - otp, nonce, encoded = attempt.get("otp"), attempt.get("nonce"), None - require(otp and nonce, "readback_credential_failed") - for share in self.shares: - update = self.write("sys/generate-root/update", {"key": share.decode(), "nonce": nonce}, - reason="readback_credential_failed") - encoded = update.get("encoded_token") or update.get("encoded_root_token") or encoded - require(encoded, "readback_credential_failed") - raw = base64.b64decode(encoded + "=" * (-len(encoded) % 4)) - require(len(raw) == len(otp), "readback_credential_failed") - self.credential = bytes(a ^ b for a, b in zip(raw, otp.encode())).decode() - for share in self.shares: - wipe(share) - self.shares.clear() - - def readback(self) -> list[dict[str, Any]]: - self.say("Step 7/7: metadata-only readback against the committed sources.") - rows = [] - cache: dict[str, Any] = {} - for spec in OBJECTS: - row = {"object": spec["object"], "path": spec["path"], "present": False, "source_match": False, - "source_path": spec.get("source"), "observed_sha256": None} - if spec["kind"] == "mount": - if spec["path"] not in cache: - cache[spec["path"]] = self.read(spec["path"], self.credential) or {} - mount = cache[spec["path"]].get(spec["mount"]) - if isinstance(mount, dict): - row["present"] = True - observed = {"type": mount.get("type"), - "kv_version": (mount.get("options") or {}).get("version")} - row["source_match"] = observed["type"] in spec["types"] and \ - ("kv_version" not in spec or observed["kv_version"] == spec["kv_version"]) - row["observed_sha256"] = sha256_bytes(canonical(observed)) - elif spec["kind"] == "source": - observed = self.read(spec["path"], self.credential) - if isinstance(observed, dict): - row["present"] = True - source = json.loads((self.root / spec["source"]).read_bytes()) - row["source_match"], projected = project(source, observed, spec["ignore"]) - row["observed_sha256"] = sha256_bytes(canonical(projected)) - elif spec["kind"] == "policy": - observed = self.read(spec["path"], self.credential) - text = (observed or {}).get("policy") - if isinstance(text, str): - row["present"] = True - row["source_match"] = text.strip() == (self.root / spec["source"]).read_text().strip() - row["observed_sha256"] = sha256_bytes(text.strip().encode()) - elif spec["kind"] == "kv_metadata": - meta = self.read(spec["path"], self.credential) - if isinstance(meta, dict): - current = meta.get("current_version") - version = (meta.get("versions") or {}).get(str(current)) or {} - observed = {"current_version": current, "versions": len(meta.get("versions") or {}), - "created_time": meta.get("created_time"), "updated_time": meta.get("updated_time"), - "current_deleted": bool(version.get("deletion_time")), - "current_destroyed": bool(version.get("destroyed"))} - row["present"] = isinstance(current, int) and current > 0 - row["source_match"] = row["present"] and not observed["current_deleted"] \ - and not observed["current_destroyed"] - row["observed_sha256"] = sha256_bytes(canonical(observed)) - rows.append(row) - self.receipt["objects"] = rows - return rows - - # ---- T10 break-glass drill steps (after restore + unseal) - def forget_unseal_shares(self) -> None: - """The drill re-enters shares for generate-root; the unseal entries are not reused.""" - for share in self.shares: - wipe(share) - self.shares.clear() - - def drill_held_token(self) -> None: - self.say("Drill 1/5: held root-token check (hidden prompt; lookup-self only, never revoked).") - held = self.prompt("Held initial root token") - try: - require(HELD_TOKEN_RE.fullmatch(held.decode("ascii", "replace")), "held_token_refused") - done = self.bao(["read", "-format=json", "auth/token/lookup-self"], credential=held.decode(), - check=False, reason="held_token_refused") - finally: - wipe(held) - record = self.receipt["held_root_token"] - if done.returncode == 0: - body = json.loads(done.stdout) - data = body.get("data") if isinstance(body, dict) and isinstance(body.get("data"), dict) else {} - record.update(valid=True, policies=policy_names(data.get("policies")), - ttl_zero=type(data.get("ttl")) is int and data["ttl"] == 0, - expire_time_null="expire_time" in data and data["expire_time"] is None, - orphan=data["orphan"] if type(data.get("orphan")) is bool else None) - body.clear() - data = {} - elif http_code(done.stderr) == 403: - record.update(valid=False) - else: - raise Refused("held_token_refused") - - def drill_generate_root(self) -> None: - self.say("Drill 2/5: fresh generate-root ceremony; re-enter two shares (hidden prompts).") - record = self.receipt["generate_root"] - status = self.read(GENERATE_ROOT_ATTEMPT, reason="generate_root_failed") or {} - require(status.get("started") is False, "generate_root_failed") - attempt = self.write(GENERATE_ROOT_ATTEMPT, {}, reason="generate_root_failed") - otp, nonce, encoded, update = attempt.get("otp"), attempt.get("nonce"), None, {} - attempt.clear() - require(isinstance(otp, str) and otp and isinstance(nonce, str) and nonce, "generate_root_failed") - record["started"] = True - for index in (1, 2): - share = self.prompt(f"Re-enter unseal share {index} of 2 for generate-root") - try: - require(SHARE_RE.fullmatch(share.decode("ascii", "replace")), "generate_root_failed") - update = self.write("sys/generate-root/update", {"key": share.decode(), "nonce": nonce}, - reason="generate_root_failed") - finally: - wipe(share) - record["shares_entered"] = index - encoded = update.get("encoded_token") or update.get("encoded_root_token") or encoded - record["complete"] = update.get("complete") is True and bool(encoded) - require(record["complete"], "generate_root_failed") - try: - raw = base64.b64decode(encoded + "=" * (-len(encoded) % 4)) - require(len(raw) == len(otp), "generate_root_failed") - self.credential = bytes(a ^ b for a, b in zip(raw, otp.encode())).decode("ascii") - except (ValueError, UnicodeDecodeError) as exc: - raise Refused("generate_root_failed") from exc - finally: - otp = nonce = encoded = None - update.clear() - record["otp_decode_ok"] = True - lookup = self.read("auth/token/lookup-self", self.credential, reason="generate_root_failed") or {} - record["policies"] = policy_names(lookup.get("policies")) - lookup.clear() - require(record["policies"] == ["root"], "generate_root_failed") - - def drill_minimal_action(self) -> None: - self.say("Drill 3/5: one read-only action (sys/auth metadata).") - mounts = self.read(MINIMAL_ACTION_PATH, self.credential, reason="minimal_action_failed") - ok = isinstance(mounts, dict) and isinstance(mounts.get("token/"), dict) - self.receipt["generate_root"]["minimal_action_ok"] = ok - require(ok, "minimal_action_failed") - - def drill_revoke(self) -> None: - self.say("Drill 4/5: revoke-self the generated token, then confirm lookup-self is refused.") - record = self.receipt["revocation"] - require("auth/token/revoke-self" in ALLOWED_WRITES, "internal_error") - done = self.bao(["write", "-format=json", "auth/token/revoke-self", "-"], credential=self.credential, - body=b"{}", check=False, reason="revocation_failed") - # The CLI prints nothing for 204 No Content; any response body means another status. - record["revoke_status"] = (204 if not done.stdout.strip() else 200) if done.returncode == 0 \ - else http_code(done.stderr) - check = self.bao(["read", "-format=json", "auth/token/lookup-self"], credential=self.credential, - check=False, reason="revocation_failed") - record["post_revoke_lookup"] = 200 if check.returncode == 0 else http_code(check.stderr) - revoked = record["revoke_status"] == 204 and record["post_revoke_lookup"] == 403 - self.receipt["teardown"]["readback_credential_revoked"] = revoked - require(revoked, "revocation_failed") - self.credential = None - - def drill_cancel(self) -> None: - self.say("Drill 5/5: start one more generate-root attempt and cancel it.") - attempt = self.write(GENERATE_ROOT_ATTEMPT, {}, reason="cancel_drill_failed") - started = attempt.get("started") is True - attempt.clear() - self.delete(GENERATE_ROOT_ATTEMPT, reason="cancel_drill_failed") - status = self.read(GENERATE_ROOT_ATTEMPT, reason="cancel_drill_failed") or {} - ok = started and status.get("started") is False and status.get("progress") == 0 - self.receipt["cancel_drill"]["started_then_cancelled"] = ok - require(ok, "cancel_drill_failed") - - def revoke(self) -> None: - if not self.credential or not self.container_started: - return - try: - self.write("auth/token/revoke-self", {}, credential=self.credential) - check = self.bao(["read", "-format=json", "auth/token/lookup-self"], credential=self.credential, - check=False) - self.receipt["teardown"]["readback_credential_revoked"] = check.returncode != 0 - except Exception: - self.receipt["teardown"]["readback_credential_revoked"] = False - finally: - self.credential = None - - # ---- teardown - def close_identity(self) -> None: - if self.identity_fd is not None: - os.close(self.identity_fd) - self.identity_fd = None - self.receipt["teardown"]["custody_memfd_closed"] = True - - def close_plaintext(self) -> None: - if self.plaintext_fd is not None: - os.close(self.plaintext_fd) - self.plaintext_fd = None - self.receipt["teardown"]["plaintext_memfd_closed"] = True - - def teardown(self) -> None: - self.revoke() - self.close_identity() - self.close_plaintext() - for share in self.shares: - wipe(share) - self.shares.clear() - self.throwaway_credential = None - td = self.receipt["teardown"] - subprocess.run([self.docker, "rm", "-f", "-v", self.name], capture_output=True, timeout=60) - absent = subprocess.run([self.docker, "container", "inspect", self.name], capture_output=True, timeout=30) - td["container_removed"] = td["container_absent_verified"] = absent.returncode != 0 - - def write_receipt(self) -> None: - finished = now_utc() - self.receipt["finished_at"] = iso(finished) - self.receipt["elapsed_seconds"] = int((finished - self.started).total_seconds()) - fd = os.open(self.receipt_path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "w") as out: - json.dump(self.receipt, out, indent=2, sort_keys=True) - out.write("\n") - - def execute(self) -> dict[str, Any]: - self.throwaway_credential = None - handlers = {} - - def interrupted(signum, _frame): - raise Refused("operator_abort") - for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP): - handlers[sig] = signal.signal(sig, interrupted) - result, reason = "aborted", "internal_error" - try: - check_window(self.adm) - if self.start_reaper: - self.spawn_reaper() - self.custody_identity() - self.decrypt() - self.start_target() - self.restore() - self.unseal_original() - if self.mode == DRILL_MODE: - self.forget_unseal_shares() - self.drill_held_token() - self.drill_generate_root() - self.drill_minimal_action() - self.drill_revoke() - self.drill_cancel() - result, reason = "drilled", "break_glass_drill_complete" - else: - self.generate_credential() - rows = self.readback() - missing = [r for r in rows if not r["present"]] - mismatch = [r for r in rows if r["present"] and not r["source_match"]] - if missing: - result, reason = "refused", "object_missing" - elif mismatch: - result, reason = "refused", "object_mismatch" - else: - result, reason = "restored", "all_required_objects_match" - except Refused as exc: - result, reason = "aborted", exc.reason if exc.reason in ABORT_REASONS else "internal_error" - except Exception: - result, reason = "aborted", "internal_error" - finally: - try: - self.teardown() - finally: - for sig, handler in handlers.items(): - signal.signal(sig, handler) - self.receipt["result"], self.receipt["reason"] = result, reason - self.write_receipt() - self.tell_reaper("DONE") - if self.reaper: - try: - self.reaper.stdin.close() - self.reaper.wait(timeout=10) - except Exception: - pass - self.say(f"Result: {result} ({reason}). Receipt written; verify it independently.") - return self.receipt - - -# --------------------------------------------------------------------------- reaper - -def reaper_main() -> int: - """Backstop: on parent loss or hard deadline remove containers, write ``aborted``.""" - stream = sys.stdin.buffer - buffer = b"" - while b"\n" not in buffer: - chunk = os.read(stream.fileno(), 65536) - if not chunk: - return 1 - buffer += chunk - first, buffer = buffer.split(b"\n", 1) - header = json.loads(first) - parent, deadline = header["parent_pid"], header["deadline_epoch"] - try: - parent_fd = os.pidfd_open(parent) - except OSError: - parent_fd = None - names: list[str] = [] - reason = "parent_lost" - while True: - while b"\n" in buffer: - line, buffer = buffer.split(b"\n", 1) - text = line.decode() - if text == "DONE": - return 0 - if text.startswith("C ") and re.fullmatch(r"rpf64-restore-[a-f0-9]{12}", text[2:]): - names.append(text[2:]) - wait = deadline - time.time() - if wait <= 0: - reason = "deadline_exceeded" - break - ready, _, _ = select.select([stream], [], [], min(wait, 5)) - if not ready: - continue - chunk = os.read(stream.fileno(), 4096) - if not chunk: - break - buffer += chunk - if parent_fd is not None: - try: - signal.pidfd_send_signal(parent_fd, signal.SIGKILL) - except OSError: - pass - select.select([parent_fd], [], [], 10) - os.close(parent_fd) - removed = True - for name in names: - subprocess.run([header["docker"], "rm", "-f", "-v", name], capture_output=True, timeout=60) - absent = subprocess.run([header["docker"], "container", "inspect", name], capture_output=True, timeout=30) - removed = removed and absent.returncode != 0 - receipt = header["base_receipt"] - finished = now_utc() - receipt.update({"result": "aborted", "reason": reason, "finished_at": iso(finished), - "elapsed_seconds": int((finished - parse_iso(receipt["started_at"])).total_seconds())}) - receipt["teardown"].update({"container_removed": removed, "container_absent_verified": removed, - "plaintext_memfd_closed": True, "custody_memfd_closed": True, - "readback_credential_revoked": None, "performed_by": "reaper"}) - try: - fd = os.open(header["receipt_path"], os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - except FileExistsError: - return 0 - with os.fdopen(fd, "w") as out: - json.dump(receipt, out, indent=2, sort_keys=True) - out.write("\n") - return 0 - - -def main(argv: list[str] | None = None) -> int: - import argparse - args = argv if argv is not None else sys.argv[1:] - if args == ["--reaper"]: - return reaper_main() - parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0]) - for name in ("admission", "admission-sig", "admission-sha256", "ciphertext", "receipt"): - parser.add_argument("--" + name, required=True) - parser.add_argument("--mode", choices=(PHASE_A, DRILL_MODE), default=PHASE_A) - opts = parser.parse_args(args) - os.umask(0o077) - try: - adm, _raw, signer, protected = load_admission(Path(opts.admission), Path(opts.admission_sig), - opts.admission_sha256, anchor_path(), opts.mode) - require(adm["material"] == "original", "admission_refused") - check_source_closure(adm, ROOT) - receipt = Path(opts.receipt) - require(receipt.is_absolute() and not receipt.exists() and receipt.parent.is_dir(), "preflight_refused") - except Refused as exc: - print(f"ERROR: {exc.reason}; no custody input requested.", file=sys.stderr) - return 1 - session = Session(admission=adm, admission_sha256=opts.admission_sha256, signer=signer, - anchor_is_protected=protected, ciphertext=Path(opts.ciphertext), receipt_path=receipt, - mode=opts.mode) - result = session.execute() - verify = subprocess.run([sys.executable, str(ROOT / HERE / "verify-restore-receipt.py"), "--receipt", - str(receipt), "--source-root", str(ROOT)], capture_output=True, timeout=60) - print("Independent verifier: " + ("PASS" if verify.returncode == 0 else "FAIL")) - return 0 if result["result"] in ("restored", "refused", "drilled") and verify.returncode == 0 else 2 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/openbao-isolated-restore/run-isolated-restore.sh b/scripts/openbao-isolated-restore/run-isolated-restore.sh deleted file mode 100644 index e1c715d..0000000 --- a/scripts/openbao-isolated-restore/run-isolated-restore.sh +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/env bash -# Attended launcher: isolated original-OpenBao restore, phase A or T10 break-glass drill (RPF-WP-0064). -# Founder workstation only. Never railiance01, never a kubeconfig, no network. -set +x -set -euo pipefail -ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)" -cd -- "$ROOT" -umask 077 -unset KUBECONFIG BAO_TOKEN VAULT_TOKEN BAO_ADDR VAULT_ADDR -usage='Usage: run-isolated-restore.sh [--break-glass-drill] ADMISSION ADMISSION_SIG ADMISSION_SHA256 CIPHERTEXT RECEIPT_DIR' -MODE=phase-a -if [[ "${1:-}" == --break-glass-drill ]]; then MODE=break-glass-drill; shift; fi -[[ $# == 5 ]] || { printf '%s\n' "$usage"; exit 1; } -[[ "$3" =~ ^sha256:[a-f0-9]{64}$ ]] || { printf '%s\n' 'ERROR: exact sha256: admission digest required.'; exit 1; } -for index in 1 2 4; do - [[ -f "${!index}" && ! -L "${!index}" ]] || { printf '%s\n' 'ERROR: provide regular owner-controlled input files.'; exit 1; } -done -[[ -d "$5" && ! -L "$5" && "$5" == /* ]] || { printf '%s\n' 'ERROR: RECEIPT_DIR must be an existing absolute directory.'; exit 1; } -[[ -t 0 && -t 1 && -t 2 ]] || { printf '%s\n' 'ERROR: run in your own interactive terminal.'; exit 1; } -[[ "$(hostname -s)" != railiance01* ]] || { printf '%s\n' 'ERROR: never on railiance01.'; exit 1; } -for tool in python3 age age-keygen docker ssh-keygen git; do - command -v "$tool" >/dev/null || { printf 'ERROR: required tool unavailable: %s\n' "$tool"; exit 1; } -done -RECEIPT="$5/rpf64-${MODE}-$(date -u +%Y%m%dT%H%M%SZ).json" -python3 scripts/openbao-isolated-restore/preflight.py --mode "$MODE" --admission "$1" --admission-sig "$2" \ - --admission-sha256 "$3" --ciphertext "$4" --receipt "$RECEIPT" -if [[ "$MODE" == break-glass-drill ]]; then - printf '%s\n' 'Break-glass drill (RPF-WP-0064-T10) on an isolated, networkless restore. Never production.' - printf '%s\n' 'Prompts: backup age identity, two unseal shares, the held initial root token (lookup-self only),' - printf '%s\n' 'then two shares AGAIN for a fresh generate-root ceremony. Production root-token validity,' - printf '%s\n' 'production generate-root availability and the SSO-down access path are not claimed.' -else - printf '%s\n' 'Phase A only: networkless restore and metadata readback. No consumer continuity,' - printf '%s\n' 'live JWT login, phase B network or owner acceptance is claimed.' - printf '%s\n' 'You will be asked for the backup age identity, then two original unseal shares.' -fi -printf '%s\n' 'Press Ctrl-C at any time to abort; the container is removed and an aborted receipt written.' -python3 scripts/openbao-isolated-restore/restore_adapter.py --mode "$MODE" --admission "$1" --admission-sig "$2" \ - --admission-sha256 "$3" --ciphertext "$4" --receipt "$RECEIPT" -printf 'Safe receipt: %s\n' "$RECEIPT" diff --git a/scripts/openbao-isolated-restore/verify-restore-receipt.py b/scripts/openbao-isolated-restore/verify-restore-receipt.py deleted file mode 100644 index 16210f5..0000000 --- a/scripts/openbao-isolated-restore/verify-restore-receipt.py +++ /dev/null @@ -1,384 +0,0 @@ -#!/usr/bin/env python3 -"""Independent verifier for v2 isolated OpenBao restore receipts (RPF-WP-0064-T03). - -Does not import the adapter. Checks the exact schema, typed digests, result -consistency, deadline, required teardown evidence, claims not made, refuses -placeholders and anything shaped like a credential, share, key or KV value, -and compares ``source_sha256`` against a clean checkout at ``adapter_commit``. - -RPF-WP-0064-T10 break-glass drill receipts (``drill: "break-glass"``) carry a -typed extension: held-token facts, generate-root ceremony facts, revocation -status codes, the cancel drill and the snapshot the held-token result is -limited to. Every extension value is an enumerated, typed fact; anything else -(token, accessor, OTP, nonce, encoded token, share) is rejected. - -Exit 0 = receipt accepted. Prints only PASS/FAIL and a reason code. -""" -from __future__ import annotations - -import argparse -import hashlib -import json -import re -import subprocess -import sys -from datetime import UTC, datetime -from pathlib import Path - -SCHEMA = "railiance-openbao-restore-receipt/v2" -NAMESPACE = "railiance-openbao-restore-attempt" -MAX_DURATION = 2700 -TOP = {"schema", "extends", "attempt_id", "material", "phase", "admission_sha256", "admission_namespace", - "signer_principal", "signer_fingerprint", "anchor_protected", "abort_operator", "adapter_commit", - "source_sha256", "started_at", "finished_at", "not_after", "deadline_seconds", "elapsed_seconds", - "image", "image_id", "snapshot", "restored", "network", "objects", "result", "reason", "teardown", - "kv_data_read", "no_secret_material_recorded", "claims_not_made"} -SNAPSHOT = {"ciphertext_sha256", "ciphertext_bytes", "plaintext_sha256", "source_cluster_id", - "source_applied_index", "source_receipt_sha256", "ciphertext_verified", "plaintext_verified"} -RESTORED = {"cluster_id", "applied_index", "sealed_after_restore", "unsealed_with_original_shares", - "cluster_id_match", "applied_index_not_behind", "throwaway_init_invalidated", "leader_wait"} -LEADER_WAIT = {"waited_seconds", "polls", "elected", "last_leader", "last_seal", "last_error"} -LEADER_FIELDS = {"ha_enabled": bool, "is_self": bool, "leader_address": str, "leader_cluster_address": str, - "raft_committed_index": int, "raft_applied_index": int, "active_time": str} -SEAL_FIELDS = {"sealed": bool, "initialized": bool, "storage_type": str, "version": str} - - -def typed_or_none(value: object, kind: type) -> bool: - if value is None: - return True - if kind is str: - return isinstance(value, str) and len(value) <= 200 - return type(value) is kind -NETWORK = {"network_mode", "docker_networks", "interfaces", "published_ports", "mounts", "egress_refused"} -TEARDOWN = {"container_removed", "container_absent_verified", "plaintext_memfd_closed", "custody_memfd_closed", - "readback_credential_revoked", "docker_volumes_created", "performed_by"} -OBJECT = {"object", "path", "present", "source_match", "source_path", "observed_sha256"} -REQUIRED_OBJECTS = ( - "auth_mount_keycape_services", "auth_mount_netkingdom", "kv_mount_platform", "keycape_services_config", - "secrets_engine_jwt_role", "netkingdom_oidc_config", "platform_admin_role", - "policy_secrets_engine_login_self", "kv_registration_metadata", -) -DRILL = "break-glass" -DRILL_TOP = {"drill", "held_root_token", "generate_root", "revocation", "cancel_drill", "claim_limits"} -HELD = {"valid", "policies", "ttl_zero", "expire_time_null", "orphan"} -GENERATE_ROOT = {"started", "shares_entered", "complete", "otp_decode_ok", "policies", "minimal_action_path", - "minimal_action_ok"} -REVOCATION = {"revoke_status", "post_revoke_lookup"} -DRILL_CLAIMS = {"production_root_token_validity", "production_generate_root_availability", "sso_down_access_path"} -DRILL_ABORT = {"held_token_refused", "generate_root_failed", "minimal_action_failed", "revocation_failed", - "cancel_drill_failed"} -POLICY_NAME = re.compile(r"^[a-z0-9][a-z0-9_.-]{0,63}$") -SNAPSHOT_LABEL = re.compile(r"^(?=.*20\d{6})[a-z0-9][a-z0-9._-]{2,95}$") -CLAIMS = {"consumer_continuity", "live_jwt_login", "phase_b_network", "owner_acceptance", - "recurring_restore_assurance", "production_redirect"} -ABORT = {"admission_refused", "preflight_refused", "custody_identity_refused", "ciphertext_mismatch", - "decrypt_failed", "plaintext_mismatch", "target_start_failed", "network_containment_failed", - "throwaway_init_failed", "restore_failed", "unseal_refused", "leader_not_elected", "cluster_mismatch", - "applied_index_behind", - "readback_credential_failed", "readback_failed", "deadline_exceeded", "operator_abort", "parent_lost", - "internal_error"} -DIGEST = re.compile(r"^sha256:[a-f0-9]{64}$") -TIME = re.compile(r"^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ$") -UUID = re.compile(r"^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$") -# Secret-shaped values: OpenBao/Vault credentials, age identities, PEM blocks, -# base64 or hex blobs the size of unseal shares/keys, JWT-like triples. -SECRET_SHAPES = ( - re.compile(r"\b(?:hv[sbr]|s|b|r)\.[A-Za-z0-9_-]{20,}"), - re.compile(r"AGE-SECRET-KEY-1", re.I), - re.compile(r"-----BEGIN"), - re.compile(r"(? bool: - """Random base64 (shares, keys) mixes cases and digits; repo paths do not.""" - return any(re.search(r"[A-Z]", m) and re.search(r"[a-z]", m) and re.search(r"[0-9+]", m) - for m in BASE64_BLOB.findall(text)) - - -def mixed_run(text: str) -> bool: - return any(re.search(r"[A-Z]", m) and re.search(r"[a-z]", m) and re.search(r"[0-9]", m) - for m in MIXED_RUN.findall(text)) -PLACEHOLDERS = re.compile(r"(?i)replace-with|placeholder|todo|changeme|<[^>]*>|example\.invalid|YYYY") -FORBIDDEN_KEYS = re.compile(r"(?i)^(?:data|value|values|secret|secrets|token|root_token|unseal_keys.*|keys?|" - r"otp|nonce|encoded_.*|password|plaintext|accessor|client_token|shares?|id)$") - - -class Reject(Exception): - pass - - -def need(condition: object, code: str) -> None: - if not condition: - raise Reject(code) - - -def strict(raw: bytes) -> object: - def unique(pairs): - out = {} - for key, value in pairs: - need(key not in out, "duplicate_key") - out[key] = value - return out - return json.loads(raw, object_pairs_hook=unique) - - -def walk(value: object, path: str = "$"): - if isinstance(value, dict): - for key, item in value.items(): - yield path, key, None - yield from walk(item, f"{path}.{key}") - elif isinstance(value, list): - for index, item in enumerate(value): - yield from walk(item, f"{path}[{index}]") - else: - yield path, None, value - - -def digest_ok(value: object) -> bool: - return isinstance(value, str) and bool(DIGEST.fullmatch(value)) and len(set(value[7:])) > 2 - - -def parse_time(value: object) -> datetime: - need(isinstance(value, str) and TIME.fullmatch(value), "bad_time") - return datetime.strptime(value, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=UTC) - - -def check_content(receipt: dict) -> None: - for path, key, value in walk(receipt): - if key is not None: - need(not FORBIDDEN_KEYS.match(key), "forbidden_field") - continue - if isinstance(value, str): - need(not PLACEHOLDERS.search(value), "placeholder") - if path == "$.adapter_commit": - continue - stripped = re.sub(r"sha256:[a-f0-9]{64}", "", value) - for shape in SECRET_SHAPES: - need(not shape.search(stripped), "secret_shaped_value") - need(not base64_blob(stripped), "secret_shaped_value") - if path != "$.signer_fingerprint": # SHA256: is regex-checked above - need(not mixed_run(stripped), "secret_shaped_value") - - -def check_sources(receipt: dict, root: Path) -> None: - sources = receipt["source_sha256"] - need(isinstance(sources, dict) and sources, "sources") - head = subprocess.run(["git", "-C", str(root), "rev-parse", "HEAD"], capture_output=True, timeout=10) - need(head.returncode == 0 and head.stdout.decode().strip() == receipt["adapter_commit"], "source_commit_mismatch") - dirty = subprocess.run(["git", "-C", str(root), "status", "--porcelain", "--", *sources], - capture_output=True, timeout=10) - need(dirty.returncode == 0 and not dirty.stdout.strip(), "source_checkout_dirty") - for name, expected in sources.items(): - need(digest_ok(expected) and not name.startswith("/") and ".." not in Path(name).parts, "sources") - path = root / name - need(path.is_file() and not path.is_symlink(), "source_missing") - need("sha256:" + hashlib.sha256(path.read_bytes()).hexdigest() == expected, "source_hash_mismatch") - - -def check_drill(receipt: dict, result: str) -> None: - """Typed T10 extension; every value is an enumerated fact, never a credential.""" - need(receipt["drill"] == DRILL, "drill") - held = receipt["held_root_token"] - need(isinstance(held, dict) and set(held) == HELD, "drill_fields") - need(all(held[k] in (True, False, None) for k in ("valid", "ttl_zero", "expire_time_null", "orphan")), - "drill_fields") - need(isinstance(held["policies"], list) and len(held["policies"]) <= 32 - and all(isinstance(p, str) and POLICY_NAME.fullmatch(p) for p in held["policies"]), "drill_fields") - if held["valid"] is not True: - need(held["policies"] == [] and held["ttl_zero"] is None and held["expire_time_null"] is None - and held["orphan"] is None, "drill_inconsistent") - gen = receipt["generate_root"] - need(isinstance(gen, dict) and set(gen) == GENERATE_ROOT, "drill_fields") - need(all(type(gen[k]) is bool for k in ("started", "complete", "otp_decode_ok", "minimal_action_ok")), - "drill_fields") - need(type(gen["shares_entered"]) is int and 0 <= gen["shares_entered"] <= 2, "drill_fields") - need(isinstance(gen["policies"], list) and gen["policies"] in ([], ["root"]), "drill_fields") - need(gen["minimal_action_path"] == "sys/auth", "drill_fields") - rev = receipt["revocation"] - need(isinstance(rev, dict) and set(rev) == REVOCATION, "drill_fields") - need(all(rev[k] is None or (type(rev[k]) is int and 100 <= rev[k] <= 599) for k in REVOCATION), "drill_fields") - cancel = receipt["cancel_drill"] - need(isinstance(cancel, dict) and set(cancel) == {"started_then_cancelled"} - and type(cancel["started_then_cancelled"]) is bool, "drill_fields") - limits = receipt["claim_limits"] - need(isinstance(limits, dict) and set(limits) == {"production_root_token_validity"}, "claim_limits") - basis = limits["production_root_token_validity"] - need(isinstance(basis, dict) and set(basis) == {"as_of_snapshot", "snapshot_plaintext_sha256"} - and isinstance(basis["as_of_snapshot"], str) and SNAPSHOT_LABEL.fullmatch(basis["as_of_snapshot"]) - and basis["snapshot_plaintext_sha256"] == receipt["snapshot"]["plaintext_sha256"], "claim_limits") - need(receipt["objects"] == [], "objects") - if result == "drilled": - need(held["valid"] in (True, False), "drill_inconsistent") - need(gen["started"] and gen["shares_entered"] == 2 and gen["complete"] and gen["otp_decode_ok"] - and gen["policies"] == ["root"] and gen["minimal_action_ok"], "drill_inconsistent") - need(rev["revoke_status"] == 204 and rev["post_revoke_lookup"] == 403, "drill_inconsistent") - need(cancel["started_then_cancelled"] is True, "drill_inconsistent") - need(receipt["teardown"]["readback_credential_revoked"] is True, "drill_inconsistent") - elif gen["policies"]: - need(gen["otp_decode_ok"], "drill_inconsistent") - - -def verify(receipt: object, *, source_root: Path, not_after: datetime | None = None, - now: datetime | None = None) -> str: - drill = isinstance(receipt, dict) and "drill" in receipt - need(isinstance(receipt, dict) and set(receipt) == (TOP | DRILL_TOP if drill else TOP), "schema_fields") - need(receipt["schema"] == SCHEMA and receipt["admission_namespace"] == NAMESPACE, "schema") - need(receipt["extends"] == "docs/openbao-restore-drill-evidence.example.json", "schema") - need(receipt["phase"] == "A" and receipt["material"] in ("original", "synthetic"), "phase") - attempt = r"RPF-WP-0064-T10-[A-Za-z0-9-]{3,64}" if drill else r"RPF-WP-0064-T0[47]-[A-Za-z0-9-]{3,64}" - need(isinstance(receipt["attempt_id"], str) and re.fullmatch(attempt, receipt["attempt_id"]), "attempt_id") - need(digest_ok(receipt["admission_sha256"]), "admission_digest") - need(isinstance(receipt["signer_principal"], str) and re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._@-]{0,127}", - receipt["signer_principal"]), "signer") - need(isinstance(receipt["signer_fingerprint"], str) - and re.fullmatch(r"SHA256:[A-Za-z0-9+/]{43}", receipt["signer_fingerprint"]), "signer") - need(receipt["anchor_protected"] is True or receipt["material"] == "synthetic", "anchor_unprotected") - need(isinstance(receipt["adapter_commit"], str) and re.fullmatch(r"[a-f0-9]{40}", receipt["adapter_commit"]), - "adapter_commit") - image = receipt["image"] - need(isinstance(image, str) and re.fullmatch(r"quay\.io/openbao/openbao@sha256:[a-f0-9]{64}", image) - and digest_ok(image.split("@", 1)[1]), "image") - need(receipt["image_id"] is None or digest_ok(receipt["image_id"]), "image") - need(receipt["no_secret_material_recorded"] is True and receipt["kv_data_read"] is False, "secret_policy") - claims = CLAIMS | DRILL_CLAIMS if drill else CLAIMS - need(isinstance(receipt["claims_not_made"], list) and set(receipt["claims_not_made"]) == claims - and len(receipt["claims_not_made"]) == len(claims), "claims_not_made") - - started, finished = parse_time(receipt["started_at"]), parse_time(receipt["finished_at"]) - deadline = receipt["deadline_seconds"] - need(type(deadline) is int and 60 <= deadline <= MAX_DURATION, "deadline") - need(receipt["material"] == "synthetic" or deadline == MAX_DURATION, "deadline") - elapsed = receipt["elapsed_seconds"] - need(type(elapsed) is int and 0 <= elapsed == int((finished - started).total_seconds()), "elapsed") - need(elapsed <= deadline + 120, "deadline_overrun") - need(finished <= parse_time(receipt["not_after"]) or receipt["reason"] == "deadline_exceeded", "window") - if not_after is not None: - need((now or datetime.now(UTC)) <= not_after, "verification_window_closed") - - snap = receipt["snapshot"] - need(isinstance(snap, dict) and set(snap) == SNAPSHOT, "snapshot_fields") - for field in ("ciphertext_sha256", "plaintext_sha256", "source_receipt_sha256"): - need(digest_ok(snap[field]), "snapshot_digest") - need(type(snap["ciphertext_bytes"]) is int and snap["ciphertext_bytes"] > 0, "snapshot") - need(isinstance(snap["source_cluster_id"], str) and UUID.fullmatch(snap["source_cluster_id"]), "snapshot") - need(type(snap["source_applied_index"]) is int and snap["source_applied_index"] > 0, "snapshot") - need(all(type(snap[f]) is bool for f in ("ciphertext_verified", "plaintext_verified")), "snapshot") - - restored = receipt["restored"] - need(isinstance(restored, dict) and set(restored) == RESTORED, "restored_fields") - need(restored["cluster_id"] is None or (isinstance(restored["cluster_id"], str) - and UUID.fullmatch(restored["cluster_id"])), "restored") - need(restored["applied_index"] is None or type(restored["applied_index"]) is int, "restored") - wait = restored["leader_wait"] - if wait is not None: - need(isinstance(wait, dict) and set(wait) == LEADER_WAIT, "leader_wait") - need(type(wait["waited_seconds"]) is int and type(wait["polls"]) is int - and type(wait["elected"]) is bool, "leader_wait") - for name, spec in (("last_leader", LEADER_FIELDS), ("last_seal", SEAL_FIELDS)): - part = wait[name] - need(isinstance(part, dict) and set(part) == set(spec) - and all(typed_or_none(part[k], t) for k, t in spec.items()), "leader_wait") - need(wait["last_error"] is None or (isinstance(wait["last_error"], str) and len(wait["last_error"]) <= 200), - "leader_wait") - network = receipt["network"] - need(isinstance(network, dict) and set(network) == NETWORK, "network_fields") - teardown = receipt["teardown"] - need(isinstance(teardown, dict) and set(teardown) == TEARDOWN, "teardown_fields") - need(teardown["container_removed"] is True and teardown["container_absent_verified"] is True - and teardown["plaintext_memfd_closed"] is True and teardown["custody_memfd_closed"] is True - and teardown["docker_volumes_created"] == 0 and teardown["performed_by"] in ("session", "reaper"), - "teardown_missing") - need(teardown["readback_credential_revoked"] in (True, False, None), "teardown_fields") - - objects = receipt["objects"] - need(isinstance(objects, list), "objects") - for row in objects: - need(isinstance(row, dict) and set(row) == OBJECT, "object_fields") - need(type(row["present"]) is bool and type(row["source_match"]) is bool, "object_fields") - need(row["observed_sha256"] is None or digest_ok(row["observed_sha256"]), "object_digest") - need(not row["present"] or row["observed_sha256"] is not None, "object_digest") - need(not row["source_match"] or row["present"], "object_fields") - need("/data/" not in row["path"], "kv_data_path") - need(row["source_path"] is None or row["source_path"] in receipt["source_sha256"], "object_source") - names = [row["object"] for row in objects] - - result, reason = receipt["result"], receipt["reason"] - if drill: - check_drill(receipt, result) - if drill and result == "drilled": - need(reason == "break_glass_drill_complete", "result_inconsistent") - need(snap["ciphertext_verified"] and snap["plaintext_verified"], "restore_inconsistent") - need(restored["sealed_after_restore"] is True and restored["unsealed_with_original_shares"] is True - and restored["cluster_id_match"] is True and restored["applied_index_not_behind"] is True - and restored["throwaway_init_invalidated"] is True - and restored["cluster_id"] == snap["source_cluster_id"] - and restored["applied_index"] >= snap["source_applied_index"], "restore_inconsistent") - need(network["network_mode"] == "none" and network["docker_networks"] == ["none"] - and network["interfaces"] == ["lo"] and network["published_ports"] == [] and network["mounts"] == [] - and network["egress_refused"] is True, "network_not_contained") - need(teardown["performed_by"] == "session", "teardown_missing") - need(digest_ok(receipt["image_id"]), "image") - elif result in ("restored", "refused") and not drill: - need(tuple(names) == REQUIRED_OBJECTS, "objects_incomplete") - need(snap["ciphertext_verified"] and snap["plaintext_verified"], "restore_inconsistent") - need(restored["sealed_after_restore"] is True and restored["unsealed_with_original_shares"] is True - and restored["cluster_id_match"] is True and restored["applied_index_not_behind"] is True - and restored["throwaway_init_invalidated"] is True - and restored["cluster_id"] == snap["source_cluster_id"] - and restored["applied_index"] >= snap["source_applied_index"], "restore_inconsistent") - need(network["network_mode"] == "none" and network["docker_networks"] == ["none"] - and network["interfaces"] == ["lo"] and network["published_ports"] == [] and network["mounts"] == [] - and network["egress_refused"] is True, "network_not_contained") - need(teardown["readback_credential_revoked"] is True and teardown["performed_by"] == "session", - "teardown_missing") - need(digest_ok(receipt["image_id"]), "image") - all_ok = all(r["present"] and r["source_match"] for r in objects) - if result == "restored": - need(all_ok and reason == "all_required_objects_match", "result_inconsistent") - else: - missing = any(not r["present"] for r in objects) - need(not all_ok and reason == ("object_missing" if missing else "object_mismatch"), "result_inconsistent") - elif result == "aborted": - need(reason in (ABORT | DRILL_ABORT if drill else ABORT), "result_inconsistent") - need(not objects or len(objects) == len(REQUIRED_OBJECTS), "objects") - if network["docker_networks"]: - need(network["docker_networks"] == ["none"] and network["published_ports"] == [], "network_not_contained") - else: - raise Reject("result") - check_content(receipt) - check_sources(receipt, source_root) - return result - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0]) - parser.add_argument("--receipt", required=True) - parser.add_argument("--source-root", required=True, help="clean checkout at the receipt's adapter_commit") - parser.add_argument("--not-after", help="refuse after this UTC time (YYYY-MM-DDTHH:MM:SSZ)") - parser.add_argument("--expect-result", choices=("restored", "refused", "aborted", "drilled")) - opts = parser.parse_args(argv) - try: - path = Path(opts.receipt) - need(path.is_file() and not path.is_symlink() and path.stat().st_size <= 256 * 1024, "receipt_file") - raw = path.read_bytes() - result = verify(strict(raw), source_root=Path(opts.source_root), - not_after=parse_time(opts.not_after) if opts.not_after else None) - need(opts.expect_result in (None, result), "unexpected_result") - except Reject as exc: - print(f"FAIL: {exc}") - return 1 - except (ValueError, OSError, subprocess.SubprocessError): - print("FAIL: unreadable_receipt") - return 1 - print(f"PASS: {result} receipt sha256:{hashlib.sha256(raw).hexdigest()}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/openbao-platform-admin-aal2.sh b/scripts/openbao-platform-admin-aal2.sh deleted file mode 100755 index 8b945e1..0000000 --- a/scripts/openbao-platform-admin-aal2.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash -# Silent child for the governed attended login (RPF-WP-0065-T04); results go to the receipt only. -set -euo pipefail -exec python3 "$(dirname "$0")/openbao_platform_admin_aal2.py" "$@" >/dev/null 2>&1 diff --git a/scripts/openbao-reboot-snapshot.py b/scripts/openbao-reboot-snapshot.py deleted file mode 100755 index 02bfa17..0000000 --- a/scripts/openbao-reboot-snapshot.py +++ /dev/null @@ -1,455 +0,0 @@ -#!/usr/bin/env python3 -"""Attended OpenBao Raft snapshot lane for the railiance01 reboot hold point. - -RPF-WP-0015-T03, docs/railiance01-coordinated-reboot.md step 6. Runs only as -the single, silent child of the contained ops-warden login:: - - python3 scripts/openbao-attended-exec.py -- scripts/openbao-reboot-snapshot.py \ - --scenario-id --receipt-file /abs/new/file --private-dir /abs/0700/dir - -Order: read-only ``bao status`` -> ``bao operator raft snapshot save`` into a -fresh 0700 temp dir -> ``bao status`` again (same cluster, index not behind) -> -age-encrypt to the declared offsite-lane recipient -> decrypt-stream verify -against the plaintext sha256 with the lane's escrow key (held only in memory) --> upload ciphertext with ``If-None-Match: *`` to the governed Nextcloud lane --> shred the temp dir (always) -> self-validate and write the receipt. - -The process prints nothing: Warden fails closed on any child output. The only -output is the 0600 receipt file (or a value-safe failure record) at -``--receipt-file``. Only booleans, hashes, ids and status codes are recorded. -""" -from __future__ import annotations - -import argparse -import hashlib -import hmac -import importlib.util -import json -import os -import re -import shutil -import signal -import stat -import subprocess -import sys -import tempfile -from datetime import UTC, datetime -from pathlib import Path -from typing import Any - -ROOT = Path(__file__).resolve().parents[1] -SCRIPTS = ROOT / "scripts" -if str(SCRIPTS) not in sys.path: - sys.path.insert(0, str(SCRIPTS)) - -RECIPIENT_SOURCE = ROOT / "lib" / "railiance-backup-common.sh" -LANE_PATH = "platform/workloads/railiance/backup/offsite-lane" -# Established offsite-lane folder used by every existing filesdrop upload. -UPLOAD_PREFIX = "openbao" # founder-created railiance-backups/openbao/ (2026-10-14) -SOURCE = {"source_cluster": "railiance01", "source_namespace": "openbao", "source_pod": "openbao-0"} -OPERATOR = "railiance-platform" -SCENARIO_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9-]{2,95}$") -AGE_RECIPIENT_RE = re.compile(r"^age1[023456789acdefghjklmnpqrstuvwxyz]{58}$") -AGE_HEADER = b"age-encryption.org/v1\n" -GZIP_MAGIC = b"\x1f\x8b" -CHUNK = 1024 * 1024 - - -def _load(name: str, filename: str) -> Any: - spec = importlib.util.spec_from_file_location(name, SCRIPTS / filename) - if not spec or not spec.loader: - raise RuntimeError(f"cannot load {filename}") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -# Reuse, do not copy: contained login env + safe_run from the lease harness, -# the reviewed receipt validator, and the governed Nextcloud request helper. -LEASE = _load("audit_core_database_lease_recovery", "audit-core-database-lease-recovery.py") -PREFLIGHT = LEASE.PREFLIGHT -import migrate_nextcloud_backup_account as NEXTCLOUD # noqa: E402 - - -class SnapshotError(RuntimeError): - pass - - -def fail(message: str) -> None: - raise SnapshotError(message) - - -def stamp(value: datetime) -> str: - return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def compact(value: datetime) -> str: - return value.astimezone(UTC).strftime("%Y%m%dT%H%M%SZ") - - -def utc_now() -> datetime: - return datetime.now(UTC) - - -def declared_recipient(path: Path = RECIPIENT_SOURCE) -> str: - """The offsite lane's public recipient, as declared in the backup helper.""" - try: - text = path.read_text(encoding="utf-8") - except OSError: - fail("offsite recipient declaration unavailable") - found = re.findall(r'RAILIANCE_BACKUP_AGE_PUBLIC_KEY:=(age1[0-9a-z]+)\}', text) - if len(found) != 1 or not AGE_RECIPIENT_RE.fullmatch(found[0]): - fail("offsite recipient declaration is not exactly one age recipient") - return found[0] - - -def inside_git_tree(path: Path) -> bool: - for candidate in (path, *path.parents): - if (candidate / ".git").exists(): - return True - return False - - -def private_dir(value: str | None, environ: dict[str, str]) -> Path: - raw = value or environ.get("HOME", "") - path = Path(raw) - if not raw or not path.is_absolute(): - fail("private dir must be an absolute path") - try: - info = path.lstat() - except OSError: - fail("private dir is missing") - if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.getuid() or info.st_mode & 0o077: - fail("private dir must be an owner-only (0700) directory") - if inside_git_tree(path.resolve()): - fail("private dir must not be inside a Git tree") - return path - - -def open_receipt(value: str) -> int: - path = Path(value) - if not path.is_absolute() or inside_git_tree(path.parent.resolve()): - raise SnapshotError("receipt file must be an absolute path outside Git") - return os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - - -def sha256_file(path: Path) -> str: - digest = hashlib.sha256() - with path.open("rb") as handle: - for chunk in iter(lambda: handle.read(CHUNK), b""): - digest.update(chunk) - return digest.hexdigest() - - -def shred(path: Path) -> None: - """Overwrite then unlink every regular file below ``path``; remove the tree.""" - if not path.exists(): - return - for item in sorted(path.rglob("*"), reverse=True): - try: - if item.is_file() and not item.is_symlink(): - size = item.stat().st_size - with item.open("r+b") as handle: - remaining = size - while remaining > 0: - step = min(CHUNK, remaining) - handle.write(b"\0" * step) - remaining -= step - handle.flush() - os.fsync(handle.fileno()) - item.unlink() - elif item.is_dir() and not item.is_symlink(): - item.rmdir() - else: - item.unlink() - except OSError: - pass - shutil.rmtree(path, ignore_errors=True) - - -class Lane: - """All side effects of one attended run; tests replace the boundaries.""" - - def __init__(self, env: dict[str, str]) -> None: - self.env = env - self.tool_env = {"PATH": env.get("PATH", "/usr/bin:/bin"), "HOME": env.get("HOME", "/")} - - def bao(self, args: list[str], *, label: str) -> str: - return LEASE.safe_run(["bao", *args], label=label, env=self.env).stdout - - def status(self) -> dict[str, Any]: - try: - status = json.loads(self.bao(["status", "-format=json"], label="read OpenBao status")) - return { - "initialized": status["initialized"] is True, - "unsealed": status["sealed"] is False, - "storage_type": status.get("storage_type"), - "cluster_id": str(status["cluster_id"]), - "raft_applied_index": int(status["raft_applied_index"]), - } - except (json.JSONDecodeError, KeyError, TypeError, ValueError): - fail("OpenBao status is incomplete") - - def snapshot(self, target: Path) -> None: - self.bao(["operator", "raft", "snapshot", "save", str(target)], label="save OpenBao Raft snapshot") - - def lane_identity(self) -> str: - """The escrow key may be stored as a standard age identity file - (``# created``/``# public key`` comments plus one key line); keep only that line.""" - raw = self.bao(["kv", "get", "-field=AGE_PRIVATE_KEY", LANE_PATH], label="read offsite lane AGE_PRIVATE_KEY") - keys = [] - for line in raw.replace("\r\n", "\n").split("\n"): - line = line.strip() - if not line or line.startswith("#"): - continue - keys.append(line) - del raw - if len(keys) != 1 or not keys[0].startswith("AGE-SECRET-KEY-1"): - fail("offsite lane AGE_PRIVATE_KEY is unusable") - return keys[0] - - def lane_field(self, field: str) -> str: - value = self.bao(["kv", "get", f"-field={field}", LANE_PATH], label=f"read offsite lane {field}") - value = value.strip() - if not value or "\n" in value or "\r" in value: - fail(f"offsite lane {field} is unusable") - return value - - def tool(self, argv: list[str], *, label: str, payload: bytes = b"") -> bytes: - try: - result = subprocess.run(argv, input=payload, capture_output=True, env=self.tool_env, timeout=1800) - except (OSError, subprocess.TimeoutExpired): - fail(f"{label} unavailable") - if result.returncode != 0: - fail(f"{label} failed (exit {result.returncode})") - return result.stdout - - def encrypt(self, recipients: list[str], source: Path, target: Path) -> None: - argv = ["age"] - for recipient in recipients: - argv += ["-r", recipient] - self.tool([*argv, "-o", str(target), str(source)], label="age encrypt") - - def recipient_of(self, identity: bytes) -> str: - return self.tool(["age-keygen", "-y", "/dev/stdin"], label="derive escrow recipient", payload=identity).decode().strip() - - def decrypt_digest(self, identity: bytes, ciphertext: Path) -> str: - """Stream-decrypt into a hash; plaintext never touches disk again.""" - digest = hashlib.sha256() - try: - proc = subprocess.Popen( - ["age", "-d", "-i", "/dev/stdin", str(ciphertext)], - stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, env=self.tool_env, - ) - except OSError: - fail("age decrypt unavailable") - try: - assert proc.stdin and proc.stdout - proc.stdin.write(identity) - proc.stdin.close() - for chunk in iter(lambda: proc.stdout.read(CHUNK), b""): - digest.update(chunk) - code = proc.wait(timeout=1800) - except (OSError, subprocess.TimeoutExpired): - proc.kill() - proc.wait() - fail("age decrypt failed") - finally: - if proc.stdout: - proc.stdout.close() - if code != 0: - fail(f"age decrypt failed (exit {code})") - return digest.hexdigest() - - def upload(self, url: str, token: str, body: bytes) -> int: - status, _ = NEXTCLOUD.request( - url, "PUT", body, (token, ""), - {"Content-Type": "application/octet-stream", "If-None-Match": "*", - "Content-Length": str(len(body))}, - ) - return status - - -def run( - args: argparse.Namespace, lane: Lane, receipt: dict[str, Any], now=utc_now -) -> tuple[dict[str, Any], dict[str, Any]]: - recipient = declared_recipient() - base = private_dir(args.private_dir, lane.env) - stage = receipt.setdefault("stages", []) - - stage.append("status_before") - before = lane.status() - if not (before["initialized"] and before["unsealed"] and before["storage_type"] == "raft"): - fail("OpenBao source is not an initialized, unsealed Raft node") - if before["raft_applied_index"] <= 0: - fail("OpenBao Raft applied index is not positive") - - created = now() - work = Path(tempfile.mkdtemp(prefix="openbao-reboot-snapshot-", dir=base)) - try: - os.chmod(work, 0o700) - plain = work / "openbao.snap" - cipher = work / "openbao.snap.age" - stage.append("snapshot_save") - lane.snapshot(plain) - if not plain.is_file() or plain.stat().st_size == 0: - fail("snapshot file was not written") - os.chmod(plain, 0o600) - with plain.open("rb") as handle: - if handle.read(2) != GZIP_MAGIC: - fail("snapshot is not a Raft snapshot archive") - - stage.append("status_after") - after = lane.status() - if after["cluster_id"] != before["cluster_id"] or after["raft_applied_index"] < before["raft_applied_index"]: - fail("OpenBao identity or Raft index changed incoherently during the snapshot") - if not (after["initialized"] and after["unsealed"]): - fail("OpenBao source sealed during the snapshot") - plain_sha = sha256_file(plain) - - stage.append("encrypt") - recipients = [recipient, *([args.custodian_recipient] if args.custodian_recipient else [])] - lane.encrypt(recipients, plain, cipher) - os.chmod(cipher, 0o600) - with cipher.open("rb") as handle: - if handle.read(len(AGE_HEADER)) != AGE_HEADER: - fail("ciphertext is not an age file") - cipher_sha = sha256_file(cipher) - if cipher_sha == plain_sha: - fail("ciphertext equals plaintext") - - stage.append("verify") - url = lane.lane_field("NC_WEBDAV_URL") - token = lane.lane_field("NC_WEBDAV_TOKEN") - identity = bytearray((lane.lane_identity() + "\n").encode()) - try: - escrow_recipient = lane.recipient_of(bytes(identity)) - if not hmac.compare_digest(escrow_recipient, recipient): - fail("escrow key does not match the declared offsite recipient") - decrypted_sha = lane.decrypt_digest(bytes(identity), cipher) - finally: - identity[:] = b"\0" * len(identity) - if not hmac.compare_digest(decrypted_sha, plain_sha): - fail("decrypted snapshot hash does not match the plaintext") - - stage.append("upload") - if url not in ( - f"{NEXTCLOUD.HOST}/public.php/dav/filesdrop/{token}", - f"{NEXTCLOUD.HOST}/public.php/dav/files/{token}", - ): - fail("offsite lane URL does not match the governed provider contract") - uploaded = now() - name = f"openbao-raft-snapshot-{args.scenario_id}-{compact(uploaded)}.snap.age" - status = lane.upload(f"{url}/{UPLOAD_PREFIX}/{name}", token, cipher.read_bytes()) - if status != 201: - fail("offsite upload did not create a new object") - # Only a 201 on a no-overwrite PUT names a location; the drop cannot read back. - receipt["upload_http_status"] = status - receipt["encrypted_location_ref"] = f"offhost-custody:{args.scenario_id}:{compact(uploaded)}" - finally: - stage.append("shred") - shred(work) - receipt["temp_dir_removed"] = not work.exists() - - if not receipt["temp_dir_removed"]: - fail("private temp dir could not be removed") - return { - "receipt_version": 1, - "receipt_id": f"{args.scenario_id}-SNAPSHOT-{compact(created)}", - "created_at": stamp(created), - "operator": OPERATOR, - **SOURCE, - "cluster_id": before["cluster_id"], - # Lower bound: the snapshot contains at least the pre-save index. - "raft_applied_index": before["raft_applied_index"], - "snapshot_created": True, - "source_initialized": True, - "source_unsealed": True, - "snapshot_sha256": f"sha256:{plain_sha}", - "snapshot_encrypted": True, - "encrypted_snapshot_sha256": f"sha256:{cipher_sha}", - "encrypted_location_ref": f"offhost-custody:{args.scenario_id}:{compact(uploaded)}", - "encrypted_copy_off_host": True, - "off_host_evidence": "upload acknowledged (HTTP 201, If-None-Match: *); remote hash not read back", - "upload_folder": UPLOAD_PREFIX, - "recipients": [recipient, *([args.custodian_recipient] if args.custodian_recipient else [])], - "custodian_escrow_ref": args.custodian_escrow_ref, - "escrow_key_read_decision": "reviews/RPF-WP-0015-T03-founder-go-20261014.json#escrow_key_read", - "encryption_verified": True, - "hash_verified": True, - "no_secret_material_recorded": True, - }, after - - -class QuietParser(argparse.ArgumentParser): - def error(self, message: str) -> None: # never print usage - raise SnapshotError("invalid arguments") - - -def parser() -> argparse.ArgumentParser: - p = QuietParser(description=__doc__, add_help=False) - p.add_argument("--scenario-id", required=True) - p.add_argument("--receipt-file", required=True) - p.add_argument("--private-dir") - p.add_argument("--remote", default="railiance01", choices=["railiance01"]) - p.add_argument("--custodian-recipient", help="extra age recipient whose key is held outside OpenBao") - p.add_argument("--custodian-escrow-ref", required=True, - help="value-safe reference to where a decryption key is held outside OpenBao") - return p - - -def main(argv: list[str] | None = None, *, lane_factory=Lane, now=utc_now) -> int: - try: - args = parser().parse_args(argv) - if not SCENARIO_RE.fullmatch(args.scenario_id): - return 2 - # Always required: where a decryption key is held outside OpenBao (alone, when the - # offline key is the offsite recipient; with --custodian-recipient otherwise). - if not (args.custodian_escrow_ref or "").strip() or ( - args.custodian_recipient and not AGE_RECIPIENT_RE.fullmatch(args.custodian_recipient) - ): - return 2 - fd = open_receipt(args.receipt_file) - except (SnapshotError, OSError): - return 2 - record: dict[str, Any] = {"status": "failed", "scenario_id": args.scenario_id, "error": "internal_error"} - result: dict[str, Any] | None = None - def _abort(signum, _frame): # warden timeout/abort: still run every finally/shred - raise SnapshotError(f"interrupted by signal {signum}") - - signal.signal(signal.SIGTERM, _abort) - signal.signal(signal.SIGHUP, _abort) - try: - os.umask(0o077) - env = LEASE.contained_bao_env() - receipt, live = run(args, lane_factory(env), record, now=now) - # Self-check with the exact preflight validator before claiming success. - check = Path(tempfile.mkdtemp(prefix="openbao-receipt-check-", dir=private_dir(args.private_dir, env))) - try: - probe = check / "receipt.json" - probe.write_text(json.dumps(receipt), encoding="utf-8") - PREFLIGHT.validate_snapshot_receipt(probe, live_openbao=live, now=now(), max_age_hours=1.0) - finally: - shutil.rmtree(check, ignore_errors=True) - result = receipt - except (SnapshotError, LEASE.ProcedureError, PREFLIGHT.PreflightError) as exc: - record["error"] = str(exc) - except Exception: # noqa: BLE001 - value-safe: never record exception text - record["error"] = "internal_error" - finally: - with os.fdopen(fd, "w", encoding="utf-8") as handle: - json.dump(result if result is not None else record, handle, indent=2, sort_keys=False) - handle.write("\n") - return 0 if result is not None else 1 - - -def silence() -> None: - devnull = os.open(os.devnull, os.O_WRONLY) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - os.close(devnull) - - -if __name__ == "__main__": - silence() - raise SystemExit(main()) diff --git a/scripts/openbao-verify-login-overlay.sh b/scripts/openbao-verify-login-overlay.sh index cfca2a8..4b23e6f 100755 --- a/scripts/openbao-verify-login-overlay.sh +++ b/scripts/openbao-verify-login-overlay.sh @@ -113,11 +113,6 @@ require_pattern \ "$presets_json" \ '"role"[[:space:]]*:[[:space:]]*"platform-admin"' -require_pattern \ - "presets.json offers the workload role selection (NK-WP-0051-T06)" \ - "$presets_json" \ - '"workloadRoles"' - require_pattern \ "overlay.css hides namespace picker" \ "$overlay_css" \ diff --git a/scripts/openbao_platform_admin_aal2.py b/scripts/openbao_platform_admin_aal2.py deleted file mode 100755 index 7afe371..0000000 --- a/scripts/openbao_platform_admin_aal2.py +++ /dev/null @@ -1,244 +0,0 @@ -#!/usr/bin/env python3 -"""Silent contained aal2 rollout for auth/netkingdom/role/platform-admin (RPF-WP-0065-T04). - -Each attended mode runs as the one reviewed child of -`warden access openbao-platform-admin-login --exec -- ...`, writes exactly one -non-secret 0600 receipt (O_EXCL) to the caller-given path and prints nothing. -Any refusal or mismatch exits 1 after recording a status in the receipt. - - prepare --receipt R back up the live role into R, then add only the - claim mapping /assurance/level -> assurance_level - (no new binding; logins are not restricted yet) - apply --receipt R gate: this login's token metadata must show - role=platform-admin and assurance_level=aal2; - then write the declared role and read it back - verify --receipt R role equals the declaration and this fresh - login is aal2 with platform-admin + operator-custody - rollback --backup B --receipt R restore the role recorded by `prepare` in B - rollback-body --backup B --out F local only (no bao): write the backed-up role as a - bao write body for the docs/openbao.md break-glass path - -Only role configuration (no secrets in an OIDC role) and the token's policy set, -role name and assurance level are recorded: never a token, accessor, email or username. -""" -from datetime import datetime, timezone -import importlib.util -import json -import os -from pathlib import Path -import subprocess -import sys - -HERE = Path(__file__).resolve().parent -_spec = importlib.util.spec_from_file_location('loopback', HERE / 'openbao_operator_loopback_callback.py') -loopback = importlib.util.module_from_spec(_spec) -_spec.loader.exec_module(loopback) - -ROLE = loopback.ROLE -Refused = loopback.Refused -DECLARED_FILE = HERE.parent / 'openbao/auth/netkingdom-platform-admin-role.json' -EXPECTED_BOUND = {'groups': ['net-kingdom-admins'], '/assurance/level': 'aal2'} -MAPPING = ('/assurance/level', 'assurance_level') -REQUIRED_POLICIES = {'platform-admin', 'operator-custody'} -SCHEMA = 'railiance-platform.openbao-platform-admin-aal2.v1' -LEGACY_ALIASES = {'policies': 'token_policies', 'ttl': 'token_ttl'} - - -def declared(): - return json.loads(DECLARED_FILE.read_text(encoding='utf-8')) - - -def norm(value): - if isinstance(value, list): - return sorted(norm(item) for item in value) if all(isinstance(i, str) for i in value) else [norm(i) for i in value] - if isinstance(value, dict): - return {key: norm(item) for key, item in value.items()} - return value - - -def drift(want, live): - """Keys of `want` whose live value differs. Legacy aliases may be folded by OpenBao.""" - out = [] - for key, value in want.items(): - if key in live: - if norm(live[key]) != norm(value): - out.append(key) - elif key in LEGACY_ALIASES and norm(live.get(LEGACY_ALIASES[key])) == norm(value): - continue - elif value not in ([], {}, '', 0, None, False): - out.append(key) - return sorted(out) - - -def bound_ok(role): - return norm(role.get('bound_claims')) == norm(EXPECTED_BOUND) - - -def mapped(role): - return (role.get('claim_mappings') or {}).get(MAPPING[0]) == MAPPING[1] - - -def token_facts(): - """Value-free view of this session: policies, role name, assurance level.""" - result = subprocess.run(['bao', 'read', '-format=json', 'auth/token/lookup-self'], - capture_output=True, check=True, timeout=30) - data = json.loads(result.stdout)['data'] - meta = data.get('meta') or {} - policies = set(data.get('policies') or []) | set(data.get('identity_policies') or []) - return {'policies': sorted(policies), 'role': meta.get('role'), - 'assurance_level': meta.get(MAPPING[1]), 'path': data.get('path')} - - -def gate(facts): - if facts['role'] != 'platform-admin': - raise Refused('gate_wrong_role') - if facts['assurance_level'] != 'aal2': - raise Refused('gate_login_not_aal2') - - -def prepare(read=loopback.read_role, write=loopback.write_role): - original = read() - if mapped(original): - return original, False - mappings = dict(original.get('claim_mappings') or {}, **{MAPPING[0]: MAPPING[1]}) - write(dict(loopback.payload(original), claim_mappings=mappings)) - after = read() - if not mapped(after): - raise Refused('readback_mapping_missing') - rest = {k: v for k, v in loopback.payload(original).items() if k != 'claim_mappings'} - if drift(rest, after): - raise Refused('readback_settings_changed') - return original, True - - -def apply(read=loopback.read_role, write=loopback.write_role, facts=token_facts): - before = read() - if not mapped(before): - raise Refused('not_prepared') - observed = facts() - gate(observed) - want = declared() - if not bound_ok(want) or not mapped(want): - raise Refused('declaration_unexpected') - changed = bool(drift(want, before)) - if changed: - write(dict(loopback.payload(before), **want)) - after = read() - if drift(want, after) or not bound_ok(after): - raise Refused('readback_mismatch') - return before, observed, changed, after - - -def verify(read=loopback.read_role, facts=token_facts): - role = read() - observed = facts() - if drift(declared(), role) or not bound_ok(role): - raise Refused('role_not_declared') - gate(observed) - if not REQUIRED_POLICIES <= set(observed['policies']): - raise Refused('token_policies_missing') - return role, observed - - -def load_backup(path): - body = json.loads(Path(path).read_text(encoding='utf-8')) - role = body.get('backup_role') - if body.get('schema') != SCHEMA or body.get('mode') != 'prepare' or not isinstance(role, dict): - raise Refused('backup_receipt_invalid') - if role.get('role_type') != 'oidc' or bound_ok(role): - raise Refused('backup_role_unexpected') - return role - - -def rollback_body(backup): - body = loopback.payload(backup) - body.setdefault('claim_mappings', {}) - return body - - -def rollback(backup, read=loopback.read_role, write=loopback.write_role): - want = rollback_body(backup) - write(want) - after = read() - if drift(want, after): - raise Refused('readback_mismatch') - return after - - -def write_receipt(path, mode, status, **extra): - fd = os.open(Path(path), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - body = { - 'schema': SCHEMA, 'mode': mode, 'status': status, 'role_path': ROLE, - 'observed_at': datetime.now(timezone.utc).isoformat(), - 'expected_bound_claims': EXPECTED_BOUND, - 'credential_values_emitted': False, **extra, - } - with os.fdopen(fd, 'w', encoding='utf-8') as handle: - json.dump(body, handle, indent=2, sort_keys=True) - handle.write('\n') - - -def parse(argv): - if not argv: - raise SystemExit(2) - mode, opts, args = argv[0], {}, list(argv[1:]) - while args: - if args[0] in ('--receipt', '--backup', '--out') and len(args) > 1: - opts[args[0][2:]] = args[1] - args = args[2:] - else: - raise SystemExit(2) - need = {'prepare': {'receipt'}, 'apply': {'receipt'}, 'verify': {'receipt'}, - 'rollback': {'receipt', 'backup'}, 'rollback-body': {'backup', 'out'}} - if mode not in need or set(opts) != need[mode]: - raise SystemExit(2) - return mode, opts - - -def run(mode, opts): - if mode == 'prepare': - original, changed = prepare() - return 'prepared' if changed else 'already_prepared', { - 'backup_role': loopback.payload(original), 'changed': changed} - if mode == 'apply': - before, observed, changed, after = apply() - return 'applied' if changed else 'already_applied', { - 'before_role': loopback.payload(before), 'gate_token': observed, - 'changed': changed, 'bound_claims': after.get('bound_claims')} - if mode == 'verify': - role, observed = verify() - return 'verified', {'token': observed, 'bound_claims': role.get('bound_claims')} - after = rollback(load_backup(opts['backup'])) - return 'rolled_back', {'bound_claims': after.get('bound_claims'), - 'claim_mappings': after.get('claim_mappings')} - - -def main(argv): - mode, opts = parse(argv) - if mode == 'rollback-body': - try: - body = rollback_body(load_backup(opts['backup'])) - fd = os.open(Path(opts['out']), os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, 'w', encoding='utf-8') as handle: - json.dump(body, handle, indent=2, sort_keys=True) - handle.write('\n') - return 0 - except Exception: - return 1 - if Path(opts['receipt']).exists(): - return 1 # never touch OpenBao when the evidence could not be written - try: - loopback.require_attended() - status, extra = run(mode, opts) - write_receipt(opts['receipt'], mode, status, **extra) - return 0 - except Exception as error: - try: - write_receipt(opts['receipt'], mode, loopback.classify(error)) - except Exception: - pass - return 1 - - -if __name__ == '__main__': - raise SystemExit(main(sys.argv[1:])) diff --git a/scripts/openbao_revoke_initial_root_token.py b/scripts/openbao_revoke_initial_root_token.py deleted file mode 100644 index 2be2faa..0000000 --- a/scripts/openbao_revoke_initial_root_token.py +++ /dev/null @@ -1,149 +0,0 @@ -#!/usr/bin/env python3 -"""Revoke the held initial OpenBao root token at production entry (NK-WP-0055-T16). - -Founder-run in their own terminal, through `kubectl port-forward` with the admin -kubeconfig. The token is read at a hidden prompt and kept in memory only: never -argv, environment, file or output. One 0600 receipt (O_EXCL) records only -non-secret facts. - - check --addr A --breakglass-addr B --receipt R - lookup-self only: the held token is valid, `root`, orphan, no TTL; and the - loopback break-glass listener answers `generate-root` status without a token. - revoke --addr A --breakglass-addr B --decision ID --receipt R - the same gates, then `revoke-self` (expects 204) and a fresh lookup-self - (expects 403). Refuses unless the break-glass `generate-root` path answers, - so a replacement path exists before the standing token is gone. - -A and B must be literal loopback URLs (the port-forwarded main listener 8200 and -break-glass listener 8210). Revoking is irreversible: run it only at production -entry, recorded under a founder decision id. -""" -import argparse -from datetime import datetime, timezone -import getpass -import json -import os -from pathlib import Path -import re -import sys -import urllib.error -import urllib.parse -import urllib.request -import warnings - -DECISION_RE = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$') -TOKEN_RE = re.compile(r'^[A-Za-z0-9._-]{16,256}$') - - -class Refused(Exception): - pass - - -class NoRedirect(urllib.request.HTTPRedirectHandler): - def redirect_request(self, *args, **kwargs): - raise Refused('redirect') - - -def opener(): - return urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect()) - - -def loopback(value): - parsed = urllib.parse.urlsplit(value or '') - if (parsed.scheme != 'http' or parsed.hostname not in ('127.0.0.1', '::1') or parsed.port is None - or parsed.path not in ('', '/') or parsed.query or parsed.fragment or parsed.username): - raise Refused('address') - return value.rstrip('/') - - -def call(client, method, url, token=None): - headers = {'X-Vault-Token': token} if token else {} - request = urllib.request.Request(url, method=method, headers=headers, data=b'' if method == 'POST' else None) - try: - with client.open(request, timeout=15) as response: - body = response.read(65536) - return response.status, json.loads(body) if body else {} - except urllib.error.HTTPError as error: - return error.code, {} - - -def lookup(client, addr, token): - status, body = call(client, 'GET', addr + '/v1/auth/token/lookup-self', token) - if status != 200: - return {'valid': False, 'lookup_status': status} - data = body.get('data') or {} - return {'valid': True, 'policies': sorted(data.get('policies') or []), 'orphan': data.get('orphan') is True, - 'ttl_zero': data.get('ttl') == 0, 'expire_time_null': data.get('expire_time') is None} - - -def is_initial_root(facts): - return (facts.get('valid') and facts.get('policies') == ['root'] and facts.get('orphan') - and facts.get('ttl_zero') and facts.get('expire_time_null')) - - -def breakglass_available(client, addr): - status, body = call(client, 'GET', addr + '/v1/sys/generate-root/attempt') - return status == 200 and isinstance(body.get('started'), bool) - - -def run(mode, addr, breakglass, token, receipt, decision=None, client=None): - if receipt.exists() or receipt.is_symlink(): - raise Refused('receipt_exists') - if not TOKEN_RE.fullmatch(token or ''): - raise Refused('token_shape') - if mode == 'revoke' and not DECISION_RE.fullmatch(decision or ''): - raise Refused('decision') - addr, breakglass = loopback(addr), loopback(breakglass) - client = client or opener() - record = {'schema': 'railiance-platform.openbao-initial-root-token.v1', 'mode': mode, - 'observed_at': datetime.now(timezone.utc).isoformat(), 'decision_id': decision, - 'credential_values_emitted': False} - record['breakglass_generate_root_available'] = breakglass_available(client, breakglass) - record['before'] = lookup(client, addr, token) - if not record['breakglass_generate_root_available']: - record['status'] = 'refused_no_breakglass_path' - elif not is_initial_root(record['before']): - record['status'] = 'refused_not_initial_root_token' if record['before'].get('valid') else 'token_already_invalid' - elif mode == 'check': - record['status'] = 'checked' - else: - record['revoke_status'] = call(client, 'POST', addr + '/v1/auth/token/revoke-self', token)[0] - record['after'] = lookup(client, addr, token) - ok = record['revoke_status'] == 204 and record['after'] == {'valid': False, 'lookup_status': 403} - record['status'] = 'revoked' if ok else 'revoke_unconfirmed' - fd = os.open(receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, 'w') as out: - json.dump(record, out, indent=2, sort_keys=True) - out.write('\n') - return record - - -def main(): - parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - parser.add_argument('mode', choices=('check', 'revoke')) - parser.add_argument('--addr', required=True) - parser.add_argument('--breakglass-addr', required=True) - parser.add_argument('--decision') - parser.add_argument('--receipt', type=Path, required=True) - args = parser.parse_args() - if not sys.stdin.isatty(): - print('refused: run in your own terminal', file=sys.stderr) - return 2 - try: - loopback(args.addr), loopback(args.breakglass_addr) - with warnings.catch_warnings(): - warnings.simplefilter('error', getpass.GetPassWarning) - token = getpass.getpass('Held initial root token (hidden): ') - record = run(args.mode, args.addr, args.breakglass_addr, token, args.receipt.expanduser(), args.decision) - except (Refused, getpass.GetPassWarning) as error: - print(f'refused: {error}', file=sys.stderr) - return 1 - except (urllib.error.URLError, OSError): - print('refused: OpenBao unreachable or local I/O failure', file=sys.stderr) - return 1 - print(f"{record['status']}; receipt {args.receipt}") - return 0 if record['status'] in ('checked', 'revoked') else 1 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/operator_matrix_lane.py b/scripts/operator_matrix_lane.py deleted file mode 100644 index 136b14a..0000000 --- a/scripts/operator_matrix_lane.py +++ /dev/null @@ -1,358 +0,0 @@ -"""workload-kv-operator-matrix CCRs: one attended OIDC role, one per-path KV policy. - -RPF-WP-0035-T03 (design docs/credential-lane-designs/fluid-telegram-operator-kv.md). -Loaded by scripts/credential-change.py for request_type workload-kv-operator-matrix. -Unlike workload-kv-read, the policy is a reviewed matrix of exact entries, each -with its own capabilities (read, create, update), and the apply also adds exact -coding-agent denies on every entry's data and metadata path to the shared -agent-high-risk-boundary policy. Live unrelated boundary content is preserved -verbatim. Nothing here reads, prints or accepts a secret value; the role is -written to an existing OIDC mount, never a new one. -""" -from __future__ import annotations - -import importlib.util -import json -import re -import tempfile -from pathlib import Path -from typing import Any - -REQUEST_TYPE = "workload-kv-operator-matrix" -ALLOWED_AUTH_MOUNTS = {"netkingdom"} -ALLOWED_CAPABILITIES = ("create", "read", "update") -SELF_GRANTS = (("auth/token/revoke-self", "update"),) -BOUNDARY_POLICY = "agent-high-risk-boundary" -BOUNDARY_FILE = "openbao/policies/agent-high-risk-boundary.hcl" -CAMPAIGN_RE = re.compile(r"^platform/workloads/[a-z0-9-]+/[a-z0-9-]+/[a-z0-9-]+$") -ENTRY_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") -FIELD_RE = re.compile(r"^[a-z][a-z0-9_]*$") -POLICY_NAME_RE = re.compile(r"^workload-kv-[a-z0-9-]+-operator$") -ROLE_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") -GROUP_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") -OIDC_SCOPES = ["openid", "profile", "email", "groups"] -# Loopback CLI callbacks and the operator-tunnel UI callback; never a public listener. -ALLOWED_REDIRECT_URIS = { - "http://localhost:8250/oidc/callback", - "http://127.0.0.1:8250/oidc/callback", - "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback", -} -MAX_TTL_SECONDS = 15 * 60 -MAX_EXPLICIT_TTL_SECONDS = 30 * 60 -ROLE_KEYS = { - "role_type", "allowed_redirect_uris", "oidc_scopes", "user_claim", "groups_claim", - "bound_claims_type", "bound_claims", "token_policies", "token_no_default_policy", - "token_type", "token_ttl", "token_max_ttl", "token_explicit_max_ttl", - "token_num_uses", "verbose_oidc_logging", -} -DURATION_KEYS = ("token_ttl", "token_max_ttl", "token_explicit_max_ttl") - - -def _service_jwt_lane(): - spec = importlib.util.spec_from_file_location( - "service_jwt_lane", Path(__file__).resolve().parent / "service_jwt_lane.py" - ) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -_shared = _service_jwt_lane() -ttl_seconds = _shared.ttl_seconds -normalized = _shared.normalized -BaoError = _shared.BaoError -_run = _shared._run -_read_json = _shared._read_json - - -def _kv_paths(ccr: dict[str, Any], entry: str) -> tuple[str, str]: - campaign = ccr["openbao"]["campaign_path"] - mount, rest = campaign.split("/", 1) - return f"{mount}/data/{rest}/{entry}", f"{mount}/metadata/{rest}/{entry}" - - -def generated_policy_hcl(ccr: dict[str, Any]) -> str: - lines = [f"# {ccr['id']} (RPF-WP-0035-T03): exact per-entry operator matrix; no metadata, list or delete."] - for row in ccr["openbao"]["matrix"]: - data_path, _ = _kv_paths(ccr, row["entry"]) - caps = ", ".join(f'"{c}"' for c in ALLOWED_CAPABILITIES if c in row["capabilities"]) - lines += [f'path "{data_path}" {{', f" capabilities = [{caps}]", "}"] - for path, capability in SELF_GRANTS: - lines += [f'path "{path}" {{', f' capabilities = ["{capability}"]', "}"] - return "\n".join(lines) + "\n" - - -def boundary_stanzas(ccr: dict[str, Any]) -> list[str]: - stanzas = [] - for row in ccr["openbao"]["matrix"]: - for path in _kv_paths(ccr, row["entry"]): - stanzas.append(f'path "{path}" {{ capabilities = ["deny"] }}') - return stanzas - - -def generated_boundary_overlay(ccr: dict[str, Any]) -> str: - header = f"# {ccr['id']}: coding-agent denies for {ccr['openbao']['campaign_path']} (data and metadata)." - return "\n".join([header, *boundary_stanzas(ccr)]) + "\n" - - -def _flat(text: str) -> set[str]: - return {" ".join(line.split()) for line in text.splitlines() if line.strip()} - - -def boundary_presence(ccr: dict[str, Any], policy_text: str) -> tuple[int, int]: - present = _flat(policy_text) - stanzas = boundary_stanzas(ccr) - return sum(1 for s in stanzas if " ".join(s.split()) in present), len(stanzas) - - -def role_payload(ccr: dict[str, Any], repo_dir: Path) -> dict[str, Any]: - return _shared._load_json(repo_dir, ccr["openbao"]["auth"]["role_file"], "openbao.auth.role_file", []) - - -def _validate_matrix(openbao: dict[str, Any], errors: list[str]) -> None: - matrix = openbao.get("matrix") - if not isinstance(matrix, list) or not matrix: - errors.append("openbao.matrix must list the exact entries") - return - seen: set[str] = set() - for row in matrix: - if not isinstance(row, dict): - errors.append("openbao.matrix rows must be objects") - continue - entry = str(row.get("entry") or "") - if not ENTRY_RE.match(entry): - errors.append(f"openbao.matrix entry must be one exact segment: {entry!r}") - if entry in seen: - errors.append(f"openbao.matrix entry is duplicated: {entry}") - seen.add(entry) - fields = row.get("fields") - if not isinstance(fields, list) or not fields or not all(FIELD_RE.match(str(f)) for f in fields): - errors.append(f"openbao.matrix {entry} fields must be safe field names") - caps = row.get("capabilities") - if not isinstance(caps, list) or not caps or len(set(caps)) != len(caps): - errors.append(f"openbao.matrix {entry} capabilities must be a non-empty unique list") - continue - unknown = sorted(set(caps) - set(ALLOWED_CAPABILITIES)) - if unknown: - errors.append(f"openbao.matrix {entry} has unsupported capabilities: {unknown}") - if "read" not in caps: - errors.append(f"openbao.matrix {entry} must include read") - - -def _validate_role(role: dict[str, Any], policy_name: str, errors: list[str]) -> None: - extra = set(role) - ROLE_KEYS - if extra: - errors.append(f"role has keys outside the reviewed set: {sorted(extra)}") - if role.get("role_type") != "oidc" or role.get("user_claim") != "sub": - errors.append("role must be role_type oidc with user_claim sub") - if role.get("groups_claim") != "groups" or role.get("oidc_scopes") != OIDC_SCOPES: - errors.append(f"role must use groups_claim groups and oidc_scopes {OIDC_SCOPES}") - uris = role.get("allowed_redirect_uris") - if not isinstance(uris, list) or not uris or not set(uris) <= ALLOWED_REDIRECT_URIS: - errors.append("role allowed_redirect_uris must be a subset of the approved loopback/tunnel callbacks") - if role.get("token_policies") != [policy_name]: - errors.append("role token_policies must be exactly [openbao.policy_name]") - if role.get("token_no_default_policy") is not True: - errors.append("role must set token_no_default_policy true") - if role.get("token_type") != "service": - errors.append("role token_type must be service") - if role.get("verbose_oidc_logging") is not False: - errors.append("role must disable verbose_oidc_logging") - claims = role.get("bound_claims") - if role.get("bound_claims_type") != "string" or not isinstance(claims, dict): - errors.append("role bound_claims must be string-typed") - claims = {} - if set(claims) != {"groups", "/assurance/level"}: - errors.append("role bound_claims must be exactly groups and /assurance/level") - groups = claims.get("groups") - if not isinstance(groups, list) or len(groups) != 1 or not GROUP_RE.match(str(groups[0])): - errors.append("role bound_claims.groups must be one exact group") - if claims.get("/assurance/level") != "aal2": - errors.append("role bound_claims./assurance/level must be aal2") - ttl = ttl_seconds(role.get("token_ttl")) - max_ttl = ttl_seconds(role.get("token_max_ttl")) - explicit = ttl_seconds(role.get("token_explicit_max_ttl")) - if ttl is None or not 0 < ttl <= MAX_TTL_SECONDS: - errors.append("role token_ttl must be set and at most 15m") - if any(v is None or not 0 < v <= MAX_EXPLICIT_TTL_SECONDS for v in (max_ttl, explicit)): - errors.append("role token_max_ttl and token_explicit_max_ttl must be set and at most 30m") - elif ttl is not None and ttl > min(max_ttl, explicit): - errors.append("role token_ttl must not exceed its maximums") - uses = role.get("token_num_uses") - if not isinstance(uses, int) or isinstance(uses, bool) or uses < 0: - errors.append("role token_num_uses must be a non-negative integer") - - -def validate(ccr: dict[str, Any], repo_dir: Path) -> list[str]: - """Schema plus every boundary the delegated applier relies on.""" - errors: list[str] = [] - openbao = ccr.get("openbao") - if not isinstance(openbao, dict): - return ["openbao must be an object"] - auth = openbao.get("auth") - if not isinstance(auth, dict): - return ["openbao.auth must be an object"] - if (ccr.get("target") or {}).get("environment") != "production": - errors.append("target.environment must be production") - review = ccr.get("review") or {} - if review.get("required") is not True or not review.get("required_approvers"): - errors.append("review.required must be true with required_approvers") - - campaign = str(openbao.get("campaign_path") or "") - if not CAMPAIGN_RE.match(campaign): - errors.append("openbao.campaign_path must be platform/workloads///") - _validate_matrix(openbao, errors) - if errors: - return errors - - policy_name = str(openbao.get("policy_name") or "") - if not POLICY_NAME_RE.match(policy_name): - errors.append("openbao.policy_name must be workload-kv-<...>-operator") - policy_file = str(openbao.get("policy_file") or "") - if policy_file != f"openbao/policies/{policy_name}.hcl": - errors.append("openbao.policy_file must be openbao/policies/.hcl") - elif not (repo_dir / policy_file).exists(): - errors.append(f"openbao.policy_file does not exist: {policy_file}") - elif normalized((repo_dir / policy_file).read_text(encoding="utf-8")) != normalized(generated_policy_hcl(ccr)): - errors.append("openbao.policy_file does not match the generated operator matrix policy") - - overlay_file = str(openbao.get("boundary_overlay_file") or "") - overlay_path = (repo_dir / overlay_file).resolve() - if not overlay_file or not overlay_path.exists(): - errors.append("openbao.boundary_overlay_file must name an existing file") - elif normalized(overlay_path.read_text(encoding="utf-8")) != normalized(generated_boundary_overlay(ccr)): - errors.append("openbao.boundary_overlay_file does not match the generated agent denies") - boundary_text = (repo_dir / BOUNDARY_FILE).read_text(encoding="utf-8") - present, total = boundary_presence(ccr, boundary_text) - if present != total: - errors.append(f"{BOUNDARY_FILE} must carry all {total} agent denies for this matrix (has {present})") - - if auth.get("method") != "oidc": - errors.append("openbao.auth.method must be oidc") - if auth.get("mount") not in ALLOWED_AUTH_MOUNTS: - errors.append(f"openbao.auth.mount must be one of {sorted(ALLOWED_AUTH_MOUNTS)}") - if not ROLE_RE.match(str(auth.get("role") or "")): - errors.append("openbao.auth.role contains unsafe characters") - if not isinstance(auth.get("bound_claims_confirmed"), bool): - errors.append("openbao.auth.bound_claims_confirmed must be a boolean") - role = _shared._load_json(repo_dir, str(auth.get("role_file") or ""), "openbao.auth.role_file", errors) - if role: - _validate_role(role, policy_name, errors) - return errors - - -def readiness_blockers(ccr: dict[str, Any], repo_dir: Path, allowed_statuses: set[str]) -> list[str]: - blockers: list[str] = [] - if ccr.get("status") not in allowed_statuses: - blockers.append( - "applier requires approved, applied, verified, or active CCR status, " - f"got {ccr.get('status')}" - ) - if ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True: - blockers.append("applier requires confirmed OpenBao auth binding") - blockers.extend(validate(ccr, repo_dir)) - return blockers - - -def dry_run_payload(ccr: dict[str, Any], repo_dir: Path, warnings: list[str]) -> dict[str, Any]: - openbao = ccr["openbao"] - auth = openbao["auth"] - return { - "id": ccr["id"], - "title": ccr["title"], - "status": ccr["status"], - "environment": ccr["target"]["environment"], - "warnings": warnings, - "source_artifacts": {}, - "mutations": [ - {"kind": "boundary_append_if_absent", "openbao_path": f"sys/policies/acl/{BOUNDARY_POLICY}", - "source": openbao["boundary_overlay_file"], - "payload": {"preserves_live_policy": True, "refuses_partial_presence": True, - "denies": boundary_stanzas(ccr)}}, - {"kind": "policy_write", "openbao_path": f"sys/policies/acl/{openbao['policy_name']}", - "policy_name": openbao["policy_name"], "source": openbao["policy_file"], - "body": generated_policy_hcl(ccr).rstrip()}, - {"kind": "auth_role_write", "openbao_path": f"auth/{auth['mount']}/role/{auth['role']}", - "payload": role_payload(ccr, repo_dir), - "precondition": f"auth/{auth['mount']} exists with type oidc (never enabled here)"}, - {"kind": "readback_compare", "openbao_path": "boundary denies, policy, role", - "payload": {"prints": "booleans only"}}, - ], - "out_of_scope": [ - "secret value reads or writes (custody moves use their own silent helper)", - "token creation or login", - "auth mount enable/config changes", - "changes to any other role or policy", - ], - "required_evidence": [ - "CCR id and approval reference", - "readback equality for boundary denies, policy and role", - "owner verification (ops-mason MASON-WP-0005-T05): positive matrix, sibling/agent/non-member denial, salt CAS 0", - ], - } - - -def apply_details(ccr: dict[str, Any], actor: str) -> list[str]: - openbao = ccr["openbao"] - auth = openbao["auth"] - return [ - f"Delegated metadata applier ran as {actor} using local bao CLI ambient authority.", - f"Agent boundary denies ensured: sys/policies/acl/{BOUNDARY_POLICY} ({len(boundary_stanzas(ccr))} exact paths)", - f"Policy metadata write: sys/policies/acl/{openbao['policy_name']}", - f"OIDC role metadata write: auth/{auth['mount']}/role/{auth['role']}", - "Readback matched source for boundary denies, policy and role.", - "No secret values were read, written, printed, or accepted in argv.", - ] - - -def readback_mismatches(ccr: dict[str, Any], repo_dir: Path, bao_bin: str) -> list[str]: - openbao = ccr["openbao"] - auth = openbao["auth"] - mismatches: list[str] = [] - boundary = _run(bao_bin, ["policy", "read", BOUNDARY_POLICY], repo_dir, quiet=True, capture=True) - present, total = boundary_presence(ccr, boundary) - if present != total: - mismatches.append("boundary policy lacks agent denies") - policy = _run(bao_bin, ["policy", "read", openbao["policy_name"]], repo_dir, quiet=True, capture=True) - if normalized(policy) != normalized(generated_policy_hcl(ccr)): - mismatches.append("policy text differs from source") - role = _read_json(bao_bin, ["read", f"auth/{auth['mount']}/role/{auth['role']}"], repo_dir).get("data", {}) - for key, want in role_payload(ccr, repo_dir).items(): - got = role.get(key) - if key in DURATION_KEYS: - same = ttl_seconds(got) == ttl_seconds(want) - elif key in ("allowed_redirect_uris", "oidc_scopes"): - same = sorted(got or []) == sorted(want) - else: - same = got == want - if not same: - mismatches.append(f"role {key} differs") - return mismatches - - -def run_apply(ccr: dict[str, Any], repo_dir: Path, bao_bin: str, quiet: bool = False) -> None: - openbao = ccr["openbao"] - auth = openbao["auth"] - mounts = _read_json(bao_bin, ["auth", "list"], repo_dir) - mounts = mounts.get("data", mounts) - if (mounts.get(f"{auth['mount']}/") or {}).get("type") != "oidc": - raise BaoError(f"auth mount {auth['mount']}/ is absent or not oidc; this lane never enables it") - - current = _run(bao_bin, ["policy", "read", BOUNDARY_POLICY], repo_dir, quiet=True, capture=True) - present, total = boundary_presence(ccr, current) - if 0 < present < total: - raise BaoError("boundary policy holds some but not all of this matrix's denies; review, do not merge") - with tempfile.TemporaryDirectory() as tmp: - if present == 0: - overlay = (repo_dir / openbao["boundary_overlay_file"]).read_text(encoding="utf-8") - boundary_path = Path(tmp) / "boundary.hcl" - # Preserve live unrelated policy verbatim; add only the reviewed denies. - boundary_path.write_text(current.rstrip("\n") + "\n\n" + overlay, encoding="utf-8") - _run(bao_bin, ["policy", "write", BOUNDARY_POLICY, str(boundary_path)], repo_dir, quiet) - _run(bao_bin, ["policy", "write", openbao["policy_name"], openbao["policy_file"]], repo_dir, quiet) - role_path = Path(tmp) / "role.json" - role_path.write_text(json.dumps(role_payload(ccr, repo_dir), sort_keys=True) + "\n") - _run(bao_bin, ["write", f"auth/{auth['mount']}/role/{auth['role']}", f"@{role_path}"], repo_dir, quiet) - mismatches = readback_mismatches(ccr, repo_dir, bao_bin) - if mismatches: - raise BaoError("readback mismatch: " + "; ".join(mismatches)) diff --git a/scripts/operators_entry_retire.py b/scripts/operators_entry_retire.py deleted file mode 100644 index 032971e..0000000 --- a/scripts/operators_entry_retire.py +++ /dev/null @@ -1,196 +0,0 @@ -#!/usr/bin/env python3 -"""Attended, silent retirement of the deprecated operators/nextcloud entry. - -Founder decision 2026-10-10: ``operators/nextcloud`` (the base entry, not -``operators/nextcloud/backup``) is a deprecated credential and is removed. The -operators mount is deliberately delete-free for ``operator-custody`` and absent -from ``platform-admin`` (openbao/policies/operator-custody.hcl: "destroying a -version is a separate, deliberate act under a broader identity"). This helper is -that act, and it grants nothing standing: - -1. As platform-admin (warden attended envelope), write one-shot policy - ``operators-retire-nextcloud``: ``delete`` and ``read`` on - ``operators/metadata/nextcloud``, ``read`` on - ``operators/metadata/nextcloud/backup``, ``revoke-self``, nothing else. -2. Mint an orphan token with only that policy (60s, 8 uses, no default policy). -3. With that token: prove the entry exists (record its version count) and that - ``nextcloud/backup`` exists; metadata-delete ``operators/nextcloud``; prove it - is absent and that ``nextcloud/backup`` is unchanged. -4. Revoke the token and delete the one-shot policy, on every path. - -No value is read. Run from a railiance-platform checkout: - - BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \\ - warden access openbao-platform-admin-login --exec -- \\ - python3 scripts/operators_entry_retire.py \\ - --confirm 'RETIRE operators/nextcloud' --receipt "$HOME/operators-nextcloud-retire.json" - -``--dry-run`` runs steps 1, 2 and the existence checks, deletes nothing, and -cleans up. The process prints nothing; the receipt (0600, refused if it exists) -holds versions, booleans and an outcome code. - -Exit codes: 0 retired / dry-run clean / already absent; 1 contained internal -error; 2 precondition refused; 4 policy, token or delete failure; 5 verification -failure (entry still present, or the sibling changed). -""" -from __future__ import annotations - -import argparse -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile - -ENTRY = 'nextcloud' -SIBLING = 'nextcloud/backup' -POLICY = 'operators-retire-nextcloud' -CONFIRM = 'RETIRE operators/nextcloud' -SCHEMA = 'platform.operators-entry-retire.v1' -POLICY_HCL = f'''# One-shot: {CONFIRM} (founder decision 2026-10-10). Deleted after use. -path "operators/metadata/{ENTRY}" {{ - capabilities = ["read", "delete"] -}} -path "operators/metadata/{SIBLING}" {{ - capabilities = ["read"] -}} -path "auth/token/revoke-self" {{ - capabilities = ["update"] -}} -''' - - -class Exit(Exception): - def __init__(self, code: int, reason: str): - super().__init__(reason) - self.code = code - self.reason = reason - - -def bao(args, token=None): - env = dict(os.environ) - if token is not None: - env['BAO_TOKEN'] = env['VAULT_TOKEN'] = token - return subprocess.run(['bao', *args], env=env, capture_output=True, timeout=60) - - -def meta(path, token): - """Version count of a KV metadata entry, None if provably absent.""" - result = bao(['read', '-format=json', f'operators/metadata/{path}'], token=token) - if result.returncode != 0: - if b'404' in result.stderr or b'No value found' in result.stderr: - return None - raise Exit(2, 'metadata_read_failed') - try: - data = json.loads(result.stdout)['data'] - return {'current_version': data['current_version'], 'versions': len(data.get('versions') or {})} - except (ValueError, KeyError, TypeError): - raise Exit(2, 'metadata_unparseable') from None - - -def run(args, receipt, state): - if args.confirm != CONFIRM: - raise Exit(2, 'exact_confirmation_required') - if (Path.home().parent.name != '.warden-attended-login' - or os.environ.get('BAO_TOKEN') or os.environ.get('VAULT_TOKEN')): - raise Exit(2, 'attended_warden_envelope_required') - ident = bao(['token', 'lookup', '-format=json']) - if ident.returncode != 0: - raise Exit(2, 'identity_lookup_failed') - policies = (json.loads(ident.stdout).get('data') or {}).get('policies') or [] - if 'platform-admin' not in policies or 'root' in policies: - raise Exit(2, 'attended_platform_admin_required') - - with tempfile.TemporaryDirectory() as tmp: - hcl = Path(tmp) / 'policy.hcl' - hcl.write_text(POLICY_HCL) - if bao(['policy', 'write', POLICY, str(hcl)]).returncode != 0: - raise Exit(4, 'policy_write_failed') - state['policy'] = True - created = bao(['token', 'create', '-orphan', f'-policy={POLICY}', '-no-default-policy', - '-ttl=60s', '-explicit-max-ttl=60s', '-use-limit=8', '-format=json']) - if created.returncode != 0: - raise Exit(4, 'token_create_failed') - try: - state['token'] = json.loads(created.stdout)['auth']['client_token'] - except (ValueError, KeyError, TypeError): - raise Exit(4, 'token_create_failed') from None - token = state['token'] - - before = meta(ENTRY, token) - sibling_before = meta(SIBLING, token) - receipt['entry_before'] = before - receipt['sibling_before'] = sibling_before - if sibling_before is None: - raise Exit(2, 'sibling_absent_refusing_to_guess_layout') - if before is None: - return 'already_absent' - if args.dry_run: - return 'dry_run_would_retire' - - if bao(['delete', f'operators/metadata/{ENTRY}'], token=token).returncode != 0: - raise Exit(4, 'delete_failed') - if meta(ENTRY, token) is not None: - raise Exit(5, 'entry_still_present') - sibling_after = meta(SIBLING, token) - receipt['sibling_after'] = sibling_after - if sibling_after != sibling_before: - raise Exit(5, 'sibling_changed') - receipt['entry_deleted'] = True - return 'retired' - - -def cleanup(receipt, state): - if state.get('token'): - receipt['token_revoked'] = bao(['token', 'revoke', '-self'], token=state['token']).returncode == 0 - if state.get('policy'): - receipt['policy_deleted'] = bao(['policy', 'delete', POLICY]).returncode == 0 - - -def main(argv=None): - devnull = os.open(os.devnull, os.O_WRONLY) - os.dup2(devnull, 1) - os.dup2(devnull, 2) - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--confirm', default='') - parser.add_argument('--receipt', type=Path) - parser.add_argument('--dry-run', action='store_true') - try: - args = parser.parse_args(argv) - except SystemExit: - return 2 - receipt = {'schema': SCHEMA, 'mode': 'dry-run' if args.dry_run else 'retire', - 'entry': f'operators/{ENTRY}', 'sibling': f'operators/{SIBLING}', 'value_reads': 0} - fd = None - if args.receipt is not None: - try: - fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - except OSError: - return 2 - state = {} - try: - receipt['outcome'] = run(args, receipt, state) - code = 0 - except Exit as exc: - receipt['outcome'] = exc.reason - code = exc.code - except Exception: - receipt['outcome'] = 'contained_internal_error' - code = 1 - try: - cleanup(receipt, state) - except Exception: - receipt['cleanup_error'] = True - if code == 0 and state.get('policy') and not receipt.get('policy_deleted'): - receipt['outcome'] += '_policy_cleanup_failed' - code = 4 - receipt['exit_code'] = code - if fd is not None: - os.write(fd, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode()) - os.close(fd) - return code - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/scripts/playwright_consumer.py b/scripts/playwright_consumer.py deleted file mode 100644 index 0927e7a..0000000 --- a/scripts/playwright_consumer.py +++ /dev/null @@ -1,215 +0,0 @@ -#!/usr/bin/env python3 -"""Build an isolated consumer kubeconfig and deliver KeyCape tokens to kubectl. - -Token claim checks here prevent accidental credential misuse. Signature verification -and authoritative group mapping belong to the Kubernetes JWT authenticator. -""" -from __future__ import annotations - -import argparse -import base64 -import hashlib -import json -import os -from pathlib import Path -import stat -import subprocess -import sys -import time -from datetime import datetime, timezone - -ROOT = Path(__file__).resolve().parents[1] -SERVER = "https://127.0.0.1:16444" -CA_SHA256 = "794d3badd7c3743fe66c3671e7f152277f2ac9adddbc5cebe534802432861bd7" -ISSUER = "https://kc.coulomb.social" -AUDIENCE = "playwright-kubernetes" -SCOPE = "playwright:connect" -API = "client.authentication.k8s.io/v1" - - -def require(condition: bool, message: str) -> None: - if not condition: - raise ValueError(message) - - -def private_path(value: str) -> Path: - path = Path(os.path.abspath(os.path.expanduser(value))) - require(path.resolve() == path, "symlink paths are refused") - require(not any((p / ".git").exists() for p in path.parents), - "consumer files must be outside Git worktrees") - parent = path.parent.stat() - require(parent.st_uid == os.getuid() and stat.S_IMODE(parent.st_mode) == 0o700, - "parent directory must be owned by the caller with mode 0700") - return path - - -def read_token(value: str) -> tuple[str, int]: - path = private_path(value) - fd = os.open(path, os.O_RDONLY | os.O_NOFOLLOW | os.O_NONBLOCK) - with os.fdopen(fd) as stream: - info = os.fstat(stream.fileno()) - require(stat.S_ISREG(info.st_mode) and info.st_uid == os.getuid() - and stat.S_IMODE(info.st_mode) == 0o600 and info.st_nlink == 1, - "token must be a caller-owned mode-0600 regular file with one link") - require(info.st_size <= 65536, "token response is too large") - response = json.loads(stream.read(65537)) - require(isinstance(response, dict), "invalid token response") - token = response.get("access_token") - require(response.get("token_type", "").lower() == "bearer" - and isinstance(token, str) and len(token.split(".")) == 3, - "expected a KeyCape bearer access token") - part = token.split(".")[1] - claims = json.loads(base64.urlsafe_b64decode(part + "=" * (-len(part) % 4))) - require(isinstance(claims, dict), "invalid token claims") - require(claims.get("iss") == ISSUER, "token issuer mismatch") - aud = claims.get("aud") - require(aud == AUDIENCE or aud == [AUDIENCE], "token audience mismatch") - require(isinstance(claims.get("scope"), str) - and SCOPE in claims["scope"].split(), "token scope mismatch") - principal = claims.get("principal_type") - roles = claims.get("roles", []) - require(principal == "human" or (principal == "service" - and isinstance(roles, list) and "coding-agent" in roles), - "token is not a human or registered coding agent") - require(isinstance(claims.get("sub"), str) and bool(claims["sub"]), - "token subject is missing") - expires = claims.get("exp") - issued = claims.get("iat") - now = time.time() - require(type(expires) is int and type(issued) is int - and issued <= now + 30 and 0 < expires - issued <= 3600 and expires > now + 30, - "token expired or has invalid validity times; authenticate again") - return token, expires - - -def credential(token_file: str) -> dict: - # Check kubectl's destination BEFORE opening the credential file. - info = json.loads(os.environ.get("KUBERNETES_EXEC_INFO", "{}")) - require(info.get("apiVersion") == API and info.get("kind") == "ExecCredential", - "invoke through the generated kubectl configuration") - cluster = info.get("spec", {}).get("cluster", {}) - require(cluster.get("server") == SERVER and not cluster.get("proxy-url") - and not cluster.get("tls-server-name") - and not cluster.get("insecure-skip-tls-verify", False), - "kubectl destination mismatch") - ca = base64.b64decode(cluster.get("certificate-authority-data", ""), validate=True) - require(hashlib.sha256(ca).hexdigest() == CA_SHA256, "kubectl CA mismatch") - require(not sys.stdout.isatty(), "credential output must be consumed by kubectl") - token, expires = read_token(token_file) - return {"apiVersion": API, "kind": "ExecCredential", "status": { - "token": token, - "expirationTimestamp": datetime.fromtimestamp(expires, timezone.utc) - .isoformat().replace("+00:00", "Z"), - }} - - -def configuration(token_file: str) -> dict: - token_path = private_path(token_file) - ca = (ROOT / "config/playwright/railiance01-ca.pem").read_bytes() - require(hashlib.sha256(ca).hexdigest() == CA_SHA256, "public CA source changed") - return { - "apiVersion": "v1", "kind": "Config", "current-context": "playwright", - "clusters": [{"name": "railiance01-playwright", "cluster": { - "server": SERVER, "certificate-authority-data": base64.b64encode(ca).decode(), - }}], - "contexts": [{"name": "playwright", "context": { - "cluster": "railiance01-playwright", "user": "playwright-consumer", - "namespace": "playwright", - }}], - "users": [{"name": "playwright-consumer", "user": {"exec": { - "apiVersion": API, "command": sys.executable, - "args": [str(Path(__file__).resolve()), "credential", "--token-file", str(token_path)], - "interactiveMode": "Never", "provideClusterInfo": True, - }}}], - } - - -def setup(token_file: str, output: str) -> None: - config = configuration(token_file) - target = private_path(output) - require(private_path(token_file) != target, "token and kubeconfig paths must differ") - fd = os.open(target, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "w") as stream: - json.dump(config, stream, indent=2) - stream.write("\n") - print("Created isolated consumer kubeconfig; authentication and API transport are required.") - - -def verify(kubeconfig: str) -> dict: - path = private_path(kubeconfig) - config = json.loads(path.read_text()) - token_file = config["users"][0]["user"]["exec"]["args"][-1] - require(config == configuration(token_file), - "verification requires the unmodified generated consumer configuration") - - def post(endpoint: str, payload: dict) -> dict: - result = subprocess.run( - ["kubectl", "--kubeconfig", str(path), "--context", "playwright", - "--request-timeout=15s", "create", "--raw", endpoint, "-f", "-"], - input=json.dumps(payload), text=True, capture_output=True, timeout=25, - ) - require(result.returncode == 0, "consumer API request failed; check login and API transport") - return json.loads(result.stdout) - - identity = post("/apis/authentication.k8s.io/v1/selfsubjectreviews", { - "apiVersion": "authentication.k8s.io/v1", "kind": "SelfSubjectReview", - })["status"]["userInfo"] - username = identity["username"] - require(username.startswith(("keycape:human:", "keycape:service:")) - and set(identity.get("groups", [])) == {"playwright-consumers", "system:authenticated"}, - "API returned an unexpected consumer identity or groups") - checks = [ - ("pods-list", "playwright", "", "pods", "", "list", "", True), - ("deployment-get", "playwright", "apps", "deployments", "", "get", "playwright", True), - ("forward", "playwright", "", "pods", "portforward", "create", "", True), - ("secrets-denied", "playwright", "", "secrets", "", "list", "", False), - ("exec-denied", "playwright", "", "pods", "exec", "create", "", False), - ("write-denied", "playwright", "apps", "deployments", "", "patch", "playwright", False), - ("other-namespace-denied", "default", "", "pods", "", "list", "", False), - ("other-deployment-denied", "playwright", "apps", "deployments", "", "get", "other", False), - ("impersonation-denied", "", "", "users", "", "impersonate", "", False), - ] - for name, namespace, group, resource, subresource, verb, resource_name, expected in checks: - result = post("/apis/authorization.k8s.io/v1/selfsubjectaccessreviews", { - "apiVersion": "authorization.k8s.io/v1", "kind": "SelfSubjectAccessReview", - "spec": {"resourceAttributes": { - "namespace": namespace, "group": group, "resource": resource, - "subresource": subresource, "verb": verb, "name": resource_name, - }}, - }) - status = result["status"] - require(not status.get("evaluationError") and status.get("allowed") is expected, - "consumer permission check failed: " + name) - return {"identity": username, "groups": sorted(identity["groups"]), - "impersonated": False, "checks_passed": [c[0] for c in checks]} - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - subs = parser.add_subparsers(dest="action", required=True) - config = subs.add_parser("setup", help="create a new isolated kubeconfig") - config.add_argument("--token-file", required=True) - config.add_argument("--out", required=True) - auth = subs.add_parser("credential", help="kubectl exec plugin; never run directly") - auth.add_argument("--token-file", required=True) - check = subs.add_parser("verify", help="prove current identity and namespace permissions") - check.add_argument("--kubeconfig", required=True) - args = parser.parse_args() - try: - if args.action == "setup": - setup(args.token_file, args.out) - elif args.action == "credential": - print(json.dumps(credential(args.token_file))) - else: - print(json.dumps(verify(args.kubeconfig), indent=2)) - except (OSError, ValueError, KeyError, IndexError, TypeError, AttributeError, OverflowError, - subprocess.TimeoutExpired): - # Parse/provider errors may contain secret input; never echo the exception. - print("Playwright consumer command failed. Check private file permissions, token validity, " - "the pinned API route and owner activation status.", file=sys.stderr) - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/prepare-backup-pair-slots.py b/scripts/prepare-backup-pair-slots.py deleted file mode 100644 index 628bc10..0000000 --- a/scripts/prepare-backup-pair-slots.py +++ /dev/null @@ -1,102 +0,0 @@ -#!/usr/bin/env python3 -"""Create the two empty backup-primary KV slots. - -One CAS=0 write per path stores both field names with the placeholder -PENDING. The founder replaces that version in the OpenBao UI. No provider -key is read or written. -""" - -from __future__ import annotations - -import json -import os -import subprocess -from pathlib import Path - -STAGE = Path("/home/worsch/.local/state/railiance/receipts/backup-pair-slots.stage") -RECEIPT = Path("/home/worsch/.local/state/railiance/receipts/backup-pair-slots.json") -PATHS = ( - "platform/workloads/coulomb-social/backup-primary", - "platform/workloads/email-connect/backup-primary", -) - - -def stage(text): - STAGE.parent.mkdir(mode=0o700, exist_ok=True) - STAGE.write_text(text + "\n") - STAGE.chmod(0o600) - - -def run(args, token=None): - env = os.environ.copy() - if token is None: - env.pop("BAO_TOKEN", None) - env.pop("VAULT_TOKEN", None) - else: - env["BAO_TOKEN"] = token - env["VAULT_TOKEN"] = token - return subprocess.run(args, capture_output=True, env=env) - - -def main(): - created = run( - [ - "bao", "token", "create", - "-policy=backup-pair-writer", - "-ttl=5m", - "-use-limit=3", - "-orphan", - "-format=json", - ] - ) - if created.returncode: - stage(f"token_create_failed {created.returncode}") - raise SystemExit(51) - token = json.loads(created.stdout)["auth"]["client_token"] - body = Path(os.environ["HOME"]) / "backup-pair-slot.json" - body.write_text(json.dumps({ - "AWS_ACCESS_KEY_ID": "PENDING", - "AWS_SECRET_ACCESS_KEY": "PENDING", - })) - body.chmod(0o600) - puts = [] - try: - for path in PATHS: - meta = run(["bao", "kv", "metadata", "get", "-format=json", path]) - if meta.returncode == 0: - stage(f"already_present {path}") - raise SystemExit(52) - put = run(["bao", "kv", "put", "-cas=0", path, f"@{body}"], token=token) - puts.append({"path": path, "put_returncode": put.returncode}) - if put.returncode: - stage(f"put_failed {path} {put.returncode}") - raise SystemExit(53) - finally: - body.unlink(missing_ok=True) - run(["bao", "token", "revoke", "-self"], token=token) - slots = [] - for path in PATHS: - meta = run(["bao", "kv", "metadata", "get", "-format=json", path]) - if meta.returncode: - slots.append({"path": path, "metadata_returncode": meta.returncode}) - continue - data = json.loads(meta.stdout)["data"] - slots.append({ - "path": path, - "current_version": data.get("current_version"), - "version_count": len(data.get("versions") or {}), - }) - RECEIPT.parent.mkdir(mode=0o700, exist_ok=True) - RECEIPT.write_text(json.dumps({"slots": slots, "puts": puts}, indent=2) + "\n") - RECEIPT.chmod(0o600) - stage("ok") - - -if __name__ == "__main__": - try: - main() - except SystemExit: - raise - except Exception as exc: - stage("failed " + type(exc).__name__) - raise SystemExit(54) from None diff --git a/scripts/primary_application_archive.py b/scripts/primary_application_archive.py deleted file mode 100644 index 3976511..0000000 --- a/scripts/primary_application_archive.py +++ /dev/null @@ -1,375 +0,0 @@ -#!/usr/bin/env python3 -"""Immutable application archives through the platform's primary storage. - -Credentials are owner-delivered environment fields, never read from another -workload's Secret. CLI transfer previews by default. Output is redacted. -""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import os -import re -import secrets -import stat -import tempfile -from datetime import UTC, datetime -from pathlib import Path - -ENDPOINT = "https://s3.nl-ams.scw.cloud" -BUCKET = "railiance-platform-pg-backup" -PROFILES = { - "coulomb-social": "platform-pg/application-archives/coulomb-social/", - "email-connect": "platform-pg/application-archives/email-connect/", -} -PART_SIZE = 64 * 1024**2 -MAX_BYTES = 20 * 1024**3 - - -def private_directory(path): - info = path.lstat() - if not stat.S_ISDIR(info.st_mode) or info.st_uid != os.geteuid() or info.st_mode & 0o077: - raise ValueError("private_owner_directory_required") - - -def regular_file(path): - info = path.lstat() - if not stat.S_ISREG(info.st_mode) or info.st_uid != os.geteuid() or info.st_mode & 0o077: - raise ValueError("private_owner_file_required") - - -def digest(path): - with path.open("rb") as source: - return hashlib.file_digest(source, "sha256").hexdigest() - - -def checkpoint(path, receipt): - fd, name = tempfile.mkstemp(prefix="archive-receipt-", dir=path.parent) - with os.fdopen(fd, "w") as output: - json.dump(receipt, output, indent=2) - output.flush() - os.fsync(output.fileno()) - Path(name).replace(path) - - -def key_for(application, captured_at): - stamp = datetime.fromisoformat(captured_at.replace("Z", "+00:00")) - if stamp.tzinfo is None: - raise ValueError("capture_timezone_required") - stamp = stamp.astimezone(UTC) - return ( - PROFILES[application] - + stamp.strftime("%Y/%m/%d/%Y%m%dT%H%M%SZ-") - + secrets.token_hex(12) - + ".tar.age" - ) - - -def validate_key(application, key): - suffix = key.removeprefix(PROFILES[application]) - if not key.startswith(PROFILES[application]) or not re.fullmatch( - r"\d{4}/\d{2}/\d{2}/\d{8}T\d{6}Z-[a-f0-9]{24}\.tar\.age", suffix - ): - raise ValueError("archive_key_outside_contract") - - -def missing(error): - response = getattr(error, "response", {}) - return response.get("ResponseMetadata", {}).get("HTTPStatusCode") == 404 - - -def require_version(value): - if ( - not isinstance(value, str) - or not value - or value == "null" - or any(c in value for c in "\r\n") - ): - raise ValueError("versioned_archive_required") - return value - - -def existing(client, key, expected, size): - try: - head = client.head_object(Bucket=BUCKET, Key=key) - except Exception as error: - if missing(error): - return None - raise - if head.get("ContentLength") != size or head.get("Metadata", {}).get("sha256") != expected: - raise ValueError("existing_archive_binding_mismatch") - return require_version(head.get("VersionId")) - - -def transfer(client, application, source, output, receipt, save=lambda: None): - regular_file(source) - private_directory(output.parent) - size = source.stat().st_size - if not 0 < size <= MAX_BYTES or output.exists() or output.is_symlink(): - raise ValueError("invalid_archive_source_or_output") - with source.open("rb") as stream: - if stream.read(22) != b"age-encryption.org/v1\n": - raise ValueError("encrypted_archive_required") - expected = digest(source) - key = receipt["object_key"] - validate_key(application, key) - if receipt.get("ciphertext_sha256", expected) != expected: - raise ValueError("archive_receipt_binding_mismatch") - receipt.update( - application=application, - destination="s3://" + BUCKET + "/" + key, - ciphertext_sha256=expected, - ciphertext_bytes=size, - remote_retrieval_verified=False, - stage="existing_object_check", - ) - save() - version = existing(client, key, expected, size) - if receipt.get("version_id") and receipt["version_id"] != version: - raise ValueError("existing_archive_version_changed") - upload_id = None - if version is None: - try: - receipt["stage"] = "multipart_upload" - save() - upload_id = client.create_multipart_upload( - Bucket=BUCKET, - Key=key, - ContentType="application/octet-stream", - Metadata={"sha256": expected, "application": application}, - )["UploadId"] - parts = [] - with source.open("rb") as stream: - while block := stream.read(PART_SIZE): - number = len(parts) + 1 - result = client.upload_part( - Bucket=BUCKET, Key=key, UploadId=upload_id, PartNumber=number, Body=block - ) - parts.append({"PartNumber": number, "ETag": result["ETag"]}) - completed = client.complete_multipart_upload( - Bucket=BUCKET, - Key=key, - UploadId=upload_id, - MultipartUpload={"Parts": parts}, - IfNoneMatch="*", - ) - upload_id = None - version = require_version(completed.get("VersionId")) - receipt["multipart_completed"] = True - finally: - if upload_id: - try: - client.abort_multipart_upload(Bucket=BUCKET, Key=key, UploadId=upload_id) - receipt["multipart_aborted"] = True - except Exception: - receipt["multipart_aborted"] = False - save() - else: - receipt["reused_verified_binding"] = True - receipt.update(stage="version_pinned_readback", version_id=version) - save() - download(client, key, version, size, expected, output) - receipt.update( - status="offsite_fetched_verified", - stage="transfer_verified", - remote_retrieval_verified=True, - immutable_create=True, - ) - save() - return receipt - - -def download(client, key, version, size, expected, output): - private_directory(output.parent) - if output.exists() or output.is_symlink() or not 0 < size <= MAX_BYTES: - raise ValueError("fresh_bounded_readback_required") - response = client.get_object(Bucket=BUCKET, Key=key, VersionId=version) - if response["ContentLength"] != size or response.get("VersionId") != version: - response["Body"].close() - raise ValueError("readback_version_or_length_mismatch") - read_bytes = 0 - with response["Body"] as body, output.open("xb") as target: - output.chmod(0o600) - while block := body.read(8 * 1024**2): - read_bytes += len(block) - if read_bytes > size: - raise ValueError("readback_exceeds_expected_size") - target.write(block) - if read_bytes != size or digest(output) != expected: - raise ValueError("readback_digest_mismatch") - - -def retrieve(client, application, output, receipt): - """Node-loss recovery needs only the independently retained public receipt.""" - key = receipt.get("object_key", "") - validate_key(application, key) - version = require_version(receipt.get("version_id")) - expected = receipt.get("ciphertext_sha256", "") - size = receipt.get("ciphertext_bytes", 0) - if ( - receipt.get("schema") != "platform.application-primary-archive.v1" - or receipt.get("application") != application - or receipt.get("status") != "offsite_fetched_verified" - or receipt.get("remote_retrieval_verified") is not True - or receipt.get("destination") != "s3://" + BUCKET + "/" + key - or not re.fullmatch(r"[a-f0-9]{64}", expected) - or not isinstance(size, int) - or isinstance(size, bool) - or not 0 < size <= MAX_BYTES - ): - raise ValueError("verified_recovery_receipt_required") - download(client, key, version, size, expected, output) - return { - "status": "retrieved_verified", - "application": application, - "object_key": key, - "version_id": version, - "ciphertext_sha256": expected, - "ciphertext_bytes": size, - "application_restore_verified": False, - "provider_mutations": False, - } - - -def runtime_client(): - import boto3 - from botocore.config import Config - - key = os.environ.get("AWS_ACCESS_KEY_ID", "").strip() - secret = os.environ.get("AWS_SECRET_ACCESS_KEY", "").strip() - if not key or not secret: - raise ValueError("owner_delivered_primary_custody_required") - return boto3.client( - "s3", - endpoint_url=ENDPOINT, - region_name="nl-ams", - aws_access_key_id=key, - aws_secret_access_key=secret, - config=Config( - signature_version="s3v4", - connect_timeout=15, - read_timeout=180, - retries={"max_attempts": 3}, - s3={"addressing_style": "path"}, - request_checksum_calculation="when_required", - response_checksum_validation="when_required", - ), - ) - - -def check_runtime(): - from botocore.session import get_session - - model = get_session().get_service_model("s3").operation_model("CompleteMultipartUpload") - if "IfNoneMatch" not in model.input_shape.members: - raise ValueError("conditional_multipart_sdk_required") - if not all( - os.environ.get(name, "").strip() for name in ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"] - ): - raise ValueError("owner_delivered_primary_custody_required") - return { - "status": "configured", - "credential_values_printed": False, - "provider_contacted": False, - "conditional_multipart_sdk": True, - } - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("action", choices=["check", "transfer", "retrieve"]) - parser.add_argument("--application", choices=PROFILES, required=True) - parser.add_argument("--source", type=Path) - parser.add_argument("--output", type=Path) - parser.add_argument("--receipt", type=Path) - parser.add_argument("--captured-at") - parser.add_argument("--execute", action="store_true") - args = parser.parse_args() - if args.action == "check": - print(json.dumps(check_runtime())) - return 0 - if not args.execute: - print( - json.dumps( - { - "status": "reviewed", - "provider_contacted": False, - "bucket": BUCKET, - "prefix": PROFILES[args.application], - "immutable_create": True, - } - ) - ) - return 0 - if args.action == "retrieve": - if not args.output or not args.receipt: - raise ValueError("retrieval_arguments_required") - regular_file(args.receipt) - receipt = json.loads(args.receipt.read_text()) - print(json.dumps(retrieve(runtime_client(), args.application, args.output, receipt))) - return 0 - if not all([args.source, args.output, args.receipt, args.captured_at]): - raise ValueError("transfer_arguments_required") - regular_file(args.source) - private_directory(args.output.parent) - private_directory(args.receipt.parent) - if args.output.is_symlink() or args.receipt.is_symlink(): - raise ValueError("linked_transfer_path_refused") - if args.receipt.exists(): - regular_file(args.receipt) - receipt = json.loads(args.receipt.read_text()) - if ( - receipt.get("schema") != "platform.application-primary-archive.v1" - or receipt.get("application") != args.application - or receipt.get("captured_at") != args.captured_at - ): - raise ValueError("transfer_receipt_scope_mismatch") - else: - receipt = { - "schema": "platform.application-primary-archive.v1", - "status": "pending", - "application": args.application, - "captured_at": args.captured_at, - "object_key": key_for(args.application, args.captured_at), - } - validate_key(args.application, receipt["object_key"]) - if args.output.exists(): - regular_file(args.output) - if ( - receipt.get("status") == "offsite_fetched_verified" - and receipt.get("remote_retrieval_verified") - and receipt.get("immutable_create") - and require_version(receipt.get("version_id")) - and args.source.stat().st_size == receipt.get("ciphertext_bytes") - and digest(args.output) == receipt.get("ciphertext_sha256") - and digest(args.source) == receipt.get("ciphertext_sha256") - ): - print(json.dumps(receipt)) - return 0 - raise ValueError("unverified_existing_readback_requires_review") - try: - checkpoint(args.receipt, receipt) - transfer( - runtime_client(), - args.application, - args.source, - args.output, - receipt, - lambda: checkpoint(args.receipt, receipt), - ) - except Exception as error: - receipt.update(status="failed", failure_type=type(error).__name__) - checkpoint(args.receipt, receipt) - raise - print(json.dumps(receipt)) - return 0 - - -if __name__ == "__main__": - try: - raise SystemExit(main()) - except Exception as error: - print(json.dumps({"status": "failed", "failure_type": type(error).__name__})) - raise SystemExit(1) diff --git a/scripts/prove-backup-publisher-prefixes.py b/scripts/prove-backup-publisher-prefixes.py deleted file mode 100644 index faa0802..0000000 --- a/scripts/prove-backup-publisher-prefixes.py +++ /dev/null @@ -1,98 +0,0 @@ -#!/usr/bin/env python3 -"""Prove each new publisher can abort its own upload and cannot write elsewhere. - -No completed object is left behind. The receipt contains status codes only. -""" - -from __future__ import annotations - -import json -import subprocess -from pathlib import Path - -import boto3 -from botocore.exceptions import ClientError - -RECEIPT = Path("/home/worsch/.local/state/railiance/receipts/backup-publisher-prefix-proof.json") -BUCKET = "railiance-platform-pg-backup" -PROFILES = { - "coulomb-social": "platform-pg/application-archives/coulomb-social/.policy-probe", - "email-connect": "platform-pg/application-archives/email-connect/.policy-probe", -} -FOREIGN = "platform-pg/apps-pg/.policy-probe" - - -def kv(path): - proc = subprocess.run(["bao", "kv", "get", "-format=json", path], capture_output=True) - if proc.returncode: - raise SystemExit(81) - return json.loads(proc.stdout)["data"]["data"] - - -def client(values): - return boto3.client( - "s3", - region_name="nl-ams", - endpoint_url="https://s3.nl-ams.scw.cloud", - aws_access_key_id=values["AWS_ACCESS_KEY_ID"], - aws_secret_access_key=values["AWS_SECRET_ACCESS_KEY"], - ) - - -def code(exc): - return exc.response.get("Error", {}).get("Code", "error") - - -def attempt(action): - try: - action() - return "ok" - except ClientError as exc: - return code(exc) - - -def open_or_deny(remote, key): - try: - upload = remote.create_multipart_upload(Bucket=BUCKET, Key=key) - except ClientError as exc: - return code(exc) - remote.abort_multipart_upload(Bucket=BUCKET, Key=key, UploadId=upload["UploadId"]) - return "opened_then_aborted" - - -def main(): - rows = [] - for name, own_key in PROFILES.items(): - remote = client(kv(f"platform/workloads/{name}/backup-primary")) - own = "error" - try: - upload = remote.create_multipart_upload(Bucket=BUCKET, Key=own_key) - remote.abort_multipart_upload( - Bucket=BUCKET, Key=own_key, UploadId=upload["UploadId"] - ) - own = "aborted" - except ClientError as exc: - own = code(exc) - sibling_key = next(key for other, key in PROFILES.items() if other != name) - foreign = open_or_deny(remote, FOREIGN) - sibling = open_or_deny(remote, sibling_key) - delete = attempt(lambda key=own_key: remote.delete_object(Bucket=BUCKET, Key=key)) - rows.append({ - "application": name, - "own_multipart": own, - "foreign_multipart": foreign, - "sibling_multipart": sibling, - "delete_object": delete, - }) - RECEIPT.parent.mkdir(mode=0o700, exist_ok=True) - RECEIPT.write_text(json.dumps({"proofs": rows}, indent=2) + "\n") - RECEIPT.chmod(0o600) - - -if __name__ == "__main__": - try: - main() - except SystemExit: - raise - except Exception: - raise SystemExit(82) from None diff --git a/scripts/provision-reuse-surface-forgejo-source.py b/scripts/provision-reuse-surface-forgejo-source.py deleted file mode 100755 index 24a83b6..0000000 --- a/scripts/provision-reuse-surface-forgejo-source.py +++ /dev/null @@ -1,383 +0,0 @@ -#!/usr/bin/env python3 -"""Silent attended CCR-2026-0046 bootstrap; never runs without exact approval. - -Only this seven-repository identity/PAT/KV path is mutable. No policy, ESO, -consumer rollout, or registration mutations occur here. A receipt is evidence, -not authorization. Uncertain requests are recorded and reconciled, never retried -in the same execution; orphan PAT drift requires attended recovery. -""" -from __future__ import annotations -import argparse -import base64 -import datetime as dt -import fcntl -import hashlib -import json -import os -from pathlib import Path -import re -import secrets -import subprocess -import sys -import urllib.error -import urllib.request -import yaml - -ROOT = Path(__file__).resolve().parent.parent -CCR = 'CCR-2026-0046' -CCR_FILE = 'credential-change-requests/CCR-2026-0046-reuse-surface-forgejo-source-read.yaml' -PLAN_FILE = 'docs/proposals/2026-10-15-reuse-private-reader/bootstrap-contract.json' -SCRIPT_FILE = 'scripts/provision-reuse-surface-forgejo-source.py' -API = 'https://forgejo.coulomb.social/api/v1' -USER = 'reuse-surface-source' -TEAM = 'reuse-surface-source-readers' -ORG = 'coulomb' -KV = 'platform/workloads/reuse/reuse-surface/forgejo-source-read' -FIELD = 'FORGEJO_SOURCE_TOKEN' -PREFIX = 'reuse-surface-source-read-' -REPOS = frozenset('rapp-openbao rapp-policy-nexus rapp-postgres rapp-sbom-nexus rapp-telemetry rapp-tenant-engine rapp-user-engine'.split()) -SCOPES = ['read:repository'] -UNITS = {'repo.code': 'read', **{x: 'none' for x in ('repo.actions', 'repo.packages', 'repo.issues', 'repo.ext_issues', 'repo.wiki', 'repo.pulls', 'repo.releases', 'repo.projects', 'repo.ext_wiki')}} - -class Refused(Exception): - """Messages must be fixed non-secret codes, never provider text.""" - -class NoRedirect(urllib.request.HTTPRedirectHandler): - def redirect_request(self, *args, **kwargs): - raise Refused('redirect_refused') - -class Journal: - def __init__(self, path): - flags = os.O_RDWR | os.O_CREAT | os.O_APPEND | os.O_NOFOLLOW - self.fd = os.open(path, flags, 0o600) - if os.fstat(self.fd).st_mode & 0o077: - os.close(self.fd) - raise Refused('receipt_permissions') - fcntl.flock(self.fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - self.attempt = secrets.token_hex(12) - def put(self, event, **fields): - # Only locally authored operation tags, scalar status/IDs are accepted. - allowed = {'operation', 'status', 'http_status', 'token_id', 'team_id', 'outcome', 'error_code'} - if set(fields) - allowed: - raise Refused('receipt_fields') - record = {'at': dt.datetime.now(dt.timezone.utc).isoformat(), 'ccr': CCR, - 'attempt': self.attempt, 'event': event, **fields} - data = (json.dumps(record, sort_keys=True) + '\n').encode() - while data: - written = os.write(self.fd, data) - if written <= 0: - raise Refused('receipt_write_failed') - data = data[written:] - os.fsync(self.fd) - def close(self): - os.close(self.fd) - -class Client: - def __init__(self, admin, journal): - self.admin, self.journal = admin, journal - self.opener = urllib.request.build_opener(NoRedirect()) - def request(self, method, path, payload=None, expected=(200,), *, token=None, sudo=False, basic=None, operation=None): - if not path.startswith('/') or '://' in path: - raise Refused('invalid_api_path') - headers = {'Authorization': basic or 'token ' + (self.admin if token is None else token), 'Accept': 'application/json'} - if sudo: - headers['Sudo'] = USER - if payload is not None: - headers['Content-Type'] = 'application/json' - if method != 'GET': - if not operation: - raise Refused('mutation_without_intent') - self.journal.put('intent', operation=operation) - req = urllib.request.Request(API + path, data=None if payload is None else json.dumps(payload).encode(), headers=headers, method=method) - self.last_total_count = None - try: - with self.opener.open(req, timeout=30) as response: - status, body = response.status, response.read(2_000_001) - raw_total = response.headers.get('X-Total-Count') - if raw_total is not None: - if not re.fullmatch(r'[0-9]+', raw_total): - raise Refused('invalid_total_count') - self.last_total_count = int(raw_total) - except urllib.error.HTTPError as error: - status, body = error.code, b'' - except Exception: - if operation: - self.journal.put('outcome', operation=operation, status='ambiguous') - raise Refused('request_observation_failed') from None - if operation: - self.journal.put('outcome', operation=operation, status='observed', http_status=status) - if status not in expected: - raise Refused('unexpected_http_status') - if len(body) > 2_000_000: - raise Refused('response_bound_exceeded') - if not body: - return status, None - try: - return status, json.loads(body) - except Exception: - raise Refused('invalid_json') from None - def pages(self, path, *, sudo=False): - """Page until observed empty; requested size may be server-clamped. - - Forgejo11.0.3 default MaxResponseItems=50, so a response shorter than - our requested100 proves nothing. Detect unstable/duplicate IDs and - reconcile X-Total-Count whenever available; exceeding bound refuses. - """ - items, seen, expected_total = [], set(), None - for page in range(1, 21): - separator = '&' if '?' in path else '?' - _, batch = self.request('GET', path + separator + f'limit=100&page={page}', sudo=sudo) - if not isinstance(batch, list) or any(not isinstance(x, dict) or type(x.get('id')) is not int or x['id'] <= 0 for x in batch): - raise Refused('invalid_list') - observed_total = getattr(self, 'last_total_count', None) - if observed_total is not None: - if expected_total is not None and observed_total != expected_total: - raise Refused('unstable_total_count') - expected_total = observed_total - if not batch: - if expected_total is not None and len(items) != expected_total: - raise Refused('total_count_mismatch') - return items - for item in batch: - if item['id'] in seen: - raise Refused('duplicate_page_id') - seen.add(item['id']) - items.extend(batch) - if expected_total is not None and len(items) > expected_total: - raise Refused('total_count_mismatch') - raise Refused('pagination_bound_exceeded') - -class Bao: - @staticmethod - def run(args, stdin=None): - try: - return subprocess.run(['bao', *args], input=stdin, capture_output=True, timeout=60, check=False, cwd=ROOT) - except Exception: - raise Refused('bao_observation_failed') from None - def get(self): - result = self.run(['kv', 'get', '-format=json', KV]) - if result.returncode != 0: - # Distinguish an actual not-found response from denied/unavailable. - # Never include captured raw errors in any evidence. - if re.search(rb'Code:\s*404\b', result.stderr or b'') or b'No value found at ' in (result.stderr or b''): - return None - raise Refused('bao_preflight_failed') - try: - data = json.loads(result.stdout)['data']['data'] - except Exception: - raise Refused('bao_invalid_record') from None - if set(data) != {FIELD, 'API_USER', 'API_BASE_URL', 'TOKEN_SCOPES', 'GENERATED_AT', 'CCR_ID'} or data['API_USER'] != USER or data['API_BASE_URL'] != 'https://forgejo.coulomb.social' or data['TOKEN_SCOPES'] != 'read:repository' or data['CCR_ID'] != CCR or not isinstance(data[FIELD], str) or not data[FIELD]: - raise Refused('bao_record_conflict') - return data - def put(self, token, journal): - journal.put('intent', operation='kv_create') - timestamp = dt.datetime.now(dt.timezone.utc).isoformat() - try: - result = self.run(['kv', 'put', '-cas=0', KV, FIELD + '=-', 'API_USER=' + USER, - 'API_BASE_URL=https://forgejo.coulomb.social', 'TOKEN_SCOPES=read:repository', - 'GENERATED_AT=' + timestamp, 'CCR_ID=' + CCR], token.encode()) - except Refused: - journal.put('outcome', operation='kv_create', status='ambiguous') - raise - journal.put('outcome', operation='kv_create', status='observed' if result.returncode == 0 else 'ambiguous') - # Readback reconciles even a response failure after successful write. - stored = self.get() - if stored is None or not secrets.compare_digest(stored[FIELD], token): - raise Refused('bao_write_requires_reconciliation') - journal.put('verified', operation='kv_create', status='matched') - - -def valid_user(user): - return isinstance(user, dict) and user.get('login') == USER and user.get('restricted') is True and user.get('is_admin') is False and user.get('visibility') == 'private' - -def valid_team(team): - return isinstance(team, dict) and isinstance(team.get('id'), int) and team.get('name') == TEAM and team.get('permission') == 'read' and team.get('includes_all_repositories') is False and team.get('can_create_org_repo') is False and team.get('units_map') == UNITS - -def repo_names(items): - names = set() - for repo in items: - if repo.get('owner', {}).get('login') != ORG or repo.get('name') not in REPOS: - raise Refused('unexpected_team_repository') - names.add(repo['name']) - if len(names) != len(items): - raise Refused('duplicate_repository') - return names - -def inspect(client, bao): - """Read-only preflight before mutations. - - Forgejo11.0.3 ListMyRepos uses OwnerID plus Collaborate=None; model - ValueOrDefault(true) includes direct access and team grants (not only owned). - Deployed-server API compatibility and live scope proof remain separate gates. - """ - for repo in sorted(REPOS): - _, meta = client.request('GET', f'/repos/{ORG}/{repo}') - if not isinstance(meta, dict) or meta.get('private') is not True or meta.get('full_name') != ORG + '/' + repo: - raise Refused('source_repository_conflict') - _, probe = client.request('GET', '/repos/coulomb/rapp-core-hub') - if not isinstance(probe, dict) or probe.get('private') is not True: - raise Refused('negative_probe_not_private') - _, user = client.request('GET', '/users/' + USER, expected=(200, 404)) - if user is not None and not valid_user(user): - raise Refused('user_conflict') - teams = [x for x in client.pages(f'/orgs/{ORG}/teams') if x.get('name') == TEAM] - if len(teams) > 1: - raise Refused('duplicate_team') - team = teams[0] if teams else None - members, repos = [], set() - if team is not None: - if not valid_team(team): - raise Refused('team_conflict') - members = client.pages('/teams/' + str(team['id']) + '/members') - if len(members) > 1 or any(x.get('login') != USER for x in members): - raise Refused('team_members_conflict') - repos = repo_names(client.pages('/teams/' + str(team['id']) + '/repos')) - tokens = [] - if user is not None: - orgs = client.pages('/user/orgs', sudo=True) - if any(x.get('username') != ORG for x in orgs): - raise Refused('extra_organization_affiliation') - grants = client.pages('/user/teams', sudo=True) - if any(x.get('name') != TEAM or x.get('organization', {}).get('username') != ORG for x in grants): - raise Refused('extra_team_grant') - for meta in client.pages('/user/repos?order_by=id', sudo=True): - if meta.get('private') is True and meta.get('full_name') not in {ORG + '/' + x for x in REPOS}: - raise Refused('extra_private_repository_grant') - if meta.get('owner', {}).get('login') == USER: - raise Refused('service_user_owned_repository') - tokens = client.pages('/users/' + USER + '/tokens') - if len(tokens) > 1 or any(not str(x.get('name', '')).startswith(PREFIX) or x.get('scopes') != SCOPES or not isinstance(x.get('id'), int) for x in tokens): - raise Refused('token_conflict') - stored = bao.get() - if bool(tokens) != bool(stored): - raise Refused('orphan_token_or_kv_requires_recovery') - if tokens and (not isinstance(tokens[0].get('token_last_eight'), str) or len(tokens[0]['token_last_eight']) != 8 or not secrets.compare_digest(tokens[0]['token_last_eight'], stored[FIELD][-8:])): - raise Refused('pat_kv_binding_unverifiable') - if tokens and (not team or repos != REPOS or len(members) != 1): - raise Refused('active_lane_grant_drift') - return user, team, repos, members, tokens, stored - -def verify(client, source_token, plan): - for source in plan['sources']: - _, repo = client.request('GET', '/repos/' + ORG + '/' + source['repo'], token=source_token) - if repo.get('permissions') != {'admin': False, 'push': False, 'pull': True}: - raise Refused('effective_permission_not_read') - # Exact consented bytes fetched quietly; raw output is never retained. - path = f"/repos/{ORG}/{source['repo']}/contents/registry/indexes/capabilities.yaml?ref={source['consented_commit']}" - _, content = client.request('GET', path, token=source_token) - try: - raw = base64.b64decode(content['content'], validate=False) - except Exception: - raise Refused('invalid_index_encoding') from None - if hashlib.sha256(raw).hexdigest() != source['index_sha256']: - raise Refused('index_digest_mismatch') - client.request('GET', f"/repos/{ORG}/{source['repo']}/actions/secrets?limit=1", token=source_token, expected=(403, 404)) - client.request('GET', '/admin/users', token=source_token, expected=(403, 404)) - # A known private excluded source is selected by reviewed contract. - probe = plan['negative_private_probe'] - _, meta = client.request('GET', '/repos/' + ORG + '/' + probe) - if not isinstance(meta, dict) or meta.get('private') is not True: - raise Refused('negative_probe_not_private') - client.request('GET', '/repos/' + ORG + '/' + probe, token=source_token, expected=(403, 404)) - client.journal.put('verified', operation='reader_scope', status='seven_exact_no_extra_private_grants') - -def provision(client, bao, plan): - user, team, repos, members, tokens, stored = inspect(client, bao) - if tokens: - verify(client, stored[FIELD], plan) - client.journal.put('terminal', outcome='verified_noop') - return 'noop' - if user is None: - _, user = client.request('POST', '/admin/users', {'username': USER, 'email': USER + '@coulomb.social', 'full_name': 'Reuse Surface seven-source reader', 'password': secrets.token_urlsafe(48), 'restricted': True, 'must_change_password': False, 'send_notify': False, 'visibility': 'private'}, expected=(201,), operation='user_create') - if not valid_user(user): - raise Refused('created_user_contract_failed') - if team is None: - _, team = client.request('POST', f'/orgs/{ORG}/teams', {'name': TEAM, 'description': CCR + ' exact seven consented source repos', 'permission': 'read', 'includes_all_repositories': False, 'can_create_org_repo': False, 'units': ['repo.code'], 'units_map': UNITS}, expected=(201,), operation='team_create') - if not valid_team(team): - raise Refused('created_team_contract_failed') - team_id = team['id'] - for repo in sorted(REPOS - repos): - client.request('PUT', f'/teams/{team_id}/repos/{ORG}/{repo}', expected=(204,), operation='team_repo_' + repo) - if not members: - client.request('PUT', f'/teams/{team_id}/members/{USER}', expected=(204,), operation='team_member_add') - # Recheck complete grants before creating any PAT; refuse implicit grants. - _, _, actual_repos, actual_members, _, _ = inspect(client, bao) - if actual_repos != REPOS or len(actual_members) != 1: - raise Refused('incomplete_team_scope') - password = secrets.token_urlsafe(48) - client.request('PATCH', '/admin/users/' + USER, {'password': password, 'restricted': True, 'admin': False, 'allow_create_organization': False, 'allow_git_hook': False, 'allow_import_local': False, 'max_repo_creation': 0, 'must_change_password': False, 'active': True, 'visibility': 'private'}, operation='service_password_randomize') - basic = 'Basic ' + base64.b64encode((USER + ':' + password).encode()).decode() - _, record = client.request('POST', '/users/' + USER + '/tokens', {'name': PREFIX + client.journal.attempt, 'scopes': SCOPES}, expected=(201,), basic=basic, operation='pat_create') - if not isinstance(record, dict) or not isinstance(record.get('id'), int) or record.get('scopes') != SCOPES or not isinstance(record.get('sha1'), str) or not record['sha1']: - raise Refused('created_pat_requires_recovery') - client.journal.put('identified', operation='pat_create', token_id=record['id'], team_id=team_id) - token = record['sha1'] - verify(client, token, plan) - bao.put(token, client.journal) - # Repeat complete grant inventory after custody; verify exactly one PAT. - inspect(client, bao) - client.journal.put('terminal', outcome='provisioned_verified_delivery_pending') - return 'minted' - -def approval(path): - try: - receipt = json.loads(Path(path).read_text()) - ccr = yaml.safe_load((ROOT / CCR_FILE).read_text()) - if receipt['ccr_id'] != CCR or receipt['decision'] != 'approved' or not receipt['supervisor'] or not receipt['decision_ref'] or ccr['status'] != 'approved': - raise Refused('approval_missing') - files = {CCR_FILE, PLAN_FILE, SCRIPT_FILE, 'openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl'} - if set(receipt['approved_artifacts']) != files: - raise Refused('approval_artifact_set') - for rel in files: - if hashlib.sha256((ROOT / rel).read_bytes()).hexdigest() != receipt['approved_artifacts'][rel]: - raise Refused('approval_revision_mismatch') - plan = json.loads((ROOT / PLAN_FILE).read_text()) - if {x['repo'] for x in plan['sources']} != REPOS or len(plan['sources']) != 7 or plan['negative_private_probe'] != 'rapp-core-hub' or any(not re.fullmatch(r'[a-f0-9]{40}', x['consented_commit']) or not re.fullmatch(r'[a-f0-9]{64}', x['index_sha256']) for x in plan['sources']): - raise Refused('plan_scope_conflict') - return plan - except Refused: - raise - except Exception: - raise Refused('approval_invalid') from None - -def main(argv=None): - parser = argparse.ArgumentParser() - parser.add_argument('--approval', required=True) - parser.add_argument('--receipt', required=True) - parser.add_argument('--inner', action='store_true', help=argparse.SUPPRESS) - args = parser.parse_args(argv) - journal = None - lane_lock = None - try: - plan = approval(args.approval) - if not args.inner: - # Governed OpenBao attended lane surrounds this command. Warden - # supplies only Forgejo API_TOKEN to inner child, not via argv/files. - result = subprocess.run(['warden', 'access', 'forgejo-admin-api-token', '--field', 'API_TOKEN', '--exec', '--', sys.executable, str(ROOT / SCRIPT_FILE), '--inner', '--approval', str(Path(args.approval).resolve()), '--receipt', str(Path(args.receipt).resolve())], capture_output=True, timeout=900, check=False, cwd=ROOT) - return 0 if result.returncode == 0 else 1 - lane_lock = os.open('/tmp/' + CCR + '-reuse-reader-' + 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 Refused('lane_lock_owner_or_permissions') - fcntl.flock(lane_lock, fcntl.LOCK_EX | fcntl.LOCK_NB) - journal = Journal(args.receipt) - admin = os.environ.get('API_TOKEN') - if not admin: - raise Refused('admin_route_missing') - provision(Client(admin, journal), Bao(), plan) - return 0 - except Exception as error: - if journal: - journal.put('terminal', outcome='stopped_reconcile_before_retry', error_code=str(error) if isinstance(error, 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/scripts/provision_argocd_issue_core_repository.py b/scripts/provision_argocd_issue_core_repository.py deleted file mode 100644 index 2fb3a0d..0000000 --- a/scripts/provision_argocd_issue_core_repository.py +++ /dev/null @@ -1,198 +0,0 @@ -#!/usr/bin/env python3 -"""Silent fixed-scope D2 repository deploy-key issuer/destination actuator. - -Run through the existing Platform attended login and Forgejo admin --exec lane. -No lane grants itself authority. The reviewed contract seals transport/host trust -and existing egress; the command performs no RBAC, policy or network expansion. -No implicit recovery: an existing journal, ambiguous result or drift stops replay. -""" -import argparse -import base64 -from datetime import datetime, timezone -import hashlib -import fnmatch -import json -import os -import re -from pathlib import Path -import stat -import shlex -import subprocess -import sys -import tempfile -import urllib.error -import urllib.request - -REPO='coulomb/rapp-issue-core' -BAO='platform/operators/argocd/repositories/rapp-issue-core' -URL='ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git' -SECRET='rapp-issue-core-repository' -TITLE='argocd-rapp-issue-core-readonly-D2' -D2='89f1058f-33bc-47a8-8a49-0b44d426c67e' -FIXED={'work_record':'RPF-WP-0044-T04','scope_approval_ref':D2,'repository':REPO,'read_only':True,'openbao_path':BAO,'destination':'argocd/'+SECRET,'project':'railiance-tenants','url':URL,'wrap_ttl_seconds':60,'new_network_scope':False,'execution_mode':'attended-founder-bounded'} - - -class Refused(Exception):pass - - -def require(ok,reason): - if not ok:raise Refused(reason) - - -def captured(argv,body=None,env=None): - try: - result=subprocess.run(argv,input=body,env=env,capture_output=True,text=True,timeout=60) - except Exception: - raise Refused('command_unverified') from None - return result - - -def run(argv,body=None,env=None): - result=captured(argv,body,env) - require(result.returncode==0,'command_unverified') - return result.stdout - - -def metadata_children(): - result=captured(['bao','list','-format=json','platform/metadata/operators/argocd/repositories/']) - # Installed CLI represents an empty/404 LIST as exit 2, JSON {}, no stderr. - if result.returncode==2 and result.stdout.strip()=='{}' and not result.stderr.strip():return [] - require(result.returncode==0,'custody_metadata_unverified') - children=json.loads(result.stdout);require(isinstance(children,list),'custody_metadata_unverified');return children - - -def replay_denied(env): - result=captured(['bao','unwrap','-format=json'],env=env) - text=result.stderr or '' - return result.returncode==2 and not result.stdout.strip() and bool(re.search(r'Code:\s*400\b',text)) and bool(re.search(r'^\* wrapping token is not valid or does not exist\s*$',text,re.M)) - - -class NoRedirect(urllib.request.HTTPRedirectHandler): - def redirect_request(self,*args,**kwargs):raise Refused('redirect_refused') - - -def api(token,method,path,payload=None,expected=(200,)): - require(path.startswith('/repos/'+REPO+'/') or path=='/repos/'+REPO,'provider_path_refused') - request=urllib.request.Request('https://forgejo.coulomb.social/api/v1'+path,method=method, - data=None if payload is None else json.dumps(payload).encode(), - headers={'Authorization':'token '+token,'Content-Type':'application/json'}) - try: - with urllib.request.build_opener(NoRedirect()).open(request,timeout=30) as response: - require(response.status in expected,'provider_status_refused') - raw=response.read(1024*1024+1);require(len(raw)<=1024*1024,'provider_body_bound') - return json.loads(raw) if raw else None - except Refused:raise - except Exception:raise Refused('provider_outcome_unverified') from None - - -def validate_contract(contract): - require(isinstance(contract,dict),'contract_type') - for key,value in FIXED.items(): - require(type(contract.get(key)) is type(value) and contract[key]==value,'contract_scope') - for key in ('owner_review_ref','ssh_egress_existing_proof_ref','runtime_identity_ref','rollback_ref'): - require(isinstance(contract.get(key),str) and contract[key].strip(),'missing_owner_proof') - require(isinstance(contract.get('ssh_known_hosts_sha256'),str) and len(contract['ssh_known_hosts_sha256'])==64,'host_pin_required') - require(datetime.now(timezone.utc) 100: - raise p.Refused('recovery_target_invalid') - return target - except p.Refused: - raise - except Exception: - raise p.Refused('recovery_approval_invalid') from None - - -def recover(client, target): - _, user = client.request('GET', '/users/' + p.USER) - if not p.valid_user(user): - raise p.Refused('recovery_user_conflict') - tokens = client.pages('/users/' + p.USER + '/tokens') - if not tokens: - # Absence reconciles a prior ambiguous revocation without new writes. - client.journal.put('terminal', outcome='revoke_verified_absent_noop') - return - if len(tokens) != 1 or tokens[0].get('id') != target['id'] or tokens[0].get('name') != target['name'] or tokens[0].get('scopes') != p.SCOPES: - raise p.Refused('recovery_token_inventory_conflict') - password = secrets.token_urlsafe(48) - client.request('PATCH', '/admin/users/' + p.USER, {'password': password, 'must_change_password': False}, operation='recovery_password_randomize') - basic = 'Basic ' + base64.b64encode((p.USER + ':' + password).encode()).decode() - try: - client.request('DELETE', '/users/' + p.USER + '/tokens/' + str(target['id']), expected=(204, 404), basic=basic, operation='exact_pat_revoke') - except p.Refused: - # Observation failure does not mean DELETE did not apply. Read-only - # reconciliation follows; never issue another DELETE in this execution. - pass - remaining = client.pages('/users/' + p.USER + '/tokens') - if remaining: - raise p.Refused('revoke_unverified_stop') - client.journal.put('verified', operation='exact_pat_revoke', token_id=target['id'], status='inventory_absent') - client.journal.put('terminal', outcome='revoked_verified_kv_preserved') - - -def main(argv=None): - parser = argparse.ArgumentParser() - parser.add_argument('--approval', required=True) - parser.add_argument('--receipt', required=True) - parser.add_argument('--inner', action='store_true', help=argparse.SUPPRESS) - args = parser.parse_args(argv) - journal, lock = None, None - try: - target = approved_target(args.approval) - if not args.inner: - result = subprocess.run(['warden', 'access', 'forgejo-admin-api-token', '--field', 'API_TOKEN', '--exec', '--', sys.executable, str(ROOT / SCRIPT), '--inner', '--approval', str(Path(args.approval).resolve()), '--receipt', str(Path(args.receipt).resolve())], capture_output=True, check=False, timeout=900, cwd=ROOT) - return 0 if result.returncode == 0 else 1 - lock = os.open('/tmp/' + p.CCR + '-reuse-reader-' + str(os.getuid()) + '.lock', os.O_RDWR | os.O_CREAT | os.O_NOFOLLOW, 0o600) - if os.fstat(lock).st_uid != os.getuid() or os.fstat(lock).st_mode & 0o077: - raise p.Refused('lane_lock_owner_or_permissions') - p.fcntl.flock(lock, p.fcntl.LOCK_EX | p.fcntl.LOCK_NB) - journal = p.Journal(args.receipt) - admin = os.environ.get('API_TOKEN') - if not admin: - raise p.Refused('admin_route_missing') - recover(p.Client(admin, journal), target) - return 0 - except Exception as error: - if journal: - journal.put('terminal', outcome='recovery_stopped_reconcile_before_retry', error_code=str(error) if isinstance(error, p.Refused) else 'internal_failure') - return 1 - finally: - if journal: - journal.close() - if lock is not None: - os.close(lock) - -if __name__ == '__main__': - try: - result = main() - except Exception: - result = 1 - raise SystemExit(result) diff --git a/scripts/rpf_wp_0044_t06_attended.sh b/scripts/rpf_wp_0044_t06_attended.sh deleted file mode 100755 index 1e9c338..0000000 --- a/scripts/rpf_wp_0044_t06_attended.sh +++ /dev/null @@ -1,193 +0,0 @@ -#!/usr/bin/env bash -# RPF-WP-0044-T06 attended ESO adoption. The founder runs this in their own terminal. -# -# scripts/rpf_wp_0044_t06_attended.sh [--dry-run] [--precheck-only] -# -# Cluster route: KUBECTL_ROUTE=founder-sudo (default here) runs -# ssh railiance01 sudo -n kubectl ... -# sudo -n never prompts, so pipes stay byte-clean (no ssh -t). -# -# Safety: -# - Credential-kind objects are never fetched, compared or printed. The render is split first. -# - A no-go stops before anything changes. -# - The Application is created only after every pre-check passes. -# - The sync needs the typed word ADOPT and runs with prune=false, force=false and ServerSideApply. -# - No prune and no delete ever happen. On post-check failure the rollback steps are printed, not run. -set -euo pipefail - -DRY=0; PRE_ONLY=0 -for arg in "$@"; do - case "$arg" in - --dry-run) DRY=1 ;; - --precheck-only) PRE_ONLY=1 ;; - *) echo "unknown argument: $arg" >&2; exit 64 ;; - esac -done - -ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -ISSUE_CORE="${ISSUE_CORE_ROOT:-$HOME/rapp-issue-core}" -TARGET_REVENUE="${TARGET_REVENUE_ROOT:-$HOME/target-revenue}" -export KUBECTL_ROUTE="${KUBECTL_ROUTE:-founder-sudo}" -case "$KUBECTL_ROUTE" in - founder-sudo) PREFIX=(sudo -n) ;; - agent) PREFIX=() ;; - *) echo "unknown KUBECTL_ROUTE" >&2; exit 64 ;; -esac -DRAFT="$ROOT/argocd/railiance01/drafts/external-secrets.application.yaml" -STAMP="$(date -u +%Y%m%dT%H%M%SZ)" -EVID="$ROOT/docs/evidence/2026-10-21-eso-adoption-prep/attended-$STAMP" -CHECK=(python3 "$ROOT/scripts/eso_attended_checks.py") -ISSUE_REV=186c817b67d3919a64acdddbd3889e539841f946 -ISSUE_URL=ssh://git@forgejo.coulomb.social:30022/coulomb/rapp-issue-core.git -SYNC_JSON='{"operation":{"initiatedBy":{"username":"rpf-wp-0044-t06"},"sync":{"revision":"0.16.1","prune":false,"syncOptions":["ServerSideApply=true"],"syncStrategy":{"apply":{"force":false}}}}}' - -say() { printf '\n== %s\n' "$*"; } -nogo() { printf '\nNO-GO: %s\nNothing was changed after this point. Receipts: %s\n' "$*" "$EVID" >&2; exit 2; } -show() { printf '+'; printf ' %q' "$@"; printf '\n'; } -# kube ARGS... : kubectl on railiance01 through the selected route (stdin passes through untouched) -kube() { - local remote; remote="$(printf '%q ' "${PREFIX[@]}" kubectl --request-timeout=30s "$@")" - if [ "$DRY" = 1 ]; then show ssh railiance01 "$remote"; return 0; fi - ssh -o BatchMode=yes -o ConnectTimeout=10 railiance01 "$remote" -} -step() { if [ "$DRY" = 1 ]; then show "$@"; else "$@"; fi; } -# check SUBCOMMAND ARGS... OUTFILE : run evaluator, keep receipt, return its verdict -check() { - local out="${*: -1}"; local args=("${@:1:$#-1}") - if [ "$DRY" = 1 ]; then show "${CHECK[@]}" "${args[@]}"; echo " > $out"; return 0; fi - "${CHECK[@]}" "${args[@]}" | tee "$out" -} -rollback_help() { - cat >&2 < "$EVID/issue-core-proof.json" || nogo "issue-core verify-live --smoke failed (see issue-core-proof.json)" -fi -step git -C "$TARGET_REVENUE" fetch -q origin -step python3 "$ROOT/scripts/workload_adoption_proof.py" --live --source-root "$TARGET_REVENUE" \ - --observation "$EVID/target-revenue-observation.json" --receipt "$EVID/target-revenue-proof.json" >/dev/null \ - || nogo "target-revenue adoption proof failed" -if [ "$DRY" = 1 ]; then - show python3 "$ROOT/scripts/eso_adoption_readiness.py" --draft "$DRAFT" --issue-core-proof "$EVID/issue-core-proof.json" --target-revenue-proof "$EVID/target-revenue-proof.json" -else - python3 "$ROOT/scripts/eso_adoption_readiness.py" --draft "$DRAFT" --issue-core-proof "$EVID/issue-core-proof.json" \ - --target-revenue-proof "$EVID/target-revenue-proof.json" > "$EVID/readiness.json" || true -fi -check gate --readiness "$EVID/readiness.json" "$EVID/gate.json" || nogo "readiness gate" - -say "2/6 render chart 0.16.1, split out credential-kind objects" -step python3 -c "import yaml,sys;print(yaml.safe_load(open(sys.argv[1]))['spec']['source']['helm']['values'])" "$DRAFT" > "$WORK/values.yaml" -step helm template external-secrets external-secrets --repo https://charts.external-secrets.io --version 0.16.1 \ - -n external-secrets -f "$WORK/values.yaml" > "$WORK/render.yaml" -check split --render "$WORK/render.yaml" --out "$WORK/comparable.yaml" "$EVID/render-split.json" || nogo "render split" -[ "$DRY" = 1 ] || sha256sum "$WORK/render.yaml" | cut -d' ' -f1 > "$EVID/render.sha256" - -say "3/6 client-side diff (T05 method) + structured classification" -if [ "$DRY" = 1 ]; then kube diff -f -; else - set +e; kube diff -f - < "$WORK/comparable.yaml" > "$EVID/client-diff.txt"; rc=$?; set -e - echo "kubectl diff rc=$rc" | tee "$EVID/client-diff.rc" - [ "$rc" -le 1 ] || nogo "kubectl diff error rc=$rc" -fi -if [ "$DRY" = 1 ]; then kube get -f - --ignore-not-found -o json; else - kube get -f - --ignore-not-found -o json < "$WORK/comparable.yaml" > "$WORK/live.json" || nogo "live read" -fi -check classify --render "$WORK/render.yaml" --live "$WORK/live.json" "$EVID/classification.json" \ - || nogo "diff classification (CRD spec change, missing CRD or spec change needing review)" - -say "4/6 ClusterSecretStore baseline (status conditions only)" -if [ "$DRY" = 1 ]; then kube get clustersecretstores.external-secrets.io -o json; else - kube get clustersecretstores.external-secrets.io -o json > "$WORK/stores.json" || nogo "store read" -fi -check stores --live "$WORK/stores.json" "$EVID/store-baseline-before.json" || nogo "unexpected not-ready ClusterSecretStore" - -say "PRE-CHECK: GO" -if [ "$PRE_ONLY" = 1 ]; then echo "--precheck-only: stopping before any change."; exit 0; fi - -say "5/6 create the Application (no automated sync, no finalizer) and confirm the sync" -if [ "$DRY" = 1 ]; then kube -n argocd get application external-secrets --ignore-not-found -o name; EXISTS=""; else - EXISTS="$(kube -n argocd get application external-secrets --ignore-not-found -o name)"; fi -if [ -z "$EXISTS" ]; then - if [ "$DRY" = 1 ]; then kube apply --dry-run=server -f -; kube apply -f -; else - kube apply --dry-run=server -f - < "$DRAFT" >/dev/null || nogo "Application server dry-run" - kube apply -f - < "$DRAFT" - fi -fi -if [ "$DRY" = 1 ]; then kube -n argocd get application external-secrets -o json; else - for _ in $(seq 1 30); do - kube -n argocd get application external-secrets -o json > "$WORK/app.json" - python3 -c "import json,sys;s=json.load(open(sys.argv[1])).get('status',{});sys.exit(0 if s.get('resources') else 1)" "$WORK/app.json" && break - sleep 5 - done -fi -if [ "$DRY" = 1 ]; then TOTAL=39; else - TOTAL="$(python3 -c "import json,sys;print(json.load(open(sys.argv[1]))['total'])" "$EVID/render-split.json")"; fi -check precheck-app --live "$WORK/app.json" --expected-resources "$TOTAL" "$EVID/argo-precheck.json" || nogo "Argo comparison" - -printf '\nAbout to sync external-secrets: revision 0.16.1, prune=false, force=false, ServerSideApply.\n' -if [ "$DRY" = 1 ]; then echo "(dry-run: would ask for ADOPT)"; else - read -r -p "Type ADOPT to sync, anything else aborts: " answer - [ "$answer" = "ADOPT" ] || nogo "founder did not confirm. The Application exists but nothing was synced." -fi -kube -n argocd patch application external-secrets --type merge -p "$SYNC_JSON" -o name -[ "$DRY" = 1 ] || date -u +%FT%TZ > "$EVID/sync-requested-at" - -say "6/6 wait for the operation, then post-checks" -if [ "$DRY" = 1 ]; then kube -n argocd get application external-secrets -o json; else - for _ in $(seq 1 120); do - kube -n argocd get application external-secrets -o json > "$WORK/app.json" - python3 - "$WORK/app.json" <<'PY' && break -import json, sys -a = json.load(open(sys.argv[1])); s = a.get('status', {}); phase = s.get('operationState', {}).get('phase') -done = phase in ('Failed', 'Error') or (phase == 'Succeeded' and s.get('health', {}).get('status') == 'Healthy') -sys.exit(0 if done and not a.get('operation') else 1) -PY - sleep 5 - done -fi -if [ "$DRY" = 1 ]; then kube get crd -o name; kube get clustersecretstores.external-secrets.io -o json; else - kube get crd -o name > "$WORK/crds.txt" - kube get clustersecretstores.external-secrets.io -o json > "$WORK/stores-after.json" -fi -check stores --live "$WORK/stores-after.json" "$EVID/store-baseline-after.json" || true -check postcheck --app "$WORK/app.json" --crds "$WORK/crds.txt" --before "$EVID/store-baseline-before.json" \ - --after "$EVID/store-baseline-after.json" "$EVID/postcheck.json" || { rollback_help; exit 4; } -step kube -n external-secrets get deploy - -say "ExternalSecret refresh sample (issue-core/issue-core-runtime)" -if [ "$DRY" = 1 ]; then kube -n issue-core get externalsecret issue-core-runtime -o json; else - kube -n issue-core get externalsecret issue-core-runtime -o json > "$WORK/es-before.json" -fi -kube -n issue-core annotate externalsecret issue-core-runtime "force-sync=$STAMP" --overwrite -o name -if [ "$DRY" = 1 ]; then kube -n issue-core get externalsecret issue-core-runtime -o json; else - for _ in $(seq 1 12); do - sleep 5 - kube -n issue-core get externalsecret issue-core-runtime -o json > "$WORK/es-after.json" - "${CHECK[@]}" refresh --before "$WORK/es-before.json" --after "$WORK/es-after.json" >/dev/null && break - done -fi -check refresh --before "$WORK/es-before.json" --after "$WORK/es-after.json" "$EVID/refresh-sample.json" \ - || { rollback_help; exit 4; } - -say "ADOPTED: external-secrets is Synced/Healthy at 0.16.1. Receipts: $EVID" -echo "Next (agent): move the draft into argocd/railiance01/applications/, commit receipts, and update T06." diff --git a/scripts/service_identity_metadata_export.py b/scripts/service_identity_metadata_export.py deleted file mode 100644 index 5f8bc40..0000000 --- a/scripts/service_identity_metadata_export.py +++ /dev/null @@ -1,292 +0,0 @@ -#!/usr/bin/env python3 -"""Value-safe service-identity metadata export for OpenBao and PostgreSQL. - -NK-WP-0054-T04 compares NetKingdom's service-identity declarations with -observed state. This exporter produces the observed side as non-secret JSON. - -OpenBao (read-only): ``bao auth list``, ``bao list auth//role``, -``bao read auth//role/`` and ``bao policy read ``. It never -reads a role-id, secret-id, KV data, token or Secret. Only allowlisted role -fields are emitted; every other field returned by OpenBao is dropped. - -PostgreSQL (read-only): ``postgresql-sql`` prints one SELECT over ``pg_roles``, -``pg_auth_members`` and ``pg_database`` (never ``pg_authid`` or -``rolpassword``); ``postgresql`` turns its single JSON output row into the -export. The operator runs the SELECT with their own access; this script holds -no database connection. - -Every output string is screened for secret shapes before printing. On a hit -the export is refused and only a count is reported, never the value. -""" - -from __future__ import annotations - -import argparse -import hashlib -import json -import re -import subprocess -import sys -from datetime import datetime, timezone -from pathlib import Path -from typing import Any, Callable - -SCHEMA = "railiance-platform.service-identity-metadata.v1" -ROLE_MOUNT_TYPES = {"jwt", "oidc", "approle", "kubernetes"} -ROLE_FIELDS = ( - "role_type", "bound_audiences", "bound_subject", "bound_claims", "bound_claims_type", - "user_claim", "groups_claim", "claim_mappings", "oidc_scopes", "allowed_redirect_uris", - "bound_service_account_names", "bound_service_account_namespaces", "audience", - "bind_secret_id", "secret_id_num_uses", "secret_id_bound_cidrs", "token_bound_cidrs", - "token_policies", "policies", "token_no_default_policy", "token_type", "token_num_uses", -) -DURATION_FIELDS = ("token_ttl", "token_max_ttl", "token_explicit_max_ttl", "secret_id_ttl", "ttl", "max_ttl") -SECRET_SHAPES = ( - re.compile(r"^(hvs|hvb|hvr|s|b)\.[A-Za-z0-9_-]{20,}"), - re.compile(r"^eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}"), - re.compile(r"-----BEGIN [A-Z ]*(PRIVATE KEY|CERTIFICATE)"), - re.compile(r"^(SCRAM-SHA-256\$|md5[0-9a-f]{32}$)"), - re.compile(r"^[A-Fa-f0-9]{40,}$"), -) -OPAQUE = re.compile(r"^[A-Za-z0-9+/_=-]{32,}$") -DIGEST_KEYS = {"sha256"} # our own policy-text digests, not credentials -POLICY_RULE = re.compile(r'path\s+"([^"]+)"\s*\{\s*capabilities\s*=\s*\[([^\]]*)\]', re.S) -DURATION = re.compile(r"(\d+)([smhd])") -UNIT = {"s": 1, "m": 60, "h": 3600, "d": 86400} - -PG_SQL = ( - "SELECT json_build_object(" - "'server_version', current_setting('server_version')," - "'roles', COALESCE((SELECT json_agg(json_build_object(" - "'name', r.rolname, 'superuser', r.rolsuper, 'create_role', r.rolcreaterole," - "'create_db', r.rolcreatedb, 'can_login', r.rolcanlogin, 'replication', r.rolreplication," - "'bypass_rls', r.rolbypassrls, 'inherit', r.rolinherit, 'connection_limit', r.rolconnlimit," - "'valid_until', r.rolvaliduntil," - "'member_of', COALESCE((SELECT json_agg(json_build_object('role', g.rolname, 'admin_option', m.admin_option)" - " ORDER BY g.rolname) FROM pg_auth_members m JOIN pg_roles g ON g.oid = m.roleid" - " WHERE m.member = r.oid), '[]'::json)) ORDER BY r.rolname)" - " FROM pg_roles r WHERE r.rolname !~ '^pg_'), '[]'::json)," - "'databases', COALESCE((SELECT json_agg(json_build_object('name', d.datname," - " 'owner', pg_get_userbyid(d.datdba)) ORDER BY d.datname)" - " FROM pg_database d WHERE NOT d.datistemplate), '[]'::json))" -) -PG_ROLE_FIELDS = ("name", "superuser", "create_role", "create_db", "can_login", "replication", - "bypass_rls", "inherit", "connection_limit", "valid_until", "member_of") - - -class Refused(Exception): - """Raised when output would carry a secret-shaped value; never holds the value.""" - - -def seconds(value: Any) -> int | None: - if value is None or value == "": - return None - if isinstance(value, bool): - raise ValueError("duration") - if isinstance(value, (int, float)): - return int(value) - text = str(value).strip() - if text.isdigit(): - return int(text) - parts = DURATION.findall(text) - if not parts or "".join(n + u for n, u in parts) != text: - raise ValueError("duration") - return sum(int(n) * UNIT[u] for n, u in parts) - - -def policy_rules(text: str) -> list[dict[str, Any]]: - rules = [{"path": p, "capabilities": sorted(c.strip().strip('"') for c in caps.split(",") if c.strip())} - for p, caps in POLICY_RULE.findall(text)] - return sorted(rules, key=lambda r: r["path"]) - - -def effective_rules(policies: list[dict[str, Any]]) -> list[dict[str, Any]]: - """Merge exact-path rules across a role's policies; deny wins (OpenBao semantics). - - Glob and segment-wildcard paths are kept as written, not expanded.""" - merged: dict[str, set[str]] = {} - for policy in policies: - for rule in policy["rules"]: - merged.setdefault(rule["path"], set()).update(rule["capabilities"]) - out = [] - for path, caps in sorted(merged.items()): - out.append({"path": path, "capabilities": ["deny"] if "deny" in caps else sorted(caps), - "wildcard": "*" in path or "+" in path}) - return out - - -def role_view(mount: str, mount_type: str, name: str, data: dict[str, Any]) -> dict[str, Any]: - view: dict[str, Any] = {"mount": mount, "mount_type": mount_type, "name": name} - for key in ROLE_FIELDS: - if key in data and data[key] not in (None, "", [], {}): - view[key] = data[key] - for key in DURATION_FIELDS: - if key in data: - value = seconds(data[key]) - if value is not None: - view[key + "_seconds"] = value - if "policies" in view and "token_policies" not in view: - view["token_policies"] = view["policies"] - view.pop("policies", None) - view.setdefault("role_type", mount_type) - return view - - -class BaoCLI: - """Read-only calls through the operator's own ``bao`` login; no token is handled here.""" - - def __init__(self, bao: str = "bao") -> None: - self.bao = bao - - def _json(self, *args: str) -> Any: - proc = subprocess.run([self.bao, *args, "-format=json"], capture_output=True, text=True, check=False) - if proc.returncode != 0: - raise RuntimeError(f"bao {args[0]} {args[1] if len(args) > 1 else ''} failed (exit {proc.returncode})") - return json.loads(proc.stdout or "null") - - def auth_mounts(self) -> dict[str, Any]: - return self._json("auth", "list") - - def roles(self, mount: str) -> list[str]: - proc = subprocess.run([self.bao, "list", "-format=json", f"auth/{mount}/role"], - capture_output=True, text=True, check=False) - if proc.returncode != 0: - return [] # no roles, or list not permitted: reported as an empty mount - return list(json.loads(proc.stdout or "[]") or []) - - def role(self, mount: str, name: str) -> dict[str, Any]: - reply = self._json("read", f"auth/{mount}/role/{name}") - return (reply or {}).get("data", {}) or {} - - def policy(self, name: str) -> str | None: - proc = subprocess.run([self.bao, "policy", "read", name], capture_output=True, text=True, check=False) - return proc.stdout if proc.returncode == 0 else None - - -class FixtureDir: - """Offline source: auth-list.json, roles//.json, policies/.hcl.""" - - def __init__(self, root: Path) -> None: - self.root = root - - def auth_mounts(self) -> dict[str, Any]: - return json.loads((self.root / "auth-list.json").read_text()) - - def roles(self, mount: str) -> list[str]: - folder = self.root / "roles" / mount - return sorted(p.stem for p in folder.glob("*.json")) if folder.is_dir() else [] - - def role(self, mount: str, name: str) -> dict[str, Any]: - return json.loads((self.root / "roles" / mount / f"{name}.json").read_text()) - - def policy(self, name: str) -> str | None: - path = self.root / "policies" / f"{name}.hcl" - return path.read_text() if path.is_file() else None - - -def export_openbao(source: Any, mounts_filter: set[str] | None = None) -> dict[str, Any]: - mounts = [] - roles = [] - for path, meta in sorted((source.auth_mounts() or {}).items()): - mount = path.rstrip("/") - mtype = (meta or {}).get("type", "unknown") - mounts.append({"path": mount, "type": mtype}) - if mtype not in ROLE_MOUNT_TYPES or (mounts_filter and mount not in mounts_filter): - continue - for name in source.roles(mount): - roles.append(role_view(mount, mtype, name, source.role(mount, name))) - policies: dict[str, dict[str, Any]] = {} - for name in sorted({p for r in roles for p in r.get("token_policies", [])}): - text = source.policy(name) - policies[name] = ({"name": name, "readable": False, "rules": []} if text is None else - {"name": name, "readable": True, "sha256": hashlib.sha256(text.encode()).hexdigest(), - "rules": policy_rules(text)}) - for role in roles: - attached = [policies[p] for p in role.get("token_policies", [])] - role["effective_rules"] = effective_rules(attached) - role["effective_complete"] = (all(p["readable"] for p in attached) - and role.get("token_no_default_policy") is True) - return {"auth_mounts": mounts, "roles": roles, "policies": list(policies.values())} - - -def export_postgresql(raw: str, cluster: str, namespace: str) -> dict[str, Any]: - lines = [line for line in raw.splitlines() if line.strip()] - if len(lines) != 1: - raise ValueError("expected exactly one JSON row from the postgresql-sql query") - data = json.loads(lines[0]) - roles = [{k: r.get(k) for k in PG_ROLE_FIELDS} for r in data.get("roles", [])] - databases = [{"name": d["name"], "owner": d["owner"]} for d in data.get("databases", [])] - return {"cluster": cluster, "namespace": namespace, "server_version": data.get("server_version"), - "roles": roles, "databases": databases} - - -def looks_secret(text: str) -> bool: - if any(p.search(text) for p in SECRET_SHAPES): - return True - # opaque high-entropy strings: mixed case plus digits, no spaces (names and paths are lower case) - return bool(OPAQUE.match(text) and re.search(r"[A-Z]", text) and re.search(r"[a-z]", text) - and re.search(r"[0-9]", text)) - - -def secret_hits(value: Any) -> int: - if isinstance(value, dict): - return sum(0 if k in DIGEST_KEYS else secret_hits(v) for k, v in value.items()) - if isinstance(value, list): - return sum(secret_hits(v) for v in value) - if isinstance(value, str): - return int(looks_secret(value)) - return 0 - - -def envelope(service: str, kind: str, body: dict[str, Any], now: Callable[[], datetime]) -> dict[str, Any]: - doc = {"schema": SCHEMA, "service": service, - "collected_at": now().astimezone(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), - "source_kind": kind, "value_safe": True, service: body} - hits = secret_hits(body) - if hits: - raise Refused(f"refused: {hits} secret-shaped value(s) in export; nothing printed") - return doc - - -def main(argv: list[str] | None = None, now: Callable[[], datetime] = lambda: datetime.now(timezone.utc)) -> int: - parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - sub = parser.add_subparsers(dest="cmd", required=True) - bao = sub.add_parser("openbao", help="export auth roles and their policies") - bao.add_argument("--fixture-dir", type=Path, help="offline source instead of the bao CLI") - bao.add_argument("--bao", default="bao", help="bao CLI binary (uses the operator's own login)") - bao.add_argument("--mount", action="append", help="limit to these auth mounts (repeatable)") - sub.add_parser("postgresql-sql", help="print the read-only SELECT to run with psql -AtX") - pg = sub.add_parser("postgresql", help="export from the SELECT's single output row") - pg.add_argument("--input", type=Path, help="file with the psql output (default stdin)") - pg.add_argument("--cluster", required=True) - pg.add_argument("--namespace", default="databases") - parser.add_argument("--output", type=Path, help="write here instead of stdout") - args = parser.parse_args(argv) - try: - if args.cmd == "postgresql-sql": - print(PG_SQL) - return 0 - if args.cmd == "openbao": - source = FixtureDir(args.fixture_dir) if args.fixture_dir else BaoCLI(args.bao) - doc = envelope("openbao", "fixture" if args.fixture_dir else "live", - export_openbao(source, set(args.mount) if args.mount else None), now) - else: - raw = args.input.read_text() if args.input else sys.stdin.read() - doc = envelope("postgresql", "live" if not args.input else "file", - export_postgresql(raw, args.cluster, args.namespace), now) - except Refused as exc: - print(str(exc), file=sys.stderr) - return 3 - except (RuntimeError, ValueError, KeyError, json.JSONDecodeError) as exc: - print(f"export failed: {type(exc).__name__}", file=sys.stderr) - return 2 - text = json.dumps(doc, indent=2, sort_keys=True) + "\n" - if args.output: - args.output.write_text(text) - else: - sys.stdout.write(text) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/service_jwt_lane.py b/scripts/service_jwt_lane.py deleted file mode 100644 index aba7142..0000000 --- a/scripts/service_jwt_lane.py +++ /dev/null @@ -1,327 +0,0 @@ -"""service-jwt-login CCRs: an isolated JWT auth mount, one role, one login-only policy. - -RPF-WP-0035-T02. Loaded by scripts/credential-change.py for request_type -service-jwt-login. Deliberately narrow: the lane is an identity login, never a -lane applier, so the policy may hold only the three self endpoints plus explicit -denies on guarded paths (ops-mason MASON-WP-0009 invariant (ii)). Everything -written to OpenBao comes from reviewed repo files; nothing here reads, prints or -accepts a secret value. -""" -from __future__ import annotations - -import json -import re -import subprocess -import tempfile -from pathlib import Path -from typing import Any - -REQUEST_TYPE = "service-jwt-login" -ALLOWED_AUTH_MOUNTS = {"keycape-services"} -SELF_GRANTS = ( - ("auth/token/lookup-self", "read"), - ("sys/capabilities-self", "update"), - ("auth/token/revoke-self", "update"), -) -POLICY_NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]*-login-self$") -ROLE_RE = re.compile(r"^[a-z0-9][a-z0-9-]*$") -TTL_RE = re.compile(r"^([1-9][0-9]*)([sm])$") -MAX_TTL_SECONDS = 15 * 60 -MAX_TOKEN_USES = 16 -CONFIG_KEYS = {"jwks_url", "bound_issuer", "jwt_supported_algs"} -ROLE_KEYS = { - "role_type", "user_claim", "bound_audiences", "bound_subject", - "bound_claims_type", "bound_claims", "token_policies", - "token_no_default_policy", "token_type", "token_ttl", "token_max_ttl", - "token_explicit_max_ttl", "token_num_uses", "verbose_oidc_logging", -} -DURATION_KEYS = ("token_ttl", "token_max_ttl", "token_explicit_max_ttl") -DISALLOWED_POLICIES = {"root", "platform-admin", "default"} - - -def ttl_seconds(value: Any) -> int | None: - if isinstance(value, int) and not isinstance(value, bool): - return value - match = TTL_RE.match(str(value)) - if not match: - return None - return int(match.group(1)) * (60 if match.group(2) == "m" else 1) - - -def generated_policy_hcl(ccr: dict[str, Any]) -> str: - openbao = ccr["openbao"] - lines = [f"# {ccr['id']} (RPF-WP-0035-T02): login-only service policy; not a lane applier."] - for path, capability in SELF_GRANTS: - lines += [f'path "{path}" {{', f' capabilities = ["{capability}"]', "}"] - for path in openbao.get("guarded_paths") or []: - lines += [f'path "{path}" {{', ' capabilities = ["deny"]', "}"] - return "\n".join(lines) + "\n" - - -def normalized(text: str) -> str: - return "\n".join( - line.rstrip() for line in text.strip().splitlines() if not line.lstrip().startswith("#") - ) - - -def _load_json(repo_dir: Path, rel: str, field: str, errors: list[str]) -> dict[str, Any]: - path = (repo_dir / rel).resolve() - try: - path.relative_to((repo_dir / "openbao" / "auth").resolve()) - except ValueError: - errors.append(f"{field} must stay under openbao/auth") - return {} - if not path.exists(): - errors.append(f"{field} does not exist: {rel}") - return {} - data = json.loads(path.read_text(encoding="utf-8")) - if not isinstance(data, dict): - errors.append(f"{field} must be a JSON object") - return {} - return data - - -def role_payload(ccr: dict[str, Any], repo_dir: Path) -> dict[str, Any]: - return _load_json(repo_dir, ccr["openbao"]["auth"]["role_file"], "openbao.auth.role_file", []) - - -def config_payload(ccr: dict[str, Any], repo_dir: Path) -> dict[str, Any]: - return _load_json(repo_dir, ccr["openbao"]["auth"]["config_file"], "openbao.auth.config_file", []) - - -def validate(ccr: dict[str, Any], repo_dir: Path) -> list[str]: - """Schema plus every boundary the delegated applier relies on.""" - errors: list[str] = [] - openbao = ccr.get("openbao") - if not isinstance(openbao, dict): - return ["openbao must be an object"] - auth = openbao.get("auth") - if not isinstance(auth, dict): - return ["openbao.auth must be an object"] - target = ccr.get("target") or {} - if target.get("environment") != "production": - errors.append("target.environment must be production") - review = ccr.get("review") or {} - if review.get("required") is not True or not review.get("required_approvers"): - errors.append("review.required must be true with required_approvers") - - policy_name = str(openbao.get("policy_name") or "") - if not POLICY_NAME_RE.match(policy_name) or policy_name in DISALLOWED_POLICIES: - errors.append("openbao.policy_name must be a *-login-self policy") - policy_file = str(openbao.get("policy_file") or "") - if policy_file != f"openbao/policies/{policy_name}.hcl": - errors.append("openbao.policy_file must be openbao/policies/.hcl") - guarded = openbao.get("guarded_paths") - if not isinstance(guarded, list) or not guarded: - errors.append("openbao.guarded_paths must list the paths this identity must never reach") - guarded = [] - for path in guarded: - if not isinstance(path, str) or any(f in path for f in ("*", "+", "..", "//")) or not path.strip(): - errors.append(f"openbao.guarded_paths entry must be an exact path: {path!r}") - policy_path = repo_dir / policy_file - if policy_file and policy_path.exists(): - if normalized(policy_path.read_text(encoding="utf-8")) != normalized(generated_policy_hcl(ccr)): - errors.append("openbao.policy_file does not match the generated login-self policy") - elif policy_file: - errors.append(f"openbao.policy_file does not exist: {policy_file}") - - if auth.get("method") != "jwt": - errors.append("openbao.auth.method must be jwt") - mount = str(auth.get("mount") or "") - if mount not in ALLOWED_AUTH_MOUNTS: - errors.append(f"openbao.auth.mount must be one of {sorted(ALLOWED_AUTH_MOUNTS)}") - role_name = str(auth.get("role") or "") - if not ROLE_RE.match(role_name): - errors.append("openbao.auth.role contains unsafe characters") - if not isinstance(auth.get("bound_claims_confirmed"), bool): - errors.append("openbao.auth.bound_claims_confirmed must be a boolean") - - config = _load_json(repo_dir, str(auth.get("config_file") or ""), "openbao.auth.config_file", errors) - if config: - if set(config) != CONFIG_KEYS: - errors.append(f"auth config keys must be exactly {sorted(CONFIG_KEYS)}") - issuer = str(config.get("bound_issuer") or "") - jwks = str(config.get("jwks_url") or "") - if not issuer.startswith("https://") or not jwks.startswith(issuer.rstrip("/") + "/"): - errors.append("auth config needs an https bound_issuer and a jwks_url under it") - if config.get("jwt_supported_algs") != ["RS256"]: - errors.append("auth config must pin jwt_supported_algs to [RS256]") - - role = _load_json(repo_dir, str(auth.get("role_file") or ""), "openbao.auth.role_file", errors) - if role: - extra = set(role) - ROLE_KEYS - if extra: - errors.append(f"role has keys outside the reviewed set: {sorted(extra)}") - if role.get("role_type") != "jwt" or role.get("user_claim") != "sub": - errors.append("role must be role_type jwt with user_claim sub") - if role.get("token_policies") != [policy_name]: - errors.append("role token_policies must be exactly [openbao.policy_name]") - if role.get("token_no_default_policy") is not True: - errors.append("role must set token_no_default_policy true") - if role.get("token_type") != "service": - errors.append("role token_type must be service") - if role.get("verbose_oidc_logging") is not False: - errors.append("role must disable verbose_oidc_logging") - audiences = role.get("bound_audiences") - if not isinstance(audiences, list) or len(audiences) != 1 or audiences[0] != role_name: - errors.append("role bound_audiences must be exactly [] (KeyCape aud = clientId)") - if not str(role.get("bound_subject") or "").startswith("service:"): - errors.append("role bound_subject must be an exact service: subject") - claims = role.get("bound_claims") - if role.get("bound_claims_type") != "string" or not isinstance(claims, dict): - errors.append("role bound_claims must be string-typed") - claims = {} - for key in ("principal_type", "tenant", "roles", "scope"): - value = claims.get(key) - if not isinstance(value, str) or not value or "*" in value or "," in value: - errors.append(f"role bound_claims.{key} must be one exact string") - if claims.get("principal_type") != "service": - errors.append("role bound_claims.principal_type must be service") - seconds = [ttl_seconds(role.get(key)) for key in DURATION_KEYS] - if any(s is None or s <= 0 or s > MAX_TTL_SECONDS for s in seconds): - errors.append("role token TTLs must be set and at most 15m") - uses = role.get("token_num_uses") - if not isinstance(uses, int) or isinstance(uses, bool) or not 0 < uses <= MAX_TOKEN_USES: - errors.append(f"role token_num_uses must be 1..{MAX_TOKEN_USES}") - return errors - - -def readiness_blockers(ccr: dict[str, Any], repo_dir: Path, allowed_statuses: set[str]) -> list[str]: - blockers: list[str] = [] - if ccr.get("status") not in allowed_statuses: - blockers.append( - "applier requires approved, applied, verified, or active CCR status, " - f"got {ccr.get('status')}" - ) - if ccr["openbao"]["auth"].get("bound_claims_confirmed") is not True: - blockers.append("applier requires confirmed OpenBao auth binding") - blockers.extend(validate(ccr, repo_dir)) - return blockers - - -def dry_run_payload(ccr: dict[str, Any], repo_dir: Path, warnings: list[str]) -> dict[str, Any]: - openbao = ccr["openbao"] - auth = openbao["auth"] - return { - "id": ccr["id"], - "title": ccr["title"], - "status": ccr["status"], - "environment": ccr["target"]["environment"], - "warnings": warnings, - "source_artifacts": {}, - "mutations": [ - {"kind": "policy_write", "openbao_path": f"sys/policies/acl/{openbao['policy_name']}", - "policy_name": openbao["policy_name"], "source": openbao["policy_file"], - "body": generated_policy_hcl(ccr).rstrip()}, - {"kind": "auth_enable_if_absent", "openbao_path": f"sys/auth/{auth['mount']}", - "payload": {"type": "jwt", "refuse_if_existing_type_differs": True}}, - {"kind": "auth_config_write", "openbao_path": f"auth/{auth['mount']}/config", - "payload": config_payload(ccr, repo_dir)}, - {"kind": "auth_role_write", "openbao_path": f"auth/{auth['mount']}/role/{auth['role']}", - "payload": role_payload(ccr, repo_dir)}, - {"kind": "readback_compare", "openbao_path": "policy, auth mount type, config, role", - "payload": {"prints": "booleans only"}}, - ], - "out_of_scope": [ - "secret value reads or writes", - "token creation or login", - "changes to any other auth mount, role or policy", - "front-door activation before the login proof", - ], - "required_evidence": [ - "CCR id and approval reference", - "readback equality for policy, mount type, config and role", - "login proof from a real KeyCape service JWT (separate, value-free)", - ], - } - - -def apply_details(ccr: dict[str, Any], actor: str) -> list[str]: - openbao = ccr["openbao"] - auth = openbao["auth"] - return [ - f"Delegated metadata applier ran as {actor} using local bao CLI ambient authority.", - f"Policy metadata write: sys/policies/acl/{openbao['policy_name']}", - f"JWT auth mount ensured: sys/auth/{auth['mount']} (type jwt)", - f"Auth config write: auth/{auth['mount']}/config", - f"Auth role metadata write: auth/{auth['mount']}/role/{auth['role']}", - "Readback matched source for policy, mount type, config and role.", - "No secret values were read, written, printed, or accepted in argv.", - ] - - -class BaoError(RuntimeError): - pass - - -def _run(bao_bin: str, args: list[str], repo_dir: Path, quiet: bool, capture: bool = False) -> str: - command = [bao_bin, *args] - if capture or quiet: - result = subprocess.run(command, cwd=repo_dir, capture_output=True, text=True) - if result.returncode != 0: - raise BaoError(f"bao {' '.join(args[:2])} failed with exit code {result.returncode}") - return result.stdout - subprocess.run(command, cwd=repo_dir, check=True) - return "" - - -def _read_json(bao_bin: str, args: list[str], repo_dir: Path) -> dict[str, Any]: - # "auth list -format=json" / "read -format=json ": flags follow the verb. - flagged = [*args, "-format=json"] if args[:2] == ["auth", "list"] else [args[0], "-format=json", *args[1:]] - out = _run(bao_bin, flagged, repo_dir, quiet=True, capture=True) - try: - return json.loads(out) - except json.JSONDecodeError as exc: - raise BaoError(f"bao {args[0]} returned non-JSON output") from exc - - -def readback_mismatches(ccr: dict[str, Any], repo_dir: Path, bao_bin: str) -> list[str]: - openbao = ccr["openbao"] - auth = openbao["auth"] - mismatches: list[str] = [] - policy = _run(bao_bin, ["policy", "read", openbao["policy_name"]], repo_dir, quiet=True, capture=True) - if normalized(policy) != normalized(generated_policy_hcl(ccr)): - mismatches.append("policy text differs from source") - mounts = _read_json(bao_bin, ["auth", "list"], repo_dir) - mounts = mounts.get("data", mounts) - if (mounts.get(f"{auth['mount']}/") or {}).get("type") != "jwt": - mismatches.append("auth mount is not type jwt") - config = _read_json(bao_bin, ["read", f"auth/{auth['mount']}/config"], repo_dir).get("data", {}) - for key, want in config_payload(ccr, repo_dir).items(): - if config.get(key) != want: - mismatches.append(f"config {key} differs") - role = _read_json(bao_bin, ["read", f"auth/{auth['mount']}/role/{auth['role']}"], repo_dir).get("data", {}) - for key, want in role_payload(ccr, repo_dir).items(): - got = role.get(key) - if key in DURATION_KEYS: - same = ttl_seconds(got) == ttl_seconds(want) - else: - same = got == want - if not same: - mismatches.append(f"role {key} differs") - return mismatches - - -def run_apply(ccr: dict[str, Any], repo_dir: Path, bao_bin: str, quiet: bool = False) -> None: - openbao = ccr["openbao"] - auth = openbao["auth"] - mount = auth["mount"] - mounts = _read_json(bao_bin, ["auth", "list"], repo_dir) - mounts = mounts.get("data", mounts) - existing = mounts.get(f"{mount}/") - if existing is not None and existing.get("type") != "jwt": - raise BaoError(f"auth mount {mount}/ exists with a different type; review, do not overwrite") - _run(bao_bin, ["policy", "write", openbao["policy_name"], openbao["policy_file"]], repo_dir, quiet) - if existing is None: - _run(bao_bin, ["auth", "enable", f"-path={mount}", - f"-description={ccr['id']} KeyCape service JWT login", "jwt"], repo_dir, quiet) - with tempfile.TemporaryDirectory() as tmp: - config_path = Path(tmp) / "config.json" - role_path = Path(tmp) / "role.json" - config_path.write_text(json.dumps(config_payload(ccr, repo_dir), sort_keys=True) + "\n") - role_path.write_text(json.dumps(role_payload(ccr, repo_dir), sort_keys=True) + "\n") - _run(bao_bin, ["write", f"auth/{mount}/config", f"@{config_path}"], repo_dir, quiet) - _run(bao_bin, ["write", f"auth/{mount}/role/{auth['role']}", f"@{role_path}"], repo_dir, quiet) - mismatches = readback_mismatches(ccr, repo_dir, bao_bin) - if mismatches: - raise BaoError("readback mismatch: " + "; ".join(mismatches)) diff --git a/scripts/survey-backup-iam.py b/scripts/survey-backup-iam.py deleted file mode 100644 index bde0c0f..0000000 --- a/scripts/survey-backup-iam.py +++ /dev/null @@ -1,153 +0,0 @@ -#!/usr/bin/env python3 -"""Survey Scaleway IAM names and the backup bucket policy. - -Reads the bootstrap and scoped-backup pairs in memory. Writes names, ids and -the bucket policy only. Never prints a credential. -""" - -from __future__ import annotations - -import datetime as dt -import hashlib -import hmac -import json -import subprocess -import urllib.error -import urllib.parse -import urllib.request -from pathlib import Path - -RECEIPT = Path("/home/worsch/.local/state/railiance/receipts/backup-iam-survey.json") -STAGE = Path("/home/worsch/.local/state/railiance/receipts/backup-iam-survey.stage") -BUCKET = "railiance-platform-pg-backup" -HOST = "s3.nl-ams.scw.cloud" - - -def stage(text): - STAGE.parent.mkdir(mode=0o700, exist_ok=True) - STAGE.write_text(text + "\n") - STAGE.chmod(0o600) - - -def kv(path): - proc = subprocess.run( - ["bao", "kv", "get", "-format=json", path], - capture_output=True, - ) - if proc.returncode: - stage(f"kv_read_failed {path} {proc.returncode}") - raise SystemExit(41) - stage(f"kv_read_ok {path}") - return json.loads(proc.stdout)["data"]["data"] - - -def scaleway(secret, url): - request = urllib.request.Request(url, headers={"X-Auth-Token": secret}) - try: - with urllib.request.urlopen(request, timeout=30) as response: - return response.status, json.loads(response.read().decode()) - except urllib.error.HTTPError as exc: - return exc.code, None - - -def sign(access_key, secret_key, canonical_uri, query): - now = dt.datetime.now(dt.timezone.utc) - amz_date = now.strftime("%Y%m%dT%H%M%SZ") - day = now.strftime("%Y%m%d") - payload = hashlib.sha256(b"").hexdigest() - canonical_query = urllib.parse.urlencode(sorted(query.items())) - canonical = "\n".join( - [ - "GET", - canonical_uri, - canonical_query, - f"host:{HOST}", - f"x-amz-content-sha256:{payload}", - f"x-amz-date:{amz_date}", - "", - "host;x-amz-content-sha256;x-amz-date", - payload, - ] - ) - scope = f"{day}/nl-ams/s3/aws4_request" - to_sign = "\n".join( - ["AWS4-HMAC-SHA256", amz_date, scope, hashlib.sha256(canonical.encode()).hexdigest()] - ) - key = ("AWS4" + secret_key).encode() - for part in (day, "nl-ams", "s3", "aws4_request"): - key = hmac.new(key, part.encode(), hashlib.sha256).digest() - signature = hmac.new(key, to_sign.encode(), hashlib.sha256).hexdigest() - return { - "Authorization": ( - "AWS4-HMAC-SHA256 " - f"Credential={access_key}/{scope}, " - "SignedHeaders=host;x-amz-content-sha256;x-amz-date, " - f"Signature={signature}" - ), - "x-amz-date": amz_date, - "x-amz-content-sha256": payload, - } - - -def bucket_policy(access_key, secret_key): - query = {"policy": ""} - headers = sign(access_key, secret_key, f"/{BUCKET}", query) - url = f"https://{HOST}/{BUCKET}?policy" - request = urllib.request.Request(url, headers=headers) - try: - with urllib.request.urlopen(request, timeout=30) as response: - return response.status, json.loads(response.read().decode()) - except urllib.error.HTTPError as exc: - return exc.code, None - - -def main(): - boot = kv("platform/workloads/railiance/scaleway/bootstrap") - backup = kv("platform/workloads/railiance/backup/object-storage") - org = boot["DEFAULT_ORGANIZATION_ID"] - project = boot["DEFAULT_PROJECT_ID"] - app_status, applications = scaleway( - boot["SECRET_KEY"], - "https://api.scaleway.com/iam/v1alpha1/applications" - f"?organization_id={urllib.parse.quote(org)}&page_size=100", - ) - policy_status, policies = scaleway( - boot["SECRET_KEY"], - "https://api.scaleway.com/iam/v1alpha1/policies" - f"?organization_id={urllib.parse.quote(org)}&page_size=100", - ) - s3_status, document = bucket_policy(backup["ACCESS_KEY"], backup["SECRET_KEY"]) - receipt = { - "organization_id": org, - "project_id": project, - "application_list_status": app_status, - "applications": [ - {"id": item.get("id"), "name": item.get("name")} - for item in (applications or {}).get("applications", []) - ], - "policy_list_status": policy_status, - "policies": [ - { - "id": item.get("id"), - "name": item.get("name"), - "application_id": (item.get("application_id") or item.get("principal", {}).get("id")), - } - for item in (policies or {}).get("policies", []) - ], - "bucket_policy_status": s3_status, - "bucket_policy": document, - } - RECEIPT.parent.mkdir(mode=0o700, exist_ok=True) - RECEIPT.write_text(json.dumps(receipt, indent=2) + "\n") - RECEIPT.chmod(0o600) - - -if __name__ == "__main__": - try: - main() - stage("ok") - except SystemExit: - raise - except Exception as exc: - stage("failed " + type(exc).__name__) - raise SystemExit(42) from None diff --git a/scripts/survey-backup-pair-metadata.sh b/scripts/survey-backup-pair-metadata.sh deleted file mode 100755 index 82f7c86..0000000 --- a/scripts/survey-backup-pair-metadata.sh +++ /dev/null @@ -1,36 +0,0 @@ -#!/bin/sh -# Record KV metadata versions only. Silent for warden access. -set -eu -receipt_dir=/home/worsch/.local/state/railiance/receipts -umask 077 -mkdir -p "$receipt_dir" -tmp=$(mktemp -d) -trap 'rm -rf "$tmp"' EXIT -python3 - "$tmp" "$receipt_dir/backup-pair-metadata.json" << 'PY' -import json, subprocess, sys -tmp, dest = sys.argv[1:] -paths = [ - "platform/workloads/coulomb-social/backup-primary", - "platform/workloads/email-connect/backup-primary", - "platform/workloads/railiance/scaleway/bootstrap", - "platform/workloads/railiance/backup/object-storage", -] -rows = [] -for path in paths: - out = subprocess.run( - ["bao", "kv", "metadata", "get", "-format=json", path], - capture_output=True, - ) - row = {"path": path, "returncode": out.returncode} - if out.returncode == 0: - data = json.loads(out.stdout)["data"] - row["current_version"] = data.get("current_version") - versions = data.get("versions") or {} - row["version_count"] = len(versions) - else: - err = (out.stderr + out.stdout).decode("utf-8", "replace") - row["absent"] = "No value found" in err - rows.append(row) -open(dest, "w").write(json.dumps({"paths": rows}, indent=2) + "\n") -PY -exit 0 diff --git a/scripts/t02-attended-window.py b/scripts/t02-attended-window.py deleted file mode 100755 index 5952737..0000000 --- a/scripts/t02-attended-window.py +++ /dev/null @@ -1,278 +0,0 @@ -#!/usr/bin/env python3 -"""One contained attended window for RAILIANCE-WP-0024-T02. - -Run only as the single child of the contained attended login, so the whole -window costs one MFA login: - - python3 scripts/openbao-attended-exec.py -- scripts/t02-attended-window.py ... - -Order (each step a reviewed command; this file adds no OpenBao call of its own): - -1. lane ``project`` -- one temporary sender, bearer to a 0600 file, audit-core restart -2. port-forward -- ``ssh -L`` to ``kubectl -n audit-core port-forward`` on the remote -3. harness ``exercise`` with the registered load driver -4. evidence -- exercise JSON written 0600 outside Git -5. lane ``cleanup`` -- with that evidence (restart #2) - -Any failure after projection was attempted runs ``cleanup --abort-reason``. -The port-forward is always stopped. Children's stdout is captured and only -their value-safe JSON is re-emitted; nothing here reads the bearer. -""" - -from __future__ import annotations - -import argparse -import importlib.util -import json -import os -import re -import subprocess -import sys -import time -import urllib.error -import urllib.request -from datetime import UTC, datetime -from pathlib import Path -from typing import Any, Callable - - -ROOT = Path(__file__).resolve().parents[1] -LANE_SCRIPT = ROOT / "scripts" / "audit_core_synthetic_sender_lane.py" -HARNESS_SCRIPT = ROOT / "scripts" / "audit-core-database-lease-recovery.py" -HARNESS_CONFIRM = "RAILIANCE-WP-0024-T02:attended" -REMOTE_PF_TIMEOUT_SECONDS = 15 * 60 -CONTAINER_PORT = 8080 - - -class WindowError(RuntimeError): - pass - - -def _load_lane() -> Any: - spec = importlib.util.spec_from_file_location("audit_core_synthetic_sender_lane", LANE_SCRIPT) - if not spec or not spec.loader: - raise WindowError("cannot load synthetic sender lane") - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -LANE = _load_lane() - - -def stamp(value: datetime) -> str: - return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def run_json(command: list[str], *, label: str, env: dict[str, str] | None = None) -> dict[str, Any]: - """Run a value-safe reviewed child; capture both streams (warden fails on any output).""" - completed = subprocess.run( - command, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, check=False - ) - if completed.returncode != 0: - # Reviewed lane/harness errors are fixed value-safe strings; keep one bounded line. - reason = (completed.stderr.strip().splitlines() or [""])[-1][:200] - raise WindowError(f"{label} failed (exit {completed.returncode}): {reason}") - try: - payload = json.loads(completed.stdout) - except json.JSONDecodeError as exc: - raise WindowError(f"{label} returned invalid JSON") from exc - if not isinstance(payload, dict) or payload.get("secret_values_observed") is not False: - raise WindowError(f"{label} did not attest value safety") - return payload - - -def private_dir(path: Path) -> Path: - """An operator-owned 0700 directory outside every Git work tree.""" - path = path.resolve() - LANE.check_bearer_path(path / "probe", must_exist=False) - return path - - -def write_private_json(path: Path, document: dict[str, Any]) -> None: - fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - with os.fdopen(fd, "w", encoding="utf-8") as handle: - json.dump(document, handle, indent=2, sort_keys=True) - handle.write("\n") - - -def slug(value: str) -> str: - cleaned = re.sub(r"[^a-z0-9-]+", "-", value.lower()).strip("-") - if not cleaned: - raise WindowError("approval id cannot form a fixture id") - return cleaned[:48] - - -def driver_env(spec: dict[str, Any], approval: dict[str, Any], bearer: Path, port: int) -> dict[str, str]: - env = dict(os.environ) - env.update({ - "AUDIT_T02_BASE_URL": f"http://127.0.0.1:{port}", - "AUDIT_T02_TOKEN_FILE": str(bearer), - "AUDIT_T02_SOURCE": spec["source"], - "AUDIT_T02_TENANT": spec["tenant"], - "AUDIT_T02_FIXTURE_ID": f"t02-{slug(approval['approval_id'])}", - "AUDIT_T02_OCCURRED_AT": approval["window"]["start"], - }) - return env - - -class PortForward: - """``ssh -L`` to a remote, time-bounded ``kubectl port-forward`` on loopback. - - The lane and harness reach the cluster only through ``ssh kubectl``; - the port-forward uses the same path so the window needs no local kubeconfig. - """ - - def __init__(self, remote: str, port: int) -> None: - self.remote = remote - self.port = port - self.process: subprocess.Popen[bytes] | None = None - - def command(self) -> list[str]: - remote_cmd = ( - f"timeout {REMOTE_PF_TIMEOUT_SECONDS} kubectl -n audit-core port-forward " - f"--address 127.0.0.1 deploy/audit-core {self.port}:{CONTAINER_PORT}" - ) - return [ - "ssh", "-o", "BatchMode=yes", "-o", "ExitOnForwardFailure=yes", - "-L", f"127.0.0.1:{self.port}:127.0.0.1:{self.port}", - self.remote, remote_cmd, - ] - - def start(self, *, timeout: float = 30) -> None: - self.process = subprocess.Popen( - self.command(), stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL - ) - deadline = time.monotonic() + timeout - while time.monotonic() < deadline: - if self.process.poll() is not None: - raise WindowError("audit-core port-forward exited early") - # A local ssh listener accepts before the remote forward is ready, so - # require a real /healthz answer through the whole chain. - try: - with urllib.request.urlopen(f"http://127.0.0.1:{self.port}/healthz", timeout=2) as response: - if response.status == 200: - return - except (OSError, urllib.error.URLError): - pass - time.sleep(1) - raise WindowError("audit-core port-forward did not become reachable") - - def stop(self) -> None: - if self.process is None or self.process.poll() is not None: - return - self.process.terminate() - try: - self.process.wait(timeout=10) - except subprocess.TimeoutExpired: - self.process.kill() - self.process.wait(timeout=5) - - -def window(args: argparse.Namespace, *, runner: Callable[..., dict[str, Any]] = run_json, - forward_factory: Callable[[str, int], Any] = PortForward) -> dict[str, Any]: - if not args.bearer_file.is_absolute() or not args.evidence_dir.is_absolute(): - raise WindowError("--bearer-file and --evidence-dir must be absolute") - LANE.LEASE.contained_bao_env() # refuse before any step outside the contained login - approval, spec = LANE.load_receipt(args.approval, datetime.now(UTC), require_open_window=True) - if args.confirm != LANE.confirm_token(approval): - raise WindowError(f"window requires --confirm {LANE.confirm_token(approval)}") - evidence_dir = private_dir(args.evidence_dir) - base = f"{slug(approval['approval_id'])}" - exercise_path = evidence_dir / f"{base}-exercise.json" - summary_path = evidence_dir / f"{base}-window.json" - for path in (exercise_path, summary_path): - if path.exists(): - raise WindowError(f"evidence file already exists: {path.name}") - lane = [sys.executable, str(LANE_SCRIPT)] - common = ["--approval", str(args.approval), "--remote", args.remote, "--contained", - "--confirm", args.confirm] - summary: dict[str, Any] = { - "procedure": "t02-attended-window", - "approval_id": approval["approval_id"], - "identity": LANE.identity_view(spec), - "started_at": stamp(datetime.now(UTC)), - "steps": [], - "secret_values_observed": False, - } - projection_attempted = False - forward = forward_factory(args.remote, args.port) - try: - projection_attempted = True - summary["project"] = runner( - [*lane, "project", *common, "--bearer-file", str(args.bearer_file)], label="lane project") - summary["steps"].append("project") - forward.start() - summary["steps"].append("port-forward") - e2 = getattr(args, "mode", "t02") == "e2" - exercise = runner( - [sys.executable, str(HARNESS_SCRIPT), "synthetic-e2" if e2 else "exercise", - "--approval", str(args.approval), - "--remote", args.remote, "--contained", "--load-driver", str(args.load_driver), - "--confirm", LANE.LEASE.E2_CONFIRM if e2 else HARNESS_CONFIRM], - label="T03 synthetic E2" if e2 else "T02 exercise", - env=driver_env(spec, approval, args.bearer_file, args.port), - ) - summary["steps"].append("exercise") - write_private_json(exercise_path, exercise) - summary["exercise_evidence"] = exercise_path.name - summary["steps"].append("evidence") - forward.stop() - summary["cleanup"] = runner( - [*lane, "cleanup", *common, "--bearer-file", str(args.bearer_file), - "--exercise-evidence", str(exercise_path)], label="lane cleanup") - summary["steps"].append("cleanup") - summary["outcome"] = "completed" - except BaseException as exc: - forward.stop() - summary["outcome"] = "aborted" - summary["abort_reason"] = str(exc)[:200] if isinstance(exc, WindowError) else type(exc).__name__ - if projection_attempted and "cleanup" not in summary["steps"]: - try: - summary["cleanup"] = runner( - [*lane, "cleanup", *common, "--bearer-file", str(args.bearer_file), - "--abort-reason", f"t02 window aborted: {summary['abort_reason']}"], - label="lane abort cleanup") - summary["steps"].append("abort-cleanup") - except BaseException as cleanup_exc: # keep the first failure; flag cleanup loudly - summary["abort_cleanup_failed"] = str(cleanup_exc)[:200] - summary["finished_at"] = stamp(datetime.now(UTC)) - write_private_json(summary_path, summary) - raise - finally: - forward.stop() - summary["finished_at"] = stamp(datetime.now(UTC)) - write_private_json(summary_path, summary) - return summary - - -def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description=__doc__.splitlines()[0]) - parser.add_argument("--approval", type=Path, required=True) - parser.add_argument("--load-driver", type=Path, required=True) - parser.add_argument("--bearer-file", type=Path, required=True) - parser.add_argument("--evidence-dir", type=Path, required=True) - parser.add_argument("--confirm", required=True) - parser.add_argument("--remote", default="railiance01") - parser.add_argument("--port", type=int, default=18081) - parser.add_argument("--mode", choices=["t02", "e2"], default="t02", - help="e2: post-reboot synthetic event accepted exactly once; no lease revocation") - args = parser.parse_args(argv) - # The contained login discards child output and fails closed on any byte, - # so this script never prints; its outcome lives only in the evidence dir. - try: - window(args) - except (OSError, KeyError, WindowError, LANE.LaneError, LANE.LEASE.ProcedureError) as exc: - try: - LANE.emit_result( - {"status": "failed", "error": str(exc), "secret_values_observed": False}, - args.evidence_dir / f"window-error-{int(time.time())}.json", - ) - except OSError: - pass - return 1 - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/task_execution_cleanup.py b/scripts/task_execution_cleanup.py deleted file mode 100644 index c865c41..0000000 --- a/scripts/task_execution_cleanup.py +++ /dev/null @@ -1,72 +0,0 @@ -"""Fixed cleanup driver for a trusted owner mediator; no native transport exists here. - -Call from the mediator's finally block while its original Journal.locked context -is still live. The owner adapter retains credential/handle custody and reconstructs -and compares the native call with the expected digest before effect. Its three -methods take no caller-selected path, body, shell command or token. This source -component neither validates consume nor creates a native execution entitlement. -""" -from __future__ import annotations - -from dataclasses import dataclass -import hashlib -import re -from typing import Protocol - -from task_execution_journal import Binding, Cleanup, Journal, Refused - - -@dataclass(frozen=True) -class CleanupResult: - confirmed: bool - evidence_digest: str - - def validate(self): - if type(self.confirmed) is not bool or type(self.evidence_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}", self.evidence_digest): - raise Refused("invalid owner cleanup evidence") - - -class OwnerCleanup(Protocol): - # Confirm requires actual native revoke/absence evidence; a timeout is never - # proof of absence. Implementations must use bounded calls and discard raw - # backend failure output. Protected handles never leave the owner adapter. - def revoke_task_token(self, expected_call_digest: str) -> CleanupResult: ... - def revoke_identity_token(self, expected_call_digest: str) -> CleanupResult: ... - def delete_grant_policy(self, expected_call_digest: str) -> CleanupResult: ... - - -def run_cleanup(journal: Journal, binding: Binding, owner: OwnerCleanup): - """Attempt each fixed obligation once, preserving business/unknown outcomes. - - Ordinary adapter failures record unconfirmed evidence and do not suppress the - remaining independent cleanup. BaseException/process death leaves durable - pending intent; neither this driver nor a reopened journal retries it. - Journal failures propagate and prohibit dispatch without durable intent. - """ - obligations = journal.cleanup_status(binding)['obligations'] - if not obligations: - raise Refused("explicit owner cleanup obligations required") - if any(item['outcome'] != 'owed' for item in obligations): - raise Refused("cleanup already attempted; owner reconciliation required") - for item in obligations: - obligation = Cleanup(item['kind'], item['call_digest']) - journal.before_cleanup(binding, item['ordinal'], obligation) - try: - if obligation.kind == 'task-token-revoke': - result = owner.revoke_task_token(obligation.call_digest) - elif obligation.kind == 'identity-token-revoke': - result = owner.revoke_identity_token(obligation.call_digest) - elif obligation.kind == 'grant-policy-delete': - result = owner.delete_grant_policy(obligation.call_digest) - else: - raise Refused("unsupported cleanup obligation") - if type(result) is not CleanupResult: - raise Refused("typed owner cleanup evidence required") - result.validate() - except Exception: - # Closed metadata only. Never hash/log a backend exception, response, - # token, accessor or secret-bearing stdout/stderr. - result = CleanupResult(False, hashlib.sha256(b'owner-cleanup-unconfirmed/v1').hexdigest()) - journal.after_cleanup(binding, item['ordinal'], obligation, - confirmed=result.confirmed, evidence_digest=result.evidence_digest) - return journal.cleanup_status(binding) diff --git a/scripts/task_execution_grant.py b/scripts/task_execution_grant.py deleted file mode 100644 index ad5ae87..0000000 --- a/scripts/task_execution_grant.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Proposed task-execution-grant CCR immutable projection; no native admission. - -The only excluded paths are typed append-only review.comments and evidence.events. -Status, updated, review requirements, effectful bytes and source revisions remain -immutable. A digest is provenance, never served approval or execution authority. -""" -from __future__ import annotations -import copy -import hashlib -import json -import re -from datetime import datetime - - -class Refused(Exception): pass - -ROOT_KEYS={'id','kind','schema_version','request_type','title','status','created','updated', - 'requester','review','target','execution','openbao','evidence'} -APPEND_ONLY_PATHS=(('review','comments'),('evidence','events')) -HASH=re.compile(r'sha256:[0-9a-f]{64}') - - -def need(value): - if not value:raise Refused('invalid task execution grant') - - -def encoded(value): - def json_value(item): - if type(item) is dict: - need(all(type(key) is str for key in item)) - for child in item.values():json_value(child) - elif type(item) is list: - for child in item:json_value(child) - else:need(item is None or type(item) in {str,int,float,bool}) - try: - json_value(value) - return json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=True,allow_nan=False).encode() - except (TypeError,ValueError,RecursionError):raise Refused('invalid task execution grant') from None - - -def closed(value,keys): - need(type(value) is dict and set(value)==set(keys));return value - - -def text(value): - need(type(value) is str and 0=len(old) and new[:len(old)]==old) - - -def bind(record,*,grant_id,expected_execution,expected_openbao,expected_digest): - """Compare an owner source record with independently reconstructed effects. - - Does not trust status/comments as approval, read source files, contact engines, - register a delegated applier or issue any backend request. - """ - value=projection(record) - need(record['id']==grant_id and type(expected_digest) is str and HASH.fullmatch(expected_digest)) - need(encoded(record['execution'])==encoded(expected_execution)) - need(encoded(record['openbao'])==encoded(expected_openbao)) - need(immutable_digest(record)==expected_digest) - return value diff --git a/scripts/task_execution_journal.py b/scripts/task_execution_journal.py deleted file mode 100644 index 1eaa583..0000000 --- a/scripts/task_execution_journal.py +++ /dev/null @@ -1,394 +0,0 @@ -"""Source-only admission primitive; never authorizes or dispatches native work. - -Trusted owner mediation must validate claim/PDP/consume before transitions. -One approval admits one operation. A persisted reservation cannot be resumed. -The process lock is held across the whole owner invocation, including cleanup. -""" -from __future__ import annotations - -import fcntl -import os -import re -import sqlite3 -import stat -import uuid -from contextlib import contextmanager -from dataclasses import dataclass -from pathlib import Path - - -class Refused(Exception): - pass - - -@dataclass(frozen=True) -class Binding: - approval_id: str - request_digest: str - operation_id: str - plan_digest: str - - def validate(self): - if any(type(v) is not str for v in (self.approval_id, self.request_digest, self.operation_id, self.plan_digest)): - raise Refused("binding fields must be strings") - if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9_-]{0,127}", self.approval_id): - raise Refused("invalid approval identifier") - # Preserve the exact served PDP/Approval value; never silently strip it. - if not re.fullmatch(r"sha256:[0-9a-f]{64}", self.request_digest): - raise Refused("invalid canonical request digest") - if not re.fullmatch(r"[0-9a-f]{64}", self.plan_digest): - raise Refused("invalid owner plan content digest") - try: - if str(uuid.UUID(self.operation_id)) != self.operation_id: - raise ValueError() - except (ValueError, AttributeError): - raise Refused("invalid operation identifier") from None - - -_NEXT = { - "reserved": {"consume-confirmed", "failed", "unknown"}, - "consume-confirmed": {"authenticating", "failed", "unknown"}, - "authenticating": {"grant-issuing", "failed", "unknown"}, - "grant-issuing": {"grant-issued", "failed", "unknown"}, - "grant-issued": {"applying", "failed", "unknown"}, - "applying": {"readback", "failed", "unknown"}, - "readback": {"verified", "failed", "unknown"}, - "verified": {"cleanup-confirmed", "unknown"}, -} - - -@dataclass(frozen=True) -class Cleanup: - """Owner-derived exact call commitment, never a handle or authority token. - - The owner mediator must include these commitments in its approved plan before - reservation, then compare the actual native cleanup call immediately before - dispatch. This journal does not authenticate that plan or native adapter. - """ - kind: str - call_digest: str - - def validate(self): - if type(self.kind) is not str or self.kind not in { - "task-token-revoke", "identity-token-revoke", "grant-policy-delete" - }: - raise Refused("unsupported cleanup obligation") - if type(self.call_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}", self.call_digest): - raise Refused("invalid cleanup call digest") - - -class Journal: - def __init__(self, directory: Path): - # Require an existing owner-private directory: deployment creates it. - if directory.is_symlink() or directory.resolve() != directory.absolute(): - raise Refused("journal path is indirect") - self.directory = directory - self._private(directory.stat(), directory=True) - self._lock_fd = self._open("execution.lock") - try: - fd = self._open("journal.sqlite") - except BaseException: - os.close(self._lock_fd) - del self._lock_fd - raise - original = os.fstat(fd) - os.close(fd) - self.db = sqlite3.connect(directory / "journal.sqlite", isolation_level=None) - current = (directory / "journal.sqlite").stat() - if (original.st_dev, original.st_ino) != (current.st_dev, current.st_ino): - self.close() - raise Refused("journal changed during open") - self.db.execute("PRAGMA synchronous=FULL") - self.db.execute("PRAGMA journal_mode=DELETE") - self.db.execute("CREATE TABLE IF NOT EXISTS operations (approval TEXT PRIMARY KEY, request TEXT NOT NULL, operation TEXT UNIQUE NOT NULL, plan TEXT NOT NULL, state TEXT NOT NULL, evidence TEXT)") - self.db.execute("CREATE TABLE IF NOT EXISTS calls (operation TEXT NOT NULL, ordinal INTEGER NOT NULL, call_digest TEXT NOT NULL, outcome TEXT NOT NULL, evidence TEXT, PRIMARY KEY(operation,ordinal))") - self.db.execute("CREATE TABLE IF NOT EXISTS cleanup (operation TEXT NOT NULL, ordinal INTEGER NOT NULL, kind TEXT NOT NULL, call_digest TEXT NOT NULL, outcome TEXT NOT NULL, evidence TEXT, PRIMARY KEY(operation,ordinal), UNIQUE(operation,kind))") - self.db.execute("CREATE TABLE IF NOT EXISTS cleanup_calls (operation TEXT NOT NULL, obligation INTEGER NOT NULL, step INTEGER NOT NULL, call_digest TEXT NOT NULL, outcome TEXT NOT NULL, evidence TEXT, PRIMARY KEY(operation,obligation,step))") - self.db.execute("CREATE TABLE IF NOT EXISTS recovery_attempts (attempt TEXT PRIMARY KEY, operation TEXT NOT NULL, original TEXT NOT NULL)") - self.db.execute("CREATE TABLE IF NOT EXISTS recovery_calls (attempt TEXT NOT NULL, obligation INTEGER NOT NULL, step INTEGER NOT NULL, digest TEXT NOT NULL, outcome TEXT NOT NULL, evidence TEXT, PRIMARY KEY(attempt,obligation,step))") - self.db.execute("CREATE TABLE IF NOT EXISTS recovery_results (attempt TEXT NOT NULL, obligation INTEGER NOT NULL, confirmed INTEGER NOT NULL, evidence TEXT NOT NULL, PRIMARY KEY(attempt,obligation))") - self._held = False - self._admission = None - self._consumed_admission = None - self._sync_directory() - - @staticmethod - def _private(s, directory=False): - kind = stat.S_ISDIR(s.st_mode) if directory else stat.S_ISREG(s.st_mode) - if not kind or s.st_uid != os.getuid() or s.st_mode & 0o077: - raise Refused("journal is not owner private") - if not directory and s.st_nlink != 1: - raise Refused("journal file has multiple links") - - def _open(self, name): - fd = os.open(self.directory / name, os.O_CREAT | os.O_RDWR | os.O_NOFOLLOW, 0o600) - try: - self._private(os.fstat(fd)) - except BaseException: - os.close(fd) - raise - return fd - - def _sync_directory(self): - fd = os.open(self.directory, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW) - try: - os.fsync(fd) - finally: - os.close(fd) - - @contextmanager - def locked(self): - if self._held: - raise Refused("nested journal admission") - try: - fcntl.flock(self._lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - except BlockingIOError: - raise Refused("owner invocation already active") from None - self._held = True - try: - yield self - finally: - self._admission = None - self._consumed_admission = None - self._held = False - fcntl.flock(self._lock_fd, fcntl.LOCK_UN) - - def reserve(self, binding: Binding, cleanup: tuple[Cleanup, ...] = ()): - binding.validate() - if type(cleanup) is not tuple or len(cleanup) > 3: - raise Refused("closed cleanup plan required") - for item in cleanup: - if type(item) is not Cleanup: - raise Refused("typed cleanup obligation required") - item.validate() - if len({item.kind for item in cleanup}) != len(cleanup): - raise Refused("duplicate cleanup obligation") - if not self._held or self._admission is not None: - raise Refused("reservation requires fresh exclusive invocation") - try: - self.db.execute("BEGIN IMMEDIATE") - self.db.execute("INSERT INTO operations VALUES (?,?,?,?,?,NULL)", - (binding.approval_id, binding.request_digest, - binding.operation_id, binding.plan_digest, "reserved")) - self.db.executemany("INSERT INTO cleanup VALUES (?,?,?,?,'owed',NULL)", - [(binding.operation_id, i, item.kind, item.call_digest) - for i, item in enumerate(cleanup)]) - self.db.execute("COMMIT") - self._sync_directory() - except sqlite3.IntegrityError: - self.db.execute("ROLLBACK") - raise Refused("approval or operation already admitted") from None - except BaseException: - if self.db.in_transaction: - self.db.execute("ROLLBACK") - raise - self._admission = binding - - def transition(self, binding: Binding, expected: str, target: str, evidence_digest=None): - if not self._held or self._admission != binding: - raise Refused("no live admission") - if target not in _NEXT.get(expected, set()): - raise Refused("invalid state transition") - if target == "cleanup-confirmed" and evidence_digest is None: - raise Refused("cleanup requires evidence") - if evidence_digest is not None and not re.fullmatch(r"[0-9a-f]{64}", evidence_digest): - raise Refused("invalid evidence digest") - try: - self.db.execute("BEGIN IMMEDIATE") - pending=self.db.execute("SELECT COUNT(*) FROM calls WHERE operation=? AND outcome='pending'",(binding.operation_id,)).fetchone()[0] - started = self.db.execute("SELECT COUNT(*) FROM cleanup WHERE operation=? AND outcome!='owed'", (binding.operation_id,)).fetchone()[0] - if started and target not in {'unknown', 'cleanup-confirmed'}: - raise Refused("cleanup has closed business execution") - if target == 'cleanup-confirmed': - total, owed = self.db.execute("SELECT COUNT(*),COALESCE(SUM(outcome!='confirmed'),0) FROM cleanup WHERE operation=?", (binding.operation_id,)).fetchone() - if not total or owed: - raise Refused("explicit cleanup obligations are unconfirmed") - if pending and target != 'unknown': - raise Refused("unconfirmed external call requires unknown") - changed = self.db.execute( - "UPDATE operations SET state=?, evidence=? WHERE approval=? AND request=? AND operation=? AND plan=? AND state=?", - (target, evidence_digest, binding.approval_id, binding.request_digest, - binding.operation_id, binding.plan_digest, expected)).rowcount - if changed != 1: - raise Refused("journal state changed") - self.db.execute("COMMIT") - self._sync_directory() - if target == "consume-confirmed": - self._consumed_admission = binding - except BaseException: - if self.db.in_transaction: - self.db.execute("ROLLBACK") - raise - - def observe(self, binding: Binding): - binding.validate() - row = self.db.execute("SELECT request,operation,plan,state,evidence FROM operations WHERE approval=?", (binding.approval_id,)).fetchone() - if row is None: - return None - if row[:3] != (binding.request_digest, binding.operation_id, binding.plan_digest): - raise Refused("journal binding mismatch") - # Readback cannot create a live admission, even for an interrupted state. - pending = self.db.execute("SELECT COUNT(*) FROM calls WHERE operation=? AND outcome='pending'", (binding.operation_id,)).fetchone()[0] - pending += self.db.execute("SELECT COUNT(*) FROM cleanup WHERE operation=? AND outcome='pending'", (binding.operation_id,)).fetchone()[0] - pending += self.db.execute("SELECT COUNT(*) FROM cleanup_calls WHERE operation=? AND outcome='pending'", (binding.operation_id,)).fetchone()[0] - pending += self.db.execute("SELECT COUNT(*) FROM recovery_calls AS c JOIN recovery_attempts AS a ON c.attempt=a.attempt WHERE a.operation=? AND c.outcome='pending'", (binding.operation_id,)).fetchone()[0] - return {"state": row[3], "evidence_digest": row[4], "dispatch_allowed": False, - "external_outcome_unknown": bool(pending)} - - def next_call_ordinal(self, binding: Binding): - """Continue the same admitted invocation after owner auth/consume calls. - - This never grants dispatch or resumes a persisted admission. The actual - before_call transaction remains the final serialization/intent guard. - """ - if not self._held or self._admission != binding: - raise Refused("next call requires same live admission") - count,pending=self.db.execute("SELECT COUNT(*),COALESCE(SUM(outcome='pending'),0) FROM calls WHERE operation=?",(binding.operation_id,)).fetchone() - cleanup=self.db.execute("SELECT COUNT(*) FROM cleanup WHERE operation=? AND outcome!='owed'",(binding.operation_id,)).fetchone()[0] - state=self.db.execute("SELECT state FROM operations WHERE operation=?",(binding.operation_id,)).fetchone()[0] - if pending or cleanup or state in {'failed','unknown','cleanup-confirmed'} or count>63: - raise Refused("call sequence is uncertain, closed or exhausted") - return count - - def before_call(self, binding: Binding, ordinal: int, call_digest: str): - """Persist one exact plan-call intent before the mediator issues it. - - Caller must derive ordinal/digest from the independently pinned owner plan. - This primitive doesn't validate method/body or decide whether a call is allowed. - """ - if not self._held or self._admission != binding: - raise Refused("no live admission") - if type(ordinal) is not int or ordinal < 0 or ordinal > 63: - raise Refused("invalid fixed-plan ordinal") - if type(call_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}", call_digest): - raise Refused("invalid call digest") - try: - self.db.execute("BEGIN IMMEDIATE") - count,pending = self.db.execute("SELECT COUNT(*), COALESCE(SUM(outcome='pending'),0) FROM calls WHERE operation=?",(binding.operation_id,)).fetchone() - state=self.db.execute("SELECT state FROM operations WHERE operation=?",(binding.operation_id,)).fetchone()[0] - cleanup_started = self.db.execute("SELECT COUNT(*) FROM cleanup WHERE operation=? AND outcome!='owed'", (binding.operation_id,)).fetchone()[0] - if cleanup_started or ordinal != count or pending or state in {'failed','unknown','cleanup-confirmed'}: - raise Refused("call is replayed, uncertain or terminal") - self.db.execute("INSERT INTO calls VALUES (?,?,?,'pending',NULL)",(binding.operation_id,ordinal,call_digest)) - self.db.execute("COMMIT") - self._sync_directory() - except BaseException: - if self.db.in_transaction: self.db.execute("ROLLBACK") - raise - - def after_call(self, binding: Binding, ordinal: int, call_digest: str, evidence_digest: str): - if not self._held or self._admission != binding: - raise Refused("no live admission") - if type(ordinal) is not int or ordinal < 0 or ordinal > 63: - raise Refused("invalid fixed-plan ordinal") - if type(call_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}", call_digest): - raise Refused("invalid call digest") - if type(evidence_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}", evidence_digest): - raise Refused("invalid evidence digest") - try: - self.db.execute("BEGIN IMMEDIATE") - n=self.db.execute("UPDATE calls SET outcome='complete', evidence=? WHERE operation=? AND ordinal=? AND call_digest=? AND outcome='pending'",(evidence_digest,binding.operation_id,ordinal,call_digest)).rowcount - if n != 1: raise Refused("call confirmation mismatch") - self.db.execute("COMMIT") - self._sync_directory() - except BaseException: - if self.db.in_transaction: self.db.execute("ROLLBACK") - raise - - def cleanup_status(self, binding: Binding): - # Observation never restores live execution or cleanup authority. - if self.observe(binding) is None: - raise Refused("operation is not admitted") - rows = self.db.execute("SELECT ordinal,kind,call_digest,outcome,evidence FROM cleanup WHERE operation=? ORDER BY ordinal", (binding.operation_id,)).fetchall() - return {"confirmed": bool(rows) and all(row[3] == "confirmed" for row in rows), - "obligations": [{"ordinal": row[0], "kind": row[1], "call_digest": row[2], - "outcome": row[3], "evidence_digest": row[4]} for row in rows]} - - def _cleanup_admission(self, binding, ordinal, obligation): - if not self._held or self._admission != binding or self._consumed_admission != binding: - raise Refused("cleanup requires same live consumed admission") - if type(ordinal) is not int or not 0 <= ordinal < 3 or type(obligation) is not Cleanup: - raise Refused("exact cleanup obligation required") - obligation.validate() - - def before_cleanup(self, binding: Binding, ordinal: int, obligation: Cleanup): - """Commit intent for one pre-admitted cleanup call, even after unknown. - - Never resumes an interrupted invocation. Independent owner recovery after - a crash still requires the owner's protected recovery channel/approval. - Cleanup has no business deadline: its authority is the original separate - explicit obligation, and cannot authorize a read, repair or business write. - """ - self._cleanup_admission(binding, ordinal, obligation) - try: - self.db.execute("BEGIN IMMEDIATE") - changed = self.db.execute("UPDATE cleanup SET outcome='pending' WHERE operation=? AND ordinal=? AND kind=? AND call_digest=? AND outcome='owed'", - (binding.operation_id, ordinal, obligation.kind, obligation.call_digest)).rowcount - if changed != 1: - raise Refused("cleanup is unadmitted, replayed or uncertain") - self.db.execute("COMMIT") - self._sync_directory() - except BaseException: - if self.db.in_transaction: self.db.execute("ROLLBACK") - raise - - def after_cleanup(self, binding: Binding, ordinal: int, obligation: Cleanup, - *, confirmed: bool, evidence_digest: str): - self._cleanup_admission(binding, ordinal, obligation) - if type(confirmed) is not bool or type(evidence_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}", evidence_digest): - raise Refused("closed cleanup evidence required") - try: - self.db.execute("BEGIN IMMEDIATE") - if confirmed and self.db.execute("SELECT COUNT(*) FROM cleanup_calls WHERE operation=? AND obligation=? AND outcome='pending'", (binding.operation_id,ordinal)).fetchone()[0]: - raise Refused("pending cleanup native call cannot confirm") - changed = self.db.execute("UPDATE cleanup SET outcome=?, evidence=? WHERE operation=? AND ordinal=? AND kind=? AND call_digest=? AND outcome='pending'", - ('confirmed' if confirmed else 'unconfirmed', evidence_digest, - binding.operation_id, ordinal, obligation.kind, obligation.call_digest)).rowcount - if changed != 1: - raise Refused("cleanup confirmation mismatch") - self.db.execute("COMMIT") - self._sync_directory() - except BaseException: - if self.db.in_transaction: self.db.execute("ROLLBACK") - raise - - def before_cleanup_call(self, binding, ordinal, obligation, step, call_digest): - """Journal each native revoke/delete AND its independent verification.""" - self._cleanup_admission(binding,ordinal,obligation) - if type(step) is not int or not 0<=step<2 or type(call_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}",call_digest): - raise Refused("exact bounded cleanup native call required") - try: - self.db.execute("BEGIN IMMEDIATE") - parent=self.db.execute("SELECT kind,call_digest,outcome FROM cleanup WHERE operation=? AND ordinal=?",(binding.operation_id,ordinal)).fetchone() - count,pending=self.db.execute("SELECT COUNT(*),COALESCE(SUM(outcome='pending'),0) FROM cleanup_calls WHERE operation=? AND obligation=?",(binding.operation_id,ordinal)).fetchone() - if parent!=(obligation.kind,obligation.call_digest,'pending') or step!=count or pending: - raise Refused("cleanup call unadmitted, replayed or uncertain") - self.db.execute("INSERT INTO cleanup_calls VALUES (?,?,?,?,'pending',NULL)",(binding.operation_id,ordinal,step,call_digest)) - self.db.execute("COMMIT");self._sync_directory() - except BaseException: - if self.db.in_transaction:self.db.execute("ROLLBACK") - raise - - def after_cleanup_call(self,binding,ordinal,obligation,step,call_digest,evidence_digest): - self._cleanup_admission(binding,ordinal,obligation) - if (type(step) is not int or not 0<=step<2 or type(call_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}",call_digest) - or type(evidence_digest) is not str or not re.fullmatch(r"[0-9a-f]{64}",evidence_digest)): - raise Refused("exact cleanup native evidence required") - try: - self.db.execute("BEGIN IMMEDIATE") - parent=self.db.execute("SELECT kind,call_digest,outcome FROM cleanup WHERE operation=? AND ordinal=?",(binding.operation_id,ordinal)).fetchone() - if parent!=(obligation.kind,obligation.call_digest,'pending'): - raise Refused("cleanup parent is no longer pending") - changed=self.db.execute("UPDATE cleanup_calls SET outcome='complete',evidence=? WHERE operation=? AND obligation=? AND step=? AND call_digest=? AND outcome='pending'", - (evidence_digest,binding.operation_id,ordinal,step,call_digest)).rowcount - if changed!=1:raise Refused("cleanup native confirmation mismatch") - self.db.execute("COMMIT");self._sync_directory() - except BaseException: - if self.db.in_transaction:self.db.execute("ROLLBACK") - raise - - def close(self): - if hasattr(self, "db"): - self.db.close() - if hasattr(self, "_lock_fd"): - os.close(self._lock_fd) diff --git a/scripts/task_execution_mediator.py b/scripts/task_execution_mediator.py deleted file mode 100644 index ec9d235..0000000 --- a/scripts/task_execution_mediator.py +++ /dev/null @@ -1,218 +0,0 @@ -"""Owner mediator seam for the existing strict Secrets handoff and Approval GET. - -Only the disposable NativeFixtureAdapter is callable here. No production backend, -caller-supplied authorizer/transport, approval boolean or portable receipt exists. -The strict Secrets module is an explicit external dependency: absence refuses -before credential-bearing requests or native effects, never a permissive fallback. -""" -from __future__ import annotations -from dataclasses import dataclass -from datetime import datetime,timezone -import importlib -import json -import os -import stat -from pathlib import Path -from urllib.parse import urlsplit - -from task_execution_cleanup import run_cleanup -from task_execution_journal import Binding,Cleanup,Journal,Refused -from task_execution_plan import Compiler,encoded,content_hash -from task_execution_native_fixture import FixtureAdmission,NativeFixtureAdapter,_absolute_budget - - -@dataclass(frozen=True) -class ConfirmationConfig: - """Owner-installed independent read credential/route, never request fields.""" - origin: str - token_file: Path - timeout_seconds: float = 3 - - -_HANDOFF_FIELDS={'execution_contract','approval_id','request_digest','decision_id', - 'native_binding','approval_binding_digest','submitted_request_digest', - 'decision_not_before','decision_expires_at','approval_expires_at'} - - -def _strict_dependency(): - try: - module=importlib.import_module('secrets_engine.task_execution_handoff') - for name in ['TaskExecutionAuthorization','TaskExecutionConsumption', - 'authorize_task_execution','consume_task_execution']: - if not hasattr(module,name):raise ImportError() - return module - except ImportError: - raise Refused('strict Secrets task execution handoff dependency unavailable') from None - - -def _trusted_v2(cfg): - return (getattr(cfg,'authorization_policy_package',None)=='secrets-engine.task-execution.service-pilot' - and getattr(cfg,'authorization_policy_version',None)=='v2') - - -def _read_source_bundle(descriptor): - # Paths/hash expectations come from independently installed owner_descriptor, - # never a requester-selected file lookup. No bytes or raw OS errors escape. - for item in descriptor['source_bundle'].values(): - path=Path(item['path']);fd=None - try: - if not path.is_absolute() or path.resolve()!=path:raise ValueError() - fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK) - before=os.fstat(fd) - if not stat.S_ISREG(before.st_mode) or before.st_nlink!=1 or before.st_mode&0o022 or not 0=min(deadline,approval,decision) or deadline>min(approval,decision) or (start is not None and lower0 and previous is not _budget_expired): - raise Refused('conflicting owner execution timer') - started=time.monotonic() - try: - signal.signal(signal.SIGALRM,_budget_expired) - signal.setitimer(signal.ITIMER_REAL,min(seconds,old) if old>0 else seconds) - yield - finally: - signal.setitimer(signal.ITIMER_REAL,0) - elapsed=time.monotonic()-started - signal.signal(signal.SIGALRM,previous) - if old>0: - if old<=elapsed:raise Refused('outer native authority budget exhausted') - signal.setitimer(signal.ITIMER_REAL,old-elapsed) - - -def _business_window(method): - @wraps(method) - def bounded(self,*args,**kwargs): - remaining=self._remaining_business()-5 - with _absolute_budget(remaining):return method(self,*args,**kwargs) - return bounded - - -def _call_window(method): - @wraps(method) - def bounded(self,*args,**kwargs): - with _absolute_budget(2):return method(self,*args,**kwargs) - return bounded - - -@dataclass(frozen=True) -class PrivateSession: - token: str = field(repr=False) - accessor: str = field(repr=False) - - -@dataclass(frozen=True) -class FixtureAdmission: - """Independently installed owner fixture inputs, not a request schema.""" - descriptor: bytes - expected_before: bytes - expected_after: bytes - owner_token: str = field(repr=False) - identity_session: PrivateSession = field(repr=False) - server_process: subprocess.Popen = field(repr=False) - - -class NativeFixtureAdapter: - def __init__(self, compiler: Compiler, plan: Plan, journal: Journal, - binding: Binding, admission: FixtureAdmission, *, recovery_custody=None): - if type(admission) is not FixtureAdmission or type(admission.descriptor) is not bytes: - raise Refused('independent fixture admission required') - self.compiler,self.plan,self.journal,self.binding=compiler,plan,journal,binding - self.admission=admission - self.meta=plan.metadata() - if self.meta.get('execution_grant',{}).get('status')!='approved': - raise Refused('independently source-bound synthetic approved grant required') - descriptor=json.loads(admission.descriptor) - fresh=compiler.bind_execution(compiler.compile(self.meta['request']),descriptor,admission.descriptor) - if fresh.serialized != plan.serialized or binding.plan_digest != plan.content_digest: - raise Refused('owner bound plan mismatch') - if binding.operation_id != descriptor['operation_id'] or binding.request_digest != self.meta['request']['request_digest']: - raise Refused('owner operation mismatch') - origin=urlsplit(descriptor['backend_origin']) - if origin.scheme!='http' or origin.hostname!='127.0.0.1' or not origin.port or origin.username or origin.password or origin.path or origin.query or origin.fragment: - raise Refused('disposable loopback backend only') - self.host,self.port=origin.hostname,origin.port - process=admission.server_process - if (type(process) is not subprocess.Popen or process.poll() is not None - or type(process.args) is not list or '-dev' not in process.args - or '-dev-no-store-token' not in process.args - or '-dev-listen-address=127.0.0.1:'+str(self.port) not in process.args): - raise Refused('live owner-spawned disposable dev process required') - budget=descriptor['backend_budget'] - if set(budget)!={'ttl_seconds','uses'} or type(budget['ttl_seconds']) is not int or not 30<=budget['ttl_seconds']<=120 or type(budget['uses']) is not int or budget['uses']!=12: - raise Refused('fixed native task budget required') - self.ttl=budget['ttl_seconds']; self.uses=budget['uses']; self.used=0 - self.deadline=datetime.fromisoformat(descriptor['operation_deadline'].replace('Z','+00:00')) - if self.deadline.tzinfo is None: raise Refused('aware operation deadline required') - remaining=(self.deadline-datetime.now(timezone.utc)).total_seconds() - self.monotonic_deadline=time.monotonic()+remaining - self.policy_name='task-exec-'+binding.operation_id - calls=[compiler.before_call(plan,i) for i in range(6)] - paths=sorted({call.path for call in calls}) - self.hcl=''.join('path '+json.dumps(path)+' {\n capabilities = ["read", "create", "update"]\n}\n' for path in paths) - self.hcl+='path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n' - if self.meta['execution_grant']['openbao']['policy']!={'name':self.policy_name,'hcl':self.hcl}: - raise Refused('native grant body differs from bound owner projection') - # Installed owner snapshots use the existing descriptor commitments. No - # request-supplied expectation or response may authorize itself. - self.expected={} - for phase in ('before','after'): - raw=getattr(admission,'expected_'+phase) - if type(raw) is not bytes or 'sha256:'+content_hash(raw)!=descriptor['expected_'+phase]: - raise Refused('owner expected-state digest mismatch') - value=json.loads(raw) - if type(value) is not dict or set(value)!=set(paths) or encoded(value)!=raw: - raise Refused('canonical exact target snapshot required') - if any(item is not None and type(item) is not dict for item in value.values()): - raise Refused('metadata snapshot object or explicit absence required') - if phase=='after' and any(item is None for item in value.values()): - raise Refused('present final metadata required') - self.expected[phase]=value - self._observed={'before':{},'after':{}} - self.cleanup=tuple(Cleanup(item['kind'],item['call_digest']) for item in cleanup_specs(binding.operation_id)) - if self.meta['cleanup'] != cleanup_specs(binding.operation_id): raise Refused('cleanup plan mismatch') - self.task=None; self._mint_attempted=False; self._policy_attempted=False; self._ordinal=0; self._next_metadata=0 - self._cleanup_steps={item.kind:0 for item in self.cleanup} - self._task_wall_deadline=None;self._task_monotonic_deadline=None - self.recovery_custody=recovery_custody - if recovery_custody is not None: - from task_execution_recovery import FixtureRecoveryCustody - if type(recovery_custody) is not FixtureRecoveryCustody or journal.observe(binding) is not None: - raise Refused('fresh independent fixture recovery custody required') - recovery_custody.prepare(binding,self.cleanup,self.port,self.hcl,admission.identity_session.accessor) - - def _live(self): - if not self.journal._held or self.journal._admission!=self.binding or self.journal._consumed_admission!=self.binding: - raise Refused('same live consumed owner admission required') - - def _remaining_business(self): - bounds=[(self.deadline-datetime.now(timezone.utc)).total_seconds(),self.monotonic_deadline-time.monotonic()] - task_wall=getattr(self,'_task_wall_deadline',None) - task_mono=getattr(self,'_task_monotonic_deadline',None) - if task_wall is not None:bounds.append((task_wall-datetime.now(timezone.utc)).total_seconds()) - if task_mono is not None:bounds.append(task_mono-time.monotonic()) - return min(bounds) - - def _fresh(self): - self._live() - if self._remaining_business()<=5: - raise Refused('business deadline exhausted') - # The constructor independently rebuilt the complete immutable Plan and - # grant. Recheck every pinned byte before each native effect. Metadata - # dispatch additionally reconstructs its exact ordinal/body below; grant - # creation reconstructs and compares its fixed body before issuance. - for path in self.compiler.pins:self.compiler.read(Path(path)) - - @_call_window - def _http(self, method, path, token, body=b''): - # Fixed caller-generated paths only; http.client has no proxy or redirect - # following. Never surface backend body/header or transport exception text. - if self.admission.server_process.poll() is not None: - raise Refused('disposable backend exited; outcome unconfirmed') - conn=http.client.HTTPConnection(self.host,self.port,timeout=2) - try: - conn.request(method,'/v1/'+path,body=body or None, - headers={'X-Vault-Token':token,'Content-Type':'application/json'}) - response=conn.getresponse(); raw=response.read(1024*1024+1) - if len(raw)>1024*1024: raise Refused('oversize native response') - data=json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant) if raw else {} - if type(data) is not dict: raise Refused('invalid native response') - return response.status,data - except Exception: - raise Refused('native transport or response unconfirmed') from None - finally: conn.close() - - @_business_window - @_call_window - def _business(self, method, path, token, payload=b'', task=False): - self._fresh() - if task: - if self.used>=self.uses-1: raise Refused('task cleanup use reserved') - self.used+=1 - evidence_payload = encoded({'token':'owner-retained-task-token'}) if path=='auth/token/lookup' else payload - digest=content_hash(encoded({'method':method,'path':path,'payload_sha256':'sha256:'+content_hash(evidence_payload),'credential_slot':'task' if task else 'owner'})) - ordinal=self.journal.next_call_ordinal(self.binding) - self.journal.before_call(self.binding,ordinal,digest);self._ordinal=ordinal+1 - try: status,data=self._http(method,path,token,payload) - except Exception: - state=self.journal.observe(self.binding)['state'] - if state!='unknown': self.journal.transition(self.binding,state,'unknown') - raise - # Native responses may contain tokens/accessors; evidence is closed status - # metadata rather than raw bytes or a guessable secret-value fingerprint. - evidence=content_hash(encoded({'call_digest':digest,'http_status':status})) - self.journal.after_call(self.binding,ordinal,digest,evidence) - return status,data - - @staticmethod - def _ok(status,data,allowed): - if status not in allowed or data.get('errors'): - raise Refused('native request refused or unverified') - - @_business_window - def materialize_grant(self): - self._fresh() - if self.journal.observe(self.binding)['state']!='grant-issuing' or self._policy_attempted: - raise Refused('fresh grant issuing state required') - if min((self.deadline-datetime.now(timezone.utc)).total_seconds(),self.monotonic_deadline-time.monotonic())=len(spec['calls']) or spec['calls'][call_index]!={'method':method,'path':path}: - raise Refused('cleanup native method/target differs from commitment') - # Commit shape/slot only: private accessor/token bytes never enter hashes - # or durable evidence. Their exact identity is owner-retained custody. - digest=content_hash(encoded({'obligation_digest':obligation.call_digest,'step':step, - 'method':method,'path':path})) - self.journal.before_cleanup_call(self.binding,ordinal,obligation,step,digest) - self._cleanup_steps[kind]+=1 - status,data=self._http(method,path,token,body) - evidence=content_hash(encoded({'call_digest':digest,'http_status':status})) - self.journal.after_cleanup_call(self.binding,ordinal,obligation,step,digest,evidence) - return status,data - - def _revoke(self, kind, digest, session, issue_uncertain=False): - self._cleanup_admitted(kind,digest) - if session is None: - return CleanupResult(not issue_uncertain,content_hash(encoded({'kind':kind,'issue_uncertain':issue_uncertain}))) - status,data=self._cleanup_http(kind,'POST','auth/token/revoke-self',session.token) - self._ok(status,data,{200,204}) - status,data=self._cleanup_http(kind,'POST','auth/token/lookup-accessor',self.admission.owner_token,encoded({'accessor':session.accessor})) - confirmed=status==400 and data.get('errors') in (['invalid accessor'],['1 error occurred:\n\t* invalid accessor\n\n']) - return CleanupResult(confirmed,content_hash(encoded({'kind':kind,'revocation_confirmed':confirmed}))) - - def revoke_task_token(self, digest): - return self._revoke('task-token-revoke',digest,self.task,self._mint_attempted and self.task is None) - - def revoke_identity_token(self, digest): - return self._revoke('identity-token-revoke',digest,self.admission.identity_session) - - def delete_grant_policy(self, digest): - self._cleanup_admitted('grant-policy-delete',digest) - path='sys/policies/acl/'+self.policy_name - if self._policy_attempted: - status,data=self._cleanup_http('grant-policy-delete','DELETE',path,self.admission.owner_token);self._ok(status,data,{200,204}) - status,data=self._cleanup_http('grant-policy-delete','GET',path,self.admission.owner_token) - confirmed=_policy_absent(status,data) - return CleanupResult(confirmed,content_hash(encoded({'kind':'grant-policy-delete','absence_confirmed':confirmed}))) diff --git a/scripts/task_execution_plan.py b/scripts/task_execution_plan.py deleted file mode 100644 index 4c086b4..0000000 --- a/scripts/task_execution_plan.py +++ /dev/null @@ -1,353 +0,0 @@ -"""Owner-derived metadata pilot compiler. Source-only; never authorizes or dispatches. - -OwnerConfig is installed by the trusted owner, never deserialized from a request. -Six fixed calls on two structure objects. Recompile immediately before each call. -Canonical PDP request digest is opaque: the authenticated mediator verifies it. -""" -from __future__ import annotations -import builtins -import hashlib -import json -import os -from pathlib import Path -import re -import stat -import sys -import types -from dataclasses import dataclass -from uuid import UUID, uuid4 -import yaml - -class Refused(Exception): pass - -def need(value, reason): - if not value: raise Refused(reason) - -def encoded(value): - return json.dumps(value,sort_keys=True,separators=(',', ':'),ensure_ascii=True,allow_nan=False).encode() - -def content_hash(raw): return hashlib.sha256(raw).hexdigest() -def tagged(raw): return 'sha256:'+content_hash(raw) -def digest_ok(value): return type(value) is str and re.fullmatch(r'sha256:[0-9a-f]{64}',value) is not None - -class UniqueLoader(yaml.SafeLoader): pass -def unique(loader,node): - result={} - for key,val in node.value: - k=loader.construct_object(key) - need(k not in result,'duplicate_owner_key') - result[k]=loader.construct_object(val) - return result -UniqueLoader.add_constructor(yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,unique) - -TARGET_KEYS=('kind','org','repo','mount','path','fields','mount_management','consumers', - 'delivery_modes','delivery_auth','delivery_config','auth_capability','workload_delivery') -REQUEST_KEYS={'action','kind','catalog_id','operation_id','request_digest','ccr_sha256','catalog_sha256','operations'} - -@dataclass(frozen=True) -class OwnerConfig: - platform_repo: Path - secrets_repo: Path - ccr_filename: str - catalog_filename: str - catalog_directory: Path - # Exact path/hash pairs, configured independently by the owner. No request paths. - source_pins: tuple[tuple[str,str], ...] - grant_filename: str | None = None - -@dataclass(frozen=True) -class Plan: - serialized: bytes - @property - def content_digest(self): return content_hash(self.serialized) # Journal uses bare content hash. - def metadata(self): - try: return json.loads(self.serialized) - except Exception: raise Refused('invalid_plan') from None - -@dataclass(frozen=True) -class Call: - ordinal: int - kind: str - method: str - path: str - payload: bytes - @property - def payload_sha256(self): return tagged(self.payload) - @property - def call_digest(self): - return content_hash(encoded({'ordinal':self.ordinal,'kind':self.kind,'method':self.method, - 'path':self.path,'payload_sha256':self.payload_sha256})) - - -def cleanup_specs(operation_id): - """Deterministic owner cleanup plan; native handles stay in owner memory.""" - try: need(str(UUID(operation_id)) == operation_id, 'operation_id_invalid') - except (ValueError, AttributeError, TypeError): raise Refused('operation_id_invalid') from None - policy_name = 'task-exec-' + operation_id - specs = [] - for kind, slot, calls in [ - ('task-token-revoke', 'task', [('POST','auth/token/revoke-self'), ('POST','auth/token/lookup-accessor')]), - ('identity-token-revoke', 'identity', [('POST','auth/token/revoke-self'), ('POST','auth/token/lookup-accessor')]), - ('grant-policy-delete', 'owner', [('DELETE','sys/policies/acl/'+policy_name), ('GET','sys/policies/acl/'+policy_name)]), - ]: - value = {'kind':kind, 'credential_slot':slot, 'operation_id':operation_id, - 'calls':[{'method':method,'path':path} for method,path in calls]} - if kind == 'task-token-revoke': value['confirm_not_created_if_no_issue_attempt'] = True - if kind == 'grant-policy-delete': value['skip_delete_if_no_create_attempt'] = True - value['call_digest'] = content_hash(encoded(value)) - specs.append(value) - return specs - -class Compiler: - def __init__(self, config: OwnerConfig): - need(type(config) is OwnerConfig,'owner_configuration_required') - self.config=config - for root in (config.platform_repo,config.secrets_repo,config.catalog_directory): - need(type(root) is Path or isinstance(root,Path),'owner_root_invalid') - need(root.is_absolute() and root.resolve()==root,'owner_root_invalid') - for name in (config.ccr_filename,config.catalog_filename): - need(type(name) is str and re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*\.yaml',name),'owner_filename_invalid') - self.ccr=config.platform_repo/'credential-change-requests'/config.ccr_filename - self.catalog=config.catalog_directory/config.catalog_filename - self.validator=config.platform_repo/'scripts/credential-change.py' - self.catalog_code=config.secrets_repo/'src/secrets_engine/catalog.py' - self.dependencies=[config.secrets_repo/'src/secrets_engine'/name for name in ('__init__.py','errors.py','redact.py')] - self.grant_record=None;self.grant_validator=None - if config.grant_filename is not None: - need(type(config.grant_filename) is str and re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.-]*\.yaml',config.grant_filename) - and config.grant_filename!=config.ccr_filename,'owner_grant_filename_invalid') - self.grant_record=config.platform_repo/'credential-change-requests'/config.grant_filename - self.grant_validator=Path(__file__).resolve().parent/'task_execution_grant.py' - pins=dict(config.source_pins) - self.pins=types.MappingProxyType(pins) - need(len(self.pins)==len(config.source_pins) and all(type(k) is str and digest_ok(v) for k,v in self.pins.items()),'owner_pins_invalid') - # Compiler source is part of the owner plan, not just generated body hashes. - required={str(p) for p in [Path(__file__).resolve(),self.ccr,self.catalog,self.validator,self.catalog_code,*self.dependencies]} - if self.grant_record is not None:required.update({str(self.grant_record),str(self.grant_validator)}) - need(required <= set(self.pins),'owner_source_pin_missing') - extra=set(self.pins)-required - need(len(extra)==1,'closed_owner_source_set_required') - artifact=Path(next(iter(extra))) - need(artifact.parent==config.platform_repo/'openbao/policies' - and re.fullmatch(r'workload-kv-read-[a-z0-9-]+\.hcl',artifact.name), - 'closed_owner_policy_source_required') - - def read(self,path): - need(str(path) in self.pins and path.is_absolute() and path.resolve()==path,'owner_source_path_invalid') - fd=None - try: - fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_NONBLOCK) - before=os.fstat(fd) - need(stat.S_ISREG(before.st_mode) and before.st_nlink==1 and not before.st_mode & 0o022 - and 0 65536: raise Refused('oversize private custody record') - try: capsule = json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant) - except Exception: raise Refused('invalid private custody record') from None - if (type(capsule) is not dict or set(capsule)!={'payload','mac'} or type(capsule['mac']) is not str - or len(capsule['mac'])!=64 or any(x not in '0123456789abcdef' for x in capsule['mac'])): - raise Refused('invalid authenticated private capsule') - expected_mac=hmac.new(self._integrity_key,_encoded(capsule['payload']),hashlib.sha256).hexdigest() - if not hmac.compare_digest(capsule['mac'],expected_mac): - raise Refused('private capsule integrity mismatch') - value=capsule['payload'] - expected={'binding','cleanup','port','policy_hcl','identity_accessor','task_accessor','phase','revision'} - if type(value) is not dict or set(value)!=expected: - raise Refused('invalid private custody schema') - stored=value['binding'] - if (type(stored) is not dict or set(stored)!=set(asdict(binding)) - or any(type(v) is not str for v in stored.values())): - raise Refused('invalid private custody binding schema') - restored=Binding(**stored) - restored.validate() - if asdict(restored)!=asdict(binding): raise Refused('private custody binding mismatch') - if (type(value['port']) is not int or not 1<=value['port']<=65535 - or type(value['policy_hcl']) is not str or not value['policy_hcl'] - or type(value['identity_accessor']) is not str or not value['identity_accessor'] - or type(value['phase']) is not str or value['phase'] not in {'prepared','mint-intent','issued'} - or type(value['revision']) is not int or value['revision']!={'prepared':0,'mint-intent':1,'issued':2}[value['phase']] - or (value['phase']=='issued' and (type(value['task_accessor']) is not str or not value['task_accessor'])) - or (value['phase']!='issued' and value['task_accessor'] is not None) - or type(value['cleanup']) is not list or len(value['cleanup'])!=3): - raise Refused('invalid private custody schema') - for item in value['cleanup']: - if type(item) is not dict or set(item)!={'kind','call_digest'}: - raise Refused('invalid private cleanup schema') - Cleanup(**item).validate() - expected_cleanup=[{'kind':item['kind'],'call_digest':item['call_digest']} - for item in cleanup_specs(binding.operation_id)] - if value['cleanup']!=expected_cleanup: - raise Refused('private cleanup differs from deterministic owner commitment') - return value - - def _write(self, binding, value, initial=False): - capsule={'payload':value,'mac':hmac.new(self._integrity_key,_encoded(value),hashlib.sha256).hexdigest()} - raw = _encoded(capsule) - if len(raw) > 65536: raise Refused('oversize private custody record') - path = self._path(binding) - if initial: - fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - else: - # Existing owner-private record checked before replacement. Unique - # temp + fsync + replace + directory fsync never truncates old state. - self.read(binding) - tmp = self.directory / ('.pending-' + secrets.token_hex(16)) - fd = os.open(tmp, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600) - try: - with os.fdopen(fd, 'wb', closefd=False) as stream: - stream.write(raw); stream.flush(); os.fsync(fd) - finally: os.close(fd) - if not initial: os.replace(tmp, path) - self._sync() - - def prepare(self, binding, cleanup, port, policy_hcl, identity_accessor): - value = {'binding': asdict(binding), 'cleanup': [asdict(x) for x in cleanup], - 'port': port, 'policy_hcl': policy_hcl, - 'identity_accessor': identity_accessor, 'task_accessor': None, - 'phase': 'prepared', 'revision': 0} - self._write(binding, value, initial=True) - - def checkpoint(self, binding, phase, task_accessor=None): - value = self.read(binding) - if (value['phase'], phase) not in {('prepared', 'mint-intent'), ('mint-intent', 'issued')}: - raise Refused('invalid protected recovery checkpoint') - if phase == 'issued' and (type(task_accessor) is not str or not task_accessor): - raise Refused('protected issued accessor required') - value.update(phase=phase, task_accessor=task_accessor, revision=value['revision'] + 1) - self._write(binding, value) - - def revoke(self, permit): - if type(permit) is not FixtureRecoveryPermit or permit.custody is not self: - raise Refused('exact owner recovery permit required') - uuid.UUID(permit.attempt_id) - path=self.directory / (permit.attempt_id+'.revoked') - fd=os.open(path,os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,0o600) - try:os.fsync(fd) - finally:os.close(fd) - self._sync() - - def admit(self, binding, *, owner_token, server_process, valid_seconds=30): - """Trusted recovery-owner action, not deserialization of caller flags. - - Fresh admission seals the current private custody revision. The owner - supplies its credential and retained backend process separately. Neither - token nor private capsule digest is emitted to the public journal. - """ - value = self.read(binding) - if type(valid_seconds) is not int or not 1 <= valid_seconds <= 60: - raise Refused('bounded recovery admission required') - if type(owner_token) is not str or not owner_token: - raise Refused('independent recovery owner credential required') - return FixtureRecoveryPermit(str(uuid.uuid4()), binding, self, _digest(value), - owner_token, server_process, time.monotonic()+valid_seconds) - - -@dataclass(frozen=True, repr=False) -class FixtureRecoveryPermit: - attempt_id: str - binding: Binding - custody: FixtureRecoveryCustody - private_revision_digest: str = field(repr=False) - owner_token: str = field(repr=False) - server_process: subprocess.Popen = field(repr=False) - deadline: float - - -class _Recorder: - def __init__(self, journal, permit, value): - self.journal, self.permit = journal, permit - b = permit.binding - if journal._admission is not None or journal._consumed_admission is not None: - raise Refused('recovery cannot share a business admission') - if journal.observe(b) is None: raise Refused('original operation absent') - current = journal.cleanup_status(b)['obligations'] - if [(x['kind'], x['call_digest']) for x in current] != [(x['kind'], x['call_digest']) for x in value['cleanup']]: - raise Refused('original cleanup commitment mismatch') - if all(x['outcome'] == 'confirmed' for x in current): - raise Refused('cleanup already confirmed') - self.current = current - # An authenticated older prepared capsule must not erase later issuance - # uncertainty. Without an issued accessor, any native call or state at - # the grant boundary makes absence unprovable. Recovery is conservative. - self.no_mint_proved=(journal.observe(b)['state'] in {'reserved','consume-confirmed','authenticating'} - and journal.db.execute('SELECT COUNT(*) FROM calls WHERE operation=?',(b.operation_id,)).fetchone()[0]==0) - db = journal.db - try: db.execute('INSERT INTO recovery_attempts VALUES (?,?,?)', (permit.attempt_id,b.operation_id,json.dumps(current,sort_keys=True))) - except Exception: raise Refused('recovery admission already used') from None - journal._sync_directory() - - def before(self, ordinal, step, method, path): - digest = _digest({'kind': self.current[ordinal]['kind'], 'commitment':self.current[ordinal]['call_digest'], 'method':method,'path':path,'step':step}) - self.journal.db.execute("INSERT INTO recovery_calls VALUES (?,?,?,?,'pending',NULL)", (self.permit.attempt_id,ordinal,step,digest)) - self.journal._sync_directory() - return digest - - def after(self, ordinal, step, status): - self.journal.db.execute("UPDATE recovery_calls SET outcome='complete', evidence=? WHERE attempt=? AND obligation=? AND step=? AND outcome='pending'", (_digest({'http_status':status}),self.permit.attempt_id,ordinal,step)) - self.journal._sync_directory() - - def finish(self, ordinal, confirmed): - evidence = _digest({'attempt':self.permit.attempt_id,'obligation':ordinal,'confirmed':confirmed}) - db = self.journal.db - try: - db.execute('BEGIN IMMEDIATE') - pending = db.execute("SELECT COUNT(*) FROM recovery_calls WHERE attempt=? AND obligation=? AND outcome='pending'",(self.permit.attempt_id,ordinal)).fetchone()[0] - if confirmed and pending: raise Refused('pending recovery cannot confirm') - db.execute('INSERT INTO recovery_results VALUES (?,?,?,?)',(self.permit.attempt_id,ordinal,int(confirmed),evidence)) - if confirmed: - item=self.current[ordinal] - db.execute("UPDATE cleanup SET outcome='confirmed', evidence=? WHERE operation=? AND ordinal=? AND kind=? AND call_digest=?",(evidence,self.permit.binding.operation_id,ordinal,item['kind'],item['call_digest'])) - db.execute('COMMIT');self.journal._sync_directory() - except BaseException: - if db.in_transaction: db.execute('ROLLBACK') - raise - - -def recover_fixture_cleanup(journal: Journal, permit: FixtureRecoveryPermit): - """Fresh independent owner admission: fixed cleanup only, no business retry. - - Each attempt preserves original pending/unconfirmed rows in recovery_attempts - and native pending call rows unchanged. A fresh owner permit is required for - another attempt after failure; never autonomously retry uncertain mutations. - """ - if type(permit) is not FixtureRecoveryPermit or type(permit.custody) is not FixtureRecoveryCustody: - raise Refused('independent typed recovery admission required') - if str(uuid.UUID(permit.attempt_id)) != permit.attempt_id: - raise Refused('invalid recovery attempt identity') - value = permit.custody.read(permit.binding) - if _digest(value) != permit.private_revision_digest: - raise Refused('private custody revision changed') - proc=permit.server_process - if (type(proc) is not subprocess.Popen or proc.poll() is not None or type(proc.args) is not list - or '-dev' not in proc.args or '-dev-no-store-token' not in proc.args - or '-dev-listen-address=127.0.0.1:'+str(value['port']) not in proc.args): - raise Refused('same live disposable backend required') - def fresh(): - if (permit.custody.directory / (permit.attempt_id+'.revoked')).exists(): - raise Refused('recovery permit revoked') - if time.monotonic() >= permit.deadline: raise Refused('recovery admission expired') - if proc.poll() is not None: raise Refused('disposable backend lost') - if _digest(permit.custody.read(permit.binding)) != permit.private_revision_digest: - raise Refused('private custody changed during recovery') - fresh() - with journal.locked(): - record = _Recorder(journal,permit,value) - for ordinal,item in enumerate(record.current): - if item['outcome']=='confirmed': continue - step=0 - def call(method,path,body=None): - nonlocal step - fresh(); index=step; step+=1 - record.before(ordinal,index,method,path) - conn=http.client.HTTPConnection('127.0.0.1',value['port'],timeout=2) - try: - conn.request(method,'/v1/'+path,body=_encoded(body) if body is not None else None, - headers={'X-Vault-Token':permit.owner_token,'Content-Type':'application/json'}) - response=conn.getresponse();raw=response.read(1048577) - if len(raw)>1048576: raise Refused('oversize recovery response') - data=json.loads(raw,object_pairs_hook=_json_unique,parse_constant=_invalid_constant) if raw else {} - if type(data) is not dict: raise Refused('invalid recovery response') - status=response.status - except Exception: raise Refused('recovery transport or response unconfirmed') from None - finally:conn.close() - record.after(ordinal,index,status) - return status,data - confirmed=False - try: - if item['kind'] in {'task-token-revoke','identity-token-revoke'}: - accessor=value['task_accessor' if ordinal==0 else 'identity_accessor'] - if accessor is None: - confirmed=ordinal==0 and value['phase']=='prepared' and record.no_mint_proved - else: - status,data=call('POST','auth/token/revoke-accessor',{'accessor':accessor}) - # Revoke is idempotent for an already absent accessor; - # actual absence must still be independently read back. - if status in {200,204,400}: - status,data=call('POST','auth/token/lookup-accessor',{'accessor':accessor}) - confirmed=status==400 and data.get('errors') in (['invalid accessor'],['1 error occurred:\n\t* invalid accessor\n\n']) - elif item['kind']=='grant-policy-delete': - path='sys/policies/acl/task-exec-'+permit.binding.operation_id - status,data=call('GET',path) - if _policy_absent(status,data):confirmed=True - elif (status==200 and not data.get('errors') and type(data.get('data')) is dict - and _same_policy(data['data'].get('policy'),value['policy_hcl'])): - status,data=call('DELETE',path) - if status in {200,204}: - status,data=call('GET',path);confirmed=_policy_absent(status,data) - except Exception: - # No backend errors/response/handle hashes enter public evidence. - confirmed=False - record.finish(ordinal,confirmed) - return journal.cleanup_status(permit.binding) diff --git a/scripts/vendor_custodian_allocation_review.py b/scripts/vendor_custodian_allocation_review.py deleted file mode 100644 index cdf677e..0000000 --- a/scripts/vendor_custodian_allocation_review.py +++ /dev/null @@ -1,92 +0,0 @@ -#!/usr/bin/env python3 -"""Vendor or verify the CUST-WP-0071-T05 weekly allocation assessment sources. - -The CronJob mounts its code and data from a ConfigMap generated from -argocd/platform-addons/custodian-allocation-review/src/. Owner sources stay in -their repositories; this script copies them at an exact clean commit and writes -vendor.lock.json (repo, commit, path, sha256). The YAML ownership map is -rendered to JSON so the job needs no PyYAML. - - scripts/vendor_custodian_allocation_review.py # refresh from ~/ - scripts/vendor_custodian_allocation_review.py --check # lock matches src/ -""" -from __future__ import annotations - -import argparse -import hashlib -import json -from pathlib import Path -import subprocess -import sys - -ROOT = Path(__file__).resolve().parents[1] -ADDON = ROOT / "argocd/platform-addons/custodian-allocation-review" -SOURCES = { - "weekly_allocation_incluster.py": ("the-custodian", "scripts/weekly_allocation_incluster.py"), - "weekly_allocation_report.py": ("the-custodian", "scripts/weekly_allocation_report.py"), - "reconcile_railiance_allocation.py": ("the-custodian", "scripts/reconcile_railiance_allocation.py"), - "weekly-allocation-queries.json": ("the-custodian", "config/weekly-allocation-queries.json"), - "namespace-ownership.json": ("the-custodian", "docs/evidence/namespace-ownership.yaml"), - "observe_cluster_resources.py": ("railiance-cluster", "tools/observe_cluster_resources.py"), - "reef-railiance-k3s.json": ("resource-control", "data/resources/reef-railiance-k3s.json"), -} - - -def sha(data: bytes) -> str: - return hashlib.sha256(data).hexdigest() - - -def git(repo: Path, *args: str) -> str: - return subprocess.check_output(["git", "-C", str(repo), *args], text=True).strip() - - -def refresh(base: Path) -> dict: - lock = {"schema": "railiance-platform.vendor-lock.v1", "workplan": "CUST-WP-0071-T05", "files": {}} - (ADDON / "src").mkdir(parents=True, exist_ok=True) - for name, (repo, path) in sorted(SOURCES.items()): - checkout = base / repo - if git(checkout, "status", "--porcelain", "--", path): - raise SystemExit(f"{repo}/{path} has uncommitted changes; commit it first") - commit = git(checkout, "rev-parse", "HEAD") - source = subprocess.check_output(["git", "-C", str(checkout), "show", f"{commit}:{path}"]) - data = source - if path.endswith(".yaml"): - import yaml - data = (json.dumps(yaml.safe_load(source), indent=2, sort_keys=True) + "\n").encode() - (ADDON / "src" / name).write_bytes(data) - lock["files"][name] = {"repo": repo, "commit": commit, "path": path, - "source_sha256": sha(source), "vendored_sha256": sha(data)} - (ADDON / "vendor.lock.json").write_text(json.dumps(lock, indent=2, sort_keys=True) + "\n") - return lock - - -def check() -> list[str]: - lock = json.loads((ADDON / "vendor.lock.json").read_text()) - problems = [] - if set(lock["files"]) != set(SOURCES): - problems.append("lock file set differs from SOURCES") - for name, entry in lock["files"].items(): - target = ADDON / "src" / name - if not target.exists() or sha(target.read_bytes()) != entry["vendored_sha256"]: - problems.append(f"{name}: vendored content does not match vendor.lock.json") - return problems - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("--check", action="store_true") - parser.add_argument("--base", type=Path, default=Path.home(), help="directory holding the owner checkouts") - args = parser.parse_args() - if args.check: - problems = check() - for problem in problems: - print(problem, file=sys.stderr) - return 1 if problems else 0 - lock = refresh(args.base) - for name, entry in sorted(lock["files"].items()): - print(f"{name}: {entry['repo']}@{entry['commit'][:12]} {entry['vendored_sha256'][:12]}") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/verify-backup-pair-versions.py b/scripts/verify-backup-pair-versions.py deleted file mode 100644 index a098223..0000000 --- a/scripts/verify-backup-pair-versions.py +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env python3 -"""Confirm backup-primary version 2 replaced the placeholder. No key material.""" - -from __future__ import annotations - -import json -import subprocess -from pathlib import Path - -RECEIPT = Path("/home/worsch/.local/state/railiance/receipts/backup-pair-version2.json") -PATHS = { - "coulomb-social": "platform/workloads/coulomb-social/backup-primary", - "email-connect": "platform/workloads/email-connect/backup-primary", -} -FIELDS = ("AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY") - - -def kv(path): - proc = subprocess.run(["bao", "kv", "get", "-format=json", path], capture_output=True) - if proc.returncode: - raise SystemExit(61) - return json.loads(proc.stdout)["data"]["data"] - - -def main(): - loaded = {name: kv(path) for name, path in PATHS.items()} - rows = [] - access_keys = [] - for name, values in loaded.items(): - row = {"application": name, "fields": sorted(values)} - for field in FIELDS: - value = values.get(field) - row[field] = { - "present": isinstance(value, str) and bool(value), - "pending": value == "PENDING", - "length": len(value) if isinstance(value, str) else 0, - } - access_keys.append(values.get("AWS_ACCESS_KEY_ID")) - rows.append(row) - receipt = { - "applications": rows, - "access_keys_differ": access_keys[0] != access_keys[1], - "secrets_differ": ( - loaded["coulomb-social"].get("AWS_SECRET_ACCESS_KEY") - != loaded["email-connect"].get("AWS_SECRET_ACCESS_KEY") - ), - } - RECEIPT.parent.mkdir(mode=0o700, exist_ok=True) - RECEIPT.write_text(json.dumps(receipt, indent=2) + "\n") - RECEIPT.chmod(0o600) - - -if __name__ == "__main__": - try: - main() - except SystemExit: - raise - except Exception: - raise SystemExit(62) from None diff --git a/scripts/verify_playwright_access.py b/scripts/verify_playwright_access.py deleted file mode 100644 index eb5360a..0000000 --- a/scripts/verify_playwright_access.py +++ /dev/null @@ -1,94 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only native RBAC checks; impersonation is not consumer login proof.""" -import argparse -from datetime import datetime, timezone -import json -from pathlib import Path -import shlex -import subprocess - - -CLUSTER_UID = 'a553c742-0115-43d4-99a4-a5ca56fe0786' -GROUP = 'playwright-consumers' -ROLE = 'playwright-consumer' - - -def verify(target): - def kubectl(args, body=None): - result = subprocess.run( - ['ssh', '-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', target, - shlex.join(['kubectl', '--request-timeout=10s', *args])], - input=json.dumps(body) if body is not None else None, - capture_output=True, text=True, timeout=20, check=True) - return json.loads(result.stdout) - - uid = kubectl(['get', 'ns', 'kube-system', '-o', 'json'])['metadata']['uid'] - if uid != CLUSTER_UID: - raise RuntimeError('wrong cluster') - binding = kubectl(['get', 'rolebinding', GROUP, '-n', 'playwright', '-o', 'json']) - role = kubectl(['get', 'role', ROLE, '-n', 'playwright', '-o', 'json']) - if binding['subjects'] != [{'apiGroup': 'rbac.authorization.k8s.io', - 'kind': 'Group', 'name': GROUP}]: - raise RuntimeError('consumer binding subjects differ') - if binding['roleRef'] != {'apiGroup': 'rbac.authorization.k8s.io', - 'kind': 'Role', 'name': ROLE}: - raise RuntimeError('consumer binding role differs') - expected = [ - {'apiGroups': [''], 'resources': ['pods'], 'verbs': ['get', 'list']}, - {'apiGroups': [''], 'resources': ['pods/portforward'], 'verbs': ['create']}, - {'apiGroups': ['apps'], 'resources': ['deployments'], - 'resourceNames': ['playwright'], 'verbs': ['get']}, - ] - if role['rules'] != expected: - raise RuntimeError('consumer role rules differ') - cases = [ - (True, 'get', 'pods', '', '', 'playwright', '', True), - (True, 'list', 'pods', '', '', 'playwright', '', True), - (True, 'create', 'pods', 'portforward', '', 'playwright', '', True), - (True, 'get', 'deployments', '', 'apps', 'playwright', 'playwright', True), - (True, 'get', 'deployments', '', 'apps', 'playwright', 'other', False), - (True, 'get', 'secrets', '', '', 'playwright', '', False), - (True, 'create', 'pods', 'exec', '', 'playwright', '', False), - (True, 'create', 'pods', '', '', 'playwright', '', False), - (True, 'patch', 'deployments', '', 'apps', 'playwright', 'playwright', False), - (True, 'create', 'pods', 'portforward', '', 'default', '', False), - (True, 'create', 'rolebindings', '', 'rbac.authorization.k8s.io', 'playwright', '', False), - (False, 'list', 'pods', '', '', 'playwright', '', False), - (False, 'create', 'pods', 'portforward', '', 'playwright', '', False), - ] - checks = [] - for member, verb, resource, subresource, group, namespace, name, expected_allowed in cases: - attributes = dict(verb=verb, resource=resource, subresource=subresource, - group=group, namespace=namespace, name=name) - args = ['--as=rpf-playwright-rbac-probe', '--as-group=system:authenticated'] - if member: - args.append('--as-group=' + GROUP) - # Raw review endpoint avoids kubectl's unrelated CRD-discovery permission. - review = kubectl([*args, 'create', '--raw', - '/apis/authorization.k8s.io/v1/selfsubjectaccessreviews', - '-f', '-'], { - 'apiVersion': 'authorization.k8s.io/v1', 'kind': 'SelfSubjectAccessReview', - 'spec': {'resourceAttributes': attributes}}) - status = review['status'] - if status.get('evaluationError') or type(status.get('allowed')) is not bool: - raise RuntimeError('authorization review incomplete') - checks.append(dict(member=member, attributes=attributes, - expected=expected_allowed, allowed=status['allowed'], - passed=status['allowed'] == expected_allowed)) - return dict(schema='platform-playwright-rbac/v1', - observed_at=datetime.now(timezone.utc).isoformat(), cluster_uid=uid, - role_uid=role['metadata']['uid'], binding_uid=binding['metadata']['uid'], - group=GROUP, checks=checks, passed=all(c['passed'] for c in checks), - actual_consumer_authentication_proven=False) - - -if __name__ == '__main__': - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--target', default='railiance01', choices=['railiance01']) - parser.add_argument('--output', required=True, type=Path) - args = parser.parse_args() - result = verify(args.target) - args.output.write_text(json.dumps(result, indent=2) + '\n') - print(json.dumps({'passed': result['passed'], 'checks': len(result['checks']), - 'evidence': str(args.output)})) - raise SystemExit(0 if result['passed'] else 1) diff --git a/scripts/workload_adoption_proof.py b/scripts/workload_adoption_proof.py deleted file mode 100644 index 96e7fed..0000000 --- a/scripts/workload_adoption_proof.py +++ /dev/null @@ -1,245 +0,0 @@ -#!/usr/bin/env python3 -"""Read-only manual workload adoption proof; never enables automation or runs hooks.""" -from datetime import datetime, timezone -import argparse -import copy -import hashlib -import json -from pathlib import Path -import re -import subprocess -from urllib.request import urlopen - -from argocd_adoption_proof import _kube_route, checked_run, identity, timestamp, tracking_settings - -PIN = 'a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0' -CONTRACT = { - 'estate_ip': '92.205.62.239', 'application': 'target-revenue', - 'project': 'railiance-tenants', 'destination': {'server': 'https://kubernetes.default.svc', 'namespace': 'target-revenue'}, - 'repo_url': 'https://forgejo.coulomb.social/coulomb/target-revenue.git', 'source_path': 'k8s/railiance', - 'revision': PIN, 'tracking_method': 'annotation', 'controller_version': 'v3.5.3', - 'minimum_proving_seconds': 86400, 'maximum_observation_age_seconds': 300, - 'health_url': 'https://revenue.coulomb.social/healthz', - 'resources': [{'group': group, 'kind': kind, 'namespace': 'target-revenue', 'name': name} - for group, kind, name in [('', 'Service', 'target-revenue'), ('apps', 'Deployment', 'target-revenue'), - ('external-secrets.io', 'ExternalSecret', 'target-revenue-runtime'), - ('networking.k8s.io', 'Ingress', 'target-revenue'), ('postgresql.cnpg.io', 'Cluster', 'target-revenue-pg')]], - 'hooks': ['target-revenue-bootstrap-binky', 'target-revenue-migrate'], - 'ready_checks': [{'kind': kind, 'namespace': 'target-revenue', 'name': name, 'condition': condition} - for kind, name, condition in [('Deployment', 'target-revenue', 'Available'), - ('ExternalSecret', 'target-revenue-runtime', 'Ready'), ('Cluster', 'target-revenue-pg', 'Ready')]], -} - - -def validate_contract(contract): - if (not re.fullmatch(r'[a-f0-9]{40}', contract['revision']) or contract['minimum_proving_seconds'] < 86400 - or not 0 < contract['maximum_observation_age_seconds'] <= 300): - raise ValueError('invalid proof bounds') - if any(r['kind'] not in {'Service', 'Deployment', 'ExternalSecret', 'Ingress', 'Cluster'} for r in contract['resources']): - raise ValueError('resource kinds not allowlisted') - if len({identity(r) for r in contract['resources']}) != len(contract['resources']): - raise ValueError('duplicate resource identities') - for r in [*contract['resources'], *contract['ready_checks']]: - if r['kind'] not in {'Service', 'Deployment', 'ExternalSecret', 'Ingress', 'Cluster'}: - raise ValueError('readiness kind not allowlisted') - if any(not re.fullmatch(r'[a-z0-9][a-z0-9.-]*', r[k]) for k in ('name', 'namespace')): - raise ValueError('invalid resource identity') - - -def normalize(items, contract, settings): - validate_contract(contract) - if (settings['method'] != 'annotation' or settings['controller_version'] != contract['controller_version'] - or settings['installation_id_absent'] is not True): - raise ValueError('tracking semantics unverified') - resources, hooks = [], [] - for raw in items: - obj = copy.deepcopy(raw) - meta = obj['metadata'] - annotation = meta.get('annotations', {}) - if 'argocd.argoproj.io/hook' in annotation: - if obj['kind'] != 'Job' or annotation['argocd.argoproj.io/hook'] != 'Sync': - raise ValueError('unsupported hook') - hooks.append(meta['name']) - continue - group = obj['apiVersion'].split('/')[0] if '/' in obj['apiVersion'] else '' - ident = (group, obj['kind'], meta.get('namespace', ''), meta['name']) - if ident not in {identity(r) for r in contract['resources']}: - raise ValueError('render resource not admitted') - tracking = f"{contract['application']}:{group}/{obj['kind']}:{meta['namespace']}/{meta['name']}" - annotations = meta.setdefault('annotations', {}) - if annotations.get('argocd.argoproj.io/tracking-id', tracking) != tracking: - raise ValueError('conflicting source tracking annotation') - annotations['argocd.argoproj.io/tracking-id'] = tracking - resources.append(obj) - if (len(resources) != len(contract['resources']) or len({(r['apiVersion'], r['kind'], r['metadata']['name']) for r in resources}) != len(resources) - or sorted(hooks) != sorted(contract['hooks'])): - raise ValueError('render resource or hook set differs') - return resources - - -def decode_objects(raw): - text = raw.decode() - decoder = json.JSONDecoder() - items = [] - while text.strip(): - value, end = decoder.raw_decode(text.lstrip()) - items.extend(value["items"] if value.get("kind") == "List" else [value]) - text = text.lstrip()[end:] - return items - - -def collect(rendered, contract): - validate_contract(contract) - def get(args): - return json.loads(checked_run(['kubectl', *args, '-o', 'json'])) - nodes = get(['get', 'nodes']) - if contract['estate_ip'] not in [a['address'] for n in nodes['items'] for a in n['status']['addresses']]: - raise ValueError('estate identity unverified') - settings = tracking_settings(contract) - parsed = decode_objects(checked_run(['kubectl', 'create', '--dry-run=client', '--validate=false', '-f', '-', '-o', 'json'], input=rendered)) - desired = normalize(parsed, contract, settings) - payload = json.dumps({'apiVersion': 'v1', 'kind': 'List', 'items': desired}, sort_keys=True).encode() - diff = subprocess.run(['kubectl', 'diff', '-f', '-'], input=payload, capture_output=True, timeout=120) - if diff.returncode not in (0, 1): - raise ValueError('native client diff failed; protected output suppressed') - obj = get(['-n', 'argocd', 'get', 'application', contract['application']]) - spec, status = obj['spec'], obj.get('status', {}) - op = status.get('operationState', {}) - sync = op.get('operation', {}).get('sync', {}) - checks = [] - for check in contract['ready_checks']: - r = get(['-n', check['namespace'], 'get', check['kind'], check['name']]) - ready = [{k: c.get(k) for k in ('status', 'lastTransitionTime')} for c in r.get('status', {}).get('conditions', []) if c.get('type') == check['condition']] - group = r['apiVersion'].split('/')[0] if '/' in r['apiVersion'] else '' - tracking = f"{contract['application']}:{group}/{r['kind']}:{check['namespace']}/{check['name']}" - checks.append({**check, 'uid': r['metadata']['uid'], 'ready': ready, - 'tracking_matches': r['metadata'].get('annotations', {}).get('argocd.argoproj.io/tracking-id') == tracking, - 'observed_generation': r.get('status', {}).get('observedGeneration'), 'generation': r['metadata'].get('generation'), - 'replicas': r.get('status', {}).get('replicas'), 'available_replicas': r.get('status', {}).get('availableReplicas'), - 'instances': r.get('status', {}).get('instances'), 'ready_instances': r.get('status', {}).get('readyInstances')}) - jobs = get(['-n', contract['destination']['namespace'], 'get', 'jobs']) - live_hooks = sorted(r['metadata']['name'] for r in jobs['items'] if r['metadata']['name'] in contract['hooks']) - return {'captured_at': datetime.now(timezone.utc).isoformat(), 'estate_ip': contract['estate_ip'], 'tracking_settings': settings, - 'application': {'name': obj['metadata']['name'], 'uid': obj['metadata']['uid'], 'project': spec.get('project'), - 'source': {k: spec.get('source', {}).get(k) for k in ('repoURL', 'path', 'targetRevision')}, 'destination': spec.get('destination'), - 'automated_present': 'automated' in spec.get('syncPolicy', {}), 'pending_operation': bool(obj.get('operation')), - 'sync': status.get('sync', {}).get('status'), 'health': status.get('health', {}).get('status'), 'revision': status.get('sync', {}).get('revision'), - 'condition_types': [c.get('type') for c in status.get('conditions', [])], - 'operation': {k: op.get(k) for k in ('phase', 'startedAt', 'finishedAt')}, - 'sync_prune': sync.get('prune', False), 'sync_strategy_apply': 'apply' in sync.get('syncStrategy', {}), - 'sync_strategy_force': sync.get('syncStrategy', {}).get('apply', {}).get('force', False), 'operation_revision': sync.get('revision'), - 'resources': [{k: r.get(k) for k in ('group', 'kind', 'namespace', 'name', 'status')} for r in status.get('resources', [])]}, - 'ready_checks': checks, 'live_hooks': live_hooks, - 'pinned_render_diff': {'revision': contract['revision'], 'path': contract['source_path'], 'client_side_exit_code': diff.returncode, - 'render_sha256': hashlib.sha256(rendered).hexdigest(), 'normalized_render_sha256': hashlib.sha256(payload).hexdigest(), - 'render_resource_count': len(desired), 'skipped_hooks': sorted(contract['hooks']), 'output_suppressed': True}} - - -def verify(observation, contract, now=None): - validate_contract(contract) - now = now or datetime.now(timezone.utc) - captured = timestamp(observation['captured_at']) - if not 0 <= (now - captured).total_seconds() <= contract['maximum_observation_age_seconds']: - raise ValueError('observation stale or future') - if not -5 <= (captured - timestamp(observation['metadata_captured_at'])).total_seconds() <= contract['maximum_observation_age_seconds']: - raise ValueError('metadata observation stale or clock skew exceeds bound') - settings = observation['tracking_settings'] - if (observation['estate_ip'] != contract['estate_ip'] or settings.get('method') != contract['tracking_method'] - or settings.get('controller_version') != contract['controller_version'] or settings.get('installation_id_absent') is not True): - raise ValueError('estate or tracking semantics unverified') - app = observation['application'] - if (app['name'] != contract['application'] or not app['uid'] or app['project'] != contract['project'] or app['destination'] != contract['destination'] - or app['source'] != {'repoURL': contract['repo_url'], 'path': contract['source_path'], 'targetRevision': contract['revision']} - or app['revision'] != contract['revision']): - raise ValueError('source or application identity mismatch') - if (app['automated_present'] is not False or app['pending_operation'] is not False or app['sync'] != 'Synced' or app['health'] != 'Healthy' - or any(c != 'OrphanedResourceWarning' for c in app['condition_types'])): - raise ValueError('manual idle healthy adoption unverified') - op = app['operation'] - if (op['phase'] != 'Succeeded' or app['sync_prune'] is not False or app['sync_strategy_apply'] is not True or app['sync_strategy_force'] is not False or app['operation_revision'] != contract['revision']): - raise ValueError('pinned manual apply strategy unverified') - start, finish = timestamp(op['startedAt']), timestamp(op['finishedAt']) - proof_age = (captured - finish).total_seconds() - if finish < start or proof_age < contract['minimum_proving_seconds']: - raise ValueError('completed 24h proving period unverified') - expected = {identity(r) for r in contract['resources']} - if (len(app['resources']) != len(expected) or {identity(r) for r in app['resources']} != expected or any(r['status'] != 'Synced' for r in app['resources'])): - raise ValueError('exact synced resource set unverified') - checks = observation['ready_checks'] - expected_checks = {(c['kind'], c['namespace'], c['name'], c['condition']) for c in contract['ready_checks']} - if len(checks) != len(expected_checks) or {(c['kind'], c['namespace'], c['name'], c['condition']) for c in checks} != expected_checks: - raise ValueError('readiness checks incomplete') - for c in checks: - if not c['uid'] or c['tracking_matches'] is not True or len(c['ready']) != 1 or c['ready'][0]['status'] != 'True': - raise ValueError('tracked resource readiness unverified') - if timestamp(c['ready'][0]['lastTransitionTime']) > start: - raise ValueError('readiness changed during proving period') - if c['kind'] == 'Deployment' and (c['observed_generation'] != c['generation'] or not c['replicas'] or c['available_replicas'] != c['replicas']): - raise ValueError('deployment availability incomplete') - if c['kind'] == 'Cluster' and (not c['instances'] or c['ready_instances'] != c['instances']): - raise ValueError('CNPG readiness incomplete') - diff = observation['pinned_render_diff'] - if (diff.get('revision') != contract['revision'] or diff.get('path') != contract['source_path'] or type(diff.get('client_side_exit_code')) is not int - or diff['client_side_exit_code'] != 0 or diff.get('render_resource_count') != len(expected) - or diff.get('skipped_hooks') != sorted(contract['hooks']) or diff.get('output_suppressed') is not True - or any(not re.fullmatch(r'[a-f0-9]{64}', diff.get(k, '')) for k in ('render_sha256', 'normalized_render_sha256'))): - raise ValueError('pinned normalized client zero-diff unverified') - if observation.get('live_hooks') != [] or observation.get('public_health') != {'url': contract['health_url'], 'status': 200}: - raise ValueError('hook absence or public health unverified') - return {'schema_version': 'argocd-workload-adoption-proof/v1', 'status': 'verified', 'application': contract['application'], - 'revision': contract['revision'], 'captured_at': observation['captured_at'], 'verified_at': now.isoformat(), - 'proving_seconds': proof_age, 'resource_count': len(expected), 'ready_count': len(checks), - 'manual_apply_prune_off': True, 'hooks_skipped': sorted(contract['hooks']), 'hooks_absent': True, - 'child_automation_enabled': False, 'public_health_status': 200, 'native_mutation': False, - 'verifier_sha256': hashlib.sha256(Path(__file__).read_bytes()).hexdigest(), - 'tracking_helper_sha256': hashlib.sha256(Path(__file__).with_name('argocd_adoption_proof.py').read_bytes()).hexdigest(), - 'contract_sha256': hashlib.sha256(json.dumps(contract, sort_keys=True).encode()).hexdigest(), - 'observation_sha256': hashlib.sha256(json.dumps(observation, sort_keys=True).encode()).hexdigest()} - - -def remote_program(common, rendered): - import base64 - return ('import sys,types,base64,json\n' + 'm=types.ModuleType("argocd_adoption_proof");sys.modules[m.__name__]=m\n' - + 'exec(compile(base64.b64decode(' + repr(base64.b64encode(common).decode()) + '),"argocd_adoption_proof.py","exec"),m.__dict__)\n' - + 'n={"__name__":"workload_proof"};exec(compile(base64.b64decode(' + repr(base64.b64encode(Path(__file__).read_bytes()).decode()) - + '),"workload_adoption_proof.py","exec"),n)\n' - + 'try:\n print(json.dumps(n["collect"](base64.b64decode(' + repr(base64.b64encode(rendered).decode()) + '),n["CONTRACT"]),sort_keys=True))\n' - + 'except Exception:\n raise SystemExit("adoption observation incomplete; protected output suppressed")\n').encode() - - -def main(): - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--live', action='store_true') - parser.add_argument('--source-root', type=Path) - parser.add_argument('--observation', type=Path, required=True) - parser.add_argument('--receipt', type=Path, required=True) - args = parser.parse_args() - try: - if args.live: - if not args.source_root: - raise ValueError('source root required') - import tempfile - with tempfile.TemporaryDirectory(prefix='workload-adoption-pin-') as temp: - archive = checked_run(['git', '-C', str(args.source_root), 'archive', CONTRACT['revision'], CONTRACT['source_path']]) - checked_run(['tar', '-x', '-C', temp], input=archive) - rendered = checked_run(['kubectl', 'kustomize', str(Path(temp) / CONTRACT['source_path'])]) - common = Path(__file__).with_name('argocd_adoption_proof.py').read_bytes() - observation = json.loads(checked_run(_kube_route().ssh_argv(['python3', '-']), input=remote_program(common, rendered), timeout=180)) - with urlopen(CONTRACT['health_url'], timeout=30) as response: - observation['public_health'] = {'url': response.geturl(), 'status': response.status} - observation['metadata_captured_at'] = observation['captured_at'] - observation['captured_at'] = datetime.now(timezone.utc).isoformat() - args.observation.write_text(json.dumps(observation, indent=2) + '\n') - else: - observation = json.loads(args.observation.read_text()) - receipt = verify(observation, CONTRACT) - except Exception: - receipt = {'schema_version': 'argocd-workload-adoption-proof/v1', 'status': 'unverified', - 'error': 'adoption proof incomplete; protected output suppressed', 'native_mutation': False} - args.receipt.write_text(json.dumps(receipt, indent=2) + '\n') - print(json.dumps(receipt, sort_keys=True)) - return 0 if receipt['status'] == 'verified' else 1 - - -if __name__ == '__main__': - raise SystemExit(main()) diff --git a/scripts/workload_kv_write_next_version.py b/scripts/workload_kv_write_next_version.py deleted file mode 100644 index 7e6e380..0000000 --- a/scripts/workload_kv_write_next_version.py +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python3 -"""workload-kv-write-next-version CCRs: a workload writes the next version of one KV entry. - -NetKingdom NK-WP-0056 (bounded executor, hub request 9d08288d). A non-human -executor rotates one existing value under a founder admission: it reads the -current version, writes the next version with CAS on that version, and may -revert by writing the prior value as a further version. It never creates a -path, reads metadata, deletes, destroys or lists. - -The contract is source-only: validation, the exact policy HCL and an attended -procedure note. The policy, Kubernetes auth role and key-level ``cas_required`` -are applied by the attended platform-admin from a reviewed commit, as with -attended-service-credential-delivery. Nothing here calls OpenBao or Kubernetes, -or reads, prints or accepts a secret value. -""" -from __future__ import annotations - -import re -from typing import Any - -REQUEST_TYPE = "workload-kv-write-next-version" -DOC = "docs/credential-lane-designs/bounded-executor.md" -APPROVED = {"approved", "applied", "verified", "active"} -KV_RE = re.compile(r"^platform/workloads/[a-z0-9][a-z0-9-]*(/[a-z0-9][a-z0-9-]*)+$") -NAME_RE = re.compile(r"^[a-z0-9][a-z0-9-]{0,62}$") -FIELD_RE = re.compile(r"^[A-Z][A-Z0-9_]*$") -OPENBAO_KEYS = {"mount", "kv_path", "fields", "policy_name", "policy_file", "write", "auth"} -WRITE = {"mode": "next-version", "cas_required": True, "create": False, "metadata_read": False, - "delete": False, "token_self_lifecycle": True} -TTL = "15m" -AUTH_FIXED = {"method": "kubernetes", "mount": "kubernetes", "audience": "openbao", "ttl": TTL, - "token_max_ttl": TTL, "token_explicit_max_ttl": TTL, "token_no_default_policy": True} -DISALLOWED_POLICIES = {"root", "default", "platform-admin"} - - -def policy_hcl(ccr: dict[str, Any]) -> str: - """Exact policy: read and update of one data path; no create, metadata, delete or list. - - No required_parameters: OpenBao applies it to reads as well, so a GET (which carries no `options`) is - refused and the executor cannot learn the version it must name (NK-WP-0056-T12 synthetic pilot, - 2026-10-31). The key's cas_required is what refuses a write without options.cas.""" - openbao = ccr["openbao"] - suffix = openbao["kv_path"][len("platform/"):] - return (f'path "platform/data/{suffix}" {{\n' - ' capabilities = ["read", "update"]\n' - ' allowed_parameters = {\n "data" = []\n "options" = []\n }\n' - "}\n\n" - 'path "auth/token/lookup-self" {\n capabilities = ["read"]\n}\n\n' - 'path "auth/token/revoke-self" {\n capabilities = ["update"]\n}\n') - - -def _approvals(ccr: dict[str, Any]) -> set[str]: - comments = (ccr.get("review") or {}).get("comments") or [] - return {str(c.get("reviewer", "")).removeprefix("User (").removesuffix(")") - for c in comments if c.get("decision") == "approved"} - - -def validate_contract(ccr: dict[str, Any]) -> list[str]: - errors: list[str] = [] - if ccr.get("request_type") != REQUEST_TYPE: - errors.append("unsupported write-next-version contract") - openbao = ccr.get("openbao") - if not isinstance(openbao, dict): - return errors + ["openbao must be an object"] - extra = set(openbao) - OPENBAO_KEYS - if extra: - errors.append("openbao declares unsupported keys: " + ", ".join(sorted(extra))) - kv_path = str(openbao.get("kv_path", "")) - if openbao.get("mount") != "platform" or not KV_RE.match(kv_path): - errors.append("openbao.kv_path must be one exact platform/workloads/... path on mount platform") - fields = openbao.get("fields") - if not isinstance(fields, list) or not fields or not all(isinstance(f, str) and FIELD_RE.match(f) for f in fields): - errors.append("openbao.fields must list upper-case field names") - policy = openbao.get("policy_name") - if not isinstance(policy, str) or not NAME_RE.match(policy) or policy in DISALLOWED_POLICIES: - errors.append("openbao.policy_name must be a dedicated lower-case policy name") - elif openbao.get("policy_file") != f"openbao/policies/{policy}.hcl": - errors.append("openbao.policy_file must be openbao/policies/.hcl") - if openbao.get("write") != WRITE: - errors.append("openbao.write must be next-version with cas_required, no create, metadata or delete, " - "and token self-lifecycle") - auth = openbao.get("auth") - if not isinstance(auth, dict): - errors.append("openbao.auth must be an object") - else: - for key, value in AUTH_FIXED.items(): - if auth.get(key) != value: - errors.append(f"openbao.auth.{key} must be {value}") - if not NAME_RE.match(str(auth.get("role", ""))): - errors.append("openbao.auth.role must be a dedicated role name") - if auth.get("policies") != [policy]: - errors.append("openbao.auth.policies must contain exactly openbao.policy_name") - claims = auth.get("bound_claims") or {} - for key in ("service_account_names", "service_account_namespaces"): - values = claims.get(key) - if not (isinstance(values, list) and len(values) == 1 and NAME_RE.match(str(values[0]))): - errors.append(f"openbao.auth.bound_claims.{key} must name exactly one value") - admission = ccr.get("admission") - if not isinstance(admission, dict) or not admission.get("standard") or not admission.get("executor_identity"): - errors.append("admission must name the admission standard and executor identity") - if (ccr.get("access_frontdoor") or {}).get("resolvable") is not False: - errors.append("runtime credential frontdoor must remain disabled") - if (ccr.get("agent_fence") or {}).get("data_and_metadata_denied") is not True: - errors.append("coding agents must be fenced from the data and metadata paths") - if ccr.get("status") in APPROVED: - approved = _approvals(ccr) - for role in (ccr.get("review") or {}).get("required_approvers") or []: - if role not in approved: - errors.append("explicit role approval missing: " + role) - for section, keys in {"verification": ["positive", "negative", "activation_conditions"], - "lifecycle": ["deactivate", "rotate", "compromised"]}.items(): - for key in keys: - if not (ccr.get(section) or {}).get(key): - errors.append(f"{section}.{key} required") - return errors - - -def procedure_note(ccr: dict[str, Any]) -> str: - openbao = ccr["openbao"] - return (f"Attended platform-admin applies policy {openbao['policy_name']} and Kubernetes role " - f"{openbao['auth']['role']} from a reviewed commit, sets cas_required on {openbao['kv_path']} " - f"and proves the negatives ({DOC}). The workload may only write the next version with CAS.") diff --git a/scripts/wp0024-owner-review.py b/scripts/wp0024-owner-review.py index d74dd5c..7e1f8b7 100644 --- a/scripts/wp0024-owner-review.py +++ b/scripts/wp0024-owner-review.py @@ -152,15 +152,7 @@ def check_command(check_id: str) -> list[str]: if check_id == "database-lease-preflight": return [sys.executable, "scripts/audit-core-recovery-preflight.py", "database-lease"] if check_id == "node-reboot-preflight": - # Procedure review lists the known stuck drill PVC explicitly; resolving it - # stays a rapp-postgres pre-window gate in the reboot runbook. - return [ - sys.executable, - "scripts/audit-core-recovery-preflight.py", - "node-reboot", - "--accept-terminating-pvc", - "platform-pg-drill/platform-pg-drill-1", - ] + return [sys.executable, "scripts/audit-core-recovery-preflight.py", "node-reboot"] raise ReviewError(f"unknown check id: {check_id}") diff --git a/tests/openbao_restore_rehearsal.py b/tests/openbao_restore_rehearsal.py deleted file mode 100644 index 8586452..0000000 --- a/tests/openbao_restore_rehearsal.py +++ /dev/null @@ -1,291 +0,0 @@ -"""Synthetic-material rehearsal fixture for RPF-WP-0064-T04. - -Everything here is generated per run: SSH admission key, age identity, Shamir -shares, root credentials and the KV value. Source containers run the pinned -OpenBao image with ``--network none`` and tmpfs storage only and are removed. -No real snapshot, key, share or cluster is touched. -""" -from __future__ import annotations - -import importlib.util -import json -import os -import secrets -import shutil -import subprocess -import tempfile -import time -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -REHEARSAL_IMAGE = "quay.io/openbao/openbao:2.5.4" - - -def load(name: str, rel: str): - spec = importlib.util.spec_from_file_location(name, ROOT / rel) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -adapter = load("restore_adapter", "scripts/openbao-isolated-restore/restore_adapter.py") -VERIFIER = ROOT / "scripts/openbao-isolated-restore/verify-restore-receipt.py" - - -def sh(argv, **kw) -> subprocess.CompletedProcess: - kw.setdefault("capture_output", True) - kw.setdefault("timeout", 120) - return subprocess.run(argv, **kw) - - -def docker_status() -> tuple[bool, str]: - if not shutil.which("docker") or not shutil.which("age") or not shutil.which("ssh-keygen"): - return False, "docker/age/ssh-keygen unavailable" - if sh(["docker", "version", "--format", "{{.Server.Version}}"], timeout=20).returncode != 0: - return False, "docker daemon unavailable" - inspect = sh(["docker", "image", "inspect", REHEARSAL_IMAGE, "--format", "{{json .RepoDigests}}"], timeout=20) - if inspect.returncode != 0: - return False, f"{REHEARSAL_IMAGE} not pulled locally (make openbao-isolated-restore-image)" - digests = [d for d in json.loads(inspect.stdout) if d.startswith(adapter.IMAGE_REPOSITORY + "@")] - return (True, digests[0]) if digests else (False, "image has no repository digest") - - -def make_source_root(base: Path, mutate: dict[str, bytes] | None = None) -> tuple[Path, str]: - """A clean git checkout holding exactly the adapter source closure.""" - root = base / ("src-" + secrets.token_hex(3)) - for name in adapter.SOURCE_PATHS: - target = root / name - target.parent.mkdir(parents=True, exist_ok=True) - target.write_bytes((mutate or {}).get(name, (ROOT / name).read_bytes())) - env = {**os.environ, "GIT_AUTHOR_NAME": "rehearsal", "GIT_AUTHOR_EMAIL": "rehearsal@invalid", - "GIT_COMMITTER_NAME": "rehearsal", "GIT_COMMITTER_EMAIL": "rehearsal@invalid"} - for argv in (["git", "init", "-q"], ["git", "add", "-A"], - ["git", "-c", "core.hooksPath=/dev/null", "commit", "-q", "-m", "rehearsal closure"]): - done = sh(argv, cwd=root, env=env) - assert done.returncode == 0, done.stderr - commit = sh(["git", "rev-parse", "HEAD"], cwd=root).stdout.decode().strip() - return root, commit - - -# The source mirrors production (rapp-openbao chart setNodeId: the pod name), -# built independently of the adapter's target config. A rehearsal with a target -# node id differing from this voter still restored and elected a leader, so the -# node id alone did not explain T07 attempt -01; it stays aligned with production. -PRODUCTION_NODE_ID = "openbao-0" -# It also declares production's file audit device (rapp-openbao values), so the -# snapshot's audit table re-enables it on the target after unseal. -PRODUCTION_AUDIT_PATH = "/openbao/audit/openbao-audit.log" -SOURCE_CONFIG = json.dumps({**json.loads(adapter.SERVER_CONFIG), - "storage": {"raft": {"path": "/openbao/data", "node_id": PRODUCTION_NODE_ID}}, - "audit": {"file": {"file": {"description": "rehearsal mirror of production", - "options": {"file_path": PRODUCTION_AUDIT_PATH}}}}}, - separators=(",", ":")) - - -class SyntheticBao: - """A generated-material OpenBao in a networkless container (source side).""" - - def __init__(self, image: str): - self.image = image - self.name = "rpf64-rehearsal-src-" + secrets.token_hex(4) - self.credential = "" - self.shares: list[str] = [] - - def __enter__(self): - argv = ["docker", "run", "--detach", "--pull=never", "--name", self.name, "--network", "none", - "--user", "100:1000", "--read-only", "--cap-drop", "ALL", "--security-opt", "no-new-privileges"] - for target, options in adapter.TMPFS.items(): - argv += ["--tmpfs", f"{target}:{options}"] - argv += ["-e", "BAO_LOCAL_CONFIG=" + SOURCE_CONFIG, "-e", "BAO_ADDR=http://127.0.0.1:8200", - self.image, "server"] - assert sh(argv).returncode == 0 - try: - return self._start() - except BaseException: - sh(["docker", "rm", "-f", "-v", self.name]) - raise - - def _start(self): - for _ in range(60): - if self.bao(["status", "-format=json"]).returncode in (0, 2): - break - time.sleep(0.5) - init = json.loads(self.bao(["operator", "init", "-key-shares=3", "-key-threshold=2", - "-format=json"], check=True).stdout) - self.shares, self.credential = init["unseal_keys_b64"], init["root_token"] - for share in self.shares[:2]: - self.bao(["write", "sys/unseal", "-"], body=json.dumps({"key": share}).encode(), check=True) - for _ in range(60): - leader = self.bao(["read", "-format=json", "sys/leader"]) - if leader.returncode == 0 and json.loads(leader.stdout)["data"].get("is_self"): - break - time.sleep(0.5) - audits = json.loads(self.bao(["audit", "list", "-format=json"], auth=True, check=True).stdout) - assert any(a.get("type") == "file" and a.get("options", {}).get("file_path") == PRODUCTION_AUDIT_PATH - for a in audits.values()), "source must carry production's file audit device" - return self - - def __exit__(self, *exc): - sh(["docker", "rm", "-f", "-v", self.name]) - - def bao(self, args, *, body: bytes = b"", auth: bool = False, check: bool = False): - script = 'IFS= read -r t; BAO_TOKEN="$t"; export BAO_TOKEN; exec "$@"' if auth else 'exec "$@"' - data = (self.credential.encode() + b"\n" + body) if auth else body - done = sh(["docker", "exec", "-i", self.name, "sh", "-c", script, "sh", "bao", *args], input=data) - if check: - assert done.returncode == 0, done.stderr.decode()[-400:] - return done - - def put(self, path: str, payload: dict): - self.bao(["write", path, "-"], body=json.dumps(payload).encode(), auth=True, check=True) - - def seed(self, *, kv_registration: bool = True, jwt_role: bool = True) -> None: - """Mirror the real source shapes (T01) with generated values only.""" - src = lambda rel: json.loads((ROOT / rel).read_bytes()) # noqa: E731 - self.bao(["auth", "enable", "-path=keycape-services", "jwt"], auth=True, check=True) - self.bao(["auth", "enable", "-path=netkingdom", "oidc"], auth=True, check=True) - self.bao(["secrets", "enable", "-path=platform", "kv-v2"], auth=True, check=True) - self.put("auth/keycape-services/config", - {**src("openbao/auth/keycape-services-config.json"), "skip_jwks_validation": True}) - if jwt_role: - self.put("auth/keycape-services/role/secrets-engine-openbao", - src("openbao/auth/secrets-engine-jwt-role.json")) - oidc = {k: v for k, v in src("openbao/auth/netkingdom-oidc-config.json").items() if k != "_note"} - self.put("auth/netkingdom/config", {**oidc, "oidc_client_secret": "synthetic-" + secrets.token_hex(8), - "skip_jwks_validation": True}) - self.put("auth/netkingdom/role/platform-admin", src("openbao/auth/netkingdom-platform-admin-role.json")) - policy = (ROOT / "openbao/policies/secrets-engine-login-self.hcl").read_bytes() - self.bao(["policy", "write", "secrets-engine-login-self", "-"], body=policy, auth=True, check=True) - if kv_registration: - self.kv_value = "synthetic-kv-" + secrets.token_urlsafe(24) - self.put("platform/data/workloads/keycape/secrets-engine-openbao", - {"data": {"client_credential": self.kv_value}}) - - def facts(self) -> tuple[str, int]: - status = json.loads(self.bao(["read", "-format=json", "sys/seal-status"], check=True).stdout)["data"] - leader = json.loads(self.bao(["read", "-format=json", "sys/leader"], check=True).stdout)["data"] - return status["cluster_id"], leader["raft_applied_index"] - - def snapshot(self) -> bytes: - done = self.bao(["operator", "raft", "snapshot", "save", "/dev/stdout"], auth=True, check=True) - assert done.stdout[:2] == b"\x1f\x8b" - return done.stdout - - -class Rehearsal: - """Generated custody, admission signing and session plumbing.""" - - def __init__(self, image: str): - self.image = image - self.work = Path(tempfile.mkdtemp(prefix="rpf64-rehearsal-", dir="/dev/shm")) - os.chmod(self.work, 0o700) - self.anchor = self.work / "anchor" - self.anchor.mkdir(mode=0o700) - key = self.work / "founder_ed25519" - assert sh(["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "founder@rehearsal", "-f", - str(key)]).returncode == 0 - self.ssh_key = key - pub = (self.work / "founder_ed25519.pub").read_text().strip() - (self.anchor / "allowed_signers").write_text( - f'founder@rehearsal namespaces="{adapter.NAMESPACE}" {pub}\n') - os.environ[adapter.ANCHOR_ENV] = str(self.anchor / "allowed_signers") - self.age_identity, self.age_recipient = self.age_pair() - self.secrets_seen: list[str] = [self.age_identity] - - @staticmethod - def age_pair() -> tuple[str, str]: - out = sh(["age-keygen"]).stdout.decode().splitlines() - identity = next(line for line in out if line.startswith("AGE-SECRET-KEY-1")) - recipient = sh(["age-keygen", "-y"], input=identity.encode()).stdout.decode().strip() - return identity, recipient - - def close(self) -> None: - os.environ.pop(adapter.ANCHOR_ENV, None) - shutil.rmtree(self.work, ignore_errors=True) - - def snapshot_set(self, *, kv_registration=True, jwt_role=True) -> dict: - with SyntheticBao(self.image) as bao: - bao.seed(kv_registration=kv_registration, jwt_role=jwt_role) - cluster_id, index = bao.facts() - plain = bao.snapshot() - shares, root_credential = list(bao.shares), bao.credential - self.secrets_seen += [bao.credential, *shares, getattr(bao, "kv_value", "")] - cipher = sh(["age", "-r", self.age_recipient], input=plain).stdout - path = self.work / ("snapshot-" + secrets.token_hex(3) + ".age") - fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "wb") as out: - out.write(cipher) - return {"path": path, "cipher": cipher, "plain_sha": adapter.sha256_bytes(plain), "cluster_id": cluster_id, - "index": index, "shares": shares, "root_credential": root_credential} - - def admission(self, snap: dict, source_root: Path, commit: str, **override) -> dict: - now = adapter.now_utc() - adm = { - "schema": adapter.ADMISSION_SCHEMA, "namespace": adapter.NAMESPACE, - "attempt_id": "RPF-WP-0064-T04-rehearsal-" + secrets.token_hex(3), "phase": "A", - "phase_b_admitted": False, "material": "synthetic", - "not_before": adapter.iso(now.replace(second=0) - __import__("datetime").timedelta(minutes=1)), - "not_after": adapter.iso(now + __import__("datetime").timedelta(hours=1)), - "max_duration_seconds": adapter.MAX_DURATION_SECONDS, "abort_operator": "founder", - "image": self.image, "adapter_commit": commit, - "snapshot": {"ciphertext_sha256": adapter.sha256_bytes(snap["cipher"]), - "ciphertext_bytes": len(snap["cipher"]), "plaintext_sha256": snap["plain_sha"], - "age_recipient": self.age_recipient, "source_cluster_id": snap["cluster_id"], - "source_applied_index": snap["index"], - "source_receipt_sha256": adapter.sha256_bytes(b"synthetic-source-receipt" + os.urandom(8))}, - "unseal": {"key_threshold": 2, "key_shares": 3}, - "readback_credential": "generate-root-from-entered-shares", - "source_sha256": adapter.source_digests(source_root), - } - for key, value in override.items(): - if key in adm["snapshot"]: - adm["snapshot"][key] = value - else: - adm[key] = value - return adm - - def signed(self, adm: dict) -> tuple[Path, Path, str]: - raw = json.dumps(adm, indent=2, sort_keys=True).encode() - path = self.work / (adm["attempt_id"] + ".json") - fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600) - with os.fdopen(fd, "wb") as out: - out.write(raw) - done = sh(["ssh-keygen", "-q", "-Y", "sign", "-f", str(self.ssh_key), "-n", adapter.NAMESPACE, str(path)]) - assert done.returncode == 0, done.stderr - sig = Path(str(path) + ".sig") - os.chmod(sig, 0o600) - return path, sig, adapter.sha256_bytes(raw) - - def drill_admission(self, snap: dict, source_root: Path, commit: str, **override) -> dict: - """RPF-WP-0064-T10 drill admission: same snapshot facts, disjoint attempt id and fields.""" - adm = self.admission(snap, source_root, commit, **override) - adm.update({"attempt_id": "RPF-WP-0064-T10-rehearsal-" + secrets.token_hex(3), "drill": "break-glass", - "snapshot_label": "openbao-raft-20261014t000000z-synthetic.snap.age", - "readback_credential": adapter.DRILL_READBACK}) - return adm - - def session(self, adm: dict, snap: dict, source_root: Path, answers: list, **kw): - path, sig, digest = self.signed(adm) - mode = kw.get("mode", adapter.PHASE_A) - loaded, _raw, signer, protected = adapter.load_admission(path, sig, digest, Path(os.environ[adapter.ANCHOR_ENV]), - mode) - queue = list(answers) - - def prompt(_label: str) -> bytearray: - item = queue.pop(0) - value = item() if callable(item) else item - return bytearray(value.encode()) - receipt = self.work / (adm["attempt_id"] + "-receipt.json") - session = adapter.Session(admission=loaded, admission_sha256=digest, signer=signer, - anchor_is_protected=protected, ciphertext=snap["path"], receipt_path=receipt, - root=source_root, prompt=prompt, say=lambda _m: None, **kw) - return session, receipt - - @staticmethod - def verify(receipt: Path, source_root: Path, *extra: str) -> subprocess.CompletedProcess: - return sh(["python3", str(VERIFIER), "--receipt", str(receipt), "--source-root", str(source_root), *extra]) - - -def leftover_containers() -> list[str]: - done = sh(["docker", "ps", "-a", "--format", "{{.Names}}", "--filter", "name=rpf64-"]) - return [n for n in done.stdout.decode().split() if n] diff --git a/tests/probe_bao_cli_synthetic.py b/tests/probe_bao_cli_synthetic.py deleted file mode 100644 index 70cb4c4..0000000 --- a/tests/probe_bao_cli_synthetic.py +++ /dev/null @@ -1,22 +0,0 @@ -import hashlib,json,os,subprocess,threading -from http.server import BaseHTTPRequestHandler,HTTPServer -captured=[] -class Handler(BaseHTTPRequestHandler): - def log_message(self,*args):pass - def do_GET(self): - if '/metadata/' in self.path: - self.send_response(404);self.end_headers();self.wfile.write(b'{"errors":[]}');return - self.send_response(200);self.end_headers();self.wfile.write(b'{"data":{"path":"platform/","type":"kv","options":{"version":"2"}}}') - def do_POST(self):self.do_PUT() - def do_PUT(self): - body=self.rfile.read(int(self.headers.get('Content-Length','0')));captured.append((self.command,self.path,json.loads(body or b'{}'))) - if self.path=='/v1/sys/wrapping/unwrap':self.send_response(400);self.end_headers();self.wfile.write(b'{"errors":["wrapping token is not valid or does not exist"]}') - else:self.send_response(200);self.end_headers();self.wfile.write(b'{"data":{"version":1}}') - def do_LIST(self):self.send_response(404);self.end_headers();self.wfile.write(b'{"errors":[]}') -server=HTTPServer(('127.0.0.1',0),Handler);threading.Thread(target=server.serve_forever,daemon=True).start() -env={**os.environ,'BAO_ADDR':'http://127.0.0.1:'+str(server.server_port),'VAULT_ADDR':'http://127.0.0.1:'+str(server.server_port),'BAO_TOKEN':'SYNTHETIC_NON_SECRET_FIXTURE','VAULT_TOKEN':'SYNTHETIC_NON_SECRET_FIXTURE'} -put=subprocess.run(['bao','kv','put','-format=json','-cas=0','platform/operators/argocd/repositories/rapp-issue-core','SSH_PRIVATE_KEY=-','REPOSITORY=coulomb/rapp-issue-core'],input='SYNTHETIC_KEY_FIXTURE\n',text=True,env=env,capture_output=True) -listing=subprocess.run(['bao','list','-format=json','platform/metadata/operators/argocd/repositories/'],env=env,capture_output=True,text=True) -unwrap=subprocess.run(['bao','unwrap','-format=json'],env=env,capture_output=True,text=True) -server.shutdown() -print(json.dumps({'synthetic_only':True,'installed_cli_kv_put_exit':put.returncode,'stdin_field_exact':any(item[1]=='/v1/platform/data/operators/argocd/repositories/rapp-issue-core' and item[2].get('data',{}).get('SSH_PRIVATE_KEY')=='SYNTHETIC_KEY_FIXTURE\n' and item[2].get('options',{}).get('cas')==0 for item in captured),'list_404_exit':listing.returncode,'list_404_safe_error':(listing.stdout+listing.stderr).strip(),'unwrap_400_exit':unwrap.returncode,'unwrap_400_safe_error':(unwrap.stdout+unwrap.stderr).strip()},indent=2)) diff --git a/tests/task_execution_source_servers.py b/tests/task_execution_source_servers.py deleted file mode 100644 index 3570382..0000000 --- a/tests/task_execution_source_servers.py +++ /dev/null @@ -1,206 +0,0 @@ -"""Real Approval/Access servers; every admission/key/identity is synthetic local. - -The positive fixture policy is separately named and exact-descriptor scoped. It -never edits either existing unadmitted production task policy or their false gate. -""" -import base64 -from contextlib import contextmanager -from dataclasses import replace -from datetime import datetime,timedelta,timezone -import hashlib -from http.client import HTTPConnection -from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer -import json -import os -from pathlib import Path -import shutil -import socket -import subprocess -import threading -import time -from types import SimpleNamespace -from wsgiref.simple_server import make_server,WSGIRequestHandler - -from cryptography import x509 -from cryptography.hazmat.primitives import serialization,hashes -from cryptography.hazmat.primitives.asymmetric import ed25519,rsa -from cryptography.x509.oid import NameOID - -ACCESS=Path(os.environ.get('ACCESS_ENGINE_SOURCE_ROOT','/home/worsch/access-engine')) -APPROVAL=Path(os.environ.get('APPROVAL_ENGINE_SOURCE_ROOT','/home/worsch/approval-engine')) -SECRETS=Path(os.environ.get('SECRETS_ENGINE_SOURCE_ROOT','/home/worsch/secrets-engine')) -FIXTURE_PACKAGE='platform.disposable-task-seam.fixture' -FIXTURE_VERSION='v1' -PRINCIPAL='system:serviceaccount:secrets-engine:secrets-engine' - - -def private(path,text):path.write_text(text);path.chmod(0o600);return path - -def free_port(): - with socket.socket() as sock:sock.bind(('127.0.0.1',0));return sock.getsockname()[1] - - -def http(origin,method,path,body=None,token=None): - from urllib.parse import urlsplit - parsed=urlsplit(origin);conn=HTTPConnection(parsed.hostname,parsed.port,timeout=4) - try: - headers={'Content-Type':'application/json'} - if token is not None:headers['Authorization']='Bearer '+token - conn.request(method,path,body=json.dumps(body) if body is not None else None,headers=headers) - response=conn.getresponse();raw=response.read(1024*1024) - return response.status,json.loads(raw) if raw else {} - finally:conn.close() - - -class QuietWSGI(WSGIRequestHandler): - def log_message(self,*args):pass - - -@contextmanager -def approval_server(directory): - from approval_engine.api import App - from approval_engine.auth import Identity,StaticTokenAuthenticator - from approval_engine.store import Engine - service='synthetic-source-service';human='synthetic-source-human';no_read='synthetic-source-no-read' - def identity(subject,kind,scopes): - return Identity(subject,'https://synthetic.invalid',('approval-engine',),kind,'tenant:platform', - frozenset(),frozenset(scopes),{'level':'aal2','methods':['synthetic']}, - 'fixture:synthetic-identity',tenant_source='directory' if kind=='human' else 'registration') - identities={service:identity('secrets-engine','service',['approval:create','approval:read','approval:consume']), - human:identity('synthetic-founder','human',['approval:approve','approval:read']), - no_read:identity('platform-fixture-confirmation','service',['approval:consume'])} - engine=Engine(str(directory/'approval.sqlite'));app=App(engine,StaticTokenAuthenticator(identities)) - calls=[] - def logged(environ,start_response): - calls.append((environ['REQUEST_METHOD'],environ['PATH_INFO'])) - return app(environ,start_response) - server=make_server('127.0.0.1',0,logged,handler_class=QuietWSGI) - thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start() - try: - yield SimpleNamespace(origin='http://127.0.0.1:'+str(server.server_port),engine=engine, - service_token=service,human_token=human,no_read_token=no_read,calls=calls) - finally:server.shutdown();server.server_close();thread.join(5);engine.close() - - -@contextmanager -def reviewer_server(directory): - # Actual Access TokenReview client consumes this independently admitted - # synthetic reviewer. This is not a Kubernetes authentication proof. - exp=int(time.time())+900 - payload=base64.urlsafe_b64encode(json.dumps({'exp':exp}).encode()).decode().rstrip('=') - token='synthetic.'+payload+'.synthetic';reviewer='synthetic-reviewer';calls=[] - class Handler(BaseHTTPRequestHandler): - def log_message(self,*args):pass - def do_POST(self): - size=int(self.headers.get('Content-Length','0'));data=json.loads(self.rfile.read(size)) - valid=(self.path=='/apis/authentication.k8s.io/v1/tokenreviews' - and self.headers.get('Authorization')=='Bearer '+reviewer - and data.get('spec',{}).get('token')==token - and 'flex-auth' in data.get('spec',{}).get('audiences',[])) - calls.append(valid) - result={'apiVersion':'authentication.k8s.io/v1','kind':'TokenReview', - 'status':{'authenticated':valid,'audiences':['flex-auth'], - 'user':{'username':PRINCIPAL}}} - raw=json.dumps(result).encode();self.send_response(201) - self.send_header('Content-Type','application/json');self.send_header('Content-Length',str(len(raw)));self.end_headers();self.wfile.write(raw) - server=ThreadingHTTPServer(('127.0.0.1',0),Handler);thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start() - # The actual reviewer constructor requires a valid CA even for its explicitly - # synthetic HTTP endpoint; this CA never trusts an estate endpoint. - key=rsa.generate_private_key(public_exponent=65537,key_size=2048) - name=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,'synthetic-local-reviewer')]) - now=datetime.now(timezone.utc) - cert=x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(key.public_key()).serial_number(x509.random_serial_number()).not_valid_before(now-timedelta(minutes=1)).not_valid_after(now+timedelta(days=1)).sign(key,hashes.SHA256()) - ca=directory/'fixture-ca.pem';ca.write_bytes(cert.public_bytes(serialization.Encoding.PEM)) - reviewer_file=private(directory/'reviewer.token',reviewer) - try:yield SimpleNamespace(origin='http://127.0.0.1:'+str(server.server_port),caller_token=token,reviewer_file=reviewer_file,ca=ca,calls=calls) - finally:server.shutdown();server.server_close();thread.join(5) - - -def fixture_policy(directory,descriptor,request): - policy=directory/'fixture-policy.md' - fixture=directory/'fixture-cases.json' - fixture.write_text(json.dumps([{'id':'synthetic-claim-free-deny','request':request, - 'expect':{'effect':'deny','reason':'fixture_unapproved'}}])) - document='''--- -id: platform.disposable-task-seam.fixture -name: Disposable exact task seam fixture -namespace: secrets-engine:secret-catalog-lane -version: v1 -status: proposed -package: fixture.platform.task_seam -allow_ttl: 5m -actions: [execute-ccr] -owner: team:synthetic-fixture -fixtures: [fixture-cases.json] -caring: {profile: caring-0.4.0-rc2, enforce: false} -activation: {mode: local} ---- -Synthetic local policy only. No production task policy is edited or enabled. - -```rego -default decision := {"effect":"deny","reason":"fixture_unapproved"} -decision := {"effect":"allow","reason":"synthetic_exact_task_approved"} { - input.action == "execute-ccr" - input.tenant == "tenant:platform" - input.subject.id == "secrets-engine" - input.subject.type == "service" - input.resource.system == "secrets-engine" - input.context.human_control == true - input.context.approval.valid_now == true - input.context.approval.consumed == false - input.context.approval.binding.human_control == true - input.context.execution_contract == DESCRIPTOR -} -``` -''' + ''' -```rego test -import data.fixture.platform.task_seam -test_missing_approval_denies { task_seam.decision.effect == "deny" with input as {} } -``` -''' - document=document.replace('DESCRIPTOR',json.dumps(descriptor,separators=(',',':'))) - policy.write_text(document) - return policy - - -@contextmanager -def access_server(directory,descriptor,request,reviewer,*,actual_policy=False): - binary=Path(os.environ.get('PLATFORM_ACCESS_FIXTURE_BINARY','/tmp/netkingdom-platform-wave4-access')) - verifier=Path(os.environ.get('PLATFORM_PDP_FIXTURE_VERIFIER','/tmp/netkingdom-platform-wave4-verify')) - if not binary.is_file() or not verifier.is_file():raise RuntimeError('build source fixture binaries first') - registry=directory/'registry.json';registry.write_bytes((ACCESS/'examples/secrets-engine/registry_snapshot.json').read_bytes()) - policy=ACCESS/'examples/secrets-engine-service-task-execution/policy_package.md' if actual_policy else fixture_policy(directory,descriptor,request) - requestfile=directory/'claim-free-request.json';requestfile.write_text(json.dumps(request)) - # Trusted owner-local source CLI derives the real package pin independently - # from the network responder. Its DENY is used only as digest provenance. - offline=subprocess.run([str(binary),'check','--registry',str(registry),'--policy',str(policy),'--request',str(requestfile)],capture_output=True,text=True,timeout=15) - if offline.returncode!=0:raise RuntimeError('source policy fixture invalid: '+offline.stderr[:300]) - produced=json.loads(offline.stdout);provenance=produced['provenance'] - key=ed25519.Ed25519PrivateKey.generate() - seed=key.private_bytes(serialization.Encoding.Raw,serialization.PrivateFormat.Raw,serialization.NoEncryption()) - public=key.public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw) - b64=lambda value:base64.urlsafe_b64encode(value).decode().rstrip('=') - seedfile=private(directory/'signing.seed',b64(seed)) - trust={name:provenance[name] for name in ['evaluator','mode','policy_package','policy_version','policy_package_digest']} - trust.update(schema='secrets-engine.pdp-trust.v1',keys={'synthetic-owner-key':b64(public)}) - trustfile=directory/'pdp-trust.json';trustfile.write_text(json.dumps(trust)) - port=free_port();origin='http://127.0.0.1:'+str(port) - args=[str(binary),'serve','--addr','127.0.0.1:'+str(port),'--registry',str(registry),'--policy',str(policy), - '--envelope-signing-key-file',str(seedfile),'--envelope-signing-kid','synthetic-owner-key', - '--caller-auth-mode','enforce','--caller-audience','flex-auth','--caller-kubernetes-url',reviewer.origin, - '--caller-reviewer-token-file',str(reviewer.reviewer_file),'--caller-ca-file',str(reviewer.ca), - '--caller-binding','secrets-engine='+PRINCIPAL] - process=subprocess.Popen(args,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - for _ in range(100): - if process.poll() is not None:raise RuntimeError('source Access server exited') - try: - with socket.create_connection(('127.0.0.1',port),timeout=.1):break - except OSError:time.sleep(.05) - else:process.kill();process.wait();raise RuntimeError('source Access server failed to start') - try:yield SimpleNamespace(origin=origin,trustfile=trustfile,verifier=verifier, - package=provenance['policy_package'],version=provenance['policy_version'], - process=process,offline_envelope=produced) - finally: - process.terminate() - try:process.wait(timeout=5) - except subprocess.TimeoutExpired:process.kill();process.wait() diff --git a/tests/task_execution_trusted_servers.py b/tests/task_execution_trusted_servers.py deleted file mode 100644 index a9dc9f6..0000000 --- a/tests/task_execution_trusted_servers.py +++ /dev/null @@ -1,189 +0,0 @@ -"""Actual v2 provider fixture; no generic policy, fabricated claim or live identity.""" -from contextlib import contextmanager -from dataclasses import replace -from datetime import datetime,timedelta,timezone -import base64 -import hashlib -import json -import os -from pathlib import Path -import socket -import ssl -import subprocess -import threading -import time -from types import SimpleNamespace -from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer -from wsgiref.simple_server import make_server - -from cryptography import x509 -from cryptography.hazmat.primitives import serialization,hashes -from cryptography.hazmat.primitives.asymmetric import ed25519,rsa -from cryptography.x509.oid import NameOID - -from task_execution_source_servers import QuietWSGI,private,free_port,http -from task_execution_journal import Binding -from task_execution_native_fixture import FixtureAdmission,NativeFixtureAdapter -import task_execution_plan as plan_source -import task_execution_grant as grant - -ACCESS=Path(os.environ.get('TRUSTED_ACCESS_SOURCE_ROOT','/tmp/access-trusted-b2-b3-r3')) -SECRETS=Path(os.environ.get('SECRETS_ENGINE_SOURCE_ROOT','/tmp/netkingdom-secrets-trusted-successor')) -BINARY=Path(os.environ.get('TRUSTED_ACCESS_BINARY','/tmp/netkingdom-access-trusted-r3')) -VERIFIER=Path(os.environ.get('TRUSTED_PDP_VERIFIER','/tmp/secrets-trusted-successor-verifier')) -POLICY_DIGEST=os.environ.get('TRUSTED_POLICY_DIGEST','sha256:de5683e5a34161b9349b10cd8e4abef704e0136e3b14b97719fcd7a79850fc4d') -PRINCIPAL='system:serviceaccount:secrets-engine:secrets-engine' - - -def certificate(directory): - key=rsa.generate_private_key(public_exponent=65537,key_size=2048) - name=x509.Name([x509.NameAttribute(NameOID.COMMON_NAME,'localhost')]) - now=datetime.now(timezone.utc) - cert=(x509.CertificateBuilder().subject_name(name).issuer_name(name).public_key(key.public_key()) - .serial_number(x509.random_serial_number()).not_valid_before(now-timedelta(minutes=1)) - .not_valid_after(now+timedelta(hours=1)) - .add_extension(x509.SubjectAlternativeName([x509.DNSName('localhost')]),critical=False) - .sign(key,hashes.SHA256())) - ca=directory/'trusted-ca.pem';ca.write_bytes(cert.public_bytes(serialization.Encoding.PEM));ca.chmod(0o600) - private_key=directory/'trusted-tls.key' - private_key.write_bytes(key.private_bytes(serialization.Encoding.PEM,serialization.PrivateFormat.PKCS8,serialization.NoEncryption()));private_key.chmod(0o600) - context=ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER);context.load_cert_chain(ca,private_key) - return ca,context - - -def serve(server,*,tls=None): - if tls is not None:server.socket=tls.wrap_socket(server.socket,server_side=True) - thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start() - return thread - - -@contextmanager -def trusted_providers(directory): - from approval_engine.api import App - from approval_engine.auth import Identity,StaticTokenAuthenticator - from approval_engine.store import Engine - ca,tls=certificate(directory) - state=SimpleNamespace(principal=PRINCIPAL,caller_valid=True) - expiry=int(time.time())+600 - encoded=base64.urlsafe_b64encode(json.dumps({'exp':expiry}).encode()).decode().rstrip('=') - caller='synthetic.'+encoded+'.synthetic';reviewer='synthetic-trusted-reviewer';calls=[] - class Review(BaseHTTPRequestHandler): - def log_message(self,*args):pass - def do_POST(self): - raw=self.rfile.read(int(self.headers.get('Content-Length','0')));body=json.loads(raw) - valid=(state.caller_valid and self.path=='/apis/authentication.k8s.io/v1/tokenreviews' - and self.headers.get('Authorization')=='Bearer '+reviewer - and body.get('spec',{}).get('token')==caller - and 'flex-auth' in body.get('spec',{}).get('audiences',[])) - calls.append(('review',valid)) - response={'status':{'authenticated':valid,'audiences':['flex-auth'],'user':{'username':state.principal}}} - payload=json.dumps(response).encode();self.send_response(200);self.send_header('Content-Type','application/json');self.send_header('Content-Length',str(len(payload)));self.end_headers();self.wfile.write(payload) - review=ThreadingHTTPServer(('127.0.0.1',0),Review);review_thread=serve(review,tls=tls) - def identity(subject,kind,scopes): - return Identity(subject,'synthetic-whole-source',('approval-engine',),kind,'tenant:platform', - frozenset(),frozenset(scopes),{'level':'aal2','methods':['synthetic']}, - 'fixture:whole-source',tenant_source='directory' if kind=='human' else 'registration') - service='synthetic-whole-service';reader='synthetic-whole-reader';human='synthetic-whole-human' - authenticator=StaticTokenAuthenticator({service:identity('secrets-engine','service',['approval:create','approval:read','approval:consume']), - reader:identity('access-source-reader','service',['approval:read']), - human:identity('whole-human','human',['approval:approve','approval:revoke'])}) - # Explicit synthetic owner observation contract, not an API default change. - engine=Engine(str(directory/'whole-approval.sqlite'),freshness_ttl=120) - app=App(engine,authenticator) - def logged(channel): - def handle(environ,start_response): - calls.append((channel,environ['REQUEST_METHOD'],environ['PATH_INFO'])) - return app(environ,start_response) - return handle - source=make_server('127.0.0.1',0,logged('source'),handler_class=QuietWSGI) - consumer=make_server('127.0.0.1',0,logged('consumer'),handler_class=QuietWSGI) - source_thread=serve(source,tls=tls);consumer_thread=serve(consumer) - reader_file=private(directory/'approval-reader.token',reader) - review_file=private(directory/'reviewer.token',reviewer) - cfg={'source_id':'synthetic-whole-actual-approval','source_revision':'approval-3485415-precision-74eb0ff6-fixture', - 'origin':'https://localhost:'+str(source.server_port),'store_tenant':'tenant:platform', - 'ca_file':str(ca),'token_file':str(reader_file)} - config=directory/'approval-source.json';private(config,json.dumps(cfg)) - seed_key=ed25519.Ed25519PrivateKey.generate() - b64=lambda value:base64.urlsafe_b64encode(value).decode().rstrip('=') - seed=private(directory/'whole-signing.seed',b64(seed_key.private_bytes(serialization.Encoding.Raw,serialization.PrivateFormat.Raw,serialization.NoEncryption()))) - public=b64(seed_key.public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw)) - registry=directory/'whole-registry.json';registry.write_bytes((ACCESS/'examples/secrets-engine/registry_snapshot.json').read_bytes()) - policy=ACCESS/'examples/secrets-engine-service-task-execution-v2/policy_package.md' - record=directory/'whole-access-record.jsonl';port=free_port();origin='http://127.0.0.1:'+str(port) - args=[str(BINARY),'serve','--addr','127.0.0.1:'+str(port),'--registry',str(registry),'--policy',str(policy), - '--log',str(record),'--envelope-signing-key-file',str(seed),'--envelope-signing-kid','synthetic-whole-key', - '--caller-auth-mode','enforce','--caller-audience','flex-auth', - '--caller-kubernetes-url','https://localhost:'+str(review.server_port), - '--caller-reviewer-token-file',str(review_file),'--caller-ca-file',str(ca), - '--caller-binding','secrets-engine='+PRINCIPAL,'--approval-source-file',str(config)] - process=subprocess.Popen(args,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,env={}) - try: - for _ in range(100): - if process.poll() is not None:raise RuntimeError('actual trusted Access source exited') - try: - with socket.create_connection(('127.0.0.1',port),timeout=.1):break - except OSError:time.sleep(.025) - else:raise RuntimeError('actual trusted Access source startup unconfirmed') - trust={'schema':'secrets-engine.pdp-trust.v1','evaluator':'flex-auth/local','mode':'standalone', - 'policy_package':'secrets-engine.task-execution.service-pilot','policy_version':'v2', - 'policy_package_digest':POLICY_DIGEST,'keys':{'synthetic-whole-key':public}} - trustfile=private(directory/'whole-pdp-trust.json',json.dumps(trust)) - approval_token=private(directory/'whole-consumer-approval.token',service) - pdp_token=private(directory/'whole-consumer-pdp.token',caller) - from task_execution_mediator import ConfirmationConfig - cfg=SimpleNamespace(approval_url='http://127.0.0.1:'+str(consumer.server_port),approval_token_file=approval_token, - approval_client_secret_file=None,pdp_url=origin,pdp_token_file=pdp_token,pdp_trust_file=trustfile,pdp_verifier=VERIFIER, - authorization_subject_id='secrets-engine',authorization_subject_type='service', - authorization_policy_package=trust['policy_package'],authorization_policy_version='v2',clock_trust_file=None) - def restart_registry(): - nonlocal process - process.terminate();process.wait(timeout=5) - changed=json.loads(registry.read_bytes()) - for subject in changed['subjects']: - if subject['id']=='secrets-engine':subject['metadata']['description']+=' synthetic revision drift' - registry.write_text(json.dumps(changed)) - process=subprocess.Popen(args,stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,env={}) - for _ in range(100): - if process.poll() is not None:raise RuntimeError('changed-registry source startup refused') - try: - with socket.create_connection(('127.0.0.1',port),timeout=.1):break - except OSError:time.sleep(.025) - else:raise RuntimeError('changed-registry source startup unconfirmed') - yield SimpleNamespace(cfg=cfg,engine=engine,calls=calls,state=state,record=record,process=process, - registry=registry,restart_registry=restart_registry, - confirmation=ConfirmationConfig(cfg.approval_url,approval_token),human_token=human, - source_config=config,reader_file=reader_file) - finally: - process.terminate();process.wait(timeout=5) - for server in [source,consumer,review]:server.shutdown();server.server_close() - for thread in [source_thread,consumer_thread,review_thread]:thread.join(5) - engine.close() - - -def install_exact_owner_descriptor(owner,*,window_seconds=60): - """All server startup precedes this exact proposal time; no remint/reissue.""" - native,journal,binding,call,compiler,descriptor=owner - from secrets_engine.execution_contract import ExecutionContract - descriptor=json.loads(json.dumps(descriptor)) - descriptor['backend_budget']={'ttl_seconds':30,'uses':12} - descriptor['operation_deadline']=(datetime.now(timezone.utc)+timedelta(seconds=window_seconds)).replace(microsecond=0).isoformat().replace('+00:00','Z') - ccr=compiler._derive()[0] - sources={'engine_dispatcher':SECRETS/'src/secrets_engine/task_execution_handoff.py', - 'platform_mediator':Path(__file__).resolve().parents[1]/'scripts/task_execution_mediator.py', - 'typed_ccr':compiler.validator,'policy_auth':compiler.config.platform_repo/ccr['openbao']['policy_file']} - descriptor['source_bundle']={role:{'path':str(path),'sha256':plan_source.tagged(path.read_bytes())} for role,path in sources.items()} - # Exact source descriptor participates in the existing reviewed pure grant - # projection. Synthetic fixture approval is not a production CCR admission. - record=json.loads(json.dumps(__import__('yaml').safe_load(compiler.grant_record.read_text()))) - record['execution'],record['openbao']=compiler.grant_effects(compiler.compile(native.meta['request']),descriptor) - compiler.grant_record.write_text(__import__('yaml').safe_dump(record,sort_keys=False)) - pins=dict(compiler.config.source_pins);pins[str(compiler.grant_record)]=plan_source.tagged(compiler.grant_record.read_bytes()) - compiler=plan_source.Compiler(replace(compiler.config,source_pins=tuple(pins.items()))) - descriptor['grant_immutable_sha256']=grant.immutable_digest(record) - descriptor=ExecutionContract.parse(descriptor).as_dict();raw=plan_source.encoded(descriptor) - plan=compiler.bind_execution(compiler.compile(native.meta['request']),descriptor,raw) - binding=Binding(binding.approval_id,binding.request_digest,binding.operation_id,plan.content_digest) - admission=replace(native.admission,descriptor=raw) - native=NativeFixtureAdapter(compiler,plan,journal,binding,admission) - return native,journal,binding,call,compiler,descriptor diff --git a/tests/test_activity_promotion_readiness.py b/tests/test_activity_promotion_readiness.py deleted file mode 100644 index b48dbe1..0000000 --- a/tests/test_activity_promotion_readiness.py +++ /dev/null @@ -1,127 +0,0 @@ -import base64 -from datetime import datetime, timezone, timedelta -import importlib.util -from pathlib import Path -import unittest -from unittest import mock -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat - -SPEC=importlib.util.spec_from_file_location('readiness',Path(__file__).parents[1]/'scripts/activity_promotion_readiness.py') -p=importlib.util.module_from_spec(SPEC);SPEC.loader.exec_module(p) -NOW=datetime(2026,10,11,20,tzinfo=timezone.utc) - - -def observation(): - return {'captured_at':NOW.isoformat(),'estate_ip':'92.205.62.239','cluster_uid':'cluster-uid','application_uid':'app-uid', - 'source':{'repoURL':'https://forgejo.coulomb.social/coulomb/activity-core.git','path':'k8s/gitops','targetRevision':p.PIN}, - 'project':'activity-core','revision':p.PIN,'destination':{'server':'https://kubernetes.default.svc','namespace':'activity-core'}, - 'automated_present':False,'pending_operation':False, - 'sync_options':['CreateNamespace=false','ApplyOutOfSyncOnly=true','PruneLast=true','FailOnSharedResource=true'], - 'sync':'Synced','health':'Healthy','condition_types':[],'project_scope_matches':True, - 'operation':{'phase':'Succeeded','finishedAt':'2026-10-04T14:13:25Z'}, - 'resources':[dict(zip(('group','kind','namespace','name'),r),status='Synced') for r in sorted(p.RESOURCES)], - 'deployments':[{'name':name,'generation_observed':True,'replicas':1,'ready':1,'updated':1,'available':1} - for name in ('actcore-api','actcore-worker','actcore-event-router')]} - - -def payload(): - return {'schema_version':'activity-core-release-acceptance/v1','owner_task':p.OWNER_TASK,'issuer':'owner-acceptance-principal', - 'authority':p.AUTHORITY,'application':'activity-core','cluster_uid':'cluster-uid','adoption_revision':p.PIN, - 'targets':dict(p.TARGETS),'release_principal':'activity-core-release-broker','broker_revision':'a'*40, - 'checks':{c:True for c in p.CHECKS},'issued_at':NOW.isoformat(),'expires_at':(NOW+timedelta(minutes=4)).isoformat(), - 'continuous_health':{'revision':p.PIN,'continuous':True,'max_gap_seconds':90,'observed_at':NOW.isoformat(), - 'healthy_since':(NOW-timedelta(days=1)).isoformat()}} - - -class GateTest(unittest.TestCase): - def setUp(self): - self.private=Ed25519PrivateKey.generate() - self.public=self.private.public_key().public_bytes(Encoding.Raw,PublicFormat.Raw) - - def envelope(self,data=None): - data=data or payload() - return {'payload':data,'signature':base64.b64encode(self.private.sign(p.canonical(data))).decode()} - - def evaluate(self,envelope=None,obs=None,key=None): - return p.readiness(obs or observation(),envelope=envelope or self.envelope(),trusted_key=key or self.public, - trusted_principal='owner-acceptance-principal',now=NOW) - - def test_complete_signed_fixture_passes_without_activation(self): - result=self.evaluate() - self.assertEqual(result['status'],'ready') - self.assertFalse(result['native_mutation']) - self.assertFalse(result['automatic_sync_candidate']) - - def test_snapshot_and_elapsed_time_cannot_substitute_for_owner_proof(self): - result=p.readiness(observation(),now=NOW) - self.assertEqual(result['status'],'waiting') - self.assertEqual(result['depends_on'],['ACTIVITY-WP-0041-T03']) - self.assertFalse(result['needs_human']) - self.assertTrue(result['adoption_snapshot_is_not_continuous_observation']) - - def test_every_acceptance_check_required(self): - for check in p.CHECKS: - for missing in (False,None,'true'): - data=payload();data['checks'][check]=missing - with self.subTest(check=check,value=missing):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified') - - def test_unknown_key_forged_tampered_or_self_introduced_trust_refused(self): - envelope=self.envelope();envelope['payload']['release_principal']='attacker' - self.assertEqual(self.evaluate(envelope)['status'],'unverified') - unknown=Ed25519PrivateKey.generate().public_key().public_bytes(Encoding.Raw,PublicFormat.Raw) - self.assertEqual(self.evaluate(key=unknown)['status'],'unverified') - envelope=self.envelope();envelope['public_key']=self.public.hex() - self.assertEqual(self.evaluate(envelope)['status'],'unverified') - envelope=self.envelope();envelope['signature']='invalid-base64!' - self.assertEqual(self.evaluate(envelope)['status'],'unverified') - - def test_wrong_scope_identity_cluster_commit_or_expired_owner_proof_refused(self): - for key,value in [('authority','admin'),('application','other'),('cluster_uid','other'), - ('adoption_revision','b'*40),('owner_task','other'),('issuer','producer'), - ('broker_revision','main'),('release_principal',''), - ('issued_at',(NOW-timedelta(minutes=6)).isoformat()), - ('issued_at',(NOW+timedelta(seconds=1)).isoformat()), - ('expires_at',NOW.isoformat())]: - data=payload();data[key]=value - with self.subTest(key=key):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified') - for key,value in [('prune',True),('root_sync_selects_only','*'),('child_path','other')]: - data=payload();data['targets'][key]=value - with self.subTest(target=key):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified') - - def test_continuous_sampling_requires_owner_signed_fresh_bounded_interval(self): - for key,value in [('continuous',False),('max_gap_seconds',91),('max_gap_seconds',True), - ('revision','b'*40),('healthy_since',(NOW-timedelta(hours=23)).isoformat()), - ('observed_at',(NOW-timedelta(seconds=91)).isoformat()), - ('observed_at',(NOW+timedelta(seconds=1)).isoformat())]: - data=payload();data['continuous_health'][key]=value - with self.subTest(key=key):self.assertEqual(self.evaluate(self.envelope(data))['status'],'unverified') - - def test_exact_live_adoption_scope_and_freshness_refusals(self): - for key,value in [('estate_ip','other'),('project_scope_matches',False),('project','other'), - ('automated_present',True),('pending_operation',True),('revision','b'*40), - ('health','Degraded'),('sync','OutOfSync'),('condition_types',['ComparisonError']), - ('captured_at',(NOW-timedelta(minutes=6)).isoformat()), - ('captured_at',(NOW+timedelta(seconds=1)).isoformat()), - ('resources',[]),('deployments',[])]: - data=observation();data[key]=value - with self.subTest(key=key):self.assertEqual(self.evaluate(obs=data)['status'],'unverified') - data=observation();data['resources'].append(data['resources'][0]) - self.assertEqual(self.evaluate(obs=data)['status'],'unverified') - for key,value in [('ready',0),('generation_observed',False),('updated',0),('replicas',0)]: - data=observation();data['deployments'][0][key]=value - with self.subTest(deployment=key):self.assertEqual(self.evaluate(obs=data)['status'],'unverified') - - def test_no_signature_verification_attempt_without_trusted_operator_config(self): - self.assertEqual(p.readiness(observation(),envelope=self.envelope(),now=NOW)['status'],'waiting') - - def test_protected_subprocess_errors_and_timeouts_never_leak_canary(self): - import subprocess - from types import SimpleNamespace - canary='SECRET-CANARY-NOT-OUTPUT' - with mock.patch.object(p.subprocess,'run',return_value=SimpleNamespace(returncode=1,stdout=canary,stderr=canary)): - with self.assertRaises(ValueError) as exc:p.run(['kubectl','get','nodes']) - self.assertNotIn(canary,str(exc.exception)) - with mock.patch.object(p.subprocess,'run',side_effect=subprocess.TimeoutExpired('cmd',1,output=canary,stderr=canary)): - with self.assertRaises(ValueError) as exc:p.run(['kubectl','get','nodes']) - self.assertNotIn(canary,str(exc.exception)) diff --git a/tests/test_application_archive_retention_plan.py b/tests/test_application_archive_retention_plan.py deleted file mode 100644 index 766e43b..0000000 --- a/tests/test_application_archive_retention_plan.py +++ /dev/null @@ -1,90 +0,0 @@ -"""Retention proposals preserve known recovered points and never own expiration.""" - -import importlib.util -import sys -from datetime import UTC, datetime, timedelta -from pathlib import Path - -import pytest - - -@pytest.fixture -def planner(monkeypatch): - scripts = Path(__file__).resolve().parents[1] / "scripts" - monkeypatch.syspath_prepend(str(scripts)) - spec = importlib.util.spec_from_file_location( - "application_archive_retention_plan", scripts / "application_archive_retention_plan.py" - ) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - sys.modules.pop("primary_application_archive", None) - return module - - -def inventory(planner): - now = datetime(2027, 1, 12, 3, 15, tzinfo=UTC) - result = [] - for day in range(70): - stamp = now - timedelta(days=day) - result.append( - { - "application": "coulomb-social", - "status": "offsite_fetched_verified", - "remote_retrieval_verified": True, - "captured_at": stamp.isoformat(), - "object_key": planner.PROFILES["coulomb-social"] - + stamp.strftime("%Y/%m/%d/%Y%m%dT%H%M%SZ-") - + f"{day:024x}" - + ".tar.age", - "version_id": "version-" + str(day), - "ciphertext_sha256": "a" * 64, - "ciphertext_bytes": 5000, - } - ) - recovery = dict( - result[-1], status="application_restored_verified", application_restore_verified=True - ) - return now, result, recovery - - -def test_protects_old_recovered_point_and_deduplicates_daily_weekly(planner): - now, records, recovery = inventory(planner) - records.append(dict(records[0], application="email-connect", object_key="foreign")) - records.append(dict(records[0], status="failed", object_key="failed")) - result = planner.plan("coulomb-social", records, recovery, now) - keys = [item["object_key"] for item in result["keep"]] - assert len(keys) == len(set(keys)) <= 19 - assert records[0]["object_key"] in keys and recovery["object_key"] in keys - assert all(item["object_key"] in keys for item in records[:14]) - assert result["ignored"] == ["foreign", "failed"] - assert result["expiration_candidates"] - assert not result["provider_mutations"] and not result["local_mutations"] - assert result["protected"]["version_id"] == recovery["version_id"] - - -@pytest.mark.parametrize("change", ["missing", "version", "hash", "unproved"]) -def test_refuses_missing_changed_or_unproved_application_recovery(planner, change): - now, records, recovery = inventory(planner) - if change == "missing": - records.pop() - elif change == "version": - recovery["version_id"] = "different-version" - elif change == "hash": - recovery["ciphertext_sha256"] = "b" * 64 - else: - recovery["application_restore_verified"] = False - with pytest.raises(ValueError): - planner.plan("coulomb-social", records, recovery, now) - - -@pytest.mark.parametrize("change", ["duplicate", "future", "unversioned"]) -def test_refuses_ambiguous_inventory(planner, change): - now, records, recovery = inventory(planner) - if change == "duplicate": - records.append(dict(records[0])) - elif change == "future": - records[0]["captured_at"] = (now + timedelta(days=1)).isoformat() - else: - records[0]["version_id"] = "null" - with pytest.raises(ValueError): - planner.plan("coulomb-social", records, recovery, now) diff --git a/tests/test_application_backup_credentials.py b/tests/test_application_backup_credentials.py deleted file mode 100644 index 0c89356..0000000 --- a/tests/test_application_backup_credentials.py +++ /dev/null @@ -1,351 +0,0 @@ -"""Synthetic credential boundaries and interrupted-delivery recovery, no live calls.""" - -import base64 -import hashlib -import importlib.util -import json -import os -import time -from pathlib import Path - -import pytest - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location( - "delivery", ROOT / "scripts/application_backup_credentials.py" -) -d = importlib.util.module_from_spec(spec) -spec.loader.exec_module(d) - -KEY = "SYNTHETIC_ACCESS_12345" -SECRET = "SYNTHETIC_SECRET_987654321" -TOKEN = "SYNTHETIC_BAO_READER_12345" -JWT = "SYNTHETIC_KUBERNETES_JWT_12345" - - -def source(application="coulomb-social"): - return ( - ROOT - / "credential-change-requests" - / f"{d.PROFILES[application]['ccr']}-{application}-backup-primary.yaml" - ) - - -def approved(application="coulomb-social"): - ccr = json.loads(source(application).read_bytes()) - ccr["status"] = "applied" - ccr["openbao"]["auth"]["bound_claims_confirmed"] = True - ccr["review"]["comments"] = [ - { - "reviewer": f"User ({owner})", - "decision": "approved", - "source_ref": "synthetic-owner-proof", - } - for owner in ("platform-owner", f"{application}-owner") - ] - ccr["delivery"]["admission"] = { - "node_executor_approved": True, - "provider_scope_verified": True, - "reader_boundary_verified": True, - "source_ref": "synthetic-proof", - } - return ccr - - -class FakeNative: - def __init__(self, application="coulomb-social"): - self.application = application - self.calls = [] - policy = d.expected_openbao(application)["policy_name"] - self.login = {"auth": {"client_token": TOKEN, "policies": [policy], "lease_duration": 300}} - self.info = { - "data": { - "policies": [policy], - "identity_policies": [], - "ttl": 299, - "period": 0, - "explicit_max_ttl": 300, - "num_uses": 3, - } - } - self.record = { - "data": { - "data": {d.FIELDS[0]: KEY, d.FIELDS[1]: SECRET}, - "metadata": {"version": 1, "destroyed": False, "deletion_time": ""}, - } - } - self.fail = None - - def identity(self, application): - assert application == self.application - self.calls.append(("identity", application, None)) - return JWT - - def request(self, method, path, payload=None, token=None): - self.calls.append((method, path, token)) - if path == self.fail: - raise RuntimeError(SECRET + TOKEN + JWT) - if path == "auth/kubernetes/login": - assert payload == { - "role": d.expected_openbao(self.application)["auth"]["role"], - "jwt": JWT, - } - return self.login - assert token == TOKEN - if path == "auth/token/lookup-self": - return self.info - if path.startswith("platform/data/"): - assert path == f"platform/data/workloads/{self.application}/backup-primary" - return self.record - assert path == "auth/token/revoke-self" - return {} - - -@pytest.fixture -def directory(tmp_path): - tmp_path.chmod(0o700) - fd = d.private_directory(tmp_path, uid=os.getuid()) - yield tmp_path, fd - os.close(fd) - - -@pytest.mark.parametrize("application", d.PROFILES) -def test_draft_preview_has_no_effects(application, capsys, monkeypatch): - monkeypatch.setattr(d, "Native", lambda: pytest.fail("preview made a native client")) - assert d.main(["--application", application, "--ccr", str(source(application))]) == 0 - receipt = json.loads(capsys.readouterr().out) - assert receipt["status"] == "preview" and receipt["effects"] == [] - assert receipt["target"] == d.PROFILES[application]["directory"] + "/primary.env" - - -@pytest.mark.parametrize( - "change", ["draft", "unconfirmed", "hash", "owner", "admission", "sibling", "default"] -) -def test_live_contract_refuses_missing_authority_before_identity(change): - ccr = approved() - if change == "draft": - ccr["status"] = "draft" - elif change == "unconfirmed": - ccr["openbao"]["auth"]["bound_claims_confirmed"] = False - elif change == "owner": - ccr["review"]["comments"].pop() - elif change == "admission": - ccr["delivery"]["admission"]["provider_scope_verified"] = False - elif change == "sibling": - ccr["openbao"]["kv_path"] = "platform/workloads/email-connect/backup-primary" - elif change == "default": - ccr["openbao"]["auth"]["policies"].append("default") - raw = json.dumps(ccr).encode() - digest = "0" * 64 if change == "hash" else hashlib.sha256(raw).hexdigest() - with pytest.raises(d.DeliveryError): - d.contract(raw, "coulomb-social", True, digest) - - -@pytest.mark.parametrize("application", d.PROFILES) -def test_atomic_private_delivery_and_identical_resume(application, directory, capsys): - path, fd = directory - ccr = approved(application) - native = FakeNative(application) - receipts = [ - d.deliver(ccr, "source-only-hash", application, 1, native, fd, os.getuid()) - for _ in range(2) - ] - assert [r["outcome"] for r in receipts] == ["created", "unchanged"] - assert ( - path / "primary.env" - ).read_bytes() == f"{d.FIELDS[0]}={KEY}\n{d.FIELDS[1]}={SECRET}\n".encode() - assert (path / "primary.env").stat().st_mode & 0o777 == 0o600 - assert len(list(path.glob("delivery-v1-*.json"))) == 2 - for receipt in receipts: - serialized = json.dumps(receipt) - assert all(secret not in serialized for secret in (KEY, SECRET, TOKEN, JWT)) - assert receipt["readback_equal"] and receipt["token_revoked"] - assert all(secret not in capsys.readouterr().out for secret in (KEY, SECRET, TOKEN, JWT)) - assert native.calls[4][1] == "auth/token/revoke-self" - - -@pytest.mark.parametrize( - "change", - ["default", "identity", "unbounded", "uses", "period", "version", "destroyed", "fields", "injection"], -) -def test_scope_and_payload_failures_revoke_without_publishing(change, directory): - path, fd = directory - native = FakeNative() - if change == "default": - native.login["auth"]["policies"].append("default") - elif change == "identity": - native.info["data"]["identity_policies"] = ["platform-admin"] - elif change == "unbounded": - native.info["data"]["explicit_max_ttl"] = 0 - elif change == "uses": - native.info["data"]["num_uses"] = 0 - elif change == "period": - native.info["data"]["period"] = 60 - elif change == "version": - native.record["data"]["metadata"]["version"] = 2 - elif change == "destroyed": - native.record["data"]["metadata"]["destroyed"] = True - elif change == "fields": - native.record["data"]["data"]["ESCROW"] = SECRET - else: - native.record["data"]["data"][d.FIELDS[1]] = SECRET + "\nOTHER=INJECTED" - with pytest.raises(d.DeliveryError): - d.deliver(approved(), "source-hash", "coulomb-social", 1, native, fd, os.getuid()) - assert native.calls[-1][1] == "auth/token/revoke-self" - assert not (path / "primary.env").exists() and not list(path.glob("delivery-v*.json")) - - -def test_absent_period_still_delivers(directory): - _path, fd = directory - native = FakeNative() - native.info["data"]["period"] = None - receipt = d.deliver(approved(), "source-hash", "coulomb-social", 1, native, fd, os.getuid()) - assert receipt["outcome"] == "created" - - -def test_revocation_failure_does_not_publish_or_disclose(directory, capsys, monkeypatch, tmp_path): - path, fd = directory - ccr = approved() - candidate = tmp_path / "contract.json" - raw = json.dumps(ccr).encode() - candidate.write_bytes(raw) - native = FakeNative() - native.fail = "auth/token/revoke-self" - monkeypatch.setattr(d.os, "geteuid", lambda: 0) - monkeypatch.setattr(d, "private_directory", lambda *_: os.dup(fd)) - monkeypatch.setattr(d, "Native", lambda: native) - original_deliver = d.deliver - monkeypatch.setattr(d, "deliver", lambda *a: original_deliver(*a, uid=os.getuid())) - assert ( - d.main( - [ - "--application", - "coulomb-social", - "--ccr", - str(candidate), - "--execute", - "--expected-ccr-sha256", - hashlib.sha256(raw).hexdigest(), - "--expected-kv-version", - "1", - ] - ) - == 1 - ) - output = capsys.readouterr() - assert "token_revocation_unconfirmed" in output.err - assert all(secret not in output.out + output.err for secret in (KEY, SECRET, TOKEN, JWT)) - assert not (path / "primary.env").exists() - - -@pytest.mark.parametrize("existing", ["different", "insecure", "symlink", "hardlink", "fifo"]) -def test_existing_target_is_preserved_and_unsafe_files_refused(existing, directory): - path, fd = directory - target = path / "primary.env" - if existing == "symlink": - victim = path / "victim" - victim.write_bytes(b"previous") - target.symlink_to(victim) - elif existing == "fifo": - os.mkfifo(target, 0o600) - else: - target.write_bytes(b"previous") - target.chmod(0o644 if existing == "insecure" else 0o600) - if existing == "hardlink": - os.link(target, path / "alias") - with pytest.raises((d.DeliveryError, OSError)): - d.deliver(approved(), "source-hash", "coulomb-social", 1, FakeNative(), fd, os.getuid()) - assert not list(path.glob("delivery-v*.json")) - if existing != "fifo": - assert target.read_bytes() == b"previous" - - -def test_secret_write_failure_removes_temporary_and_is_resumable(directory, monkeypatch): - path, fd = directory - original = d.os.link - monkeypatch.setattr(d.os, "link", lambda *a, **k: (_ for _ in ()).throw(OSError(SECRET))) - with pytest.raises(OSError): - d.deliver(approved(), "source-hash", "coulomb-social", 1, FakeNative(), fd, os.getuid()) - assert not list(path.glob(".primary.env.*")) and not (path / "primary.env").exists() - monkeypatch.setattr(d.os, "link", original) - assert ( - d.deliver(approved(), "source-hash", "coulomb-social", 1, FakeNative(), fd, os.getuid())[ - "status" - ] - == "delivered" - ) - - -def test_receipt_failure_resume_keeps_identical_secret(directory, monkeypatch): - path, fd = directory - original = d.publish - - def fail_receipt(fd, name, body, uid): - if name.startswith("delivery-v"): - raise OSError(SECRET) - return original(fd, name, body, uid) - - monkeypatch.setattr(d, "publish", fail_receipt) - with pytest.raises(OSError): - d.deliver(approved(), "source-hash", "coulomb-social", 1, FakeNative(), fd, os.getuid()) - before = (path / "primary.env").read_bytes() - monkeypatch.setattr(d, "publish", original) - assert ( - d.deliver(approved(), "source-hash", "coulomb-social", 1, FakeNative(), fd, os.getuid())[ - "outcome" - ] - == "unchanged" - ) - assert (path / "primary.env").read_bytes() == before - - -def test_native_wrong_cluster_stops_before_token_request(monkeypatch): - calls = [] - - def command(argv): - calls.append(argv) - return json.dumps({"metadata": {"uid": "wrong-cluster"}}).encode() - - monkeypatch.setattr(d, "command", command) - with pytest.raises(d.DeliveryError, match="cluster_identity_mismatch"): - d.Native().identity("coulomb-social") - assert len(calls) == 1 and "create" not in calls[0] - - -def test_native_tokenrequest_has_no_secret_argv_or_secret_get(monkeypatch): - calls = [] - claims = { - "sub": f"system:serviceaccount:coulomb-social:{d.SA}", - "aud": ["openbao"], - "exp": time.time() + 600, - "kubernetes.io": {"serviceaccount": {"uid": "account-uid"}}, - } - jwt = ( - "header." - + base64.urlsafe_b64encode(json.dumps(claims).encode()).decode().rstrip("=") - + ".signature" - ) - responses = [ - {"metadata": {"uid": d.CLUSTER_UID}}, - {"spec": {"clusterIP": "10.43.12.34", "ports": [{"port": 8200}]}}, - {"metadata": {"uid": "account-uid"}, "automountServiceAccountToken": False}, - ] - - def command(argv): - calls.append(argv) - return json.dumps(responses.pop(0)).encode() if responses else jwt.encode() - - monkeypatch.setattr(d, "command", command) - native = d.Native() - assert native.identity("coulomb-social") == jwt - assert native.address == "http://10.43.12.34:8200/v1/" - assert calls[-1][-3:] == [d.SA, "--audience=openbao", "--duration=10m"] - assert all("secret" not in arg.lower() and jwt not in arg for call in calls for arg in call) - - -def test_private_directory_rejects_symlink_ancestor(tmp_path): - real = tmp_path / "real" - real.mkdir(mode=0o700) - (tmp_path / "alias").symlink_to(real) - with pytest.raises(OSError): - d.private_directory(tmp_path / "alias", os.getuid()) diff --git a/tests/test_application_backup_pair_custody.py b/tests/test_application_backup_pair_custody.py deleted file mode 100644 index eb47f46..0000000 --- a/tests/test_application_backup_pair_custody.py +++ /dev/null @@ -1,139 +0,0 @@ -"""Pair custody stays preview-only until a writer identity is admitted.""" - -import importlib.util -import json -from pathlib import Path - -import pytest - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location( - "pair_custody", ROOT / "scripts/application_backup_pair_custody.py" -) -custody = importlib.util.module_from_spec(spec) -spec.loader.exec_module(custody) - -PAIR = { - "AWS_ACCESS_KEY_ID": "SYNTHETICACCESSKEY123", - "AWS_SECRET_ACCESS_KEY": "SYNTHETICSECRETKEY4567", -} - - -class Transport: - def __init__(self): - self.calls = [] - self.metadata_result = None - self.version = 1 - - def metadata(self, path): - self.calls.append(("metadata", path)) - return self.metadata_result - - def put_cas0(self, path, body): - self.calls.append(("put", path, body["options"]["cas"], tuple(body["data"]))) - return {"version": self.version, "request_id": "synthetic-request"} - - def revoke(self): - self.calls.append(("revoke",)) - - -def identity(): - return { - "policies": [custody.WRITER_POLICY], - "identity_policies": [], - "ttl": 300, - "num_uses": 2, - } - - -@pytest.mark.parametrize("application", sorted(custody.PROFILES)) -def test_preview_reads_recorded_contract_and_writes_nothing(application, capsys, monkeypatch): - monkeypatch.setattr(custody, "provision", lambda *_: pytest.fail("preview provisioned")) - assert custody.main(["--application", application]) == 0 - receipt = json.loads(capsys.readouterr().out) - recorded = json.loads(custody.ccr_path(application).read_text(encoding="utf-8")) - assert receipt["status"] == "preview" - assert receipt["ccr_status"] == recorded["status"] - assert receipt["writer_admitted"] is True - assert receipt["openbao_write"] is False - assert receipt["provider_keys"] == "not_created" - assert receipt["effects"] == [] - assert receipt["fields"] == list(custody.FIELDS) - rendered = json.dumps(receipt) - assert "SYNTHETIC" not in rendered - assert "AKIA" not in rendered - - -def test_unreviewed_writer_makes_no_transport_call(monkeypatch): - monkeypatch.setattr(custody, "WRITER_ADMITTED", False) - transport = Transport() - with pytest.raises(custody.CustodyError, match="writer_identity_unreviewed"): - custody.provision("email-connect", PAIR, identity(), transport) - assert transport.calls == [] - - -def test_admitted_writer_uses_one_cas0_write_and_revokes(monkeypatch): - monkeypatch.setattr(custody, "WRITER_ADMITTED", True) - transport = Transport() - receipt = custody.provision("email-connect", dict(PAIR), identity(), transport) - assert receipt["kv_version"] == 1 - assert receipt["secret_material"] is False - assert PAIR["AWS_SECRET_ACCESS_KEY"] not in json.dumps(receipt) - assert transport.calls == [ - ("metadata", "platform/metadata/workloads/email-connect/backup-primary"), - ( - "put", - "platform/data/workloads/email-connect/backup-primary", - 0, - custody.FIELDS, - ), - ("revoke",), - ] - - -def test_reader_policy_and_existing_version_are_refused(monkeypatch): - monkeypatch.setattr(custody, "WRITER_ADMITTED", True) - reader = dict(identity(), policies=["workload-kv-read-email-connect-backup-primary"]) - with pytest.raises(custody.CustodyError, match="writer_identity_unreviewed"): - custody.provision("email-connect", PAIR, reader, Transport()) - transport = Transport() - transport.metadata_result = {"current_version": 1} - with pytest.raises(custody.CustodyError, match="existing_custody_requires_rotation_review"): - custody.provision("email-connect", PAIR, identity(), transport) - assert [call[0] for call in transport.calls] == ["metadata"] - - -def test_writer_policy_is_pair_scoped_and_cannot_read_or_delete(): - text = (ROOT / "openbao/policies/backup-pair-writer.hcl").read_text(encoding="utf-8") - for application in custody.PROFILES: - assert f'path "platform/data/workloads/{application}/backup-primary"' in text - assert f'path "platform/metadata/workloads/{application}/backup-primary"' in text - capabilities = [ - line.split("=", 1)[1] - for line in text.splitlines() - if line.strip().startswith("capabilities") - ] - assert capabilities - assert all("delete" not in line and "list" not in line for line in capabilities) - assert 'capabilities = ["read"]' in text - data_blocks = [ - block - for block in text.split("path ") - if "platform/data/" in block - ] - assert data_blocks - assert all('["create", "update"]' in block and "read" not in block for block in data_blocks) - - -@pytest.mark.parametrize( - "values", - [ - {"AWS_ACCESS_KEY_ID": PAIR["AWS_ACCESS_KEY_ID"]}, - dict(PAIR, AWS_SECRET_ACCESS_KEY="short"), - dict(PAIR, AWS_ACCESS_KEY_ID=PAIR["AWS_SECRET_ACCESS_KEY"]), - dict(PAIR, EXTRA="SYNTHETICEXTRAVALUE1"), - ], -) -def test_partial_or_embedded_pairs_are_refused(values): - with pytest.raises(custody.CustodyError): - custody.validate_pair(values) diff --git a/tests/test_application_recovery_access.py b/tests/test_application_recovery_access.py deleted file mode 100644 index 732f0f1..0000000 --- a/tests/test_application_recovery_access.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Synthetic access refusals; no live API or private custody values.""" -import base64 -import copy -import importlib.util -import sys -import unittest -from pathlib import Path - -SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" -sys.path.insert(0, str(SCRIPTS)) -spec = importlib.util.spec_from_file_location("access", SCRIPTS / "application_recovery_access.py") -m = importlib.util.module_from_spec(spec) -spec.loader.exec_module(m) - - -class AccessTests(unittest.TestCase): - def setUp(self): - self.contract = {"schema": "platform.coulomb-recovery-api.v1", "project_id": m.PROJECT, - "materials": {"reader-pair": {"id": "synthetic-id", "name": "synthetic-name", "revision": 1}}} - self.metadata = {"id": "synthetic-id", "name": "synthetic-name", "project_id": m.PROJECT, - "protected": True, "type": "opaque", "ephemeral_policy": None} - self.version = {"status": "enabled", "ephemeral_properties": {}} - self.accesses = 0 - - def api(self, method, path): - self.assertEqual(method, "GET") - if path.endswith("/access"): - self.accesses += 1 - return {"secret_id": "synthetic-id", "revision": 1, "data": base64.b64encode( - b'{"AWS_ACCESS_KEY_ID":"SYNTHETIC_ID","AWS_SECRET_ACCESS_KEY":"SYNTHETIC_VALUE"}').decode()} - if "/versions/" in path: - return self.version - return self.metadata - - def test_routine_retrieval_uses_only_protected_bound_scoped_secret(self): - payload = m.retrieve(self.api, self.contract, "reader-pair") - self.assertIn(b"SYNTHETIC_ID", payload) - self.assertEqual(self.accesses, 1) - - def test_project_name_protection_type_and_expiry_drift_refuse_before_data(self): - baseline = copy.deepcopy(self.metadata) - for key, value in (("project_id", "foreign"), ("name", "foreign"), ("protected", False), - ("type", "certificate"), ("ephemeral_policy", {"time_to_live": "60s"}), - ("deletion_requested_at", "synthetic-time")): - self.metadata = dict(baseline, **{key: value}) - with self.assertRaises(m.Refused): - m.retrieve(self.api, self.contract, "reader-pair") - self.assertEqual(self.accesses, 0) - - def test_disabled_or_expiring_version_refuses_before_data(self): - for version in ({"status": "disabled"}, {"status": "enabled", "ephemeral_properties": {"expires_at": "soon"}}, - {"status": "enabled", "ephemeral_properties": {"expires_once_accessed": True}}): - self.version = version - with self.assertRaises(m.Refused): - m.retrieve(self.api, self.contract, "reader-pair") - self.assertEqual(self.accesses, 0) - - def test_wrong_response_revision_refused(self): - def api(method, path): - result = self.api(method, path) - return dict(result, revision=2) if path.endswith("/access") else result - with self.assertRaises(m.Refused): - m.retrieve(api, self.contract, "reader-pair") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_application_recovery_drill.py b/tests/test_application_recovery_drill.py deleted file mode 100644 index 9c3cb83..0000000 --- a/tests/test_application_recovery_drill.py +++ /dev/null @@ -1,65 +0,0 @@ -"""Kit integrity failures must refuse before extracting executable material.""" -import hashlib -import io -import json -import sys -import tarfile -from pathlib import Path - -import pytest - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) -import application_recovery_drill as drill - - -def kit(path, mutation=None): - payload = b"synthetic recovery code" - manifest = {"schema": "platform.coulomb-recovery-kit-manifest.v1", - "private_identity_included": False, "api_pairs_included": False, - "files": {"tools/recover.py": {"bytes": len(payload), - "sha256": hashlib.sha256(payload).hexdigest()}}} - name = "../escape" if mutation == "traversal" else "tools/recover.py" - if mutation == "credential": - manifest["private_identity_included"] = True - with tarfile.open(path, "w") as archive: - data = json.dumps(manifest).encode() - meta = tarfile.TarInfo("manifest.json") - meta.size = len(data) - archive.addfile(meta, io.BytesIO(data)) - item = tarfile.TarInfo(name) - item.size = len(payload) - if mutation == "link": - item.type, item.linkname, item.size = tarfile.SYMTYPE, "/etc/passwd", 0 - actual = b"X" * len(payload) if mutation == "hash" else payload - archive.addfile(item, io.BytesIO(actual)) - if mutation == "duplicate": - archive.addfile(item, io.BytesIO(actual)) - - -def test_every_member_is_verified_before_extraction(tmp_path): - source = tmp_path / "kit.tar" - kit(source) - result = drill.extract_verified(source, tmp_path / "extracted") - assert (tmp_path / "extracted/tools/recover.py").read_bytes() == b"synthetic recovery code" - assert result["api_pairs_included"] is False - - -@pytest.mark.parametrize("mutation", ["traversal", "link", "duplicate", "hash", "credential"]) -def test_unsafe_or_corrupt_kit_never_extracts_code(tmp_path, mutation): - source = tmp_path / "kit.tar" - kit(source, mutation) - output = tmp_path / "extracted" - with pytest.raises(drill.Refused): - drill.extract_verified(source, output) - assert not output.exists() - - -def test_existing_extraction_is_preserved(tmp_path): - source = tmp_path / "kit.tar" - kit(source) - output = tmp_path / "existing" - output.mkdir() - (output / "keep").write_text("keep") - with pytest.raises(drill.Refused): - drill.extract_verified(source, output) - assert (output / "keep").read_text() == "keep" diff --git a/tests/test_application_recovery_identities.py b/tests/test_application_recovery_identities.py deleted file mode 100644 index f764b53..0000000 --- a/tests/test_application_recovery_identities.py +++ /dev/null @@ -1,239 +0,0 @@ -"""Synthetic provider responses only; no live identity or network access.""" - -import copy -import importlib.util -import json -import os -import tempfile -import unittest -from datetime import UTC, datetime, timedelta -from pathlib import Path -from unittest.mock import patch -from urllib.parse import parse_qs, urlsplit -from urllib.request import HTTPRedirectHandler, Request - -SOURCE = Path(__file__).resolve().parents[1] / "scripts/application_recovery_identities.py" -spec = importlib.util.spec_from_file_location("recovery_identities", SOURCE) -module = importlib.util.module_from_spec(spec) -spec.loader.exec_module(module) -NOW = datetime(2026, 10, 28, 10, tzinfo=UTC) - - -class Provider: - def __init__(self): - self.calls, self.projects, self.policies, self.keys = [], {}, {}, {} - self.apps = {module.PUBLISHER: {"id": module.PUBLISHER, "name": - "coulomb-social-backup-primary", "organization_id": "synthetic-org"}} - self.rules = {} - self.extra_org_rule = False - self.uncertain_key = False - self.key_http_error = None - self.foreign_key_response = False - self.unbounded_bootstrap = False - self.issued_keys = 0 - - def __call__(self, method, url, data=None): - self.calls.append((method, url, copy.deepcopy(data))) - path = urlsplit(url).path - if path.endswith("/permission-sets"): - names = sorted({p for role in module.ROLES.values() for p in role["permissions"]}) - return {"permission_sets": [{"name": n} for n in names], "total_count": str(len(names))} - if path.endswith("/rules"): - policy_id = parse_qs(urlsplit(url).query)["policy_id"][0] - rules = copy.deepcopy(self.rules[policy_id]) - if self.extra_org_rule: - rules[0]["organization_id"] = "synthetic-org" - return {"rules": rules} - for suffix, store, key in [("projects", self.projects, "projects"), - ("applications", self.apps, "applications"), - ("policies", self.policies, "policies"), - ("api-keys", self.keys, "api_keys")]: - if path.endswith("/" + suffix): - if method == "GET": - return {key: list(store.values()), "total_count": str(len(store))} - if method == "POST": - identity = suffix + "-" + str(len(store) + 1) - created = {"id": identity, **copy.deepcopy(data)} - if suffix == "policies": - self.rules[identity] = created.pop("rules") - if suffix == "api-keys": - self.issued_keys += 1 - identity = suffix + "-" + str(self.issued_keys) - created["id"] = identity - created.update(access_key=identity, - secret_key="SYNTHETIC_SECRET_" + identity) - if self.foreign_key_response: - created["application_id"] = "foreign-application" - if self.unbounded_bootstrap: - created["expires_at"] = None - store[identity] = created - if suffix == "api-keys" and self.uncertain_key: - raise OSError("synthetic lost acknowledgement") - if suffix == "api-keys" and self.key_http_error: - raise module.ProviderError(self.key_http_error) - return copy.deepcopy(created) - marker = "/" + suffix + "/" - if marker in path: - identity = path.split(marker, 1)[1] - if method == "DELETE": - del store[identity] - return {} - if identity not in store: - raise module.ProviderError(404) - return copy.deepcopy(store[identity]) - raise AssertionError("Unexpected synthetic request") - - -class IdentityBootstrap(unittest.TestCase): - def setUp(self): - self.temporary = tempfile.TemporaryDirectory(dir="/tmp") - self.addCleanup(self.temporary.cleanup) - self.directory = Path(self.temporary.name) - self.directory.chmod(0o700) - # Some workstations manage /tmp itself as a Git worktree. Isolate the - # synthetic provider cases; exercise the real Git guard separately below. - self.git_guard = patch.object(module, "inside_git", return_value=False) - self.git_guard.start() - self.addCleanup(self.git_guard.stop) - self.api = Provider() - - def run_bootstrap(self): - return module.Bootstrap(self.api, self.directory, NOW).run() - - def test_project_only_grants_private_pairs_no_publisher_rotation_and_idempotent_resume(self): - before = copy.deepcopy(self.api.apps[module.PUBLISHER]) - result = self.run_bootstrap() - self.assertFalse(result["organization_wide_grants"]) - self.assertFalse(result["native_s3_escrow_boundaries_verified"]) - policies = [d for method, path, d in self.api.calls - if method == "POST" and path.endswith("/policies")] - self.assertEqual(len(policies), 4) - for policy in policies: - self.assertEqual(policy["rules"][0]["project_ids"], ["projects-1"]) - self.assertNotIn("organization_id", policy["rules"][0]) - self.assertFalse(any(p.startswith(("IAM", "AllProducts")) - for p in policy["rules"][0]["permission_set_names"])) - self.assertEqual(self.api.apps[module.PUBLISHER], before) - self.assertFalse(any(k["application_id"] == module.PUBLISHER - for k in self.api.keys.values())) - for role in ["bootstrap", "reader", "custodian"]: - path = self.directory / (role + "-pair.json") - self.assertEqual(path.stat().st_mode & 0o777, 0o600) - self.assertIn("AWS_SECRET_ACCESS_KEY", json.loads(path.read_text())) - self.assertNotIn("SYNTHETIC_SECRET", (self.directory / "identity-state.json").read_text()) - self.assertNotIn("SYNTHETIC_SECRET", json.dumps(result)) - posts = sum(method == "POST" for method, _, _ in self.api.calls) - self.run_bootstrap() - self.assertEqual(sum(method == "POST" for method, _, _ in self.api.calls), posts) - - def test_untracked_existing_project_is_not_adopted_or_granted_access(self): - self.api.projects["foreign"] = {"id": "foreign", "name": module.PROJECT, - "organization_id": "synthetic-org"} - with self.assertRaisesRegex(module.Refused, "untracked_existing_project"): - self.run_bootstrap() - self.assertFalse(any(method == "POST" for method, _, _ in self.api.calls)) - - def test_expired_construction_key_renews_only_its_owned_role(self): - self.run_bootstrap() - routine = {r: (self.directory / (r + "-pair.json")).read_bytes() - for r in ("reader", "custodian")} - prior = json.loads((self.directory / "bootstrap-pair.json").read_text())["AWS_ACCESS_KEY_ID"] - result = module.Bootstrap(self.api, self.directory, NOW + timedelta(days=1)).run() - self.assertEqual(result["status"], "identity_bootstrap_complete_native_boundaries_pending") - self.assertNotIn(prior, self.api.keys) - self.assertEqual(len(self.api.keys), 3) - for role, value in routine.items(): - self.assertEqual((self.directory / (role + "-pair.json")).read_bytes(), value) - - def test_effective_org_wide_rule_refuses_before_any_key_is_issued(self): - self.api.extra_org_rule = True - with self.assertRaisesRegex(module.Refused, "effective_project_rule"): - self.run_bootstrap() - self.assertEqual(self.api.keys, {}) - - def test_unknown_key_outcome_stops_restart_without_minting_a_duplicate(self): - self.api.uncertain_key = True - with self.assertRaises(OSError): - self.run_bootstrap() - self.assertEqual(len(self.api.keys), 1) - calls = len(self.api.calls) - with self.assertRaisesRegex(module.Refused, "uncertain_key_issue"): - self.run_bootstrap() - self.assertEqual(len(self.api.calls), calls) - - def test_server_error_during_issuance_remains_uncertain_and_blocks_duplicate(self): - self.api.key_http_error = 503 - with self.assertRaises(module.ProviderError): - self.run_bootstrap() - calls = len(self.api.calls) - with self.assertRaisesRegex(module.Refused, "uncertain_key_issue"): - self.run_bootstrap() - self.assertEqual(len(self.api.calls), calls) - - def test_unbounded_bootstrap_key_is_revoked_and_never_delivered(self): - self.api.unbounded_bootstrap = True - with self.assertRaisesRegex(module.Refused, "pair_persistence_failed"): - self.run_bootstrap() - self.assertEqual(self.api.keys, {}) - self.assertFalse((self.directory / "bootstrap-pair.json").exists()) - - def test_mismatched_returned_application_never_authorizes_deleting_a_foreign_key(self): - self.api.foreign_key_response = True - with self.assertRaisesRegex(module.Refused, "pair_persistence_failed"): - self.run_bootstrap() - self.assertFalse(any(method == "DELETE" for method, _, _ in self.api.calls)) - self.assertIn("pending_key_issue", json.loads(( - self.directory / "identity-state.json").read_text())) - - def test_pair_write_failure_revokes_only_the_just_issued_owned_key(self): - real_open = os.open - - def fail_reader(path, *args, **kwargs): - if str(path).endswith("reader-pair.json"): - raise OSError("synthetic disk failure") - return real_open(path, *args, **kwargs) - - with (patch.object(module.os, "open", fail_reader), - self.assertRaisesRegex(module.Refused, "pair_persistence_failed")): - self.run_bootstrap() - deletions = [path for method, path, _ in self.api.calls if method == "DELETE"] - self.assertEqual(deletions, ["/iam/v1alpha1/api-keys/api-keys-2"]) - self.assertEqual(len(self.api.keys), 1) # Earlier persisted bootstrap pair is retained. - - def test_private_credentials_cannot_be_written_inside_a_git_checkout(self): - self.git_guard.stop() - (self.directory / ".git").mkdir() - with self.assertRaisesRegex(module.Refused, "outside_git"): - module.Bootstrap(self.api, self.directory, NOW) - self.assertEqual(self.api.calls, []) - - def test_bootstrap_retirement_revokes_only_temporary_key_and_retains_routine_pairs(self): - self.run_bootstrap() - before = {role: (self.directory / (role + "-pair.json")).read_bytes() - for role in ["reader", "custodian"]} - result = module.Bootstrap(self.api, self.directory, NOW).retire_bootstrap() - self.assertEqual(result["status"], "bootstrap_api_key_retired") - self.assertEqual(len(self.api.keys), 2) - self.assertFalse((self.directory / "bootstrap-pair.json").exists()) - for role, value in before.items(): - self.assertEqual((self.directory / (role + "-pair.json")).read_bytes(), value) - self.assertTrue(module.Bootstrap(self.api, self.directory, NOW).retire_bootstrap()) - - def test_retirement_cannot_delete_a_key_after_foreign_application_binding_drift(self): - self.run_bootstrap() - bootstrap_key = json.loads((self.directory / "bootstrap-pair.json").read_text()) - self.api.keys[bootstrap_key["AWS_ACCESS_KEY_ID"]]["application_id"] = "foreign" - with self.assertRaisesRegex(module.Refused, "retirement_key_binding_refused"): - module.Bootstrap(self.api, self.directory, NOW).retire_bootstrap() - self.assertFalse(any(method == "DELETE" for method, _, _ in self.api.calls)) - - def test_provider_redirect_cannot_forward_the_temporary_operator_token(self): - transport = module.Transport({"session_token": "synthetic-only", "user_agent": "test"}) - handler = next(h for h in transport.opener.handlers if isinstance(h, HTTPRedirectHandler)) - with self.assertRaisesRegex(module.Refused, "provider_redirect_refused"): - handler.redirect_request(Request("https://api.scaleway.com/"), None, 302, "", - {}, "https://example.invalid/") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_application_recovery_kit.py b/tests/test_application_recovery_kit.py deleted file mode 100644 index fc4c021..0000000 --- a/tests/test_application_recovery_kit.py +++ /dev/null @@ -1,120 +0,0 @@ -import hashlib -import io -import sys -from pathlib import Path - -import pytest - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) -import application_recovery_kit as kit - - -class Missing(Exception): - def __init__(self): - self.response = {"ResponseMetadata": {"HTTPStatusCode": 404}} - - -class Provider: - def __init__(self): - self.data, self.calls = None, [] - self.version = "synthetic-kit-version" - self.corrupt = False - self.rules = [] - - def get_bucket_versioning(self, **args): - return {"Status": "Enabled"} - - def get_bucket_lifecycle_configuration(self, **args): - return {"Rules": self.rules} - - def head_object(self, **args): - self.calls.append("head") - if self.data is None: - raise Missing() - return {"ContentLength": len(self.data), "VersionId": self.version, - "Metadata": {"sha256": hashlib.sha256(self.data).hexdigest()}} - - def put_object(self, **args): - self.calls.append("put") - assert args["Bucket"] == kit.BUCKET and args["IfNoneMatch"] == "*" - body = args["Body"] - self.data = body.read() if hasattr(body, "read") else body - return {"VersionId": self.version} - - def get_object(self, **args): - self.calls.append("get") - assert args["Bucket"] == kit.BUCKET and args["VersionId"] == self.version - body = b"X" * len(self.data) if self.corrupt else self.data - return {"VersionId": self.version, "ContentLength": len(body), "Body": io.BytesIO(body)} - - -@pytest.fixture -def fixture(tmp_path, monkeypatch): - tmp_path.chmod(0o700) - # Real outside-Git enforcement is covered in identity tests; isolate the - # synthetic provider paths from a workstation's possible /tmp Git ancestor. - monkeypatch.setattr(kit, "private_directory", kit.archive.private_directory) - source = tmp_path / "kit.tar" - source.write_bytes(b"synthetic-public-kit-no-private-keys") - source.chmod(0o600) - return source, Provider() - - -def test_immutable_versioned_kit_readback_and_idempotent_publication(fixture, tmp_path): - source, client = fixture - ref = kit.publish(client, source) - assert ref["automatic_expiry"] is False - assert ref["object_key"] == "kits/" + hashlib.sha256(source.read_bytes()).hexdigest() + "/recovery-kit.tar" - assert kit.download(client, ref, tmp_path / "fetched.tar")["independently_retrieved_hash_verified"] - assert kit.publish(client, source) == ref - assert client.calls.count("put") == 1 - - -def test_corruption_is_not_accepted_as_independent_kit(fixture, tmp_path): - source, client = fixture - ref = kit.publish(client, source) - client.corrupt = True - with pytest.raises(kit.Refused, match="kit_readback_hash_differs"): - kit.download(client, ref, tmp_path / "fetched.tar") - - -@pytest.mark.parametrize("field,value", [("project_id", "foreign"), ("bucket", "foreign"), - ("object_key", "kits/foreign.tar")]) -def test_foreign_reference_refused_before_get(fixture, tmp_path, field, value): - source, client = fixture - ref = kit.publish(client, source) - ref[field] = value - with pytest.raises(kit.Refused): - kit.download(client, ref, tmp_path / "fetched.tar") - assert "get" not in client.calls - - -def test_existing_output_is_never_overwritten(fixture, tmp_path): - source, client = fixture - ref = kit.publish(client, source) - output = tmp_path / "fetched.tar" - output.write_bytes(b"keep") - with pytest.raises(kit.Refused): - kit.download(client, ref, output) - assert output.read_bytes() == b"keep" - - -def test_protected_bucket_expiry_refuses_before_upload(fixture): - source, client = fixture - client.rules = [{"Expiration": {"Days": 30}, "Status": "Enabled"}] - with pytest.raises(kit.Refused, match="no_lifecycle_expiry"): - kit.publish(client, source) - assert client.calls == [] - - -def test_recovery_proof_is_conditionally_retained_and_independently_verified(): - client = Provider() - receipt = {"status": "application_restored_verified", "application": "coulomb-social"} - ref = kit.protect_receipt(client, client, receipt) - assert ref["independent_readback_verified"] is True - assert ref["object_key"] == "kits/receipts/" + ref["sha256"] + "/recovery-proof.json" - assert kit.protect_receipt(client, client, receipt) == ref - assert client.calls.count("put") == 1 - client.corrupt = True - with pytest.raises(kit.Refused, match="proof_readback_differs"): - kit.protect_receipt(client, client, receipt) diff --git a/tests/test_application_recovery_mirror.py b/tests/test_application_recovery_mirror.py deleted file mode 100644 index e7d0ba6..0000000 --- a/tests/test_application_recovery_mirror.py +++ /dev/null @@ -1,82 +0,0 @@ -"""Exercise the real multipart engine through the exact recovery remapping.""" -import copy -import hashlib -import importlib.util -import sys -from pathlib import Path - -import pytest - -SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" -sys.path.insert(0, str(SCRIPTS)) -import application_recovery_mirror as mirror - -spec = importlib.util.spec_from_file_location("primary_tests", Path(__file__).with_name("test_primary_application_archive.py")) -provider = importlib.util.module_from_spec(spec) -spec.loader.exec_module(provider) - - -def fixture(tmp_path, failure=None): - tmp_path.chmod(0o700) - source = tmp_path / "ciphertext.age" - data = b"age-encryption.org/v1\nsynthetic-encrypted-pair" - source.write_bytes(data) - source.chmod(0o600) - key = mirror.primary.key_for("coulomb-social", "2026-10-29T00:00:00Z") - receipt = {"schema": "platform.application-primary-archive.v1", "application": "coulomb-social", - "status": "offsite_fetched_verified", "object_key": key, "version_id": "primary-version", - "destination": "s3://" + mirror.primary.BUCKET + "/" + key, - "ciphertext_sha256": hashlib.sha256(data).hexdigest(), "ciphertext_bytes": len(data), - "remote_retrieval_verified": True, "immutable_create": True, - "captured_at": "2026-10-29T00:00:00Z", "image": "synthetic-image", - "age_recipient": "synthetic-recipient", "database_sha256": "a" * 64, "content_sha256": "b" * 64} - return source, receipt, provider.Provider(data, failure) - - -def test_multipart_mirror_and_independent_version_retrieval(tmp_path): - source, original, client = fixture(tmp_path) - before = copy.deepcopy(original) - journal = {} - receipt = mirror.mirror(client, source, tmp_path / "readback.age", original, journal, lambda: None) - assert original == before - assert receipt["version_id"] == "primary-version" - assert receipt["recovery_mirror"]["version_id"] == client.version - for _, args in client.calls: - assert args["Bucket"] == mirror.BUCKET - assert args["Key"] == "archives/" + original["object_key"] - client.calls = [] - result = mirror.retrieve(client, tmp_path / "independent.age", receipt) - assert result["provider_mutations"] is False - assert [name for name, _ in client.calls] == ["get"] - - -def test_uncertain_completion_reuses_same_mirror_without_reupload(tmp_path): - source, receipt, client = fixture(tmp_path, "lost-response") - journal = {} - with pytest.raises(TimeoutError): - mirror.mirror(client, source, tmp_path / "readback.age", receipt, journal, lambda: None) - client.failure = None - mirror.mirror(client, source, tmp_path / "retry.age", receipt, journal, lambda: None) - assert len([name for name, _ in client.calls if name == "start"]) == 1 - - -@pytest.mark.parametrize("field,value", [("bucket", "foreign"), ("project_id", "foreign"), - ("object_key", "archives/foreign"), ("ciphertext_sha256", "c" * 64)]) -def test_foreign_mirror_reference_refuses_before_contact(tmp_path, field, value): - source, receipt, client = fixture(tmp_path) - result = mirror.mirror(client, source, tmp_path / "readback.age", receipt, {}, lambda: None) - client.calls = [] - result["recovery_mirror"][field] = value - with pytest.raises(mirror.Refused): - mirror.retrieve(client, tmp_path / "independent.age", result) - assert client.calls == [] - - -def test_adapter_cannot_address_siblings_or_delete(tmp_path): - _, receipt, client = fixture(tmp_path) - adapter = mirror.Mirror(client) - with pytest.raises(mirror.Refused): - adapter.delete_object(Bucket=mirror.primary.BUCKET, Key=receipt["object_key"]) - with pytest.raises(ValueError): - adapter.get_object(Bucket=mirror.primary.BUCKET, Key="platform-pg/application-archives/email-connect/foreign") - assert client.calls == [] diff --git a/tests/test_application_recovery_resources.py b/tests/test_application_recovery_resources.py deleted file mode 100644 index 657de7f..0000000 --- a/tests/test_application_recovery_resources.py +++ /dev/null @@ -1,97 +0,0 @@ -import copy -import importlib.util -import sys -import unittest -from pathlib import Path -from unittest.mock import Mock, patch - -SCRIPTS = Path(__file__).resolve().parents[1] / "scripts" -sys.path.insert(0, str(SCRIPTS)) -spec = importlib.util.spec_from_file_location("resources", SCRIPTS / "application_recovery_resources.py") -m = importlib.util.module_from_spec(spec) -spec.loader.exec_module(m) - - -class ResourcesTests(unittest.TestCase): - def setUp(self): - self.apps = {r: "synthetic-" + r for r in ("bootstrap", "reader", "custodian", "publisher")} - - def grants(self, prefix, role): - policy = m.policy("synthetic-bucket", prefix, self.apps, "synthetic-owner") - return [s for s in policy["Statement"] - if s["Principal"]["SCW"] == "application_id:" + self.apps[role]] - - def test_reader_only_prefixed_read_and_list(self): - rows = self.grants("kits", "reader") - actions = {a for r in rows for a in r["Action"]} - self.assertTrue({"s3:GetObject", "s3:GetObjectVersion", "s3:ListBucketVersions"} <= actions) - self.assertFalse(any("Put" in a or "Delete" in a or a == "s3:*" for a in actions)) - objects = [r for r in rows if "s3:GetObject" in r["Action"]] - self.assertEqual(objects[0]["Resource"], "synthetic-bucket/kits/*") - listing = next(r for r in rows if "s3:ListBucket" in r["Action"]) - self.assertEqual(listing["Condition"]["StringLike"]["s3:prefix"], ["kits/", "kits/*"]) - - def test_publisher_has_no_kit_grant(self): - self.assertEqual(self.grants("kits", "publisher"), []) - - def test_publisher_can_abort_without_delete(self): - rows = self.grants("archives", "publisher") - actions = {a for r in rows for a in r["Action"]} - self.assertIn("s3:AbortMultipartUpload", actions) - self.assertIn("s3:PutObject", actions) - self.assertFalse(any("Delete" in a or "Policy" in a for a in actions)) - - def test_custodian_no_retained_delete_or_administration(self): - rows = self.grants("kits", "custodian") - actions = {a for r in rows for a in r["Action"]} - self.assertIn("s3:PutObject", actions) - self.assertFalse(any("Delete" in a or "Policy" in a or a == "s3:*" for a in actions)) - - def test_denial_requires_403_not_404_or_transport_error(self): - def failing(code): - raise m.NativeError(code) - self.assertTrue(m.denied(lambda: failing(403))["denied"]) - for code in (400, 401, 404, 500): - with self.assertRaises(m.Refused): - m.denied(lambda code=code: failing(code)) - with self.assertRaises(m.Refused): - m.denied(dict) - - def test_bucket_policy_preserves_owner_and_temporary_constructor(self): - p = m.policy("b", "kits", self.apps, "o") - self.assertEqual(p["Version"], "2023-04-17") - self.assertEqual(p["Statement"][0]["Principal"], {"SCW": "user_id:o"}) - self.assertTrue(all(s["Effect"] == "Allow" for s in p["Statement"])) - - def test_archive_policy_respects_native_ten_statement_limit(self): - p = m.policy("b", "archives", self.apps, "o") - self.assertLessEqual(len(p["Statement"]), 10) - shared = next(s for s in p["Statement"] if s["Sid"] == "ArchiveMetadata") - self.assertEqual(set(shared["Principal"]["SCW"]), - {"application_id:" + self.apps[r] for r in ("reader", "custodian", "publisher")}) - self.assertTrue(all(a.startswith("s3:Get") and "Object" not in a for a in shared["Action"])) - - def test_provider_policy_reordering_allowed_but_extra_permission_refused(self): - original = m.policy("b", "archives", self.apps, "o") - returned = copy.deepcopy(original) - returned["Statement"].reverse() - for row in returned["Statement"]: - if isinstance(row["Action"], list): - row["Action"].reverse() - self.assertEqual(m.canonical_policy(original), m.canonical_policy(returned)) - returned["Statement"][0]["Action"].append("s3:DeleteObject") - self.assertNotEqual(m.canonical_policy(original), m.canonical_policy(returned)) - - def test_negative_probe_uses_and_closes_its_own_transport(self): - resource = object.__new__(m.Resources) - resource.pairs, resource.project = {"reader": {}}, "synthetic-project" - client = Mock() - client.put_object.side_effect = m.NativeError(403) - with patch.object(m, "s3", return_value=client) as factory: - self.assertTrue(resource.deny_s3("reader", "put_object", Bucket="synthetic")["denied"]) - factory.assert_called_once_with({}, "synthetic-project") - client.close.assert_called_once() - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_argocd_adoption_proof.py b/tests/test_argocd_adoption_proof.py deleted file mode 100644 index 0253ace..0000000 --- a/tests/test_argocd_adoption_proof.py +++ /dev/null @@ -1,143 +0,0 @@ -import copy -from datetime import datetime, timezone, timedelta -import importlib.util -from pathlib import Path -import unittest -from unittest import mock - -SPEC=importlib.util.spec_from_file_location('adoption',Path(__file__).parents[1]/'scripts/argocd_adoption_proof.py') -p=importlib.util.module_from_spec(SPEC);SPEC.loader.exec_module(p) -NOW=datetime(2026,10,11,20,tzinfo=timezone.utc) - - -def fixture(): - return {'captured_at':NOW.isoformat(),'estate_ip':'92.205.62.239', - 'tracking_settings':{'method':'annotation','controller_version':'v3.5.3','installation_id_absent':True,'instance_label_key':'app.kubernetes.io/instance'}, - 'application':{'name':'openbao-secretstore','uid':'app-uid','project':p.CONTRACT['project'],'destination':p.CONTRACT['destination'], - 'source':{'repoURL':p.CONTRACT['repo_url'],'path':p.CONTRACT['source_path'],'targetRevision':p.PIN}, - 'revision':p.PIN,'automated_present':False,'pending_operation':False,'sync':'Synced','health':'Healthy', - 'condition_types':['OrphanedResourceWarning'],'sync_prune':False,'sync_strategy_apply':True, - 'operation_revision':p.PIN,'operation':{'phase':'Succeeded','startedAt':'2026-09-21T17:09:16Z','finishedAt':'2026-09-21T17:09:18Z'}, - 'resources':[dict(p.CONTRACT['resources'][0],status='Synced')]}, - 'ready_checks':[{'kind':c['kind'],'namespace':c['namespace'],'name':c['name'],'uid':'resource-uid', - 'ready':[{'status':'True','lastTransitionTime':'2026-08-19T19:49:59Z'}], - 'tracking_matches':True if 'tracking_id' in c else None} for c in p.CONTRACT['ready_checks']], - 'pinned_render_diff':{'revision':p.PIN,'path':p.CONTRACT['source_path'],'exit_code':0,'render_resource_count':1,'render_sha256':'a'*64, - 'client_side_exit_code':0,'normalized_render_sha256':'b'*64,'same_api_version':True, - 'normalization':'argocd-v3-annotation-tracking-destination-namespace', - 'tracking_settings':{'method':'annotation','controller_version':'v3.5.3','installation_id_absent':True,'instance_label_key':'app.kubernetes.io/instance'}, - 'normalized_client_changed_paths':[],'normalized_server_changed_paths':[], - 'raw_client_exit_code':1,'raw_server_exit_code':1, - 'raw_client_changed_paths':['/metadata/annotations/argocd.argoproj.io~1tracking-id','/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration'], - 'raw_server_changed_paths':['/metadata/annotations/kubectl.kubernetes.io~1last-applied-configuration']}} - - -class ProofTest(unittest.TestCase): - def test_complete_fixture_passes(self): - self.assertEqual(p.verify(fixture(),p.CONTRACT,now=NOW)['status'],'verified') - - def test_wrong_estate_stale_future_or_missing_observation_refused(self): - for values in [{'estate_ip':'other'},{'captured_at':(NOW-timedelta(minutes=6)).isoformat()}, - {'captured_at':(NOW+timedelta(seconds=1)).isoformat()},{'captured_at':'2026-10-11'}]: - with self.subTest(values=values), self.assertRaises(ValueError): - p.verify(fixture()|values,p.CONTRACT,now=NOW) - - def test_exact_pin_project_source_policy_and_health_refusals(self): - for key,value in [('revision','b'*40),('destination',{'server':'other'}),('project','other'),('automated_present',True), - ('pending_operation',True),('sync','OutOfSync'),('health','Degraded'), - ('condition_types',['ComparisonError']),('sync_prune',True), - ('sync_strategy_apply',False),('operation_revision','b'*40), - ('source',{'repoURL':'different'})]: - data=fixture();data['application'][key]=value - with self.subTest(key=key),self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - - def test_extra_duplicate_missing_or_wrong_tracked_resources_refused(self): - for resources in [[],[p.CONTRACT['resources'][0]]*2, - [dict(p.CONTRACT['resources'][0],name='another',status='Synced')], - [dict(p.CONTRACT['resources'][0],status='OutOfSync')]]: - data=fixture();data['application']['resources']=resources - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - - def test_proving_period_and_nonmonotonic_operation_refusals(self): - for operation in [{'phase':'Succeeded','startedAt':NOW.isoformat(),'finishedAt':NOW.isoformat()}, - {'phase':'Failed','startedAt':'2026-09-21T17:09:16Z','finishedAt':'2026-09-21T17:09:18Z'}, - {'phase':'Succeeded','startedAt':'2026-09-22T17:09:16Z','finishedAt':'2026-09-21T17:09:18Z'}]: - data=fixture();data['application']['operation']=operation - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - - def test_readiness_tracking_and_incomplete_health_refusals(self): - for key,value in [('tracking_matches',False),('ready',[]), - ('ready',[{'status':'False','lastTransitionTime':'2026-08-19T19:49:59Z'}]), - ('ready',[{'status':'True','lastTransitionTime':'2026-10-11T19:49:59Z'}])]: - data=fixture();data['ready_checks'][0][key]=value - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - data=fixture();data['ready_checks']=[] - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - - def test_nonzero_wrong_pin_missing_or_invalid_diff_refusals(self): - for key,value in [('exit_code',1),('exit_code',2),('revision','b'*40), - ('render_resource_count',2),('render_sha256','wrong'),('client_side_exit_code',1), - ('exit_code',False),('same_api_version',False),('normalization','unknown'), - ('normalized_client_changed_paths',['/spec/provider']), - ('normalized_server_changed_paths',['/metadata/labels/unknown']), - ('raw_client_changed_paths',['unknown']),('raw_server_changed_paths',['/spec/provider'])]: - data=fixture();data['pinned_render_diff'][key]=value - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - data=fixture();del data['pinned_render_diff'] - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - - def test_subprocess_failure_and_timeout_do_not_leak_output(self): - import subprocess - from types import SimpleNamespace - secret='CANARY-DO-NOT-EMIT' - with mock.patch.object(p.subprocess,'run',return_value=SimpleNamespace(returncode=1,stdout=secret,stderr=secret)): - with self.assertRaises(RuntimeError) as exc:p.checked_run(['kubectl','get','nodes']) - self.assertNotIn(secret,str(exc.exception)) - with mock.patch.object(p.subprocess,'run',side_effect=subprocess.TimeoutExpired('cmd',1,output=secret,stderr=secret)): - with self.assertRaises(RuntimeError) as exc:p.checked_run(['kubectl','get','nodes']) - self.assertNotIn(secret,str(exc.exception)) - - def test_argo_tracking_injection_preserves_source_spec_and_annotations(self): - source={'apiVersion':'external-secrets.io/v1beta1','kind':'ClusterSecretStore', - 'metadata':{'name':'openbao','annotations':{'source-owned':'preserve'}}, - 'spec':{'provider':{'vault':{'path':'platform'}}}} - original=copy.deepcopy(source) - settings=fixture()['tracking_settings'] - desired=p.argo_desired(source,p.CONTRACT,settings) - self.assertEqual(source,original) - self.assertEqual(desired['spec'],source['spec']) - self.assertEqual(desired['metadata']['annotations']['source-owned'],'preserve') - self.assertEqual(desired['metadata']['annotations']['argocd.argoproj.io/tracking-id'], - 'openbao-secretstore:external-secrets.io/ClusterSecretStore:external-secrets/openbao') - self.assertNotIn('labels',desired['metadata']) - - def test_unknown_tracking_semantics_conflicting_id_or_secret_render_refused(self): - source={'apiVersion':'external-secrets.io/v1beta1','kind':'ClusterSecretStore','metadata':{'name':'openbao'}} - settings=fixture()['tracking_settings'] - for delta in [{'method':'label'},{'controller_version':'unknown'},{'installation_id_absent':False}]: - with self.assertRaises(ValueError):p.argo_desired(source,p.CONTRACT,settings|delta) - for delta in [{'kind':'Secret'},{'apiVersion':'external-secrets.io/v1'}, - {'metadata':{'name':'openbao','namespace':'external-secrets'}}, - {'metadata':{'name':'openbao','annotations':{'argocd.argoproj.io/tracking-id':'foreign-owner'}}}]: - with self.assertRaises(ValueError):p.argo_desired(source|delta,p.CONTRACT,settings) - - def test_changed_paths_preserve_unknown_fields_and_never_expose_values(self): - before={'metadata':{'annotations':{'x/y~z':'CANARY-PRIVATE'}},'spec':{'path':'old'}} - after={'metadata':{'annotations':{'x/y~z':'OTHER-PRIVATE'}},'spec':{'path':'new'}} - paths=p.changed_paths(before,after) - self.assertEqual(paths,['/metadata/annotations/x~1y~0z','/spec/path']) - self.assertNotIn('CANARY',str(paths)) - self.assertNotIn('PRIVATE',str(paths)) - - def test_runtime_tracking_and_diagnostic_integrity_refusals(self): - for delta in [{'method':'label'},{'controller_version':'v2.14.0'},{'installation_id_absent':False}]: - data=fixture();data['tracking_settings'].update(delta) - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - for delta in [{'raw_client_changed_paths':None},{'raw_client_exit_code':0}, - {'raw_server_changed_paths':['unknown']},{'raw_server_exit_code':2}]: - data=fixture();data['pinned_render_diff'].update(delta) - with self.assertRaises(ValueError):p.verify(data,p.CONTRACT,now=NOW) - - def test_secret_read_contract_is_rejected(self): - contract=copy.deepcopy(p.CONTRACT);contract['ready_checks'][0]['kind']='Secret' - with self.assertRaises(ValueError):p.validate_contract(contract) diff --git a/tests/test_attended_service_delivery.py b/tests/test_attended_service_delivery.py deleted file mode 100644 index 6f9c45f..0000000 --- a/tests/test_attended_service_delivery.py +++ /dev/null @@ -1,40 +0,0 @@ -"""attended-service-credential-delivery contract (NetKingdom NK-WP-0056-T04); no values.""" -import copy -import importlib.util -from pathlib import Path - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location("asd", ROOT / "scripts/attended_service_delivery.py") -m = importlib.util.module_from_spec(spec) -spec.loader.exec_module(m) -CCR = yaml.safe_load((ROOT / "credential-change-requests/CCR-2026-0055-net-kingdom-privacyidea-bind.yaml").read_text()) - - -def test_checked_in_request_is_valid(): - assert m.validate_contract(CCR) == [] - - -def test_any_workload_access_is_refused(): - for change in ({"policy_name": "x"}, {"runtime_access": True}, {"initial_cas": 1}, - {"auth": {"method": "kubernetes", "mount": "kubernetes", "role": "x"}}): - ccr = copy.deepcopy(CCR); ccr["openbao"].update(change) - assert m.validate_contract(ccr), change - ccr = copy.deepcopy(CCR); ccr["access_frontdoor"]["resolvable"] = True - assert "runtime credential frontdoor must remain disabled" in m.validate_contract(ccr) - - -def test_delivery_and_lifecycle_are_required(): - ccr = copy.deepcopy(CCR); del ccr["delivery"]["gate"] - assert "delivery needs surface, service, target, procedure and gate" in m.validate_contract(ccr) - ccr = copy.deepcopy(CCR); ccr["lifecycle"]["rotate"] = "" - assert "lifecycle.rotate required" in m.validate_contract(ccr) - - -def test_applied_stage_needs_every_required_approval(): - ccr = copy.deepcopy(CCR); ccr["status"] = "verified" - ccr["review"]["comments"] = [{"reviewer": "User (platform-owner)", "decision": "approved"}] - assert m.validate_contract(ccr) == ["explicit role approval missing: net-kingdom-owner"] - ccr["review"]["comments"].append({"reviewer": "net-kingdom-owner", "decision": "approved"}) - assert m.validate_contract(ccr) == [] diff --git a/tests/test_audit_core_database_lease_recovery.py b/tests/test_audit_core_database_lease_recovery.py index ec45a1b..198b013 100644 --- a/tests/test_audit_core_database_lease_recovery.py +++ b/tests/test_audit_core_database_lease_recovery.py @@ -1,12 +1,10 @@ from __future__ import annotations import importlib.util -import argparse import json import os import tempfile import unittest -from unittest import mock from datetime import UTC, datetime, timedelta from pathlib import Path @@ -37,111 +35,10 @@ def approved_receipt(now: datetime) -> dict: for owner in ("audit-core", "rapp-postgres", "railiance-platform") }, "synthetic_load": {"contract_id": "load-1", "driver_revision": "abc123"}, - "synthetic_sender": { - "identity": "t02-synthetic-sender", - "source": "t02-synthetic", - "tenant": "tenant:trial:t02-synthetic", - "expires_at": "2026-08-22T18:15:00Z", - }, } class DatabaseLeaseRecoveryTests(unittest.TestCase): - def test_attest_chain_guard_refuses_active_job_and_schedule_overlap(self) -> None: - cronjob = {"spec": {"schedule": "17 3 * * *", "timeZone": "Etc/UTC"}, "status": {}} - quiet = datetime(2026, 10, 15, 10, 0, tzinfo=UTC) - module.assert_attest_chain_quiet(cronjob, quiet) - for near in (datetime(2026, 10, 15, 3, 0, tzinfo=UTC), datetime(2026, 10, 15, 3, 30, tzinfo=UTC)): - with self.assertRaises(module.ProcedureError): - module.assert_attest_chain_quiet(cronjob, near) - with self.assertRaises(module.ProcedureError): - module.assert_attest_chain_quiet({**cronjob, "status": {"active": [{"name": "j"}]}}, quiet) - with self.assertRaises(module.ProcedureError): - module.assert_attest_chain_quiet( - {"spec": {"schedule": "*/5 * * * *", "timeZone": "Etc/UTC"}, "status": {}}, quiet - ) - - def test_pod_state_ignores_attest_chain_job_pods(self) -> None: - class Remote: - def kubectl_json(self, args, label): - owned = lambda kind, uid: { - "metadata": {"uid": uid, "ownerReferences": [{"kind": kind}]}, - "status": {"containerStatuses": [{"restartCount": 0}]}, - } - return {"items": [owned("ReplicaSet", "serving"), owned("Job", "attest")]} - - self.assertEqual("serving", module.pod_state(Remote())["uid"]) - - def test_approval_requires_projected_synthetic_sender(self) -> None: - now = datetime(2026, 8, 22, 12, 0, tzinfo=UTC) - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / "approval.json" - path.write_text(json.dumps(approved_receipt(now)), encoding="utf-8") - self.assertTrue(module.load_approval(path, now, require_open_window=True)["synthetic_sender"]) - for sender in (None, {}, {"identity": "", "source": "s", "tenant": "t", "expires_at": "x"}): - path.write_text(json.dumps({**approved_receipt(now), "synthetic_sender": sender}), encoding="utf-8") - with self.assertRaises(module.ProcedureError): - module.load_approval(path, now, require_open_window=True) - - def test_select_runtime_lease_ignores_older_predecessors_only(self) -> None: - refresh = datetime(2026, 10, 14, 16, 14, 7, tzinfo=UTC) - def lease(name: str, issued: datetime) -> dict: - return {"id": name, "fingerprint": name, "issue_time": issued, - "expire_time": issued + timedelta(minutes=15), "ttl": 600} - current = lease("current", refresh + timedelta(seconds=1)) - older = lease("older", refresh - timedelta(minutes=15)) - secret = {"refresh_time": refresh} - selected = module.select_runtime_lease([older, current], secret) - self.assertEqual("current", selected["id"]) - self.assertEqual(1, selected["predecessor_handles"]) - with self.assertRaises(module.ProcedureError): # nothing matches the mounted refresh - module.select_runtime_lease([older], secret) - with self.assertRaises(module.ProcedureError): # a newer unmatched handle exists - module.select_runtime_lease([current, lease("newer", refresh + timedelta(minutes=1))], secret) - with self.assertRaises(module.ProcedureError): # two handles match the refresh - module.select_runtime_lease([current, lease("twin", refresh)], secret) - - def test_synthetic_e2_requires_t03_receipt_and_duplicate_replay(self) -> None: - now = datetime(2026, 8, 22, 12, 0, tzinfo=UTC) - with tempfile.TemporaryDirectory() as directory: - path = Path(directory) / "approval.json" - path.write_text(json.dumps(approved_receipt(now)), encoding="utf-8") - with self.assertRaises(module.ProcedureError): # a T02 receipt is not an E2 receipt - module.load_approval(path, now, require_open_window=True, task_id=module.E2_TASK_ID) - path.write_text(json.dumps({**approved_receipt(now), "task_id": module.E2_TASK_ID}), encoding="utf-8") - self.assertEqual(module.E2_TASK_ID, module.load_approval( - path, now, require_open_window=True, task_id=module.E2_TASK_ID)["task_id"]) - - class Driver: - def __init__(self, *sends): - self.sends = list(sends) - def run(self, phase): - if phase == "expect-recovered": - return {"status": self.sends.pop(0), "http_status": 202, "fixture_id": "f"} - return {"status": "clean", "fixture_id": "f"} - - args = argparse.Namespace(approval=path, confirm=module.E2_CONFIRM, remote="r", load_driver=Path("/d")) - same = {"uid": "u", "restart_count": 0} - pods = iter([same, same, same, same, same, {"uid": "changed", "restart_count": 0}]) - with mock.patch.object(module, "load_approval", wraps=lambda *a, **k: { - **approved_receipt(now), "task_id": module.E2_TASK_ID}), \ - mock.patch.object(module.PREFLIGHT, "Remote"), \ - mock.patch.object(module.PREFLIGHT, "endpoint_status", return_value=200), \ - mock.patch.object(module, "pod_state", side_effect=lambda r: next(pods)): - with mock.patch.object(module, "LoadDriver", return_value=Driver("accepted", "duplicate")): - result = module.synthetic_e2(args) - self.assertTrue(result["accepted_exactly_once"] and result["same_pod_uid"]) - self.assertEqual("accepted", result["first_send_status"]) - self.assertEqual("post-reboot-synthetic-e2", result["mode"]) - for sends in (("accepted", "accepted"), ("duplicate", "duplicate")): - # a second accept is not exactly once; a duplicate first send wrote nothing now - with mock.patch.object(module, "LoadDriver", return_value=Driver(*sends)): - with self.assertRaises(module.ProcedureError): - module.synthetic_e2(args) - with mock.patch.object(module, "LoadDriver", return_value=Driver("accepted", "duplicate")): - with self.assertRaises(module.ProcedureError): # a restarted pod is not this proof - module.synthetic_e2(args) - def test_approval_requires_exact_owner_and_window_contract(self) -> None: now = datetime(2026, 8, 22, 12, 0, tzinfo=UTC) with tempfile.TemporaryDirectory() as directory: diff --git a/tests/test_audit_core_recovery_preflight.py b/tests/test_audit_core_recovery_preflight.py index ea6a0eb..4632629 100644 --- a/tests/test_audit_core_recovery_preflight.py +++ b/tests/test_audit_core_recovery_preflight.py @@ -19,69 +19,6 @@ SPEC.loader.exec_module(module) class RecoveryPreflightTests(unittest.TestCase): - def test_installed_warden_must_equal_published_accepted_revision(self) -> None: - import subprocess - - with tempfile.TemporaryDirectory() as tmp: - repo, package = Path(tmp) / "ops-warden", Path(tmp) / "site" / "warden" - (repo / "src" / "warden").mkdir(parents=True) - (repo / "src" / "warden" / "proxy.py").write_text("accepted\n", encoding="utf-8") - git = lambda *a: subprocess.run(["git", "-C", str(repo), *a], check=True, capture_output=True) - git("init", "-q") - git("-c", "user.email=t@t", "-c", "user.name=t", "commit", "-q", "--allow-empty", "-m", "base") - git("add", ".") - git("-c", "user.email=t@t", "-c", "user.name=t", "commit", "-q", "-m", "accepted") - revision = git("rev-parse", "HEAD").stdout.decode().strip() - package.mkdir(parents=True) - (package / "proxy.py").write_text("accepted\n", encoding="utf-8") - with self.assertRaises(module.PreflightError): # not published yet - module.verify_installed_warden(package, repo, revision) - git("update-ref", "refs/remotes/origin/main", revision) - self.assertTrue(module.verify_installed_warden(package, repo, revision)["installed_revision_matches"]) - (package / "proxy.py").write_text("relaxed\n", encoding="utf-8") - with self.assertRaises(module.PreflightError): - module.verify_installed_warden(package, repo, revision) - (package / "proxy.py").write_text("accepted\n", encoding="utf-8") - (package / "extra.py").write_text("x\n", encoding="utf-8") - with self.assertRaises(module.PreflightError): - module.verify_installed_warden(package, repo, revision) - - def test_ops_warden_receipt_reads_current_decision_of_review_history(self) -> None: - revision = "a" * 40 - history = { - "workplan_id": "RPF-WP-0017", - "current_decision": "accept", - "current_reviewed_revision": revision, - "reviews": [{"decision": "reject"}, {"decision": "accept"}], - } - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp) / "receipt.json" - path.write_text(json.dumps(history), encoding="utf-8") - self.assertEqual(revision, module.validate_ops_warden_receipt(path)["accepted_revision"]) - path.write_text(json.dumps({**history, "current_decision": "reject"}), encoding="utf-8") - with self.assertRaises(module.PreflightError): - module.validate_ops_warden_receipt(path) - - def test_serving_pods_ignores_job_owned_attest_chain_pods(self) -> None: - def pod(name: str, kind: str) -> dict: - return {"metadata": {"name": name, "ownerReferences": [{"kind": kind}]}} - - pods = { - "items": [ - pod("audit-core-66767ddd58-x", "ReplicaSet"), - pod("audit-core-attest-chain-1", "Job"), - pod("audit-core-attest-chain-2", "Job"), - ] - } - self.assertEqual( - ["audit-core-66767ddd58-x"], - [item["metadata"]["name"] for item in module.serving_pods(pods)], - ) - - def test_serving_pods_keeps_two_deployment_pods_visible(self) -> None: - pods = {"items": [{"metadata": {"ownerReferences": [{"kind": "ReplicaSet"}]}}] * 2} - self.assertEqual(2, len(module.serving_pods(pods))) - def test_condition_selects_named_condition(self) -> None: resource = { "status": { @@ -135,7 +72,6 @@ class RecoveryPreflightTests(unittest.TestCase): "external_secrets": {"a": {"ready": True}, "b": {"ready": True}}, "audit_core": { "image_matches_reviewed_digest": True, - "running_pod_image_matches_reviewed_digest": True, "ready_replicas": 1, "replicas": 1, }, @@ -143,9 +79,6 @@ class RecoveryPreflightTests(unittest.TestCase): self.assertTrue(module.automated_common_pass(state)) state["openbao"]["sealed"] = True self.assertFalse(module.automated_common_pass(state)) - state["openbao"]["sealed"] = False - state["audit_core"]["running_pod_image_matches_reviewed_digest"] = False - self.assertFalse(module.automated_common_pass(state)) def test_expected_database_projection_is_exact_key_set(self) -> None: self.assertEqual( @@ -188,7 +121,6 @@ host: 50 bytes "encryption_verified": True, "hash_verified": True, "no_secret_material_recorded": True, - "custodian_escrow_ref": "founder-offline:test", } with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "receipt.json" @@ -226,7 +158,6 @@ host: 50 bytes "encryption_verified": True, "hash_verified": True, "no_secret_material_recorded": True, - "custodian_escrow_ref": "founder-offline:test", } with tempfile.TemporaryDirectory() as directory: path = Path(directory) / "receipt.json" @@ -240,234 +171,5 @@ host: 50 bytes ) -DIGEST = module.EXPECTED_IMAGE.rsplit("@", 1)[1] - - -def workload(ns: str, kind: str, name: str, ready: int = 1, replicas: int = 1) -> dict: - return { - "kind": kind, - "metadata": {"namespace": ns, "name": name}, - "spec": {"replicas": replicas}, - "status": {"readyReplicas": ready}, - } - - -def es_item(name: str, reason: str, status: str = "True", ns: str | None = None) -> dict: - meta = {"name": name, **({"namespace": ns} if ns else {})} - return {"metadata": meta, "status": {"conditions": [{"type": "Ready", "status": status, "reason": reason}]}} - - -def cnpg_cluster(primary: str = "pg-1", archiving: str | None = "True") -> dict: - cluster = { - "metadata": {"namespace": "databases", "name": "pg", "uid": "uid-1"}, - "spec": {"instances": 1, **({"backup": {"barmanObjectStore": {}}} if archiving else {})}, - "status": {"readyInstances": 1, "currentPrimary": primary, "healthyPVC": [primary], "phase": "Cluster in healthy state"}, - } - if archiving: - cluster["status"]["conditions"] = [{"type": "ContinuousArchiving", "status": archiving}] - return cluster - - -class NodeRebootPreflightTests(unittest.TestCase): - def test_running_pod_image_uses_resolved_digest(self) -> None: - pod = { - "spec": {"containers": [{"name": "app", "image": module.EXPECTED_IMAGE}]}, - "status": {"containerStatuses": [{"name": "app", "imageID": "forgejo.coulomb.social/coulomb/audit-core@" + DIGEST}]}, - } - self.assertEqual(DIGEST, module.running_pod_image(pod)["digest"]) - pod["status"]["containerStatuses"][0]["imageID"] = "x@sha256:" + "0" * 64 - self.assertNotEqual(DIGEST, module.running_pod_image(pod)["digest"]) - pod["status"]["containerStatuses"] = [] - self.assertIsNone(module.running_pod_image(pod)["digest"]) - - def test_core_workloads_require_named_and_all_in_namespace(self) -> None: - items = [workload(*key) for key in module.CORE_PLATFORM_WORKLOADS] - self.assertTrue(module.core_platform_workloads({"items": items})["ready"]) - result = module.core_platform_workloads({"items": items[1:]}) - self.assertEqual(["kube-system/Deployment/coredns"], result["missing"]) - extra = items + [workload("argocd", "Deployment", "argocd-server", ready=0)] - result = module.core_platform_workloads({"items": extra}) - self.assertEqual(["argocd/Deployment/argocd-server"], result["unready"]) - self.assertFalse(result["ready"]) - ignored = items + [workload("coulomb", "Deployment", "probe", ready=0)] - self.assertTrue(module.core_platform_workloads({"items": ignored})["ready"]) - - def test_failing_pods_and_baseline_diff(self) -> None: - def pod(name: str, phase: str, ready: bool = True) -> dict: - return {"metadata": {"namespace": "ns", "name": name}, - "status": {"phase": phase, "containerStatuses": [{"ready": ready}]}} - pods = {"items": [pod("ok", "Running"), pod("done", "Succeeded"), pod("bad", "Failed", False), - pod("pending", "Pending", False), pod("unready", "Running", False)]} - current = module.failing_pods(pods) - self.assertEqual(["ns/bad", "ns/pending", "ns/unready"], current) - diff = module.failing_pod_diff(["ns/bad", "ns/gone"], current) - self.assertEqual(["ns/pending", "ns/unready"], diff["new_failing"]) - self.assertEqual(["ns/gone"], diff["resolved"]) - self.assertFalse(diff["no_new_failures"]) - self.assertTrue(module.failing_pod_diff(current, current)["no_new_failures"]) - - def test_cnpg_identity_and_health(self) -> None: - self.assertEqual("7672", module.system_identifier("x\nDatabase system identifier: 7672\ny")) - self.assertIsNone(module.system_identifier("nothing")) - identity = module.cnpg_identity(cnpg_cluster(), {"pg-1": "pvc-uid"}) - identity["system_id"] = "7672" - self.assertEqual("pvc-uid", identity["primary_pvc_uid"]) - self.assertTrue(module.cnpg_healthy(identity)) - no_backup = module.cnpg_identity(cnpg_cluster(archiving=None), {"pg-1": "pvc-uid"}) - no_backup["system_id"] = "7672" - self.assertIsNone(no_backup["continuous_archiving"]) - self.assertTrue(module.cnpg_healthy(no_backup)) - broken = module.cnpg_identity(cnpg_cluster(archiving="False"), {"pg-1": "pvc-uid"}) - broken["system_id"] = "7672" - self.assertFalse(module.cnpg_healthy(broken)) - missing_pvc = module.cnpg_identity(cnpg_cluster(), {}) - missing_pvc["system_id"] = "7672" - self.assertFalse(module.cnpg_healthy(missing_pvc)) - - def test_backups_grouped_per_cluster(self) -> None: - backups = {"items": [ - {"metadata": {"namespace": "databases", "name": "b1"}, "spec": {"cluster": {"name": "platform-pg"}}, - "status": {"phase": "completed", "stoppedAt": "2026-10-14T00:00:00Z"}}, - {"metadata": {"namespace": "databases", "name": "b2"}, "spec": {"cluster": {"name": "apps-pg"}}, - "status": {"phase": "failed"}}, - ]} - result = module.latest_backups_by_cluster(backups, datetime(2026, 10, 14, 2, tzinfo=UTC)) - self.assertEqual(2.0, result["databases/platform-pg"]["age_hours"]) - self.assertIsNone(result["databases/apps-pg"]) - - def test_eso_inventory_requires_every_store_and_projection(self) -> None: - stores = {"items": [es_item("a", "Valid"), es_item("b", "Valid")]} - projections = {"items": [es_item("x", "SecretSynced", ns="n")]} - self.assertTrue(module.eso_inventory(stores, projections)["ready"]) - stores["items"].append(es_item("c", "InvalidProviderConfig", "False")) - projections["items"].append(es_item("y", "SecretSyncedError", "False", ns="n")) - result = module.eso_inventory(stores, projections) - self.assertEqual(["c"], result["invalid_cluster_stores"]) - self.assertEqual(["n/y"], result["unsynced_external_secrets"]) - self.assertFalse(result["ready"]) - self.assertFalse(module.eso_inventory({"items": []}, {"items": []})["ready"]) - - def test_ufw_matches_declaration_only(self) -> None: - ok = """Status: active - -To Action From --- ------ ---- -OpenSSH ALLOW Anywhere -2224/tcp ALLOW Anywhere # nydus -OpenSSH (v6) ALLOW Anywhere (v6) -2224/tcp (v6) ALLOW Anywhere (v6) # nydus -""" - self.assertTrue(module.ufw_state(ok)["matches_declaration"]) - opened = ok + "6443/tcp ALLOW Anywhere\n" - result = module.ufw_state(opened) - self.assertEqual(["6443/tcp"], result["forbidden_ports_open"]) - self.assertFalse(result["matches_declaration"]) - self.assertFalse(module.ufw_state("Status: inactive\n")["matches_declaration"]) - - def test_boot_count_and_reboot_comparison(self) -> None: - pre_time = datetime(2026, 10, 14, 8, tzinfo=UTC) - after = int((pre_time + timedelta(minutes=5)).timestamp() * 1_000_000) - before = int((pre_time - timedelta(days=1)).timestamp() * 1_000_000) - boots = json.dumps([{"boot_id": "a", "first_entry": before}, {"boot_id": "b", "first_entry": after}]) - self.assertEqual(1, module.boots_since(boots, pre_time)) - db = {"databases/pg": {"uid": "u", "primary_pvc_uid": "p", "system_id": "s"}} - pre = {"host": {"boot_id": "a"}, "cnpg_clusters": db, "failing_pods": ["ns/old"]} - post = {"host": {"boot_id": "b"}, "cnpg_clusters": json.loads(json.dumps(db)), "failing_pods": ["ns/old"]} - self.assertTrue(module.reboot_comparison(pre, post, 1)["passed"]) - self.assertFalse(module.reboot_comparison(pre, post, 2)["exactly_one_reboot"]) - same_boot = {**post, "host": {"boot_id": "a"}} - self.assertFalse(module.reboot_comparison(pre, same_boot, 1)["passed"]) - post["cnpg_clusters"]["databases/pg"]["system_id"] = "rebootstrapped" - result = module.reboot_comparison(pre, post, 1) - self.assertEqual(["databases/pg:system_id"], result["database_identity_changes"]) - self.assertFalse(result["passed"]) - post["cnpg_clusters"] = {} - self.assertFalse(module.reboot_comparison(pre, post, 1)["passed"]) - - def test_consumed_decisions_and_scenario_are_not_reusable(self) -> None: - gates = module.fresh_decision_gates( - "WARDEN-WP-0027-T02-DRILL-20260822-01", "449a697a-5303-4582-aa9e-b0bc8b35ab2d" - ) - self.assertFalse(any(gates.values())) - self.assertFalse(module.fresh_decision_gates("x", "85724c0c-e70f")["fresh_human_decision_recorded"]) - gates = module.fresh_decision_gates("RAILIANCE-WP-0024-T03-REBOOT-20261020-01", "0123abcd-new") - self.assertTrue(all(gates.values())) - self.assertFalse(any(module.fresh_decision_gates(None, None).values())) - - def _write(self, directory: str, name: str, text: str) -> Path: - path = Path(directory) / name - path.write_text(text, encoding="utf-8") - return path - - def test_ops_warden_receipt_requires_acceptance_and_revision(self) -> None: - base = {"workplan_id": "RPF-WP-0017", "reviewed_revision": "a" * 40, - "secret_values_observed": False, "sensitive_material_recorded": False} - with tempfile.TemporaryDirectory() as directory: - ok = self._write(directory, "ok.json", json.dumps({**base, "decision": "accept"})) - self.assertEqual("a" * 40, module.validate_ops_warden_receipt(ok)["accepted_revision"]) - for name, body in ( - ("reject.json", {**base, "decision": "reject"}), - ("norev.json", {**base, "decision": "accept", "reviewed_revision": "HEAD"}), - ("other.json", {**base, "decision": "accept", "workplan_id": "RPF-WP-0099"}), - ("leak.json", {**base, "decision": "accept", "note": "hvs.abc"}), - ("sensitive.json", {**base, "decision": "accept", "sensitive_material_recorded": True}), - ): - with self.assertRaises(module.PreflightError, msg=name): - module.validate_ops_warden_receipt(self._write(directory, name, json.dumps(body))) - with self.assertRaises(module.PreflightError): - module.validate_ops_warden_receipt(Path(directory) / "missing.json") - - def test_console_receipt_same_day_window_and_independent(self) -> None: - now = datetime(2026, 10, 15, 9, 0, tzinfo=UTC) - good = { - "receipt_id": "RAIL01-CONSOLE-20261015T080000Z", "host": "railiance01", - "attested_by_role": "host-europe-account-holder", "attested_at": "2026-10-15T08:00:00Z", - "panel_login": "ok", "mfa_used": True, "console_mechanism": "VNC console", - "console_shows_host": True, "independent_of_guest_network": True, - "rescue_mode_offered": True, "hard_reboot_offered": True, "reboot_window_id": "WIN-20261015-01", - } - name = good["receipt_id"] + ".yaml" - with tempfile.TemporaryDirectory() as directory: - ok = self._write(directory, name, json.dumps(good)) # JSON is valid YAML - result = module.validate_console_receipt(ok, window_id="WIN-20261015-01", now=now) - self.assertTrue(result["verified"]) - with self.assertRaises(module.PreflightError): - module.validate_console_receipt(ok, window_id="WIN-OTHER", now=now) - with self.assertRaises(module.PreflightError): - module.validate_console_receipt(ok, window_id="WIN-20261015-01", now=now + timedelta(days=1)) - for change in ( - {"independent_of_guest_network": "unknown"}, - {"console_shows_host": False}, - {"panel_login": "failed"}, - {"console_mechanism": ""}, - {"note": "password=hunter2"}, - ): - path = self._write(directory, name, json.dumps({**good, **change})) - with self.assertRaises(module.PreflightError, msg=str(change)): - module.validate_console_receipt(path, window_id="WIN-20261015-01", now=now) - renamed = self._write(directory, "RAIL01-CONSOLE-20261015T070000Z.yaml", json.dumps(good)) - with self.assertRaises(module.PreflightError): - module.validate_console_receipt(renamed, window_id="WIN-20261015-01", now=now) - - def test_reboot_automated_pass_names_each_boundary(self) -> None: - extra = { - "host": {"ntp_synchronized": True, "firewall": {"matches_declaration": True}}, - "k3s_api_tunnel_answers": True, - "core_platform_workloads": {"ready": True}, - "missing_expected_cnpg_clusters": [], - "cnpg_clusters": {}, - "cnpg_backups": {"databases/platform-pg": {"age_hours": 3}}, - "eso": {"ready": True}, - } - self.assertTrue(all(module.reboot_automated_pass(extra, 26).values())) - extra["host"]["ntp_synchronized"] = False - extra["missing_expected_cnpg_clusters"] = ["databases/state-hub-db"] - checks = module.reboot_automated_pass(extra, 26) - self.assertFalse(checks["ntp_synchronized"]) - self.assertFalse(checks["all_cnpg_clusters_healthy"]) - extra["cnpg_backups"] = {} - self.assertFalse(module.reboot_automated_pass(extra, 26)["required_backups_fresh"]) - - if __name__ == "__main__": unittest.main() diff --git a/tests/test_audit_core_synthetic_sender_lane.py b/tests/test_audit_core_synthetic_sender_lane.py deleted file mode 100644 index 62b7195..0000000 --- a/tests/test_audit_core_synthetic_sender_lane.py +++ /dev/null @@ -1,281 +0,0 @@ -from __future__ import annotations - -import argparse -import importlib.util -import io -import json -import os -import tempfile -import unittest -from contextlib import redirect_stderr, redirect_stdout -from datetime import UTC, datetime, timedelta -from pathlib import Path -from unittest import mock - - -ROOT = Path(__file__).resolve().parents[1] -SPEC = importlib.util.spec_from_file_location( - "audit_core_synthetic_sender_lane", - ROOT / "scripts" / "audit_core_synthetic_sender_lane.py", -) -assert SPEC and SPEC.loader -lane = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(lane) - -CONTRACT = "RAILIANCE-WP-0024-T02-SYNTHETIC-01" - - -def z(value: datetime) -> str: - return value.astimezone(UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") - - -def receipt(now: datetime, **sender: str) -> dict: - start = now - timedelta(minutes=1) - end = start + timedelta(minutes=15) - return { - "procedure": lane.LEASE.PROCEDURE, - "task_id": lane.LEASE.TASK_ID, - "status": "approved", - "approval_id": "t02-window-1", - "window": {"start": z(start), "end": z(end)}, - "abort_operator": "operator:example", - "owners": { - owner: {"acknowledged": True, "message_id": f"msg-{owner}"} - for owner in ("audit-core", "rapp-postgres", "railiance-platform") - }, - "synthetic_load": {"contract_id": CONTRACT, "driver_revision": "sha256:" + "0" * 64}, - "synthetic_sender": { - "identity": "t02-synthetic-1", - "source": "railiance-t02-synthetic", - "tenant": "tenant:trial:t02-synthetic-1", - "expires_at": z(end), - **sender, - }, - } - - -class FakeOperator: - def __init__(self, registry: list) -> None: - self.registry_value = registry - self.writes: list[list] = [] - - def registry(self) -> list: - return json.loads(json.dumps(self.registry_value)) - - def write_registry(self, registry: list) -> None: - self.writes.append(registry) - self.registry_value = json.loads(json.dumps(registry)) - - -class SyntheticSenderLaneTests(unittest.TestCase): - def setUp(self) -> None: - self.temp = tempfile.TemporaryDirectory() - self.dir = Path(self.temp.name) - os.chmod(self.dir, 0o700) - self.now = datetime.now(UTC) - - def tearDown(self) -> None: - self.temp.cleanup() - - def write_receipt(self, document: dict) -> Path: - path = self.dir / "receipt.json" - path.write_text(json.dumps(document), encoding="utf-8") - return path - - def test_receipt_yields_exactly_one_scoped_expiring_identity(self) -> None: - _, spec = lane.load_receipt(self.write_receipt(receipt(self.now)), self.now, require_open_window=True) - entry = lane.registry_entry(spec, "bearer") - self.assertEqual(["railiance-t02-synthetic"], entry["sources"]) - self.assertEqual(["tenant:trial:t02-synthetic-1"], entry["tenants"]) - self.assertTrue(entry["may_write"] and entry["may_read"]) - self.assertLessEqual(lane.LEASE.parse_time(entry["expires_at"]), lane.LEASE.parse_time(receipt(self.now)["window"]["end"])) - - def test_receipt_rejections(self) -> None: - end = lane.LEASE.parse_time(receipt(self.now)["window"]["end"]) - bad = [ - {"source": "*"}, - {"source": "user-engine"}, - {"tenant": "*"}, - {"tenant": "tenant:friendly:binky"}, - {"identity": "user-engine"}, - {"expires_at": z(end + timedelta(seconds=1))}, - {"expires_at": "2026-10-14T10:00:00+00:00"}, - ] - for override in bad: - with self.subTest(override=override): - with self.assertRaises(lane.LaneError): - lane.sender_spec(receipt(self.now, **override)) - missing = receipt(self.now) - del missing["synthetic_sender"] - with self.assertRaises(lane.LaneError): - lane.sender_spec(missing) - extra = receipt(self.now) - extra["synthetic_sender"]["tokens"] = ["x"] - with self.assertRaises(lane.LaneError): - lane.sender_spec(extra) - wrong_contract = receipt(self.now) - wrong_contract["synthetic_load"]["contract_id"] = "OTHER" - with self.assertRaises(lane.LaneError): - lane.sender_spec(wrong_contract) - - def test_pending_example_receipt_is_refused(self) -> None: - with self.assertRaises(lane.LEASE.ProcedureError): - lane.load_receipt(ROOT / "docs" / "audit-core-database-lease-approval.example.json", self.now, require_open_window=False) - - def test_projection_time_gate(self) -> None: - document = receipt(self.now) - spec = lane.sender_spec(document) - lane.assert_projection_time(document, spec, self.now) - start = lane.LEASE.parse_time(document["window"]["start"]) - for when in (start - timedelta(seconds=1), start + lane.PROJECT_CUTOFF + timedelta(seconds=1)): - with self.assertRaises(lane.LaneError): - lane.assert_projection_time(document, spec, when) - short = receipt(self.now, expires_at=z(self.now + timedelta(minutes=5))) - with self.assertRaises(lane.LaneError): - lane.assert_projection_time(short, lane.sender_spec(short), self.now) - - def test_registry_add_refuses_overlap_and_remove_is_exact(self) -> None: - spec = lane.sender_spec(receipt(self.now)) - production = [{"name": "user-engine", "tokens": ["p"], "sources": ["user-engine"], "tenants": ["*"]}] - updated = lane.add_identity(production, spec, "bearer") - self.assertEqual(2, len(updated)) - with self.assertRaises(lane.LaneError): - lane.add_identity(updated, spec, "again") - shared_source = [{"name": "x", "sources": [spec["source"]], "tenants": ["tenant:trial:x"]}] - self.assertTrue(lane.registry_conflicts(shared_source, spec)) - cleaned, removed = lane.remove_identity(updated, spec) - self.assertEqual((production, 1), (cleaned, removed)) - self.assertEqual((production, 0), lane.remove_identity(production, spec)) - hijacked = [{"name": spec["name"], "sources": ["user-engine"], "tenants": ["*"]}] - with self.assertRaises(lane.LaneError): - lane.remove_identity(hijacked, spec) - - def test_bearer_file_is_new_0600_outside_git(self) -> None: - target = self.dir / "t02.bearer" - lane.write_bearer(target, "value") - self.assertEqual(0o600, target.stat().st_mode & 0o777) - lane.check_bearer_path(target, must_exist=True) - with self.assertRaises(lane.LaneError): - lane.write_bearer(target, "value") - with self.assertRaises(lane.LaneError): - lane.check_bearer_path(ROOT / "t02.bearer", must_exist=False) - with self.assertRaises(lane.LaneError): - lane.check_bearer_path(Path("relative.bearer"), must_exist=False) - open_dir = self.dir / "open" - open_dir.mkdir(mode=0o755) - os.chmod(open_dir, 0o755) - with self.assertRaises(lane.LaneError): - lane.check_bearer_path(open_dir / "t02.bearer", must_exist=False) - - def test_cleanup_requires_matching_evidence_or_abort_reason(self) -> None: - approval = receipt(self.now) - with self.assertRaises(lane.LaneError): - lane.assert_evidence(None, None, approval) - evidence = self.dir / "evidence.json" - evidence.write_text(json.dumps({ - "procedure": lane.LEASE.PROCEDURE, "approval_id": "other", - "completed_at": z(self.now), "secret_values_observed": False, - }), encoding="utf-8") - with self.assertRaises(lane.LaneError): - lane.assert_evidence(evidence, None, approval) - evidence.write_text(json.dumps({ - "procedure": lane.LEASE.PROCEDURE, "approval_id": approval["approval_id"], - "completed_at": z(self.now), "secret_values_observed": False, - }), encoding="utf-8") - self.assertEqual("evidence-recorded", lane.assert_evidence(evidence, None, approval)["mode"]) - self.assertEqual("abort", lane.assert_evidence(None, "readiness never dropped", approval)["mode"]) - - def test_cleanup_registry_is_idempotent_and_restarts_only_on_change(self) -> None: - spec = lane.sender_spec(receipt(self.now)) - operator = FakeOperator(lane.add_identity([], spec, "bearer")) - with mock.patch.object(lane.CREDS, "force_sender_sync_and_restart") as restart: - self.assertEqual(1, lane._cleanup_registry(operator, spec)) - self.assertEqual(0, lane._cleanup_registry(operator, spec)) - self.assertEqual(1, restart.call_count) - self.assertEqual([], operator.registry_value) - - def test_project_rolls_back_registry_and_bearer_on_restart_failure(self) -> None: - approval_path = self.write_receipt(receipt(self.now)) - approval = json.loads(approval_path.read_text()) - bearer = self.dir / "t02.bearer" - operator = FakeOperator([{"name": "user-engine", "tokens": ["p"], "sources": ["user-engine"], "tenants": ["*"]}]) - args = argparse.Namespace( - command="project", approval=approval_path, confirm=lane.confirm_token(approval), - bearer_file=bearer, remote="railiance01", contained=True, - ) - green = {"automated_checks_passed": True, "ready": "1/1", "pod_uid": "uid-1"} - restart = mock.Mock(side_effect=[lane.CREDS.ProcedureError("rollout failed"), None]) - with mock.patch.object(lane.PREFLIGHT, "Remote"), \ - mock.patch.object(lane, "cluster_state", return_value=green), \ - mock.patch.object(lane.LEASE, "attest_chain_state"), \ - mock.patch.object(lane, "contained_operator", return_value=operator), \ - mock.patch.object(lane.CREDS, "force_sender_sync_and_restart", restart): - with self.assertRaises(lane.CREDS.ProcedureError): - lane.project(args) - self.assertFalse(bearer.exists()) - self.assertEqual(["user-engine"], [entry["name"] for entry in operator.registry_value]) - self.assertEqual(2, restart.call_count) - - def test_project_requires_exact_confirmation_and_no_bearer_on_stdout(self) -> None: - approval_path = self.write_receipt(receipt(self.now)) - stdout, stderr = io.StringIO(), io.StringIO() - with redirect_stdout(stdout), redirect_stderr(stderr): - code = lane.main(["project", "--approval", str(approval_path), "--confirm", "wrong", - "--bearer-file", str(self.dir / "b")]) - self.assertEqual(1, code) - self.assertIn("--confirm audit-core-synthetic-sender:t02-window-1:attended", stderr.getvalue()) - self.assertEqual("", stdout.getvalue()) - - def test_live_commands_refuse_outside_contained_login(self) -> None: - approval_path = self.write_receipt(receipt(self.now)) - approval = json.loads(approval_path.read_text()) - base = dict(approval=approval_path, confirm=lane.confirm_token(approval), - bearer_file=self.dir / "t02.bearer", remote="railiance01", - exercise_evidence=None, abort_reason="test") - for command in ("project", "cleanup"): - with self.subTest(command=command): - with self.assertRaisesRegex(lane.LaneError, "--contained"): - getattr(lane, command)(argparse.Namespace(command=command, contained=False, **base)) - contained = {"WARDEN_ROUTING_CATALOG": "/catalog.yaml", "BAO_ADDR": lane.LEASE.TUNNEL_ADDR} - refusals = [ - {}, - {**contained, "WARDEN_ROUTING_CATALOG": ""}, - {**contained, "BAO_ADDR": "https://bao.coulomb.social"}, - {**contained, "BAO_TOKEN": "x"}, - {**contained, "VAULT_TOKEN": "x"}, - ] - for env in refusals: - with self.subTest(env=sorted(env)): - with mock.patch.dict(os.environ, env, clear=True): - with self.assertRaises(lane.LEASE.ProcedureError): - lane.ContainedOperator("railiance01") - with mock.patch.dict(os.environ, contained, clear=True): - operator = lane.ContainedOperator("railiance01") - self.assertEqual(lane.LEASE.TUNNEL_ADDR, operator.bao_env["BAO_ADDR"]) - self.assertNotIn("BAO_TOKEN", operator.bao_env) - - def test_harness_exercise_requires_contained(self) -> None: - stderr = io.StringIO() - with redirect_stderr(stderr): - code = lane.LEASE.main(["exercise", "--approval", str(self.write_receipt(receipt(self.now))), - "--load-driver", "/bin/true", "--confirm", lane.LEASE.CONFIRM]) - self.assertEqual(1, code) - self.assertIn("--contained", stderr.getvalue()) - with mock.patch.dict(os.environ, {"BAO_ADDR": lane.LEASE.TUNNEL_ADDR}, clear=True): - with self.assertRaises(lane.LEASE.ProcedureError): - lane.LEASE.Authority() - - def test_offline_preflight_is_value_safe(self) -> None: - stdout = io.StringIO() - with redirect_stdout(stdout): - code = lane.main(["preflight", "--offline", "--approval", str(self.write_receipt(receipt(self.now))), - "--bearer-file", str(self.dir / "t02.bearer")]) - self.assertEqual(0, code) - result = json.loads(stdout.getvalue()) - self.assertEqual("projection-window-open", result["time_state"]) - self.assertFalse(result["secret_values_observed"]) - self.assertNotIn("tokens", json.dumps(result)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_backup_credential_contract_export.py b/tests/test_backup_credential_contract_export.py deleted file mode 100644 index 1b52338..0000000 --- a/tests/test_backup_credential_contract_export.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Public export preserves the canonical YAML approval state and exact policy.""" - -import hashlib -import importlib.util -import json -import sys -from pathlib import Path - -import pytest -import yaml - -ROOT = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(ROOT / "scripts")) -spec = importlib.util.spec_from_file_location( - "exporter", ROOT / "scripts/render_backup_credential_contract.py" -) -e = importlib.util.module_from_spec(spec) -spec.loader.exec_module(e) - - -@pytest.mark.parametrize("application", e.PROFILES) -def test_owner_yaml_updates_export_without_node_yaml_dependency(application, tmp_path): - source = ( - ROOT - / "credential-change-requests" - / f"{e.PROFILES[application]['ccr']}-{application}-backup-primary.yaml" - ) - ccr = yaml.safe_load(source.read_text()) - ccr["status"] = "applied" - ccr["openbao"]["auth"]["bound_claims_confirmed"] = True - ccr["review"]["comments"] = [] - ccr["delivery"]["admission"] = { - "node_executor_approved": False, - "provider_scope_verified": False, - "reader_boundary_verified": False, - "source_ref": None, - } - candidate = tmp_path / "reviewed.yaml" - candidate.write_text(yaml.safe_dump(ccr)) - raw = e.render(candidate, application) - exported = json.loads(raw) - assert exported["status"] == "applied" - assert exported["source_ccr_sha256"] == hashlib.sha256(candidate.read_bytes()).hexdigest() - assert len(exported["policy_sha256"]) == 64 - # Export is a representation, not an approval: missing reviews/admission fail. - with pytest.raises(Exception): - e.contract(raw, application, True, hashlib.sha256(raw).hexdigest()) - - -def test_export_rejects_source_secret_marker(tmp_path): - source = ROOT / "credential-change-requests/CCR-2026-0036-coulomb-social-backup-primary.yaml" - ccr = json.loads(source.read_bytes()) - ccr["accidental_value"] = "AGE-SECRET-KEY-1SYNTHETIC" - candidate = tmp_path / "bad.yaml" - candidate.write_text(yaml.safe_dump(ccr)) - with pytest.raises(ValueError, match="invalid_reader_contract"): - e.render(candidate, "coulomb-social") - - -def test_export_cannot_change_reader_namespace(tmp_path): - source = ROOT / "credential-change-requests/CCR-2026-0036-coulomb-social-backup-primary.yaml" - ccr = json.loads(source.read_bytes()) - ccr["openbao"]["auth"]["bound_claims"]["service_account_namespaces"] = ["email-connect"] - candidate = tmp_path / "bad.yaml" - candidate.write_text(yaml.safe_dump(ccr)) - with pytest.raises(Exception): - e.render(candidate, "coulomb-social") diff --git a/tests/test_broker_grant_lane.py b/tests/test_broker_grant_lane.py deleted file mode 100644 index 2cb084f..0000000 --- a/tests/test_broker_grant_lane.py +++ /dev/null @@ -1,155 +0,0 @@ -"""RPF-WP-0054-T04: custody-generate-grant lane (CCR-2026-0040).""" -from __future__ import annotations - -import importlib.util -import json -import os -import re -import stat -import sys -import tempfile -import unittest -from pathlib import Path - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -CCR_PATH = ROOT / "credential-change-requests/CCR-2026-0040-keycape-client-secret-generate-grant.yaml" - - -def load(name: str, rel: str): - spec = importlib.util.spec_from_file_location(name, ROOT / rel) - module = importlib.util.module_from_spec(spec) - sys.modules[name] = module - spec.loader.exec_module(module) - return module - - -lane = load("broker_grant_lane", "scripts/broker_grant_lane.py") -cc = load("credential_change_cli_grant", "scripts/credential-change.py") - - -def ccr() -> dict: - return yaml.safe_load(CCR_PATH.read_text()) - - -# Stateful fake: policies stored as text; token role key=value args stored the way -# OpenBao returns them (lists, booleans, seconds). -FAKE_BAO = r'''#!/usr/bin/env python3 -import json, os, sys -path = os.environ["FAKE_BAO_STATE"] -state = json.load(open(path)) -args = [a for a in sys.argv[1:] if a != "-format=json"] -if args[:2] == ["policy", "write"]: - state["policies"][args[2]] = open(args[3]).read() -elif args[:2] == ["policy", "read"]: - print(state["policies"][args[2]]) -elif args[0] == "write": - role = {} - for kv in args[2:]: - k, v = kv.split("=", 1) - if k.endswith("_policies"): role[k] = v.split(",") - elif v in ("true", "false"): role[k] = v == "true" - elif k.endswith("_ttl"): role[k] = int(v[:-1]) * 60 - else: role[k] = v - if state.get("tamper"): role["renewable"] = True - state["roles"][args[1]] = role -elif args[0] == "read": - print(json.dumps({"data": state["roles"][args[1]]})) -json.dump(state, open(path, "w")) -''' - - -def stanzas(text: str) -> dict[str, str]: - return dict(re.findall(r'path "([^"]+)"\s*{\s*capabilities = \[([^\]]*)\]', text)) - - -class BrokerGrantLaneTests(unittest.TestCase): - def test_ccr_validates_and_records_the_delegated_apply(self): - _ccr, errors, _w = cc.validate_ccr(CCR_PATH) - self.assertEqual(errors, []) - self.assertEqual(_ccr["status"], "applied") - self.assertTrue(any(e["kind"] == "delegated_metadata_apply" and e["result"] == "passed" - for e in _ccr["verification"]["evidence"])) - - def test_unapproved_ccr_blocks_the_applier(self): - c = ccr() - c["status"] = "proposed" - self.assertTrue(any("got proposed" in b for b in cc.applier_readiness_blockers(c))) - - def test_grant_policy_is_create_only(self): - policy = stanzas((ROOT / ccr()["openbao"]["policy_file"]).read_text()) - self.assertEqual(policy["platform/data/workloads/keycape/+"], '"create"') - self.assertEqual(policy["platform/metadata/workloads/keycape/+"], '"read"') - for path in ("platform/data/workloads/keycape/secrets-engine-openbao", - "platform/metadata/workloads/keycape/secrets-engine-openbao"): - self.assertEqual(policy[path], '"deny"') - granted = " ".join(c for p, c in policy.items() if p != "auth/token/revoke-self") - for word in ("update", "delete", "list", "patch", "sudo"): - self.assertNotIn(f'"{word}"', granted) - self.assertFalse(any("*" in p for p in policy)) - - def test_issuer_policy_mints_only_through_this_role(self): - issuer = stanzas((ROOT / ccr()["openbao"]["issuer_policy_file"]).read_text()) - creates = [p for p in issuer if p.startswith("auth/token/create")] - self.assertEqual(creates, ["auth/token/create/keycape-client-secret-generate"]) - # Probe 2026-10-10: the operator must be able to end its own session. - self.assertEqual(issuer["auth/token/revoke-self"], '"update"') - - def test_validator_rejects_drift(self): - cases = [ - ("long ttl", lambda c: c["openbao"].update(token_ttl="1h")), - ("prefix wildcard", lambda c: c["openbao"].update(kv_prefix="platform/workloads/*")), - ("unknown grant", lambda c: c["openbao"].update(grant_id="keycape/other")), - ("reserved glob", lambda c: c["openbao"].update(reserved_entries=["*"])), - ("policy drift", lambda c: c["openbao"].update(reserved_entries=[])), - ] - for label, mutate in cases: - with self.subTest(label): - c = ccr() - mutate(c) - self.assertNotEqual(lane.validate(c, ROOT), [], label) - - def test_state_hub_evidence_summary_handles_prefix_lanes(self): - posted = {} - original = cc.state_hub_post_json - cc.state_hub_post_json = lambda base, route, body: posted.update(body) or {"id": "x"} - try: - cc.record_evidence_state_hub(ccr(), "http://hub", "founder", "delegated_metadata_apply", "passed", ["ok"]) - finally: - cc.state_hub_post_json = original - self.assertIn("path=platform/workloads/keycape", posted["summary"]) - - def _apply(self, tamper=False): - tmp = Path(tempfile.mkdtemp()) - (tmp / "state.json").write_text(json.dumps({"policies": {}, "roles": {}, "tamper": tamper})) - bao = tmp / "bao" - bao.write_text(FAKE_BAO) - bao.chmod(bao.stat().st_mode | stat.S_IEXEC) - os.environ["FAKE_BAO_STATE"] = str(tmp / "state.json") - error = None - try: - lane.run_apply(ccr(), ROOT, str(bao), quiet=True) - except lane.BaoError as exc: - error = exc - return error, json.loads((tmp / "state.json").read_text()) - - def test_apply_writes_three_objects_and_reads_back(self): - error, state = self._apply() - self.assertIsNone(error) - self.assertEqual(sorted(state["policies"]), [ - "credential-broker-keycape-client-secret-generate-issuer", - "keycape-client-secret-generate", - ]) - role = state["roles"]["auth/token/roles/keycape-client-secret-generate"] - self.assertEqual(role["allowed_policies"], ["keycape-client-secret-generate"]) - self.assertEqual(role["token_explicit_max_ttl"], 300) - - def test_readback_detects_role_drift(self): - error, _state = self._apply(tamper=True) - self.assertIsNotNone(error) - self.assertIn("renewable", str(error)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_cadence_observer_readback.py b/tests/test_cadence_observer_readback.py deleted file mode 100644 index 6212b8d..0000000 --- a/tests/test_cadence_observer_readback.py +++ /dev/null @@ -1,237 +0,0 @@ -"""Model-only independent reader bounds and no-payload projection tests.""" - -import copy -import json -import sys -import unittest -from pathlib import Path -from unittest.mock import patch - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) -import cadence_observer_readback as lane # noqa: E402 - -SINCE = "2026-10-15T10:00:00Z" -UNTIL = "2026-10-15T11:00:00Z" - - -class ReadbackBounds(unittest.TestCase): - def record(self): - return dict( - event_id="test-1", - source="qonto-assistant", - tenant="tenant:friendly:binky", - action="audit-core.heartbeat", - accepted_at="2026-10-15T10:30:00Z", - observed_at="2026-10-15T10:29:59Z", - details={ - "data": { - "stream_instance_id": "instance", - "stream_sequence": 1, - "class": "audit.deny", - "sensitive-request": "must-never-leave", - } - }, - ) - - def sanitize(self, record): - return lane.sanitize_record( - record, - "qonto-assistant", - "tenant:friendly:binky", - "instance", - "test-1", - SINCE, - UNTIL, - ) - - def test_payload_is_never_returned(self): - result = self.sanitize(self.record()) - self.assertNotIn("must-never-leave", json.dumps(result)) - self.assertEqual(result["covered_event_classes"], ["audit.deny"]) - self.assertTrue(result["independent_custody_readback"]) - - def test_wrong_source_tenant_instance_and_id_are_refused(self): - for key in ("source", "tenant", "event_id", "stream_instance_id"): - record = self.record() - if key == "stream_instance_id": - record["details"]["data"][key] = "other" - else: - record[key] = "other" - with self.subTest(key=key), self.assertRaises(lane.LaneError): - self.sanitize(record) - - def test_outside_acceptance_window_is_refused(self): - record = self.record() - record["accepted_at"] = "2026-10-15T12:00:00Z" - with self.assertRaisesRegex(lane.LaneError, "acceptance_window"): - self.sanitize(record) - - def test_exact_until_is_excluded_like_receiver_sql(self): - record = self.record() - record["accepted_at"] = UNTIL - with self.assertRaisesRegex(lane.LaneError, "acceptance_window"): - self.sanitize(record) - - def test_paths_are_bounded_to_exact_approved_coordinates(self): - result = lane.paths( - "qonto-assistant", - "tenant:friendly:binky", - "instance", - SINCE, - UNTIL, - ["test-1"], - ) - self.assertEqual(result[-1], "/v1/events/test-1") - with self.assertRaisesRegex(lane.LaneError, "exact_source_tenant"): - lane.paths( - "qonto-assistant", "tenant:other", "instance", SINCE, UNTIL, ["test-1"] - ) - with self.assertRaisesRegex(lane.LaneError, "invalid_event_id"): - lane.paths( - "qonto-assistant", - "tenant:friendly:binky", - "instance", - SINCE, - UNTIL, - ["../secret"], - ) - - def test_reader_cannot_open_arbitrary_paths(self): - with self.assertRaisesRegex(lane.LaneError, "exact_packet"): - lane.read_http( - "http://127.0.0.1:1", - "fixture-only", - "/v1/events/other", - ["/v1/events/test-1"], - ) - - def test_readback_discards_unrelated_findings_and_checks_exact_reconciliation(self): - query = dict( - source="qonto-assistant", - tenant="tenant:friendly:binky", - stream_instance_id="instance", - since=SINCE, - until=UNTIL, - counts=[], - ) - findings = { - "stream_findings": [ - dict(source="unrelated", **{"class": "sensitive"}), - dict( - source="qonto-assistant", - kind="missing_heartbeat", - **{"class": "audit.deny"}, - private="no-output", - ), - ] - } - with patch.object( - lane, "read_http", side_effect=[query, findings, self.record()] - ): - result = lane.readback( - "http://127.0.0.1:1", - "fixture", - "qonto-assistant", - "tenant:friendly:binky", - "instance", - SINCE, - UNTIL, - ["test-1"], - ) - self.assertEqual(len(result["stream_findings"]), 1) - self.assertNotIn("no-output", json.dumps(result)) - wrong = copy.deepcopy(query) - wrong["tenant"] = "other" - with patch.object(lane, "read_http", return_value=wrong): - with self.assertRaisesRegex(lane.LaneError, "scope_window_mismatch"): - lane.readback( - "http://127.0.0.1:1", - "fixture", - "qonto-assistant", - "tenant:friendly:binky", - "instance", - SINCE, - UNTIL, - ["test-1"], - ) - - -class ApprovalReadback(unittest.TestCase): - def test_approval_omits_instance_filter_without_inventing_one(self): - paths = lane.paths( - "approval-engine", "tenant:platform", None, SINCE, UNTIL, ["approval-1"] - ) - self.assertNotIn("stream_instance_id", paths[0]) - with self.assertRaisesRegex(lane.LaneError, "no_declared_instance"): - lane.paths( - "approval-engine", - "tenant:platform", - "invented", - SINCE, - UNTIL, - ["approval-1"], - ) - with self.assertRaisesRegex(lane.LaneError, "exact_instance_required"): - lane.paths( - "qonto-assistant", "tenant:friendly:binky", None, SINCE, UNTIL, ["q-1"] - ) - - def test_actual_approval_record_needs_no_instance_sequence(self): - record = dict( - event_id="approval-1", - source="approval-engine", - tenant="tenant:platform", - action="approval.issuance", - accepted_at="2026-10-15T10:30:00Z", - observed_at="2026-10-15T10:29:59Z", - details={"data": {"transition": "issuance"}}, - ) - result = lane.sanitize_record( - record, - "approval-engine", - "tenant:platform", - None, - "approval-1", - SINCE, - UNTIL, - ) - self.assertIsNone(result["stream_instance_id"]) - self.assertIsNone(result["stream_sequence"]) - self.assertEqual(result["type"], "approval.issuance") - - def test_reconciliation_compares_semantic_window_times(self): - response = dict( - source="approval-engine", - tenant="tenant:platform", - since="2026-10-15T10:00:00+00:00", - until="2026-10-15T11:00:00+00:00", - counts=[], - ) - record = dict( - event_id="approval-1", - source="approval-engine", - tenant="tenant:platform", - action="audit-core.heartbeat", - accepted_at="2026-10-15T10:30:00Z", - observed_at="2026-10-15T10:29:59Z", - details={"data": {"class": "revocation"}}, - ) - with patch.object( - lane, "read_http", side_effect=[response, {"stream_findings": []}, record] - ): - result = lane.readback( - "http://127.0.0.1:1", - "fixture-only", - "approval-engine", - "tenant:platform", - None, - SINCE, - UNTIL, - ["approval-1"], - ) - self.assertEqual(result["events"][0]["covered_event_classes"], ["revocation"]) - self.assertIn("no process-instance completeness claim", result["scope"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_cadence_receiver_custody.py b/tests/test_cadence_receiver_custody.py deleted file mode 100644 index f38036a..0000000 --- a/tests/test_cadence_receiver_custody.py +++ /dev/null @@ -1,255 +0,0 @@ -"""Contained model exercises, no live registry reads or credentials.""" - -import copy -import json -import subprocess -import sys -import unittest -from pathlib import Path -from unittest.mock import patch - -ROOT = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(ROOT / "scripts")) -import cadence_receiver_custody as lane # noqa: E402 - - -class PacketContracts(unittest.TestCase): - def test_proposed_refuses_before_native_calls(self): - module = lane.credential_module() - ccr, errors, warnings = module.validate_ccr(lane.CCR) - ccr = copy.deepcopy(ccr) - ccr["status"] = "proposed" - with ( - patch.object(lane, "credential_module", return_value=module), - patch.object(module, "validate_ccr", return_value=(ccr, errors, warnings)), - patch.object(lane, "bao", side_effect=AssertionError("no native call")), - ): - with self.assertRaisesRegex(lane.LaneError, "approved_ccr_required"): - lane.contracts(approved=True) - - def test_exact_namespace_path_policy_and_projection(self): - contract = lane.contracts() - self.assertEqual(contract["namespace"], "rapp-qonto") - self.assertEqual(contract["name"], "rapp-qonto-collector") - objects = list(lane.yaml.safe_load_all(lane.MANIFEST.read_text())) - self.assertEqual( - objects[0]["spec"]["conditions"], [{"namespaces": ["rapp-qonto"]}] - ) - self.assertEqual( - objects[1]["spec"]["data"], - [ - { - "secretKey": "bearer", - "remoteRef": { - "key": "workloads/rapp-qonto/audit-sender", - "property": "AUDIT_TOKEN", - "conversionStrategy": "Default", - "decodingStrategy": "None", - "metadataPolicy": "None", - }, - } - ], - ) - self.assertEqual( - contract["hcl"], - 'path "platform/data/workloads/rapp-qonto/audit-sender" {\n capabilities = ["read"]\n}\n', - ) - - def test_offline_plan_contains_hashes_but_no_credentials(self): - result = lane.packet() - self.assertEqual(len(result["sha256"]), 64) - self.assertIn( - "audit-core/audit_core/cadence_registration.py", result["source_sha256"] - ) - self.assertFalse(result["credential_values_emitted"]) - self.assertNotIn("tokens", json.dumps(result)) - - def test_receiver_compatibility_probe_against_actual_source(self): - result = subprocess.run( - [ - str(lane.WORKSPACE / "audit-core/.venv/bin/python"), - "-c", - lane.COMPATIBILITY, - ], - cwd=lane.WORKSPACE / "audit-core", - capture_output=True, - input=json.dumps(lane.proposal_metadata()).encode(), - check=True, - ) - self.assertTrue(all(json.loads(result.stdout).values())) - - -class FakeCustody(unittest.TestCase): - def setUp(self): - self.contract = lane.contracts() - self.kv = None - self.version = 7 - self.rows = [ - dict( - name="approval-engine", - tokens=["a" * 64], - sources=["approval-engine"], - tenants=["tenant:platform"], - may_write=True, - may_read=False, - evidence_kind="load-bearing", - secret_policy="redact", - ), - dict( - name="reader", - tokens=["b" * 64], - sources=["*"], - tenants=["*"], - may_write=False, - may_read=True, - secret_policy="reject", - ), - ] - self.body = {"senders.json": json.dumps(self.rows), "other": "keep"} - self.writes = [] - self.saves = [] - self.receipt = {} - self.fail_registry = False - self.snapshot_patch = patch.object(lane, "snapshot", side_effect=self.snapshot) - self.optional_patch = patch.object( - lane, "read_optional", side_effect=self.metadata - ) - self.bao_patch = patch.object(lane, "bao", side_effect=self.bao) - for p in [self.snapshot_patch, self.optional_patch, self.bao_patch]: - p.start() - self.addCleanup(p.stop) - - def snapshot(self): - rows = json.loads(self.body["senders.json"]) - return ( - self.version, - copy.deepcopy(self.body), - rows, - {t for r in rows for t in r["tokens"]}, - ) - - def metadata(self, path): - self.assertEqual(path, self.contract["metadata"]) - return ( - None if self.kv is None else {"current_version": 1, "versions": {"1": {}}} - ) - - def bao(self, args, *, payload=None): - if args[0] == "read": - self.assertEqual(args[2], self.contract["kv"]) - result = {"data": {"metadata": {"version": 1}, "data": self.kv}} - elif args[2] == self.contract["kv"]: - self.assertIsNone(self.kv) - self.assertEqual(payload["options"], {"cas": 0}) - self.kv = copy.deepcopy(payload["data"]) - self.writes.append("custody") - result = {"data": {"version": 1}, "request_id": "test-custody"} - else: - self.assertEqual(args[2], lane.REGISTRY) - if self.fail_registry: - raise lane.LaneError("simulated_registry_conflict") - self.assertEqual(payload["options"]["cas"], self.version) - self.body = copy.deepcopy(payload["data"]) - self.version += 1 - self.writes.append("registry") - result = {"data": {"version": self.version}, "request_id": "test-registry"} - return subprocess.CompletedProcess([], 0, json.dumps(result).encode(), b"") - - def run_merge(self, resume=False): - return lane.merge_and_seed( - self.contract, - self.receipt, - lambda: self.saves.append(copy.deepcopy(self.receipt)), - resume=resume, - ) - - def test_first_provision_preserves_all_other_credentials_and_fields(self): - token, updated = self.run_merge() - rows = json.loads(updated["senders.json"]) - self.assertEqual(rows[0]["tokens"], ["a" * 64]) - self.assertEqual(rows[1], self.rows[1]) - self.assertEqual(rows[2]["tokens"], [token]) - self.assertEqual(rows[2]["heartbeat_classes"], {"audit.deny": 7200}) - self.assertEqual(updated["other"], "keep") - self.assertNotIn(token, json.dumps(self.saves)) - self.assertEqual(self.writes, ["custody", "registry"]) - - def test_idempotent_resume_reuses_version1_and_no_registry_write(self): - token, _ = self.run_merge() - prior = list(self.writes) - resumed, _ = self.run_merge(resume=True) - self.assertEqual(resumed, token) - self.assertEqual(self.writes, prior) - with self.assertRaisesRegex( - lane.LaneError, "reviewed_version1_resume_required" - ): - self.run_merge() - - def test_partial_failure_after_cas0_resumes_without_rotation(self): - self.fail_registry = True - with self.assertRaisesRegex(lane.LaneError, "simulated_registry_conflict"): - self.run_merge() - token = self.kv["AUDIT_TOKEN"] - self.assertEqual(self.writes, ["custody"]) - self.fail_registry = False - actual, _ = self.run_merge(resume=True) - self.assertEqual(actual, token) - self.assertEqual(self.writes, ["custody", "registry"]) - - def test_conflicting_custody_provenance_refuses(self): - self.run_merge() - self.kv["CUSTODY_REQUEST"] = "OTHER" - with self.assertRaisesRegex(lane.LaneError, "custody_provenance_mismatch"): - self.run_merge(resume=True) - - def test_registered_scope_drift_refuses(self): - self.run_merge() - rows = json.loads(self.body["senders.json"]) - rows[-1]["tenants"] = ["wrong"] - self.body["senders.json"] = json.dumps(rows) - with self.assertRaisesRegex(lane.LaneError, "registered_sender_drift"): - self.run_merge(resume=True) - - def test_orphan_registry_sender_refuses_without_custody_write(self): - rows = json.loads(self.body["senders.json"]) - rows.append(dict(lane.proposal_metadata(), tokens=["c" * 64])) - self.body["senders.json"] = json.dumps(rows) - with self.assertRaisesRegex(lane.LaneError, "registry_sender_without_custody"): - self.run_merge() - self.assertEqual(self.writes, []) - - def test_custody_preflight_refuses_bad_provenance_before_grant_writes(self): - self.run_merge() - self.kv["CUSTODY_REQUEST"] = "unknown-owner" - with patch.object( - lane, "prepare_metadata", side_effect=AssertionError("no grants") - ): - with self.assertRaisesRegex(lane.LaneError, "custody_provenance_mismatch"): - lane.preflight_custody(self.contract, resume=True) - - -class ProjectionResume(unittest.TestCase): - def test_exact_metadata_template_is_resumable(self): - objects = list(lane.yaml.safe_load_all(lane.MANIFEST.read_text())) - responses = [ - subprocess.CompletedProcess([], 0, json.dumps(o).encode(), b"") - for o in objects - ] - with patch.object(lane, "command", side_effect=responses): - lane.preflight_projection(["kubectl"], objects) - - def test_extra_template_payload_is_refused(self): - objects = list(lane.yaml.safe_load_all(lane.MANIFEST.read_text())) - actual = copy.deepcopy(objects) - actual[1]["spec"]["target"]["template"]["data"] = {"injected": "payload"} - responses = [ - subprocess.CompletedProcess([], 0, json.dumps(o).encode(), b"") - for o in actual - ] - with patch.object(lane, "command", side_effect=responses): - with self.assertRaisesRegex(lane.LaneError, "existing_projection_drift"): - lane.preflight_projection(["kubectl"], objects) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_ccr_intake.py b/tests/test_ccr_intake.py deleted file mode 100644 index 3569e7d..0000000 --- a/tests/test_ccr_intake.py +++ /dev/null @@ -1,296 +0,0 @@ -from __future__ import annotations - -import argparse -import importlib.util -import os -import shutil -import sys -import tempfile -import unittest -from pathlib import Path -from unittest import mock - -REPO_DIR = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(REPO_DIR / "scripts")) -SPEC = importlib.util.spec_from_file_location( - "credential_change", REPO_DIR / "scripts/credential-change.py" -) -credential_change = importlib.util.module_from_spec(SPEC) -assert SPEC.loader is not None -sys.modules[SPEC.name] = credential_change -SPEC.loader.exec_module(credential_change) - -import ccr_intake # noqa: E402 - -cc = credential_change -STANDARD = "CCR-2026-0051-mas-tem-relay-smtp.yaml" -POLICY = "openbao/policies/workload-kv-read-mas-tem-relay.hcl" - - -class CcrIntakeTests(unittest.TestCase): - """Run against a scratch repo root so nothing is written into the real tree.""" - - def setUp(self) -> None: - self.root = Path(tempfile.mkdtemp()) - self.addCleanup(shutil.rmtree, self.root, True) - self.ccrs = self.root / "credential-change-requests" - self.ccrs.mkdir() - (self.root / "openbao/policies").mkdir(parents=True) - shutil.copy2(REPO_DIR / "credential-change-requests" / STANDARD, self.ccrs / STANDARD) - shutil.copy2(REPO_DIR / POLICY, self.root / POLICY) - for target in (cc, ccr_intake): - patcher = mock.patch.object(target, "REPO_DIR", self.root) - patcher.start() - self.addCleanup(patcher.stop) - env = mock.patch.dict(os.environ, {"CCR_DIR": str(self.ccrs)}) - env.start() - self.addCleanup(env.stop) - self.profile, _digest = ccr_intake.load_profile() - self.path = self.ccrs / STANDARD - # The live CCR moves through approval; the fixture must stay pre-approval. - self.edit(lambda d: (d.update(status="proposed"), d["review"].update(comments=[]), - d.pop("state_hub", None))) - - def edit(self, change) -> None: - data = cc.load_yaml(self.path) - change(data) - cc.dump_yaml(self.path, data) - - def violations(self) -> list[str]: - return ccr_intake.standard_violations(cc.load_yaml(self.path), self.profile, self.ccrs, cc) - - def assert_violation(self, change, fragment: str) -> None: - self.edit(change) - found = self.violations() - self.assertTrue(any(fragment in item for item in found), found) - - def test_standard_lane_passes_and_records_review_without_approval(self) -> None: - self.assertEqual(ccr_intake.review_standard(self.path, cc), []) - data = cc.load_yaml(self.path) - self.assertEqual(data["status"], "proposed") - comment = data["review"]["comments"][-1] - self.assertEqual(comment["decision"], "approved") - self.assertIn("ccr-standard-lane v1 sha256:", comment["comment"]) - self.assertTrue(comment["reviewer"].startswith("railiance-platform-owner")) - self.assertIn("apply requires status approved, got proposed", cc.apply_blockers(data)) - - def test_check_mode_does_not_write(self) -> None: - before = self.path.read_text() - ccr_intake.review_standard(self.path, cc, write=False) - self.assertEqual(self.path.read_text(), before) - - def test_out_of_profile_records_needs_platform_review(self) -> None: - self.edit(lambda d: d["risk"].update(classification="high")) - self.assertTrue(ccr_intake.review_standard(self.path, cc)) - comment = cc.load_yaml(self.path)["review"]["comments"][-1] - self.assertEqual(comment["decision"], "needs_platform_review") - self.assertEqual(cc.load_yaml(self.path)["status"], "proposed") - - def test_requires_proposed_status(self) -> None: - self.edit(lambda d: d.update(status="approved")) - with self.assertRaises(SystemExit): - ccr_intake.review_standard(self.path, cc) - - def test_metadata_read_rejected(self) -> None: - self.assert_violation(lambda d: d["openbao"].update(metadata_read=True), "metadata_read") - - def test_kv_path_outside_prefixes_rejected(self) -> None: - self.assert_violation( - lambda d: d["openbao"].update(kv_path="platform/operators/founder/x"), "outside the standard" - ) - - def test_supplier_path_needs_exactly_two_segments(self) -> None: - self.assert_violation( - lambda d: d["openbao"].update(kv_path="platform/suppliers/scaleway/a/b"), "outside the standard" - ) - - def test_policy_file_drift_rejected(self) -> None: - (self.root / POLICY).write_text('path "platform/data/suppliers/*" {\n capabilities = ["read"]\n}\n') - self.assertTrue(any("differs" in v for v in self.violations())) - - def test_long_ttl_rejected(self) -> None: - self.assert_violation(lambda d: d["openbao"]["auth"].update(token_max_ttl="1h"), "token_max_ttl") - - def test_default_policy_rejected(self) -> None: - self.assert_violation( - lambda d: d["openbao"]["auth"].update(token_no_default_policy=False), "token_no_default_policy" - ) - - def test_role_prefix_and_audience_enforced(self) -> None: - self.assert_violation(lambda d: d["openbao"]["auth"].update(role="mas-eso"), "role must start") - self.assert_violation(lambda d: d["openbao"]["auth"].update(audience="vault"), "audience") - - def test_two_service_accounts_rejected(self) -> None: - self.assert_violation( - lambda d: d["openbao"]["auth"]["bound_claims"]["service_account_names"].append("other"), - "service account", - ) - - def test_reserved_namespace_rejected(self) -> None: - self.assert_violation( - lambda d: d["openbao"]["auth"]["bound_claims"].update(service_account_namespaces=["external-secrets"]), - "reserved", - ) - - def test_second_reader_of_path_rejected(self) -> None: - other = cc.load_yaml(self.path) - other.update(id="CCR-2026-0052", status="active") - cc.dump_yaml(self.ccrs / "CCR-2026-0052-other.yaml", other) - self.assertTrue(any("already has a reader: CCR-2026-0052" in v for v in self.violations())) - - def test_cancelled_reader_is_not_a_conflict(self) -> None: - other = cc.load_yaml(self.path) - other.update(id="CCR-2026-0052", status="cancelled") - cc.dump_yaml(self.ccrs / "CCR-2026-0052-other.yaml", other) - self.assertEqual(self.violations(), []) - - def test_next_id_and_duplicate_detection(self) -> None: - self.assertEqual(ccr_intake.next_ccr_id(self.ccrs, 2026), "CCR-2026-0052") - self.assertEqual(ccr_intake.next_ccr_id(self.ccrs, 2027), "CCR-2027-0001") - self.assertEqual(ccr_intake.duplicate_ids(self.ccrs, cc), {}) - shutil.copy2(self.path, self.ccrs / "CCR-2026-0051-race.yaml") - self.assertIn("CCR-2026-0051", ccr_intake.duplicate_ids(self.ccrs, cc)) - - def submit_args(self, **overrides) -> argparse.Namespace: - draft_dir = Path(tempfile.mkdtemp()) - self.addCleanup(shutil.rmtree, draft_dir, True) - draft = cc.load_yaml(self.path) - draft.update(id="CCR-DRAFT-MAS", status="draft") - draft["openbao"].update( - kv_path="platform/suppliers/scaleway/other-relay", - policy_name="workload-kv-read-other-relay", - policy_file="openbao/policies/workload-kv-read-other-relay.hcl", - ) - draft["openbao"]["auth"]["policies"] = ["workload-kv-read-other-relay"] - draft["review"]["comments"] = [{"reviewer": "someone", "decision": "approved", "comment": "x"}] - cc.dump_yaml(draft_dir / "draft.yaml", draft) - (draft_dir / "policy.hcl").write_text( - (self.root / POLICY).read_text().replace("mas-tem-relay", "other-relay") - ) - values = dict(ccr=str(draft_dir / "draft.yaml"), policy=str(draft_dir / "policy.hcl"), - slug="other-relay", requester="ops-mason", profile=str(ccr_intake.DEFAULT_PROFILE)) - values.update(overrides) - return argparse.Namespace(**values) - - def test_submit_allocates_id_copies_policy_and_reviews(self) -> None: - destination, violations = ccr_intake.submit(self.submit_args(), cc) - self.assertEqual(violations, []) - self.assertEqual(destination.name, "CCR-2026-0052-other-relay.yaml") - data = cc.load_yaml(destination) - self.assertEqual((data["id"], data["status"]), ("CCR-2026-0052", "proposed")) - self.assertEqual(len(data["review"]["comments"]), 1) - self.assertTrue(data["review"]["comments"][0]["reviewer"].startswith("railiance-platform-owner")) - self.assertTrue((self.root / "openbao/policies/workload-kv-read-other-relay.hcl").exists()) - - def test_submit_refuses_unlisted_requester(self) -> None: - with self.assertRaises(SystemExit): - ccr_intake.submit(self.submit_args(requester="random-agent"), cc) - - def test_submit_refuses_policy_overwrite(self) -> None: - (self.root / "openbao/policies/workload-kv-read-other-relay.hcl").write_text("different\n") - with self.assertRaises(SystemExit): - ccr_intake.submit(self.submit_args(), cc) - - def test_submit_refuses_secret_markers(self) -> None: - args = self.submit_args() - Path(args.ccr).write_text(Path(args.ccr).read_text() + "# hvs.notarealtoken\n") - with self.assertRaises(SystemExit): - ccr_intake.submit(args, cc) - - def submitted_branch(self) -> tuple[Path, str]: - main_root = Path(tempfile.mkdtemp()) - self.addCleanup(shutil.rmtree, main_root, True) - shutil.copytree(self.root, main_root, dirs_exist_ok=True) - destination, _violations = ccr_intake.submit(self.submit_args(), cc) - return main_root, destination.stem - - def test_branch_gate_accepts_clean_submission(self) -> None: - main_root, stem = self.submitted_branch() - self.assertEqual(ccr_intake.branch_gate(main_root, stem, cc), []) - - def test_branch_gate_refuses_extra_changes(self) -> None: - main_root, stem = self.submitted_branch() - (self.root / "registry").mkdir() - (self.root / "registry/ccr-standard-profile.yaml").write_text("submitters: [anyone]\n") - errors = ccr_intake.branch_gate(main_root, stem, cc) - self.assertIn("ccr branch may not change registry/ccr-standard-profile.yaml", errors) - - def test_branch_gate_refuses_modified_existing_policy(self) -> None: - main_root, stem = self.submitted_branch() - path = self.ccrs / f"{stem}.yaml" - data = cc.load_yaml(path) - data["openbao"]["policy_file"] = POLICY - cc.dump_yaml(path, data) - (self.root / POLICY).write_text((self.root / POLICY).read_text() + "# widened\n") - errors = ccr_intake.branch_gate(main_root, stem, cc) - self.assertTrue(any("may not modify" in e for e in errors), errors) - - def test_branch_gate_refuses_hand_edited_review(self) -> None: - main_root, stem = self.submitted_branch() - path = self.ccrs / f"{stem}.yaml" - data = cc.load_yaml(path) - data["review"]["comments"].append({"reviewer": "founder", "decision": "approved", "comment": "ok"}) - cc.dump_yaml(path, data) - errors = ccr_intake.branch_gate(main_root, stem, cc) - self.assertTrue(any("policy-as-code approval" in e for e in errors), errors) - - def test_branch_gate_refuses_unlisted_requester(self) -> None: - main_root, stem = self.submitted_branch() - path = self.ccrs / f"{stem}.yaml" - data = cc.load_yaml(path) - data["requester"]["agent"] = "someone-else" - cc.dump_yaml(path, data) - errors = ccr_intake.branch_gate(main_root, stem, cc) - self.assertTrue(any("listed submitter" in e for e in errors), errors) - - def test_branch_gate_refuses_existing_ccr(self) -> None: - main_root = Path(tempfile.mkdtemp()) - self.addCleanup(shutil.rmtree, main_root, True) - shutil.copytree(self.root, main_root, dirs_exist_ok=True) - errors = ccr_intake.branch_gate(main_root, Path(STANDARD).stem, cc) - self.assertTrue(any("already exists on main" in e for e in errors), errors) - - def test_request_decision_records_id_after_platform_review(self) -> None: - ccr_intake.review_standard(self.path, cc) - with mock.patch.object(cc, "state_hub_post_json", return_value={"id": "dec-1", "status": "open"}) as post: - ccr_intake.request_decision(self.path, cc, "http://hub") - payload = post.call_args.args[2] - self.assertIn("APPROVE: CCR-2026-0051", payload["description"]) - self.assertNotIn("status", payload) - self.assertNotIn("Decision link", payload["description"]) - state_hub = cc.load_yaml(self.path)["state_hub"] - self.assertEqual(state_hub["decision_id"], "dec-1") - self.assertEqual(cc.load_yaml(self.path)["status"], "proposed") - with self.assertRaises(SystemExit): - ccr_intake.request_decision(self.path, cc, "http://hub") - - def test_request_decision_requires_platform_approval(self) -> None: - self.edit(lambda d: d["risk"].update(classification="high")) - ccr_intake.review_standard(self.path, cc) - with mock.patch.object(cc, "state_hub_post_json") as post, self.assertRaises(SystemExit): - ccr_intake.request_decision(self.path, cc, "http://hub") - post.assert_not_called() - - def test_request_decision_refuses_pre_resolved_decision(self) -> None: - ccr_intake.review_standard(self.path, cc) - resolved = {"id": "dec-2", "status": "resolved"} - with mock.patch.object(cc, "state_hub_post_json", return_value=resolved), self.assertRaises(SystemExit): - ccr_intake.request_decision(self.path, cc, "http://hub") - - def test_render_policy_matches_reviewed_policy_file(self) -> None: - rendered = ccr_intake.standard_policy_hcl("platform/suppliers/scaleway/mas-tem-relay", cc) - self.assertEqual(rendered, (REPO_DIR / POLICY).read_text()) - - def test_plan_ref_passes_through_submit_and_is_shape_checked(self) -> None: - args = self.submit_args() - draft = cc.load_yaml(Path(args.ccr)) - draft["plan_ref"] = {"repo": "ops-mason", "path": "plans/x.md", "commit": "d19e6b1"} - cc.dump_yaml(Path(args.ccr), draft) - destination, violations = ccr_intake.submit(args, cc) - self.assertEqual(violations, []) - self.assertEqual(cc.load_yaml(destination)["plan_ref"]["commit"], "d19e6b1") - self.assert_violation(lambda d: d.update(plan_ref={"repo": "ops-mason"}), "plan_ref") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_credential_change.py b/tests/test_credential_change.py index b6c78a8..00fe480 100644 --- a/tests/test_credential_change.py +++ b/tests/test_credential_change.py @@ -436,32 +436,6 @@ class CredentialChangeTests(unittest.TestCase): self.assertEqual(payload["bound_service_account_namespaces"], ["external-secrets"]) self.assertNotIn("bound_claims", payload) - def test_quiet_metadata_apply_captures_bao_output(self) -> None: - ccr, errors, _warnings = credential_change.validate_ccr(self.sample) - self.assertEqual(errors, []) - seen = [] - - class Done: - returncode = 0 - - def fake_run(command, **kwargs): - seen.append(kwargs.get("capture_output")) - return Done() - - original = credential_change.subprocess.run - credential_change.subprocess.run = fake_run - try: - credential_change.run_bao_metadata_apply(ccr, "bao", quiet=True) - finally: - credential_change.subprocess.run = original - self.assertEqual(seen, [True, True]) - - def test_scaffold_default_redirects_exclude_retired_public_host(self) -> None: - source = (REPO_DIR / "scripts/credential-change.py").read_text() - default = source.split('if args.auth_method == "oidc" and not allowed_redirect_uris:', 1)[1].split("]", 1)[0] - self.assertNotIn('"https://bao.coulomb.social', default) - self.assertIn("http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback", default) - def test_oidc_auth_payload_includes_redirect_uris(self) -> None: ccr, errors, _warnings = credential_change.validate_ccr(self.sample) self.assertEqual(errors, []) @@ -531,13 +505,12 @@ class CredentialChangeTests(unittest.TestCase): self.assertEqual(errors, []) self.assertNotIn("consumer_copy", ccr) self.assertEqual(ccr["target"]["domain"], "infotech") - # Approved under 2ab717e1 and applied on 2026-10-07. - self.assertIn(ccr["status"], credential_change.POST_APPLY_STATUSES) + self.assertEqual(ccr["status"], "proposed") meta = ccr["openbao"]["kv_metadata"] self.assertEqual((meta["cas_required"], meta["max_versions"], meta["delete_version_after"]), (True, 2, "0s")) self.assertEqual(meta["custom_metadata"]["readers"], "eso-sso-issuer,oidc-secrets-engine-operator") - self.assertIs(ccr["openbao"]["auth"]["bound_claims_confirmed"], True) + self.assertIs(ccr["openbao"]["auth"]["bound_claims_confirmed"], False) def test_apply_plan_refuses_unapproved_ccr(self) -> None: with self.assertRaises(SystemExit): @@ -683,31 +656,6 @@ class CredentialChangeTests(unittest.TestCase): self.assertTrue(ccr["openbao"]["auth"]["bound_claims_confirmed"]) self.assertEqual(ccr["review"]["comments"][-1]["decision"], "binding_confirmed") - def test_hub_writer_caller_gains_generated_capabilities_self(self) -> None: - # NK-WP-0051-T06: the proof no longer needs sys/capabilities with a root token. - path = REPO_DIR / "credential-change-requests/CCR-2026-0045-reuse-surface-hub-writer-capabilities-self.yaml" - ccr, errors, _warnings = credential_change.validate_ccr(path) - self.assertEqual(errors, []) - body = credential_change.generated_policy_hcl(ccr) - self.assertIn('path "sys/capabilities-self" {\n capabilities = ["update"]\n}', body) - self.assertNotIn('path "sys/capabilities" {', body) - self.assertNotIn("*", body) - self.assertEqual(credential_change.policy_artifact_diff(ccr)["status"], "matches") - self.assertEqual(credential_change.applier_policy_violations(ccr), []) - self.assertEqual(ccr["openbao"]["auth"]["policies"], ["workload-kv-read-reuse-surface-runtime"]) - without = json.loads(json.dumps(ccr)) - without["openbao"]["token_capabilities_self"] = False - self.assertNotIn("capabilities-self", credential_change.generated_policy_hcl(without)) - - def test_token_capabilities_self_must_be_boolean(self) -> None: - path = REPO_DIR / "credential-change-requests/CCR-2026-0045-reuse-surface-hub-writer-capabilities-self.yaml" - with tempfile.TemporaryDirectory() as tmp: - bad = Path(tmp) / "bad.yaml" - bad.write_text(path.read_text(encoding="utf-8").replace( - "token_capabilities_self: true", "token_capabilities_self: 'yes'"), encoding="utf-8") - _ccr, errors, _warnings = credential_change.validate_ccr(bad) - self.assertIn("openbao.token_capabilities_self must be boolean", errors) - def test_generated_policy_is_narrow(self) -> None: ccr, _errors, _warnings = credential_change.validate_ccr(self.sample) policy = credential_change.generated_policy_hcl(ccr) @@ -796,37 +744,6 @@ class CredentialChangeTests(unittest.TestCase): violations = credential_change.applier_policy_violations(ccr) self.assertEqual(violations, [], violations) - def _supplier_ccr(self, kv_path: str) -> dict: - ccr, errors, _warnings = credential_change.validate_ccr(self.sample) - self.assertEqual(errors, []) - ccr["status"] = "approved" - ccr["openbao"]["mount"] = "platform" - ccr["openbao"]["kv_path"] = kv_path - ccr["openbao"]["policy_file"] = "" - return ccr - - def test_applier_accepts_platform_suppliers_path(self) -> None: - """WARDEN-WP-0043-T02 — supplier accounts at platform/suppliers//.""" - ccr = self._supplier_ccr("platform/suppliers/ionos/main") - violations = credential_change.applier_policy_violations(ccr) - self.assertEqual(violations, [], violations) - - def test_applier_rejects_malformed_platform_suppliers_paths(self) -> None: - for kv_path in ( - "platform/suppliers/ionos", - "platform/suppliers/ionos/main/extra", - "platform/suppliers/*/main", - "platform/suppliers/../workloads/x", - "platform/suppliers/ionos//main", - "platform/data/suppliers/ionos/main", - "platform/other/ionos/main", - ): - with self.subTest(kv_path=kv_path): - violations = credential_change.applier_policy_violations( - self._supplier_ccr(kv_path) - ) - self.assertNotEqual(violations, [], kv_path) - def test_nonprod_applier_policy_remains_metadata_only(self) -> None: policy = ( REPO_DIR / "openbao/policies/credential-change-nonprod-applier.hcl" @@ -878,7 +795,7 @@ class CredentialChangeTests(unittest.TestCase): original_apply = credential_change.run_bao_metadata_apply original_post = credential_change.state_hub_post_json try: - credential_change.run_bao_metadata_apply = lambda ccr, bao_bin, quiet=False: calls.append((ccr["id"], bao_bin)) + credential_change.run_bao_metadata_apply = lambda ccr, bao_bin: calls.append((ccr["id"], bao_bin)) credential_change.state_hub_post_json = ( lambda _base_url, _path, payload: events.append(payload) or {"id": "event-1"} ) diff --git a/tests/test_credential_grants.py b/tests/test_credential_grants.py index 7bd1364..fc81816 100644 --- a/tests/test_credential_grants.py +++ b/tests/test_credential_grants.py @@ -25,20 +25,6 @@ class CredentialGrantBindTests(unittest.TestCase): grants.validate_grant(grant, index, catalog, errors) self.assertEqual(errors, []) - def test_keycape_generator_actor_matches_applied_issuer_binding(self) -> None: - catalog = grants.yaml.safe_load( - (REPO_DIR / 'credential-grants/catalog.yaml').read_text()) - grant = next(g for g in catalog['grants'] if g['id'] == 'keycape/client-secret-generate') - ccr = grants.yaml.safe_load((REPO_DIR / 'credential-change-requests' / - 'CCR-2026-0043-keycape-client-secret-generate-issuer-binding.yaml').read_text()) - role = grants.yaml.safe_load((REPO_DIR / ccr['openbao']['auth']['role_file']).read_text()) - auth = ccr['openbao']['auth'] - self.assertEqual(grant['actors']['allowed_types'], ['human-operator']) - self.assertEqual(grant['actors']['required_subject_binding'], - f"auth/{auth['mount']}/role/{auth['role']}") - self.assertEqual(role['bound_claims'], - {'groups': ['net-kingdom-admins'], '/assurance/level': 'aal2'}) - def test_postgres_grants_bind_rapp(self) -> None: catalog = grants.yaml.safe_load( (REPO_DIR / "credential-grants/catalog.yaml").read_text() diff --git a/tests/test_custodian_agent_rbac.py b/tests/test_custodian_agent_rbac.py deleted file mode 100644 index f587d0a..0000000 --- a/tests/test_custodian_agent_rbac.py +++ /dev/null @@ -1,110 +0,0 @@ -"""Offline guard for the custodian agent observer identity (RPF-WP-0062, CUST-WP-0073-T02). - -Parses the GitOps manifests and asserts the founder's 2026-10-20 boundary: -read-only verbs, curated resources, no forbidden resources, no wildcards, -no static token Secret, no token automount, and a manual, pinned Application. -""" -from pathlib import Path -import re - -import pytest -import yaml - -ROOT = Path(__file__).resolve().parents[1] -ADDON = ROOT / "argocd/platform-addons/custodian-agents" -APP = ROOT / "argocd/railiance01/applications/custodian-agents.application.yaml" - -READ_VERBS = {"get", "list", "watch"} -FORBIDDEN_RESOURCES = { - "secrets", "configmaps", "pods/log", "pods/exec", "pods/attach", - "pods/portforward", "serviceaccounts/token", "serviceaccounts", "nodes/proxy", - "services/proxy", "pods/proxy", "tokenreviews", "subjectaccessreviews", - "clusterroles", "clusterrolebindings", "roles", "rolebindings", -} -EXPECTED = { - "": {"namespaces", "nodes", "pods", "services", "endpoints", "events", - "persistentvolumeclaims"}, - "apps": {"deployments", "statefulsets", "daemonsets", "replicasets"}, - "discovery.k8s.io": {"endpointslices"}, - "networking.k8s.io": {"ingresses"}, - "events.k8s.io": {"events"}, - "argoproj.io": {"applications", "appprojects"}, - "postgresql.cnpg.io": {"clusters", "backups", "scheduledbackups"}, -} -EXPECTED_NON_RESOURCE = {"/version", "/healthz", "/livez", "/readyz"} - - -def _docs(): - kust = yaml.safe_load((ADDON / "kustomization.yaml").read_text()) - docs = [] - for res in kust["resources"]: - docs.extend(d for d in yaml.safe_load_all((ADDON / res).read_text()) if d) - return docs - - -def _one(kind): - found = [d for d in _docs() if d["kind"] == kind] - assert len(found) == 1, kind - return found[0] - - -def test_only_expected_kinds_no_secret(): - kinds = sorted(d["kind"] for d in _docs()) - assert kinds == ["ClusterRole", "ClusterRoleBinding", "Namespace", "ServiceAccount"] - for path in ADDON.iterdir(): - assert "kind: Secret" not in path.read_text(), path - - -def test_service_account_names_and_no_automount(): - sa = _one("ServiceAccount") - assert sa["metadata"] == {**sa["metadata"], "name": "custodian-agent", - "namespace": "custodian-agents"} - assert sa["automountServiceAccountToken"] is False - assert "secrets" not in sa - assert _one("Namespace")["metadata"]["name"] == "custodian-agents" - - -def test_cluster_role_read_only_curated_no_wildcards(): - role = _one("ClusterRole") - assert role["metadata"]["name"] == "custodian-supervised-observer" - assert "aggregationRule" not in role - granted, non_resource = {}, set() - for rule in role["rules"]: - verbs = set(rule["verbs"]) - assert verbs <= READ_VERBS, rule - values = [*rule["verbs"], *rule.get("apiGroups", []), *rule.get("resources", []), - *rule.get("nonResourceURLs", []), *rule.get("resourceNames", [])] - assert not any("*" in v for v in values), rule - if "nonResourceURLs" in rule: - assert verbs == {"get"} and not rule.get("resources"), rule - non_resource |= set(rule["nonResourceURLs"]) - continue - assert verbs == READ_VERBS, rule - for group in rule["apiGroups"]: - for resource in rule["resources"]: - assert resource not in FORBIDDEN_RESOURCES, resource - assert "/" not in resource, f"no subresources: {resource}" - granted.setdefault(group, set()).add(resource) - assert granted == EXPECTED - assert non_resource == EXPECTED_NON_RESOURCE - - -def test_binding_targets_only_the_agent(): - binding = _one("ClusterRoleBinding") - assert binding["roleRef"] == {"apiGroup": "rbac.authorization.k8s.io", - "kind": "ClusterRole", - "name": "custodian-supervised-observer"} - assert binding["subjects"] == [{"kind": "ServiceAccount", "name": "custodian-agent", - "namespace": "custodian-agents"}] - - -def test_application_manual_and_pinned(): - if not APP.exists(): - pytest.skip("Application not yet placed") - app = yaml.safe_load(APP.read_text()) - spec = app["spec"] - assert spec["project"] == "railiance-platform-addons" - assert spec["source"]["path"] == "argocd/platform-addons/custodian-agents" - assert re.fullmatch(r"[0-9a-f]{40}", spec["source"]["targetRevision"]) - assert "automated" not in spec.get("syncPolicy", {}) - assert "finalizers" not in app["metadata"] diff --git a/tests/test_custodian_allocation_review.py b/tests/test_custodian_allocation_review.py deleted file mode 100644 index 464e1b1..0000000 --- a/tests/test_custodian_allocation_review.py +++ /dev/null @@ -1,125 +0,0 @@ -"""Offline guard for the CUST-WP-0071-T05 weekly allocation assessment addon. - -Asserts the founder's 2026-10-21 boundary: read-only get/list RBAC limited to -the runner's reads, Monday 08:00 Europe/Berlin, Forbid, suspended, retained -PVC that ArgoCD never deletes, not referenced by any live Application, and -vendored sources matching vendor.lock.json. -""" -from pathlib import Path -import subprocess -import sys - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -ADDON = ROOT / "argocd/platform-addons/custodian-allocation-review" -APPS = ROOT / "argocd/railiance01/applications" -DRAFT = ROOT / "argocd/railiance01/drafts/custodian-allocation-review.application.yaml" -EXPECTED_RULES = { - ("", "nodes"), ("", "pods"), ("", "persistentvolumeclaims"), ("", "persistentvolumes"), - ("storage.k8s.io", "storageclasses"), ("metrics.k8s.io", "nodes"), ("metrics.k8s.io", "pods"), -} - - -def docs(): - kust = yaml.safe_load((ADDON / "kustomization.yaml").read_text()) - out = [] - for resource in kust["resources"]: - out.extend(d for d in yaml.safe_load_all((ADDON / resource).read_text()) if d) - return out - - -def one(kind): - found = [d for d in docs() if d["kind"] == kind] - assert len(found) == 1, kind - return found[0] - - -def test_rbac_is_read_only_and_exact(): - role = one("ClusterRole") - seen = set() - for rule in role["rules"]: - assert set(rule["verbs"]) == {"get", "list"} - assert "*" not in rule["apiGroups"] and "*" not in rule["resources"] - assert "resourceNames" not in rule and "nonResourceURLs" not in rule - seen |= {(g, r) for g in rule["apiGroups"] for r in rule["resources"]} - assert seen == EXPECTED_RULES - binding = one("ClusterRoleBinding") - assert binding["roleRef"]["name"] == role["metadata"]["name"] - assert binding["subjects"] == [{"kind": "ServiceAccount", "name": "custodian-allocation-review", - "namespace": "custodian-assessment"}] - assert not [d for d in docs() if d["kind"] in {"Role", "RoleBinding", "Secret"}] - - -def test_cronjob_schedule_suspended_and_hardened(): - spec = one("CronJob")["spec"] - assert spec["schedule"] == "0 8 * * 1" and spec["timeZone"] == "Europe/Berlin" - assert spec["suspend"] is True and spec["concurrencyPolicy"] == "Forbid" - pod = spec["jobTemplate"]["spec"]["template"]["spec"] - assert pod["serviceAccountName"] == "custodian-allocation-review" - assert pod["securityContext"]["runAsNonRoot"] is True - (container,) = pod["containers"] - assert "@sha256:" in container["image"] - assert container["securityContext"]["readOnlyRootFilesystem"] is True - assert container["securityContext"]["allowPrivilegeEscalation"] is False - assert container["resources"]["requests"]["cpu"] == "20m" - assert not any("secret" in v for v in pod["volumes"]) - claim = next(v for v in pod["volumes"] if "persistentVolumeClaim" in v)["persistentVolumeClaim"]["claimName"] - pvc = one("PersistentVolumeClaim") - assert pvc["metadata"]["name"] == claim - assert pvc["metadata"]["annotations"]["argocd.argoproj.io/sync-options"] == "Prune=false,Delete=false" - - -def test_not_registered_with_any_live_application(): - for path in APPS.glob("*.y*ml"): - assert "custodian-allocation-review" not in path.read_text(), path - draft = yaml.safe_load(DRAFT.read_text()) - assert "automated" not in draft["spec"].get("syncPolicy", {}) - assert "finalizers" not in draft["metadata"] - - -def test_vendored_sources_match_lock(): - result = subprocess.run([sys.executable, str(ROOT / "scripts/vendor_custodian_allocation_review.py"), "--check"], - capture_output=True, text=True) - assert result.returncode == 0, result.stderr - - -def test_default_deny_egress_allows_only_dns_api_prometheus_and_hub(): - policies = {d["metadata"]["name"]: d["spec"] for d in docs() if d["kind"] == "NetworkPolicy"} - deny = policies.pop("custodian-assessment-default-deny") - assert deny["podSelector"] == {} and set(deny["policyTypes"]) == {"Ingress", "Egress"} - assert "ingress" not in deny and "egress" not in deny - (egress,) = policies.values() - assert egress["policyTypes"] == ["Egress"] and "ingress" not in egress - allowed = set() - for rule in egress["egress"]: - assert rule.get("to") and rule.get("ports"), "every egress rule names peers and ports" - for peer in rule["to"]: - if "ipBlock" in peer: - assert "except" not in peer["ipBlock"] - key = peer["ipBlock"]["cidr"] - else: - key = (peer["namespaceSelector"]["matchLabels"]["kubernetes.io/metadata.name"], - tuple(sorted(peer["podSelector"]["matchLabels"].items()))) - allowed |= {(key, p["protocol"], p["port"]) for p in rule["ports"]} - assert allowed == { - (("kube-system", (("k8s-app", "kube-dns"),)), "UDP", 53), - (("kube-system", (("k8s-app", "kube-dns"),)), "TCP", 53), - ("10.43.0.1/32", "TCP", 443), - ("92.205.62.239/32", "TCP", 6443), - (("telemetry", (("app.kubernetes.io/name", "prometheus"),)), "TCP", 9090), - (("state-hub", (("app", "state-hub"),)), "TCP", 8000), - } - env = {e["name"]: e["value"] for e in one("CronJob")["spec"]["jobTemplate"]["spec"]["template"]["spec"]["containers"][0]["env"]} - assert env["STATE_HUB_URL"] == "http://state-hub.state-hub.svc:8000" - assert env["PROMETHEUS_URL"].startswith("http://telemetry-prometheus.telemetry.svc:9090") - - -def test_platform_addons_project_admits_the_addon_kinds(): - project = yaml.safe_load((ROOT / "argocd/railiance01/bootstrap/02-railiance-platform-addons-project.yaml").read_text()) - allowed = {(k["group"], k["kind"]) for k in project["spec"]["namespaceResourceWhitelist"]} - allowed |= {(k["group"], k["kind"]) for k in project["spec"]["clusterResourceWhitelist"]} - for doc in docs(): - group = doc["apiVersion"].rpartition("/")[0] - assert (group, doc["kind"]) in allowed, doc["kind"] - assert ("", "ConfigMap") in allowed # generated source ConfigMap diff --git a/tests/test_email_connect_smtp_delivery.py b/tests/test_email_connect_smtp_delivery.py deleted file mode 100644 index aa08da5..0000000 --- a/tests/test_email_connect_smtp_delivery.py +++ /dev/null @@ -1,254 +0,0 @@ -"""Fixed SMTP admission with a synthetic Bao/SMTP boundary; no native credentials.""" -import copy -import importlib.util -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import unittest -from unittest.mock import patch - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location('email_connect_delivery', ROOT / 'scripts/email_connect_smtp_delivery.py') -delivery = importlib.util.module_from_spec(spec) -spec.loader.exec_module(delivery) - - -class FakeOwner: - def __init__(self, lane): - self.lane = lane - self.writes = [] - self.revoked = [] - self.authenticated = False - self.mailbox = {'data': dict(lane.FIELDS, SMTP_PASSWORD='fixture-only'), - 'metadata': {'version': 2}} - boundary = '\n'.join('path "'+p+'" { capabilities = ["deny"] }' for p in ( - lane.LANE['kv_path'], lane.LANE['kv_path'].replace('/data/', '/metadata/', 1))) - self.values = {p: {'policy': 'existing-consumer'} if '/policies/' in p else {'unchanged': True} - for p in delivery.PROTECTED} - self.values[delivery.PROTECTED[0]] = {'policy': boundary} - self.changed_consumer = False - self.changed_mailbox = False - self.sealed = False - self.cluster_id = delivery.OPENBAO_CLUSTER_ID - self.auth_policies = ['platform-admin'] - - def bao(self, args, *, payload=None, allow_failure=False): - if args[0] == 'status': - value = {'cluster_id': self.cluster_id, 'initialized': True, 'sealed': self.sealed} - elif args[:2] == ['token', 'lookup']: - value = {'data': {'policies': self.auth_policies}} - elif args[:2] == ['token', 'create']: - value = {'auth': {'client_token': 'fixture-negative-token'}} - elif args[0] == 'read': - path = args[-1] - if path == self.lane.LANE['kv_path']: - v = copy.deepcopy(self.mailbox) - if self.writes and self.changed_mailbox: - v['metadata']['version'] += 1 - value = {'data': v} - elif path in self.values: - v = copy.deepcopy(self.values[path]) - if self.writes and self.changed_consumer and path == delivery.PROTECTED[1]: - v['policy'] = 'changed' - value = {'data': v} - else: - assert allow_failure - return subprocess.CompletedProcess([], 2, b'', b'No value found') - elif args[0] == 'write': - path = args[1] - assert path in ('sys/policies/acl/'+self.lane.LANE['policy'], - 'auth/kubernetes/role/'+self.lane.LANE['role']) - self.writes.append(path) - v = copy.deepcopy(payload) - for key in ('token_ttl', 'token_max_ttl', 'token_explicit_max_ttl'): - if key in v: - v[key] = 900 - self.values[path] = v - value = {} - else: - raise AssertionError(args) - return subprocess.CompletedProcess([], 0, json.dumps(value).encode(), b'') - - def smtp(self, host, port, timeout): - owner = self - assert (host, port) == ('smtp.ionos.de', 587) - class SMTP: - def __enter__(self): return self - def __exit__(self, *args): pass - def ehlo(self): pass - def starttls(self, context): assert context.check_hostname - def login(self, user, password): - assert user == 'platform@coulomb.social' and password == 'fixture-only' - owner.authenticated = True - return SMTP() - - def run(self): - records = [] - receipt = {} - with patch.object(delivery, 'lane_module', return_value=self.lane), \ - patch.object(self.lane, 'bao', side_effect=self.bao), \ - patch.object(self.lane, 'capabilities', side_effect=lambda token, paths: {p: ['deny'] for p in paths}), \ - patch.object(self.lane, 'revoke', side_effect=self.revoked.append), \ - patch.object(self.lane.smtplib, 'SMTP', side_effect=self.smtp): - delivery.admit(receipt, lambda r: records.append(copy.deepcopy(r))) - return receipt, records - - -class AdmissionTests(unittest.TestCase): - def setUp(self): - self.lane = delivery.lane_module() - self.owner = FakeOwner(self.lane) - - def test_default_preview_has_no_owner_calls_and_exact_role(self): - with patch.object(delivery, 'lane_module', return_value=self.lane), \ - patch.object(self.lane, 'bao', side_effect=AssertionError('no owner call')): - ccr, proposal, digest = delivery.prepare() - self.assertEqual(ccr['status'], 'proposed') - self.assertFalse(ccr['openbao']['auth']['bound_claims_confirmed']) - role = proposal['role'] - self.assertEqual(role['bound_service_account_namespaces'], ['email-connect']) - self.assertEqual(role['bound_service_account_names'], ['email-connect-platform-smtp-eso']) - self.assertEqual(role['token_explicit_max_ttl'], '15m') - self.assertTrue(role['token_no_default_policy']) - self.assertNotIn('*', proposal['policy']) - self.assertTrue(digest.startswith('sha256:')) - - def test_approval_and_native_binding_do_not_change_reviewed_scope_digest(self): - ccr, _, initial = delivery.prepare() - ccr['status'] = 'approved'; ccr['openbao']['auth']['bound_claims_confirmed'] = True - ccr['review']['comments'] = [{'reviewer': 'fixture', 'decision': 'approved'}] - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp) / 'ccr.yaml'; path.write_text(yaml.safe_dump(ccr)) - with patch.object(delivery, 'CCR', path): - self.assertEqual(delivery.prepare()[2], initial) - ccr['openbao']['auth']['bound_claims']['service_account_namespaces'] = ['other'] - path.write_text(yaml.safe_dump(ccr)) - with self.assertRaises(delivery.Refused): delivery.prepare() - - def test_all_three_execution_gates(self): - ccr, _, digest = delivery.prepare() - with self.assertRaises(delivery.Refused): delivery.execution_guard(ccr, 'wrong', digest) - with self.assertRaises(delivery.Refused): delivery.execution_guard(ccr, digest, digest) - ccr['status'] = 'approved' - with self.assertRaises(delivery.Refused): delivery.execution_guard(ccr, digest, digest) - ccr['openbao']['auth']['bound_claims_confirmed'] = True - delivery.execution_guard(ccr, digest, digest) - - def test_new_reader_only_and_existing_consumers_preserved(self): - receipt, records = self.owner.run() - self.assertEqual(receipt['status'], 'reader_admitted_verified') - self.assertTrue(receipt['existing_consumers_preserved']) - self.assertTrue(receipt['mailbox_unchanged']) - self.assertFalse(receipt['native_consumer_auth_verified']) - self.assertFalse(receipt['email_sent']) - self.assertFalse(receipt['application_sender_changed']) - self.assertTrue(self.owner.authenticated) - self.assertEqual(len(self.owner.writes), 2) - self.assertEqual(self.owner.revoked, ['fixture-negative-token']) - self.assertEqual(records[0]['mutation_outcome'], 'unconfirmed') - - def test_instance_sealed_and_admin_guards_precede_writes(self): - for kind, value in [('cluster_id', 'wrong'), ('sealed', True), ('auth_policies', ['root'])]: - with self.subTest(kind=kind): - owner = FakeOwner(self.lane); setattr(owner, kind, value) - with self.assertRaises(delivery.Refused): owner.run() - self.assertFalse(owner.writes) - self.assertFalse(owner.authenticated) - - def test_missing_agent_boundary_cannot_be_added_implicitly(self): - self.owner.values[delivery.PROTECTED[0]] = {'policy': ''} - with self.assertRaises(delivery.Refused): self.owner.run() - self.assertEqual(self.owner.writes, []) - - def test_different_existing_role_prevents_even_policy_creation(self): - self.owner.values['auth/kubernetes/role/'+delivery.LANE['role']] = {'different': True} - with self.assertRaises(self.lane.LaneError): self.owner.run() - self.assertFalse(self.owner.authenticated) - self.assertEqual(self.owner.writes, []) - - def test_different_existing_policy_is_not_replaced(self): - self.owner.values['sys/policies/acl/'+delivery.LANE['policy']] = {'policy': 'different'} - with self.assertRaises(delivery.Refused): self.owner.run() - self.assertEqual(self.owner.writes, []) - - def test_existing_exact_reader_is_idempotent_without_metadata_writes(self): - self.owner.run(); self.owner.writes.clear() - receipt, _ = self.owner.run() - self.assertEqual(receipt['status'], 'reader_admitted_verified') - self.assertEqual(self.owner.writes, []) - - def test_existing_consumer_or_mailbox_change_refuses_success(self): - for field in ['changed_consumer', 'changed_mailbox']: - with self.subTest(field=field): - owner = FakeOwner(self.lane); setattr(owner, field, True) - with self.assertRaises(delivery.Refused): owner.run() - self.assertEqual(len(owner.writes), 2) - - def approved(self): - ccr, proposal, digest = delivery.prepare() - ccr['status'] = 'approved'; ccr['openbao']['auth']['bound_claims_confirmed'] = True - return ccr, proposal, digest - - def test_failed_attempt_is_private_append_only_and_redacted(self): - ccr, proposal, digest = self.approved() - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp) / 'receipt.jsonl' - def fail(receipt, checkpoint): - receipt.update(phase='admitting', mutation_outcome='unconfirmed') - checkpoint(receipt) - raise RuntimeError('synthetic private provider text must not leave') - self.assertEqual(delivery.execute(ccr, proposal, digest, digest, path, fail), 1) - rows = [json.loads(line) for line in path.read_text().splitlines()] - self.assertEqual(len(rows), 3) - self.assertEqual(rows[0]['mutation_outcome'], 'not_started') - self.assertEqual(rows[1]['mutation_outcome'], 'unconfirmed') - self.assertEqual(rows[-1]['reason'], 'contained_admission_failure') - self.assertNotIn('private provider', path.read_text()) - self.assertEqual(path.stat().st_mode & 0o777, 0o600) - before = path.read_bytes() - with self.assertRaises(FileExistsError): delivery.execute(ccr, proposal, digest, digest, path, fail) - self.assertEqual(path.read_bytes(), before) - - def test_interrupted_attempt_never_records_verified(self): - ccr, proposal, digest = self.approved() - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp) / 'receipt.jsonl' - def stop(receipt, checkpoint): - receipt.update(phase='admitting', mutation_outcome='unconfirmed') - checkpoint(receipt); raise SystemExit(7) - with self.assertRaises(SystemExit): delivery.execute(ccr, proposal, digest, digest, path, stop) - rows = [json.loads(line) for line in path.read_text().splitlines()] - self.assertTrue(all(r['status'] == 'started' for r in rows)) - self.assertEqual(rows[-1]['mutation_outcome'], 'unconfirmed') - self.assertNotIn('completed_at', rows[-1]) - self.assertIn('interrupted_at', rows[-1]) - - def test_public_receipt_parent_refused(self): - ccr, proposal, digest = self.approved() - with tempfile.TemporaryDirectory() as tmp: - os.chmod(tmp, 0o755) - with self.assertRaises(delivery.Refused): - delivery.execute(ccr, proposal, digest, digest, Path(tmp)/'r', lambda *_: None) - self.assertEqual(list(Path(tmp).iterdir()), []) - - def test_execute_refusal_is_silent_and_does_not_create_receipt(self): - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp)/'receipt.jsonl' - r = subprocess.run([sys.executable, str(ROOT/'scripts/email_connect_smtp_delivery.py'), - '--execute', '--expect-digest', 'wrong', '--receipt', str(path)], - capture_output=True, timeout=10) - self.assertNotEqual(r.returncode, 0) - self.assertEqual((r.stdout, r.stderr), (b'', b'')) - self.assertFalse(path.exists()) - - def test_telemetry_module_is_not_rebound(self): - import telemetry_smtp_delivery - self.assertEqual(telemetry_smtp_delivery.LANE['namespace'], 'telemetry') - - -if __name__ == '__main__': unittest.main() diff --git a/tests/test_email_connect_smtp_foundation.py b/tests/test_email_connect_smtp_foundation.py deleted file mode 100644 index 5fdd921..0000000 --- a/tests/test_email_connect_smtp_foundation.py +++ /dev/null @@ -1,262 +0,0 @@ -"""Exercise the attended controller against synthetic Kubernetes/Bao boundaries.""" -import copy -import io -import json -from pathlib import Path -import subprocess -import sys -import tempfile -import unittest -from unittest.mock import patch, Mock - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts')) -import email_connect_smtp_foundation as foundation - - -class Native: - def __init__(self, proposal): - self.bundle = proposal['foundation'] - self.created = [] - self.objects = {} - self.bad_cluster = False - self.bad_binding = False - self.target_exists = False - self.fail_kind = None - self.not_ready = False - self.drift = False - self.changed_application = False - self.bad_ownership = False - - def get(self, kind, name, namespace=foundation.NS): - if kind == 'namespace': - key = 'cluster_uid' if name == 'kube-system' else 'namespace_uid' - return {'metadata': {'uid': 'wrong' if self.bad_cluster else self.bundle[key]}} - if kind == 'deployment': - return {'metadata': {'uid': self.bundle['deployment_uid'], 'generation': 74}, - 'spec': {'unchanged': not (self.created and self.changed_application)}} - if name in ('email-connect-config', 'email-connect-runtime'): - return {'metadata': {'uid': name}, 'spec': {}, 'data': {}} - return copy.deepcopy(self.objects.get((kind, name))) - - def target_metadata(self, name): - if self.target_exists: - return {'uid': 'foreign'} - if 'ExternalSecret' in self.created: - return {'ownerReferences': [{'uid': 'wrong' if self.bad_ownership else 'uid-ExternalSecret', 'controller': True}]} - return None - - def create(self, wanted): - kind = wanted['kind'] - self.created.append(kind) - if self.fail_kind == kind: - raise RuntimeError('synthetic provider secret text') - obj = copy.deepcopy(wanted) - obj['metadata']['uid'] = 'uid-'+kind - obj['status'] = {'conditions': [{'type': 'Ready', 'status': 'False' if self.not_ready else 'True'}], - 'refreshTime': '2026-10-31T12:00:00Z'} - if self.drift and kind == 'SecretStore': - obj['spec']['provider']['vault']['path'] = 'wrong' - self.objects[(kind, wanted['metadata']['name'])] = obj - return copy.deepcopy(obj) - - def jwt(self, *args): - return 'fixture-jwt' - - def token_review(self, jwt): - return {'authenticated': not self.bad_binding, 'audiences': ['openbao'], - 'user': {'username': 'system:serviceaccount:email-connect:'+foundation.SA, 'uid': 'uid-ServiceAccount'}} - - def wait(self, resource): - pass - - -def completed(value, code=0): - return subprocess.CompletedProcess([], code, json.dumps(value).encode(), b'403 permission denied' if code else b'') - - -class FoundationTests(unittest.TestCase): - def setUp(self): - self.ccr, self.proposal, self.digest = foundation.prepare() - self.native = Native(self.proposal) - self.validator = Mock() - self.records = [] - self.owner_calls = [] - - def owner(self, args, **kwargs): - if args[0] == 'status': - return completed({'cluster_id': foundation.admission.OPENBAO_CLUSTER_ID, 'initialized': True, 'sealed': False}) - return completed({'data': {'policies': ['platform-admin']}}) - - def run_controller(self): - approved = copy.deepcopy(self.ccr) - approved['status'] = 'approved' - approved['openbao']['auth']['bound_claims_confirmed'] = True - def admit(row, checkpoint): - self.owner_calls.append('admit') - row.update(status='reader_admitted_verified'); checkpoint(row) - receipt = {'proposal_digest': self.digest, 'status': 'started'} - with patch.object(foundation, 'bao', side_effect=self.owner), \ - patch.object(foundation.admission, 'module', return_value=self.validator), \ - patch.object(foundation.admission, 'prepare', return_value=(approved, {}, self.proposal['admission_digest'])), \ - patch.object(foundation.admission, 'admit', side_effect=admit), \ - patch.object(foundation, 'verify_login', return_value={'native_consumer_auth_verified': True}): - foundation.run(self.proposal, receipt, lambda r: self.records.append(copy.deepcopy(r)), self.native) - return receipt - - def test_offline_proposal_and_live_default_refusal(self): - self.assertEqual(self.ccr['status'], 'proposed') - self.assertEqual(len(self.proposal['foundation']['resources']), 3) - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp)/'receipt' - with self.assertRaises(foundation.admission.Refused): - foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path) - self.assertFalse(path.exists()) - - def test_complete_controller_requires_binding_before_admission_and_ready_after(self): - result = self.run_controller() - self.assertEqual(self.native.created, ['ServiceAccount', 'SecretStore', 'ExternalSecret']) - self.validator.confirm_binding.assert_called_once() - self.assertEqual(result['status'], 'foundation_admitted_projection_ready') - self.assertFalse(result['application_sender_changed']) - phases = [r['phase'] for r in self.records] - self.assertLess(phases.index('binding_verified'), phases.index('owner_admission')) - self.assertLess(phases.index('native_login_verification'), phases.index('creating_secretstore')) - - def test_wrong_cluster_and_existing_targets_precede_mutation(self): - for mode in ('cluster', 'target', 'existing'): - with self.subTest(mode=mode): - self.native = Native(self.proposal) - self.native.bad_cluster = mode == 'cluster' - self.native.target_exists = mode == 'target' - if mode == 'existing': - self.native.objects[('SecretStore', 'openbao-email-connect-platform-smtp')] = {'foreign': True} - with self.assertRaises(foundation.admission.Refused): - self.run_controller() - self.assertFalse(self.native.created) - self.assertFalse(self.owner_calls) - - def test_bad_tokenreview_never_confirms_or_admits(self): - self.native.bad_binding = True - with self.assertRaises(foundation.admission.Refused): self.run_controller() - self.assertEqual(self.native.created, ['ServiceAccount']) - self.validator.confirm_binding.assert_not_called() - self.assertFalse(self.owner_calls) - - def test_projection_drift_not_ready_foreign_owner_and_application_changes_refuse(self): - for field in ('drift', 'not_ready', 'bad_ownership', 'changed_application'): - with self.subTest(field=field): - self.native = Native(self.proposal); setattr(self.native, field, True) - with self.assertRaises(foundation.admission.Refused): self.run_controller() - self.assertFalse(any(r.get('status') == 'foundation_admitted_projection_ready' for r in self.records)) - - def test_creation_failure_keeps_checkpoint_and_does_not_rollback_or_retry(self): - self.native.fail_kind = 'SecretStore' - with self.assertRaises(RuntimeError): self.run_controller() - self.assertEqual(self.native.created, ['ServiceAccount', 'SecretStore']) - self.assertEqual(self.records[-1]['phase'], 'creating_secretstore') - self.assertEqual(self.records[-1]['mutation_outcome'], 'unconfirmed') - - def test_journal_failure_silent_private_and_exclusive(self): - self.ccr['status'] = 'approved' - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp)/'receipt' - def fail(proposal, receipt, checkpoint): - receipt.update(phase='creating_service_account', mutation_outcome='unconfirmed') - checkpoint(receipt) - raise RuntimeError('synthetic private provider text') - self.assertEqual(foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path, fail), 1) - raw = path.read_bytes() - rows = [json.loads(line) for line in raw.splitlines()] - self.assertEqual(rows[-1]['status'], 'failed_requires_inspection') - self.assertEqual(rows[-1]['phase'], 'creating_service_account') - self.assertNotIn(b'private provider', raw) - self.assertEqual(path.stat().st_mode & 0o777, 0o600) - with self.assertRaises(FileExistsError): - foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path, fail) - self.assertEqual(path.read_bytes(), raw) - - def test_interruption_cannot_be_success(self): - self.ccr['status'] = 'approved' - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp)/'receipt' - def interrupt(*args): raise SystemExit(9) - with self.assertRaises(SystemExit): - foundation.execute(self.ccr, self.proposal, self.digest, self.digest, path, interrupt) - last = json.loads(path.read_text().splitlines()[-1]) - self.assertEqual(last['status'], 'started') - self.assertIn('interrupted_at', last) - - def test_native_login_scope_denials_and_all_tokens_revoked(self): - calls = [] - def bao(args, **kw): - calls.append((args, kw)) - if args[:2] == ['write', '-format=json']: - # The fake JWT identifies the requested service account/audience. - if kw['payload']['jwt'] != foundation.SA+':openbao': return completed({}, 1) - return completed({'auth': {'client_token': 'fixture', 'token_policies': [foundation.SA], 'lease_duration': 900}}) - if args[0] == 'read': - return completed({'data': {'data': dict(foundation.admission.lane_module().FIELDS, SMTP_PASSWORD='fixture'), 'metadata': {'version': 2}}}) - if args[0] == 'write': return completed({}) - return completed({}, 1) - def caps(token, paths): - return dict(zip(paths, [['read'], ['deny'], ['deny'], ['deny'], ['read'], ['update']])) - native = Mock() - native.jwt.side_effect = lambda sa=foundation.SA, audience='openbao': sa+':'+audience - with patch.object(foundation, 'bao', side_effect=bao), patch.object(foundation, 'capabilities', side_effect=caps): - result = foundation.verify_login(native) - self.assertTrue(result['other_service_account_denied']) - self.assertTrue(result['workload_token_revocation_verified']) - self.assertEqual(sum(a == ['write', '-f', 'auth/token/revoke-self'] for a, _ in calls), 1) - - def test_wrong_digest_and_cli_refusal_are_before_receipt_or_owner_calls(self): - self.ccr['status'] = 'approved' - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp)/'receipt' - with self.assertRaises(foundation.admission.Refused): - foundation.execute(self.ccr, self.proposal, self.digest, 'wrong', path) - result = subprocess.run([sys.executable, str(Path(foundation.__file__)), '--execute', - '--expect-digest', 'wrong', '--receipt', str(path)], capture_output=True, timeout=10) - self.assertNotEqual(result.returncode, 0) - self.assertEqual((result.stdout, result.stderr), (b'', b'')) - self.assertFalse(path.exists()) - - def test_metadata_query_negotiates_partial_object_and_cleans_proxy(self): - for status, payload, refuses in [ - (404, {}, False), - (200, {'kind': 'PartialObjectMetadata', 'apiVersion': 'meta.k8s.io/v1', 'metadata': {'uid': 'fixture'}}, False), - (200, {'kind': 'Secret', 'apiVersion': 'v1', 'data': {'fixture': 'must-not-be-accepted'}}, True), - (403, {}, True), - ]: - with self.subTest(status=status, refuses=refuses): - response = io.BytesIO(json.dumps(payload).encode()); response.status = status - process = Mock(); process.poll.return_value = None - conn = Mock(); conn.getresponse.return_value = response - with patch.object(foundation.subprocess, 'Popen', return_value=process), \ - patch.object(foundation.socket, 'socket'), \ - patch.object(foundation.http.client, 'HTTPConnection', return_value=conn), \ - patch.object(foundation.time, 'sleep'): - if refuses: - with self.assertRaises(foundation.admission.Refused): - foundation.Native().target_metadata('email-connect-platform-smtp') - else: - result = foundation.Native().target_metadata('email-connect-platform-smtp') - self.assertEqual(result, None if status == 404 else {'uid': 'fixture'}) - self.assertEqual(conn.request.call_args.kwargs['headers']['Accept'], - 'application/json;as=PartialObjectMetadata;g=meta.k8s.io;v=v1') - process.terminate.assert_called_once(); process.wait.assert_called_once() - conn.close.assert_called_once() - - def test_native_scope_failure_still_revokes_workload_token(self): - calls = [] - def bao(args, **kwargs): - calls.append(args) - if args[:2] == ['write', '-format=json']: - return completed({'auth': {'client_token': 'fixture', 'token_policies': ['too-broad'], 'lease_duration': 900}}) - if args[:2] == ['write', '-f']: return completed({}) - return completed({}, 1) - with patch.object(foundation, 'bao', side_effect=bao): - with self.assertRaises(foundation.admission.Refused): foundation.verify_login(Mock()) - self.assertIn(['write', '-f', 'auth/token/revoke-self'], calls) - - -if __name__ == '__main__': unittest.main() diff --git a/tests/test_eso_adoption_readiness.py b/tests/test_eso_adoption_readiness.py deleted file mode 100644 index 70cf303..0000000 --- a/tests/test_eso_adoption_readiness.py +++ /dev/null @@ -1,38 +0,0 @@ -import copy -from datetime import datetime,timezone,timedelta -import importlib.util -import json -from pathlib import Path -import unittest -import yaml -spec=importlib.util.spec_from_file_location('eso_gate',Path(__file__).resolve().parents[1]/'scripts/eso_adoption_readiness.py');gate=importlib.util.module_from_spec(spec);spec.loader.exec_module(gate) -NOW=datetime(2026,10,11,21,0,tzinfo=timezone.utc) - -class ReadinessTests(unittest.TestCase): - def app(self):return yaml.safe_load((Path(__file__).resolve().parents[1]/'argocd/railiance01/drafts/external-secrets.application.yaml').read_text()) - def issue(self):return {'schema_version':'rapp-issue-core-adoption/v2','passed':True,'captured_at':NOW.isoformat(),'checks':{'estate_node_ip':True,'argo_synced_healthy':True,'argo_tracking':True,'external_secret_ready':True,'private_service':True,'source_revision':'a'*40,'healthz_status':200,'unauthenticated_ingest_status':401,'authenticated_ingest_status':201,'authenticated_close_status':200}} - def target(self):return {'schema_version':'argocd-workload-adoption-proof/v1','status':'verified','application':'target-revenue','captured_at':NOW.isoformat(),'manual_apply_prune_off':True,'child_automation_enabled':False} - def test_prepared_source_cannot_skip_issue_core_dependency(self): - result=gate.assess(self.app(),{},self.target(),NOW);self.assertTrue(result['source_ready']);self.assertFalse(result['ready_for_fresh_native_render_diff']);self.assertEqual(result['missing_or_invalid'],['RPF-WP-0044-T04']);self.assertFalse(result['founder_approval_required']) - def test_complete_fresh_predecessors_allow_diff_only(self): - result=gate.assess(self.app(),self.issue(),self.target(),NOW);self.assertTrue(result['ready_for_fresh_native_render_diff']);self.assertFalse(result['native_mutation']);self.assertEqual(result['next_action'],'fresh_native_render_diff') - def test_stale_future_wrong_estate_or_missing_smoke_cannot_authorize_adoption(self): - for key,value in [('captured_at',(NOW-timedelta(minutes=6)).isoformat()),('captured_at',(NOW+timedelta(seconds=1)).isoformat())]: - issue=self.issue();issue[key]=value;self.assertFalse(gate.assess(self.app(),issue,self.target(),NOW)['ready_for_fresh_native_render_diff']) - for key,value in [('estate_node_ip',False),('argo_tracking',False),('authenticated_close_status',None),('source_revision','main')]: - issue=self.issue();issue['checks'][key]=value;self.assertFalse(gate.assess(self.app(),issue,self.target(),NOW)['ready_for_fresh_native_render_diff']) - def test_crd_guards_manual_policy_and_no_replace_required(self): - for mutation in ('guard','automation','replace','chart'): - app=self.app() - if mutation=='guard':app['spec']['source']['helm']['values']='installCRDs: true\n' - elif mutation=='automation':app['spec']['syncPolicy']['automated']={} - elif mutation=='replace':app['spec']['syncPolicy']['syncOptions'].append('Replace=true') - else:app['spec']['source']['targetRevision']='latest' - result=gate.assess(app,self.issue(),self.target(),NOW);self.assertFalse(result['source_ready']);self.assertFalse(result['ready_for_fresh_native_render_diff']) - def test_invalid_canary_fields_never_echo_values(self): - app=self.app();app['spec']['source']['helm']['values']='SECRET_CANARY';issue=self.issue();issue['private_value']='SECRET_CANARY';result=gate.assess(app,issue,{},NOW);self.assertNotIn('SECRET_CANARY',json.dumps(result)) - def test_target_revenue_proof_must_be_exact_application_and_prune_off(self): - for key,value in [('application','foreign-app'),('manual_apply_prune_off',False),('child_automation_enabled',True),('status','unverified')]: - target=self.target();target[key]=value;self.assertFalse(gate.assess(self.app(),self.issue(),target,NOW)['ready_for_fresh_native_render_diff']) - -if __name__=='__main__':unittest.main() diff --git a/tests/test_eso_attended_checks.py b/tests/test_eso_attended_checks.py deleted file mode 100644 index f03eb1a..0000000 --- a/tests/test_eso_attended_checks.py +++ /dev/null @@ -1,168 +0,0 @@ -import copy -import importlib.util -import json -from pathlib import Path -import subprocess -import sys -import tempfile -import unittest - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location('eso_checks', ROOT / 'scripts/eso_attended_checks.py') -checks = importlib.util.module_from_spec(spec) -spec.loader.exec_module(checks) -CRED = 'Sec' + 'ret' - - -def crd(i, annotated=True): - meta = {'name': f'r{i}.external-secrets.io', 'labels': {'app': 'eso'}} - if annotated: - meta['annotations'] = {'argocd.argoproj.io/sync-options': 'Prune=false,Delete=false'} - return {'apiVersion': 'apiextensions.k8s.io/v1', 'kind': 'CustomResourceDefinition', 'metadata': meta, - 'spec': {'group': 'external-secrets.io', 'scope': 'Namespaced'}} - - -DEPLOY = {'apiVersion': 'apps/v1', 'kind': 'Deployment', 'metadata': {'name': 'external-secrets', 'namespace': 'external-secrets'}, - 'spec': {'replicas': 1, 'template': {'spec': {'containers': [{'name': 'c', 'image': 'eso:v0.16.1'}]}}}} -HOLDER = {'apiVersion': 'v1', 'kind': CRED, 'metadata': {'name': 'external-secrets-webhook', 'namespace': 'external-secrets'}} - - -def render(): - return [crd(i) for i in range(20)] + [DEPLOY] - - -def live(): - out = [crd(i, annotated=False) for i in range(20)] - for obj in out: - obj['spec']['versions'] = ['defaulted'] - obj['status'] = {'x': 1} - deploy = copy.deepcopy(DEPLOY) - deploy['spec']['template']['spec']['containers'][0]['imagePullPolicy'] = 'IfNotPresent' - return out + [deploy] - - -class ClassifyTests(unittest.TestCase): - def test_metadata_only_crd_annotation_is_go(self): - result = checks.classify(render(), live()) - self.assertTrue(result['go'], result['problems']) - self.assertEqual(result['counts']['metadata-only'], 20) - self.assertEqual(result['counts']['unchanged'], 1) - - def test_crd_spec_change_is_no_go(self): - objs = live(); objs[3]['spec']['scope'] = 'Cluster' - result = checks.classify(render(), objs) - self.assertFalse(result['go']) - self.assertIn('crd_spec_change:r3.external-secrets.io', result['problems']) - - def test_missing_crd_is_no_go(self): - result = checks.classify(render(), live()[1:]) - self.assertIn('crd_missing_live:r0.external-secrets.io', result['problems']) - - def test_workload_spec_change_needs_review(self): - objs = live(); objs[-1]['spec']['template']['spec']['containers'][0]['image'] = 'eso:v0.15' - self.assertFalse(checks.classify(render(), objs)['go']) - - def test_crd_count_and_store_objects_guarded(self): - self.assertIn('render_crd_count:19', checks.classify(render()[1:], live())['problems']) - store = {'apiVersion': 'external-secrets.io/v1beta1', 'kind': 'ClusterSecretStore', 'metadata': {'name': 's'}} - self.assertIn('render_contains_store_or_externalsecret', checks.classify(render() + [store], live() + [store])['problems']) - - def test_credential_kind_in_live_input_refused(self): - with self.assertRaises(ValueError): - checks.classify(render(), live() + [HOLDER]) - - def test_split_excludes_credential_kind(self): - keep, held = checks.split_render(yaml.safe_dump_all(render() + [HOLDER])) - self.assertEqual(len(keep), 21) - self.assertEqual(held, [('', CRED, 'external-secrets', 'external-secrets-webhook')]) - - -def stores(failing): - return {'items': [{'metadata': {'name': n}, 'spec': {'provider': {'vault': {'auth': {'tokenSecretRef': {'name': 'never-printed'}}}}}, - 'status': {'conditions': [{'type': 'Ready', 'status': 'False' if n in failing else 'True', - 'reason': 'ValidationFailed' if n in failing else 'Valid'}]}} - for n in ('openbao', 'openbao-activity-core', 'openbao-email-connect', 'openbao-issue-core')]} - - -class StoreAndPostTests(unittest.TestCase): - def test_expected_failures_are_go_and_no_spec_in_receipt(self): - result = checks.store_baseline(stores({'openbao-activity-core', 'openbao-email-connect'})) - self.assertTrue(result['go']) - self.assertNotIn('never-printed', json.dumps(result)) - - def test_unexpected_failure_is_no_go(self): - result = checks.store_baseline(stores({'openbao-issue-core'})) - self.assertEqual(result['unexpected_not_ready'], ['openbao-issue-core']) - self.assertFalse(result['go']) - - def test_empty_store_list_is_no_go(self): - self.assertFalse(checks.store_baseline({'items': []})['go']) - - def app(self, **status): - base = {'sync': {'status': 'Synced', 'revision': '0.16.1'}, 'health': {'status': 'Healthy'}, - 'operationState': {'phase': 'Succeeded'}, 'resources': [{}] * 39} - base.update(status) - return {'spec': {'syncPolicy': {'syncOptions': ['ServerSideApply=true']}}, 'status': base} - - def test_postcheck_pass_and_failures(self): - crds = [f'r{i}.external-secrets.io' for i in range(20)] + ['other.example.io'] - before = checks.store_baseline(stores(set())) - self.assertTrue(checks.postcheck(self.app(), crds, before, before)['go']) - self.assertIn('crd_count:19', checks.postcheck(self.app(), crds[1:], before, before)['problems']) - changed = checks.store_baseline(stores({'openbao'})) - self.assertIn('store_baseline_changed', checks.postcheck(self.app(), crds, before, changed)['problems']) - failed = self.app(operationState={'phase': 'Failed'}) - self.assertFalse(checks.postcheck(failed, crds, before, before)['go']) - - def test_precheck_app_refuses_automation_and_errors(self): - app = self.app(conditions=[{'type': 'ComparisonError'}]) - app['spec']['syncPolicy']['automated'] = {} - result = checks.precheck_app(app, 39) - self.assertIn('automated_sync_present', result['problems']) - self.assertIn('argo_error_condition', result['problems']) - self.assertTrue(checks.precheck_app(self.app(), 39)['go']) - - def test_refresh_sample(self): - before = {'status': {'refreshTime': 'a', 'conditions': [{'type': 'Ready', 'status': 'True'}]}} - after = {'status': {'refreshTime': 'b', 'conditions': [{'type': 'Ready', 'status': 'True'}]}} - self.assertTrue(checks.refresh_sample(before, after)['go']) - self.assertFalse(checks.refresh_sample(before, before)['go']) - - -class ScriptTests(unittest.TestCase): - def test_dry_run_prints_only_and_writes_nothing(self): - evidence = ROOT / 'docs/evidence/2026-10-21-eso-adoption-prep' - before = set(evidence.iterdir()) - result = subprocess.run([str(ROOT / 'scripts/rpf_wp_0044_t06_attended.sh'), '--dry-run'], - capture_output=True, text=True, timeout=60) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(set(evidence.iterdir()), before) - self.assertIn('sudo\\ -n\\ kubectl', result.stdout) - self.assertNotIn('ssh -t', result.stdout) - self.assertIn('would ask for ADOPT', result.stdout) - self.assertIn('patch\\ application\\ external-secrets', result.stdout) - self.assertNotIn('--prune', result.stdout) - - def test_script_never_prunes_or_force_syncs(self): - text = (ROOT / 'scripts/rpf_wp_0044_t06_attended.sh').read_text() - sync = text.split("SYNC_JSON='", 1)[1].split("'", 1)[0] - payload = json.loads(sync)['operation']['sync'] - self.assertIs(payload['prune'], False) - self.assertIs(payload['syncStrategy']['apply']['force'], False) - self.assertEqual(payload['syncOptions'], ['ServerSideApply=true']) - self.assertNotIn('--prune', text) - self.assertIn('read -r -p "Type ADOPT', text) - - def test_gate_cli_exit_codes(self): - with tempfile.TemporaryDirectory() as tmp: - path = Path(tmp) / 'r.json' - path.write_text(json.dumps({'ready_for_fresh_native_render_diff': False, 'missing_or_invalid': ['RPF-WP-0044-T04']})) - self.assertEqual(checks.main(['gate', '--readiness', str(path)]), 3) - path.write_text(json.dumps({'ready_for_fresh_native_render_diff': True})) - self.assertEqual(checks.main(['gate', '--readiness', str(path)]), 0) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_flex_auth_signer_custody.py b/tests/test_flex_auth_signer_custody.py deleted file mode 100644 index 797b657..0000000 --- a/tests/test_flex_auth_signer_custody.py +++ /dev/null @@ -1,115 +0,0 @@ -"""Actual disposable KV CAS/readback; no estate key, identity or grants.""" -from dataclasses import replace -import base64 -import json -from pathlib import Path -import sys - -import pytest -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from test_task_execution_native_fixture import bao_dev -from flex_auth_signer_custody import SignerWriter,FixtureWriterAdmission,BINDINGS,PATH -from task_execution_journal import Refused - - -@pytest.fixture -def fixture(bao_dev,tmp_path): - origin,root,call,process=bao_dev - assert call('POST','sys/mounts/platform',{'type':'kv','options':{'version':'2'}})[0] in {200,204} - directory=tmp_path/'receipts';directory.mkdir(mode=0o700) - admission=FixtureWriterAdmission(int(origin.rsplit(':',1)[1]),root,process,True) - def writer(operation='first',version=0,**kwargs): - return SignerWriter(admission,directory,operation,kid=kwargs.pop('kid','synthetic-key-'+str(version)), - expected_version=version,bindings={key:'sha256:'+'a'*64 for key in BINDINGS},**kwargs) - return writer,call,directory,admission - - -def test_actual_bootstrap_rotation_public_correspondence_and_cas_conflict(fixture): - writer,call,directory,admission=fixture - first=writer();receipt=first.write() - assert receipt['status']=='custody-verified-pending-independent-public-admission' - assert receipt['kv_version']==1 and receipt['exact_readback'] - document=call('GET',PATH)[1]['data']['data'] - seed=base64.urlsafe_b64decode(document['ED25519_PRIVATE_KEY']+'=') - public=Ed25519PrivateKey.from_private_bytes(seed).public_key().public_bytes(serialization.Encoding.Raw,serialization.PublicFormat.Raw) - assert len(seed)==32 - assert base64.urlsafe_b64encode(public).decode().rstrip('=')==receipt['public_key']==document['ED25519_PUBLIC_KEY'] - raw=(directory/'first.json').read_bytes() - assert document['ED25519_PRIVATE_KEY'].encode() not in raw and admission.owner_token.encode() not in raw - assert (directory/'first.json').stat().st_mode&0o777==0o600 - with pytest.raises(Refused):first.write() - with pytest.raises(FileExistsError):writer() - conflict=writer('conflict',0) - with pytest.raises(Refused,match='outcome unconfirmed'):conflict.write() - assert json.loads((directory/'conflict.json').read_bytes())['status']=='write-intent-unknown' - assert call('GET',PATH)[1]['data']['metadata']['version']==1 - second=writer('rotation',1,kid='synthetic-successor').write() - assert second['kv_version']==2 and second['public_key']!=receipt['public_key'] - assert call('GET',PATH+'?version=1')[1]['data']['data']==document - assert call('GET',PATH)[1]['data']['data']['kid']=='synthetic-successor' - - -@pytest.mark.parametrize('case',['lost-response','readback-mismatch','read-denied','write-denied']) -def test_unknown_is_durable_and_no_retry_or_private_output(fixture,monkeypatch,case): - factory,call,directory,admission=fixture - writer=factory();original=writer._http;seen=[] - def fault(method,path,body=None): - seen.append((method,path)) - if case=='write-denied' and method=='POST':return 403,{'errors':['synthetic secret backend detail']} - if case=='read-denied' and method=='GET':return 403,{'errors':['synthetic secret backend detail']} - result=original(method,path,body) - if case=='lost-response' and method=='POST':raise OSError('synthetic secret backend detail') - if case=='readback-mismatch' and method=='GET':result[1]['data']['data']['kid']='different' - return result - monkeypatch.setattr(writer,'_http',fault) - with pytest.raises(Refused,match='^signer custody outcome unconfirmed$'):writer.write() - raw=(directory/'first.json').read_bytes();receipt=json.loads(raw) - assert receipt['status']=='write-intent-unknown' and receipt['exact_readback'] is False - assert b'synthetic secret backend detail' not in raw and admission.owner_token.encode() not in raw - if case!='write-denied': - document=call('GET',PATH)[1]['data']['data'] - assert document['ED25519_PRIVATE_KEY'].encode() not in raw - before=list(seen) - with pytest.raises(Refused):writer.write() - assert seen==before - with pytest.raises(FileExistsError):factory() - - -def test_durable_intent_failure_prevents_effect(fixture,monkeypatch): - factory,call,directory,admission=fixture - writer=factory() - def fail(**kwargs):raise OSError('private storage failed') - monkeypatch.setattr(writer,'_save',fail) - with pytest.raises(OSError):writer.write() - assert call('GET',PATH)[0]==404 - - -@pytest.mark.parametrize('case',['unfenced','wrong-port','bool-version','indirect-directory','unsafe-directory']) -def test_invalid_custody_contract_before_generation_or_http(fixture,case): - factory,call,directory,admission=fixture - expected=0 - if case=='unfenced':admission=replace(admission,fenced=False) - elif case=='wrong-port':admission=replace(admission,port=admission.port+1) - elif case=='bool-version':expected=True - elif case=='indirect-directory': - link=directory.parent/'indirect';link.symlink_to(directory,target_is_directory=True);directory=link - elif case=='unsafe-directory':directory.chmod(0o755) - with pytest.raises(Refused):SignerWriter(admission,directory,'bad',kid='synthetic',expected_version=expected, - bindings={key:'sha256:'+'a'*64 for key in BINDINGS}) - assert call('GET',PATH)[0]==404 - - -def test_failed_success_receipt_retains_unknown_after_actual_write(fixture,monkeypatch): - factory,call,directory,admission=fixture - writer=factory();original=writer._save - def fail_success_only(**kwargs): - if writer.receipt['status']=='custody-verified-pending-independent-public-admission': - raise OSError('synthetic receipt failure') - return original(**kwargs) - monkeypatch.setattr(writer,'_save',fail_success_only) - with pytest.raises(Refused,match='outcome unconfirmed'):writer.write() - assert call('GET',PATH)[1]['data']['metadata']['version']==1 - assert json.loads((directory/'first.json').read_bytes())['status']=='write-intent-unknown' - with pytest.raises(Refused):writer.write() diff --git a/tests/test_flex_auth_signer_native.py b/tests/test_flex_auth_signer_native.py deleted file mode 100644 index db0e9dd..0000000 --- a/tests/test_flex_auth_signer_native.py +++ /dev/null @@ -1,346 +0,0 @@ -"""Actual disposable KV effects through native production state machine. - -Kubernetes, attended identity and owner dispositions are explicitly synthetic -fixture inputs; no production authority/identity/custody claim follows. -""" -from copy import deepcopy -from datetime import datetime,timedelta,timezone -import hashlib -import json -import os -from pathlib import Path -import shutil -import sys -import yaml -import pytest -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from test_task_execution_native_fixture import bao_dev -import flex_auth_signer_native as m -from task_execution_journal import Refused - - -@pytest.fixture -def native(bao_dev,tmp_path,monkeypatch): - origin,token,call,process=bao_dev - assert call('POST','sys/mounts/platform',{'type':'kv','options':{'version':'2'}})[0] in {200,204} - source=tmp_path/'source';source.mkdir() - for name in [*m.SOURCE_FILES,m.CCR,m.PLAN,'openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl']: - p=source/name;p.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(m.ROOT/name,p) - ccr=yaml.safe_load((source/m.CCR).read_text());ccr['status']='approved';ccr['openbao']['auth']['bound_claims_confirmed']=True - (source/m.CCR).write_text(yaml.safe_dump(ccr,sort_keys=False)) - plan=json.loads((source/m.PLAN).read_text());plan['bootstrap']['kid']='synthetic-native-kid';plan['receipts']['directory']=str(tmp_path/'private');(source/m.PLAN).write_text(json.dumps(plan)) - monkeypatch.setattr(m,'ROOT',source) - directory=tmp_path/'private';directory.mkdir(mode=0o700) - image='forgejo.coulomb.social/coulomb/flex-auth@sha256:'+'1'*64 - dep={'metadata':{'uid':'dep','generation':1},'spec':{'replicas':0,'template':{'spec':{'serviceAccountName':m.NAME, - 'containers':[{'image':image,'args':['serve','--caller-auth-mode','enforce','--caller-audience','flex-auth','--caller-binding','secrets-engine=system:serviceaccount:secrets-engine:secrets-engine','--envelope-signing-key-file','/var/run/secrets/decision-envelope-signing/seed','--envelope-signing-kid','synthetic-native-kid'], - 'volumeMounts':[{'name':'flex-auth-envelope-signing','mountPath':'/var/run/secrets/decision-envelope-signing','readOnly':True}]}], - 'volumes':[{'name':'flex-auth-envelope-signing','secret':{'secretName':m.SECRET,'defaultMode':288,'items':[{'key':'seed','path':'seed'}]}}]}}},'status':{'observedGeneration':1,'replicas':0}} - class IO: - def __init__(self): - self.calls=[];self.dep=deepcopy(dep);self.pods=[];self.hpas=[];self.rs=[];self.cluster=m.CLUSTER - self.entity='11111111-1111-1111-1111-111111111111';self.backend=m.BAO_CLUSTER;self.root=False;self.role_drift=False;self.lost=False;self.denied=False;self.pod_on_recheck=False;self.fences=0 - def bao(self,args,payload=None,allowed=(0,)): - self.calls.append(('bao',tuple(args))) - if args[0]=='status':return 0,{'cluster_id':self.backend,'sealed':False} - if args[:2]==['token','lookup']:return 0,{'data':{'policies':['root'] if self.root else ['platform-admin'],'path':'auth/netkingdom/login/x','entity_id':self.entity,'id':token}} - if args[0]=='audit':return 0,{'file/':{}} - if args[-1].startswith('sys/policies/acl/'): - return 0,{'data':{'policy':(source/'openbao/policies/workload-kv-read-flex-auth-envelope-signing.hcl').read_text()}} - if args[-1].startswith('auth/kubernetes/role/'): - return 0,{'data':{'bound_service_account_names':['foreign'] if self.role_drift else ['flex-auth-envelope-signing-eso'],'bound_service_account_namespaces':['flex-auth'],'bound_service_account_namespace_selector':'','audience':'openbao','token_policies':['workload-kv-read-flex-auth-envelope-signing'],'token_ttl':900,'token_max_ttl':900,'token_explicit_max_ttl':900,'token_no_default_policy':True,'token_period':0,'token_num_uses':0,'token_bound_cidrs':[]}} - raise AssertionError('unexpected fixed API') - def kube(self,resource,name=None): - self.calls.append(('kube',resource,name)) - if resource=='namespace':return {'metadata':{'uid':self.cluster}} - if resource=='deployment': - self.fences+=1 - if self.pod_on_recheck and self.fences>=3:self.pods=[{'spec':{'serviceAccountName':m.NAME}}] - return deepcopy(self.dep) - return {'items':deepcopy({'pods':self.pods,'hpa':self.hpas,'replicasets':self.rs}[resource])} - def http(self,method,path,body=None): - self.calls.append(('http',method,path)) - if self.denied and path in {m.PATH,m.PATH+'?version=1'} and method=='GET':return 403,{'errors':['secret backend detail']} - result=call(method,path,body) - if self.lost and method=='POST' and path==m.PATH:raise OSError('secret response lost') - return result - io=IO() - c={'schema':'railiance-platform.signer-native-admission.v1','action':'bootstrap','operation_id':'synthetic-native-operation','kid':'synthetic-native-kid', - 'expires_at':(datetime.now(timezone.utc)+timedelta(seconds=100)).isoformat(),'founder_entity_id':io.entity, - 'ccr_sha256':m.digest((source/m.CCR).read_bytes()),'source_bundle':{n:m.digest((source/n).read_bytes()) for n in m.SOURCE_FILES}, - 'bindings':{k:'sha256:'+'2'*64 for k in m.BINDINGS},'producer_image':image,'caller_binding':'secrets-engine=system:serviceaccount:secrets-engine:secrets-engine', - 'deployment_projection_sha256':m.digest(m.encoded(m.deployment_projection(dep))), - 'authority':'founder-attended-exact-source-scope; no standing grant', - 'routing':{'lane_id':'openbao-platform-admin-login','verdict':'founder_required','founder_act':'oidc_login','receipt_sha256':'sha256:'+'3'*64}, - 'owner_reviews':{},'exclusive_window_sha256':'sha256:'+'4'*64,'consumer_trust_sha256':'sha256:'+'5'*64,'original_receipt_sha256':None,'original_execution':None} - c['bindings'].update(proposal_sha256=m.digest((source/m.PLAN).read_bytes()),policy_sha256=m.LIFECYCLE,image_sha256='sha256:'+'1'*64,trust_sha256=c['consumer_trust_sha256'],deployment_sha256=c['deployment_projection_sha256'],executed_revision_sha256=m.digest(m.encoded(c['source_bundle']))) - def approve(value): - if value['action']=='recover' and value['original_execution'] is None: - old=json.loads((directory/(c['operation_id']+'.json')).read_bytes()) - value['original_execution']={key:deepcopy(old[key]) for key in ['bindings','writer_sha256','source_ccr_sha256']} - scope=m.review_scope_digest(value) - value['owner_reviews']={role:{'approved_admission_sha256':scope,'approval_ref':'synthetic-fixture-'+role} for role in m.REVIEWS} - return value - approve(c) - return c,io,directory,call,approve,source - - -def test_actual_native_bootstrap_exact_readback_and_no_retry(native): - c,io,directory,call,approve,source=native - result=m.execute(c,directory,io=io) - assert result['status']=='custody-verified-pending-independent-public-admission' and result['exact_readback'] - assert call('GET',m.META)[1]['data']['current_version']==1 - assert [(x[1],x[2]) for x in io.calls if x[0]=='http' and x[1]=='POST']==[('POST',m.META),('POST',m.PATH)] - private=call('GET',m.PATH)[1]['data']['data']['ED25519_PRIVATE_KEY'] - raw=(directory/(c['operation_id']+'.json')).read_bytes();assert private.encode() not in raw - before=len(io.calls) - with pytest.raises(FileExistsError):m.execute(c,directory,io=io) - assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls[before:]) - - -def test_actual_process_loss_unknown_fresh_recovery_read_only(native): - c,io,directory,call,approve,source=native - child=os.fork() - if child==0: - original_http=io.http - def die_after_native_write(method,path,body=None): - result=original_http(method,path,body) - if method=='POST' and path==m.PATH:os._exit(73) - return result - io.http=die_after_native_write - try:m.execute(c,directory,io=io) - except BaseException:os._exit(74) - os._exit(75) - _,status=os.waitpid(child,0) - assert os.waitstatus_to_exitcode(status)==73 - old=directory/(c['operation_id']+'.json');original=old.read_bytes();assert json.loads(original)['status']=='write-intent-unknown' - recovery=deepcopy(c);recovery.update(action='recover',operation_id='synthetic-independent-recovery',original_receipt_sha256=m.digest(original));approve(recovery) - io.lost=False;io.calls.clear() - result=m.execute(recovery,directory,io=io,original_receipt=old) - assert result['status']=='custody-reconciled-pending-independent-public-admission' - assert result['no_write_or_remint'] and old.read_bytes()==original - assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls) - assert call('GET',m.META)[1]['data']['current_version']==1 - assert (directory/(recovery['operation_id']+'.json')).stat().st_mode&0o777==0o600 - io.calls.clear() - with pytest.raises(FileExistsError):m.execute(recovery,directory,io=io,original_receipt=old) - assert not any(x[0]=='http' for x in io.calls) - - -@pytest.mark.parametrize('case',['missing-image','missing-trust','mismatched-source','bad-review','bad-route','expired','wrong-entity','wrong-backend','wrong-cluster','root','pod','terminating-pod','hpa','replicas','image-drift','role-drift','pod-after-metadata']) -def test_refusals_no_seed_effect(native,case): - c,io,directory,call,approve,source=native - if case=='missing-image':c['producer_image']='' - elif case=='missing-trust':c['consumer_trust_sha256']='' - elif case=='mismatched-source':(source/'scripts/flex_auth_signer_custody.py').write_text('changed source') - elif case=='bad-review':c['owner_reviews']['founder']['approved_admission_sha256']='sha256:'+'f'*64 - elif case=='bad-route':c['routing']['lane_id']='openbao-structure-survey';approve(c) - elif case=='expired':c['expires_at']=(datetime.now(timezone.utc)-timedelta(seconds=1)).isoformat();approve(c) - elif case=='wrong-entity':io.entity='other' - elif case=='wrong-backend':io.backend='other' - elif case=='wrong-cluster':io.cluster='other' - elif case=='root':io.root=True - elif case=='pod':io.pods=[{'spec':{'serviceAccountName':m.NAME}}] - elif case=='terminating-pod':io.pods=[{'metadata':{'deletionTimestamp':'now'},'spec':{'volumes':[{'secret':{'secretName':m.SECRET}}]}}] - elif case=='hpa':io.hpas=[{'spec':{'scaleTargetRef':{'name':m.NAME}}}] - elif case=='replicas':io.dep['spec']['replicas']=1 - elif case=='image-drift':io.dep['spec']['template']['spec']['containers'][0]['image']='unreviewed' - elif case=='role-drift':io.role_drift=True - elif case=='pod-after-metadata':io.pod_on_recheck=True - with pytest.raises(Refused):m.execute(c,directory,io=io) - assert not any(x[0]=='http' and x[1]=='POST' and x[2]==m.PATH for x in io.calls) - assert call('GET',m.PATH)[0]==404 - - -@pytest.mark.parametrize('case',['damaged-receipt','typed-damaged-receipt','changed-version','read-denied','no-fresh-review']) -def test_recovery_refuses_without_new_effect(native,case): - c,io,directory,call,approve,source=native;io.lost=True - with pytest.raises(Refused):m.execute(c,directory,io=io) - old=directory/(c['operation_id']+'.json');recovery=deepcopy(c);recovery.update(action='recover',operation_id='recovery',original_receipt_sha256=m.digest(old.read_bytes()));approve(recovery) - if case=='damaged-receipt':old.write_text('{}') - elif case=='typed-damaged-receipt': - damaged=json.loads(old.read_text());damaged['expected_version']=False;old.write_text(json.dumps(damaged));recovery['original_receipt_sha256']=m.digest(old.read_bytes());approve(recovery) - elif case=='changed-version': - doc=call('GET',m.PATH)[1]['data']['data'];assert call('POST',m.PATH,{'options':{'cas':1},'data':doc})[0]==200 - elif case=='read-denied':io.denied=True - elif case=='no-fresh-review':recovery['owner_reviews']['founder']['approved_admission_sha256']='sha256:'+'f'*64 - io.calls.clear();io.lost=False - with pytest.raises(Refused):m.execute(recovery,directory,io=io,original_receipt=old) - assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls) - - -@pytest.mark.parametrize('bad_args',[False,True]) -def test_main_missing_approval_silent_and_no_io(tmp_path,capsys,bad_args): - if bad_args: - assert m.main(['--unrecognized'])==1 - output=capsys.readouterr();assert output.out==output.err=='' - return - assert m.main(['--admission',str(tmp_path/'missing'),'--admission-sha256','sha256:'+'0'*64,'--receipt-directory',str(tmp_path)])==1 - output=capsys.readouterr();assert output.out==output.err=='' - - -def test_native_command_byte_and_elapsed_bounds_no_output(capsys): - io=m.NativeIO() - with pytest.raises(Refused):io.captured([sys.executable,'-c',"import sys;sys.stdout.write('x'*1048577)"]) - import time - started=time.monotonic() - with pytest.raises(Refused):io.captured([sys.executable,'-c',"import time;time.sleep(10)"]) - assert time.monotonic()-started<3 - output=capsys.readouterr();assert output.out==output.err=='' - - -def test_exclusive_native_window_refuses_second_writer(native): - import fcntl - c,io,directory,call,approve,source=native - fd=os.open(directory/'.signer-window.lock',os.O_RDWR|os.O_CREAT,0o600) - try: - fcntl.flock(fd,fcntl.LOCK_EX|fcntl.LOCK_NB) - with pytest.raises(BlockingIOError):m.execute(c,directory,io=io) - assert io.calls==[] - finally:os.close(fd) - - -def test_native_fixed_http_transport_against_disposable_backend(bao_dev,monkeypatch,capsys): - origin,token,call,process=bao_dev - assert call('POST','sys/mounts/platform',{'type':'kv','options':{'version':'2'}})[0] in {200,204} - factory=m.http.client.HTTPConnection;port=int(origin.rsplit(':',1)[1]);destinations=[] - def fixture_socket(host,requested_port,*args,**kwargs): - destinations.append((host,requested_port)) - return factory(host,port if requested_port==18200 else requested_port,*args,**kwargs) - monkeypatch.setattr(m.http.client,'HTTPConnection',fixture_socket) - io=m.NativeIO();io._token=token - assert io.http('GET',m.META)==(404,{'errors':[]}) - assert io.http('POST',m.META,{'cas_required':True,'max_versions':2,'delete_version_after':'0s'})[0] in {200,204} - m.exact_metadata(*io.http('GET',m.META),created=False) - assert all(host=='127.0.0.1' and selected_port==18200 for host,selected_port in destinations) - with pytest.raises(Refused):io.http('GET','other/path') - output=capsys.readouterr();assert output.out==output.err=='' - - -def metadata_phase_crash(native,*,after): - c,io,directory,call,approve,source=native - child=os.fork() - if child==0: - original=io.http - def die(method,path,body=None): - if method=='POST' and path==m.META: - if after:original(method,path,body) - os._exit(73) - return original(method,path,body) - io.http=die - try:m.execute(c,directory,io=io) - except BaseException:os._exit(74) - os._exit(75) - _,status=os.waitpid(child,0);assert os.waitstatus_to_exitcode(status)==73 - old=directory/(c['operation_id']+'.json');raw=old.read_bytes();receipt=json.loads(raw) - assert receipt['phase']=='metadata-setup' and receipt['status']=='write-intent-unknown' - assert receipt['public_key'] is None and receipt['kv_version'] is None - recovery=deepcopy(c);recovery.update(action='recover',operation_id='metadata-recovery',original_receipt_sha256=m.digest(raw));approve(recovery) - return old,raw,recovery - - -@pytest.mark.parametrize('after',[False,True]) -def test_real_metadata_process_loss_read_only_phase_recovery(native,after): - c,io,directory,call,approve,source=native - old,raw,recovery=metadata_phase_crash(native,after=after) - io.calls.clear();result=m.execute(recovery,directory,io=io,original_receipt=old) - assert result['status']==('metadata-only-confirmed' if after else 'current-custody-absent') - assert result['public_key'] is result['public_fingerprint'] is result['kv_version'] is None - assert result['key_custody_verified'] is False and result['historical_effect']=='unresolved' - assert 'fresh independently reviewed exact action required' in result['next_action'] - assert old.read_bytes()==raw and result['original_unknown_preserved'] - assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls) - assert call('GET',m.PATH+'?version=1')==(404,{'errors':[]}) - - -@pytest.mark.parametrize('case',['metadata-denied','metadata-misleading404','metadata-misleading200','metadata-malformed','metadata-timeout','data-denied','unexpected-version']) -def test_metadata_recovery_ambiguous_or_denied_never_classifies_or_writes(native,case): - c,io,directory,call,approve,source=native - old,raw,recovery=metadata_phase_crash(native,after=True) - if case=='unexpected-version': - assert call('POST',m.PATH,{'options':{'cas':0},'data':{'ED25519_PRIVATE_KEY':'synthetic-unadmitted','ED25519_PUBLIC_KEY':'synthetic-unadmitted','kid':c['kid']}})[0]==200 - original=io.http - def faulty(method,path,body=None): - if path==m.META: - if case=='metadata-denied':return 403,{'errors':['denied']} - if case=='metadata-misleading404':return 404,{'errors':['permission denied']} - if case=='metadata-misleading200': - result=original(method,path,body);result[1]['errors']=['denied'];return result - if case=='metadata-malformed':return 200,{'data':{'current_version':False}} - if case=='metadata-timeout':raise OSError('synthetic private backend detail') - if path==m.PATH+'?version=1' and case=='data-denied':return 404,{'errors':['denied']} - return original(method,path,body) - io.http=faulty;io.calls.clear() - with pytest.raises((Refused,OSError)):m.execute(recovery,directory,io=io,original_receipt=old) - evidence=json.loads((directory/(recovery['operation_id']+'.json')).read_bytes()) - assert evidence['status']=='recovery-read-intent-unconfirmed' and evidence['key_custody_verified'] is False - assert old.read_bytes()==raw - assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls) - if case=='unexpected-version':assert not any(x[0]=='http' and x[2]==m.PATH+'?version=1' for x in io.calls) - - -def test_stale_current_primitive_lineage_refuses_before_native_io(native): - c,io,directory,call,approve,source=native - plan=json.loads((source/m.PLAN).read_bytes());plan['bootstrap']['source_primitive_sha256']='sha256:'+'8'*64 - (source/m.PLAN).write_text(json.dumps(plan));c['bindings']['proposal_sha256']=m.digest((source/m.PLAN).read_bytes());approve(c) - with pytest.raises(Refused,match='primitive lineage'):m.execute(c,directory,io=io) - assert io.calls==[] - - -def test_recovery_explicitly_binds_historical_execution_and_current_source(native): - c,io,directory,call,approve,source=native - old,raw,recovery=metadata_phase_crash(native,after=True) - # Model a separately retained older executed bundle/proposal. It must be - # explicitly selected in fresh recovery review, never treated as current. - original=json.loads(raw);original['bindings']['executed_revision_sha256']='sha256:'+'6'*64 - original['bindings']['proposal_sha256']='sha256:'+'7'*64 - old.write_text(json.dumps(original));recovery['original_receipt_sha256']=m.digest(old.read_bytes()) - recovery['original_execution']={key:deepcopy(original[key]) for key in ['bindings','writer_sha256','source_ccr_sha256']};approve(recovery) - result=m.execute(recovery,directory,io=io,original_receipt=old) - assert result['status']=='metadata-only-confirmed' - assert result['original_execution']['bindings']['executed_revision_sha256']!=recovery['bindings']['executed_revision_sha256'] - assert json.loads(old.read_text())==original - - -def test_generated_but_unstored_seed_phase_recovers_without_public_admission(native): - c,io,directory,call,approve,source=native - io.pod_on_recheck=True - with pytest.raises(Refused):m.execute(c,directory,io=io) - old=directory/(c['operation_id']+'.json');raw=old.read_bytes();receipt=json.loads(raw) - assert receipt['phase']=='seed-cas' and receipt['public_key'] is not None - assert call('GET',m.PATH+'?version=1')==(404,{'errors':[]}) - io.pod_on_recheck=False;io.pods=[];io.calls.clear() - recovery=deepcopy(c);recovery.update(action='recover',operation_id='unstored-seed-recovery',original_receipt_sha256=m.digest(raw));approve(recovery) - result=m.execute(recovery,directory,io=io,original_receipt=old) - assert result['status']=='metadata-only-confirmed' and result['key_custody_verified'] is False - assert result['public_key'] is None and result['public_fingerprint'] is None - assert old.read_bytes()==raw - assert not any(x[0]=='http' and x[1]=='POST' for x in io.calls) - - -def test_exact_runtime_storage_owner_slot_replaces_conflated_role(native): - c,io,directory,call,approve,source=native - assert m.REVIEWS=={'founder','railiance-platform-owner','access-engine-owner','secrets-engine-owner','cluster-runtime-storage-owner'} - assert m.validate_admission(c)>5 - old=deepcopy(c) - old['owner_reviews']['cluster-gitops-owner']=old['owner_reviews'].pop('cluster-runtime-storage-owner') - with pytest.raises(Refused,match='distinct owner reviews'): - m.validate_admission(old) - missing=deepcopy(c);missing['owner_reviews'].pop('cluster-runtime-storage-owner') - with pytest.raises(Refused,match='distinct owner reviews'): - m.validate_admission(missing) - extra=deepcopy(c);extra['owner_reviews']['railiance-enablement-owner']=next(iter(extra['owner_reviews'].values())) - with pytest.raises(Refused,match='distinct owner reviews'): - m.validate_admission(extra) - assert io.calls==[] - - -def test_owner_reviews_survive_fresh_expiry_but_not_scope_change(native): - c,io,directory,call,approve,source=native - reviewed=m.review_scope_digest(c) - c['expires_at']=(datetime.now(timezone.utc)+timedelta(seconds=90)).isoformat() - assert m.review_scope_digest(c)==reviewed - m.validate_admission(c) - changed=deepcopy(c);changed['kid']='other-native-kid' - assert m.review_scope_digest(changed)!=reviewed - with pytest.raises(m.Refused):m.validate_admission(changed) diff --git a/tests/test_flex_auth_signer_native_preparation.py b/tests/test_flex_auth_signer_native_preparation.py deleted file mode 100644 index bd1b357..0000000 --- a/tests/test_flex_auth_signer_native_preparation.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Real CCR schema/applier contract, no credentials or backend invocation.""" -from copy import deepcopy -import importlib.util -import json -from pathlib import Path -import yaml -ROOT=Path(__file__).resolve().parents[1] -spec=importlib.util.spec_from_file_location('signer_ccr',ROOT/'scripts/credential-change.py') -ccr_module=importlib.util.module_from_spec(spec);spec.loader.exec_module(ccr_module) -CCR=ROOT/'credential-change-requests/CCR-2026-0050-flex-auth-envelope-signing.yaml' - - -def test_real_schema_and_apply_refusal(): - ccr,errors,warnings=ccr_module.validate_ccr(CCR) - assert errors==[] - assert warnings==['OIDC/Kubernetes bound claim is not confirmed; apply is blocked'] - blockers=ccr_module.applier_readiness_blockers(ccr) - assert len(blockers)==2 - assert any('status, got proposed' in x for x in blockers) - assert any('confirmed OpenBao auth binding' in x for x in blockers) - assert ccr['review']['comments']==[] and ccr['access_frontdoor']['resolvable'] is False - # Pure synthetic candidate only: ensure admitted-pattern scope has no latent - # schema/applier role/field failures once the real owners eventually approve. - admitted=deepcopy(ccr);admitted['status']='approved';admitted['openbao']['auth']['bound_claims_confirmed']=True - assert ccr_module.applier_readiness_blockers(admitted)==[] - - -def test_generated_read_scope_and_projection_join(): - ccr,_,_=ccr_module.validate_ccr(CCR) - assert (ROOT/ccr['openbao']['policy_file']).read_text()==ccr_module.generated_policy_hcl(ccr) - hcl=ccr_module.generated_policy_hcl(ccr) - assert 'metadata/' not in hcl and 'list' not in hcl and 'create' not in hcl - assert ccr['openbao']['fields']==['ED25519_PRIVATE_KEY'] - docs=list(yaml.safe_load_all((ROOT/'docs/credential-lane-designs/flex-auth-envelope-signing-native-projection.yaml.proposed').read_text())) - store=next(x for x in docs if x['kind']=='SecretStore') - assert store['spec']['provider']['vault']['auth']['kubernetes']['role']==ccr['openbao']['auth']['role'] - external=next(x for x in docs if x['kind']=='ExternalSecret') - assert external['spec']['data']==[{'secretKey':'seed','remoteRef':{'key':'workloads/flex-auth/envelope-signing','property':'ED25519_PRIVATE_KEY'}}] - - -def test_native_scope_separates_read_grant_and_unadmitted_execution(): - plan=json.loads((ROOT/'docs/credential-lane-designs/flex-auth-envelope-signing-native-plan.json').read_text()) - assert plan['authority_granted'] is False and plan['status']=='prepared-unapproved' - assert plan['bootstrap']['expected_version']==0 - assert all(x['disposition'].startswith('pending ') for x in plan['reviews_required']) - assert 'implementation/independent tests still required' in plan['protected_adapter']['state'] - assert plan['receipts']['maximum_bytes']==65536 - assert all('platform/' in x['path'] for x in plan['bootstrap']['allowed_native_methods']) - assert 'zero flex-auth PVCs' in plan['requested_scope']['new_resources'] diff --git a/tests/test_fluid_telegram_custody_move.py b/tests/test_fluid_telegram_custody_move.py deleted file mode 100644 index 20f03b1..0000000 --- a/tests/test_fluid_telegram_custody_move.py +++ /dev/null @@ -1,144 +0,0 @@ -"""Offline tests for the fluid-telegram custody move helper against a fake ``bao``. - -Runs the helper as a real subprocess with a fake ``bao`` first on PATH, so the -silence contract (empty stdout and stderr) is tested end to end. -""" -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import textwrap -import unittest - -ROOT = Path(__file__).resolve().parents[1] -SCRIPT = ROOT / 'scripts/fluid_telegram_custody_move.py' -CONFIRM = 'MOVE fluid-telegram hall-of-helix to coulomb' -SRC = 'workloads/infotech/fluid-telegram/hall-of-helix' -DST = 'workloads/coulomb/fluid-telegram/hall-of-helix' -VALUES = { - 'bot-token': {'token': 'bt-' + 'Q1w2' * 10}, - 'operator-app': {'api_id': '1234567', 'api_hash': 'ah-' + 'E3r4' * 8}, - 'operator-session': {'session_b64': 'c2Vzc2lvbg' * 12}, - 'redaction-salt': {'salt': 'sa-' + 'T5y6' * 8}, -} - -FAKE_BAO = textwrap.dedent('''\ - #!/usr/bin/env python3 - import json, os, sys - sf = os.environ['FAKE_BAO_STATE'] - st = json.load(open(sf)) - st['calls'].append(sys.argv[1:]) - def save(): json.dump(st, open(sf, 'w')) - def out(obj): save(); print(json.dumps(obj)); sys.exit(0) - def missing(): save(); sys.stderr.write('No value found at x\\n'); sys.exit(2) - a = sys.argv[1:] - print('Success! noise', file=sys.stderr) - if a[0] == 'read': - p = a[-1] - if p == 'auth/token/lookup-self': - out({'data': st['identity']}) - mount, kind, rest = p.split('/', 2) - e = st['kv'].get(rest) - if not e: missing() - cv = len(e['versions']) - if kind == 'metadata': - out({'data': {'current_version': cv, 'versions': {str(i + 1): {} for i in range(cv)}}}) - out({'data': {'data': e['versions'][-1], 'metadata': {'version': cv}}}) - if a[0] == 'write': - p = a[-2].split('/', 2)[2] - body = json.loads(sys.stdin.read()) - if st.get('fail_write'): save(); sys.exit(2) - e = st['kv'].setdefault(p, {'versions': []}) - if body['options']['cas'] != len(e['versions']): save(); sys.exit(2) - e['versions'].append(body['data']) - out({'data': {'version': len(e['versions'])}}) - if a[0] == 'delete': - st['kv'].pop(a[1].split('/', 2)[2], None) - save(); sys.exit(0) - save(); sys.exit(9) -''') - - -class CustodyMove(unittest.TestCase): - def setUp(self): - self.tmp = Path(tempfile.mkdtemp()) - (self.tmp / 'bin').mkdir() - fake = self.tmp / 'bin/bao' - fake.write_text(FAKE_BAO) - fake.chmod(0o755) - self.home = self.tmp / '.warden-attended-login' / 'session' - self.home.mkdir(parents=True) - kv = {f'{SRC}/{k}': {'versions': [v]} for k, v in VALUES.items()} - kv[f'{SRC}/operator-session']['versions'] = [{'session_b64': 'old'}] * 3 + [VALUES['operator-session']] - self.state = {'calls': [], 'identity': {'policies': ['default', 'platform-admin']}, 'kv': kv} - - def run_helper(self, *extra, confirm=CONFIRM): - (self.tmp / 'state.json').write_text(json.dumps(self.state)) - receipt = self.tmp / f'receipt-{len(list(self.tmp.glob("receipt-*")))}.json' - env = {k: v for k, v in os.environ.items() if k not in ('BAO_TOKEN', 'VAULT_TOKEN')} - env.update(PATH=f'{self.tmp / "bin"}:{env["PATH"]}', HOME=str(self.home), - FAKE_BAO_STATE=str(self.tmp / 'state.json')) - proc = subprocess.run([sys.executable, str(SCRIPT), '--confirm', confirm, - '--receipt', str(receipt), *extra], - env=env, capture_output=True, text=True) - self.assertEqual((proc.stdout, proc.stderr), ('', '')) - self.state = json.loads((self.tmp / 'state.json').read_text()) - text = receipt.read_text() - for value in VALUES.values(): - for secret in value.values(): - self.assertNotIn(secret, text) - return proc.returncode, json.loads(text) - - def test_moves_all_four_then_deletes_sources(self): - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (0, 'moved')) - for entry, value in VALUES.items(): - self.assertEqual(self.state['kv'][f'{DST}/{entry}']['versions'], [value]) - self.assertNotIn(f'{SRC}/{entry}', self.state['kv']) - self.assertTrue(receipt['entries'][entry]['source_deleted']) - self.assertEqual(receipt['entries']['operator-session']['source_version'], 4) - self.assertFalse(receipt['credential_values_emitted']) - - def test_rerun_after_move_is_already_moved(self): - self.run_helper() - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (0, 'already_moved')) - - def test_dry_run_reads_no_values(self): - code, receipt = self.run_helper('--dry-run') - self.assertEqual((code, receipt['outcome']), (0, 'dry_run_would_move')) - self.assertFalse(any(c[0] == 'read' and '/data/' in c[-1] for c in self.state['calls'])) - self.assertFalse(any(c[0] in ('write', 'delete') for c in self.state['calls'])) - - def test_keep_source_copies_without_deleting(self): - code, receipt = self.run_helper('--keep-source') - self.assertEqual((code, receipt['outcome']), (0, 'copied_source_kept')) - self.assertIn(f'{SRC}/bot-token', self.state['kv']) - self.assertIn(f'{DST}/bot-token', self.state['kv']) - - def test_differing_target_refuses_and_deletes_nothing(self): - self.state['kv'][f'{DST}/redaction-salt'] = {'versions': [{'salt': 'other'}]} - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (3, 'target_differs')) - self.assertFalse(any(c[0] == 'delete' for c in self.state['calls'])) - self.assertEqual(self.state['kv'][f'{DST}/redaction-salt']['versions'], [{'salt': 'other'}]) - - def test_write_failure_deletes_nothing(self): - self.state['fail_write'] = True - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (4, 'write_failed')) - self.assertFalse(any(c[0] == 'delete' for c in self.state['calls'])) - - def test_refuses_without_confirmation_identity_or_envelope(self): - code, receipt = self.run_helper(confirm='MOVE') - self.assertEqual((code, receipt['outcome']), (2, 'exact_confirmation_required')) - self.state['identity'] = {'policies': ['root', 'platform-admin']} - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (2, 'attended_platform_admin_required')) - self.assertFalse(any(c[0] in ('write', 'delete') for c in self.state['calls'])) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_issue_core_credential_executor.py b/tests/test_issue_core_credential_executor.py deleted file mode 100644 index f939fe5..0000000 --- a/tests/test_issue_core_credential_executor.py +++ /dev/null @@ -1,92 +0,0 @@ -import importlib.util -import json -import os -from pathlib import Path -import subprocess -import tempfile -import unittest -from unittest.mock import patch -spec=importlib.util.spec_from_file_location('issuer',Path(__file__).resolve().parents[1]/'scripts/provision_argocd_issue_core_repository.py') -issuer=importlib.util.module_from_spec(spec);spec.loader.exec_module(issuer) - -class IssuerTests(unittest.TestCase): - def contract(self):return {**issuer.FIXED,'owner_review_ref':'fixed-review','ssh_egress_existing_proof_ref':'fixed-egress','runtime_identity_ref':'attended-platform','rollback_ref':'fixed-rollback','ssh_known_hosts_sha256':'a'*64,'expires_at':'2099-01-01T00:00:00+00:00'} - def test_existing_d2_covers_exact_scope_but_broadening_refused(self): - issuer.validate_contract(self.contract()) - for key,value in [('repository','coulomb/*'),('read_only',False),('scope_approval_ref','self-approved'),('destination','argocd/broad-creds'),('new_network_scope',True)]: - data=self.contract();data[key]=value - with self.assertRaises(issuer.Refused):issuer.validate_contract(data) - def test_wrong_estate_prevents_credentials_and_grants(self): - calls=[] - def runner(args,*a,**kw): - calls.append(args);return json.dumps({'items':[{'status':{'addresses':[{'address':'192.0.2.1'}]}}]}) - with patch.dict(os.environ,{'API_TOKEN':'SYNTHETIC_TEST_ONLY'}),patch.object(issuer,'run',runner),patch.object(issuer,'api') as api: - with self.assertRaises(issuer.Refused):issuer.execute(self.contract(),Path('/tmp/unused-fixture-journal')) - api.assert_not_called() - self.assertEqual(len(calls),1) - def test_provider_cannot_redirect_or_use_other_repo(self): - with self.assertRaises(issuer.Refused):issuer.api('synthetic','GET','/repos/coulomb/other/keys') - with self.assertRaises(issuer.Refused):issuer.NoRedirect().redirect_request(None,None,None,None,None,None) - def test_secret_has_exact_scope_no_apply_annotation(self): - body=issuer.secret_payload('SYNTHETIC_PRIVATE_CANARY') - self.assertEqual(body['metadata']['namespace'],'argocd');self.assertEqual(body['metadata']['name'],issuer.SECRET) - self.assertNotIn('kubectl.kubernetes.io/last-applied-configuration',body['metadata']['annotations']) - self.assertNotIn('password',body['stringData']);self.assertEqual(body['stringData']['url'],issuer.URL) - def test_silent_main_does_not_echo_invalid_contract_or_raw_child_errors(self): - with tempfile.TemporaryDirectory() as tmp: - p=Path(tmp)/'contract.json';p.write_text('SECRET_CANARY') - result=subprocess.run(['python3',str(Path(__file__).resolve().parents[1]/'scripts/provision_argocd_issue_core_repository.py'),'--contract',str(p),'--contract-sha256','a'*64,'--journal',str(Path(tmp)/'journal.json')],capture_output=True,text=True) - self.assertEqual(result.returncode,1);self.assertEqual(result.stdout,'');self.assertEqual(result.stderr,'');self.assertEqual(json.loads((Path(tmp)/'journal.json').read_text())['failure'],'contract_changed') - def test_synthetic_end_to_end_uses_one_unwrap_and_exact_destination(self): - commands=[];public='ssh-ed25519 SYNTHETIC_PUBLIC fixture';private='SYNTHETIC_PRIVATE_CANARY';known='[forgejo.coulomb.social]:30022 ssh-ed25519 SYNTHETIC_HOST\n' - contract=self.contract();contract['ssh_known_hosts_sha256']=__import__('hashlib').sha256(known.encode()).hexdigest() - with tempfile.TemporaryDirectory() as tmp: - journal=Path(tmp)/'journal.json';keydir=Path(tmp)/'keys';keydir.mkdir() - class FakeTmp: - def __enter__(self):return str(keydir) - def __exit__(self,*args):return False - def runner(argv,body=None,env=None): - commands.append((argv,body,env));text=' '.join(argv) - if 'get nodes' in text:return json.dumps({'items':[{'status':{'addresses':[{'address':'92.205.62.239'}]}}]}) - if 'get configmap' in text:return json.dumps({'data':{'ssh_known_hosts':known}}) - if 'get appproject' in text:return json.dumps({'spec':{'sourceRepos':[issuer.URL]}}) - if argv[:3]==['bao','token','capabilities']:return 'create, read, update' - if argv[:3]==['bao','audit','list']:return json.dumps({'file/':{'type':'file'}}) - if 'get secret' in text:return '' if '--ignore-not-found' in text else 'secret/'+issuer.SECRET - if argv[0]=='ssh-keygen': - path=Path(argv[-1]);path.write_text(private);path.chmod(0o600);Path(str(path)+'.pub').write_text(public);return '' - if argv[:3]==['bao','kv','put']:return json.dumps({'request_id':'write-fixture','data':{'version':1}}) - if argv[:3]==['bao','kv','get']:return json.dumps({'wrap_info':{'ttl':60,'token':'SYNTHETIC_WRAP','accessor':'fixture-accessor','creation_path':'platform/data/operators/argocd/repositories/rapp-issue-core'}}) - if argv[:2]==['bao','unwrap']: - self.assertEqual(env['BAO_TOKEN'],'SYNTHETIC_WRAP');return json.dumps({'request_id':'unwrap-fixture','data':{'data':{'SSH_PRIVATE_KEY':private,'PROVIDER_KEY_ID':'7'}}}) - return 'secret/'+issuer.SECRET - def capture(argv,body=None,env=None): - commands.append((argv,body,env)) - if argv[:2]==['bao','list']:return subprocess.CompletedProcess(argv,2,'{}','') - return subprocess.CompletedProcess(argv,2,'','Code: 400. Errors:\n\n* wrapping token is not valid or does not exist\n') - def provider(token,method,path,payload=None,expected=(200,)): - if method=='POST': - self.assertEqual(payload,{'title':issuer.TITLE,'key':public,'read_only':True});return {'id':7,'key':public,'read_only':True} - return [] if '/keys?' in path else {'full_name':issuer.REPO,'private':True} - with patch.dict(os.environ,{'API_TOKEN':'SYNTHETIC_ADMIN'}),patch.object(issuer,'run',runner),patch.object(issuer,'captured',capture),patch.object(issuer,'api',provider),patch.object(issuer.tempfile,'TemporaryDirectory',lambda **kw:FakeTmp()):issuer.execute(contract,journal) - receipt=json.loads(journal.read_text());self.assertEqual(receipt['state'],'delivered_not_adopted');self.assertNotIn(private,journal.read_text());self.assertNotIn('SYNTHETIC_WRAP',journal.read_text()) - self.assertEqual(sum(argv[:2]==['bao','unwrap'] for argv,body,env in commands),2);self.assertTrue(receipt['consumed_wrap_replay_denied_400']);self.assertEqual(receipt['wrap_creation_path'],'platform/data/operators/argocd/repositories/rapp-issue-core') - for argv,body,env in commands: - self.assertNotIn(private,' '.join(argv));self.assertNotIn('SYNTHETIC_WRAP',' '.join(argv));self.assertNotIn(' apply ',' '.join(argv));self.assertNotIn(' patch ',' '.join(argv)) - - def test_empty_metadata_list_404_is_accepted_other_errors_refused(self): - with patch.object(issuer,'captured',return_value=subprocess.CompletedProcess([],2,'{}','')):self.assertEqual(issuer.metadata_children(),[]) - for value in [subprocess.CompletedProcess([],2,'{}','SECRET_CANARY'),subprocess.CompletedProcess([],1,'','Code: 403'),subprocess.CompletedProcess([],2,'','network unreachable')]: - with patch.object(issuer,'captured',return_value=value): - with self.assertRaisesRegex(issuer.Refused,'^custody_metadata_unverified$'):issuer.metadata_children() - def test_replay_denial_requires_specific_400_not_transport_or_forbidden(self): - good='Error unwrapping: Error making API request.\nCode: 400. Errors:\n\n* wrapping token is not valid or does not exist\n' - for code,text,expected in [(2,good,True),(2,good.replace('400','403'),False),(2,'dial tcp: connection refused SECRET_CANARY',False),(1,good,False),(2,'Code: 400\n* unrelated failure',False)]: - with patch.object(issuer,'captured',return_value=subprocess.CompletedProcess([],code,'',text)):self.assertEqual(issuer.replay_denied({}),expected) - - def test_raw_command_output_timeout_and_error_suppressed(self): - for result in [subprocess.CompletedProcess(['fixture'],1,'SECRET_CANARY','SECRET_CANARY'),subprocess.TimeoutExpired(['fixture'],60,output='SECRET_CANARY',stderr='SECRET_CANARY')]: - with patch.object(issuer.subprocess,'run',side_effect=result if isinstance(result,Exception) else None,return_value=result): - with self.assertRaisesRegex(issuer.Refused,'^command_unverified$'):issuer.run(['fixture']) - -if __name__=='__main__':unittest.main() diff --git a/tests/test_issuer_binding_lane.py b/tests/test_issuer_binding_lane.py deleted file mode 100644 index e521bc8..0000000 --- a/tests/test_issuer_binding_lane.py +++ /dev/null @@ -1,133 +0,0 @@ -"""RPF-WP-0054-T05: broker-issuer-binding lane (CCR-2026-0043).""" -from __future__ import annotations - -import importlib.util -import json -import os -import stat -import sys -import tempfile -import unittest -from pathlib import Path - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -CCR_PATH = ROOT / "credential-change-requests/CCR-2026-0043-keycape-client-secret-generate-issuer-binding.yaml" -ISSUER = "credential-broker-keycape-client-secret-generate-issuer" - - -def load(name: str, rel: str): - spec = importlib.util.spec_from_file_location(name, ROOT / rel) - module = importlib.util.module_from_spec(spec) - sys.modules[name] = module - spec.loader.exec_module(module) - return module - - -lane = load("issuer_binding_lane", "scripts/issuer_binding_lane.py") -cc = load("credential_change_cli_issuer", "scripts/credential-change.py") - - -def ccr() -> dict: - return yaml.safe_load(CCR_PATH.read_text()) - - -FAKE_BAO = r'''#!/usr/bin/env python3 -import json, os, sys -path = os.environ["FAKE_BAO_STATE"] -state = json.load(open(path)) -args = [a for a in sys.argv[1:] if a != "-format=json"] -state.setdefault("calls", []).append(args) -if args[:2] == ["auth", "list"]: - print(json.dumps({k: {"type": v} for k, v in state["mounts"].items()})) -elif args[:2] == ["policy", "read"]: - if args[2] not in state["policies"]: - json.dump(state, open(path, "w")); sys.exit(2) - print(state["policies"][args[2]]) -elif args[0] == "write": - state["roles"][args[1]] = json.load(open(args[2][1:])) -elif args[0] == "read": - print(json.dumps({"data": state["roles"][args[1]]})) -json.dump(state, open(path, "w")) -''' - - -class IssuerBindingLaneTests(unittest.TestCase): - def test_ccr_validates_and_is_ready_to_reapply(self): - _ccr, errors, _w = cc.validate_ccr(CCR_PATH) - self.assertEqual(errors, []) - self.assertEqual(cc.applier_readiness_blockers(_ccr), []) - - def test_unconfirmed_binding_blocks_the_applier(self): - c = ccr() - c["openbao"]["auth"]["bound_claims_confirmed"] = False - self.assertIn("applier requires confirmed OpenBao auth binding", cc.applier_readiness_blockers(c)) - - def test_role_carries_only_the_issuer_policy(self): - role = json.loads((ROOT / ccr()["openbao"]["auth"]["role_file"]).read_text()) - self.assertEqual(role["token_policies"], [ISSUER]) - self.assertEqual(role["bound_claims"], {"groups": ["net-kingdom-admins"], "/assurance/level": "aal2"}) - self.assertTrue(role["token_no_default_policy"]) - - def test_validator_rejects_widening(self): - good = json.loads((ROOT / ccr()["openbao"]["auth"]["role_file"]).read_text()) - cases = [ - ("other policy", {"token_policies": [ISSUER, "platform-admin"]}), - ("no aal2", {"bound_claims": {"groups": ["net-kingdom-admins"]}}), - ("public callback", {"allowed_redirect_uris": ["https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback"]}), - ("long ttl", {"token_ttl": "1h"}), - # Probe 2026-10-10: a limited-use token cannot create the grant token. - ("limited uses", {"token_num_uses": 8}), - ] - for label, change in cases: - with self.subTest(label): - with tempfile.TemporaryDirectory(dir=ROOT / "openbao/auth") as tmp: - role_file = Path(tmp) / "role.json" - role_file.write_text(json.dumps({**good, **change})) - c = ccr() - c["openbao"]["auth"]["role_file"] = str(role_file.relative_to(ROOT)) - self.assertNotEqual(lane.validate(c, ROOT), [], label) - c = ccr() - c["openbao"]["issuer_policy"] = "platform-admin" - self.assertNotEqual(lane.validate(c, ROOT), []) - - def _apply(self, policies, mounts=None): - tmp = Path(tempfile.mkdtemp()) - state = {"mounts": mounts if mounts is not None else {"netkingdom/": "oidc"}, - "policies": policies, "roles": {}} - (tmp / "state.json").write_text(json.dumps(state)) - bao = tmp / "bao" - bao.write_text(FAKE_BAO) - bao.chmod(bao.stat().st_mode | stat.S_IEXEC) - os.environ["FAKE_BAO_STATE"] = str(tmp / "state.json") - error = None - try: - lane.run_apply(ccr(), ROOT, str(bao), quiet=True) - except lane.BaoError as exc: - error = exc - return error, json.loads((tmp / "state.json").read_text()) - - def live_issuer(self): - return {ISSUER: (ROOT / f"openbao/policies/{ISSUER}.hcl").read_text()} - - def test_apply_writes_role_and_reads_back(self): - error, state = self._apply(self.live_issuer()) - self.assertIsNone(error) - self.assertIn("auth/netkingdom/role/credential-broker-keycape-client-secret-generate", state["roles"]) - - def test_apply_refuses_missing_or_drifted_issuer_policy_before_writing(self): - for policies in ({}, {ISSUER: 'path "platform/*" { capabilities = ["read"] }'}): - with self.subTest(policies=list(policies)): - error, state = self._apply(policies) - self.assertIsNotNone(error) - self.assertEqual(state["roles"], {}) - - def test_apply_refuses_non_oidc_mount(self): - error, state = self._apply(self.live_issuer(), {"netkingdom/": "jwt"}) - self.assertIsNotNone(error) - self.assertEqual(state["roles"], {}) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_keycape_client_secret_generate.py b/tests/test_keycape_client_secret_generate.py deleted file mode 100644 index bd34ffd..0000000 --- a/tests/test_keycape_client_secret_generate.py +++ /dev/null @@ -1,302 +0,0 @@ -"""Offline tests for credential.py generate (RPF-WP-0054-T03) against a fake ``bao``. - -Runs ``credential.py generate`` as a real subprocess, so the silence contract and -the delegation from credential.py are tested end to end. The fake records which -token each call used: KV calls must use the minted grant token, never the -caller's session. -""" -import hashlib -import importlib.util -import shutil -from unittest import mock - -import yaml -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import textwrap -import unittest - -ROOT = Path(__file__).resolve().parents[1] -CLI = ROOT / 'scripts/credential.py' -GRANT_TOKEN = 'hvs.grant-' + 'G7h8' * 6 - -FAKE_BAO = textwrap.dedent(f'''\ - #!/usr/bin/env python3 - import json, os, sys - sf = os.environ['FAKE_BAO_STATE'] - st = json.load(open(sf)) - a = sys.argv[1:] - tok = os.environ.get('BAO_TOKEN', 'caller-session') - st['calls'].append({{'args': a, 'token': tok}}) - def save(): json.dump(st, open(sf, 'w')) - def out(obj): save(); print(json.dumps(obj)); sys.exit(0) - def missing(): save(); sys.stderr.write('No value found at x\\n'); sys.exit(2) - if a[:2] == ['token', 'lookup']: out({{'data': {{'policies': st['caller_policies']}}}}) - if a[:2] == ['token', 'create']: - if st.get('fail_create'): save(); sys.exit(2) - out({{'auth': {{'client_token': '{GRANT_TOKEN}', 'accessor': 'acc'}}}}) - if a[:2] == ['token', 'revoke']: st['revoked'] = tok; save(); sys.exit(0) - if a[0] == 'read': - rest = a[-1].split('/', 2)[2] - if rest not in st['kv']: missing() - n = len(st['kv'][rest]) - out({{'data': {{'current_version': n, 'versions': {{str(i + 1): {{}} for i in range(n)}}}}}}) - if a[0] == 'write': - rest = a[-2].split('/', 2)[2] - body = json.loads(sys.stdin.read()) - st['written'] = body - if st.get('race'): st['kv'][rest] = [{{'x': 1}}]; save(); sys.exit(2) - if st.get('fail_write'): save(); sys.exit(2) - e = st['kv'].setdefault(rest, []) - if body['options']['cas'] != len(e): save(); sys.exit(2) - e.append(body['data']) - out({{'data': {{'version': len(e)}}}}) - save(); sys.exit(9) -''') - -# Exact accepted Forgejo @1.1 shape; materialized only in this fake test. -CASE = '# First KnownSecurityCase of oidc-browser-relying-party 1.1 (NK-WP-0048-T06).\n# Parameters are the request the founder accepted on 2026-10-08\n# (key-cape docs/forgejo-oidc-client-request.yaml, KEY-WP-0038-T01); that\n# acceptance carries over as this case\'s acceptance. One difference: custody is\n# derived by the type (platform/workloads/keycape/forgejo), not the request\'s\n# proposed platform/workloads/forgejo/netkingdom-oidc-client. The founder\n# acknowledged that derivation with the type (memo r4, 2026-10-09).\n# Re-pinned from 1.0 to 1.1 on 2026-10-10 on the founder\'s go, before any\n# apply, so no 1.0 run overlaps (minor change: consumer-enforces-groups).\napiVersion: netkingdom.io/security-case/v0.1\nkind: SecurityCase\nspec:\n id: forgejo-netkingdom-oidc\n type: oidc-browser-relying-party@1.1\n status: materialized\n acceptance:\n at: "2026-10-08"\n record: "key-cape docs/forgejo-oidc-client-request.yaml (status accepted-2026-10-08, KEY-WP-0038-T01); type acknowledged via informed-decision memo nk-wp-0048-t05-oidc-browser-relying-party-r4, hub decision e418263e"\n by:\n - {role: owner, name: "founder (for railiance-apps)"}\n - {role: founder, name: founder}\n owner:\n repo: railiance-apps\n person: founder\n parameters:\n client_id: forgejo\n display_name: Forgejo source forge\n redirect_uris: https://forgejo.coulomb.social/user/oauth2/netkingdom/callback\n allowed_scopes: openid,profile,email,groups\n client_type: confidential\n mfa_required: "true"\n access_groups: forge-admins,forge-coulomb-owners,forge-coulomb-readers\n initial_members: forge-admins=tegwick;forge-coulomb-owners=tegwick\n consuming_service: forgejo\n consumer_owner_repo: railiance-apps\n' - - -class Generate(unittest.TestCase): - def setUp(self): - self.tmp = Path(tempfile.mkdtemp()) - (self.tmp / 'bin').mkdir() - (self.tmp / 'bin/bao').write_text(FAKE_BAO) - (self.tmp / 'bin/bao').chmod(0o755) - self.state = {'calls': [], 'kv': {}, - 'caller_policies': ['default', 'credential-broker-keycape-client-secret-generate-issuer']} - self.write_case(CASE) - - def write_case(self, text): - self.case = self.tmp / 'case.yaml' - self.case.write_text(text) - self.ref = 'forgejo-netkingdom-oidc@sha256:' + hashlib.sha256(text.encode()).hexdigest() - - def run_cli(self, *extra, client_id='forgejo', ref=None, env_extra=None): - (self.tmp / 'state.json').write_text(json.dumps(self.state)) - receipt = self.tmp / f'r{len(list(self.tmp.glob("r*.json")))}.json' - empty_home = self.tmp / 'empty-home' - empty_home.mkdir(exist_ok=True) - env = {'PATH': os.defpath, 'HOME': str(empty_home)} - env.update(PATH=f'{self.tmp / "bin"}:{env["PATH"]}', FAKE_BAO_STATE=str(self.tmp / 'state.json'), - **(env_extra or {})) - proc = subprocess.run([sys.executable, str(CLI), 'generate', '--grant', 'keycape/client-secret-generate', - '--client-id', client_id, '--case-file', str(self.case), - '--case-ref', ref or self.ref, '--receipt', str(receipt), *extra], - env=env, capture_output=True, text=True) - self.assertEqual((proc.stdout, proc.stderr), ('', '')) - self.state = json.loads((self.tmp / 'state.json').read_text()) - text = receipt.read_text() - self.assertNotIn(GRANT_TOKEN, text) - written = self.state.get('written') - if written: - self.assertNotIn(written['data']['CLIENT_SECRET'], text) - return proc.returncode, json.loads(text) - - def kv_calls(self): - return [c for c in self.state['calls'] if c['args'][0] in ('read', 'write')] - - def test_generates_once_with_grant_token_and_revokes_it(self): - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (0, 'generated')) - self.assertEqual(receipt['kv_version'], 1) - self.assertTrue(receipt['grant_token_revoked']) - self.assertEqual(self.state['revoked'], GRANT_TOKEN) - self.assertTrue(all(c['token'] == GRANT_TOKEN for c in self.kv_calls())) - self.assertFalse(any(c['args'][0] == 'read' and '/data/' in c['args'][-1] for c in self.kv_calls())) - value = self.state['written']['data']['CLIENT_SECRET'] - self.assertEqual(len(value), 64) - self.assertEqual(self.state['written']['options'], {'cas': 0}) - - def test_existing_entry_is_never_touched(self): - self.state['kv']['workloads/keycape/forgejo'] = [{'CLIENT_SECRET': 'old'}] - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (3, 'already_present')) - self.assertNotIn('written', self.state) - self.assertTrue(receipt['grant_token_revoked']) - - def test_lost_cas_race_reports_already_present(self): - self.state['race'] = True - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (3, 'already_present_lost_race')) - - def test_dry_run_writes_nothing(self): - code, receipt = self.run_cli('--dry-run') - self.assertEqual((code, receipt['outcome']), (0, 'dry_run_would_generate')) - self.assertNotIn('written', self.state) - - def test_case_checks_refuse_before_any_bao_call(self): - cases = [ - ('case_ref_mismatch', {'ref': 'forgejo-netkingdom-oidc@sha256:' + '0' * 64}), - ('case_client_id_mismatch', {'client_id': 'gitea'}), - ('client_id_invalid_or_reserved', {'client_id': 'secrets-engine-openbao'}), - ('client_id_invalid_or_reserved', {'client_id': '../forgejo'}), - ('case_ref_malformed', {'ref': 'forgejo'}), - ] - for outcome, kwargs in cases: - with self.subTest(outcome=outcome, **kwargs): - self.state['calls'] = [] - code, receipt = self.run_cli(**kwargs) - self.assertEqual((code, receipt['outcome']), (2, outcome)) - self.assertEqual(self.state['calls'], []) - - def test_unmaterialized_or_public_case_refused(self): - for text, outcome in ((CASE.replace('materialized', 'accepted'), 'case_not_materialized'), - (CASE.replace('confidential', 'public'), 'case_client_not_confidential')): - with self.subTest(outcome=outcome): - self.write_case(text) - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (2, outcome)) - - def test_only_exact_acknowledged_type_version_is_admitted(self): - for type_ref in ('oidc-browser-relying-party@1.0', - 'oidc-browser-relying-party@1.2', - 'oidc-browser-relying-party@1.3', - 'oidc-browser-relying-party@99.0', - 'confidential-service-client@1.1'): - with self.subTest(type_ref=type_ref): - self.write_case(CASE.replace('oidc-browser-relying-party@1.1', type_ref)) - self.state['calls'] = [] - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (2, 'case_type_unacknowledged')) - self.assertEqual(self.state['calls'], []) - - def test_source_acknowledgment_alone_does_not_admit_12(self): - self.write_case(CASE.replace('oidc-browser-relying-party@1.1', - 'oidc-browser-relying-party@1.2') - + ' consumer_namespace: forgejo\n') - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (2, 'case_type_unacknowledged')) - self.assertEqual(self.state['calls'], []) - - def test_caller_environment_cannot_select_type_or_source_root(self): - code, receipt = self.run_cli(env_extra={ - 'NETKINGDOM_ROOT': '/nonexistent', - 'WARDEN_NETKINGDOM_ROOT': '/nonexistent', - 'PYTHONPATH': '/nonexistent', - }) - self.assertEqual((code, receipt['outcome']), (0, 'generated')) - - def test_acknowledged_11_still_requires_its_exact_parameters(self): - self.write_case(CASE + ' unexpected_target: forgejo\n') - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (2, 'case_source_validation_failed')) - self.assertEqual(self.state['calls'], []) - - def test_future_revision_requires_explicit_owner_binding_and_acknowledgment(self): - # Synthetic owner configuration exercises the extension mechanism only. - # This is not the actual proposed 1.3 type, acknowledgment or parameter pin. - spec = importlib.util.spec_from_file_location( - 'future_fixture_generator', ROOT / 'scripts/keycape_client_secret_generate.py') - broker = importlib.util.module_from_spec(spec) - spec.loader.exec_module(broker) - source = self.tmp / 'synthetic-owner-root' - source.mkdir() - relative, digest = broker.ACKNOWLEDGED_TYPES['oidc-browser-relying-party@1.1'] - for rel in (relative, *broker.SOURCE_PINS): - dest = source / rel - dest.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(broker.NETKINGDOM_ROOT / rel, dest) - typ = source / relative - original = typ.read_bytes() - doc = yaml.safe_load(CASE) - doc['spec']['type'] = 'oidc-browser-relying-party@1.3' - typ.write_bytes(original.replace(b'version: "1.1"', b'version: "1.3"')) - # YAML numeric spelling is owner-authored, never an inferred revision. - type_doc = yaml.safe_load(original) - type_doc['spec']['version'] = '1.3' - typ.write_text(yaml.safe_dump(type_doc)) - binding = {doc['spec']['type']: (relative, hashlib.sha256(typ.read_bytes()).hexdigest())} - with mock.patch.object(broker, 'NETKINGDOM_ROOT', source): - with self.assertRaises(broker.Exit): - broker.check_acknowledged_case(doc) # no explicit binding - with mock.patch.dict(broker.ACKNOWLEDGED_TYPES, binding): - with self.assertRaises(broker.Exit): - broker.check_acknowledged_case(doc) # no parameter admission - with mock.patch.dict(broker.PARAMETER_PINS, {'1.3': broker.PARAMETER_PINS['1.1']}): - broker.check_acknowledged_case(doc) - type_doc['spec']['status'] = 'proposed' - typ.write_text(yaml.safe_dump(type_doc)) - proposed = {doc['spec']['type']: (relative, hashlib.sha256(typ.read_bytes()).hexdigest())} - with mock.patch.dict(broker.ACKNOWLEDGED_TYPES, proposed): - with self.assertRaises(broker.Exit) as caught: - broker.check_acknowledged_case(doc) - self.assertEqual(caught.exception.reason, 'case_source_validation_failed') - - def test_wrong_sources_proposed_unknown_versions_and_symlinks_fail_closed(self): - spec = importlib.util.spec_from_file_location( - 'candidate_generator', ROOT / 'scripts/keycape_client_secret_generate.py') - broker = importlib.util.module_from_spec(spec) - spec.loader.exec_module(broker) - source = self.tmp / 'net-kingdom' - source.mkdir() - relative, digest = broker.ACKNOWLEDGED_TYPES['oidc-browser-relying-party@1.1'] - for rel in (relative, *broker.SOURCE_PINS): - dest = source / rel - dest.parent.mkdir(parents=True, exist_ok=True) - shutil.copyfile(broker.NETKINGDOM_ROOT / rel, dest) - doc = yaml.safe_load(CASE) - with mock.patch.object(broker, 'NETKINGDOM_ROOT', source): - broker.check_acknowledged_case(doc) - producer = source / next(iter(broker.SOURCE_PINS)) - raw = producer.read_bytes() - producer.write_bytes(raw + b'\n# drift\n') - with self.assertRaises(broker.Exit) as caught: - broker.check_acknowledged_case(doc) - self.assertEqual(caught.exception.reason, 'case_source_validation_failed') - producer.write_bytes(raw) - typ = source / relative - raw = typ.read_bytes() - typ.write_bytes(raw.replace(b'status: acknowledged', b'status: proposed')) - # Even a hypothetical owner pin update cannot admit proposed status. - with mock.patch.dict(broker.ACKNOWLEDGED_TYPES, { - doc['spec']['type']: (relative, hashlib.sha256(typ.read_bytes()).hexdigest())}): - with self.assertRaises(broker.Exit) as caught: - broker.check_acknowledged_case(doc) - self.assertEqual(caught.exception.reason, 'case_source_validation_failed') - typ.write_bytes(raw + b'\n# source drift\n') - with self.assertRaises(broker.Exit) as caught: - broker.check_acknowledged_case(doc) - self.assertEqual(caught.exception.reason, 'case_source_validation_failed') - typ.unlink() - typ.symlink_to(broker.__file__) - with self.assertRaises(broker.Exit) as caught: - broker.check_acknowledged_case(doc) - self.assertEqual(caught.exception.reason, 'case_source_validation_failed') - for ref in ('oidc-browser-relying-party@1.3', 'oidc-browser-relying-party@99.0'): - doc['spec']['type'] = ref - with self.assertRaises(broker.Exit) as caught: - broker.check_acknowledged_case(doc) - self.assertEqual(caught.exception.reason, 'case_type_unacknowledged') - - def test_exact_accepted_case_still_requires_materialization(self): - self.write_case(CASE.replace('status: materialized', 'status: accepted')) - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (2, 'case_not_materialized')) - self.assertEqual(self.state['calls'], []) - - def test_agents_root_and_unbound_callers_refused(self): - code, receipt = self.run_cli(env_extra={'WARDEN_AGENT_ID': 'codex'}) - self.assertEqual((code, receipt['outcome']), (2, 'agent_callers_refused')) - self.state['caller_policies'] = ['root'] - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (2, 'root_refused')) - self.state['caller_policies'] = ['default'] - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (2, 'caller_lacks_issuer_policy')) - self.assertFalse(any(c['args'][:2] == ['token', 'create'] for c in self.state['calls'])) - - def test_write_failure_is_reported_and_token_revoked(self): - self.state['fail_write'] = True - code, receipt = self.run_cli() - self.assertEqual((code, receipt['outcome']), (4, 'write_failed')) - self.assertTrue(receipt['grant_token_revoked']) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_keycape_issuer_eso.py b/tests/test_keycape_issuer_eso.py deleted file mode 100644 index fd6e75d..0000000 --- a/tests/test_keycape_issuer_eso.py +++ /dev/null @@ -1,129 +0,0 @@ -"""Public metadata fixtures only: no API, credentials, or native calls.""" -import copy -import sys -import unittest -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts")) -import keycape_issuer_eso as contract - - -class FixedIssuerContract(unittest.TestCase): - def observations(self): - resources = contract.documents("forgejo") - for resource in resources: - resource["metadata"].update(uid="public-fixture-uid", resourceVersion="7") - resource["status"] = {"conditions": [{"type": "Ready", "status": "True"}]} - return resources - - def test_independent_forgejo_binding(self): - # Independent owner-source example: CCR0041 role/SA/audience and the - # existing issuer ESO draft's provider/target/relative key convention. - store, external = contract.documents("forgejo") - self.assertEqual(store["kind"], "ClusterSecretStore") - self.assertEqual(store["metadata"], {"name": "openbao-keycape-forgejo-issuer"}) - self.assertEqual(store["spec"], { - "provider": {"vault": { - "server": "http://openbao.openbao.svc:8200", "path": "platform", "version": "v2", - "auth": {"kubernetes": {"mountPath": "kubernetes", - "role": "external-secrets-keycape-forgejo", - "serviceAccountRef": {"name": "external-secrets", "namespace": "external-secrets", - "audiences": ["openbao"]}}}}}, - "conditions": [{"namespaces": ["sso"]}]}) - self.assertEqual(external["metadata"], {"name": "keycape-forgejo-client", "namespace": "sso"}) - self.assertEqual(external["spec"], { - "refreshInterval": "5m", - "secretStoreRef": {"kind": "ClusterSecretStore", "name": "openbao-keycape-forgejo-issuer"}, - "target": {"name": "keycape-forgejo-client", "creationPolicy": "Owner", "deletionPolicy": "Retain"}, - "data": [{"secretKey": "client-secret", "remoteRef": { - "key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]}) - - def test_valid_ready_metadata_allows_extra_controller_metadata(self): - store, external = self.observations() - contract.require_fixed_store(store, "forgejo") - contract.require_ready_delivery(external, "forgejo") - - def test_store_drift_refuses(self): - changes = [ - ("kind", "SecretStore"), - ("metadata.namespace", "sso"), - ("metadata.name", "caller-store"), - ("spec.provider.vault.server", "https://wrong.invalid"), - ("spec.provider.vault.path", "tenant"), - ("spec.provider.vault.auth.kubernetes.role", "external-secrets-keycape-sibling"), - ("spec.provider.vault.auth.kubernetes.serviceAccountRef.audiences", ["wrong"]), - ("spec.provider.vault.auth.kubernetes.serviceAccountRef.namespace", "sso"), - ("spec.conditions", [{"namespaces": ["sso", "other"]}]), - ] - for path, value in changes: - with self.subTest(path=path): - store = self.observations()[0] - mutate(store, path, value) - with self.assertRaises(contract.Refused): - contract.require_fixed_store(store, "forgejo") - - def test_external_drift_refuses(self): - changes = [ - ("metadata.namespace", ""), ("metadata.namespace", None), ("metadata.namespace", "other"), - ("metadata.name", "keycape-sibling-client"), - ("spec.secretStoreRef.kind", "SecretStore"), - ("spec.secretStoreRef.name", "openbao-keycape-forgejo-consumer"), - ("spec.target.name", "other"), ("spec.target.creationPolicy", "Merge"), - ("spec.target.deletionPolicy", "Delete"), - ("spec.data", [{"secretKey": "client-secret", "remoteRef": { - "key": "platform/workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]), - ("spec.data", [{"secretKey": "wrong", "remoteRef": { - "key": "workloads/keycape/forgejo", "property": "CLIENT_SECRET"}}]), - ("spec.data", [{"secretKey": "client-secret", "remoteRef": { - "key": "workloads/keycape/forgejo", "property": "wrong"}}]), - ] - for path, value in changes: - with self.subTest(path=path, value=value): - external = self.observations()[1] - mutate(external, path, value) - with self.assertRaises(contract.Refused): - contract.require_ready_delivery(external, "forgejo") - - def test_missing_or_unready_refuses(self): - for check, index in [(contract.require_fixed_store, 0), (contract.require_ready_delivery, 1)]: - for status in [None, {}, [], "bad", {"conditions": None}, - {"conditions": [{"type": "Ready", "status": "False"}]}]: - with self.subTest(check=check.__name__, status=status): - observed = self.observations()[index] - observed["status"] = status - with self.assertRaises(contract.Refused): - check(observed, "forgejo") - for malformed in [None, [], {}, {"metadata": None}]: - with self.assertRaises(contract.Refused): - check(malformed, "forgejo") - - def test_no_caller_store_or_path_override(self): - for invalid in ["", "f", "../forgejo", "forgejo/sibling", "Forgejo", "forgejo\n", "-forgejo", "f" * 49, None]: - with self.subTest(invalid=invalid), self.assertRaises(contract.Refused): - contract.documents(invalid) - with self.assertRaises(TypeError): - contract.external_secret("forgejo", store_name="caller-store") - - def test_type_admitted_trailing_dash_is_preserved(self): - self.assertEqual(contract.external_secret("forgejo-")["metadata"]["name"], - "keycape-forgejo--client") - self.assertEqual(contract.documents("forgejo-")[0]["metadata"]["name"], - "openbao-keycape-forgejo--issuer") - - def test_delivery_document_is_only_external_and_detached(self): - external = contract.external_secret("forgejo") - self.assertEqual(external["kind"], "ExternalSecret") - external["spec"]["secretStoreRef"]["name"] = "tampered" - self.assertEqual(contract.external_secret("forgejo")["spec"]["secretStoreRef"]["name"], - "openbao-keycape-forgejo-issuer") - - -def mutate(resource, path, value): - parts = path.split(".") - for key in parts[:-1]: - resource = resource[key] - resource[parts[-1]] = copy.deepcopy(value) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/test_keycape_secrets_engine_openbao_custody.py b/tests/test_keycape_secrets_engine_openbao_custody.py index 11e2a6f..b7856c5 100644 --- a/tests/test_keycape_secrets_engine_openbao_custody.py +++ b/tests/test_keycape_secrets_engine_openbao_custody.py @@ -117,17 +117,11 @@ class CustodyChild(unittest.TestCase): self.assertEqual((code, receipt['error']), (1, reason)) self.assertEqual(self.bao.calls, []) - def proposed_copy(self, name): - text = (ROOT / 'credential-change-requests' / name).read_text() - path = Path(self.tmp.name) / name - path.write_text(text.replace('\nstatus: approved\n', '\nstatus: proposed\n', 1)) - return path - - def test_proposed_ccrs_are_refused(self): - with patch.object(child, 'CCR_PATH', self.proposed_copy('CCR-2026-0033.yaml')): + def test_committed_proposed_ccrs_are_refused(self): + with patch.object(child, 'CCR_PATH', ROOT / 'credential-change-requests/CCR-2026-0033.yaml'): code, receipt = self.run_child() self.assertEqual((code, receipt['error']), (1, 'approved_ccr_required')) - with patch.object(child, 'SIBLING_PATH', self.proposed_copy('CCR-2026-0034.yaml')): + with patch.object(child, 'SIBLING_PATH', ROOT / 'credential-change-requests/CCR-2026-0034.yaml'): code, receipt = self.run_child() self.assertEqual((code, receipt['error']), (1, 'approved_ccr_required')) self.assertEqual(self.bao.calls, []) diff --git a/tests/test_kube_route.py b/tests/test_kube_route.py deleted file mode 100644 index da5a4b1..0000000 --- a/tests/test_kube_route.py +++ /dev/null @@ -1,54 +0,0 @@ -import importlib.util -from pathlib import Path -import shlex -import unittest -from unittest.mock import patch - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location('kube_route', ROOT / 'scripts/kube_route.py') -route = importlib.util.module_from_spec(spec) -spec.loader.exec_module(route) - - -class KubeRouteTest(unittest.TestCase): - def test_default_is_agent_without_escalation(self): - argv = route.ssh_argv(['kubectl', 'get', 'nodes'], environ={}) - self.assertEqual(argv[:-1], route.SSH) - self.assertEqual(shlex.split(argv[-1]), ['kubectl', 'get', 'nodes']) - self.assertNotIn('sudo', argv[-1]) - - def test_founder_sudo_never_prompts_and_never_allocates_tty(self): - argv = route.ssh_argv(['python3', '-'], environ={'KUBECTL_ROUTE': 'founder-sudo'}) - self.assertEqual(shlex.split(argv[-1]), ['sudo', '-n', 'python3', '-']) - self.assertNotIn('-t', argv) - self.assertIn('BatchMode=yes', argv) - - def test_unknown_or_shell_route_refused(self): - for value in ('', 'root', 'sudo kubectl', 'founder-sudo; rm -rf /'): - with self.assertRaises(ValueError): - route.ssh_argv(['kubectl'], environ={'KUBECTL_ROUTE': value}) - - def test_remote_arguments_are_quoted(self): - argv = route.ssh_argv(['kubectl', 'patch', '-p', '{"a": "b c"}'], environ={}) - self.assertEqual(shlex.split(argv[-1])[-1], '{"a": "b c"}') - - def test_environment_is_read_when_not_given(self): - with patch.dict('os.environ', {'KUBECTL_ROUTE': 'founder-sudo'}): - self.assertEqual(route.remote_prefix(), ['sudo', '-n']) - - def test_empty_command_refused(self): - with self.assertRaises(ValueError): - route.ssh_argv([], environ={}) - - def test_bounded_scripts_use_the_route(self): - for name in ('argocd_public_transport.py', 'argocd_adoption_proof.py', 'workload_adoption_proof.py', - 'activity_promotion_readiness.py', 'argocd_child_pin_drift.py', 'capture_service_observation.py'): - text = (ROOT / 'scripts' / name).read_text() - self.assertIn('ssh_argv(', text, name) - self.assertNotIn("'railiance01', shlex.join(['kubectl'", text, name) - self.assertNotIn("['ssh', 'railiance01', 'python3'", text, name) - self.assertNotIn("['ssh','railiance01','python3'", text, name) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_kv_duplicate_retire.py b/tests/test_kv_duplicate_retire.py deleted file mode 100644 index 85416ab..0000000 --- a/tests/test_kv_duplicate_retire.py +++ /dev/null @@ -1,121 +0,0 @@ -"""Offline tests for the duplicate KV retirement helper against a fake ``bao``.""" -import importlib.util -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import textwrap -import unittest - -ROOT = Path(__file__).resolve().parents[1] -SCRIPT = ROOT / 'scripts/kv_duplicate_retire.py' -spec = importlib.util.spec_from_file_location('kv_duplicate_retire', SCRIPT) -helper = importlib.util.module_from_spec(spec) -sys.path.insert(0, str(ROOT / 'scripts')) -spec.loader.exec_module(helper) - -CONFIRM = 'RETIRE activity-core/default/llm-connect-provider-secrets' -DUP = 'workloads/activity-core/default/llm-connect-provider-secrets' -CANON = 'workloads/activity-core/llm-connect/llm-connect-provider-secrets' - -FAKE_BAO = textwrap.dedent('''\ - #!/usr/bin/env python3 - import json, os, sys - sf = os.environ['FAKE_BAO_STATE'] - st = json.load(open(sf)) - st['calls'].append(sys.argv[1:]) - def save(): json.dump(st, open(sf, 'w')) - def out(text): save(); print(text); sys.exit(0) - a = [x for x in sys.argv[1:] if x != '-format=json'] - if a[:2] == ['policy', 'list']: out(json.dumps(sorted(st['policies']))) - if a[:2] == ['policy', 'read']: out(st['policies'][a[2]]) - if a[0] == 'read': - if a[1] == 'auth/token/lookup-self': out(json.dumps({'data': st['identity']})) - rest = a[1].split('/', 2)[2] - if rest not in st['kv']: save(); sys.stderr.write('No value found\\n'); sys.exit(2) - n = st['kv'][rest] - out(json.dumps({'data': {'current_version': n, 'versions': {str(i + 1): {} for i in range(n)}}})) - if a[0] == 'delete': - st['kv'].pop(a[1].split('/', 2)[2], None); save(); sys.exit(0) - save(); sys.exit(9) -''') - -CLEAN = { - 'platform-admin': 'path "platform/*" { capabilities = ["read", "delete"] }', - 'agent-high-risk-boundary': f'path "platform/data/{DUP}" {{ capabilities = ["deny"] }}', - 'workload-kv-read-activity-core': f'path "platform/data/{CANON}" {{\n capabilities = ["read"]\n}}', -} - - -class GlobMatching(unittest.TestCase): - def test_exact_plus_and_trailing_star(self): - path = f'platform/data/{DUP}' - self.assertTrue(helper.glob_matches(path, path)) - self.assertTrue(helper.glob_matches('platform/data/workloads/activity-core/*', path)) - self.assertTrue(helper.glob_matches('platform/data/workloads/+/default/*', path)) - self.assertFalse(helper.glob_matches('platform/data/workloads/activity-core/llm-connect/*', path)) - self.assertFalse(helper.glob_matches('platform/data/workloads/+/llm-connect/*', path)) - - -class Retire(unittest.TestCase): - def setUp(self): - self.tmp = Path(tempfile.mkdtemp()) - (self.tmp / 'bin').mkdir() - (self.tmp / 'bin/bao').write_text(FAKE_BAO) - (self.tmp / 'bin/bao').chmod(0o755) - self.home = self.tmp / '.warden-attended-login' / 'session' - self.home.mkdir(parents=True) - self.state = {'calls': [], 'identity': {'policies': ['platform-admin']}, - 'policies': dict(CLEAN), 'kv': {DUP: 1, CANON: 3}} - - def run_helper(self, *extra): - (self.tmp / 'state.json').write_text(json.dumps(self.state)) - receipt = self.tmp / f'r{len(self.state["calls"])}.json' - env = {k: v for k, v in os.environ.items() if k not in ('BAO_TOKEN', 'VAULT_TOKEN')} - env.update(PATH=f'{self.tmp / "bin"}:{env["PATH"]}', HOME=str(self.home), - FAKE_BAO_STATE=str(self.tmp / 'state.json')) - proc = subprocess.run([sys.executable, str(SCRIPT), '--confirm', CONFIRM, - '--receipt', str(receipt), *extra], env=env, capture_output=True, text=True) - self.assertEqual((proc.stdout, proc.stderr), ('', '')) - self.state = json.loads((self.tmp / 'state.json').read_text()) - return proc.returncode, json.loads(receipt.read_text()) - - def test_retires_when_no_policy_grants(self): - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (0, 'retired')) - self.assertNotIn(DUP, self.state['kv']) - self.assertIn(CANON, self.state['kv']) - self.assertFalse(any(c[0] == 'read' and '/data/' in c[-1] for c in self.state['calls'])) - - def test_dry_run_deletes_nothing(self): - code, receipt = self.run_helper('--dry-run') - self.assertEqual((code, receipt['outcome']), (0, 'dry_run_would_retire')) - self.assertIn(DUP, self.state['kv']) - - def test_wildcard_grant_blocks(self): - self.state['policies']['coding-agent'] = 'path "platform/data/workloads/activity-core/*" { capabilities = ["read"] }' - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (3, 'live_policy_grants_duplicate')) - self.assertEqual(list(receipt['granting_policies']), ['coding-agent']) - self.assertIn(DUP, self.state['kv']) - - def test_mount_wide_metadata_survey_does_not_block(self): - self.state['policies']['platform-readonly'] = 'path "platform/metadata/*" { capabilities = ["read", "list"] }' - code, receipt = self.run_helper('--dry-run') - self.assertEqual((code, receipt['outcome']), (0, 'dry_run_would_retire')) - self.assertEqual(receipt['metadata_only_policies'], {'platform-readonly': ['platform/metadata/*']}) - - def test_unexpected_versions_or_missing_canonical_refuse(self): - self.state['kv'][DUP] = 2 - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (2, 'duplicate_version_unexpected')) - self.state['kv'] = {DUP: 1} - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (2, 'canonical_not_live')) - self.assertIn(DUP, self.state['kv']) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_netkingdom_role_files.py b/tests/test_netkingdom_role_files.py deleted file mode 100644 index 31cbdcc..0000000 --- a/tests/test_netkingdom_role_files.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Source checks for the netkingdom OpenBao role files (RPF-WP-0065). No live access.""" -import json -from pathlib import Path - -AUTH = Path(__file__).resolve().parents[1] / 'openbao/auth' - - -def role(name): - return json.loads((AUTH / name).read_text()) - - -def test_platform_admin_binds_group_and_aal2(): - r = role('netkingdom-platform-admin-role.json') - assert r['bound_claims_type'] == 'string' - assert r['bound_claims'] == {'groups': ['net-kingdom-admins'], '/assurance/level': 'aal2'} - assert r['groups_claim'] == 'groups' - assert r['token_policies'] == ['platform-admin', 'operator-custody'] - - -def test_platform_admin_matches_credential_broker_assurance_binding(): - admin = role('netkingdom-platform-admin-role.json')['bound_claims'] - broker = role('credential-broker-keycape-client-secret-generate-oidc-role.json')['bound_claims'] - assert admin['/assurance/level'] == broker['/assurance/level'] == 'aal2' - - -def test_coding_agent_accepts_service_and_agent_during_transition(): - r = role('coding-agent-jwt-role.json') - claims = r['bound_claims'] - assert sorted(claims['principal_type']) == ['agent', 'service'] - # nothing else loosened - assert claims['sub'] == 'service:codex:railiance-platform' - assert claims['tenant'] == 'tenant:platform' - assert claims['roles'] == 'coding-agent' - assert r['bound_audiences'] == ['codex-railiance-platform'] - assert r['token_ttl'] == r['token_max_ttl'] == '15m' - assert r['token_no_default_policy'] is True - assert 'default' not in r['token_policies'] diff --git a/tests/test_openbao_break_glass_drill.py b/tests/test_openbao_break_glass_drill.py deleted file mode 100644 index 84f205b..0000000 --- a/tests/test_openbao_break_glass_drill.py +++ /dev/null @@ -1,372 +0,0 @@ -"""RPF-WP-0064-T10 break-glass drill: offline tests with a fake OpenBao (no Docker). - -The fake answers the bao CLI calls the drill makes, keeps generated tokens, -OTPs and nonces in memory only, and records which credential reached which -path, so the tests can show the held token is used for ``lookup-self`` only. -""" -from __future__ import annotations - -import base64 -import copy -import json -import secrets -import subprocess -import sys -import unittest -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent)) -import test_openbao_isolated_restore as U # noqa: E402 - -A, V, R = U.A, U.V, U.R -LABEL = "openbao-raft-20261014t153200z.snap.age" - - -def b64share() -> str: - return base64.b64encode(secrets.token_bytes(33)).decode() - - -class FakeBao: - """Just enough OpenBao for the drill steps; failure switches per test.""" - - def __init__(self, held_valid=True): - self.held = "s." + secrets.token_urlsafe(18)[:24] - self.held_valid = held_valid - self.generated: str | None = None - self.revoked = False - self.attempt: dict | None = None - self.calls: list[tuple[str, ...]] = [] - self.held_paths: list[str] = [] - self.generated_policies = ["root"] - self.revoke_body = b"" - self.cancel_works = True - - @staticmethod - def ok(body=b""): - return subprocess.CompletedProcess([], 0, body if isinstance(body, bytes) else json.dumps(body).encode(), b"") - - @staticmethod - def code(status): - return subprocess.CompletedProcess([], 2, b"", f"Error: Error making API request.\n\nCode: {status}. " - "Errors:\n\n* permission denied\n".encode()) - - def __call__(self, args, *, credential=None, body=b"", limit=30, check=True, reason="internal_error"): - verb, path = args[0], args[-2] if args[-1] == "-" else args[-1] - self.calls.append((verb, path)) - if credential is not None and credential == self.held: - self.held_paths.append(f"{verb} {path}") - done = self.answer(verb, path, credential, body) - if check and done.returncode != 0: - raise A.Refused(reason) - return done - - def status(self): - att = self.attempt or {} - return {"started": bool(self.attempt), "progress": att.get("progress", 0), "required": 2, - "complete": False, "otp": "", "nonce": "", "encoded_token": "", "otp_length": 26} - - def answer(self, verb, path, credential, body): - if path == "auth/token/lookup-self" and verb == "read": - if credential == self.held and self.held_valid: - return self.ok({"data": {"policies": ["root"], "ttl": 0, "expire_time": None, "orphan": True, - "id": self.held, "accessor": "Ab3" * 8}}) - if credential and credential == self.generated and not self.revoked: - return self.ok({"data": {"policies": self.generated_policies, "ttl": 0, "expire_time": None, - "id": self.generated}}) - return self.code(403) - if path == "sys/generate-root/attempt" and verb == "read": - return self.ok({"data": self.status()}) - if path == "sys/generate-root/attempt" and verb == "write": - self.attempt = {"otp": secrets.token_urlsafe(30)[:26].replace("-", "a").replace("_", "b"), - "nonce": "0b0e" + secrets.token_hex(14), "progress": 0} - return self.ok({"data": {**self.status(), "otp": self.attempt["otp"], "nonce": self.attempt["nonce"]}}) - if path == "sys/generate-root/attempt" and verb == "delete": - if self.cancel_works: - self.attempt = None - return self.ok(b"Success! Data deleted\n") - if path == "sys/generate-root/update": - payload = json.loads(body) - assert payload["nonce"] == self.attempt["nonce"] - self.attempt["progress"] += 1 - if self.attempt["progress"] < 2: - return self.ok({"data": {**self.status(), "progress": 1}}) - self.generated = "s." + secrets.token_urlsafe(18)[:24] - otp = self.attempt["otp"].encode() - encoded = base64.b64encode(bytes(a ^ b for a, b in zip(self.generated.encode(), otp))).decode() - self.attempt = None - return self.ok({"data": {"complete": True, "encoded_token": encoded, "progress": 2}}) - if path == "sys/auth" and credential == self.generated and not self.revoked: - return self.ok({"data": {"token/": {"type": "token"}, "netkingdom/": {"type": "oidc"}}}) - if path == "auth/token/revoke-self" and credential == self.generated: - self.revoked = True - return self.ok(self.revoke_body) - return self.code(403) - - -class DrillBase(U.Base): - def drill_admission(self, **override): - adm = self.admission(attempt_id="RPF-WP-0064-T10-unit-01", drill="break-glass", snapshot_label=LABEL, - readback_credential=A.DRILL_READBACK) - adm.update(override) - return adm - - def session(self, fake, answers, **adm_override): - queue = list(answers) - self.prompts: list[str] = [] - - def prompt(label): - self.prompts.append(label) - return bytearray(queue.pop(0).encode()) - adm = self.drill_admission(**adm_override) - session = A.Session(admission=adm, admission_sha256=U.digest("adm"), - signer={"principal": "founder@rehearsal", "fingerprint": "SHA256:" + "AbCd" * 10 + "AbC"}, - anchor_is_protected=False, ciphertext=self.work / "none", receipt_path=self.work / "r", - root=self.root, prompt=prompt, say=lambda _m: None, start_reaper=False, - mode=A.DRILL_MODE) - session.bao = fake - return session - - def run_drill(self, fake, answers=None): - answers = answers if answers is not None else [fake.held, b64share(), b64share()] - session = self.session(fake, answers) - session.drill_held_token() - session.drill_generate_root() - session.drill_minimal_action() - session.drill_revoke() - session.drill_cancel() - return session - - def drill_receipt(self, session=None): - """A complete ``drilled`` receipt from the adapter's own drill record plus a valid restore.""" - session = session or self.run_drill(FakeBao()) - base = self.receipt() - rc = copy.deepcopy(session.receipt) - for key in ("started_at", "finished_at", "elapsed_seconds", "image_id", "network", "restored", "snapshot"): - rc[key] = base[key] - rc["claim_limits"]["production_root_token_validity"]["snapshot_plaintext_sha256"] = \ - rc["snapshot"]["plaintext_sha256"] - rc["image"] = base["image"] - rc["teardown"].update(container_removed=True, container_absent_verified=True) - rc.update(result="drilled", reason="break_glass_drill_complete") - return rc - - -class DrillAdmissionTests(DrillBase): - def test_valid_drill_admission(self): - A.validate_admission(self.drill_admission(), A.DRILL_MODE) - - def test_phase_a_and_drill_admissions_are_disjoint(self): - with self.assertRaises(A.Refused): - A.validate_admission(self.drill_admission(), A.PHASE_A) - with self.assertRaises(A.Refused): - A.validate_admission(self.admission(), A.DRILL_MODE) - with self.assertRaises(A.Refused): - A.Session(admission=self.admission(), admission_sha256=U.digest("a"), signer={}, anchor_is_protected=False, - ciphertext=self.work, receipt_path=self.work / "x", mode=A.DRILL_MODE, start_reaper=False) - - def test_drill_refusals(self): - cases = [{"drill": "phase-a"}, {"attempt_id": "RPF-WP-0064-T07-01"}, {"snapshot_label": "latest"}, - {"snapshot_label": "Openbao-20261014"}, {"snapshot_label": "openbao-raft-snapshot"}, - {"readback_credential": "generate-root-from-entered-shares"}, {"phase_b_admitted": True}, - {"material": "original", "max_duration_seconds": 600}] - for override in cases: - with self.subTest(override=override), self.assertRaises(A.Refused): - A.validate_admission(self.drill_admission(**override), A.DRILL_MODE) - - def test_allowed_surfaces(self): - self.assertIn("sys/generate-root/attempt", A.ALLOWED_READS) - self.assertIn("sys/generate-root/attempt", A.ALLOWED_WRITES) - self.assertIn("sys/generate-root/update", A.ALLOWED_WRITES) - self.assertEqual(A.ALLOWED_DELETES, frozenset({"sys/generate-root/attempt"})) - self.assertIn("auth/token/lookup-self", A.ALLOWED_READS) - self.assertIn("sys/auth", A.ALLOWED_READS) - self.assertFalse(any("/data/" in p for p in A.ALLOWED_READS)) - self.assertEqual(A.MAX_DURATION_SECONDS, 2700) - listener = json.loads(A.SERVER_CONFIG)["listener"]["tcp"] - self.assertEqual(listener["address"], "127.0.0.1:8200") - self.assertIs(listener["disable_unauthed_generate_root_endpoints"], False) - with self.assertRaises(A.Refused): - self.session(FakeBao(), []).delete("sys/unseal") - - -class DrillStepTests(DrillBase): - def test_full_drill_records_only_facts(self): - fake = FakeBao() - shares = [b64share(), b64share()] - session = self.run_drill(fake, [fake.held, *shares]) - rc = session.receipt - self.assertEqual(self.prompts, ["Held initial root token", "Re-enter unseal share 1 of 2 for generate-root", - "Re-enter unseal share 2 of 2 for generate-root"]) - self.assertEqual(rc["held_root_token"], {"valid": True, "policies": ["root"], "ttl_zero": True, - "expire_time_null": True, "orphan": True}) - self.assertEqual(rc["generate_root"], {"started": True, "shares_entered": 2, "complete": True, - "otp_decode_ok": True, "policies": ["root"], - "minimal_action_path": "sys/auth", "minimal_action_ok": True}) - self.assertEqual(rc["revocation"], {"revoke_status": 204, "post_revoke_lookup": 403}) - self.assertEqual(rc["cancel_drill"], {"started_then_cancelled": True}) - self.assertIsNone(session.credential) - self.assertTrue(rc["teardown"]["readback_credential_revoked"]) - # The held token reached lookup-self only: never revoked, never used for anything else. - self.assertEqual(fake.held_paths, ["read auth/token/lookup-self"]) - self.assertIn(("delete", "sys/generate-root/attempt"), fake.calls) - text = json.dumps(rc) - for value in (fake.held, fake.generated, *shares): - self.assertNotIn(value, text) - self.assertEqual(rc["claim_limits"]["production_root_token_validity"], - {"as_of_snapshot": LABEL, "snapshot_plaintext_sha256": rc["snapshot"]["plaintext_sha256"]}) - self.assertTrue(set(A.DRILL_CLAIMS_NOT_MADE) <= set(rc["claims_not_made"])) - - def test_invalid_held_token_is_a_finding_not_an_abort(self): - fake = FakeBao(held_valid=False) - rc = self.run_drill(fake).receipt - self.assertEqual(rc["held_root_token"], {"valid": False, "policies": [], "ttl_zero": None, - "expire_time_null": None, "orphan": None}) - self.assertEqual(self.verify(self.drill_receipt(self.run_drill(FakeBao(held_valid=False)))), "drilled") - - def failing(self, fake, step, reason, answers=None): - session = self.session(fake, answers if answers is not None else [fake.held, b64share(), b64share()]) - steps = [session.drill_held_token, session.drill_generate_root, session.drill_minimal_action, - session.drill_revoke, session.drill_cancel] - with self.assertRaises(A.Refused) as ctx: - for fn in steps[:step + 1]: - fn() - self.assertEqual(ctx.exception.reason, reason) - return session - - def test_failures(self): - self.failing(FakeBao(), 0, "held_token_refused", ["not a token!", b64share(), b64share()]) - fake = FakeBao() - fake.generated_policies = ["default"] - self.failing(fake, 1, "generate_root_failed") - self.failing(FakeBao(), 1, "generate_root_failed", [FakeBao().held, "short", b64share()]) - fake = FakeBao() - fake.revoke_body = b'{"data": {}}' - session = self.failing(fake, 3, "revocation_failed") - self.assertFalse(session.receipt["teardown"]["readback_credential_revoked"]) - fake = FakeBao() - fake.cancel_works = False - self.failing(fake, 4, "cancel_drill_failed") - - def test_stale_attempt_refused(self): - fake = FakeBao() - fake.attempt = {"otp": "x", "nonce": "y", "progress": 1} - self.failing(fake, 1, "generate_root_failed") - - -class DrillVerifierTests(DrillBase): - def test_drilled_receipt_passes(self): - self.assertEqual(self.verify(self.drill_receipt()), "drilled") - - def test_aborted_drill_receipt_passes(self): - session = self.session(FakeBao(), []) - rc = self.drill_receipt(session) - rc.update(result="aborted", reason="generate_root_failed") - self.assertEqual(self.verify(rc), "aborted") - - def test_drill_and_phase_a_shapes_do_not_mix(self): - rc = self.drill_receipt() - rc.update(result="restored", reason="all_required_objects_match") - with self.assertRaises(V.Reject): - self.verify(rc) - phase_a = self.receipt() - phase_a.update(result="drilled", reason="break_glass_drill_complete") - with self.assertRaises(V.Reject): - self.verify(phase_a) - - def test_adversarial_fixtures(self): - token = "s." + "Kq3x9ZbT7mW2pL8vR4nY6cJd" - hvs = "hvs.CAESI" + "Kq3x9ZbT7mW2pL8vR4nY6cJd" * 3 - accessor = "Kq3x9ZbT7mW2pL8vR4nY6cJd" - otp = "Zq8T1mVb4Xw9Kc2Lp7Rn3Hs6Yd" - nonce = "0b0e6c2d-9f3a-4e1b-8c7d-5a6b7c8d9e0f" - encoded = base64.b64encode(bytes(range(40, 66))).decode() - share = base64.b64encode(bytes(range(1, 34))).decode() - hex_share = "ab01" * 17 - gen = lambda **kw: (lambda r: r["generate_root"].update(**kw)) # noqa: E731 - held = lambda **kw: (lambda r: r["held_root_token"].update(**kw)) # noqa: E731 - cases = { - "token_value_policies": held(policies=[token]), - "token_in_path": lambda r: r.update(attempt_id="RPF-WP-0064-T10-" + token.replace(".", "-")), - "hvs_in_label": lambda r: r["claim_limits"]["production_root_token_validity"].update(as_of_snapshot=hvs), - "token_key": lambda r: r["held_root_token"].update(token=token), - "root_token_key": lambda r: r.update(root_token=token), - "accessor_key": held(accessor=accessor), - "accessor_value": gen(minimal_action_path=accessor), - "accessor_in_network": lambda r: r["network"].update(network_mode=accessor), - "otp_key": gen(otp=otp), - "otp_value": gen(policies=[otp]), - "otp_in_last_error": lambda r: r["restored"]["leader_wait"].update(last_error=otp) if - r["restored"]["leader_wait"] else r["network"].update(mounts=[otp]), - "nonce_key": gen(nonce=nonce), - "nonce_value": gen(minimal_action_path=nonce), - "encoded_token_key": gen(encoded_token=encoded), - "encoded_root_token_key": gen(encoded_root_token=encoded), - "encoded_value": lambda r: r["network"].update(mounts=[encoded]), - "share_key": gen(shares=[share]), - "share_value": gen(policies=[share]), - "share_in_cancel": lambda r: r.update(cancel_drill={"started_then_cancelled": share}), - "hex_share": lambda r: r["network"].update(mounts=[hex_share]), - "unseal_keys": lambda r: r.update(unseal_keys_b64=[share]), - "client_token": lambda r: r["revocation"].update(client_token=token), - } - for name, mutate in cases.items(): - with self.subTest(name): - rc = self.drill_receipt() - mutate(rc) - with self.assertRaises(V.Reject): - self.verify(rc) - - def test_inconsistent_drill_receipts(self): - cases = { - "revoke_200": (lambda r: r["revocation"].update(revoke_status=200), "drill_inconsistent"), - "lookup_after_revoke_ok": (lambda r: r["revocation"].update(post_revoke_lookup=200), - "drill_inconsistent"), - "one_share": (lambda r: r["generate_root"].update(shares_entered=1), "drill_inconsistent"), - "not_root": (lambda r: r["generate_root"].update(policies=["default"]), "drill_fields"), - "no_cancel": (lambda r: r["cancel_drill"].update(started_then_cancelled=False), "drill_inconsistent"), - "minimal_path": (lambda r: r["generate_root"].update(minimal_action_path="sys/mounts"), "drill_fields"), - "held_invalid_with_facts": (lambda r: r["held_root_token"].update(valid=False), "drill_inconsistent"), - "held_unchecked": (lambda r: r["held_root_token"].update(valid=None, policies=[], ttl_zero=None, - expire_time_null=None, orphan=None), - "drill_inconsistent"), - "claims_missing": (lambda r: r["claims_not_made"].remove("sso_down_access_path"), "claims_not_made"), - "snapshot_unnamed": (lambda r: r["claim_limits"]["production_root_token_validity"].update( - as_of_snapshot="latest"), "claim_limits"), - "snapshot_other_sha": (lambda r: r["claim_limits"]["production_root_token_validity"].update( - snapshot_plaintext_sha256=U.digest("other")), "claim_limits"), - "objects_read": (lambda r: r.update(objects=self.receipt()["objects"]), "objects"), - "phase_a_attempt": (lambda r: r.update(attempt_id="RPF-WP-0064-T07-01x"), "attempt_id"), - "unrevoked_teardown": (lambda r: r["teardown"].update(readback_credential_revoked=None), - "drill_inconsistent"), - "kv_data_read": (lambda r: r.update(kv_data_read=True), "secret_policy"), - "drill_reason_on_phase_a": (lambda r: r.update(result="aborted", reason="nope"), "result_inconsistent"), - } - for name, (mutate, code) in cases.items(): - with self.subTest(name): - rc = self.drill_receipt() - mutate(rc) - with self.assertRaises(V.Reject) as ctx: - self.verify(rc) - self.assertEqual(str(ctx.exception), code) - - def test_cli_accepts_drilled(self): - path = self.work / "drill.json" - path.write_text(json.dumps(self.drill_receipt())) - done = subprocess.run([sys.executable, str(R.VERIFIER), "--receipt", str(path), "--source-root", - str(self.root), "--expect-result", "drilled"], capture_output=True) - self.assertEqual(done.returncode, 0, done.stdout) - - -class DrillLauncherTests(DrillBase): - def test_launcher_mode_flag_refuses_before_prompt(self): - launcher = R.ROOT / "scripts/openbao-isolated-restore/run-isolated-restore.sh" - f = self.work / "f" - f.write_text("x") - out = subprocess.run(["bash", str(launcher), "--break-glass-drill", str(f), str(f), U.digest("x"), str(f), - str(self.work)], capture_output=True, stdin=subprocess.DEVNULL).stdout - self.assertIn(b"interactive terminal", out) - self.assertIn(b"--break-glass-drill", subprocess.run(["bash", str(launcher)], capture_output=True).stdout) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_openbao_isolated_restore.py b/tests/test_openbao_isolated_restore.py deleted file mode 100644 index 9ebc8e5..0000000 --- a/tests/test_openbao_isolated_restore.py +++ /dev/null @@ -1,306 +0,0 @@ -"""RPF-WP-0064-T02/T03 unit tests without Docker: admission, verifier, launcher. - -Adversarial receipt fixtures are derived from one valid synthetic receipt built -here; nothing in this module touches Docker, OpenBao or custody material. -""" -from __future__ import annotations - -import copy -import hashlib -import json -import os -import shutil -import subprocess -import sys -import tempfile -import unittest -from datetime import UTC, datetime, timedelta -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent)) -import openbao_restore_rehearsal as R # noqa: E402 - -A = R.adapter -V = R.load("verify_restore_receipt", "scripts/openbao-isolated-restore/verify-restore-receipt.py") -FAKE_IMAGE = "quay.io/openbao/openbao@sha256:" + hashlib.sha256(b"image").hexdigest() - - -def digest(seed: str) -> str: - return "sha256:" + hashlib.sha256(seed.encode()).hexdigest() - - -class Base(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.work = Path(tempfile.mkdtemp(prefix="rpf64-unit-")) - cls.root, cls.commit = R.make_source_root(cls.work) - - @classmethod - def tearDownClass(cls): - shutil.rmtree(cls.work, ignore_errors=True) - - def admission(self, **override): - now = datetime.now(UTC).replace(microsecond=0) - adm = json.loads((R.ROOT / "docs/openbao-restore-admission.example.json").read_text()) - adm.update({"attempt_id": "RPF-WP-0064-T04-unit-01", "material": "synthetic", - "not_before": A.iso(now - timedelta(minutes=5)), "not_after": A.iso(now + timedelta(hours=1)), - "image": FAKE_IMAGE, "adapter_commit": self.commit, "source_sha256": A.source_digests(self.root)}) - adm["snapshot"].update({"ciphertext_sha256": digest("c"), "ciphertext_bytes": 10, - "plaintext_sha256": digest("p"), "source_receipt_sha256": digest("r"), - "age_recipient": R.Rehearsal.age_pair()[1], - "source_cluster_id": "9b5aa97f-933c-bd94-095a-5b5d7f766da5", - "source_applied_index": 50}) - adm.update(override) - return adm - - def receipt(self): - rc = json.loads((R.ROOT / "docs/openbao-restore-receipt-v2.example.json").read_text()) - adm = self.admission() - now = datetime.now(UTC).replace(microsecond=0) - rc.update({"attempt_id": adm["attempt_id"], "material": "synthetic", "admission_sha256": digest("adm"), - "signer_principal": "founder@rehearsal", "anchor_protected": False, - "signer_fingerprint": "SHA256:" + "AbCd" * 10 + "AbC", "adapter_commit": self.commit, - "source_sha256": adm["source_sha256"], "started_at": A.iso(now - timedelta(seconds=40)), - "finished_at": A.iso(now), "not_after": adm["not_after"], "elapsed_seconds": 40, - "image": FAKE_IMAGE, "image_id": digest("id")}) - rc["snapshot"].update({k: adm["snapshot"][k] for k in rc["snapshot"] if k in adm["snapshot"]}) - rc["restored"].update({"cluster_id": adm["snapshot"]["source_cluster_id"], "applied_index": 50}) - for row in rc["objects"]: - row["observed_sha256"] = digest(row["object"]) - return rc - - def verify(self, rc, **kw): - return V.verify(copy.deepcopy(rc), source_root=self.root, **kw) - - -class AdmissionTests(Base): - def test_valid_synthetic_admission(self): - A.validate_admission(self.admission()) - - def test_example_admission_refused_as_placeholder(self): - with self.assertRaises(A.Refused): - A.validate_admission(json.loads((R.ROOT / "docs/openbao-restore-admission.example.json").read_text())) - - def test_refusals(self): - cases = [ - {"phase": "B"}, {"phase_b_admitted": True}, {"image": "quay.io/openbao/openbao:2.5.4"}, - {"image": "quay.io/openbao/openbao@sha256:" + "0" * 64}, {"image": "docker.io/x/openbao@" + digest("i")}, - {"max_duration_seconds": 2701}, {"material": "original", "max_duration_seconds": 600}, - {"namespace": "secrets-engine-recovery-attempt"}, {"readback_credential": "operator-oidc"}, - {"unseal": {"key_threshold": 1, "key_shares": 1}}, {"extra": True}, {"adapter_commit": "HEAD"}, - ] - for override in cases: - with self.subTest(override=override), self.assertRaises(A.Refused): - A.validate_admission(self.admission(**override)) - adm = self.admission() - adm["source_sha256"].pop(next(iter(adm["source_sha256"]))) - with self.assertRaises(A.Refused): - A.validate_admission(adm) - - def test_window(self): - adm = self.admission() - A.check_window(adm) - with self.assertRaises(A.Refused): - A.check_window(adm, A.parse_iso(adm["not_after"])) - - def test_source_closure_detects_dirty_or_changed(self): - adm = self.admission() - A.check_source_closure(adm, self.root) - with self.assertRaises(A.Refused): - A.check_source_closure(dict(adm, adapter_commit="f" * 40), self.root) - target = self.root / "openbao/auth/secrets-engine-jwt-role.json" - original = target.read_bytes() - try: - target.write_bytes(original + b" ") - with self.assertRaises(A.Refused): - A.check_source_closure(adm, self.root) - finally: - target.write_bytes(original) - - def signed(self, adm, namespace=A.NAMESPACE): - anchor = self.work / "anchor" - anchor.mkdir(mode=0o700, exist_ok=True) - key = anchor / "k" - if not key.exists(): - subprocess.run(["ssh-keygen", "-q", "-t", "ed25519", "-N", "", "-C", "f", "-f", str(key)], check=True) - pub = (anchor / "k.pub").read_text().strip() - (anchor / "allowed_signers").write_text(f'founder@rehearsal namespaces="{A.NAMESPACE}" {pub}\n') - raw = json.dumps(adm).encode() - path = Path(tempfile.mkstemp(dir=self.work)[1]) - path.write_bytes(raw) - subprocess.run(["ssh-keygen", "-q", "-Y", "sign", "-f", str(key), "-n", namespace, str(path)], check=True) - sig = Path(str(path) + ".sig") - os.chmod(sig, 0o600) - return path, sig, A.sha256_bytes(raw), anchor / "allowed_signers" - - def test_signature_namespace_and_bytes_bound(self): - path, sig, sha, signers = self.signed(self.admission()) - _adm, _raw, signer, protected = A.load_admission(path, sig, sha, signers) - self.assertEqual(signer["principal"], "founder@rehearsal") - self.assertFalse(protected) # runtime-owned anchor: rehearsal only - with self.assertRaises(A.Refused): - A.load_admission(path, sig, digest("other"), signers) - path2, sig2, sha2, _ = self.signed(self.admission(), namespace="secrets-engine-recovery-attempt") - with self.assertRaises(A.Refused): - A.load_admission(path2, sig2, sha2, signers) - - def test_original_material_needs_protected_anchor_and_t06_pin(self): - path, sig, sha, signers = self.signed(self.admission(material="original")) - with self.assertRaises(A.Refused): - A.load_admission(path, sig, sha, signers) - self.assertEqual(A.PINNED_IMAGE_DIGEST, - "sha256:436eaf9778cad75507ff70ea26ace30dcbe15606e619ac3823495663d7f7c115") - - def test_target_takes_production_raft_node_id(self): - # rapp-openbao runs the chart with setNodeId, so the snapshot's only voter is the pod name. - self.assertEqual(A.RESTORE_NODE_ID, "openbao-0") - self.assertEqual(json.loads(A.SERVER_CONFIG)["storage"]["raft"]["node_id"], "openbao-0") - self.assertIn("leader_not_elected", A.ABORT_REASONS) - - def test_leader_wait_diagnostics_are_typed_and_redacted(self): - line = A.sanitize_status_line(b"\nError: token hvs.CAESIabcdefghijklmnop local node not active\nmore") - self.assertNotIn("hvs.CAES", line) - self.assertIn("local node not active", line) - self.assertLessEqual(len(A.sanitize_status_line(b"x" * 500) or ""), 200) - self.assertIsNone(A.typed("5", int)) - self.assertIsNone(A.typed(1, bool)) - self.assertEqual(A.typed("http://openbao-0.openbao-internal:8200", str), "http://openbao-0.openbao-internal:8200") - - def test_normalise_and_project(self): - source = {"token_ttl": "5m", "token_policies": ["b", "a"], "_note": "x"} - ok, projected = A.project(source, {"token_ttl": 300, "token_policies": ["a", "b"], "extra": 1}, ("_note",)) - self.assertTrue(ok) - self.assertEqual(projected, {"token_policies": ["a", "b"], "token_ttl": 300}) - self.assertFalse(A.project(source, {"token_ttl": 600, "token_policies": ["a", "b"]}, ("_note",))[0]) - - def test_reads_never_include_kv_data(self): - self.assertFalse(any("/data/" in p for p in A.ALLOWED_READS)) - self.assertIn(A.KV_METADATA_PATH, A.ALLOWED_READS) - - -class VerifierTests(Base): - def test_valid_receipts(self): - self.assertEqual(self.verify(self.receipt()), "restored") - rc = self.receipt() - rc["objects"][-1].update({"present": False, "source_match": False, "observed_sha256": None}) - rc.update(result="refused", reason="object_missing") - self.assertEqual(self.verify(rc), "refused") - rc = self.receipt() - rc.update(result="aborted", reason="unseal_refused", objects=[]) - self.assertEqual(self.verify(rc), "aborted") - - def test_example_keys_match_verifier_schema(self): - rc = json.loads((R.ROOT / "docs/openbao-restore-receipt-v2.example.json").read_text()) - self.assertEqual(set(rc), V.TOP) - self.assertEqual(set(rc["teardown"]), V.TEARDOWN) - self.assertEqual(tuple(o["object"] for o in rc["objects"]), V.REQUIRED_OBJECTS) - self.assertEqual(tuple(o["object"] for o in A.OBJECTS), V.REQUIRED_OBJECTS) - self.assertEqual(set(A.CLAIMS_NOT_MADE), V.CLAIMS) - self.assertEqual(A.ABORT_REASONS, V.ABORT | V.DRILL_ABORT) - self.assertEqual(A.DRILL_ABORT_REASONS, V.DRILL_ABORT) - self.assertEqual(set(A.DRILL_CLAIMS_NOT_MADE), V.DRILL_CLAIMS) - with self.assertRaises(V.Reject): - self.verify(rc) # placeholders - - def reject(self, mutate, code=None, **kw): - rc = self.receipt() - mutate(rc) - with self.assertRaises(V.Reject) as ctx: - self.verify(rc, **kw) - if code: - self.assertEqual(str(ctx.exception), code) - - def test_adversarial_fixtures(self): - share = "Kq3/" + "x9Z+" * 10 - cases = { - "unknown_field": (lambda r: r.update(notes="ok"), "schema_fields"), - "missing_field": (lambda r: r.pop("teardown"), "schema_fields"), - "missing_teardown": (lambda r: r["teardown"].update(container_removed=False), "teardown_missing"), - "reaper_on_success": (lambda r: r["teardown"].update(performed_by="reaper"), "teardown_missing"), - "unrevoked": (lambda r: r["teardown"].update(readback_credential_revoked=False), "teardown_missing"), - "data_field": (lambda r: r["objects"][0].update(data={"x": 1}), "object_fields"), - "kv_data_path": (lambda r: r["objects"][-1].update( - path="platform/data/workloads/keycape/secrets-engine-openbao"), "kv_data_path"), - "kv_data_read": (lambda r: r.update(kv_data_read=True), "secret_policy"), - "credential_shaped": (lambda r: r.update(signer_principal="hvs.CAESIJ" + "a" * 30), None), - "share_shaped": (lambda r: r["objects"][0].update(path=share), "secret_shaped_value"), - "hex_key_shaped": (lambda r: r["objects"][0].update(path="f00d" * 16), "secret_shaped_value"), - "age_identity": (lambda r: r["objects"][0].update(path="AGE-SECRET-KEY-1QQQ"), "secret_shaped_value"), - "pem": (lambda r: r["objects"][0].update(path="-----BEGIN PRIVATE"), "secret_shaped_value"), - "placeholder_digest": (lambda r: r["snapshot"].update(ciphertext_sha256="sha256:" + "1" * 64), - "snapshot_digest"), - "placeholder_text": (lambda r: r["objects"][0].update(path="replace-with-path"), "placeholder"), - "untyped_digest": (lambda r: r.update(admission_sha256=hashlib.sha256(b"x").hexdigest()), - "admission_digest"), - "claims_missing": (lambda r: r["claims_not_made"].remove("consumer_continuity"), "claims_not_made"), - "deadline_overrun": (lambda r: r.update(elapsed_seconds=4000, started_at=A.iso( - A.parse_iso(r["finished_at"]) - timedelta(seconds=4000))), "deadline_overrun"), - "original_short_deadline": (lambda r: r.update(material="original", anchor_protected=True, - deadline_seconds=600), "deadline"), - "original_unprotected_anchor": (lambda r: r.update(material="original"), "anchor_unprotected"), - "restored_with_mismatch": (lambda r: r["objects"][3].update(source_match=False), "result_inconsistent"), - "restored_network": (lambda r: r["network"].update(interfaces=["eth0", "lo"]), "network_not_contained"), - "cluster_mismatch": (lambda r: r["restored"].update(cluster_id_match=False), "restore_inconsistent"), - "objects_incomplete": (lambda r: r["objects"].pop(), "objects_incomplete"), - "bad_reason": (lambda r: r.update(result="aborted", reason="because"), "result_inconsistent"), - "phase_b": (lambda r: r.update(phase="B"), "phase"), - "source_hash": (lambda r: r["source_sha256"].update( - {"openbao/auth/secrets-engine-jwt-role.json": digest("zz")}), "source_hash_mismatch"), - "source_commit": (lambda r: r.update(adapter_commit="a" * 40), "source_commit_mismatch"), - } - for name, (mutate, code) in cases.items(): - with self.subTest(name): - self.reject(mutate, code) - - def test_not_after_window(self): - self.reject(lambda r: None, "verification_window_closed", - not_after=datetime(2026, 1, 1, tzinfo=UTC)) - - def test_duplicate_keys_rejected_by_cli(self): - path = self.work / "dup.json" - path.write_text('{"schema": "a", "schema": "b"}') - done = subprocess.run([sys.executable, str(R.VERIFIER), "--receipt", str(path), "--source-root", - str(self.root)], capture_output=True) - self.assertEqual(done.returncode, 1) - self.assertIn(b"duplicate_key", done.stdout) - - def test_cli_pass_prints_digest_only(self): - path = self.work / "good.json" - path.write_text(json.dumps(self.receipt())) - done = subprocess.run([sys.executable, str(R.VERIFIER), "--receipt", str(path), "--source-root", - str(self.root), "--expect-result", "restored"], capture_output=True) - self.assertEqual(done.returncode, 0, done.stdout) - self.assertRegex(done.stdout.decode(), r"^PASS: restored receipt sha256:[a-f0-9]{64}\n$") - - -class LauncherTests(Base): - launcher = R.ROOT / "scripts/openbao-isolated-restore/run-isolated-restore.sh" - - def run_launcher(self, *args): - return subprocess.run(["bash", str(self.launcher), *args], capture_output=True, stdin=subprocess.DEVNULL) - - def test_refusals_before_any_prompt(self): - f = self.work / "f" - f.write_text("x") - self.assertIn(b"Usage", self.run_launcher().stdout) - self.assertIn(b"exact sha256", self.run_launcher(str(f), str(f), "abc", str(f), str(self.work)).stdout) - out = self.run_launcher(str(f), str(f), digest("x"), str(f), str(self.work)).stdout - self.assertIn(b"interactive terminal", out) - self.assertNotIn(b"hidden", out) - - def test_preflight_refuses_unsigned_admission_without_prompt(self): - adm = self.work / "adm.json" - raw = json.dumps(self.admission(material="original")).encode() - adm.write_bytes(raw) - os.chmod(adm, 0o600) - done = subprocess.run([sys.executable, str(R.ROOT / "scripts/openbao-isolated-restore/preflight.py"), - "--admission", str(adm), "--admission-sig", str(adm), "--admission-sha256", - A.sha256_bytes(raw), "--ciphertext", str(adm), "--receipt", str(self.work / "r.json")], - capture_output=True, stdin=subprocess.DEVNULL) - self.assertEqual(done.returncode, 1) - self.assertIn(b"admission_refused", done.stdout) - self.assertNotIn(b"hidden", done.stdout + done.stderr) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_openbao_isolated_restore_native.py b/tests/test_openbao_isolated_restore_native.py deleted file mode 100644 index 77b3f10..0000000 --- a/tests/test_openbao_isolated_restore_native.py +++ /dev/null @@ -1,239 +0,0 @@ -"""RPF-WP-0064-T04 native rehearsal: real OpenBao containers, generated material only. - -Runs the pinned OpenBao image with ``--network none``. Skips (and says so) when -Docker or the image is unavailable; a skip is not native coverage. The only -mocked element is the network-interface abort, where a wrapper reports an -extra network in ``docker container inspect`` because attaching a real network -is out of bounds for this rehearsal. -""" -from __future__ import annotations - -import base64 -import json -import os -import signal -import stat -import subprocess -import sys -import time -import unittest -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parent)) -import openbao_restore_rehearsal as R # noqa: E402 - -OK, IMAGE = R.docker_status() - - -@unittest.skipUnless(OK, f"native rehearsal skipped: {IMAGE}") -class NativeRehearsal(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.h = R.Rehearsal(IMAGE) - cls.root, cls.commit = R.make_source_root(cls.h.work) - cls.full = cls.h.snapshot_set() - cls.partial = cls.h.snapshot_set(kv_registration=False, jwt_role=False) - - @classmethod - def tearDownClass(cls): - cls.h.close() - - def tearDown(self): - self.assertEqual([n for n in R.leftover_containers() if n.startswith("rpf64-restore-")], []) - - def run_session(self, snap=None, answers=None, root=None, commit=None, adm_override=None, **kw): - snap = snap or self.full - root, commit = root or self.root, commit or self.commit - adm = self.h.admission(snap, root, commit, **(adm_override or {})) - if answers is None: - answers = [self.h.age_identity, snap["shares"][0], snap["shares"][2]] - session, receipt = self.h.session(adm, snap, root, answers, **kw) - return session, receipt - - def finish(self, session, receipt, root=None, expect="aborted"): - out = session.execute() - self.assertEqual(out["result"], expect, out["reason"]) - self.assertEqual(stat.S_IMODE(receipt.stat().st_mode), 0o600) - text = receipt.read_text() - for value in self.h.secrets_seen: - if value: - self.assertNotIn(value, text) - verified = R.Rehearsal.verify(receipt, root or self.root, "--expect-result", expect) - self.assertEqual(verified.returncode, 0, verified.stdout) - return out - - def test_restored_end_to_end(self): - session, receipt = self.run_session() - out = self.finish(session, receipt, expect="restored") - self.assertEqual(out["reason"], "all_required_objects_match") - self.assertTrue(all(r["present"] and r["source_match"] for r in out["objects"])) - self.assertEqual(out["restored"]["cluster_id"], self.full["cluster_id"]) - self.assertEqual(out["network"]["interfaces"], ["lo"]) - self.assertTrue(out["network"]["egress_refused"]) - self.assertTrue(out["teardown"]["readback_credential_revoked"]) - self.assertTrue(out["restored"]["throwaway_init_invalidated"]) - late = R.Rehearsal.verify(receipt, self.root, "--not-after", "2026-01-01T00:00:00Z") - self.assertNotEqual(late.returncode, 0) - # Independent source check: a checkout with one changed source refuses. - other, _ = R.make_source_root(self.h.work, {"openbao/auth/secrets-engine-jwt-role.json": b"{}\n"}) - self.assertNotEqual(R.Rehearsal.verify(receipt, other).returncode, 0) - - def test_wrong_age_identity_aborts_before_target(self): - other, _ = R.Rehearsal.age_pair() - session, receipt = self.run_session(answers=[other]) - out = self.finish(session, receipt) - self.assertEqual(out["reason"], "custody_identity_refused") - self.assertEqual(out["network"]["docker_networks"], []) - - def test_unwritable_audit_path_reseals_and_aborts(self): - # Regression for T07 attempts -01/-02: without /openbao/audit the restored - # file audit device fails after unseal and OpenBao seals itself again. - removed = R.adapter.TMPFS.pop("/openbao/audit") - try: - session, receipt = self.run_session() - out = self.finish(session, receipt) - finally: - R.adapter.TMPFS["/openbao/audit"] = removed - self.assertEqual(out["reason"], "leader_not_elected") - self.assertTrue(out["restored"]["unsealed_with_original_shares"]) - self.assertIs(out["restored"]["leader_wait"]["last_seal"]["sealed"], True) - - def test_wrong_ciphertext_hash_aborts(self): - session, receipt = self.run_session(adm_override={"ciphertext_sha256": R.adapter.sha256_bytes(b"other")}) - self.assertEqual(self.finish(session, receipt)["reason"], "ciphertext_mismatch") - - def test_wrong_plaintext_hash_aborts(self): - session, receipt = self.run_session(adm_override={"plaintext_sha256": R.adapter.sha256_bytes(b"other")}) - self.assertEqual(self.finish(session, receipt)["reason"], "plaintext_mismatch") - - def test_wrong_share_aborts_and_removes_target(self): - wrong = base64.b64encode(os.urandom(33)).decode() - session, receipt = self.run_session(answers=[self.h.age_identity, self.full["shares"][0], wrong]) - out = self.finish(session, receipt) - self.assertEqual(out["reason"], "unseal_refused") - self.assertTrue(out["restored"]["sealed_after_restore"]) - self.assertTrue(out["teardown"]["container_absent_verified"]) - - def test_missing_objects_refused_not_repaired(self): - session, receipt = self.run_session(snap=self.partial) - out = self.finish(session, receipt, expect="refused") - self.assertEqual(out["reason"], "object_missing") - missing = sorted(r["object"] for r in out["objects"] if not r["present"]) - self.assertEqual(missing, ["kv_registration_metadata", "secrets_engine_jwt_role"]) - - def test_source_mismatch_refused(self): - role = json.loads((R.ROOT / "openbao/auth/secrets-engine-jwt-role.json").read_bytes()) - role["token_ttl"] = "10m" - root, commit = R.make_source_root(self.h.work, { - "openbao/auth/secrets-engine-jwt-role.json": json.dumps(role, indent=2).encode()}) - session, receipt = self.run_session(root=root, commit=commit) - out = self.finish(session, receipt, root=root, expect="refused") - self.assertEqual(out["reason"], "object_mismatch") - self.assertEqual([r["object"] for r in out["objects"] if not r["source_match"]], ["secrets_engine_jwt_role"]) - - def test_network_interface_present_aborts(self): - # Mocked element: real container, inspect output reports an extra network. - wrapper = self.h.work / "docker-extra-network" - wrapper.write_text( - "#!/usr/bin/env python3\nimport json,subprocess,sys\n" - "a=sys.argv[1:]\nr=subprocess.run(['docker',*a],capture_output=True)\n" - "out=r.stdout\n" - "if a[:2]==['container','inspect'] and r.returncode==0:\n" - " d=json.loads(out);d[0]['NetworkSettings']['Networks']['bridge']={};out=json.dumps(d).encode()\n" - "sys.stdout.buffer.write(out);sys.stderr.buffer.write(r.stderr);sys.exit(r.returncode)\n") - wrapper.chmod(0o700) - session, receipt = self.run_session(docker=str(wrapper)) - out = session.execute() - self.assertEqual((out["result"], out["reason"]), ("aborted", "network_containment_failed")) - self.assertEqual(out["network"]["docker_networks"], ["bridge", "none"]) - self.assertTrue(out["teardown"]["container_absent_verified"]) - # The verifier refuses a receipt claiming any non-"none" network. - self.assertNotEqual(R.Rehearsal.verify(receipt, self.root).returncode, 0) - - def test_timeout_aborts(self): - holder = {} - - def slow_share(): - holder["s"].deadline = time.monotonic() - 1 - return self.full["shares"][0] - session, receipt = self.run_session(answers=[self.h.age_identity, slow_share, self.full["shares"][2]]) - holder["s"] = session - out = self.finish(session, receipt) - self.assertEqual(out["reason"], "deadline_exceeded") - - def test_operator_abort_signal(self): - def interrupt(): - os.kill(os.getpid(), signal.SIGINT) - time.sleep(1) - return self.full["shares"][0] - session, receipt = self.run_session(answers=[self.h.age_identity, interrupt]) - out = self.finish(session, receipt) - self.assertEqual(out["reason"], "operator_abort") - self.assertTrue(out["teardown"]["container_absent_verified"]) - - # ---- RPF-WP-0064-T10 break-glass drill (generated material; the held token is synthetic) - def run_drill(self, held: str, reentered: list[str]): - adm = self.h.drill_admission(self.full, self.root, self.commit) - answers = [self.h.age_identity, self.full["shares"][0], self.full["shares"][2], held, *reentered] - return self.h.session(adm, self.full, self.root, answers, mode=R.adapter.DRILL_MODE) - - def test_break_glass_drill_with_valid_held_token(self): - # The synthetic source's initial root token stands in for the founder's held token. - session, receipt = self.run_drill(self.full["root_credential"], [self.full["shares"][1], self.full["shares"][0]]) - out = self.finish(session, receipt, expect="drilled") - self.assertEqual(out["held_root_token"], {"valid": True, "policies": ["root"], "ttl_zero": True, - "expire_time_null": True, "orphan": True}) - self.assertEqual(out["generate_root"]["policies"], ["root"]) - self.assertTrue(out["generate_root"]["minimal_action_ok"]) - self.assertEqual(out["revocation"], {"revoke_status": 204, "post_revoke_lookup": 403}) - self.assertTrue(out["cancel_drill"]["started_then_cancelled"]) - self.assertEqual(out["objects"], []) - self.assertEqual(out["claim_limits"]["production_root_token_validity"]["snapshot_plaintext_sha256"], - self.full["plain_sha"]) - - def test_break_glass_drill_with_fake_held_token(self): - fake = "s." + base64.b32encode(os.urandom(15)).decode().lower()[:24] - self.h.secrets_seen.append(fake) - session, receipt = self.run_drill(fake, [self.full["shares"][0], self.full["shares"][1]]) - out = self.finish(session, receipt, expect="drilled") - self.assertIs(out["held_root_token"]["valid"], False) - - def test_break_glass_drill_wrong_reentered_share_aborts(self): - wrong = base64.b64encode(os.urandom(33)).decode() - session, receipt = self.run_drill(self.full["root_credential"], [self.full["shares"][0], wrong]) - out = self.finish(session, receipt) - self.assertEqual(out["reason"], "generate_root_failed") - self.assertTrue(out["teardown"]["container_absent_verified"]) - - def reaper_case(self, *, kill_parent: bool, deadline_in: float) -> dict: - session, receipt = self.run_session() - parent = subprocess.Popen(["sleep", "300"]) - R.sh(["docker", "run", "--detach", "--pull=never", "--name", session.name, "--network", "none", - "--entrypoint", "sleep", IMAGE, "300"]).check_returncode() - reaper = subprocess.Popen([sys.executable, str(R.ROOT / "scripts/openbao-isolated-restore/restore_adapter.py"), - "--reaper"], stdin=subprocess.PIPE, start_new_session=True) - header = {"parent_pid": parent.pid, "deadline_epoch": time.time() + deadline_in, "docker": "docker", - "receipt_path": str(receipt), "base_receipt": session.receipt} - reaper.stdin.write((json.dumps(header) + "\nC " + session.name + "\n").encode()) - reaper.stdin.flush() - if kill_parent: - parent.kill() - reaper.stdin.close() # parent death closes the pipe - self.assertEqual(reaper.wait(timeout=120), 0) - if not reaper.stdin.closed: - reaper.stdin.close() - parent.wait(timeout=20) - out = json.loads(receipt.read_text()) - self.assertEqual(out["teardown"]["performed_by"], "reaper") - self.assertEqual(R.Rehearsal.verify(receipt, self.root, "--expect-result", "aborted").returncode, 0) - return out - - def test_parent_loss_reaper_cleans_up(self): - self.assertEqual(self.reaper_case(kill_parent=True, deadline_in=600)["reason"], "parent_lost") - - def test_hard_deadline_reaper_kills_session(self): - self.assertEqual(self.reaper_case(kill_parent=False, deadline_in=3)["reason"], "deadline_exceeded") - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_openbao_platform_admin_aal2.py b/tests/test_openbao_platform_admin_aal2.py deleted file mode 100644 index 45a4e25..0000000 --- a/tests/test_openbao_platform_admin_aal2.py +++ /dev/null @@ -1,202 +0,0 @@ -"""RPF-WP-0065-T04 aal2 rollout script against a fake bao. No live access.""" -import json -import os -from pathlib import Path -import stat -import subprocess -import sys - -import pytest - -ROOT = Path(__file__).resolve().parents[1] -WRAPPER = ROOT / 'scripts/openbao-platform-admin-aal2.sh' -DECLARED = json.loads((ROOT / 'openbao/auth/netkingdom-platform-admin-role.json').read_text()) -EXPECTED = {'groups': ['net-kingdom-admins'], '/assurance/level': 'aal2'} - -# The live role before T04: group binding only, no assurance mapping. -LIVE = {k: v for k, v in DECLARED.items()} -LIVE['bound_claims'] = {'groups': ['net-kingdom-admins']} -LIVE['claim_mappings'] = {'email': 'email', 'preferred_username': 'username'} -LIVE['max_age'] = 0 - -FAKE_BAO = r'''#!/usr/bin/env python3 -import json, os, sys -state_path = os.environ['FAKE_BAO_STATE'] -state = json.load(open(state_path)) -state.setdefault('calls', []).append(sys.argv[1:]) -args = [a for a in sys.argv[1:] if a != '-format=json'] -def save(): - json.dump(state, open(state_path, 'w')) -if state.get('fail') in args: - save(); sys.stderr.write('Code: 403. Errors:\n* permission denied\n'); sys.exit(2) -if args[:2] == ['read', 'auth/token/lookup-self']: - save(); print(json.dumps({'data': {'id': 'hvs.SECRETTOKENVALUE00000', 'accessor': 'ACCESSORVALUE', - 'policies': ['default', 'operator-custody', 'platform-admin'], 'path': 'auth/netkingdom/oidc/callback', - 'meta': state['meta']}})); sys.exit(0) -if args[:2] == ['read', 'auth/netkingdom/role/platform-admin']: - save(); print(json.dumps({'data': state['role']})); sys.exit(0) -if args[:2] == ['write', 'auth/netkingdom/role/platform-admin']: - body = json.load(open(args[2][1:])) - if state.get('ignore_bound'): - body.pop('bound_claims', None) - state['role'].update(body); state['writes'] = state.get('writes', 0) + 1 - save(); print('Success! Data written to: auth/netkingdom/role/platform-admin'); sys.exit(0) -save(); sys.stderr.write('unsupported\n'); sys.exit(1) -''' - - -@pytest.fixture -def env(tmp_path): - home = tmp_path / '.warden-attended-login' / 'session' - home.mkdir(parents=True) - bindir = tmp_path / 'bin' - bindir.mkdir() - bao = bindir / 'bao' - bao.write_text(FAKE_BAO) - bao.chmod(bao.stat().st_mode | stat.S_IEXEC) - state = tmp_path / 'state.json' - state.write_text(json.dumps({'role': LIVE, 'meta': {'role': 'platform-admin', 'email': 'x@example.org', - 'username': 'admin'}})) - base = {k: v for k, v in os.environ.items() if k not in ('BAO_TOKEN', 'VAULT_TOKEN')} - base.update(HOME=str(home), PATH='%s:%s' % (bindir, os.environ['PATH']), FAKE_BAO_STATE=str(state)) - return tmp_path, state, base - - -def run(env, *args): - _, _, base = env - proc = subprocess.run([str(WRAPPER), *args], capture_output=True, env=base) - assert proc.stdout == b'' and proc.stderr == b'' # warden fails closed on any output - return proc.returncode - - -def st(env): - return json.loads(env[1].read_text()) - - -def patch(env, **values): - state = st(env) - state.update(values) - env[1].write_text(json.dumps(state)) - - -def receipt(path): - assert stat.S_IMODE(path.stat().st_mode) == 0o600 - text = path.read_text() - for value in ('SECRETTOKENVALUE', 'ACCESSORVALUE', 'x@example.org'): - assert value not in text - return json.loads(text) - - -def test_declaration_maps_assurance_and_binds_aal2(): - assert DECLARED['bound_claims'] == EXPECTED - assert DECLARED['claim_mappings']['/assurance/level'] == 'assurance_level' - - -def test_full_rollout_prepare_apply_verify_is_silent(env): - tmp = env[0] - assert run(env, 'prepare', '--receipt', str(tmp / 'p.json')) == 0 - p = receipt(tmp / 'p.json') - assert p['status'] == 'prepared' and p['backup_role']['bound_claims'] == {'groups': ['net-kingdom-admins']} - role = st(env)['role'] - assert role['bound_claims'] == {'groups': ['net-kingdom-admins']} # prepare does not restrict logins - assert role['claim_mappings']['/assurance/level'] == 'assurance_level' - - patch(env, meta={'role': 'platform-admin', 'assurance_level': 'aal2'}) - assert run(env, 'apply', '--receipt', str(tmp / 'a.json')) == 0 - a = receipt(tmp / 'a.json') - assert a['status'] == 'applied' and a['bound_claims'] == EXPECTED - assert a['gate_token']['assurance_level'] == 'aal2' - assert st(env)['role']['bound_claims'] == EXPECTED - - assert run(env, 'verify', '--receipt', str(tmp / 'v.json')) == 0 - assert receipt(tmp / 'v.json')['status'] == 'verified' - assert all(call[:2] != ['token', 'revoke'] for call in st(env)['calls']) # warden revokes, not us - - -def test_apply_refused_without_aal2_login_and_writes_nothing(env): - tmp = env[0] - assert run(env, 'prepare', '--receipt', str(tmp / 'p.json')) == 0 - writes = st(env)['writes'] - patch(env, meta={'role': 'platform-admin', 'assurance_level': 'aal1'}) - assert run(env, 'apply', '--receipt', str(tmp / 'a.json')) == 1 - assert receipt(tmp / 'a.json')['status'] == 'gate_login_not_aal2' - assert st(env)['writes'] == writes and st(env)['role']['bound_claims'] == {'groups': ['net-kingdom-admins']} - - -def test_apply_refused_before_prepare(env): - tmp = env[0] - patch(env, meta={'role': 'platform-admin', 'assurance_level': 'aal2'}) - assert run(env, 'apply', '--receipt', str(tmp / 'a.json')) == 1 - assert receipt(tmp / 'a.json')['status'] == 'not_prepared' - assert 'writes' not in st(env) - - -def test_apply_readback_mismatch_exits_nonzero(env): - tmp = env[0] - assert run(env, 'prepare', '--receipt', str(tmp / 'p.json')) == 0 - patch(env, meta={'role': 'platform-admin', 'assurance_level': 'aal2'}, ignore_bound=True) - assert run(env, 'apply', '--receipt', str(tmp / 'a.json')) == 1 - assert receipt(tmp / 'a.json')['status'] == 'readback_mismatch' - - -def test_verify_fails_on_unapplied_role(env): - tmp = env[0] - patch(env, meta={'role': 'platform-admin', 'assurance_level': 'aal2'}) - assert run(env, 'verify', '--receipt', str(tmp / 'v.json')) == 1 - assert receipt(tmp / 'v.json')['status'] == 'role_not_declared' - - -def test_rollback_restores_backup_with_fresh_login(env): - tmp = env[0] - assert run(env, 'prepare', '--receipt', str(tmp / 'p.json')) == 0 - patch(env, meta={'role': 'platform-admin', 'assurance_level': 'aal2'}) - assert run(env, 'apply', '--receipt', str(tmp / 'a.json')) == 0 - assert run(env, 'rollback', '--backup', str(tmp / 'p.json'), '--receipt', str(tmp / 'r.json')) == 0 - r = receipt(tmp / 'r.json') - assert r['status'] == 'rolled_back' and r['bound_claims'] == {'groups': ['net-kingdom-admins']} - assert '/assurance/level' not in st(env)['role']['claim_mappings'] - - -def test_rollback_refuses_a_non_backup_receipt(env): - tmp = env[0] - patch(env, meta={'role': 'platform-admin', 'assurance_level': 'aal2'}) - assert run(env, 'prepare', '--receipt', str(tmp / 'p.json')) == 0 - assert run(env, 'apply', '--receipt', str(tmp / 'a.json')) == 0 - assert run(env, 'rollback', '--backup', str(tmp / 'a.json'), '--receipt', str(tmp / 'r.json')) == 1 - assert receipt(tmp / 'r.json')['status'] == 'backup_receipt_invalid' - - -def test_rollback_body_is_local_and_private(env, monkeypatch): - tmp = env[0] - assert run(env, 'prepare', '--receipt', str(tmp / 'p.json')) == 0 - calls = len(st(env)['calls']) - _, _, base = env - base = dict(base, HOME=str(tmp)) # outside the envelope on purpose - proc = subprocess.run([str(WRAPPER), 'rollback-body', '--backup', str(tmp / 'p.json'), - '--out', str(tmp / 'body.json')], capture_output=True, env=base) - assert proc.returncode == 0 and proc.stdout == proc.stderr == b'' - body = receipt(tmp / 'body.json') - assert body['bound_claims'] == {'groups': ['net-kingdom-admins']} - assert len(st(env)['calls']) == calls - - -def test_refuses_outside_attended_envelope(env): - tmp = env[0] - _, _, base = env - proc = subprocess.run([str(WRAPPER), 'prepare', '--receipt', str(tmp / 'p.json')], - capture_output=True, env=dict(base, HOME=str(tmp))) - assert proc.returncode == 1 and proc.stdout == proc.stderr == b'' - assert receipt(tmp / 'p.json')['status'] == 'attended_envelope_required' - - -def test_existing_receipt_is_never_overwritten(env): - tmp = env[0] - (tmp / 'p.json').write_text('{}') - assert run(env, 'prepare', '--receipt', str(tmp / 'p.json')) == 1 - assert (tmp / 'p.json').read_text() == '{}' - assert 'calls' not in st(env) - - -def test_bad_arguments_exit_2(env): - assert run(env, 'apply') == 2 - assert run(env, 'destroy', '--receipt', '/dev/null') == 2 diff --git a/tests/test_openbao_platform_admin_callback_prune.py b/tests/test_openbao_platform_admin_callback_prune.py index 3bf97b8..2efb379 100644 --- a/tests/test_openbao_platform_admin_callback_prune.py +++ b/tests/test_openbao_platform_admin_callback_prune.py @@ -32,7 +32,7 @@ def test_prunes_only_the_two_retired_callbacks_and_keeps_settings(): assert not set(m.RETIRED) & set(uris) assert m.KEEP in uris and 'http://localhost:8250/oidc/callback' in uris assert state['role']['token_policies'] == ['platform-admin', 'operator-custody'] - assert state['role']['bound_claims'] == DECLARED['bound_claims'] + assert state['role']['bound_claims'] == {'groups': ['net-kingdom-admins']} def test_idempotent_when_already_pruned(): diff --git a/tests/test_openbao_reboot_snapshot.py b/tests/test_openbao_reboot_snapshot.py deleted file mode 100644 index 02c7a05..0000000 --- a/tests/test_openbao_reboot_snapshot.py +++ /dev/null @@ -1,332 +0,0 @@ -"""Offline tests for scripts/openbao-reboot-snapshot.py (no OpenBao, no network). - -``bao`` and the Nextcloud upload are faked; ``age`` runs for real against a -throwaway key when installed (otherwise those cases are skipped). -""" -from __future__ import annotations - -import gzip -import importlib.util -import io -import json -import os -import shutil -import subprocess -import sys -import tempfile -import unittest -from contextlib import redirect_stderr, redirect_stdout -from datetime import UTC, datetime -from pathlib import Path -from unittest import mock - -ROOT = Path(__file__).resolve().parents[1] -SCRIPT = ROOT / "scripts" / "openbao-reboot-snapshot.py" -SPEC = importlib.util.spec_from_file_location("openbao_reboot_snapshot", SCRIPT) -assert SPEC and SPEC.loader -snap = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(snap) - -HAVE_AGE = bool(shutil.which("age") and shutil.which("age-keygen")) -CLUSTER = "fd28df5d-98ec-57dd-42ec-9b3e4f4e53bf" -SCENARIO = "RPF-WP-0015-T03-REBOOT-20261014-01" -TOKEN = "tok3nValue" -URL = f"{snap.NEXTCLOUD.HOST}/public.php/dav/filesdrop/{TOKEN}" - - -def throwaway_key() -> tuple[str, str]: - out = subprocess.run(["age-keygen"], capture_output=True, text=True, check=True).stdout - identity = next(line for line in out.splitlines() if line.startswith("AGE-SECRET-KEY-1")) - recipient = subprocess.run( - ["age-keygen", "-y"], input=identity + "\n", capture_output=True, text=True, check=True - ).stdout.strip() - return identity, recipient - - -class FakeLane(snap.Lane): - """Replaces only the bao CLI and the HTTP boundary.""" - - def __init__(self, env, *, identity, status_seq=None, upload_status=201, url=URL, payload=None): - super().__init__(env) - self.calls: list[str] = [] - self.identity = identity - self.status_seq = list(status_seq or [self.status_json(100), self.status_json(105)]) - self.upload_status = upload_status - self.url = url - self.payload = payload if payload is not None else gzip.compress(b"raft-state" * 1000) - self.uploads: list[tuple[str, bytes]] = [] - self.snapshot_path: Path | None = None - - @staticmethod - def status_json(index, sealed=False, initialized=True, cluster=CLUSTER): - return json.dumps({"initialized": initialized, "sealed": sealed, "storage_type": "raft", - "cluster_id": cluster, "raft_applied_index": index, "t": 2, "n": 3}) - - def bao(self, args, *, label): - assert self.env.get("WARDEN_ROUTING_CATALOG") and "BAO_TOKEN" not in self.env - if args[:1] == ["status"]: - self.calls.append("status") - return self.status_seq.pop(0) - if args[:4] == ["operator", "raft", "snapshot", "save"]: - self.calls.append("snapshot") - self.snapshot_path = Path(args[4]) - assert not self.snapshot_path.exists() - self.snapshot_path.write_bytes(self.payload) - return "" - if args[:2] == ["kv", "get"]: - field = args[2].removeprefix("-field=") - assert args[3] == snap.LANE_PATH - self.calls.append(f"kv:{field}") - return {"NC_WEBDAV_URL": self.url, "NC_WEBDAV_TOKEN": TOKEN, - "AGE_PRIVATE_KEY": self.identity}[field] + "\n" - raise AssertionError(f"unexpected bao call {args}") - - def upload(self, url, token, body): - self.calls.append("upload") - self.uploads.append((url, body)) - return self.upload_status - - -@unittest.skipUnless(HAVE_AGE, "age/age-keygen not installed") -class SnapshotLaneTest(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.identity, cls.recipient = throwaway_key() - - def setUp(self): - self.tmp = Path(tempfile.mkdtemp(prefix="snap-test-")) - self.home = self.tmp / "home" - self.home.mkdir(mode=0o700) - self.receipt = self.tmp / "receipt.json" - self.env = {"WARDEN_ROUTING_CATALOG": "/catalog.yaml", "BAO_ADDR": "http://127.0.0.1:18200", - "HOME": str(self.home), "PATH": os.environ.get("PATH", "/usr/bin:/bin")} - self.lanes: list[FakeLane] = [] - patcher = mock.patch.object(snap, "declared_recipient", return_value=self.recipient) - patcher.start() - self.addCleanup(patcher.stop) - self.addCleanup(shutil.rmtree, self.tmp, True) - - def factory(self, **kwargs): - def make(env): - lane = FakeLane(env, identity=kwargs.pop("identity", self.identity), **kwargs) - self.lanes.append(lane) - return lane - return make - - def run_main(self, env=None, extra=(), **kwargs): - out, err = io.StringIO(), io.StringIO() - escrow = () if "--custodian-escrow-ref" in extra else ("--custodian-escrow-ref", "founder-offline:test") - argv = ["--scenario-id", SCENARIO, "--receipt-file", str(self.receipt), *escrow, *extra] - with mock.patch.dict(os.environ, env or self.env, clear=True), redirect_stdout(out), redirect_stderr(err): - code = snap.main(argv, lane_factory=self.factory(**kwargs)) - self.assertEqual(out.getvalue() + err.getvalue(), "", "child must stay silent") - return code, json.loads(self.receipt.read_text()) - - def assert_clean(self): - self.assertEqual(list(self.home.iterdir()), [], "private temp dir must be shredded") - - def test_happy_path_order_and_receipt_validates(self): - code, receipt = self.run_main() - self.assertEqual(code, 0) - lane = self.lanes[0] - self.assertEqual(lane.calls, ["status", "snapshot", "status", "kv:NC_WEBDAV_URL", - "kv:NC_WEBDAV_TOKEN", "kv:AGE_PRIVATE_KEY", "upload"]) - self.assertEqual(self.receipt.stat().st_mode & 0o777, 0o600) - live = {"cluster_id": CLUSTER, "raft_applied_index": 105} - checked = snap.PREFLIGHT.validate_snapshot_receipt( - self.receipt, live_openbao=live, now=datetime.now(UTC), max_age_hours=24) - self.assertTrue(checked["verified"]) - self.assertEqual(receipt["raft_applied_index"], 100) - self.assertTrue(receipt["encrypted_location_ref"].startswith(f"offhost-custody:{SCENARIO}:")) - url, body = lane.uploads[0] - self.assertTrue(url.startswith(f"{URL}/openbao/openbao-raft-snapshot-{SCENARIO}-")) - self.assertTrue(body.startswith(snap.AGE_HEADER)) - self.assertNotIn(lane.payload, body) - text = self.receipt.read_text() - for value in (self.identity, TOKEN, "AGE-SECRET-KEY"): - self.assertNotIn(value, text) - self.assertFalse(lane.snapshot_path.exists()) - self.assert_clean() - - def test_upload_conflict_fails_and_cleans_up(self): - code, record = self.run_main(upload_status=412) - self.assertEqual(code, 1) - self.assertEqual(record["status"], "failed") - self.assertIn("did not create", record["error"]) - self.assertTrue(record["temp_dir_removed"]) - self.assertNotIn("receipt_version", record) - self.assertNotIn("encrypted_location_ref", record) # no location without a 201 - self.assertNotIn("upload_http_status", record) - self.assert_clean() - - def test_custodian_recipient_alone_decrypts_upload(self): - identity, recipient = throwaway_key() - code, receipt = self.run_main( - extra=("--custodian-recipient", recipient, "--custodian-escrow-ref", "founder-offline:test")) - self.assertEqual(code, 0) - self.assertEqual(receipt["recipients"][1], recipient) - self.assertEqual(receipt["custodian_escrow_ref"], "founder-offline:test") - self.assertEqual(receipt["upload_folder"], "openbao") - _, body = self.lanes[0].uploads[0] - with tempfile.TemporaryDirectory() as tmp: - key, ct = Path(tmp) / "k", Path(tmp) / "c" - key.write_bytes(identity if isinstance(identity, bytes) else identity.encode()) - ct.write_bytes(body) - out = subprocess.run(["age", "-d", "-i", str(key), str(ct)], capture_output=True, check=False) - self.assertEqual(out.returncode, 0) - self.assertEqual(out.stdout, self.lanes[0].payload) - - def test_escrow_ref_alone_suffices_and_is_cited(self): - code, receipt = self.run_main() - self.assertEqual(code, 0) - self.assertEqual(len(receipt["recipients"]), 1) - self.assertEqual(receipt["custodian_escrow_ref"], "founder-offline:test") - self.assertIn("escrow_key_read", receipt["escrow_key_read_decision"]) - - def test_preflight_rejects_receipt_without_escrow_ref(self): - code, receipt = self.run_main() - self.assertEqual(code, 0) - receipt["custodian_escrow_ref"] = None - bad = self.home / "bad.json" - bad.write_text(json.dumps(receipt)) - with self.assertRaises(snap.PREFLIGHT.PreflightError): - snap.PREFLIGHT.validate_snapshot_receipt( - bad, live_openbao={"cluster_id": CLUSTER, "raft_applied_index": 105}, - now=datetime.now(UTC), max_age_hours=24) - bad.unlink() - - def test_escrow_key_in_age_identity_file_format_is_accepted(self): - recipient = snap.declared_recipient() - identity_file = f"# created: 2026-01-01T00:00:00Z\n# public key: {recipient}\n{self.identity}" - code, receipt = self.run_main(identity=identity_file) - self.assertEqual(code, 0) - self.assertNotIn("AGE-SECRET-KEY", self.receipt.read_text()) - - def test_escrow_key_with_two_keys_or_junk_is_unusable(self): - for bad in (f"{self.identity}\n{self.identity}", f"junk\n{self.identity}"): - code, record = self.run_main(identity=bad) - self.assertEqual(code, 1) - self.assertIn("AGE_PRIVATE_KEY is unusable", record["error"]) - self.assertNotIn("upload", self.lanes[-1].calls) - self.receipt.unlink() - self.assert_clean() - - def test_custodian_recipient_requires_escrow_ref(self): - _, recipient = throwaway_key() - argv = ["--scenario-id", SCENARIO, "--receipt-file", str(self.receipt), "--custodian-recipient", recipient] - with mock.patch.dict(os.environ, self.env, clear=True): - self.assertEqual(snap.main(argv, lane_factory=self.factory()), 2) - self.assertFalse(self.receipt.exists()) - - def test_escrow_key_mismatch_never_uploads(self): - other, _ = throwaway_key() - code, record = self.run_main(identity=other) - self.assertEqual(code, 1) - self.assertIn("escrow key does not match", record["error"]) - self.assertNotIn("upload", self.lanes[0].calls) - self.assertNotIn(other, json.dumps(record)) - self.assert_clean() - - def test_decrypt_hash_mismatch_never_uploads(self): - with mock.patch.object(snap.Lane, "decrypt_digest", return_value="0" * 64): - code, record = self.run_main() - self.assertEqual(code, 1) - self.assertIn("decrypted snapshot hash", record["error"]) - self.assertNotIn("upload", self.lanes[0].calls) - self.assert_clean() - - def test_sealed_source_never_snapshots(self): - code, record = self.run_main(status_seq=[FakeLane.status_json(100, sealed=True)]) - self.assertEqual(code, 1) - self.assertEqual(self.lanes[0].calls, ["status"]) - self.assert_clean() - - def test_cluster_change_during_snapshot_fails(self): - seq = [FakeLane.status_json(100), FakeLane.status_json(105, cluster="other")] - code, record = self.run_main(status_seq=seq) - self.assertEqual(code, 1) - self.assertNotIn("kv:AGE_PRIVATE_KEY", self.lanes[0].calls) - self.assert_clean() - - def test_non_snapshot_payload_refused(self): - code, record = self.run_main(payload=b"plain text, not a raft archive") - self.assertEqual(code, 1) - self.assertIn("not a Raft snapshot", record["error"]) - self.assert_clean() - - def test_unexpected_provider_url_never_uploads(self): - code, record = self.run_main(url="https://elsewhere.example/dav") - self.assertEqual(code, 1) - self.assertNotIn("upload", self.lanes[0].calls) - self.assert_clean() - - def test_internal_error_is_value_safe(self): - with mock.patch.object(snap, "sha256_file", side_effect=ValueError(TOKEN)): - code, record = self.run_main() - self.assertEqual(record["error"], "internal_error") - self.assertNotIn(TOKEN, json.dumps(record)) - self.assert_clean() - - def test_refused_outside_contained_login(self): - for env in ({"HOME": str(self.home)}, - {**self.env, "BAO_TOKEN": "x"}, - {**self.env, "BAO_ADDR": "https://openbao.example"}): - self.receipt.unlink(missing_ok=True) - self.lanes.clear() - code, record = self.run_main(env=env) - self.assertEqual(code, 1) - self.assertEqual(record["status"], "failed") - self.assertEqual(self.lanes, [], "no bao call may happen outside containment") - - def test_private_dir_must_be_owner_only_and_outside_git(self): - loose = self.tmp / "loose" - loose.mkdir(mode=0o755) - loose.chmod(0o755) - code, record = self.run_main(extra=["--private-dir", str(loose)]) - self.assertIn("owner-only", record["error"]) - self.receipt.unlink() - code, record = self.run_main(extra=["--private-dir", str(ROOT / "scripts")]) - self.assertEqual(code, 1) - self.assertEqual(self.lanes[-1].calls if self.lanes else [], []) - - def test_existing_receipt_or_bad_scenario_refused(self): - self.receipt.write_text("keep") - with mock.patch.dict(os.environ, self.env, clear=True): - self.assertEqual(snap.main(["--scenario-id", SCENARIO, "--receipt-file", str(self.receipt)]), 2) - self.assertEqual(snap.main(["--scenario-id", "../x", "--receipt-file", str(self.tmp / "n.json")]), 2) - self.assertEqual(snap.main(["--bogus"]), 2) - self.assertEqual(self.receipt.read_text(), "keep") - - -class BoundaryTest(unittest.TestCase): - def test_declared_recipient_comes_from_offsite_lane_helper(self): - recipient = snap.declared_recipient() - self.assertRegex(recipient, snap.AGE_RECIPIENT_RE) - self.assertIn(recipient, (ROOT / "lib" / "railiance-backup-common.sh").read_text()) - - def test_upload_uses_no_overwrite_governed_request(self): - lane = snap.Lane({"PATH": "/usr/bin"}) - with mock.patch.object(snap.NEXTCLOUD, "request", return_value=(201, b"")) as request: - self.assertEqual(lane.upload(URL + "/forgejo/x.age", TOKEN, b"cipher"), 201) - url, method, body, auth, headers = request.call_args.args - self.assertEqual((method, body, auth), ("PUT", b"cipher", (TOKEN, ""))) - self.assertEqual(headers["If-None-Match"], "*") - - def test_script_is_silent_and_refuses_as_process_outside_login(self): - with tempfile.TemporaryDirectory() as tmp: - receipt = Path(tmp) / "r.json" - env = {k: v for k, v in os.environ.items() - if k not in ("WARDEN_ROUTING_CATALOG", "BAO_TOKEN", "VAULT_TOKEN", "OPENBAO_TOKEN")} - env["HOME"] = tmp - result = subprocess.run( - [sys.executable, str(SCRIPT), "--scenario-id", SCENARIO, "--receipt-file", str(receipt), "--custodian-escrow-ref", "founder-offline:test"], - capture_output=True, env=env, timeout=60) - self.assertEqual((result.stdout, result.stderr), (b"", b"")) - self.assertNotEqual(result.returncode, 0) - self.assertIn("contained attended login", json.loads(receipt.read_text())["error"]) - bad = subprocess.run([sys.executable, str(SCRIPT), "--help"], capture_output=True, timeout=60) - self.assertEqual((bad.stdout, bad.stderr, bad.returncode), (b"", b"", 2)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_openbao_revoke_initial_root_token.py b/tests/test_openbao_revoke_initial_root_token.py deleted file mode 100644 index cdcbc4a..0000000 --- a/tests/test_openbao_revoke_initial_root_token.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Initial root-token check/revoke on a fake OpenBao (NK-WP-0055-T16). No network, synthetic token.""" -import importlib.util -import io -import json -from pathlib import Path -import tempfile -import unittest -import urllib.error - -ROOT = Path(__file__).resolve().parents[1] -_spec = importlib.util.spec_from_file_location('revoke_root', ROOT / 'scripts/openbao_revoke_initial_root_token.py') -m = importlib.util.module_from_spec(_spec) -_spec.loader.exec_module(m) - -TOKEN = 's.syntheticRootToken0123' -ADDR, BG = 'http://127.0.0.1:18201', 'http://127.0.0.1:18210' -DECISION = '11111111-2222-3333-4444-555555555555' - - -class Response(io.BytesIO): - def __init__(self, status, body): - super().__init__(json.dumps(body).encode() if body is not None else b'') - self.status = status - - def __enter__(self): - return self - - def __exit__(self, *a): - self.close() - - -class FakeBao: - def __init__(self, policies=('root',), ttl=0, orphan=True, breakglass=True, revoke_code=204): - self.valid = True - self.policies, self.ttl, self.orphan = list(policies), ttl, orphan - self.breakglass, self.revoke_code = breakglass, revoke_code - self.calls = [] - - def open(self, request, timeout=None): - url, method = request.full_url, request.get_method() - self.calls.append((method, url)) - if url == BG + '/v1/sys/generate-root/attempt': - if not self.breakglass: - raise urllib.error.HTTPError(url, 405, 'disabled', {}, None) - return Response(200, {'started': False}) - assert url.startswith(ADDR) - if request.headers.get('X-vault-token') != TOKEN or not self.valid: - raise urllib.error.HTTPError(url, 403, 'denied', {}, None) - if url.endswith('/lookup-self'): - return Response(200, {'data': {'policies': self.policies, 'ttl': self.ttl, 'orphan': self.orphan, - 'expire_time': None, 'id': TOKEN}}) - if url.endswith('/revoke-self'): - if self.revoke_code == 204: - self.valid = False - return Response(self.revoke_code, None) - raise AssertionError(url) - - -class InitialRootTokenTest(unittest.TestCase): - def setUp(self): - self.dir = tempfile.TemporaryDirectory() - self.receipt = Path(self.dir.name) / 'r.json' - - def tearDown(self): - self.dir.cleanup() - - def run_mode(self, mode, bao, **kw): - return m.run(mode, ADDR, BG, TOKEN, self.receipt, kw.get('decision', DECISION if mode == 'revoke' else None), bao) - - def test_check_records_facts_without_revoking(self): - bao = FakeBao() - record = self.run_mode('check', bao) - self.assertEqual(record['status'], 'checked') - self.assertTrue(bao.valid) - self.assertNotIn(('POST', ADDR + '/v1/auth/token/revoke-self'), bao.calls) - text = self.receipt.read_text() - self.assertNotIn(TOKEN, text) - self.assertEqual(oct(self.receipt.stat().st_mode & 0o777), '0o600') - - def test_revoke_confirms_204_then_403(self): - bao = FakeBao() - record = self.run_mode('revoke', bao) - self.assertEqual(record['status'], 'revoked') - self.assertEqual(record['revoke_status'], 204) - self.assertEqual(record['after'], {'valid': False, 'lookup_status': 403}) - self.assertEqual(record['decision_id'], DECISION) - self.assertNotIn(TOKEN, self.receipt.read_text()) - - def test_revoke_refused_without_breakglass_path(self): - bao = FakeBao(breakglass=False) - record = self.run_mode('revoke', bao) - self.assertEqual(record['status'], 'refused_no_breakglass_path') - self.assertTrue(bao.valid) - - def test_revoke_refused_for_a_non_initial_root_token(self): - for kw in ({'policies': ('default', 'platform-admin')}, {'ttl': 3600}, {'orphan': False}): - bao = FakeBao(**kw) - self.receipt.unlink(missing_ok=True) - record = self.run_mode('revoke', bao) - self.assertEqual(record['status'], 'refused_not_initial_root_token') - self.assertTrue(bao.valid) - - def test_already_invalid_token_is_reported(self): - bao = FakeBao() - bao.valid = False - self.assertEqual(self.run_mode('check', bao)['status'], 'token_already_invalid') - - def test_unconfirmed_revoke_is_not_reported_as_revoked(self): - self.assertEqual(self.run_mode('revoke', FakeBao(revoke_code=500))['status'], 'revoke_unconfirmed') - - def test_refusals_before_any_call(self): - bao = FakeBao() - cases = [ - lambda: m.run('revoke', ADDR, BG, TOKEN, self.receipt, None, bao), - lambda: m.run('revoke', ADDR, BG, TOKEN, self.receipt, 'not-a-decision', bao), - lambda: m.run('check', 'http://10.0.0.1:8200', BG, TOKEN, self.receipt, None, bao), - lambda: m.run('check', 'https://127.0.0.1:8200', BG, TOKEN, self.receipt, None, bao), - lambda: m.run('check', ADDR, BG, 'bad token\n', self.receipt, None, bao), - ] - for case in cases: - with self.assertRaises(m.Refused): - case() - self.receipt.write_text('{}') - with self.assertRaises(m.Refused): - m.run('check', ADDR, BG, TOKEN, self.receipt, None, bao) - self.assertEqual(bao.calls, []) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_openbao_ui_role_select.py b/tests/test_openbao_ui_role_select.py deleted file mode 100644 index b9c34cf..0000000 --- a/tests/test_openbao_ui_role_select.py +++ /dev/null @@ -1,95 +0,0 @@ -"""NK-WP-0051-T06: the OpenBao login page can choose the workload role. - -The main button stays role platform-admin. Workload roles come only from the -presets allow-list. The script runs in node against a minimal DOM stub. -""" - -from __future__ import annotations - -import json -import shutil -import subprocess -import unittest -from pathlib import Path - -REPO_DIR = Path(__file__).resolve().parents[1] -OVERLAY = REPO_DIR / "helm/openbao-ui-overlay" -WORKLOAD_ROLE = "reuse-surface-hub-writer-workload-kv-read" - -HARNESS = r""" -const fs = require("fs"); -const [script, presetsPath, clickId, selectValue] = process.argv.slice(1); -const presets = JSON.parse(fs.readFileSync(presetsPath, "utf8")); -presets.workloadRoles = (presets.workloadRoles || []).concat([{ role: "Bad Role!" }]); -const els = {}; -function el(id) { - const node = { id, hidden: false, disabled: false, textContent: "", value: "", - children: [], listeners: {}, classList: { add() {} }, - appendChild(c) { this.children.push(c); if (!this.value) this.value = c.value; }, - addEventListener(t, f) { this.listeners[t] = f; } }; - els[id] = node; return node; -} -["login-title","login-banner","login-submit","login-error","login-workload", - "login-workload-summary","login-workload-hint","login-workload-role","login-workload-submit"].forEach(el); -els["login-workload"].hidden = true; -const posted = []; -global.document = { getElementById: (id) => els[id] || null, - createElement: () => ({ value: "", textContent: "" }) }; -global.window = { location: { origin: "http://127.0.0.1:18200", assign() {} } }; -global.fetch = async (url, opts) => { - if (url.endsWith("presets.json")) return { ok: true, json: async () => presets }; - posted.push({ url, body: JSON.parse(opts.body) }); - return { ok: true, json: async () => ({ data: { auth_url: "https://kc.example/authorize" } }) }; -}; -eval(fs.readFileSync(script, "utf8")); -setTimeout(async () => { - if (selectValue) els["login-workload-role"].value = selectValue; - await els[clickId].listeners.click(); - console.log(JSON.stringify({ posted, - options: els["login-workload-role"].children.map((c) => c.value), - workloadHidden: els["login-workload"].hidden, - error: els["login-error"].textContent })); -}, 20); -""" - - -@unittest.skipUnless(shutil.which("node"), "node is not installed") -class OpenBaoUiRoleSelectTests(unittest.TestCase): - def run_page(self, click_id: str, select_value: str = "") -> dict: - proc = subprocess.run( - ["node", "-e", HARNESS, str(OVERLAY / "login.js"), str(OVERLAY / "presets.json"), - click_id, select_value], - capture_output=True, text=True, check=True, timeout=30, - ) - return json.loads(proc.stdout.strip().splitlines()[-1]) - - def test_presets_keep_platform_admin_and_list_workload_role(self) -> None: - presets = json.loads((OVERLAY / "presets.json").read_text(encoding="utf-8")) - self.assertEqual(presets["role"], "platform-admin") - self.assertEqual(presets["mount"], "netkingdom") - self.assertIn(WORKLOAD_ROLE, [entry["role"] for entry in presets["workloadRoles"]]) - - def test_main_button_stays_platform_admin(self) -> None: - result = self.run_page("login-submit") - self.assertEqual(len(result["posted"]), 1) - self.assertEqual(result["posted"][0]["url"], "/v1/auth/netkingdom/oidc/auth_url") - self.assertEqual(result["posted"][0]["body"]["role"], "platform-admin") - self.assertEqual( - result["posted"][0]["body"]["redirect_uri"], - "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback", - ) - - def test_workload_button_uses_selected_listed_role(self) -> None: - result = self.run_page("login-workload-submit", WORKLOAD_ROLE) - self.assertFalse(result["workloadHidden"]) - self.assertEqual(result["options"], [WORKLOAD_ROLE]) - self.assertEqual(result["posted"][0]["body"]["role"], WORKLOAD_ROLE) - - def test_unlisted_role_is_refused(self) -> None: - result = self.run_page("login-workload-submit", "platform-admin") - self.assertEqual(result["posted"], []) - self.assertIn("listed workload role", result["error"]) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_operator_matrix_lane.py b/tests/test_operator_matrix_lane.py deleted file mode 100644 index 63f5423..0000000 --- a/tests/test_operator_matrix_lane.py +++ /dev/null @@ -1,179 +0,0 @@ -"""RPF-WP-0035-T03: workload-kv-operator-matrix lane (CCR-2026-0039).""" -from __future__ import annotations - -import copy -import importlib.util -import json -import os -import stat -import sys -import tempfile -import unittest -from pathlib import Path - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -CCR_PATH = ROOT / "credential-change-requests/CCR-2026-0039-fluid-telegram-hall-of-helix-operator-matrix.yaml" -CAMPAIGN = "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix" - - -def load(name: str, rel: str): - spec = importlib.util.spec_from_file_location(name, ROOT / rel) - module = importlib.util.module_from_spec(spec) - sys.modules[name] = module - spec.loader.exec_module(module) - return module - - -lane = load("operator_matrix_lane", "scripts/operator_matrix_lane.py") -cc = load("credential_change_cli_matrix", "scripts/credential-change.py") - - -def ccr() -> dict: - return yaml.safe_load(CCR_PATH.read_text()) - - -# Stateful fake: policy/role writes are stored and read back; every call is logged. -FAKE_BAO = r'''#!/usr/bin/env python3 -import json, os, sys -path = os.environ["FAKE_BAO_STATE"] -state = json.load(open(path)) -open(os.environ["FAKE_BAO_LOG"], "a").write(json.dumps(sys.argv[1:]) + "\n") -args = [a for a in sys.argv[1:] if a != "-format=json"] -if args[:2] == ["auth", "list"]: - print(json.dumps({k: {"type": v} for k, v in state["mounts"].items()})) -elif args[:2] == ["policy", "read"]: - if args[2] not in state["policies"]: - sys.exit(2) - print(state["policies"][args[2]]) -elif args[:2] == ["policy", "write"]: - state["policies"][args[2]] = open(args[3]).read() -elif args[0] == "write": - state["roles"][args[1]] = json.load(open(args[2][1:])) -elif args[0] == "read": - print(json.dumps({"data": state["roles"][args[1]]})) -json.dump(state, open(path, "w")) -''' - -LIVE_BOUNDARY = 'path "platform/data/workloads/forgejo/forgejo-admin" {\n capabilities = ["deny"]\n}\n' - - -class OperatorMatrixLaneTests(unittest.TestCase): - def test_ccr_validates_and_records_the_delegated_apply(self): - _ccr, errors, _w = cc.validate_ccr(CCR_PATH) - self.assertEqual(errors, []) - self.assertEqual(_ccr["status"], "applied") - self.assertEqual(cc.applier_readiness_blockers(_ccr), []) - self.assertTrue(any(e["kind"] == "delegated_metadata_apply" and e["result"] == "passed" - for e in _ccr["verification"]["evidence"])) - - def test_unconfirmed_binding_blocks_the_applier(self): - c = ccr() - c["openbao"]["auth"]["bound_claims_confirmed"] = False - self.assertIn("applier requires confirmed OpenBao auth binding", - cc.applier_readiness_blockers(c)) - - def test_policy_is_the_exact_matrix_and_nothing_else(self): - policy = (ROOT / ccr()["openbao"]["policy_file"]).read_text() - self.assertNotIn("metadata", policy.split("\n", 1)[1]) - self.assertNotIn("*", policy) - for word in ("delete", "list", "patch", "sudo"): - self.assertNotIn(f'"{word}"', policy) - self.assertIn(f'path "{CAMPAIGN}/redaction-salt" {{\n capabilities = ["create", "read"]', policy) - self.assertIn(f'path "{CAMPAIGN}/operator-app" {{\n capabilities = ["read"]', policy) - - def test_repo_boundary_carries_all_eight_agent_denies(self): - text = (ROOT / lane.BOUNDARY_FILE).read_text() - self.assertEqual(lane.boundary_presence(ccr(), text), (8, 8)) - - def test_validator_rejects_widening(self): - cases = [ - ("wildcard entry", lambda c: c["openbao"]["matrix"][0].update(entry="*")), - ("unknown capability", lambda c: c["openbao"]["matrix"][0]["capabilities"].append("delete")), - ("no read", lambda c: c["openbao"]["matrix"][3].update(capabilities=["create"])), - ("duplicate entry", lambda c: c["openbao"]["matrix"].append(copy.deepcopy(c["openbao"]["matrix"][0]))), - ("campaign wildcard", lambda c: c["openbao"].update(campaign_path="platform/workloads/coulomb/fluid-telegram/*")), - ("other auth mount", lambda c: c["openbao"]["auth"].update(mount="kubernetes")), - ] - for label, mutate in cases: - with self.subTest(label): - c = ccr() - mutate(c) - self.assertNotEqual(lane.validate(c, ROOT), [], label) - - def test_validator_rejects_bad_role_file(self): - good = json.loads((ROOT / ccr()["openbao"]["auth"]["role_file"]).read_text()) - cases = [ - ("public callback", {"allowed_redirect_uris": ["https://bao.coulomb.social/ui/vault/auth/netkingdom/oidc/callback"]}), - ("no aal2", {"bound_claims": {"groups": ["fluid-telegram-operators"]}}), - ("two groups", {"bound_claims": {"groups": ["a", "b"], "/assurance/level": "aal2"}}), - ("default policy", {"token_no_default_policy": False}), - ("long ttl", {"token_ttl": "60m"}), - ("long max", {"token_explicit_max_ttl": "2h"}), - ("extra key", {"token_period": "15m"}), - ] - for label, change in cases: - with self.subTest(label): - with tempfile.TemporaryDirectory(dir=ROOT / "openbao/auth") as tmp: - role_file = Path(tmp) / "role.json" - role_file.write_text(json.dumps({**good, **change})) - c = ccr() - c["openbao"]["auth"]["role_file"] = str(role_file.relative_to(ROOT)) - self.assertNotEqual(lane.validate(c, ROOT), [], label) - - def _apply(self, boundary: str, mounts: dict | None = None): - tmp = Path(tempfile.mkdtemp()) - state = {"mounts": mounts if mounts is not None else {"netkingdom/": "oidc"}, - "policies": {lane.BOUNDARY_POLICY: boundary}, "roles": {}} - (tmp / "state.json").write_text(json.dumps(state)) - bao = tmp / "bao" - bao.write_text(FAKE_BAO) - bao.chmod(bao.stat().st_mode | stat.S_IEXEC) - os.environ["FAKE_BAO_STATE"] = str(tmp / "state.json") - os.environ["FAKE_BAO_LOG"] = str(tmp / "log") - (tmp / "log").write_text("") - error = None - try: - lane.run_apply(ccr(), ROOT, str(bao), quiet=True) - except lane.BaoError as exc: - error = exc - state = json.loads((tmp / "state.json").read_text()) - writes = [json.loads(l) for l in (tmp / "log").read_text().splitlines() - if json.loads(l)[0] == "write" or json.loads(l)[:2] == ["policy", "write"]] - return error, state, writes - - def test_apply_appends_denies_preserving_live_boundary_and_reads_back(self): - error, state, writes = self._apply(LIVE_BOUNDARY) - self.assertIsNone(error) - boundary = state["policies"][lane.BOUNDARY_POLICY] - self.assertTrue(boundary.startswith(LIVE_BOUNDARY.rstrip("\n"))) - self.assertEqual(lane.boundary_presence(ccr(), boundary), (8, 8)) - self.assertEqual([w[:3] for w in writes][:2], [ - ["policy", "write", lane.BOUNDARY_POLICY], - ["policy", "write", "workload-kv-fluid-telegram-hall-of-helix-operator"], - ]) - self.assertIn("auth/netkingdom/role/fluid-telegram-hall-of-helix-workload-kv", state["roles"]) - - def test_apply_skips_boundary_when_denies_already_live(self): - overlay = (ROOT / ccr()["openbao"]["boundary_overlay_file"]).read_text() - error, state, writes = self._apply(LIVE_BOUNDARY + overlay) - self.assertIsNone(error) - self.assertNotIn(["policy", "write", lane.BOUNDARY_POLICY], [w[:3] for w in writes]) - - def test_apply_refuses_partial_boundary_before_any_write(self): - partial = LIVE_BOUNDARY + lane.boundary_stanzas(ccr())[0] + "\n" - error, _state, writes = self._apply(partial) - self.assertIsNotNone(error) - self.assertEqual(writes, []) - - def test_apply_refuses_missing_or_foreign_oidc_mount_before_any_write(self): - for mounts in ({}, {"netkingdom/": "jwt"}): - with self.subTest(mounts=mounts): - error, _state, writes = self._apply(LIVE_BOUNDARY, mounts) - self.assertIsNotNone(error) - self.assertEqual(writes, []) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_operators_entry_retire.py b/tests/test_operators_entry_retire.py deleted file mode 100644 index 34aa45a..0000000 --- a/tests/test_operators_entry_retire.py +++ /dev/null @@ -1,130 +0,0 @@ -"""Offline tests for the operators/nextcloud retirement helper against a fake ``bao``.""" -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import textwrap -import unittest - -ROOT = Path(__file__).resolve().parents[1] -SCRIPT = ROOT / 'scripts/operators_entry_retire.py' -CONFIRM = 'RETIRE operators/nextcloud' -ONE_SHOT = 'hvs.oneshot-' + 'K3l4' * 6 - -# KV calls are refused unless they carry the one-shot token and its policy exists. -FAKE_BAO = textwrap.dedent(f'''\ - #!/usr/bin/env python3 - import json, os, sys - sf = os.environ['FAKE_BAO_STATE'] - st = json.load(open(sf)) - a = sys.argv[1:] - tok = os.environ.get('BAO_TOKEN', 'caller') - st['calls'].append({{'args': a, 'token': tok}}) - def save(): json.dump(st, open(sf, 'w')) - def out(obj): save(); print(json.dumps(obj)); sys.exit(0) - def deny(): save(); sys.stderr.write('permission denied\\n'); sys.exit(2) - if a[:2] == ['token', 'lookup']: out({{'data': {{'policies': st['caller_policies']}}}}) - if a[:2] == ['policy', 'write']: st['policy'] = open(a[3]).read(); save(); sys.exit(0) - if a[:2] == ['policy', 'delete']: st.pop('policy', None); save(); sys.exit(0) - if a[:2] == ['token', 'create']: - if st.get('fail_create'): save(); sys.exit(2) - out({{'auth': {{'client_token': '{ONE_SHOT}'}}}}) - if a[:2] == ['token', 'revoke']: st['revoked'] = tok; save(); sys.exit(0) - if tok != '{ONE_SHOT}' or 'policy' not in st: deny() - if a[0] == 'read': - rest = a[-1].split('/', 2)[2] - if rest not in st['kv']: save(); sys.stderr.write('No value found\\n'); sys.exit(2) - n = st['kv'][rest] - out({{'data': {{'current_version': n, 'versions': {{str(i + 1): {{}} for i in range(n)}}}}}}) - if a[0] == 'delete': - if st.get('fail_delete'): deny() - st['kv'].pop(a[1].split('/', 2)[2], None); save(); sys.exit(0) - save(); sys.exit(9) -''') - - -class Retire(unittest.TestCase): - def setUp(self): - self.tmp = Path(tempfile.mkdtemp()) - (self.tmp / 'bin').mkdir() - (self.tmp / 'bin/bao').write_text(FAKE_BAO) - (self.tmp / 'bin/bao').chmod(0o755) - self.home = self.tmp / '.warden-attended-login' / 'session' - self.home.mkdir(parents=True) - self.state = {'calls': [], 'caller_policies': ['default', 'platform-admin'], - 'kv': {'nextcloud': 2, 'nextcloud/backup': 3}} - - def run_helper(self, *extra, confirm=CONFIRM): - (self.tmp / 'state.json').write_text(json.dumps(self.state)) - receipt = self.tmp / f'r{len(list(self.tmp.glob("r*.json")))}.json' - env = {k: v for k, v in os.environ.items() if k not in ('BAO_TOKEN', 'VAULT_TOKEN')} - env.update(PATH=f'{self.tmp / "bin"}:{env["PATH"]}', HOME=str(self.home), - FAKE_BAO_STATE=str(self.tmp / 'state.json')) - proc = subprocess.run([sys.executable, str(SCRIPT), '--confirm', confirm, '--receipt', str(receipt), *extra], - env=env, capture_output=True, text=True) - self.assertEqual((proc.stdout, proc.stderr), ('', '')) - self.state = json.loads((self.tmp / 'state.json').read_text()) - text = receipt.read_text() - self.assertNotIn(ONE_SHOT, text) - return proc.returncode, json.loads(text) - - def assert_cleaned_up(self, receipt): - self.assertNotIn('policy', self.state) - self.assertTrue(receipt['policy_deleted']) - self.assertEqual(self.state.get('revoked'), ONE_SHOT) - - def test_retires_entry_and_leaves_backup_untouched(self): - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (0, 'retired')) - self.assertNotIn('nextcloud', self.state['kv']) - self.assertEqual(self.state['kv']['nextcloud/backup'], 3) - self.assertEqual(receipt['entry_before'], {'current_version': 2, 'versions': 2}) - self.assert_cleaned_up(receipt) - self.assertFalse(any(c['args'][0] == 'read' and '/data/' in c['args'][-1] for c in self.state['calls'])) - - def test_one_shot_policy_is_exact(self): - self.run_helper('--dry-run') - calls = [c for c in self.state['calls'] if c['args'][:2] == ['token', 'create']] - self.assertEqual(len(calls), 1) - args = calls[0]['args'] - for flag in ('-orphan', '-policy=operators-retire-nextcloud', '-no-default-policy', '-ttl=60s'): - self.assertIn(flag, args) - - def test_dry_run_deletes_nothing_and_cleans_up(self): - code, receipt = self.run_helper('--dry-run') - self.assertEqual((code, receipt['outcome']), (0, 'dry_run_would_retire')) - self.assertIn('nextcloud', self.state['kv']) - self.assert_cleaned_up(receipt) - - def test_rerun_reports_already_absent(self): - self.state['kv'].pop('nextcloud') - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (0, 'already_absent')) - self.assert_cleaned_up(receipt) - - def test_missing_sibling_refuses(self): - self.state['kv'].pop('nextcloud/backup') - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (2, 'sibling_absent_refusing_to_guess_layout')) - self.assertIn('nextcloud', self.state['kv']) - self.assert_cleaned_up(receipt) - - def test_delete_failure_still_cleans_up(self): - self.state['fail_delete'] = True - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (4, 'delete_failed')) - self.assert_cleaned_up(receipt) - - def test_refuses_without_confirmation_or_platform_admin(self): - code, receipt = self.run_helper(confirm='RETIRE') - self.assertEqual((code, receipt['outcome']), (2, 'exact_confirmation_required')) - self.state['caller_policies'] = ['operator-custody'] - code, receipt = self.run_helper() - self.assertEqual((code, receipt['outcome']), (2, 'attended_platform_admin_required')) - self.assertFalse(any(c['args'][:2] == ['policy', 'write'] for c in self.state['calls'])) - - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_ops_mason_builder_rbac.py b/tests/test_ops_mason_builder_rbac.py deleted file mode 100644 index ab7e22b..0000000 --- a/tests/test_ops_mason_builder_rbac.py +++ /dev/null @@ -1,101 +0,0 @@ -"""Offline guard for the ops-mason builder identity (MASON-IN-0006 choice (b)). - -Parses the GitOps manifests and asserts the founder's 2026-10-22 boundary: -the five granted rules only, no forbidden resources, no escalate/bind/impersonate, -no wildcards, a User subject with no ServiceAccount, and a manual, pinned Application. -""" -from pathlib import Path -import re - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -ADDON = ROOT / "argocd/platform-addons/ops-mason-builder" -APP = ROOT / "argocd/railiance01/applications/ops-mason-builder.application.yaml" - -WRITE_VERBS = ["create", "get", "list", "watch", "update", "patch", "delete"] -READ_VERBS = ["get", "list", "watch"] -FORBIDDEN_RESOURCES = { - "secrets", "pods", "serviceaccounts/token", "configmaps", "roles", - "rolebindings", "clusterroles", "clusterrolebindings", "nodes", - "nodes/proxy", "pods/exec", "pods/log", -} -FORBIDDEN_VERBS = {"escalate", "bind", "impersonate"} -EXPECTED_RULES = [ - {"apiGroups": [""], "resources": ["namespaces"], "verbs": WRITE_VERBS}, - {"apiGroups": [""], "resources": ["serviceaccounts"], "verbs": WRITE_VERBS}, - {"apiGroups": ["networking.k8s.io"], "resources": ["networkpolicies"], - "verbs": WRITE_VERBS}, - {"apiGroups": [""], "resources": ["services"], "verbs": READ_VERBS}, - {"apiGroups": ["authorization.k8s.io"], - "resources": ["selfsubjectaccessreviews", "selfsubjectrulesreviews"], - "verbs": ["create"]}, -] - - -def _docs(): - kust = yaml.safe_load((ADDON / "kustomization.yaml").read_text()) - docs = [] - for res in kust["resources"]: - docs.extend(d for d in yaml.safe_load_all((ADDON / res).read_text()) if d) - return docs - - -def _one(kind): - found = [d for d in _docs() if d["kind"] == kind] - assert len(found) == 1, kind - return found[0] - - -def test_only_expected_kinds_no_secret(): - kinds = sorted(d["kind"] for d in _docs()) - assert kinds == ["ClusterRole", "ClusterRoleBinding"] - for path in ADDON.iterdir(): - if path.is_file(): - assert "kind: Secret" not in path.read_text(), path - - -def test_cluster_role_exact_rules_no_wildcards(): - role = _one("ClusterRole") - assert role["metadata"]["name"] == "ops-mason-builder" - assert "aggregationRule" not in role - assert role["rules"] == EXPECTED_RULES - granted = set() - for rule in role["rules"]: - values = [*rule["verbs"], *rule.get("apiGroups", []), *rule.get("resources", [])] - assert not any("*" in v for v in values), rule - assert FORBIDDEN_VERBS.isdisjoint(rule["verbs"]), rule - for resource in rule["resources"]: - assert resource not in FORBIDDEN_RESOURCES, resource - granted.add(resource) - assert granted == { - "namespaces", "serviceaccounts", "networkpolicies", "services", - "selfsubjectaccessreviews", "selfsubjectrulesreviews", - } - by_resource = {r["resources"][0]: r["verbs"] for r in role["rules"] if len(r["resources"]) == 1} - assert by_resource["namespaces"] == WRITE_VERBS - assert by_resource["serviceaccounts"] == WRITE_VERBS - assert by_resource["networkpolicies"] == WRITE_VERBS - assert by_resource["services"] == READ_VERBS - review = next(r for r in role["rules"] if "selfsubjectaccessreviews" in r["resources"]) - assert review["verbs"] == ["create"] - assert review["resources"] == ["selfsubjectaccessreviews", "selfsubjectrulesreviews"] - - -def test_binding_targets_only_the_user(): - binding = _one("ClusterRoleBinding") - assert binding["roleRef"]["name"] == "ops-mason-builder" - assert binding["roleRef"]["kind"] == "ClusterRole" - assert binding["subjects"] == [{"kind": "User", "name": "ops-mason-builder", - "apiGroup": "rbac.authorization.k8s.io"}] - assert all(s["kind"] != "ServiceAccount" for s in binding["subjects"]) - - -def test_application_manual_and_pinned(): - app = yaml.safe_load(APP.read_text()) - spec = app["spec"] - assert spec["project"] == "railiance-platform-addons" - assert spec["source"]["path"] == "argocd/platform-addons/ops-mason-builder" - assert re.fullmatch(r"[0-9a-f]{40}", spec["source"]["targetRevision"]) - assert "automated" not in spec.get("syncPolicy", {}) - assert "finalizers" not in app["metadata"] diff --git a/tests/test_playwright_consumer.py b/tests/test_playwright_consumer.py deleted file mode 100644 index a157b71..0000000 --- a/tests/test_playwright_consumer.py +++ /dev/null @@ -1,173 +0,0 @@ -"""Credential delivery and evidence checks; synthetic tokens are never login proof.""" -import base64 -import importlib.util -import json -import os -from pathlib import Path -import subprocess -import sys -import tempfile -import time -import unittest -from unittest.mock import patch - -ROOT = Path(__file__).resolve().parents[1] -SCRIPT = ROOT / "scripts/playwright_consumer.py" -spec = importlib.util.spec_from_file_location("playwright_consumer", SCRIPT) -consumer = importlib.util.module_from_spec(spec) -spec.loader.exec_module(consumer) - - -class ConsumerTests(unittest.TestCase): - def setUp(self): - self.temp = tempfile.TemporaryDirectory(dir=os.environ.get("PLAYWRIGHT_TEST_TMPDIR", "/tmp")) - self.addCleanup(self.temp.cleanup) - self.directory = Path(self.temp.name) - self.token = self.directory / "token.json" - self.config = self.directory / "kubeconfig.json" - self.claims = dict(iss=consumer.ISSUER, aud=consumer.AUDIENCE, - sub="synthetic", principal_type="human", scope=consumer.SCOPE, - iat=int(time.time()), exp=int(time.time()) + 900) - self.cluster = consumer.configuration(str(self.token))["clusters"][0]["cluster"] - - def write_token(self): - part = base64.urlsafe_b64encode(json.dumps(self.claims).encode()).decode().rstrip("=") - value = "synthetic." + part + ".not-a-real-signature" - self.token.write_text(json.dumps(dict(access_token=value, token_type="Bearer"))) - self.token.chmod(0o600) - return value - - def invoke(self, cluster=None): - env = dict(os.environ, KUBERNETES_EXEC_INFO=json.dumps({ - "apiVersion": consumer.API, "kind": "ExecCredential", - "spec": {"cluster": self.cluster if cluster is None else cluster}, - })) - return subprocess.run([sys.executable, str(SCRIPT), "credential", "--token-file", - str(self.token)], env=env, capture_output=True, text=True) - - def test_exec_delivers_only_expected_credential_and_expiry(self): - token = self.write_token() - result = self.invoke() - self.assertEqual(result.returncode, 0, result.stderr) - value = json.loads(result.stdout) - self.assertEqual(value["status"]["token"], token) - self.assertTrue(value["status"]["expirationTimestamp"].endswith("Z")) - self.assertEqual(result.stderr, "") - - def test_registered_coding_agent_supported(self): - self.claims.update(principal_type="service", roles=["coding-agent"]) - self.write_token() - self.assertEqual(self.invoke().returncode, 0) - - def test_wrong_destination_refused_before_token_read(self): - variants = [dict(server="https://example.org"), {"proxy-url": "http://example.org"}, - {"insecure-skip-tls-verify": True}, {"tls-server-name": "other"}, - {"certificate-authority-data": "ZmFrZQ=="}] - for change in variants: - with self.subTest(change=change), patch.object(consumer, "read_token") as read: - info = dict(apiVersion=consumer.API, kind="ExecCredential", - spec={"cluster": dict(self.cluster, **change)}) - with patch.dict(os.environ, KUBERNETES_EXEC_INFO=json.dumps(info)): - with self.assertRaises(ValueError): - consumer.credential(str(self.token)) - read.assert_not_called() - - def test_wrong_claims_fail_without_leaking_token(self): - original = self.claims.copy() - variants = [{"iss": "https://other.example"}, {"aud": "openbao"}, - {"aud": [consumer.AUDIENCE, "openbao"]}, {"scope": "openid"}, - {"principal_type": "service", "roles": []}, {"sub": ""}, - {"exp": int(time.time()) - 5}, {"exp": True}, - {"iat": int(time.time()) + 100}, {"exp": int(time.time()) + 7200}] - for change in variants: - with self.subTest(change=change): - self.claims = dict(original, **change) - token = self.write_token() - result = self.invoke() - self.assertNotEqual(result.returncode, 0) - self.assertEqual(result.stdout, "") - self.assertNotIn(token, result.stderr) - - def test_file_permissions_symlinks_and_hardlinks_refused(self): - self.write_token() - self.token.chmod(0o644) - self.assertNotEqual(self.invoke().returncode, 0) - self.token.chmod(0o600) - other = self.directory / "other.json" - os.link(self.token, other) - self.assertNotEqual(self.invoke().returncode, 0) - self.token.unlink() - self.token.symlink_to(other) - self.assertNotEqual(self.invoke().returncode, 0) - - def test_nonprivate_parent_and_git_worktree_refused(self): - self.directory.chmod(0o755) - with self.assertRaises(ValueError): - consumer.configuration(str(self.token)) - self.directory.chmod(0o700) - (self.directory / ".git").write_text("gitdir: synthetic") - with self.assertRaises(ValueError): - consumer.configuration(str(self.token)) - - def test_setup_no_credentials_no_overwrite_and_kubectl_parses(self): - consumer.setup(str(self.token), str(self.config)) - config = json.loads(self.config.read_text()) - self.assertEqual(self.config.stat().st_mode & 0o777, 0o600) - self.assertEqual(set(config["users"][0]["user"]), {"exec"}) - with self.assertRaises(FileExistsError): - consumer.setup(str(self.token), str(self.config)) - result = subprocess.run(["kubectl", "--kubeconfig", str(self.config), - "config", "view", "-o", "json"], capture_output=True, text=True) - self.assertEqual(result.returncode, 0, result.stderr) - self.assertEqual(json.loads(result.stdout)["current-context"], "playwright") - - def test_verify_rejects_impersonation_configuration_without_api_call(self): - config = consumer.configuration(str(self.token)) - config["users"][0]["user"]["as"] = "keycape:human:fake" - self.config.write_text(json.dumps(config)) - with patch.object(consumer.subprocess, "run") as run: - with self.assertRaises(ValueError): - consumer.verify(str(self.config)) - run.assert_not_called() - - def test_verify_requires_real_identity_and_all_permission_results(self): - self.config.write_text(json.dumps(consumer.configuration(str(self.token)))) - calls = [] - - def api(argv, **kwargs): - calls.append(argv) - body = json.loads(kwargs["input"]) - if body["kind"] == "SelfSubjectReview": - data = {"status": {"userInfo": {"username": "keycape:human:synthetic", - "groups": ["system:authenticated", "playwright-consumers"]}}} - else: - data = {"status": {"allowed": len(calls) <= 4}} - return subprocess.CompletedProcess(argv, 0, json.dumps(data), "") - - with patch.object(consumer.subprocess, "run", side_effect=api): - receipt = consumer.verify(str(self.config)) - self.assertEqual(len(receipt["checks_passed"]), 9) - self.assertFalse(receipt["impersonated"]) - self.assertTrue(all("--kubeconfig" in argv and "--as" not in argv for argv in calls)) - - with patch.object(consumer.subprocess, "run", return_value=subprocess.CompletedProcess( - [], 0, json.dumps({"status": {"userInfo": {"username": "system:admin", - "groups": ["system:masters"]}}}), "")): - with self.assertRaises(ValueError): - consumer.verify(str(self.config)) - - calls.clear() - - def excessive_permission(argv, **kwargs): - result = api(argv, **kwargs) - if len(calls) > 1: - result.stdout = json.dumps({"status": {"allowed": True}}) - return result - - with patch.object(consumer.subprocess, "run", side_effect=excessive_permission): - with self.assertRaisesRegex(ValueError, "secrets-denied"): - consumer.verify(str(self.config)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_primary_application_archive.py b/tests/test_primary_application_archive.py deleted file mode 100644 index 3702adc..0000000 --- a/tests/test_primary_application_archive.py +++ /dev/null @@ -1,204 +0,0 @@ -"""Immutable archive transfer with a synthetic provider; no live credentials.""" - -import hashlib -import importlib.util -import io -from pathlib import Path - -import pytest - - -@pytest.fixture -def archive(): - spec = importlib.util.spec_from_file_location( - "primary_application_archive", - Path(__file__).resolve().parents[1] / "scripts/primary_application_archive.py", - ) - module = importlib.util.module_from_spec(spec) - spec.loader.exec_module(module) - return module - - -class NotFound(Exception): - response = {"ResponseMetadata": {"HTTPStatusCode": 404}} - - -class Provider: - def __init__(self, data, failure=None, exists=False): - self.data, self.failure, self.exists = data, failure, exists - self.calls = [] - self.version = "synthetic-version-1" - - def head_object(self, **kwargs): - self.calls.append(("head", kwargs)) - if self.failure == "permission": - raise PermissionError("synthetic provider failure") - if not self.exists: - raise NotFound() - return { - "ContentLength": len(self.data), - "VersionId": self.version, - "Metadata": {"sha256": hashlib.sha256(self.data).hexdigest()}, - } - - def create_multipart_upload(self, **kwargs): - self.calls.append(("start", kwargs)) - return {"UploadId": "own-unfinished-upload"} - - def upload_part(self, **kwargs): - self.calls.append(("part", kwargs)) - if self.failure == "part": - raise TimeoutError("synthetic timeout") - assert kwargs["Body"] == self.data - return {"ETag": "synthetic-part-etag"} - - def complete_multipart_upload(self, **kwargs): - self.calls.append(("complete", kwargs)) - assert kwargs["IfNoneMatch"] == "*" - self.exists = True - if self.failure == "lost-response": - raise TimeoutError("synthetic lost acknowledgement") - return {"VersionId": None if self.failure == "unversioned" else self.version} - - def abort_multipart_upload(self, **kwargs): - self.calls.append(("abort", kwargs)) - assert kwargs["UploadId"] == "own-unfinished-upload" - - def get_object(self, **kwargs): - self.calls.append(("get", kwargs)) - assert kwargs["VersionId"] == self.version - data = b"X" * len(self.data) if self.failure == "corrupt" else self.data - if self.failure == "oversize": - data += b"extra" - return { - "ContentLength": len(self.data), - "VersionId": "other-version" if self.failure == "wrong-version" else self.version, - "Body": io.BytesIO(data), - } - - -def fixture(archive, tmp_path, failure=None, exists=False): - tmp_path.chmod(0o700) - source = tmp_path / "packet.tar.age" - source.write_bytes(b"age-encryption.org/v1\nsynthetic-encrypted-pair") - source.chmod(0o600) - receipt = {"object_key": archive.key_for("coulomb-social", "2026-10-07T20:00:00Z")} - return source, tmp_path / "fetched.age", receipt, Provider(source.read_bytes(), failure, exists) - - -def test_conditional_create_and_version_pinned_readback(archive, tmp_path): - source, output, receipt, client = fixture(archive, tmp_path) - saved = [] - result = archive.transfer( - client, "coulomb-social", source, output, receipt, lambda: saved.append(dict(receipt)) - ) - assert result["status"] == "offsite_fetched_verified" - assert result["remote_retrieval_verified"] and result["immutable_create"] - assert result["version_id"] == client.version - assert output.read_bytes() == source.read_bytes() - assert output.stat().st_mode & 0o777 == 0o600 - assert [call[0] for call in client.calls] == ["head", "start", "part", "complete", "get"] - assert not saved[0]["remote_retrieval_verified"] - assert saved[-1]["remote_retrieval_verified"] - - -@pytest.mark.parametrize( - "failure", - ["part", "lost-response", "unversioned", "corrupt", "oversize", "wrong-version", "permission"], -) -def test_failure_never_declares_retrieval_or_deletes_retained_objects(archive, tmp_path, failure): - source, output, receipt, client = fixture(archive, tmp_path, failure) - with pytest.raises((ValueError, TimeoutError, PermissionError)): - archive.transfer(client, "coulomb-social", source, output, receipt) - assert not receipt["remote_retrieval_verified"] - assert not any(name == "delete" for name, _ in client.calls) - if failure in {"part", "lost-response"}: - assert len([call for call in client.calls if call[0] == "abort"]) == 1 - else: - assert not any(name == "abort" for name, _ in client.calls) - - -def test_uncertain_completion_retries_readback_without_another_upload(archive, tmp_path): - source, output, receipt, client = fixture(archive, tmp_path, "lost-response") - with pytest.raises(TimeoutError): - archive.transfer(client, "coulomb-social", source, output, receipt) - key = receipt["object_key"] - client.failure = None - archive.transfer(client, "coulomb-social", source, output, receipt) - assert receipt["object_key"] == key and receipt["reused_verified_binding"] - assert len([call for call in client.calls if call[0] == "start"]) == 1 - assert receipt["remote_retrieval_verified"] - - -def test_existing_key_must_bind_bytes_and_previous_version(archive, tmp_path): - source, output, receipt, client = fixture(archive, tmp_path, exists=True) - client.data = b"other ciphertext" - with pytest.raises(ValueError, match="existing_archive_binding_mismatch"): - archive.transfer(client, "coulomb-social", source, output, receipt) - assert [name for name, _ in client.calls] == ["head"] - client.data = source.read_bytes() - receipt["version_id"] = "previous-version" - with pytest.raises(ValueError, match="existing_archive_version_changed"): - archive.transfer(client, "coulomb-social", source, output, receipt) - assert not output.exists() - - -@pytest.mark.parametrize( - "invalid", ["plaintext", "scope", "receipt-hash", "linked-source", "linked-output"] -) -def test_invalid_packet_refuses_before_provider_contact(archive, tmp_path, invalid): - source, output, receipt, client = fixture(archive, tmp_path) - if invalid == "plaintext": - source.write_bytes(b"private unencrypted data") - elif invalid == "scope": - receipt["object_key"] = archive.key_for("email-connect", "2026-10-07T20:00:00Z") - elif invalid == "receipt-hash": - receipt["ciphertext_sha256"] = "bad" - elif invalid == "linked-source": - target = tmp_path / "real.age" - source.rename(target) - source.symlink_to(target) - else: - output.symlink_to(tmp_path / "absent") - with pytest.raises(ValueError): - archive.transfer(client, "coulomb-social", source, output, receipt) - assert client.calls == [] - - -def test_retrieve_after_source_loss_uses_saved_version_without_provider_writes(archive, tmp_path): - source, output, receipt, client = fixture(archive, tmp_path) - receipt.update( - schema="platform.application-primary-archive.v1", captured_at="2026-10-07T20:00:00Z" - ) - archive.transfer(client, "coulomb-social", source, output, receipt) - source.unlink() - output.unlink() - client.calls.clear() - result = archive.retrieve(client, "coulomb-social", output, receipt) - assert result["status"] == "retrieved_verified" - assert not result["provider_mutations"] and not result["application_restore_verified"] - assert output.read_bytes() == client.data - assert [name for name, _ in client.calls] == ["get"] - - -@pytest.mark.parametrize( - "field,value", - [ - ("remote_retrieval_verified", False), - ("destination", "s3://another-bucket/key"), - ("ciphertext_sha256", "bad"), - ("version_id", "null"), - ("ciphertext_bytes", -1), - ("application", "email-connect"), - ], -) -def test_retrieval_rejects_unbound_receipt_before_contact(archive, tmp_path, field, value): - source, output, receipt, client = fixture(archive, tmp_path) - receipt.update(schema="platform.application-primary-archive.v1") - archive.transfer(client, "coulomb-social", source, output, receipt) - output.unlink() - client.calls.clear() - receipt[field] = value - with pytest.raises(ValueError): - archive.retrieve(client, "coulomb-social", output, receipt) - assert client.calls == [] diff --git a/tests/test_public_transport.py b/tests/test_public_transport.py deleted file mode 100644 index d3755a3..0000000 --- a/tests/test_public_transport.py +++ /dev/null @@ -1,88 +0,0 @@ -import copy -import importlib.util -import json -from pathlib import Path -import subprocess -import unittest -from unittest.mock import patch -spec=importlib.util.spec_from_file_location('transport',Path(__file__).resolve().parents[1]/'scripts/argocd_public_transport.py') -t=importlib.util.module_from_spec(spec);spec.loader.exec_module(t) - -class TransportTests(unittest.TestCase): - def plan(self):return json.loads((Path(__file__).resolve().parents[1]/'docs/evidence/2026-10-11-issue-core-transport/transport-plan.json').read_text()) - def objects(self,plan): - result={} - for item in plan['resources']: - obj={'metadata':{'uid':item['uid'],'resourceVersion':'17','labels':{'unrelated':'retain'}},'data':{},'spec':{}} - t.replace(obj,item['path'],copy.deepcopy(item['before']));result[item['kind']]=obj - return result - def runner(self,objects,calls,modify_dry=False): - def run(args,body=None): - calls.append((args,body)) - if args[:2]==['get','nodes']:return json.dumps({'items':[{'status':{'addresses':[{'address':'92.205.62.239'}]}}]}) - kind=args[3] - if args[2]=='get':return json.dumps(objects[kind]) - obj=copy.deepcopy(objects[kind]) - for op in json.loads(body): - if op['op']=='test':self.assertEqual(t.value(obj,op['path']),op['value']) - else:t.replace(obj,op['path'],op['value']) - if '--dry-run=server' in args: - if modify_dry:obj['metadata']['labels']['unrelated']='changed' - return json.dumps(obj) - objects[kind]=obj;return kind+'/'+args[4] - return run - def receipt(self):return {'resources':[],'mutation_attempted':False} - def test_dry_run_changes_no_live_values(self): - plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects);calls=[] - with patch.object(t,'run',self.runner(objects,calls)):r=t.perform(plan,'dry-run',self.receipt()) - self.assertEqual(objects,before);self.assertTrue(r['passed']);self.assertFalse(r['mutation_attempted']);self.assertEqual(sum(args[2]=='patch' for args,body in calls if len(args)>2),2) - def test_execute_and_guarded_rollback_preserve_other_fields(self): - plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects);calls=[];checkpoints=[];receipt=self.receipt() - with patch.object(t,'run',self.runner(objects,calls)): - t.perform(plan,'execute',receipt,lambda:checkpoints.append(copy.deepcopy(receipt))) - for item in plan['resources']:self.assertEqual(t.value(objects[item['kind']],item['path']),item['after']) - self.assertTrue(any(x.get('pending_target') for x in checkpoints)) - t.perform(plan,'verify',self.receipt());t.perform(plan,'rollback',self.receipt()) - self.assertEqual(objects,before) - for args,body in calls: - if body: - ops=json.loads(body);self.assertEqual([op['op'] for op in ops],['test','test','test','replace']);self.assertEqual(ops[0]['path'],'/metadata/uid');self.assertEqual(ops[1]['path'],'/metadata/resourceVersion') - def test_other_target_preimage_drift_stops_before_first_patch(self): - plan=self.plan();objects=self.objects(plan);objects['appproject']['spec']['sourceRepos'].append('ssh://unreviewed/repo');calls=[] - with patch.object(t,'run',self.runner(objects,calls)): - with self.assertRaisesRegex(t.Refused,'^target_preimage_changed$'):t.perform(plan,'execute',self.receipt()) - self.assertFalse(any(args[2]=='patch' for args,body in calls if len(args)>2)) - def test_partial_second_write_is_recorded_and_rollback_can_restore_first(self): - plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects);calls=[];runner=self.runner(objects,calls);receipt=self.receipt();snapshots=[] - def failing(args,body=None): - if len(args)>3 and args[2]=='patch' and args[3]=='appproject' and '--dry-run=server' not in args:raise t.Refused('command_outcome_unverified') - return runner(args,body) - with patch.object(t,'run',failing): - with self.assertRaises(t.Refused):t.perform(plan,'execute',receipt,lambda:snapshots.append(copy.deepcopy(receipt))) - self.assertEqual(receipt['pending_target'],'appproject/railiance-tenants');self.assertTrue(receipt['resources'][0]['changed_verified']);self.assertTrue(snapshots[-1]['mutation_attempted']) - with patch.object(t,'run',runner):t.perform(plan,'rollback',self.receipt()) - self.assertEqual(objects,before) - - def test_target_uid_change_refused(self): - plan=self.plan();objects=self.objects(plan);objects['configmap']['metadata']['uid']='foreign' - with patch.object(t,'run',self.runner(objects,[])): - with self.assertRaisesRegex(t.Refused,'^target_uid_changed$'):t.perform(plan,'execute',self.receipt()) - def test_server_admission_extra_change_refused(self): - plan=self.plan();objects=self.objects(plan);before=copy.deepcopy(objects) - with patch.object(t,'run',self.runner(objects,[],True)): - with self.assertRaisesRegex(t.Refused,'^dry_run_modified_other_fields$'):t.perform(plan,'execute',self.receipt()) - self.assertEqual(objects,before) - def test_broader_repo_or_host_replacement_refused(self): - for kind in ('host','repo'): - plan=self.plan() - if kind=='host':plan['resources'][0]['after']='unreviewed replacement\n' - else:plan['resources'][1]['after'].append('ssh://forgejo.coulomb.social/coulomb/*') - with self.assertRaises(t.Refused):t.validate(plan) - def test_wrong_estate_and_raw_canary_refused(self): - with patch.object(t,'run',return_value=json.dumps({'items':[{'status':{'addresses':[{'address':'192.0.2.8'}]}}]})) as run: - with self.assertRaisesRegex(t.Refused,'^wrong_estate$'):t.perform(self.plan(),'execute',self.receipt()) - self.assertEqual(run.call_count,1) - with patch.object(t.subprocess,'run',return_value=subprocess.CompletedProcess([],1,'SECRET_CANARY','SECRET_CANARY')): - with self.assertRaisesRegex(t.Refused,'^command_outcome_unverified$'):t.run(['get','nodes']) - -if __name__=='__main__':unittest.main() diff --git a/tests/test_reuse_surface_forgejo_source_provision.py b/tests/test_reuse_surface_forgejo_source_provision.py deleted file mode 100644 index 4bc1b2e..0000000 --- a/tests/test_reuse_surface_forgejo_source_provision.py +++ /dev/null @@ -1,201 +0,0 @@ -"""Meaningful fail-closed tests; no credentials or network are used.""" -import importlib.util -import io -import json -import hashlib -import yaml -from pathlib import Path -import tempfile -import unittest -from contextlib import redirect_stdout, redirect_stderr -from unittest.mock import patch - -spec = importlib.util.spec_from_file_location('reuse_provision', Path(__file__).parents[1] / 'scripts/provision-reuse-surface-forgejo-source.py') -p = importlib.util.module_from_spec(spec) -spec.loader.exec_module(p) - -class Journal: - attempt = 'mock-attempt' - def __init__(self): self.rows = [] - def put(self, event, **kw): self.rows.append((event, kw)) - -class Fake: - def __init__(self): - self.journal = Journal() - self.user = None - self.team = None - self.members = [] - self.repos = [] - self.tokens = [] - self.extra = [] - self.mutations = [] - def pages(self, path, sudo=False): - if path == '/orgs/coulomb/teams': return [] if self.team is None else [self.team] - if path.endswith('/members'): return self.members - if path.endswith('/repos') and path.startswith('/teams/'): return self.repos - if path == '/user/orgs': return [] if not self.members else [{'username':'coulomb'}] - if path == '/user/teams': return [] if not self.members else [{**self.team,'organization':{'username':'coulomb'}}] - if path == '/user/repos?order_by=id': return self.repos + self.extra - if path.endswith('/tokens'): return self.tokens - raise AssertionError(path) - def request(self, method, path, payload=None, expected=(200,), **kwargs): - if method != 'GET': - self.mutations.append((method,path)) - raise AssertionError('mutation must not occur in preflight failure test') - if path == '/users/' + p.USER: return (404,None) if self.user is None else (200,self.user) - if path.startswith('/repos/'): return 200, {'private':True,'full_name':'coulomb/'+path.split('/')[3]} - raise AssertionError(path) - -class Bao: - def __init__(self, data=None): self.data = data - def get(self): return self.data - -class Tests(unittest.TestCase): - def complete(self): - c = Fake() - c.user = {'login':p.USER,'restricted':True,'is_admin':False,'visibility':'private'} - c.team = {'id':10,'name':p.TEAM,'permission':'read','includes_all_repositories':False,'can_create_org_repo':False,'units_map':p.UNITS} - c.members = [{'login':p.USER}] - c.repos = [{'name':x,'owner':{'login':p.ORG},'private':True,'full_name':p.ORG+'/'+x} for x in sorted(p.REPOS)] - return c - def test_server_capped_pages_continue_until_empty(self): - c=p.Client('mock-admin',Journal()); pages={1:[{'id':x} for x in range(1,51)],2:[{'id':51}],3:[]} - def get(method,path,**kwargs): - c.last_total_count=51 - page=int(path.split('page=')[1]); return 200,pages[page] - with patch.object(c,'request',side_effect=get) as request: - self.assertEqual(len(c.pages('/user/repos?order_by=id',sudo=True)),51) - self.assertEqual(request.call_count,3) - def test_late_collaborator_private_grant_zero_mutations(self): - c=self.complete() - original_pages=c.pages - def pages(path,sudo=False): - if path.startswith('/user/repos'): - return p.Client.pages(c,path,sudo=sudo) - return original_pages(path,sudo=sudo) - original_request=c.request - def request(method,path,**kwargs): - if path.startswith('/user/repos?'): - page=int(path.split('page=')[1]) - if page==1: return 200,[{'id':x,'private':False,'full_name':'someone/public-'+str(x),'owner':{'login':'someone'}} for x in range(1,51)] - if page==2: return 200,[{'id':51,'private':True,'full_name':'other-org/excluded-collaborator','owner':{'login':'other-org'}}] - return 200,[] - return original_request(method,path,**kwargs) - c.pages=pages; c.request=request - with self.assertRaisesRegex(p.Refused,'extra_private_repository_grant'): p.provision(c,Bao(),{}) - self.assertEqual(c.mutations,[]) - def test_duplicate_page_ids_refuse(self): - c=p.Client('mock-admin',Journal()) - with patch.object(c,'request',return_value=(200,[{'id':1}])): - with self.assertRaisesRegex(p.Refused,'duplicate_page_id'): c.pages('/user/repos') - def test_total_count_mismatch_refuses(self): - c=p.Client('mock-admin',Journal()) - def request(method,path,**kwargs): - c.last_total_count=2 - return 200,[{'id':1}] if 'page=1' in path else [] - with patch.object(c,'request',side_effect=request): - with self.assertRaisesRegex(p.Refused,'total_count_mismatch'): c.pages('/user/repos') - def test_unstable_total_count_refuses(self): - c=p.Client('mock-admin',Journal()) - def request(method,path,**kwargs): - page=int(path.split('page=')[1]); c.last_total_count=2 if page==1 else 3 - return 200,[{'id':page}] - with patch.object(c,'request',side_effect=request): - with self.assertRaisesRegex(p.Refused,'unstable_total_count'): c.pages('/user/repos') - def test_excess_pagination_refuses(self): - c=p.Client('mock-admin',Journal()) - def request(method,path,**kwargs): - return 200,[{'id':int(path.split('page=')[1])}] - with patch.object(c,'request',side_effect=request): - with self.assertRaisesRegex(p.Refused,'pagination_bound_exceeded'): c.pages('/user/repos') - def test_existing_all_repository_team_refused_before_mutation(self): - c=self.complete(); c.team['includes_all_repositories']=True - with self.assertRaisesRegex(p.Refused,'team_conflict'): p.provision(c,Bao(),{}) - self.assertEqual(c.mutations,[]) - def test_existing_excluded_team_repo_refused_before_mutation(self): - c=self.complete(); c.repos.append({'name':'rapp-core-hub','owner':{'login':'coulomb'}}) - with self.assertRaisesRegex(p.Refused,'unexpected_team_repository'): p.provision(c,Bao(),{}) - self.assertEqual(c.mutations,[]) - def test_existing_admin_user_refused_before_mutation(self): - c=self.complete(); c.user['is_admin']=True - with self.assertRaisesRegex(p.Refused,'user_conflict'): p.provision(c,Bao(),{}) - self.assertEqual(c.mutations,[]) - def test_existing_token_kv_binding_mismatch_refused(self): - c=self.complete(); c.tokens=[{'id':20,'name':p.PREFIX+'one','scopes':p.SCOPES,'token_last_eight':'different'}] - with self.assertRaisesRegex(p.Refused,'pat_kv_binding_unverifiable'): p.provision(c,Bao({p.FIELD:'mock-token'}),{}) - self.assertEqual(c.mutations,[]) - def test_extra_private_grants_refused(self): - c=self.complete(); c.extra=[{'private':True,'full_name':'coulomb/secret-outside'}] - with self.assertRaisesRegex(p.Refused,'extra_private'): p.provision(c,Bao(),{}) - self.assertEqual(c.mutations,[]) - def test_orphan_pat_not_reminted(self): - c=self.complete(); c.tokens=[{'id':20,'name':p.PREFIX+'one','scopes':p.SCOPES,'token_last_eight':'ck-token'}] - with self.assertRaisesRegex(p.Refused,'orphan'): p.provision(c,Bao(),{}) - self.assertEqual(c.mutations,[]) - def test_verified_noop_rechecks_secret_and_scope(self): - c=self.complete(); c.tokens=[{'id':20,'name':p.PREFIX+'one','scopes':p.SCOPES,'token_last_eight':'ck-token'}] - with patch.object(p,'verify') as verify: - self.assertEqual(p.provision(c,Bao({p.FIELD:'mock-token'}),{}),'noop') - verify.assert_called_once_with(c,'mock-token',{}) - self.assertEqual(c.mutations,[]) - self.assertEqual(c.journal.rows[-1],('terminal',{'outcome':'verified_noop'})) - def test_existing_write_unit_refused(self): - c=self.complete(); c.team['units_map']={**p.UNITS,'repo.code':'write'} - with self.assertRaisesRegex(p.Refused,'team_conflict'): p.inspect(c,Bao()) - def test_api_ambiguous_mutation_records_intent_without_raw_error(self): - j=Journal(); c=p.Client('fake-secret-admin',j) - with patch.object(c.opener,'open',side_effect=RuntimeError('fake-secret-admin sensitive provider body')): - with self.assertRaisesRegex(p.Refused,'request_observation_failed'): - c.request('POST','/admin/users',{'password':'fake-secret-password'},operation='user_create') - self.assertEqual([x[0] for x in j.rows],['intent','outcome']) - self.assertNotIn('secret',json.dumps(j.rows)) - self.assertEqual(j.rows[-1][1]['status'],'ambiguous') - def test_network_error_never_reaches_stdout_stderr(self): - out,err=io.StringIO(),io.StringIO() - with patch.object(p,'approval',return_value={}), patch.object(p,'Journal',return_value=Journal()) as factory, patch.dict(p.os.environ,{'API_TOKEN':'fake-secret-admin'}), patch.object(p,'provision',side_effect=RuntimeError('fake-secret-admin body')): - factory.return_value.close=lambda:None - with redirect_stdout(out),redirect_stderr(err): - code=p.main(['--inner','--approval','mock','--receipt','mock']) - self.assertEqual(code,1); self.assertEqual(out.getvalue()+err.getvalue(),'') - self.assertNotIn('secret',json.dumps(factory.return_value.rows)) - def test_unapproved_ccr_refuses_before_warden(self): - with tempfile.TemporaryDirectory() as tmp: - f=Path(tmp)/'approval.json'; f.write_text(json.dumps({'ccr_id':p.CCR,'decision':'approved','supervisor':'mock','decision_ref':'mock'})) - with patch.object(p.subprocess,'run') as run: - self.assertEqual(p.main(['--approval',str(f),'--receipt',str(Path(tmp)/'receipt')]),1) - run.assert_not_called() - def test_approved_artifact_revision_mismatch_before_warden(self): - with tempfile.TemporaryDirectory() as tmp: - root=Path(tmp); files={p.CCR_FILE,p.PLAN_FILE,p.SCRIPT_FILE,'openbao/policies/workload-kv-read-reuse-surface-runtime-eso.hcl'} - for rel in files: - f=root/rel; f.parent.mkdir(parents=True,exist_ok=True); f.write_text('mock') - (root/p.CCR_FILE).write_text(yaml.safe_dump({'status':'approved'})) - receipt={'ccr_id':p.CCR,'decision':'approved','supervisor':'founder','decision_ref':'mock-resolved-decision','approved_artifacts':{rel:hashlib.sha256((root/rel).read_bytes()).hexdigest() for rel in files}} - f=root/'approval.json'; f.write_text(json.dumps(receipt)); (root/p.SCRIPT_FILE).write_text('modified-after-approval') - with patch.object(p,'ROOT',root),patch.object(p.subprocess,'run') as run: - self.assertEqual(p.main(['--approval',str(f),'--receipt',str(root/'receipt')]),1) - run.assert_not_called() - def test_journal_symlink_refused(self): - with tempfile.TemporaryDirectory() as tmp: - f=Path(tmp)/'real'; f.write_text(''); link=Path(tmp)/'link'; link.symlink_to(f) - with self.assertRaises(OSError): p.Journal(link) - def test_redirects_never_follow_auth_header(self): - with self.assertRaisesRegex(p.Refused,'redirect_refused'): - p.NoRedirect().redirect_request(None,None,302,'',{ },'https://other.example') - def test_bao_cas_write_uncertain_response_reconciled(self): - b=p.Bao(); j=Journal() - with patch.object(b,'run',return_value=type('R',(),{'returncode':1})()) as run, patch.object(b,'get',return_value={p.FIELD:'fake-source'}): - b.put('fake-source',j) - self.assertIn('-cas=0',run.call_args.args[0]); self.assertEqual(run.call_args.args[1],b'fake-source') - self.assertEqual(j.rows[-1],('verified',{'operation':'kv_create','status':'matched'})) - def test_bao_timeout_preserves_ambiguous_outcome(self): - b=p.Bao(); j=Journal() - with patch.object(b,'run',side_effect=p.Refused('bao_observation_failed')): - with self.assertRaises(p.Refused): b.put('fake-source',j) - self.assertEqual(j.rows[-1],('outcome',{'operation':'kv_create','status':'ambiguous'})) - def test_kv_denial_not_treated_as_absence(self): - b=p.Bao() - with patch.object(b,'run',return_value=type('R',(),{'returncode':1,'stderr':b'Code: 403 Error: fake-secret'})()): - with self.assertRaisesRegex(p.Refused,'bao_preflight_failed'): b.get() - -if __name__=='__main__': unittest.main() diff --git a/tests/test_reuse_surface_forgejo_source_recovery.py b/tests/test_reuse_surface_forgejo_source_recovery.py deleted file mode 100644 index 719626e..0000000 --- a/tests/test_reuse_surface_forgejo_source_recovery.py +++ /dev/null @@ -1,43 +0,0 @@ -import importlib.util -from pathlib import Path -import unittest - -spec=importlib.util.spec_from_file_location('reuse_recovery',Path(__file__).parents[1]/'scripts/revoke-reuse-surface-forgejo-source.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 Fake: - def __init__(self,ambiguous=False,deleted=True): - self.journal=Journal(); self.mutations=[]; self.token={'id':123,'name':r.p.PREFIX+'attempt','scopes':r.p.SCOPES}; self.ambiguous=ambiguous; self.deleted=deleted; self.basic=None - def pages(self,path): return [] if self.token is None else [self.token] - def request(self,method,path,payload=None,**kw): - if method=='GET': return 200,{'login':r.p.USER,'restricted':True,'is_admin':False,'visibility':'private'} - self.mutations.append((method,path)) - if method=='PATCH': - assert set(payload)=={'password','must_change_password'} - return 200,{} - if method=='DELETE': - self.basic=kw.get('basic'); assert self.basic.startswith('Basic ') - if self.deleted: self.token=None - if self.ambiguous: raise r.p.Refused('request_observation_failed') - return 204,None - raise AssertionError(method) -class Tests(unittest.TestCase): - def target(self): return {'id':123,'name':r.p.PREFIX+'attempt'} - def test_basic_exact_token_revocation(self): - c=Fake(); r.recover(c,self.target()); self.assertEqual(c.mutations[-1],('DELETE','/users/'+r.p.USER+'/tokens/123')); self.assertEqual(c.journal.rows[-1][1]['outcome'],'revoked_verified_kv_preserved') - def test_unexpected_token_refuses_without_password_change(self): - c=Fake(); c.token['id']=124 - with self.assertRaisesRegex(r.p.Refused,'inventory_conflict'): r.recover(c,self.target()) - self.assertEqual(c.mutations,[]) - def test_ambiguous_delete_reconciles_absence_without_retry(self): - c=Fake(ambiguous=True); r.recover(c,self.target()); self.assertEqual(sum(m=='DELETE' for m,p in c.mutations),1) - def test_ambiguous_delete_still_present_stops(self): - c=Fake(ambiguous=True,deleted=False) - with self.assertRaisesRegex(r.p.Refused,'revoke_unverified'): r.recover(c,self.target()) - self.assertEqual(sum(m=='DELETE' for m,p in c.mutations),1) - def test_absent_token_verified_noop_has_no_mutations(self): - c=Fake(); c.token=None; r.recover(c,self.target()); self.assertEqual(c.mutations,[]) -if __name__=='__main__': unittest.main() diff --git a/tests/test_reuse_surface_reader_custody_apply.py b/tests/test_reuse_surface_reader_custody_apply.py deleted file mode 100644 index f9465e9..0000000 --- a/tests/test_reuse_surface_reader_custody_apply.py +++ /dev/null @@ -1,116 +0,0 @@ -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/tests/test_secrets_engine_jwt_contract.py b/tests/test_secrets_engine_jwt_contract.py index 1eb24d8..9840ace 100644 --- a/tests/test_secrets_engine_jwt_contract.py +++ b/tests/test_secrets_engine_jwt_contract.py @@ -24,9 +24,7 @@ class ServiceJWTContract(unittest.TestCase): def test_only_self_endpoints_and_pinned_signature_verifier(self): policy = (ROOT / "openbao/policies/secrets-engine-login-self.hcl").read_text() grants = re.findall(r'path "([^"]+)"\s*{\s*capabilities = \["([^"]+)"\]', policy) - self.assertEqual(grants, [("auth/token/lookup-self", "read"), ("sys/capabilities-self", "update"), ("auth/token/revoke-self", "update"), - ("platform/data/workloads/keycape/secrets-engine-openbao", "deny"), - ("platform/metadata/workloads/keycape/secrets-engine-openbao", "deny")]) + self.assertEqual(grants, [("auth/token/lookup-self", "read"), ("sys/capabilities-self", "update"), ("auth/token/revoke-self", "update")]) self.assertNotIn("*", policy) config = json.loads((ROOT / "openbao/auth/keycape-services-config.json").read_text()) self.assertEqual(config, dict(jwks_url="https://kc.coulomb.social/jwks", bound_issuer="https://kc.coulomb.social", jwt_supported_algs=["RS256"])) diff --git a/tests/test_service_identity_metadata_export.py b/tests/test_service_identity_metadata_export.py deleted file mode 100644 index d58eb31..0000000 --- a/tests/test_service_identity_metadata_export.py +++ /dev/null @@ -1,173 +0,0 @@ -import contextlib -import importlib.util -import io -import json -import shutil -import tempfile -import unittest -from datetime import datetime, timezone -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[1] -spec = importlib.util.spec_from_file_location("si_export", ROOT / "scripts/service_identity_metadata_export.py") -m = importlib.util.module_from_spec(spec) -spec.loader.exec_module(m) -NOW = lambda: datetime(2026, 10, 28, 12, 0, tzinfo=timezone.utc) # noqa: E731 - -# Source-derived fixture: the committed role and policy files, laid out at their live mount paths. -SOURCE_ROLES = { - ("netkingdom", "platform-admin"): "netkingdom-platform-admin-role.json", - ("netkingdom", "credential-broker-keycape-client-secret-generate"): - "credential-broker-keycape-client-secret-generate-oidc-role.json", - ("netkingdom", "fluid-telegram-hall-of-helix-workload-kv"): "fluid-telegram-hall-of-helix-oidc-role.json", - ("netkingdom", "coding-agent"): "coding-agent-jwt-role.json", - ("keycape-services", "secrets-engine-openbao"): "secrets-engine-jwt-role.json", - ("approle", "coding-agent"): "coding-agent-approle.json", -} - - -def source_fixture(tmp: Path) -> Path: - (tmp / "auth-list.json").write_text(json.dumps({ - "netkingdom/": {"type": "oidc"}, "keycape-services/": {"type": "jwt"}, - "approle/": {"type": "approle"}, "token/": {"type": "token"}})) - for (mount, name), filename in SOURCE_ROLES.items(): - folder = tmp / "roles" / mount - folder.mkdir(parents=True, exist_ok=True) - shutil.copy(ROOT / "openbao/auth" / filename, folder / f"{name}.json") - (tmp / "policies").mkdir() - for policy in (ROOT / "openbao/policies").glob("*.hcl"): - shutil.copy(policy, tmp / "policies" / policy.name) - return tmp - - -def run(argv): - out, err = io.StringIO(), io.StringIO() - with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): - code = m.main(argv, now=NOW) - return code, out.getvalue(), err.getvalue() - - -class OpenBaoExportTests(unittest.TestCase): - def setUp(self): - self.tmp = Path(tempfile.mkdtemp()) - self.addCleanup(shutil.rmtree, self.tmp) - - def export(self): - code, out, err = run(["openbao", "--fixture-dir", str(source_fixture(self.tmp))]) - self.assertEqual(0, code, err) - return json.loads(out) - - def role(self, doc, mount, name): - return next(r for r in doc["openbao"]["roles"] if r["mount"] == mount and r["name"] == name) - - def test_envelope_and_mounts(self): - doc = self.export() - self.assertEqual(m.SCHEMA, doc["schema"]) - self.assertEqual("fixture", doc["source_kind"]) - self.assertEqual("2026-10-28T12:00:00Z", doc["collected_at"]) - self.assertIn({"path": "token", "type": "token"}, doc["openbao"]["auth_mounts"]) - self.assertEqual(6, len(doc["openbao"]["roles"])) - - def test_fluid_telegram_policy_is_exact_kv_operator_matrix(self): - role = self.role(self.export(), "netkingdom", "fluid-telegram-hall-of-helix-workload-kv") - self.assertEqual({"groups": ["fluid-telegram-operators"], "/assurance/level": "aal2"}, role["bound_claims"]) - self.assertEqual(900, role["token_ttl_seconds"]) - self.assertEqual(1800, role["token_explicit_max_ttl_seconds"]) - rules = {r["path"]: r["capabilities"] for r in role["effective_rules"]} - base = "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/" - self.assertEqual(["read"], rules[base + "operator-app"]) - self.assertEqual(["create", "read", "update"], rules[base + "bot-token"]) - self.assertFalse(any("metadata" in p or "delete" in c for p, c in rules.items())) - self.assertTrue(role["effective_complete"]) - - def test_coding_agent_deny_overrides_issue_core_runtime_read(self): - doc = self.export() - for mount in ("netkingdom", "approle"): - role = self.role(doc, mount, "coding-agent") - rules = {r["path"]: r["capabilities"] for r in role["effective_rules"]} - self.assertEqual(["deny"], rules["platform/data/workloads/issue-core/issue-core/issue-core-runtime"]) - readable_data = [p for p, c in rules.items() if "/data/" in p and c != ["deny"]] - self.assertEqual([], readable_data) - self.assertEqual(900, role["token_max_ttl_seconds"]) - jwt = self.role(doc, "netkingdom", "coding-agent") - self.assertEqual(["service", "agent"], jwt["bound_claims"]["principal_type"]) - - def test_live_shape_drops_unlisted_fields_and_normalizes_ttls(self): - view = m.role_view("approle", "approle", "x", { - "token_ttl": 900, "secret_id_ttl": 300, "role_id": "ignored", "secret_id_accessor": "ignored", - "policies": ["p"], "local_secret_ids": False}) - self.assertNotIn("role_id", view) - self.assertNotIn("secret_id_accessor", view) - self.assertEqual(["p"], view["token_policies"]) - self.assertEqual(300, view["secret_id_ttl_seconds"]) - - def test_secret_shaped_value_refuses_without_echo(self): - fixture = source_fixture(self.tmp) - token = "hvs." + "A1b2C3d4" * 4 - path = fixture / "roles/netkingdom/coding-agent.json" - data = json.loads(path.read_text()) - data["bound_claims"]["sub"] = token - path.write_text(json.dumps(data)) - code, out, err = run(["openbao", "--fixture-dir", str(fixture)]) - self.assertEqual(3, code) - self.assertEqual("", out) - self.assertNotIn(token, err) - self.assertIn("1 secret-shaped", err) - - def test_unreadable_policy_marks_role_incomplete(self): - fixture = source_fixture(self.tmp) - (fixture / "policies/agent-high-risk-boundary.hcl").unlink() - code, out, _ = run(["openbao", "--fixture-dir", str(fixture)]) - role = self.role(json.loads(out), "netkingdom", "coding-agent") - self.assertFalse(role["effective_complete"]) - - def test_names_and_paths_are_not_false_positives(self): - for value in ("credential-broker-keycape-client-secret-generate", - "platform/data/workloads/coulomb/fluid-telegram/hall-of-helix/operator-session", - "http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback"): - self.assertFalse(m.looks_secret(value), value) - self.assertTrue(m.looks_secret("SCRAM-SHA-256$4096:abc")) - self.assertTrue(m.looks_secret("Zx9" * 12)) - - -class PostgresExportTests(unittest.TestCase): - ROW = json.dumps({ - "server_version": "16.4", - "roles": [ - {"name": "postgres", "superuser": True, "create_role": True, "create_db": True, "can_login": True, - "replication": True, "bypass_rls": True, "inherit": True, "connection_limit": -1, - "valid_until": None, "member_of": []}, - {"name": "privacyidea", "superuser": False, "create_role": False, "create_db": False, - "can_login": True, "replication": False, "bypass_rls": False, "inherit": True, - "connection_limit": -1, "valid_until": None, "member_of": [], "rolpassword": "********"}], - "databases": [{"name": "privacyidea_db", "owner": "privacyidea"}]}) - - def test_export_projects_only_role_attributes(self): - tmp = Path(tempfile.mkdtemp()) - self.addCleanup(shutil.rmtree, tmp) - (tmp / "row.txt").write_text(self.ROW + "\n") - code, out, err = run(["postgresql", "--cluster", "net-kingdom-pg", "--input", str(tmp / "row.txt")]) - self.assertEqual(0, code, err) - doc = json.loads(out)["postgresql"] - self.assertEqual("net-kingdom-pg", doc["cluster"]) - pi = next(r for r in doc["roles"] if r["name"] == "privacyidea") - self.assertFalse(pi["superuser"]) - self.assertNotIn("rolpassword", pi) - self.assertEqual([{"name": "privacyidea_db", "owner": "privacyidea"}], doc["databases"]) - - def test_sql_is_read_only_and_never_touches_password_catalog(self): - code, out, _ = run(["postgresql-sql"]) - self.assertEqual(0, code) - sql = out.lower() - self.assertTrue(sql.startswith("select ")) - for word in ("pg_authid", "rolpassword", "insert", "update ", "delete", "alter", "grant ", ";"): - self.assertNotIn(word, sql) - - def test_rejects_multi_row_input(self): - code, _, err = run(["postgresql", "--cluster", "c", "--input", "/dev/null"]) - self.assertEqual(2, code) - self.assertIn("ValueError", err) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_service_jwt_lane.py b/tests/test_service_jwt_lane.py deleted file mode 100644 index 803aac7..0000000 --- a/tests/test_service_jwt_lane.py +++ /dev/null @@ -1,171 +0,0 @@ -"""CCR-2026-0038 service-jwt-login lane: validator, invariant (ii), fake-bao applier.""" -from __future__ import annotations - -import copy -import importlib.util -import json -import os -import re -import stat -import sys -import tempfile -import unittest -from pathlib import Path - -import yaml - -ROOT = Path(__file__).resolve().parents[1] -CCR_PATH = ROOT / "credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml" -GUARDED = "platform/data/workloads/keycape/secrets-engine-openbao" - - -def load(name: str, rel: str): - spec = importlib.util.spec_from_file_location(name, ROOT / rel) - module = importlib.util.module_from_spec(spec) - sys.modules[name] = module - spec.loader.exec_module(module) - return module - - -lane = load("service_jwt_lane", "scripts/service_jwt_lane.py") -cc = load("credential_change_cli", "scripts/credential-change.py") - - -def ccr() -> dict: - return yaml.safe_load(CCR_PATH.read_text()) - - -FAKE_BAO = r'''#!/usr/bin/env python3 -import json, os, sys -state = json.load(open(os.environ["FAKE_BAO_STATE"])) -open(os.environ["FAKE_BAO_LOG"], "a").write(json.dumps(sys.argv[1:]) + "\n") -args = [a for a in sys.argv[1:] if a != "-format=json"] -if args[:2] == ["auth", "list"]: - print(json.dumps({k: {"type": v} for k, v in state["mounts"].items()})) -elif args[:2] == ["policy", "read"]: - print(state["policy"]) -elif args[0] == "read" and args[1].endswith("/config"): - print(json.dumps({"data": state["config"]})) -elif args[0] == "read": - print(json.dumps({"data": state["role"]})) -''' - - -class ServiceJwtLaneTests(unittest.TestCase): - def test_ccr_validates_and_records_the_delegated_apply(self): - _ccr, errors, _w = cc.validate_ccr(CCR_PATH) - self.assertEqual(errors, []) - self.assertEqual(_ccr["status"], "applied") - self.assertTrue(_ccr["openbao"]["auth"]["bound_claims_confirmed"]) - evidence = _ccr["verification"]["evidence"] - self.assertTrue( - any(e["kind"] == "delegated_metadata_apply" and e["result"] == "passed" - for e in evidence), - evidence, - ) - - def test_invariant_ii_policy_has_no_capability_on_the_client_secret_path(self): - policy = (ROOT / "openbao/policies/secrets-engine-login-self.hcl").read_text() - stanzas = re.findall(r'path "([^"]+)"\s*{\s*capabilities = \[([^\]]*)\]', policy) - for path, caps in stanzas: - if path.startswith("platform/"): - self.assertEqual(caps.strip(), '"deny"', path) - self.assertIn((GUARDED, '"deny"'), stanzas) - granted = {p for p, c in stanzas if c.strip() != '"deny"'} - self.assertEqual(granted, {p for p, _ in lane.SELF_GRANTS}) - self.assertNotIn("*", policy) - - def test_validator_rejects_widening(self): - cases = [] - c = ccr(); c["openbao"]["guarded_paths"] = ["platform/data/workloads/*"]; cases.append(c) - c = ccr(); c["openbao"]["auth"]["mount"] = "netkingdom"; cases.append(c) - c = ccr(); c["openbao"]["policy_name"] = "platform-admin"; cases.append(c) - c = ccr(); c["target"]["environment"] = "staging"; cases.append(c) - for case in cases: - self.assertTrue(lane.validate(case, ROOT), case["openbao"]) - - def test_validator_rejects_bad_role_file(self): - with tempfile.TemporaryDirectory() as tmp: - repo = Path(tmp) - (repo / "openbao/auth").mkdir(parents=True) - (repo / "openbao/policies").mkdir(parents=True) - c = ccr() - (repo / c["openbao"]["policy_file"]).write_text(lane.generated_policy_hcl(c)) - (repo / c["openbao"]["auth"]["config_file"]).write_text( - (ROOT / c["openbao"]["auth"]["config_file"]).read_text()) - base = json.loads((ROOT / c["openbao"]["auth"]["role_file"]).read_text()) - self.assertEqual(self._role_errors(repo, c, base), []) - for mutate in ( - lambda r: r.update(token_ttl="1h"), - lambda r: r.update(token_no_default_policy=False), - lambda r: r.update(token_policies=["secrets-engine-login-self", "default"]), - lambda r: r.update(token_period="5m"), - lambda r: r["bound_claims"].update(roles="*"), - lambda r: r.update(bound_audiences=["other"]), - lambda r: r.update(token_num_uses=0), - ): - role = copy.deepcopy(base); mutate(role) - self.assertTrue(self._role_errors(repo, c, role), role) - - def _role_errors(self, repo, c, role): - (repo / c["openbao"]["auth"]["role_file"]).write_text(json.dumps(role)) - return lane.validate(c, repo) - - def _fake(self, tmp: Path, mounts: dict, policy: str | None = None, role_ttl=300): - c = ccr() - role = lane.role_payload(c, ROOT); role["token_ttl"] = role_ttl - role["token_max_ttl"] = role["token_explicit_max_ttl"] = 300 - state = {"mounts": mounts, "policy": policy if policy is not None else lane.generated_policy_hcl(c), - "config": lane.config_payload(c, ROOT), "role": role} - (tmp / "state.json").write_text(json.dumps(state)) - bao = tmp / "bao"; bao.write_text(FAKE_BAO); bao.chmod(bao.stat().st_mode | stat.S_IEXEC) - os.environ["FAKE_BAO_STATE"] = str(tmp / "state.json") - os.environ["FAKE_BAO_LOG"] = str(tmp / "log") - return c, str(bao) - - def _log(self, tmp: Path): - return [json.loads(l) for l in (tmp / "log").read_text().splitlines()] - - def test_apply_enables_absent_mount_writes_in_order_and_reads_back(self): - with tempfile.TemporaryDirectory() as t: - tmp = Path(t) - c, bao = self._fake(tmp, {"keycape-services/": "jwt"}) - # first auth list sees no mount; later readback sees jwt - state = json.loads((tmp / "state.json").read_text()) - state["mounts"] = {} - (tmp / "state.json").write_text(json.dumps(state)) - with self.assertRaises(lane.BaoError): # readback: mount absent in fake - lane.run_apply(c, ROOT, bao, quiet=True) - verbs = [a[:2] for a in self._log(tmp)] - self.assertEqual(verbs[:5], [["auth", "list"], ["policy", "write"], ["auth", "enable"], - ["write", "auth/keycape-services/config"], - ["write", "auth/keycape-services/role/secrets-engine-openbao"]]) - self.assertIn("jwt", self._log(tmp)[2]) - - def test_apply_on_existing_jwt_mount_skips_enable_and_passes_readback(self): - with tempfile.TemporaryDirectory() as t: - tmp = Path(t) - c, bao = self._fake(tmp, {"keycape-services/": "jwt"}) - lane.run_apply(c, ROOT, bao, quiet=True) - verbs = [a[:2] for a in self._log(tmp)] - self.assertNotIn(["auth", "enable"], verbs) - for argv in self._log(tmp): - self.assertFalse(any("token" in a.lower() and "=" in a for a in argv), argv) - - def test_apply_refuses_foreign_mount_type_before_any_write(self): - with tempfile.TemporaryDirectory() as t: - tmp = Path(t) - c, bao = self._fake(tmp, {"keycape-services/": "oidc"}) - with self.assertRaises(lane.BaoError): - lane.run_apply(c, ROOT, bao, quiet=True) - self.assertEqual([a[:2] for a in self._log(tmp)], [["auth", "list"]]) - - def test_readback_detects_policy_drift(self): - with tempfile.TemporaryDirectory() as t: - tmp = Path(t) - c, bao = self._fake(tmp, {"keycape-services/": "jwt"}, policy='path "platform/data/*" {}') - self.assertIn("policy text differs from source", lane.readback_mismatches(c, ROOT, bao)) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_t02_attended_window.py b/tests/test_t02_attended_window.py deleted file mode 100644 index dc0e10e..0000000 --- a/tests/test_t02_attended_window.py +++ /dev/null @@ -1,205 +0,0 @@ -from __future__ import annotations - -import argparse -import importlib.util -import json -import os -import tempfile -import unittest -from datetime import UTC, datetime -from pathlib import Path -from unittest import mock - - -ROOT = Path(__file__).resolve().parents[1] -SPEC = importlib.util.spec_from_file_location("t02_attended_window", ROOT / "scripts" / "t02-attended-window.py") -assert SPEC and SPEC.loader -window = importlib.util.module_from_spec(SPEC) -SPEC.loader.exec_module(window) - -TEST_SPEC = importlib.util.spec_from_file_location( - "test_audit_core_synthetic_sender_lane", ROOT / "tests" / "test_audit_core_synthetic_sender_lane.py") -assert TEST_SPEC and TEST_SPEC.loader -lane_tests = importlib.util.module_from_spec(TEST_SPEC) -TEST_SPEC.loader.exec_module(lane_tests) - -CONTAINED = {"WARDEN_ROUTING_CATALOG": "/catalog.yaml", "BAO_ADDR": "http://127.0.0.1:18200", "PATH": os.environ.get("PATH", "")} - - -class FakeForward: - def __init__(self, log: list[str], fail: bool = False) -> None: - self.log, self.fail = log, fail - - def start(self) -> None: - self.log.append("port-forward-start") - if self.fail: - raise window.WindowError("audit-core port-forward exited early") - - def stop(self) -> None: - self.log.append("port-forward-stop") - - -class Runner: - """Records the step of each reviewed child; optionally fails one step.""" - - def __init__(self, log: list[str], fail_on: str | None = None) -> None: - self.log, self.fail_on, self.envs = log, fail_on, {} - - def __call__(self, command: list[str], *, label: str, env: dict | None = None) -> dict: - self.commands = getattr(self, "commands", []) + [command] - step = "exercise" if ("exercise" in command or "synthetic-e2" in command) else command[2] - if step == "cleanup" and "--abort-reason" in command: - step = "abort-cleanup" - self.log.append(step) - self.envs[step] = env - assert "--contained" in command - if step == self.fail_on: - raise window.WindowError(f"{label} failed (exit 1)") - if step == "exercise": - return {"procedure": window.LANE.LEASE.PROCEDURE, "approval_id": "t02-window-1", - "completed_at": "2026-10-14T00:00:00Z", "secret_values_observed": False} - return {"secret_values_observed": False} - - -class AttendedWindowTests(unittest.TestCase): - def setUp(self) -> None: - self.temp = tempfile.TemporaryDirectory() - self.dir = Path(self.temp.name) - os.chmod(self.dir, 0o700) - self.now = datetime.now(UTC) - document = lane_tests.receipt(self.now) - self.approval = self.dir / "receipt.json" - self.approval.write_text(json.dumps(document), encoding="utf-8") - self.evidence = self.dir / "evidence" - self.evidence.mkdir(mode=0o700) - self.args = argparse.Namespace( - approval=self.approval, load_driver=Path("/opt/driver"), bearer_file=self.dir / "t02.bearer", - evidence_dir=self.evidence, confirm=window.LANE.confirm_token(document), - remote="railiance01", port=18081, - ) - - def tearDown(self) -> None: - self.temp.cleanup() - - def run_window(self, runner: Runner, forward: FakeForward) -> dict: - with mock.patch.dict(os.environ, CONTAINED, clear=True): - return window.window(self.args, runner=runner, forward_factory=lambda remote, port: forward) - - def test_main_never_prints_and_records_failure_in_evidence_dir(self) -> None: - import contextlib - import io - - out, err = io.StringIO(), io.StringIO() - with tempfile.TemporaryDirectory() as tmp, contextlib.redirect_stdout(out), contextlib.redirect_stderr(err): - evidence = Path(tmp) - with mock.patch.object(window, "window", side_effect=window.WindowError("project failed (exit 1): x")): - code = window.main( - ["--approval", "/a", "--load-driver", "/d", "--bearer-file", "/b", - "--evidence-dir", str(evidence), "--confirm", "c"] - ) - records = list(evidence.glob("window-error-*.json")) - self.assertEqual(1, code) - self.assertEqual(1, len(records)) - self.assertEqual(0o600, records[0].stat().st_mode & 0o777) - self.assertEqual("failed", json.loads(records[0].read_text())["status"]) - with mock.patch.object(window, "window", return_value={"status": "ok"}): - self.assertEqual(0, window.main( - ["--approval", "/a", "--load-driver", "/d", "--bearer-file", "/b", - "--evidence-dir", str(evidence), "--confirm", "c"] - )) - self.assertEqual("", out.getvalue()) - self.assertEqual("", err.getvalue()) - - def test_result_file_is_new_private_and_suppresses_stdout(self) -> None: - import contextlib - import io - - out = io.StringIO() - with tempfile.TemporaryDirectory() as tmp, contextlib.redirect_stdout(out): - target = Path(tmp) / "r.json" - window.LANE.emit_result({"secret_values_observed": False}, target) - self.assertEqual(0o600, target.stat().st_mode & 0o777) - with self.assertRaises(FileExistsError): - window.LANE.emit_result({"secret_values_observed": False}, target) - self.assertEqual("", out.getvalue()) - - def test_happy_path_order_and_evidence(self) -> None: - log: list[str] = [] - runner = Runner(log) - summary = self.run_window(runner, FakeForward(log)) - self.assertEqual( - ["project", "port-forward-start", "exercise", "port-forward-stop", "cleanup", "port-forward-stop"], log) - self.assertEqual("completed", summary["outcome"]) - exercise = self.evidence / "t02-window-1-exercise.json" - self.assertEqual(0o600, exercise.stat().st_mode & 0o777) - self.assertTrue((self.evidence / "t02-window-1-window.json").exists()) - env = runner.envs["exercise"] - self.assertEqual("http://127.0.0.1:18081", env["AUDIT_T02_BASE_URL"]) - self.assertEqual(str(self.args.bearer_file), env["AUDIT_T02_TOKEN_FILE"]) - self.assertEqual("railiance-t02-synthetic", env["AUDIT_T02_SOURCE"]) - self.assertEqual("tenant:trial:t02-synthetic-1", env["AUDIT_T02_TENANT"]) - - def test_e2_mode_runs_synthetic_e2_not_the_lease_exercise(self) -> None: - log: list[str] = [] - runner = Runner(log) - self.args.mode = "e2" - summary = self.run_window(runner, FakeForward(log)) - self.assertEqual("completed", summary["outcome"]) - harness = [c for c in runner.commands if "synthetic-e2" in c or "exercise" in c] - self.assertEqual(1, len(harness)) - self.assertIn("synthetic-e2", harness[0]) - self.assertNotIn("exercise", harness[0]) - self.assertIn(window.LANE.LEASE.E2_CONFIRM, harness[0]) - - def test_failures_after_projection_run_abort_cleanup(self) -> None: - for fail_on in ("project", "exercise", "cleanup"): - with self.subTest(fail_on=fail_on): - for path in self.evidence.iterdir(): - path.unlink() - log: list[str] = [] - with self.assertRaises(window.WindowError): - self.run_window(Runner(log, fail_on), FakeForward(log)) - self.assertEqual("abort-cleanup", log[-2] if log[-1] == "port-forward-stop" else log[-1]) - self.assertIn("port-forward-stop", log) - summary = json.loads((self.evidence / "t02-window-1-window.json").read_text()) - self.assertEqual("aborted", summary["outcome"]) - self.assertFalse(summary["secret_values_observed"]) - - def test_port_forward_failure_aborts_without_exercise(self) -> None: - log: list[str] = [] - with self.assertRaises(window.WindowError): - self.run_window(Runner(log), FakeForward(log, fail=True)) - self.assertNotIn("exercise", log) - self.assertIn("abort-cleanup", log) - - def test_refuses_outside_contained_login_before_any_step(self) -> None: - log: list[str] = [] - with mock.patch.dict(os.environ, {"BAO_TOKEN": "x", **CONTAINED}, clear=True): - with self.assertRaises(window.LANE.LEASE.ProcedureError): - window.window(self.args, runner=Runner(log), forward_factory=lambda r, p: FakeForward(log)) - with mock.patch.dict(os.environ, {"PATH": "/usr/bin"}, clear=True): - with self.assertRaises(window.LANE.LEASE.ProcedureError): - window.window(self.args, runner=Runner(log), forward_factory=lambda r, p: FakeForward(log)) - self.assertEqual([], log) - - def test_wrong_confirm_and_git_evidence_dir_refused_before_any_step(self) -> None: - log: list[str] = [] - self.args.confirm = "wrong" - with self.assertRaises(window.WindowError): - self.run_window(Runner(log), FakeForward(log)) - self.args.confirm = window.LANE.confirm_token(json.loads(self.approval.read_text())) - self.args.evidence_dir = ROOT / "docs" - with self.assertRaises(window.LANE.LaneError): - self.run_window(Runner(log), FakeForward(log)) - self.assertEqual([], log) - - def test_port_forward_uses_ssh_remote_loopback_and_bounded_kubectl(self) -> None: - command = window.PortForward("railiance01", 18081).command() - self.assertIn("ExitOnForwardFailure=yes", command) - self.assertIn("127.0.0.1:18081:127.0.0.1:18081", command) - self.assertTrue(command[-1].startswith("timeout 900 kubectl -n audit-core port-forward --address 127.0.0.1")) - self.assertTrue(command[-1].endswith("deploy/audit-core 18081:8080")) - - -if __name__ == "__main__": - unittest.main() diff --git a/tests/test_task_execution_cleanup.py b/tests/test_task_execution_cleanup.py deleted file mode 100644 index 348d4b8..0000000 --- a/tests/test_task_execution_cleanup.py +++ /dev/null @@ -1,199 +0,0 @@ -"""Disposable local SQLite/owner-adapter fixtures. No OpenBao or credential access.""" -import multiprocessing -import os -from pathlib import Path -import sys -import tempfile -import unittest -import uuid - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts')) -from task_execution_journal import Binding, Cleanup, Journal, Refused -from task_execution_cleanup import CleanupResult, run_cleanup - -OBLIGATIONS = (Cleanup('task-token-revoke', '1'*64), - Cleanup('identity-token-revoke', '2'*64), - Cleanup('grant-policy-delete', '3'*64)) - - -class OwnerFixture: - def __init__(self, fail=None): self.calls = []; self.fail = fail - def call(self, kind, digest): - self.calls.append((kind, digest)) - if kind == self.fail: raise TimeoutError('SYNTHETIC-PRIVATE-BACKEND-OUTPUT') - return CleanupResult(True, 'e'*64) - def revoke_task_token(self, digest): return self.call('task-token-revoke', digest) - def revoke_identity_token(self, digest): return self.call('identity-token-revoke', digest) - def delete_grant_policy(self, digest): return self.call('grant-policy-delete', digest) - - -def crash_worker(directory, binding, after_effect): - journal = Journal(Path(directory)) - with journal.locked(): - journal.reserve(binding, OBLIGATIONS) - journal.transition(binding, 'reserved', 'consume-confirmed') - journal.before_cleanup(binding, 0, OBLIGATIONS[0]) - if after_effect: - # A real durable synthetic effect, separate from the admission DB. - fd = os.open(Path(directory)/'synthetic-effect', os.O_CREAT | os.O_WRONLY | os.O_APPEND, 0o600) - os.write(fd, b'one fixture revocation\n'); os.fsync(fd); os.close(fd) - os._exit(23) - - -def cleanup_racer(directory, binding, gate, results): - journal = Journal(Path(directory)) - gate.wait() - try: - with journal.locked(): - journal.reserve(binding, OBLIGATIONS) - journal.transition(binding, 'reserved', 'consume-confirmed') - run_cleanup(journal, binding, OwnerFixture()) - results.put('admitted') - except Refused: results.put('refused') - finally: journal.close() - - -class Tests(unittest.TestCase): - def setUp(self): - self.tmp = tempfile.TemporaryDirectory(); self.addCleanup(self.tmp.cleanup) - self.path = Path(self.tmp.name) - self.binding = Binding('approval-cleanup', 'sha256:'+'a'*64, str(uuid.uuid4()), 'b'*64) - self.journal = Journal(self.path); self.addCleanup(self.journal.close) - - def consumed(self): - self.journal.reserve(self.binding, OBLIGATIONS) - self.journal.transition(self.binding, 'reserved', 'consume-confirmed') - - def test_timeout_business_call_keeps_unknown_while_all_cleanup_runs(self): - with self.journal.locked(): - self.consumed() - self.journal.before_call(self.binding, 0, 'd'*64) - self.journal.transition(self.binding, 'consume-confirmed', 'unknown') - owner = OwnerFixture() - status = run_cleanup(self.journal, self.binding, owner) - self.assertTrue(status['confirmed']) - self.assertEqual(owner.calls, [(c.kind,c.call_digest) for c in OBLIGATIONS]) - self.assertEqual(self.journal.observe(self.binding)['state'], 'unknown') - self.assertTrue(self.journal.observe(self.binding)['external_outcome_unknown']) - with self.assertRaises(Refused): self.journal.before_call(self.binding, 1, 'f'*64) - - def test_failed_business_outcome_preserved(self): - with self.journal.locked(): - self.consumed(); self.journal.transition(self.binding,'consume-confirmed','failed') - self.assertTrue(run_cleanup(self.journal,self.binding,OwnerFixture())['confirmed']) - self.assertEqual(self.journal.observe(self.binding)['state'], 'failed') - - def test_one_revoke_failure_never_passes_but_independent_cleanup_continues(self): - with self.journal.locked(): - self.consumed(); owner = OwnerFixture('task-token-revoke') - result = run_cleanup(self.journal,self.binding,owner) - self.assertFalse(result['confirmed']); self.assertEqual(len(owner.calls),3) - self.assertEqual([r['outcome'] for r in result['obligations']], ['unconfirmed','confirmed','confirmed']) - self.assertNotIn('SYNTHETIC-PRIVATE', str(result)) - with self.assertRaises(Refused): run_cleanup(self.journal,self.binding,owner) - self.assertEqual(len(owner.calls),3) - self.assertNotIn(b'SYNTHETIC-PRIVATE', (self.path/'journal.sqlite').read_bytes()) - - def test_cleanup_closes_business_before_native_attempt(self): - with self.journal.locked(): - self.consumed(); self.journal.before_cleanup(self.binding,0,OBLIGATIONS[0]) - with self.assertRaises(Refused): self.journal.before_call(self.binding,0,'d'*64) - with self.assertRaises(Refused): self.journal.transition(self.binding,'consume-confirmed','authenticating') - - def test_no_preconsume_cleanup_even_when_unknown(self): - with self.journal.locked(): - self.journal.reserve(self.binding,OBLIGATIONS) - self.journal.transition(self.binding,'reserved','unknown') - owner = OwnerFixture() - with self.assertRaises(Refused): run_cleanup(self.journal,self.binding,owner) - self.assertEqual(owner.calls,[]) - - def test_changed_kind_digest_ordinal_or_binding_never_dispatches(self): - with self.journal.locked(): - self.consumed() - for ordinal,item in [(False,OBLIGATIONS[0]),(3,OBLIGATIONS[0]),(0,Cleanup('task-token-revoke','9'*64)),(0,Cleanup('kv-delete','1'*64)),(0,{'kind':'task-token-revoke'})]: - with self.subTest(ordinal=ordinal,item=item): - with self.assertRaises(Refused): self.journal.before_cleanup(self.binding,ordinal,item) - changed = Binding('another',self.binding.request_digest,self.binding.operation_id,self.binding.plan_digest) - with self.assertRaises(Refused): self.journal.before_cleanup(changed,0,OBLIGATIONS[0]) - self.assertTrue(all(x['outcome']=='owed' for x in self.journal.cleanup_status(self.binding)['obligations'])) - - def test_cleanup_plan_must_be_closed_before_reservation(self): - for plan in [list(OBLIGATIONS), (OBLIGATIONS[0],OBLIGATIONS[0]), (Cleanup('kv-delete','1'*64),), (Cleanup('task-token-revoke',False),)]: - with self.journal.locked(): - with self.assertRaises(Refused): self.journal.reserve(self.binding,plan) - self.assertIsNone(self.journal.observe(self.binding)) - - def test_crash_before_and_after_real_synthetic_effect_does_not_resume(self): - context = multiprocessing.get_context('fork') - for after_effect in (False, True): - with tempfile.TemporaryDirectory() as directory: - worker = context.Process(target=crash_worker,args=(directory,self.binding,after_effect)) - worker.start(); worker.join(10) - if worker.is_alive(): worker.terminate(); worker.join(); self.fail('crash fixture exceeded bound') - self.assertEqual(worker.exitcode,23) - reopened = Journal(Path(directory)) - try: - self.assertEqual(reopened.cleanup_status(self.binding)['obligations'][0]['outcome'],'pending') - self.assertTrue(reopened.observe(self.binding)['external_outcome_unknown']) - owner = OwnerFixture() - with reopened.locked(): - with self.assertRaises(Refused): reopened.reserve(self.binding,OBLIGATIONS) - with self.assertRaises(Refused): run_cleanup(reopened,self.binding,owner) - with self.assertRaises(Refused): reopened.before_cleanup(self.binding,1,OBLIGATIONS[1]) - self.assertEqual(owner.calls,[]) - effect = Path(directory)/'synthetic-effect' - self.assertEqual(effect.read_bytes() if effect.exists() else b'',b'one fixture revocation\n' if after_effect else b'') - self.assertFalse(reopened.cleanup_status(self.binding)['confirmed']) - finally: reopened.close() - - def test_actual_process_race_admits_one_cleanup_driver(self): - context = multiprocessing.get_context('fork'); gate = context.Event(); results = context.Queue() - workers = [context.Process(target=cleanup_racer,args=(str(self.path),self.binding,gate,results)) for _ in range(4)] - for worker in workers: worker.start() - gate.set() - for worker in workers: - worker.join(10) - if worker.is_alive(): worker.terminate(); worker.join(); self.fail('race exceeded bound') - self.assertEqual(worker.exitcode,0) - self.assertEqual(sorted(results.get(timeout=2) for _ in workers),['admitted','refused','refused','refused']) - self.assertTrue(self.journal.cleanup_status(self.binding)['confirmed']) - - def test_empty_or_malformed_native_result_never_proves_cleanup(self): - with self.journal.locked(): - self.journal.reserve(self.binding) - self.journal.transition(self.binding,'reserved','consume-confirmed') - with self.assertRaises(Refused): run_cleanup(self.journal,self.binding,OwnerFixture()) - self.assertFalse(self.journal.cleanup_status(self.binding)['confirmed']) - - def test_bad_native_confirmation_still_attempts_remaining_cleanup(self): - class BadOwner(OwnerFixture): - def revoke_task_token(self, digest): - self.calls.append(('task-token-revoke',digest)) - return CleanupResult('true','e'*64) - with self.journal.locked(): - self.consumed(); owner=BadOwner() - self.assertFalse(run_cleanup(self.journal,self.binding,owner)['confirmed']) - self.assertEqual(len(owner.calls),3) - - def test_confirmation_exact_types_and_no_duplicate_receipts(self): - with self.journal.locked(): - self.consumed(); self.journal.before_cleanup(self.binding,0,OBLIGATIONS[0]) - for confirmed,digest in [('true','e'*64),(1,'e'*64),(True,None),(True,'E'*64)]: - with self.assertRaises(Refused): self.journal.after_cleanup(self.binding,0,OBLIGATIONS[0],confirmed=confirmed,evidence_digest=digest) - self.journal.after_cleanup(self.binding,0,OBLIGATIONS[0],confirmed=True,evidence_digest='e'*64) - with self.assertRaises(Refused): self.journal.after_cleanup(self.binding,0,OBLIGATIONS[0],confirmed=True,evidence_digest='e'*64) - with self.assertRaises(Refused): self.journal.before_cleanup(self.binding,0,OBLIGATIONS[0]) - - def test_cleanup_state_is_independent_of_legacy_business_transition(self): - with self.journal.locked(): - self.consumed() - states=['consume-confirmed','authenticating','grant-issuing','grant-issued','applying','readback','verified'] - for before,after in zip(states,states[1:]): self.journal.transition(self.binding,before,after) - with self.assertRaises(Refused): self.journal.transition(self.binding,'verified','cleanup-confirmed','e'*64) - run_cleanup(self.journal,self.binding,OwnerFixture()) - self.journal.transition(self.binding,'verified','cleanup-confirmed','e'*64) - self.assertEqual(self.journal.observe(self.binding)['state'],'cleanup-confirmed') - - -if __name__ == '__main__': unittest.main() diff --git a/tests/test_task_execution_cleanup_calls.py b/tests/test_task_execution_cleanup_calls.py deleted file mode 100644 index 814ab29..0000000 --- a/tests/test_task_execution_cleanup_calls.py +++ /dev/null @@ -1,57 +0,0 @@ -"""Real SQLite subcall durability for native cleanup/verification pairs.""" -import multiprocessing -import os -from pathlib import Path -import sys -import uuid - -import pytest -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from task_execution_journal import Binding,Cleanup,Journal,Refused - - -def child_crash(directory,binding,obligation): - journal=Journal(Path(directory)) - with journal.locked(): - journal.reserve(binding,(obligation,)) - journal.transition(binding,'reserved','consume-confirmed') - journal.before_cleanup(binding,0,obligation) - journal.before_cleanup_call(binding,0,obligation,0,'c'*64) - os._exit(29) - - -def test_subcall_crash_persists_and_never_restores_admission(tmp_path): - binding=Binding('fixture-approval','sha256:'+'a'*64,str(uuid.uuid4()),'b'*64) - obligation=Cleanup('task-token-revoke','d'*64) - os.chmod(tmp_path,0o700) - context=multiprocessing.get_context('fork') - process=context.Process(target=child_crash,args=(str(tmp_path),binding,obligation)) - process.start();process.join(10) - if process.is_alive():process.terminate();process.join();pytest.fail('crash fixture exceeded bound') - assert process.exitcode==29 - journal=Journal(tmp_path) - try: - assert journal.observe(binding)['external_outcome_unknown'] - assert not journal.cleanup_status(binding)['confirmed'] - with journal.locked(): - with pytest.raises(Refused):journal.before_cleanup_call(binding,0,obligation,1,'c'*64) - finally:journal.close() - - -def test_pending_native_subcall_never_confirms_parent_and_survives_unconfirmed(tmp_path): - os.chmod(tmp_path,0o700);journal=Journal(tmp_path) - binding=Binding('fixture-approval','sha256:'+'a'*64,str(uuid.uuid4()),'b'*64) - obligation=Cleanup('task-token-revoke','d'*64) - try: - with journal.locked(): - journal.reserve(binding,(obligation,));journal.transition(binding,'reserved','consume-confirmed') - journal.before_cleanup(binding,0,obligation) - journal.before_cleanup_call(binding,0,obligation,0,'c'*64) - with pytest.raises(Refused):journal.after_cleanup(binding,0,obligation,confirmed=True,evidence_digest='e'*64) - with pytest.raises(Refused):journal.before_cleanup_call(binding,0,obligation,1,'c'*64) - for step in [False,0.0,'0',-1,2]: - with pytest.raises(Refused):journal.after_cleanup_call(binding,0,obligation,step,'c'*64,'e'*64) - journal.after_cleanup(binding,0,obligation,confirmed=False,evidence_digest='e'*64) - assert journal.observe(binding)['external_outcome_unknown'] - with pytest.raises(Refused):journal.after_cleanup_call(binding,0,obligation,0,'c'*64,'e'*64) - finally:journal.close() diff --git a/tests/test_task_execution_grant.py b/tests/test_task_execution_grant.py deleted file mode 100644 index 13298e8..0000000 --- a/tests/test_task_execution_grant.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Pure proposed grant projection: source provenance, never approval/native proof.""" -import copy -from pathlib import Path -import sys -import pytest -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -import task_execution_grant as grant - - -@pytest.fixture -def record(): - return {'id':'CCR-FIXTURE-GRANT','kind':'credential-change-request','schema_version':1, - 'request_type':'task-execution-grant','title':'Proposed source fixture','status':'proposed', - 'created':'2026-10-16','updated':'2026-10-16','requester':{'agent':'fixture','reason':'Local proof'}, - 'review':{'required':True,'required_approvers':['railiance-platform-owner','secrets-engine-owner','access-engine-owner','approval-engine-owner'],'comments':[]}, - 'target':{'domain':'infotech','tenant':'platform','workload':'secrets-engine','environment':'production','purpose':'fixture'}, - 'execution':{'action':'execute-ccr','descriptor_binding':{'operation_deadline':'2026-10-16T12:00:00Z','ccr_immutable_sha256':'sha256:'+'a'*64}, - 'source_pins':{'/fixture/source':'sha256:'+'b'*64},'cleanup':[{'kind':'task-token-revoke','call_digest':'c'*64}]}, - 'openbao':{'policy':{'name':'task-exec-fixture','hcl':'exact owner generated HCL'}, - 'token_create_body':{'ttl':'120s','num_uses':12,'renewable':False}},'evidence':{'events':[]}} - - -def comment():return {'at':'2026-10-16T12:00:00Z','reviewer':'railiance-platform-owner','decision':'approved','comment':'Source only; no execution authority'} -def event():return {'at':'2026-10-16T12:00:00Z','kind':'test','result':'unknown','reference_sha256':'sha256:'+'e'*64} - - -def test_only_typed_review_comments_and_evidence_events_are_excluded(record): - changed=copy.deepcopy(record);changed['review']['comments'].append(comment());changed['evidence']['events'].append(event()) - assert grant.immutable_digest(changed)==grant.immutable_digest(record) - grant.require_append_only(record,changed) - assert record['review']['comments']==[] # detached immutable projection - - -@pytest.mark.parametrize('field',['status','updated','title','requester','target','execution','openbao','review']) -def test_every_other_root_or_effectful_field_changes_digest(record,field): - changed=copy.deepcopy(record) - if field=='status':changed[field]='approved' - elif field=='updated':changed[field]='2026-10-17' - elif field=='title':changed[field]='different title' - elif field=='requester':changed[field]['reason']='different requester reason' - elif field=='target':changed[field]['purpose']='different purpose' - elif field=='execution':changed[field]['source_pins']['/fixture/source']='sha256:'+'f'*64 - elif field=='openbao':changed[field]['policy']['hcl']='different HCL' - elif field=='review':changed[field]['required_approvers'].reverse() - if field=='review': - with pytest.raises(grant.Refused):grant.immutable_digest(changed) - else: - assert grant.immutable_digest(changed)!=grant.immutable_digest(record) - with pytest.raises(grant.Refused):grant.require_append_only(record,changed) - - -@pytest.mark.parametrize('location,key',[('root','audit'),('root','receipts'),('root','evidence_exclusions'),('evidence','files'),('evidence','policy'),('review','approval_ref'),('review','exclusions')]) -def test_guessed_evidence_exclusions_or_unknown_fields_refuse(record,location,key): - changed=copy.deepcopy(record) - (changed if location=='root' else changed[location])[key]=[] - with pytest.raises(grant.Refused):grant.immutable_digest(changed) - - -def test_append_only_rejects_removal_rewrite_and_reordering(record): - previous=copy.deepcopy(record);previous['review']['comments']=[comment(),dict(comment(),comment='Second')] - previous['evidence']['events']=[event()] - for field,value in [('comments',[]),('comments',[dict(comment(),decision='declined')]),('comments',list(reversed(previous['review']['comments']))),('events',[])]: - changed=copy.deepcopy(previous);changed['review' if field=='comments' else 'evidence'][field]=value - assert grant.immutable_digest(changed)==grant.immutable_digest(previous) - with pytest.raises(grant.Refused):grant.require_append_only(previous,changed) - - -@pytest.mark.parametrize('change',['policy','token_budget','cleanup','source','deadline','target_ccr','wrong_hash','grant_id']) -def test_bind_reconstructs_effects_and_exact_existing_descriptor_join(record,change): - expected=copy.deepcopy(record);changed=copy.deepcopy(record);digest=grant.immutable_digest(record);grant_id=record['id'] - if change=='policy':changed['openbao']['policy']['hcl']='path "*" {}' - elif change=='token_budget':changed['openbao']['token_create_body']['num_uses']=13 - elif change=='cleanup':changed['execution']['cleanup'][0]['kind']='kv-delete' - elif change=='source':changed['execution']['source_pins']['/fixture/source']='sha256:'+'f'*64 - elif change=='deadline':changed['execution']['descriptor_binding']['operation_deadline']='2027-01-01T00:00:00Z' - elif change=='target_ccr':changed['execution']['descriptor_binding']['ccr_immutable_sha256']='sha256:'+'f'*64 - elif change=='wrong_hash':digest='sha256:'+'f'*64 - elif change=='grant_id':grant_id='CCR-OTHER' - with pytest.raises(grant.Refused):grant.bind(changed,grant_id=grant_id,expected_execution=expected['execution'],expected_openbao=expected['openbao'],expected_digest=digest) - - -def test_proposed_source_record_can_bind_without_becoming_approved(record): - projection=grant.bind(record,grant_id=record['id'],expected_execution=record['execution'],expected_openbao=record['openbao'],expected_digest=grant.immutable_digest(record)) - assert projection['status']=='proposed' - - -@pytest.mark.parametrize('change',['schema_bool','comment_extra','event_extra','unaware_comment','event_missing','nan','tuple','integer_key','status_list']) -def test_closed_types_refuse_instead_of_silent_coercion(record,change): - if change=='schema_bool':record['schema_version']=True - elif change=='comment_extra':record['review']['comments']=[dict(comment(),policy='unreviewed')] - elif change=='event_extra':record['evidence']['events']=[dict(event(),token='never accepted')] - elif change=='unaware_comment':record['review']['comments']=[dict(comment(),at='2026-10-16T12:00:00')] - elif change=='event_missing':record['evidence']['events']=[{'kind':'test'}] - elif change=='nan':record['execution']['value']=float('nan') - elif change=='tuple':record['execution']['value']=(1,2) - elif change=='integer_key':record['execution'][1]='invalid' - elif change=='status_list':record['status']=[] - with pytest.raises(grant.Refused):grant.immutable_digest(record) diff --git a/tests/test_task_execution_journal.py b/tests/test_task_execution_journal.py deleted file mode 100644 index 654c024..0000000 --- a/tests/test_task_execution_journal.py +++ /dev/null @@ -1,171 +0,0 @@ -import importlib.util -import multiprocessing -import os -import sys -import tempfile -import unittest -import uuid -from pathlib import Path - -sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts')) -import task_execution_journal as module -Binding, Journal, Refused = module.Binding, module.Journal, module.Refused -Cleanup = module.Cleanup - - -def reserve_worker(directory, binding, gate, results): - journal = Journal(Path(directory)) - gate.wait() - try: - with journal.locked(): - journal.reserve(binding) - results.put('admitted') - except Refused: - results.put('refused') - finally: - journal.close() - - -class Tests(unittest.TestCase): - def setUp(self): - self.tmp = tempfile.TemporaryDirectory() - self.addCleanup(self.tmp.cleanup) - self.path = Path(self.tmp.name) - self.binding = Binding('approval-1', 'sha256:'+'a'*64, str(uuid.uuid4()), 'b'*64) - self.journal = Journal(self.path) - self.addCleanup(self.journal.close) - - def test_actual_multi_process_race_admits_once(self): - context = multiprocessing.get_context('fork') - gate, results = context.Event(), context.Queue() - workers = [context.Process(target=reserve_worker,args=(str(self.path),self.binding,gate,results)) for _ in range(4)] - for w in workers: w.start() - gate.set() - for w in workers: - w.join(10) - if w.is_alive(): w.terminate(); w.join(); self.fail('worker exceeded bound') - self.assertEqual(w.exitcode, 0) - self.assertEqual(sorted(results.get(timeout=2) for _ in workers), ['admitted','refused','refused','refused']) - - def test_reservation_persists_after_process_abandonment(self): - with self.journal.locked(): self.journal.reserve(self.binding) - reopened = Journal(self.path) - try: - self.assertEqual(reopened.observe(self.binding)['state'], 'reserved') - self.assertFalse(reopened.observe(self.binding)['dispatch_allowed']) - with reopened.locked(): - with self.assertRaises(Refused): reopened.reserve(self.binding) - with self.assertRaises(Refused): reopened.transition(self.binding,'reserved','consume-confirmed') - finally: reopened.close() - - def test_same_approval_different_digest_refused(self): - with self.journal.locked(): self.journal.reserve(self.binding) - changed = Binding('approval-1','sha256:'+'c'*64,str(uuid.uuid4()),'b'*64) - with self.journal.locked(): - with self.assertRaises(Refused): self.journal.reserve(changed) - with self.assertRaises(Refused): self.journal.observe(changed) - - def test_same_operation_different_approval_refused(self): - with self.journal.locked(): self.journal.reserve(self.binding) - with self.journal.locked(): - with self.assertRaises(Refused): self.journal.reserve(Binding('approval-2','sha256:'+'a'*64,self.binding.operation_id,'b'*64)) - - def test_full_path_cleanup_and_terminal_replay(self): - states=['reserved','consume-confirmed','authenticating','grant-issuing','grant-issued','applying','readback','verified','cleanup-confirmed'] - with self.journal.locked(): - cleanup = Cleanup('task-token-revoke','d'*64) - self.journal.reserve(self.binding,(cleanup,)) - for before,after in zip(states,states[1:]): - if after == 'cleanup-confirmed': - self.journal.before_cleanup(self.binding,0,cleanup) - self.journal.after_cleanup(self.binding,0,cleanup,confirmed=True,evidence_digest='c'*64) - self.journal.transition(self.binding,before,after,'c'*64) - with self.assertRaises(Refused): self.journal.transition(self.binding,'cleanup-confirmed','applying') - self.assertEqual(self.journal.observe(self.binding),{'state':'cleanup-confirmed','evidence_digest':'c'*64,'dispatch_allowed':False,'external_outcome_unknown':False}) - with self.journal.locked(): - with self.assertRaises(Refused): self.journal.reserve(self.binding) - - def test_unknown_is_terminal(self): - with self.journal.locked(): - self.journal.reserve(self.binding) - self.journal.transition(self.binding,'reserved','unknown') - with self.assertRaises(Refused): self.journal.transition(self.binding,'unknown','reserved') - - def test_early_dispatch_wrong_state_and_cleanup_without_evidence_refused(self): - with self.journal.locked(): - self.journal.reserve(self.binding) - with self.assertRaises(Refused): self.journal.transition(self.binding,'reserved','applying') - with self.assertRaises(Refused): self.journal.transition(self.binding,'consume-confirmed','authenticating') - self.assertEqual(self.journal.observe(self.binding)['state'],'reserved') - - def test_other_live_invocation_cannot_obtain_lock(self): - second=Journal(self.path) - try: - with self.journal.locked(): - with self.assertRaises(Refused): - with second.locked(): pass - finally: second.close() - - def test_symlink_and_hardlink_files_refused(self): - for name,link in [('journal.sqlite','hard'),('execution.lock','symbolic')]: - with tempfile.TemporaryDirectory() as d: - p=Path(d); target=p/'other'; target.touch(mode=0o600) - if link=='hard': os.link(target,p/name) - else: (p/name).symlink_to(target) - with self.assertRaises((Refused,OSError)): Journal(p) - - def test_nonprivate_directory_refused(self): - with tempfile.TemporaryDirectory() as d: - os.chmod(d,0o755) - with self.assertRaises(Refused): Journal(Path(d)) - - def test_binding_rejects_unbounded_or_malformed_metadata(self): - for value in ['token'+ 'x'*200, '../approval', 'approval\n1']: - with self.assertRaises(Refused): Binding(value,'sha256:'+'a'*64,self.binding.operation_id,'b'*64).validate() - for value in ['a'*64,'sha256:'+'a'*63,'sha256:'+'A'*64,'not-a-digest']: - with self.assertRaises(Refused): Binding('a',value,self.binding.operation_id,'b'*64).validate() - - def test_pending_external_call_blocks_next_and_survives_reopen(self): - with self.journal.locked(): - self.journal.reserve(self.binding) - self.journal.before_call(self.binding,0,'d'*64) - with self.assertRaises(Refused): self.journal.before_call(self.binding,1,'e'*64) - with self.assertRaises(Refused): self.journal.transition(self.binding,'reserved','consume-confirmed') - reopened=Journal(self.path) - try: - self.assertTrue(reopened.observe(self.binding)['external_outcome_unknown']) - self.assertFalse(reopened.observe(self.binding)['dispatch_allowed']) - finally: reopened.close() - - def test_call_order_and_exact_confirmation(self): - with self.journal.locked(): - self.journal.reserve(self.binding) - with self.assertRaises(Refused): self.journal.before_call(self.binding,1,'d'*64) - self.journal.before_call(self.binding,0,'d'*64) - with self.assertRaises(Refused): self.journal.after_call(self.binding,0,'e'*64,'f'*64) - self.journal.after_call(self.binding,0,'d'*64,'f'*64) - with self.assertRaises(Refused): self.journal.after_call(self.binding,0,'d'*64,'f'*64) - with self.assertRaises(Refused): self.journal.before_call(self.binding,0,'d'*64) - self.journal.before_call(self.binding,1,'e'*64) - - def test_nonstring_binding_and_boolean_ordinal_refused(self): - with self.assertRaises(Refused): Binding(True,'sha256:'+'a'*64,self.binding.operation_id,'b'*64).validate() - with self.journal.locked(): - self.journal.reserve(self.binding) - with self.assertRaises(Refused): self.journal.before_call(self.binding,False,'d'*64) - - def test_confirmation_rejects_sqlite_ordinal_coercion(self): - with self.journal.locked(): - self.journal.reserve(self.binding) - self.journal.before_call(self.binding,0,'d'*64) - for ordinal in (False,0.0,'0',None,-1,64): - with self.subTest(ordinal=ordinal): - with self.assertRaises(Refused): self.journal.after_call(self.binding,ordinal,'d'*64,'f'*64) - self.assertTrue(self.journal.observe(self.binding)['external_outcome_unknown']) - for digest in (None,False,'D'*64,'d'*63): - with self.assertRaises(Refused): self.journal.after_call(self.binding,0,digest,'f'*64) - self.assertTrue(self.journal.observe(self.binding)['external_outcome_unknown']) - self.journal.after_call(self.binding,0,'d'*64,'f'*64) - self.assertFalse(self.journal.observe(self.binding)['external_outcome_unknown']) - -if __name__=='__main__': unittest.main() diff --git a/tests/test_task_execution_mediation_journal.py b/tests/test_task_execution_mediation_journal.py deleted file mode 100644 index d0f8e19..0000000 --- a/tests/test_task_execution_mediation_journal.py +++ /dev/null @@ -1,32 +0,0 @@ -"""Durable CAS/confirmation call prefix cannot overlap native call ordinals.""" -import os -from pathlib import Path -import sys -import uuid -import pytest -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from task_execution_journal import Binding,Cleanup,Journal,Refused - - -def test_next_call_continues_auth_prefix_without_replay_or_resume(tmp_path): - os.chmod(tmp_path,0o700);journal=Journal(tmp_path) - binding=Binding('fixture-approval','sha256:'+'a'*64,str(uuid.uuid4()),'b'*64) - try: - with pytest.raises(Refused):journal.next_call_ordinal(binding) - with journal.locked(): - journal.reserve(binding,(Cleanup('task-token-revoke','c'*64),)) - assert journal.next_call_ordinal(binding)==0 - for ordinal in [0,1]: - journal.before_call(binding,ordinal,'d'*64) - with pytest.raises(Refused):journal.next_call_ordinal(binding) - journal.after_call(binding,ordinal,'d'*64,'e'*64) - assert journal.next_call_ordinal(binding)==2 - journal.transition(binding,'reserved','consume-confirmed') - journal.before_call(binding,2,'f'*64) - with pytest.raises(Refused):journal.before_call(binding,0,'f'*64) - journal.after_call(binding,2,'f'*64,'e'*64) - journal.before_cleanup(binding,0,Cleanup('task-token-revoke','c'*64)) - with pytest.raises(Refused):journal.next_call_ordinal(binding) - with journal.locked(): - with pytest.raises(Refused):journal.next_call_ordinal(binding) - finally:journal.close() diff --git a/tests/test_task_execution_mediator.py b/tests/test_task_execution_mediator.py deleted file mode 100644 index 52024f1..0000000 --- a/tests/test_task_execution_mediator.py +++ /dev/null @@ -1,162 +0,0 @@ -"""Whole source-server/native fixture proof, never production owner acceptance.""" -from contextlib import contextmanager -from dataclasses import replace -import importlib -import json -from pathlib import Path -import sys -from types import SimpleNamespace -import uuid - -import pytest -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from test_task_execution_native_fixture import owner,bao_dev,compiler -from task_execution_mediator import ConfirmationConfig,run_native_fixture_task -from task_execution_journal import Refused -from task_execution_source_servers import approval_server,reviewer_server,access_server,http,private -from secrets_engine.approval_consume import _request_purpose -from secrets_engine.approval_claim import binding_from_check_request -from secrets_engine.authorization import build_action_request -from secrets_engine.decision_check import check_decision -from secrets_engine.execution_contract import ExecutionContract - - -@contextmanager -def whole_fixture(owner,tmp_path,*,actual_policy=False,confirmation_read=True): - native,journal,binding,call,compiler,descriptor=owner - snapshot={path:compiler.read(Path(path)) for path in compiler.pins} - entry=compiler._catalogue_entry(snapshot) - approval_id=str(uuid.uuid4()) - entry=replace(entry,approval={**entry.approval,'authorization_id':approval_id}) - request=build_action_request(entry,'execute-ccr',subject_id='secrets-engine',subject_type='service', - purpose=_request_purpose(entry),execution_contract=ExecutionContract.parse(descriptor)) - with reviewer_server(tmp_path) as reviewer, access_server(tmp_path,descriptor,request,reviewer,actual_policy=actual_policy) as access, approval_server(tmp_path) as approval: - issue=access.offline_envelope - body={'id':approval_id,'binding':binding_from_check_request(issue['binding']), - 'pdp_digest':issue['binding']['request_digest'],'pdp_path':True,'human_control':True, - 'validity':{'not_before':'2026-01-01T00:00:00+00:00', - 'expires_at':reviewer_expiry()}} - status,obj=http(approval.origin,'POST','/v1/approvals',body,approval.service_token) - assert status==201 - assert http(approval.origin,'POST','/v1/approvals/'+approval_id+'/entries',{},approval.human_token)[0]==200 - approval_token=private(tmp_path/'approval.token',approval.service_token) - pdp_token=private(tmp_path/'pdp.token',reviewer.caller_token) - confirmation_token=private(tmp_path/'confirmation.token',approval.service_token if confirmation_read else approval.no_read_token) - cfg=SimpleNamespace(approval_url=approval.origin,approval_token_file=approval_token, - approval_client_secret_file=None,pdp_url=access.origin,pdp_token_file=pdp_token, - pdp_trust_file=access.trustfile,pdp_verifier=access.verifier, - authorization_subject_id='secrets-engine',authorization_subject_type='service', - authorization_policy_package=access.package,authorization_policy_version=access.version, - clock_trust_file=None) - yield SimpleNamespace(native=native,journal=journal,call=call,compiler=compiler, - descriptor=descriptor,entry=entry,cfg=cfg,approval=approval, - confirmation=ConfirmationConfig(approval.origin,confirmation_token), - approval_id=approval_id,request=request,reviewer=reviewer) - - -def reviewer_expiry(): - from datetime import datetime,timedelta,timezone - return (datetime.now(timezone.utc)+timedelta(minutes=10)).isoformat() - - -def invoke(fixture): - return run_native_fixture_task(compiler=fixture.compiler,journal=fixture.journal, - plan_request=fixture.native.meta['request'],owner_descriptor=fixture.native.admission.descriptor, - secrets_cfg=fixture.cfg,entry=fixture.entry,confirmation=fixture.confirmation, - native_admission=fixture.native.admission) - - -def test_real_source_servers_signed_fixture_allow_and_actual_task_policy_deny(owner,tmp_path): - # These two Checks test real source servers and independently installed keys; - # they are not a fabricated DecisionEnvelope or activation of a real policy. - for actual_policy in [False,True]: - directory=tmp_path/('actual-policy' if actual_policy else 'fixture-policy');directory.mkdir() - with whole_fixture(owner,directory,actual_policy=actual_policy) as fixture: - status,claim=http(fixture.approval.origin,'GET','/v1/approvals/'+fixture.approval_id+'/claim',token=fixture.approval.service_token) - assert status==200 - request=json.loads(json.dumps(fixture.request));request['context']['approval']=claim - envelope=check_decision(base_url=fixture.cfg.pdp_url,token_file=fixture.cfg.pdp_token_file,request=request, - trust_file=fixture.cfg.pdp_trust_file,verifier=fixture.cfg.pdp_verifier) - assert envelope['effect']==('deny' if actual_policy else 'allow') - assert envelope['provenance']['caller']['mode']=='enforce' - assert all(fixture.reviewer.calls) - assert fixture.approval.engine.get(fixture.approval_id).status=='approved' - assert fixture.call('GET','sys/policies/acl/'+fixture.native.policy_name)[0]==404 - - -def test_missing_strict_dependency_never_reads_credentials_or_reserves(monkeypatch): - original=importlib.import_module - def missing(name,*args,**kwargs): - if name=='secrets_engine.task_execution_handoff':raise ImportError() - return original(name,*args,**kwargs) - monkeypatch.setattr(importlib,'import_module',missing) - with pytest.raises(Refused,match='dependency unavailable'): - run_native_fixture_task(compiler=None,journal=None,plan_request={},owner_descriptor=b'', - secrets_cfg=None,entry=None,confirmation=None,native_admission=None) - - -def test_whole_strict_consume_confirmation_precedes_real_native_effect(owner,tmp_path): - with whole_fixture(owner,tmp_path) as fixture: - result=invoke(fixture) - assert result['native_calls_completed']==6 and result['cleanup_confirmed'] - assert result['native_apply']=='verified' - assert result['business_state']=='cleanup-confirmed' - assert len(result['readback_evidence_digest'])==64 - obj=fixture.approval.engine.get(fixture.approval_id) - assert obj.status=='consumed' - assert obj.consumed_digest==result['request_digest'] - calls=fixture.journal.db.execute('SELECT ordinal,outcome FROM calls ORDER BY ordinal').fetchall() - assert calls[:2]==[(0,'complete'),(1,'complete')] - assert len(calls)==13 # CAS + GET + five native grant calls + six metadata calls - assert fixture.approval.calls.count(('POST','/v1/approvals/'+fixture.approval_id+'/consume'))==1 - assert fixture.approval.calls.count(('GET','/v1/approvals/'+fixture.approval_id))==1 - assert fixture.call('GET','sys/policies/acl/'+fixture.native.policy_name)[0]==404 - raw=(fixture.journal.directory/'journal.sqlite').read_bytes() - for token in [fixture.native.admission.owner_token,fixture.native.admission.identity_session.token, - fixture.approval.service_token,fixture.reviewer.caller_token]: - assert token.encode() not in raw - with pytest.raises(Exception):invoke(fixture) - assert fixture.approval.calls.count(('POST','/v1/approvals/'+fixture.approval_id+'/consume'))==1 - - -def test_actual_confirmation_read_scope_denial_keeps_native_untouched(owner,tmp_path): - with whole_fixture(owner,tmp_path,confirmation_read=False) as fixture: - with pytest.raises(Refused,match='confirmation unconfirmed'):invoke(fixture) - assert fixture.approval.engine.get(fixture.approval_id).status=='consumed' - state=fixture.journal.db.execute('SELECT state FROM operations').fetchone()[0] - assert state=='unknown' - assert fixture.journal.db.execute("SELECT COUNT(*) FROM calls WHERE outcome='pending'").fetchone()[0]==1 - assert fixture.call('GET','sys/policies/acl/'+fixture.native.policy_name)[0]==404 - policy_path=fixture.compiler.before_call(fixture.native.plan,1).path - assert fixture.call('GET',policy_path)[0]==404 - with pytest.raises(Exception):invoke(fixture) - assert fixture.approval.calls.count(('POST','/v1/approvals/'+fixture.approval_id+'/consume'))==1 - - -def test_actual_unadmitted_policy_refuses_before_cas_and_native(owner,tmp_path): - with whole_fixture(owner,tmp_path,actual_policy=True) as fixture: - with pytest.raises(Exception):invoke(fixture) - assert fixture.approval.engine.get(fixture.approval_id).status=='approved' - assert not any(method=='POST' and path.endswith('/consume') for method,path in fixture.approval.calls) - assert fixture.journal.db.execute('SELECT COUNT(*) FROM operations').fetchone()[0]==0 - assert fixture.call('GET','sys/policies/acl/'+fixture.native.policy_name)[0]==404 - - -def test_entry_purpose_substitution_refuses_before_authorizer_or_cas(owner,tmp_path): - with whole_fixture(owner,tmp_path) as fixture: - fixture.entry=replace(fixture.entry,approval={**fixture.entry.approval,'purpose':'caller-substituted-purpose'}) - before=list(fixture.approval.calls) - with pytest.raises(Refused,match='approval metadata differs'):invoke(fixture) - assert fixture.approval.calls==before - assert fixture.journal.db.execute('SELECT COUNT(*) FROM operations').fetchone()[0]==0 - assert fixture.call('GET','sys/policies/acl/'+fixture.native.policy_name)[0]==404 - - -def test_catalogue_bool_integer_alias_refuses_before_authorizer(owner,tmp_path): - with whole_fixture(owner,tmp_path) as fixture: - assert fixture.entry.delivery_auth['secret_id_num_uses']==1 - fixture.entry=replace(fixture.entry,delivery_auth={**fixture.entry.delivery_auth,'secret_id_num_uses':True}) - before=list(fixture.approval.calls) - with pytest.raises(Refused,match='catalogue does not match'):invoke(fixture) - assert fixture.approval.calls==before - assert fixture.journal.db.execute('SELECT COUNT(*) FROM operations').fetchone()[0]==0 diff --git a/tests/test_task_execution_native_budget.py b/tests/test_task_execution_native_budget.py deleted file mode 100644 index 75cc126..0000000 --- a/tests/test_task_execution_native_budget.py +++ /dev/null @@ -1,167 +0,0 @@ -"""Absolute native budgets/source pins; no actual-v2 positive admission claim.""" -from datetime import datetime,timedelta,timezone -from pathlib import Path -import signal -import sys -import threading -import time -from types import SimpleNamespace - -import pytest -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from test_task_execution_native_fixture import owner,bao_dev,compiler -from task_execution_native_fixture import NativeFixtureAdapter,_absolute_budget -from task_execution_journal import Refused - - -def test_absolute_budget_interrupts_source_work_and_restores_timer(): - before=signal.getitimer(signal.ITIMER_REAL) - started=time.monotonic() - with pytest.raises(Refused): - with _absolute_budget(.03):time.sleep(.5) - assert time.monotonic()-started<.3 - assert signal.getitimer(signal.ITIMER_REAL)==before - - -def test_nested_budget_cannot_refresh_outer_deadline(): - started=time.monotonic() - with pytest.raises(Refused): - with _absolute_budget(.05): - time.sleep(.02) - with _absolute_budget(1):time.sleep(.5) - assert time.monotonic()-started<.3 - - -def test_absolute_budget_rejects_foreign_threads(): - refused=[] - def run(): - try: - with _absolute_budget(1):pass - except Refused:refused.append(True) - worker=threading.Thread(target=run);worker.start();worker.join(2) - assert refused==[True] - - -def test_slow_compiler_is_interrupted_before_native_effect(): - native=object.__new__(NativeFixtureAdapter) - native.deadline=datetime.now(timezone.utc)+timedelta(seconds=5.05) - native.monotonic_deadline=time.monotonic()+5.05 - native.binding='synthetic';native.plan=None;native.task=object();native._next_metadata=0 - native.journal=SimpleNamespace(_held=True,_admission='synthetic',_consumed_admission='synthetic',observe=lambda binding:{'state':'applying'}) - native.compiler=SimpleNamespace(pins=[],before_call=lambda *args:time.sleep(.5)) - sent=[];native._business=lambda *args,**kwargs:sent.append(True) - with pytest.raises(Refused):native.metadata_call(0) - assert sent==[] - - -@pytest.mark.parametrize('phase',['headers','body','body-and-slow-close']) -def test_absolute_transport_budget_interrupts_drip(phase,monkeypatch): - from http.server import BaseHTTPRequestHandler,ThreadingHTTPServer - class Drip(BaseHTTPRequestHandler): - def log_message(self,*args):pass - def do_GET(self): - if phase=='headers': - try: - for value in b'HTTP/1.1 200 OK\r\nX-Drip: '+b' '*1000: - self.connection.sendall(bytes([value]));time.sleep(.05) - except (BrokenPipeError,ConnectionResetError):pass - return - self.send_response(200);self.send_header('Content-Type','application/json');self.send_header('Content-Length','1000');self.end_headers() - try: - for _ in range(1000): - self.wfile.write(b' ');self.wfile.flush();time.sleep(.05) - except (BrokenPipeError,ConnectionResetError):pass - server=ThreadingHTTPServer(('127.0.0.1',0),Drip) - server.daemon_threads=True - thread=threading.Thread(target=server.serve_forever,daemon=True);thread.start() - native=object.__new__(NativeFixtureAdapter);native.host='127.0.0.1';native.port=server.server_port - native.admission=SimpleNamespace(server_process=SimpleNamespace(poll=lambda:None)) - if phase=='body-and-slow-close': - import http.client - original_close=http.client.HTTPConnection.close - def slow_close(connection): - original_close(connection);time.sleep(5) - monkeypatch.setattr(http.client.HTTPConnection,'close',slow_close) - start=time.monotonic() - try: - with pytest.raises(Refused):native._http('GET','drip','synthetic') - assert time.monotonic()-start<2.6 - finally:server.shutdown();server.server_close();thread.join(2) - - -def test_cleanup_remains_independent_but_absolute_after_business_expiry(owner,monkeypatch): - from test_task_execution_native_fixture import admit - from task_execution_cleanup import run_cleanup - native,journal,binding,call,*_=owner - with journal.locked(): - admit(native,journal,binding);native.materialize_grant() - original=native._http - def stalled(method,path,token,body=b''): - if path=='auth/token/revoke-self' and token==native.task.token:time.sleep(5) - return original(method,path,token,body) - monkeypatch.setattr(native,'_http',stalled) - native.monotonic_deadline=time.monotonic()-1 - native._task_monotonic_deadline=time.monotonic()-1 - started=time.monotonic();result=run_cleanup(journal,binding,native) - assert time.monotonic()-started<3 - assert not result['confirmed'] - assert [x['outcome'] for x in result['obligations']]==['unconfirmed','confirmed','confirmed'] - assert journal.observe(binding)['external_outcome_unknown'] - # Explicit independent synthetic owner recovery, never adapter retry. - assert call('POST','auth/token/revoke-accessor',{'accessor':native.task.accessor})[0] in {200,204} - - -def test_absolute_budget_invalid_input_and_conflicting_timer_refuse(): - original_handler=signal.getsignal(signal.SIGALRM) - original_timer=signal.getitimer(signal.ITIMER_REAL) - for value in [True,None,0,-1,float('nan'),float('inf')]: - with pytest.raises(Refused): - with _absolute_budget(value):pass - assert signal.getsignal(signal.SIGALRM)==original_handler - assert signal.getitimer(signal.ITIMER_REAL)==original_timer - try: - signal.signal(signal.SIGALRM,lambda *args:None) - signal.setitimer(signal.ITIMER_REAL,1) - with pytest.raises(Refused): - with _absolute_budget(.1):pass - assert signal.getitimer(signal.ITIMER_REAL)[0]>0 - finally: - signal.setitimer(signal.ITIMER_REAL,0) - signal.signal(signal.SIGALRM,original_handler) - - -def test_actual_credential_bound_interrupts_before_later_global_deadline(): - native=object.__new__(NativeFixtureAdapter) - native.deadline=datetime.now(timezone.utc)+timedelta(minutes=10) - native.monotonic_deadline=time.monotonic()+600 - native._task_wall_deadline=datetime.now(timezone.utc)+timedelta(seconds=5.05) - native._task_monotonic_deadline=time.monotonic()+5.05 - native.binding='synthetic';native.plan=None;native.task=object();native._next_metadata=0 - native.journal=SimpleNamespace(_held=True,_admission='synthetic',_consumed_admission='synthetic',observe=lambda binding:{'state':'applying'}) - native.compiler=SimpleNamespace(pins=[],before_call=lambda *args:time.sleep(.5)) - sent=[];native._business=lambda *args,**kwargs:sent.append(True) - started=time.monotonic() - with pytest.raises(Refused):native.metadata_call(0) - assert time.monotonic()-started<.3 and sent==[] - - - -@pytest.mark.parametrize('case',['valid','drift','writable','symlink','fifo','oversize']) -def test_exact_owner_source_bundle_is_bounded_and_fails_closed(tmp_path,case): - import hashlib - import os - from task_execution_mediator import _source_bundle - source=tmp_path/'owner-source.py';source.write_bytes(b'# independently installed source\n');source.chmod(0o600) - roles=['engine_dispatcher','platform_mediator','typed_ccr','policy_auth'] - item={'path':str(source),'sha256':'sha256:'+hashlib.sha256(source.read_bytes()).hexdigest()} - descriptor={'source_bundle':{role:dict(item) for role in roles}} - if case=='valid':_source_bundle(descriptor);return - if case=='drift':source.write_bytes(b'# drift\n') - elif case=='writable':source.chmod(0o666) - elif case=='symlink': - target=tmp_path/'target.py';source.rename(target);source.symlink_to(target) - elif case=='fifo':source.unlink();os.mkfifo(source,0o600) - elif case=='oversize':source.write_bytes(b'X'*1048577) - started=time.monotonic() - with pytest.raises(Refused):_source_bundle(descriptor) - assert time.monotonic()-started<.5 diff --git a/tests/test_task_execution_native_fixture.py b/tests/test_task_execution_native_fixture.py deleted file mode 100644 index 4c54313..0000000 --- a/tests/test_task_execution_native_fixture.py +++ /dev/null @@ -1,324 +0,0 @@ -"""Actual disposable OpenBao effects; all identities/approvals are synthetic. - -These tests do not contact estate OpenBao or claim production owner acceptance. -""" -import hashlib -from dataclasses import replace -import yaml -import http.client -import json -import shutil -import socket -import subprocess -import time -from datetime import datetime,timedelta,timezone -from pathlib import Path -import sys - -import pytest - -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from test_task_execution_plan import compiler,request,m -from task_execution_journal import Binding,Cleanup,Journal,Refused -from task_execution_cleanup import run_cleanup -from task_execution_native_fixture import FixtureAdmission,NativeFixtureAdapter,PrivateSession -import task_execution_grant as grant - - -@pytest.fixture -def bao_dev(): - executable=shutil.which('bao') - if not executable: pytest.skip('OpenBao executable unavailable') - with socket.socket() as sock: - sock.bind(('127.0.0.1',0));port=sock.getsockname()[1] - root='synthetic-disposable-fixture-root' - process=subprocess.Popen([executable,'server','-dev','-dev-no-store-token', - '-dev-root-token-id='+root,'-dev-listen-address=127.0.0.1:'+str(port)], - stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL) - def call(method,path,body=None,token=root): - conn=http.client.HTTPConnection('127.0.0.1',port,timeout=2) - try: - conn.request(method,'/v1/'+path,body=json.dumps(body) if body is not None else None, - headers={'X-Vault-Token':token,'Content-Type':'application/json'}) - response=conn.getresponse();raw=response.read() - return response.status,json.loads(raw) if raw else {} - finally: conn.close() - for _ in range(100): - if process.poll() is not None: pytest.fail('disposable backend exited') - try: - if call('GET','sys/health')[0]==200: break - except OSError: pass - time.sleep(0.05) - else: - process.kill();process.wait();pytest.fail('disposable backend failed to start') - try: - yield 'http://127.0.0.1:'+str(port),root,call,process - finally: - process.terminate() - try: process.wait(timeout=5) - except subprocess.TimeoutExpired: process.kill();process.wait() - - -@pytest.fixture -def owner(compiler,bao_dev,tmp_path): - origin,root,call,process=bao_dev - assert call('POST','sys/auth/kubernetes',{'type':'kubernetes'})[0] in {200,204} - hcl='path "auth/token/revoke-self" { capabilities = ["update"] }' - assert call('PUT','sys/policies/acl/fixture-self-only',{'policy':hcl})[0] in {200,204} - status,result=call('POST','auth/token/create',{'policies':['fixture-self-only'],'no_default_policy':True,'renewable':False,'ttl':'300s','num_uses':8}) - assert status==200 - identity=PrivateSession(result['auth']['client_token'],result['auth']['accessor']) - req=request(compiler);plan=compiler.compile(req) - descriptor={'contract_version':'SECRETS-EXEC-20261011-v1','operation_id':req['operation_id'], - 'operation':'metadata-apply-ccr','execution_actor':'service:secrets-engine', - 'ccr_id':compiler._derive()[0]['id'],'ccr_immutable_sha256':req['ccr_sha256'], - 'execution_grant_ccr_id':'FIXTURE-GRANT-NOT-PRODUCTION','grant_immutable_sha256':'sha256:'+'f'*64, - 'catalog_sha256':req['catalog_sha256'], - 'source_bundle':{role:{'path':'/fixture/'+role,'sha256':'sha256:'+'d'*64} for role in ['engine_dispatcher','platform_mediator','typed_ccr','policy_auth']}, - 'backend_origin':origin,'cluster_uid':'disposable-fixture-only', - 'operations':req['operations'],'operation_deadline':(datetime.now(timezone.utc)+timedelta(seconds=240)).isoformat(), - 'backend_budget':{'ttl_seconds':120,'uses':12},'expected_before':'sha256:'+'0'*64,'expected_after':'sha256:'+'1'*64} - # Independently installed synthetic expected state, before any native write. - policy_call=compiler.before_call(plan,1);role_call=compiler.before_call(plan,4) - body=json.loads(role_call.payload) - before=m.encoded({policy_call.path:None,role_call.path:None}) - after=m.encoded({policy_call.path:{'cas_required':False,'name':policy_call.path.rsplit('/',1)[1], - 'policy':json.loads(policy_call.payload)['policy'],'version':1},role_call.path:{ - 'alias_name_source':'serviceaccount_uid','bound_service_account_names':body['bound_service_account_names'], - 'bound_service_account_namespace_selector':'','bound_service_account_namespaces':body['bound_service_account_namespaces'], - 'policies':body['policies'].split(','),'token_bound_cidrs':[],'token_explicit_max_ttl':0, - 'token_max_ttl':0,'token_no_default_policy':False,'token_num_uses':0,'token_period':0, - 'token_policies':body['policies'].split(','),'token_strictly_bind_ip':False, - 'token_ttl':900,'token_type':'default','ttl':900}}) - descriptor.update(expected_before=m.tagged(before),expected_after=m.tagged(after)) - # Exercise the actual consumer closed descriptor parser as well; source pins, - # grant/expected-state digests remain deliberately synthetic owner fixtures. - from secrets_engine.execution_contract import ExecutionContract - descriptor=ExecutionContract.parse(descriptor).as_dict() - # Install an independently pinned synthetic grant source and its pure - # validator. No real CCR is written or approved in the owner checkout. - grantfile=compiler.config.platform_repo/'credential-change-requests/CCR-FIXTURE-GRANT.yaml' - grantfile.write_text('placeholder: true\n') - grantcode=Path(grant.__file__).resolve() - pins=dict(compiler.config.source_pins) - pins.update({str(grantfile):m.tagged(grantfile.read_bytes()),str(grantcode):m.tagged(grantcode.read_bytes())}) - compiler=m.Compiler(replace(compiler.config,source_pins=tuple(pins.items()),grant_filename=grantfile.name)) - plan=compiler.compile(req) - expected_execution,expected_openbao=compiler.grant_effects(plan,descriptor) - record={'id':descriptor['execution_grant_ccr_id'],'kind':'credential-change-request', - 'schema_version':1,'request_type':'task-execution-grant','title':'Synthetic disposable fixture grant', - 'status':'approved','created':'2026-10-16','updated':'2026-10-16', - 'requester':{'agent':'fixture','reason':'Synthetic disposable proof; no founder approval'}, - 'review':{'required':True,'required_approvers':['railiance-platform-owner','secrets-engine-owner','access-engine-owner','approval-engine-owner'],'comments':[]}, - 'target':{'domain':'infotech','tenant':'platform','workload':'secrets-engine','environment':'production','purpose':'Synthetic metadata primitive proof'}, - 'execution':expected_execution,'openbao':expected_openbao,'evidence':{'events':[]}} - grantfile.write_text(yaml.safe_dump(record,sort_keys=False)) - pins[str(grantfile)]=m.tagged(grantfile.read_bytes()) - compiler=m.Compiler(replace(compiler.config,source_pins=tuple(pins.items()))) - descriptor['grant_immutable_sha256']=grant.immutable_digest(record) - installed=m.encoded(descriptor) - plan=compiler.bind_execution(compiler.compile(req),descriptor,installed) - binding=Binding('SYNTHETIC-APPROVAL-NOT-PRODUCTION',req['request_digest'],req['operation_id'],plan.content_digest) - state=tmp_path/'private-journal';state.mkdir(mode=0o700);journal=Journal(state) - admission=FixtureAdmission(installed,before,after,root,identity,process) - native=NativeFixtureAdapter(compiler,plan,journal,binding,admission) - try: yield native,journal,binding,call,compiler,descriptor - finally: journal.close() - - -def admit(native,journal,binding): - # Synthetic postconsume transition is fixture setup, not served approval proof. - journal.reserve(binding,native.cleanup) - journal.transition(binding,'reserved','consume-confirmed') - journal.transition(binding,'consume-confirmed','authenticating') - journal.transition(binding,'authenticating','grant-issuing') - - -def test_native_fixed_grant_metadata_denials_and_confirmed_cleanup(owner): - native,journal,binding,call,compiler,descriptor=owner - with journal.locked(): - admit(native,journal,binding) - info=native.materialize_grant() - assert info['issued'] and info['ttl_ceiling_seconds']==120 and info['use_ceiling']==12 - journal.transition(binding,'grant-issued','applying') - baseline=native.metadata_call(0);assert baseline[0]==404 - assert native.metadata_call(1)[0] in {200,204} - policy=native.metadata_call(2);assert policy[0]==200 - assert policy[1]['data']['policy'].rstrip()==json.loads(compiler.before_call(native.plan,1).payload)['policy'].rstrip() - assert native.metadata_call(3)[0]==404 - assert native.metadata_call(4)[0] in {200,204} - role=native.metadata_call(5);assert role[0]==200 - # Actual native role metadata correspondence for fixture's known K8s body. - expected=json.loads(compiler.before_call(native.plan,4).payload) - assert role[1]['data']['bound_service_account_names']==expected['bound_service_account_names'] - assert role[1]['data']['bound_service_account_namespaces']==expected['bound_service_account_namespaces'] - assert role[1]['data']['token_policies']==expected['policies'].split(',') - assert len(native.verify_metadata())==64 - # Root probes native capabilities without spending the limited task token. - for path in ['sys/policies/acl/sibling','auth/kubernetes/role/sibling','secret/data/sibling','auth/token/create','sys/mounts/secret','auth/approle/role/sibling/secret-id']: - status,data=call('POST','sys/capabilities',{'token':native.task.token,'paths':[path]}) - assert status==200 and data['capabilities']==['deny'] - task_accessor=native.task.accessor;identity_accessor=native.admission.identity_session.accessor - result=run_cleanup(journal,binding,native) - # Assert native absence before summary so fixture failures identify protocol semantics. - for accessor in [task_accessor,identity_accessor]: - status,data=call('POST','auth/token/lookup-accessor',{'accessor':accessor}) - assert status==400 and data.get('errors') in (['invalid accessor'],['1 error occurred:\n\t* invalid accessor\n\n']) - assert result['confirmed'] - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - assert journal.observe(binding)['state']=='verified' - with pytest.raises(Refused): native.metadata_call(6) - with pytest.raises(Refused): run_cleanup(journal,binding,native) - - -def test_no_consume_no_native_grant_and_changed_digest_no_cleanup(owner): - native,journal,binding,call,*_=owner - with journal.locked(): - journal.reserve(binding,native.cleanup) - with pytest.raises(Refused):native.materialize_grant() - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - with pytest.raises(Refused):native.delete_grant_policy('0'*64) - - -def test_native_cleanup_after_deadline_and_pending_business_uncertainty(owner): - native,journal,binding,call,*_=owner - with journal.locked(): - admit(native,journal,binding);native.materialize_grant() - journal.transition(binding,'grant-issued','applying') - journal.before_call(binding,native._ordinal,'a'*64) - journal.transition(binding,'applying','unknown') - native.monotonic_deadline=time.monotonic()-1 - with pytest.raises(Refused):native.metadata_call(0) - assert run_cleanup(journal,binding,native)['confirmed'] - assert journal.observe(binding)['state']=='unknown' - assert journal.observe(binding)['external_outcome_unknown'] - - -def test_whole_descriptor_and_cleanup_commitment_are_bound(owner): - native,journal,binding,call,compiler,descriptor=owner - base=compiler.compile(native.meta['request']) - changed=dict(descriptor,cluster_uid='different-cluster') - with pytest.raises(m.Refused):compiler.bind_execution(base,changed,native.admission.descriptor) - data=native.plan.metadata();data['cleanup'][0]['call_digest']='9'*64 - with pytest.raises(m.Refused):compiler.before_call(m.Plan(m.encoded(data)),0) - with pytest.raises(Refused):NativeFixtureAdapter(compiler,m.Plan(m.encoded(data)),journal,binding,native.admission) - - -def test_source_drift_after_admission_prevents_mint_but_allows_native_cleanup(owner): - native,journal,binding,call,compiler,*_=owner - with journal.locked(): - admit(native,journal,binding) - compiler.ccr.write_bytes(compiler.ccr.read_bytes()+b'\n# changed owner source\n') - with pytest.raises(m.Refused):native.materialize_grant() - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - assert run_cleanup(journal,binding,native)['confirmed'] - - -def test_actual_native_write_then_lost_response_stays_unknown_and_cleans(owner): - native,journal,binding,call,compiler,*_=owner - with journal.locked(): - admit(native,journal,binding);native.materialize_grant() - journal.transition(binding,'grant-issued','applying') - assert native.metadata_call(0)[0]==404 - original=native._http - target=compiler.before_call(native.plan,1).path - def lost_response(method,path,token,body=b''): - result=original(method,path,token,body) - if method=='PUT' and path==target: - raise Refused('synthetic lost response after real native write') - return result - native._http=lost_response - with pytest.raises(Refused):native.metadata_call(1) - assert call('GET',target)[0]==200 # actual side effect despite missing confirmation - assert journal.observe(binding)['state']=='unknown' - assert journal.observe(binding)['external_outcome_unknown'] - assert run_cleanup(journal,binding,native)['confirmed'] - assert journal.observe(binding)['state']=='unknown' - with pytest.raises(Refused):native.metadata_call(1) - assert call('GET',target)[0]==200 # cleanup never rolls back/reapplies business state - - -def test_actual_native_policy_retirement_does_not_prove_failed_token_revoke(owner): - native,journal,binding,call,*_=owner - with journal.locked(): - admit(native,journal,binding);native.materialize_grant() - original=native._http - def refuse_task_revoke(method,path,token,body=b''): - if method=='POST' and path=='auth/token/revoke-self' and token==native.task.token: - return 503,{'errors':['synthetic cleanup transport failure']} - return original(method,path,token,body) - native._http=refuse_task_revoke - result=run_cleanup(journal,binding,native) - assert not result['confirmed'] - assert [item['outcome'] for item in result['obligations']]==['unconfirmed','confirmed','confirmed'] - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - # Actual owner lookup demonstrates token still exists despite deleted ACL. - status,data=call('POST','auth/token/lookup-accessor',{'accessor':native.task.accessor}) - assert status==200 and data['data']['policies']==[native.policy_name] - with pytest.raises(Refused):run_cleanup(journal,binding,native) - # Explicit synthetic owner recovery, not adapter retry/resumption. - assert call('POST','auth/token/revoke-accessor',{'accessor':native.task.accessor})[0] in {200,204} - - -def test_nonloopback_estate_origin_is_refused_before_native_transport(owner): - native,journal,binding,call,compiler,descriptor=owner - changed=dict(descriptor,backend_origin='https://estate.invalid:8200') - # The owner grant now also binds origin. Build a matching synthetic grant so - # this case reaches the adapter's independent nonloopback refusal; ordinary - # origin substitution is already refused earlier by the grant join. - record=yaml.safe_load(compiler.grant_record.read_text()) - record['execution'],record['openbao']=compiler.grant_effects(compiler.compile(native.meta['request']),changed) - compiler.grant_record.write_text(yaml.safe_dump(record)) - pins=dict(compiler.config.source_pins);pins[str(compiler.grant_record)]=m.tagged(compiler.grant_record.read_bytes()) - changed_compiler=m.Compiler(replace(compiler.config,source_pins=tuple(pins.items()))) - changed['grant_immutable_sha256']=grant.immutable_digest(record) - installed=m.encoded(changed) - plan=changed_compiler.bind_execution(changed_compiler.compile(native.meta['request']),changed,installed) - changed_binding=Binding(binding.approval_id,binding.request_digest,binding.operation_id,plan.content_digest) - admission=replace(native.admission,descriptor=installed) - with pytest.raises(Refused):NativeFixtureAdapter(changed_compiler,plan,journal,changed_binding,admission) - - -def test_changed_grant_immutable_join_refuses_before_any_native_grant(owner): - native,journal,binding,call,compiler,descriptor=owner - changed=dict(descriptor,grant_immutable_sha256='sha256:'+'0'*64) - with pytest.raises(m.Refused):compiler.bind_execution(compiler.compile(native.meta['request']),changed,m.encoded(changed)) - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - - -def test_grant_body_repin_cannot_replace_independently_derived_effects(owner): - native,journal,binding,call,compiler,descriptor=owner - record=yaml.safe_load(compiler.grant_record.read_text()) - record['openbao']['policy']['hcl']='path "*" { capabilities = ["sudo"] }' - compiler.grant_record.write_text(yaml.safe_dump(record)) - pins=dict(compiler.config.source_pins);pins[str(compiler.grant_record)]=m.tagged(compiler.grant_record.read_bytes()) - changed_compiler=m.Compiler(replace(compiler.config,source_pins=tuple(pins.items()))) - changed_descriptor=dict(descriptor,grant_immutable_sha256=grant.immutable_digest(record)) - with pytest.raises(m.Refused):changed_compiler.bind_execution(changed_compiler.compile(native.meta['request']),changed_descriptor,m.encoded(changed_descriptor)) - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - - -def test_proposed_source_grant_never_materializes_even_with_matching_digest(owner): - native,journal,binding,call,compiler,descriptor=owner - record=yaml.safe_load(compiler.grant_record.read_text());record['status']='proposed' - compiler.grant_record.write_text(yaml.safe_dump(record)) - pins=dict(compiler.config.source_pins);pins[str(compiler.grant_record)]=m.tagged(compiler.grant_record.read_bytes()) - changed_compiler=m.Compiler(replace(compiler.config,source_pins=tuple(pins.items()))) - changed_descriptor=dict(descriptor,grant_immutable_sha256=grant.immutable_digest(record)) - installed=m.encoded(changed_descriptor) - plan=changed_compiler.bind_execution(changed_compiler.compile(native.meta['request']),changed_descriptor,installed) - changed_binding=Binding(binding.approval_id,binding.request_digest,binding.operation_id,plan.content_digest) - admission=replace(native.admission,descriptor=installed) - with pytest.raises(Refused):NativeFixtureAdapter(changed_compiler,plan,journal,changed_binding,admission) - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - - -def test_grant_source_drift_after_admission_prevents_protected_effect(owner): - native,journal,binding,call,compiler,*_=owner - with journal.locked(): - admit(native,journal,binding) - record=yaml.safe_load(compiler.grant_record.read_text());record['execution']['cleanup'][0]['call_digest']='f'*64 - compiler.grant_record.write_text(yaml.safe_dump(record)) - with pytest.raises(m.Refused):native.materialize_grant() - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - assert run_cleanup(journal,binding,native)['confirmed'] diff --git a/tests/test_task_execution_plan.py b/tests/test_task_execution_plan.py deleted file mode 100644 index bb04079..0000000 --- a/tests/test_task_execution_plan.py +++ /dev/null @@ -1,195 +0,0 @@ -import importlib.util,json,hashlib,shutil,sys,uuid,os -from pathlib import Path -import pytest,yaml -SOURCE=Path(__file__).resolve().parents[1]/'scripts/task_execution_plan.py' -sys.path.insert(0,str(SOURCE.parent)) -import task_execution_plan as m -PLATFORM=Path(os.environ.get('PLATFORM_PLAN_TEST_SOURCE_ROOT',str(SOURCE.parents[1]))) -SECRETS=Path(os.environ.get('SECRETS_ENGINE_SOURCE_ROOT',str(PLATFORM.parent/'secrets-engine'))) -@pytest.fixture -def compiler(tmp_path): - repo=tmp_path/'platform';(repo/'scripts').mkdir(parents=True);(repo/'credential-change-requests').mkdir();(repo/'openbao/policies').mkdir(parents=True) - shutil.copyfile(PLATFORM/'scripts/credential-change.py',repo/'scripts/credential-change.py') - ccr=yaml.safe_load((PLATFORM/'credential-change-requests/CCR-2026-0002-issue-core-ingestion-api-key.yaml').read_text()) - ccr['status']='approved';ccr['access_frontdoor'].update(readiness='approved-pending-apply',resolvable=False) - ccrfile=repo/'credential-change-requests/CCR-2026-0002-fixture.yaml';ccrfile.write_text(yaml.safe_dump(ccr)) - policy=repo/ccr['openbao']['policy_file'];shutil.copyfile(PLATFORM/ccr['openbao']['policy_file'],policy) - catalogdir=tmp_path/'catalog';catalogdir.mkdir();catalog=yaml.safe_load((SECRETS/'catalog/issue-core-ingestion-api-key.yaml').read_text()) - catalog['delivery_auth']['management']='existing' - catalogfile=catalogdir/'issue-core-ingestion-api-key.yaml';catalogfile.write_text(yaml.safe_dump(catalog)) - paths=[SOURCE,repo/'scripts/credential-change.py',ccrfile,policy,catalogfile,*[SECRETS/'src/secrets_engine'/name for name in ['catalog.py','__init__.py','errors.py','redact.py']]] - pins=tuple((str(p),m.tagged(p.read_bytes())) for p in paths) - return m.Compiler(m.OwnerConfig(repo,SECRETS,ccrfile.name,catalogfile.name,catalogdir,pins)) - -def request(c): - value=c.proposal();value.pop('catalog_target');value.update(operation_id=str(uuid.uuid4()),request_digest='sha256:'+'a'*64);return value - -def test_compiles_real_owner_generated_bodies_and_fixed_six_calls(compiler): - req=request(compiler);plan=compiler.compile(req) - calls=[compiler.before_call(plan,i) for i in range(6)] - assert [x.method for x in calls]==['GET','PUT','GET','GET','PUT','GET'] - assert len({x.path for x in calls})==2 - policy=json.loads(calls[1].payload)['policy'];assert 'capabilities = ["read"]' in policy - role=json.loads(calls[4].payload);assert role['policies']=='workload-kv-read-issue-core-runtime' - assert role['bound_service_account_namespaces']==['external-secrets'] - assert len(plan.content_digest)==64 and not plan.content_digest.startswith('sha256:') - assert all(x.payload_sha256.startswith('sha256:') and len(x.call_digest)==64 for x in calls) - -@pytest.mark.parametrize('field,value',[('action','apply'),('kind','service-jwt-login'),('catalog_id','another'),('ccr_sha256','sha256:'+'b'*64),('catalog_sha256','sha256:'+'b'*64),('request_digest','a'*64)]) -def test_altered_bindings_refuse(compiler,field,value): - req=request(compiler);req[field]=value - with pytest.raises(m.Refused):compiler.compile(req) - -@pytest.mark.parametrize('ordinal',[False,0.0,'0',-1,6]) -def test_fixed_ordinals_refuse(compiler,ordinal): - plan=compiler.compile(request(compiler)) - with pytest.raises(m.Refused):compiler.before_call(plan,ordinal) - -@pytest.mark.parametrize('field,value',[('method','DELETE'),('path','platform/data/workloads/keycape/x'),('payload_sha256','sha256:'+'c'*64)]) -def test_caller_operation_cannot_change_target_body_or_method(compiler,field,value): - req=request(compiler);req['operations'][1][field]=value - with pytest.raises(m.Refused):compiler.compile(req) - -@pytest.mark.parametrize('which',['ccr','catalog','policy','validator']) -def test_owner_source_drift_between_calls_refuses(compiler,which): - plan=compiler.compile(request(compiler));compiler.before_call(plan,0) - path={'ccr':compiler.ccr,'catalog':compiler.catalog,'policy':next(Path(p) for p in compiler.pins if p.endswith('.hcl')),'validator':compiler.validator}[which] - path.write_bytes(path.read_bytes()+b'\n# drift\n') - with pytest.raises(m.Refused):compiler.before_call(plan,1) - -@pytest.mark.parametrize('change',['extra','budget','request']) -def test_direct_plan_constructor_cannot_bypass_recompilation(compiler,change): - plan=compiler.compile(request(compiler));data=plan.metadata() - if change=='extra':data['caller_hcl']='path "*" {}' - elif change=='budget':data['task_token_max_uses']=999 - else:data['request']['operations'][3]['payload_sha256']='sha256:'+'d'*64 - with pytest.raises(m.Refused):compiler.before_call(m.Plan(m.encoded(data)),3) - - -def repin(c,path): - pins=dict(c.config.source_pins);pins[str(path)]=m.tagged(path.read_bytes()) - return m.Compiler(m.OwnerConfig(c.config.platform_repo,c.config.secrets_repo,c.config.ccr_filename,c.config.catalog_filename,c.config.catalog_directory,tuple(pins.items()))) - -@pytest.mark.parametrize('status',['in_flight','proposed','applied','verified','active','cancelled']) -def test_even_re_pinned_nonapproved_owner_record_refuses(compiler,status): - ccr=yaml.safe_load(compiler.ccr.read_text());ccr['status']=status;compiler.ccr.write_text(yaml.safe_dump(ccr)) - with pytest.raises(m.Refused):repin(compiler,compiler.ccr).proposal() - - -def test_source_role_policy_escalation_refuses_even_when_re_pinned(compiler): - ccr=yaml.safe_load(compiler.ccr.read_text());ccr['openbao']['auth']['policies']=['root'];compiler.ccr.write_text(yaml.safe_dump(ccr)) - with pytest.raises(m.Refused):repin(compiler,compiler.ccr).proposal() - - -def test_catalog_hash_agrees_with_actual_consumer_unicode_canonicalization(compiler): - catalog=yaml.safe_load(compiler.catalog.read_text());catalog['consumers'][0]['purpose']='café';compiler.catalog.write_text(yaml.safe_dump(catalog)) - compiler=repin(compiler,compiler.catalog) - from secrets_engine.execution_contract import catalog_target_digest - proposed=compiler.proposal();assert proposed['catalog_sha256']==catalog_target_digest(proposed['catalog_target']) - - -def test_caller_file_path_and_policy_fields_are_refused(compiler): - req=request(compiler);req['file']='/etc/passwd';req['policy']='path "*" {}' - with pytest.raises(m.Refused):compiler.compile(req) - - -def test_actual_inflight_ccr42_is_not_an_admitted_target(): - actual=yaml.safe_load((PLATFORM/'credential-change-requests/CCR-2026-0042-forgejo-netkingdom-oidc-consumer-reader.yaml').read_text()) - assert actual['status']=='in_flight' and 'auth' not in actual['openbao'] - -@pytest.mark.parametrize('kind',['symlink','hardlink','writable']) -def test_owner_policy_file_indirection_or_permissions_refuse(compiler,tmp_path,kind): - req=request(compiler) - path=next(Path(p) for p in compiler.pins if p.endswith('.hcl')) - if kind=='symlink': - copy=tmp_path/'outside-policy';copy.write_bytes(path.read_bytes());path.unlink();path.symlink_to(copy) - elif kind=='hardlink': - import os - os.link(path,tmp_path/'policy-link') - else:path.chmod(0o666) - with pytest.raises(m.Refused):compiler.compile(req) - - -def test_re_pinned_policy_body_escalation_refuses(compiler): - path=next(Path(p) for p in compiler.pins if p.endswith('.hcl')) - path.write_text('path "*" { capabilities = ["sudo", "read", "update"] }\n') - with pytest.raises(m.Refused):repin(compiler,path).proposal() - - -def test_duplicate_owner_yaml_key_refuses_even_when_re_pinned(compiler): - compiler.ccr.write_bytes(compiler.ccr.read_bytes()+b'\nstatus: approved\n') - with pytest.raises(m.Refused):repin(compiler,compiler.ccr).proposal() - - -def test_unconfirmed_claim_binding_refuses_even_when_re_pinned(compiler): - ccr=yaml.safe_load(compiler.ccr.read_text());ccr['openbao']['auth']['bound_claims_confirmed']=False - compiler.ccr.write_text(yaml.safe_dump(ccr)) - with pytest.raises(m.Refused):repin(compiler,compiler.ccr).proposal() - - -def test_catalogue_engine_managed_approle_is_not_silently_generic_apply(compiler): - data=yaml.safe_load(compiler.catalog.read_text());data['delivery_auth']['management']='engine' - compiler.catalog.write_text(yaml.safe_dump(data)) - with pytest.raises(m.Refused):repin(compiler,compiler.catalog).proposal() - - -def test_oidc_role_body_reuses_owner_auth_payload_and_existing_mount(compiler): - ccr=yaml.safe_load(compiler.ccr.read_text()) - source=yaml.safe_load((PLATFORM/'credential-change-requests/CCR-2026-0034.yaml').read_text()) - ccr['openbao']['auth']=source['openbao']['auth'];ccr['openbao']['auth']['role']='synthetic-operator-workload-kv-read' - ccr['openbao']['auth']['policies']=[ccr['openbao']['policy_name']] - compiler.ccr.write_text(yaml.safe_dump(ccr));compiler=repin(compiler,compiler.ccr) - plan=compiler.compile(request(compiler));call=compiler.before_call(plan,4) - role=json.loads(call.payload) - assert call.path=='auth/netkingdom/role/synthetic-operator-workload-kv-read' - assert role['role_type']=='oidc' and role['policies']=='workload-kv-read-issue-core-runtime' - assert role['bound_claims']==source['openbao']['auth']['bound_claims'] - - -def test_cached_catalogue_code_cannot_replace_verified_source(compiler, monkeypatch): - import secrets_engine.catalog as catalogue - expected = compiler.proposal() - - def stale_validator(*args, **kwargs): - raise AssertionError('cached code differs from the pinned source') - - monkeypatch.setattr(catalogue, 'validate_entry', stale_validator) - assert compiler.proposal() == expected - - -def test_cached_catalogue_dependency_cannot_replace_verified_source(compiler, monkeypatch): - import secrets_engine.catalog as catalogue - expected = compiler.proposal() - - def stale_redactor(*args, **kwargs): - raise AssertionError('cached dependency differs from the pinned source') - - monkeypatch.setattr(catalogue, 'looks_secret', stale_redactor) - assert compiler.proposal() == expected - - -def test_cached_validator_cannot_admit_invalid_pinned_catalogue(compiler, monkeypatch): - import secrets_engine.catalog as catalogue - accepted = catalogue.validate_entry(yaml.safe_load(compiler.catalog.read_text()), source='fixture') - data = yaml.safe_load(compiler.catalog.read_text()) - del data['rotation'] - compiler.catalog.write_text(yaml.safe_dump(data)) - compiler = repin(compiler, compiler.catalog) - monkeypatch.setattr(catalogue, 'validate_entry', lambda *a, **kw: accepted) - with pytest.raises(m.Refused): - compiler.proposal() - - -@pytest.mark.parametrize('invalid', [False, True]) -def test_snapshot_modules_removed_without_replacing_ambient_modules(compiler, invalid): - import secrets_engine.catalog - before = {k: v for k, v in sys.modules.items() if k.startswith('secrets_engine')} - if invalid: - compiler.catalog.write_text('id: incomplete\n') - compiler = repin(compiler, compiler.catalog) - with pytest.raises(m.Refused): - compiler.proposal() - else: - compiler.proposal() - assert {k: v for k, v in sys.modules.items() if k.startswith('secrets_engine')} == before - assert not any(k.startswith('_platform_catalogue_') for k in sys.modules) diff --git a/tests/test_task_execution_readback.py b/tests/test_task_execution_readback.py deleted file mode 100644 index 5bc0b5b..0000000 --- a/tests/test_task_execution_readback.py +++ /dev/null @@ -1,127 +0,0 @@ -"""Exact disposable native acceptance, partial effects and restart refusal.""" -import json -from dataclasses import replace -import pytest -from test_task_execution_native_fixture import owner,bao_dev,compiler,admit -from task_execution_native_fixture import NativeFixtureAdapter -from task_execution_journal import Refused,Journal -from task_execution_cleanup import run_cleanup - - -def start(native,journal,binding): - admit(native,journal,binding);native.materialize_grant() - journal.transition(binding,'grant-issued','applying') - - -def test_expectation_digest_pin_rejected_before_reserve(owner): - n,j,b,c,compiler,*_=owner - with pytest.raises(Refused,match='expected-state digest'): - NativeFixtureAdapter(compiler,n.plan,j,b,replace(n.admission,expected_after=b'{}')) - assert c('GET','sys/policies/acl/'+n.policy_name)[0]==404 - - -@pytest.mark.parametrize('target',[0,3]) -def test_actual_conflicting_baseline_gates_its_target_write(owner,target): - n,j,b,c,compiler,*_=owner - call=compiler.before_call(n.plan,target+1) - existing={'policy':'path "unrelated" { capabilities = ["read"] }'} if target==0 else json.loads(call.payload) - assert c('PUT',call.path,existing)[0] in {200,204} - with j.locked(): - start(n,j,b) - for i in range(target): n.metadata_call(i) - with pytest.raises(Refused,match='baseline or readback'):n.metadata_call(target) - assert j.observe(b)['state']=='unknown' - if target==3: - # Policy effect already happened: this is deliberately no aggregate CAS. - assert c('GET',compiler.before_call(n.plan,1).path)[1]['data']['policy']==json.loads(compiler.before_call(n.plan,1).payload)['policy'] - assert run_cleanup(j,b,n)['confirmed'] - assert j.observe(b)['state']=='unknown' - - -@pytest.mark.parametrize('target',[2,5]) -def test_actual_readback_drift_never_verifies(owner,target): - n,j,b,c,compiler,*_=owner - with j.locked(): - start(n,j,b) - for i in range(target):n.metadata_call(i) - call=compiler.before_call(n.plan,target-1) - body=json.loads(call.payload) - if target==2:body['policy']+='\npath "sibling/*" { capabilities = ["read"] }' - else:body['policies']+=',unexpected-policy' - assert c('PUT',call.path,body)[0] in {200,204} - with pytest.raises(Refused,match='baseline or readback'):n.metadata_call(target) - with pytest.raises(Refused):n.verify_metadata() - assert run_cleanup(j,b,n)['confirmed'] - assert j.observe(b)['state']=='unknown' - - -@pytest.mark.parametrize('status,response',[(403,{'errors':[]}), (404,{}),(404,{'errors':['permission denied']}), (200,{'data':None})]) -def test_only_exact_backend_absence_counts(status,response): - with pytest.raises(Refused):NativeFixtureAdapter._metadata_snapshot('auth/kubernetes/role/x',status,response) - - -def test_json_types_and_unknown_role_fields_remain_significant(owner): - n,j,b,c,compiler,*_=owner - original=n._http - def changed(method,path,token,body=b''): - status,response=original(method,path,token,body) - if method=='GET' and path==compiler.before_call(n.plan,5).path and status==200: - response['data']['token_num_uses']=False # False == 0 in Python; never canonical JSON. - return status,response - n._http=changed - with j.locked(): - start(n,j,b) - for i in range(5):n.metadata_call(i) - with pytest.raises(Refused):n.metadata_call(5) - assert run_cleanup(j,b,n)['confirmed'] - assert j.observe(b)['state']=='unknown' - - -def test_reopened_sqlite_cannot_resume_or_recover_memory_handles(owner): - n,j,b,c,compiler,*_=owner - with j.locked(): - start(n,j,b) - n.metadata_call(0);n.metadata_call(1) - # Model loss of the live admission; handles deliberately are not reconstructed. - # The still-running disposable server proves native effect survives reopening. - location=j.directory - assert c('GET',compiler.before_call(n.plan,1).path)[0]==200 - j.db.close() - # Owner journal retains the exact DB path; this is a real reopened SQLite. - reopened=Journal(location) - try: - with reopened.locked(): - assert reopened.observe(b)['dispatch_allowed'] is False - with pytest.raises(Refused):reopened.reserve(b,n.cleanup) - with pytest.raises(Refused):reopened.next_call_ordinal(b) - finally:reopened.close() - # No recovery/cleanup claim: only same-process fixture teardown retains handles. - - -def test_verified_readback_with_real_cleanup_denial_is_not_complete(owner): - n,j,b,c,compiler,*_=owner - with j.locked(): - start(n,j,b) - for i in range(6):n.metadata_call(i) - n.verify_metadata() - # Actual backend removal denies the task's self-revoke capability. - assert c('DELETE','sys/policies/acl/'+n.policy_name)[0] in {200,204} - result=run_cleanup(j,b,n) - assert result['confirmed'] is False - assert j.observe(b)['state']=='verified' - with pytest.raises(Refused):j.transition(b,'verified','cleanup-confirmed','0'*64) - assert c('POST','auth/token/lookup-accessor',{'accessor':n.task.accessor})[0]==200 - - -def test_actual_denied_baseline_cannot_be_caught_then_write_retried(owner): - n,j,b,c,compiler,*_=owner - with j.locked(): - start(n,j,b) - # Task still has update but loses read: real 403 is not absent baseline. - denied=n.hcl.replace('["read", "create", "update"]','["create", "update"]') - assert c('PUT','sys/policies/acl/'+n.policy_name,{'policy':denied})[0] in {200,204} - with pytest.raises(Refused):n.metadata_call(0) - assert j.observe(b)['state']=='unknown' - with pytest.raises(Refused):n.metadata_call(1) - assert c('GET',compiler.before_call(n.plan,1).path)[0]==404 - assert run_cleanup(j,b,n)['confirmed'] diff --git a/tests/test_task_execution_recovery.py b/tests/test_task_execution_recovery.py deleted file mode 100644 index 52b8b61..0000000 --- a/tests/test_task_execution_recovery.py +++ /dev/null @@ -1,320 +0,0 @@ -"""Actual executor process loss against independent disposable OpenBao.""" -from dataclasses import replace -import hashlib -import json -import os -import secrets -from pathlib import Path -import sys -import time - -import pytest - -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from test_task_execution_plan import compiler -from test_task_execution_native_fixture import bao_dev, owner, admit -from task_execution_journal import Journal, Refused -from task_execution_native_fixture import NativeFixtureAdapter -from task_execution_recovery import FixtureRecoveryCustody, recover_fixture_cleanup - - -def protected(owner,tmp_path): - native,journal,binding,call,compiler,descriptor=owner - directory=tmp_path/'separate-owner-custody';directory.mkdir(mode=0o700) - custody=FixtureRecoveryCustody(directory,integrity_key=secrets.token_bytes(32)) - native=NativeFixtureAdapter(compiler,native.plan,journal,binding,native.admission,recovery_custody=custody) - return native,journal,binding,call,custody - - -def crash(native,journal,binding,stage='issued'): - pid=os.fork() - if pid==0: - try: - # The backend belongs to the independent parent, so this child cannot - # waitpid it. Parent retains the actual Popen and checks it on recovery. - native.admission.server_process.poll=lambda:None - native.journal=Journal(journal.directory) - with native.journal.locked(): - admit(native,native.journal,binding) - if stage=='mint-response-lost': - original=native._http - def lost(method,path,token,body=b''): - result=original(method,path,token,body) - if path=='auth/token/create':os._exit(73) - return result - native._http=lost - native.materialize_grant() - native.journal.transition(binding,'grant-issued','applying') - if stage=='business-response-lost': - native.metadata_call(0) - original=native._http - def lost(method,path,token,body=b''): - result=original(method,path,token,body) - if method=='PUT':os._exit(73) - return result - native._http=lost - native.metadata_call(1) - if stage=='cleanup-response-lost': - from task_execution_cleanup import run_cleanup - original=native._http - def lost(method,path,token,body=b''): - result=original(method,path,token,body) - if path=='auth/token/revoke-self':os._exit(73) - return result - native._http=lost - run_cleanup(native.journal,binding,native) - os._exit(73) - except BaseException:os._exit(74) - _,status=os.waitpid(pid,0) - assert os.waitstatus_to_exitcode(status)==73 - # Original admission never appears in the independent parent or reopened DB. - assert native.task is None - assert journal._admission is None and journal._consumed_admission is None - - -def permit(native,custody,binding,**kwargs): - return custody.admit(binding,owner_token=kwargs.pop('owner_token',native.admission.owner_token), - server_process=native.admission.server_process,**kwargs) - - -@pytest.mark.parametrize('stage',['issued','business-response-lost','cleanup-response-lost']) -def test_process_loss_then_independent_cleanup_never_resumes_business(owner,tmp_path,stage,monkeypatch,request): - native,journal,binding,call,custody=protected(owner,tmp_path) - crash(native,journal,binding,stage) - journal=Journal(journal.directory) - request.addfinalizer(journal.close) - native.journal=journal - private=custody.read(binding) - assert private['phase']=='issued' and private['task_accessor'] - target=native.compiler.before_call(native.plan,1).path - before=call('GET',target) - with journal.locked(): - with pytest.raises(Refused):journal.reserve(binding,native.cleanup) - with pytest.raises(Refused):native.materialize_grant() - with pytest.raises(Refused):native.metadata_call(0) - with pytest.raises(Refused):native.revoke_task_token(native.cleanup[0].call_digest) - admitted=permit(native,custody,binding) - import task_execution_recovery as recovery_module - sent=[] - original_request=recovery_module.http.client.HTTPConnection.request - def tracked(connection,method,url,*args,**kwargs): - sent.append((method,url)) # metadata only; never capture body/headers - return original_request(connection,method,url,*args,**kwargs) - with monkeypatch.context() as patch: - patch.setattr(recovery_module.http.client.HTTPConnection,'request',tracked) - result=recover_fixture_cleanup(journal,admitted) - assert all((method,path) in { - ('POST','/v1/auth/token/revoke-accessor'),('POST','/v1/auth/token/lookup-accessor'), - ('GET','/v1/sys/policies/acl/'+native.policy_name), - ('DELETE','/v1/sys/policies/acl/'+native.policy_name)} for method,path in sent) - assert result['confirmed'] - for accessor in [private['task_accessor'],private['identity_accessor']]: - assert call('POST','auth/token/lookup-accessor',{'accessor':accessor})[0]==400 - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==404 - after=call('GET',target) - assert after[0]==before[0] and after[1].get('data')==before[1].get('data') # request IDs vary - assert journal.observe(binding)['dispatch_allowed'] is False - if stage!='issued':assert journal.observe(binding)['external_outcome_unknown'] - if stage=='cleanup-response-lost': - original=json.loads(journal.db.execute('SELECT original FROM recovery_attempts').fetchone()[0]) - assert original[0]['outcome']=='pending' - assert journal.db.execute("SELECT COUNT(*) FROM cleanup_calls WHERE outcome='pending'").fetchone()[0]==1 - with pytest.raises(Refused):recover_fixture_cleanup(journal,admitted) - public=(journal.directory/'journal.sqlite').read_bytes() - for value in [private['task_accessor'],private['identity_accessor'],native.admission.owner_token]: - assert value.encode() not in public - assert 'client_token' not in json.dumps(private) - assert 'token' not in repr(admitted) and private['task_accessor'] not in repr(admitted) - - -def test_process_loss_before_handle_commit_preserves_unknown_mint_debt(owner,tmp_path): - native,journal,binding,call,custody=protected(owner,tmp_path) - crash(native,journal,binding,'mint-response-lost') - assert custody.read(binding)['phase']=='mint-intent' - result=recover_fixture_cleanup(journal,permit(native,custody,binding)) - assert not result['confirmed'] - assert [x['outcome'] for x in result['obligations']]==['owed','confirmed','confirmed'] - assert journal.observe(binding)['external_outcome_unknown'] - assert journal.db.execute('SELECT confirmed FROM recovery_results ORDER BY obligation').fetchall()==[(0,),(1,),(1,)] - - -def test_revoked_expired_wrong_revision_and_public_flag_refuse_before_calls(owner,tmp_path): - native,journal,binding,call,custody=protected(owner,tmp_path);crash(native,journal,binding) - p=permit(native,custody,binding) - custody.revoke(p) - for bad in [p,replace(permit(native,custody,binding),deadline=time.monotonic()-1), - replace(permit(native,custody,binding),private_revision_digest='0'*64),{'verified':True}]: - with pytest.raises(Refused):recover_fixture_cleanup(journal,bad) - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==200 - assert journal.cleanup_status(binding)['obligations'][0]['outcome']=='owed' - - -def test_revoked_recovery_credential_cannot_fake_cleanup_success(owner,tmp_path): - native,journal,binding,call,custody=protected(owner,tmp_path);crash(native,journal,binding) - status,data=call('POST','auth/token/create',{'policies':['root'],'ttl':'30s'}) - assert status==200 - token=data['auth']['client_token'] - assert call('POST','auth/token/revoke',{'token':token})[0] in {200,204} - result=recover_fixture_cleanup(journal,permit(native,custody,binding,owner_token=token)) - assert not result['confirmed'] and all(x['outcome']=='owed' for x in result['obligations']) - assert call('GET','sys/policies/acl/'+native.policy_name)[0]==200 - # A separately admitted owner attempt can recover after the failed attempt; - # neither the failed attempt nor its receipt grants an automatic retry. - assert recover_fixture_cleanup(journal,permit(native,custody,binding))['confirmed'] - assert journal.db.execute('SELECT COUNT(*) FROM recovery_attempts').fetchone()[0]==2 - - -def test_policy_name_reuse_never_deletes_different_policy(owner,tmp_path): - native,journal,binding,call,custody=protected(owner,tmp_path);crash(native,journal,binding) - replacement='path "unrelated" { capabilities = ["read"] }' - assert call('PUT','sys/policies/acl/'+native.policy_name,{'policy':replacement})[0] in {200,204} - result=recover_fixture_cleanup(journal,permit(native,custody,binding)) - assert not result['confirmed'] - assert [x['outcome'] for x in result['obligations']]==['confirmed','confirmed','owed'] - assert call('GET','sys/policies/acl/'+native.policy_name)[1]['data']['policy']==replacement - - -def test_private_custody_permissions_and_identity_binding_fail_closed(owner,tmp_path): - native,journal,binding,call,custody=protected(owner,tmp_path) - path=custody._path(binding);path.chmod(0o644) - with pytest.raises(Refused):custody.read(binding) - path.chmod(0o600) - data=json.loads(path.read_bytes());data['payload']['binding']['operation_id']='other' - path.write_text(json.dumps(data)) - with pytest.raises(Refused):custody.read(binding) - - -def test_lost_recovery_response_stays_unknown_until_new_independent_attempt(owner,tmp_path,monkeypatch): - native,journal,binding,call,custody=protected(owner,tmp_path);crash(native,journal,binding) - import task_execution_recovery as recovery_module - original=recovery_module.http.client.HTTPConnection.getresponse - once=[True] - def lost(connection): - response=original(connection) - if once[0]: - once[0]=False - response.close() - raise Refused('synthetic lost recovery response after real revoke') - return response - first=permit(native,custody,binding) - with monkeypatch.context() as patch: - patch.setattr(recovery_module.http.client.HTTPConnection,'getresponse',lost) - assert not recover_fixture_cleanup(journal,first)['confirmed'] - assert journal.observe(binding)['external_outcome_unknown'] - with pytest.raises(Refused):recover_fixture_cleanup(journal,first) - assert recover_fixture_cleanup(journal,permit(native,custody,binding))['confirmed'] - # Reconciliation does not erase the historic uncertain invocation. - assert journal.observe(binding)['external_outcome_unknown'] - assert journal.db.execute("SELECT COUNT(*) FROM recovery_calls WHERE outcome='pending'").fetchone()[0]==1 - - -@pytest.fixture -def capsule(tmp_path): - from task_execution_journal import Binding,Cleanup - from task_execution_plan import cleanup_specs - binding=Binding('SYNTHETIC','sha256:'+'a'*64,'12345678-1234-1234-1234-123456789abc','b'*64) - directory=tmp_path/'private-capsule';directory.mkdir(mode=0o700) - custody=FixtureRecoveryCustody(directory,integrity_key=secrets.token_bytes(32)) - cleanup=tuple(Cleanup(x['kind'],x['call_digest']) for x in cleanup_specs(binding.operation_id)) - custody.prepare(binding,cleanup,12345,'path "fixture" {}\n','synthetic-private-accessor') - return custody,binding - - -@pytest.mark.parametrize('mutate',[ - lambda v:v.update(extra=True),lambda v:v.pop('phase'),lambda v:v.update(binding=True), - lambda v:v['binding'].update(approval_id=True),lambda v:v['binding'].update(extra='x'), - lambda v:v['binding'].update(request_digest='0'*64),lambda v:v.update(port=True), - lambda v:v.update(port=12345.0),lambda v:v.update(port=0),lambda v:v.update(phase=[]), - lambda v:v.update(phase='issued'),lambda v:v.update(revision=False), - lambda v:v.update(revision=0.0),lambda v:v.update(identity_accessor=False), - lambda v:v.update(task_accessor='unexpected-before-issue'),lambda v:v.update(policy_hcl=[]), - lambda v:v.update(cleanup={}),lambda v:v['cleanup'].pop(), - lambda v:v['cleanup'].__setitem__(0,False),lambda v:v['cleanup'][0].update(extra=True), - lambda v:v['cleanup'][0].update(kind=True),lambda v:v['cleanup'][0].update(call_digest=0), - lambda v:v['cleanup'][0].update(call_digest='f'*64),lambda v:v['cleanup'].reverse(), -]) -def test_authenticated_but_damaged_capsule_schema_refuses(capsule,mutate): - custody,binding=capsule - value=custody.read(binding);mutate(value) - # Trusted test owner re-signs malformed payload to exercise schema defenses - # independently of MAC validation; untrusted corruption cannot do this. - custody._write(binding,value) - with pytest.raises(Refused):custody.read(binding) - - -def test_capsule_corruption_wrong_key_duplicate_and_nonfinite_refuse(capsule): - custody,binding=capsule;path=custody._path(binding);original=path.read_bytes() - value=json.loads(original);value['payload']['identity_accessor']='different-synthetic-accessor' - path.write_text(json.dumps(value)) - with pytest.raises(Refused):custody.read(binding) - path.write_bytes(original) - other=FixtureRecoveryCustody(custody.directory,integrity_key=b'X'*32) - with pytest.raises(Refused):other.read(binding) - for raw in [b'{',original[:-1]+b',"mac":"duplicate"}',original.replace(b'12345',b'NaN'),b'[]',b'{}',b'{"payload":{},"mac":"'+b'\xc3\xa9'*64+b'"}']: - path.write_bytes(raw) - with pytest.raises(Refused):custody.read(binding) - path.write_bytes(original) - assert custody.read(binding)['identity_accessor']=='synthetic-private-accessor' - - -def test_exact_absence_and_single_terminal_lf_contract(): - from task_execution_recovery import _policy_absent,_same_policy - assert _policy_absent(404,{'errors':[]}) - for status,body in [(403,{'errors':[]}),(404,{}),(404,{'errors':['denied']}), - (404,{'errors':None}),(404,{'errors':[]} | {'data':{}}), - (404,{'errors':False}),(404,{'errors':0}),(404,{'errors':{}})]: - assert not _policy_absent(status,body) - assert _same_policy('fixture','fixture\n') - assert _same_policy('fixture\n','fixture\n') - for text in ['fixture \n','fixture\t','fixture\r\n','fixture\n\n',False,None]: - assert not _same_policy(text,'fixture\n') - - -@pytest.mark.parametrize('after_delete',[False,True]) -def test_recovery_policy_absence_rejects_misleading_native_http_bodies(owner,tmp_path,monkeypatch,after_delete): - native,journal,binding,call,custody=protected(owner,tmp_path);crash(native,journal,binding) - import task_execution_recovery as module - malformed=[{}, {'errors':['permission denied']}, {'errors':None}, {'errors':False}, - {'errors':0}, {'errors':[],'data':{}}, {'errors':['not really absent']}, - b'{"errors":["permission denied"],"errors":[]}',b'{"errors":NaN}',b'not JSON'] - path='sys/policies/acl/'+native.policy_name - for body in malformed: - sent=[];gets=[0] - class Response: - def __init__(self,status,data):self.status=status;self.data=data - def read(self,limit):return self.data if type(self.data) is bytes else json.dumps(self.data).encode() - class Connection: - def __init__(self,*args,**kwargs):pass - def request(self,method,url,**kwargs):sent.append((method,url)) - def getresponse(self): - method,url=sent[-1] - if url=='/v1/'+path: - if method=='DELETE':return Response(204,{}) - gets[0]+=1 - if after_delete and gets[0]==1: - return Response(200,{'data':{'policy':native.hcl[:-1]}}) - return Response(404,body) - return Response(403,{'errors':['synthetic refusal']}) - def close(self):pass - with monkeypatch.context() as patch: - patch.setattr(module.http.client,'HTTPConnection',Connection) - result=recover_fixture_cleanup(journal,permit(native,custody,binding)) - assert not result['confirmed'] and result['obligations'][2]['outcome']=='owed' - assert ('DELETE','/v1/'+path) in sent if after_delete else ('DELETE','/v1/'+path) not in sent - # The denied/malformed probes above emitted no real native calls. - assert call('GET',path)[0]==200 - assert recover_fixture_cleanup(journal,permit(native,custody,binding))['confirmed'] - - -def test_authenticated_prepared_capsule_rollback_cannot_hide_later_mint(owner,tmp_path): - native,journal,binding,call,custody=protected(owner,tmp_path) - old=custody._path(binding).read_bytes() - crash(native,journal,binding) - current=custody.read(binding) - custody._path(binding).write_bytes(old) - # Still authentic under the owner key, but the durable journal proves the - # operation reached issuance. The stale prepared record cannot prove absence. - result=recover_fixture_cleanup(journal,permit(native,custody,binding)) - assert not result['confirmed'] and result['obligations'][0]['outcome']=='owed' - assert call('POST','auth/token/lookup-accessor',{'accessor':current['task_accessor']})[0]==200 - assert call('POST','auth/token/revoke-accessor',{'accessor':current['task_accessor']})[0] in {200,204} diff --git a/tests/test_task_execution_trusted_whole.py b/tests/test_task_execution_trusted_whole.py deleted file mode 100644 index 232fce4..0000000 --- a/tests/test_task_execution_trusted_whole.py +++ /dev/null @@ -1,195 +0,0 @@ -"""Exact actual source successor path; legacy fixture evidence remains separate.""" -from contextlib import contextmanager -from dataclasses import replace -from datetime import datetime,timedelta,timezone -import json -from pathlib import Path -import signal -import sys -import threading -import time -from types import SimpleNamespace -import uuid - -import pytest -sys.path.insert(0,str(Path(__file__).resolve().parents[1]/'scripts')) -from test_task_execution_native_fixture import owner,bao_dev,compiler -from task_execution_trusted_servers import trusted_providers,install_exact_owner_descriptor,http,private -import task_execution_trusted_servers as providers_source -from task_execution_mediator import run_native_fixture_task -from task_execution_native_fixture import NativeFixtureAdapter,_absolute_budget -from task_execution_journal import Refused -from secrets_engine.approval_claim import binding_from_check_request -from secrets_engine.approval_consume import _expected_request -from secrets_engine.authorization import approval_binding_digest -from secrets_engine.execution_contract import ExecutionContract - - -@contextmanager -def whole(owner,directory,*,window_seconds=60): - # OpenBao, Approval HTTPS, TokenReview TLS and actual Access CLI are all up - # before proposing an immutable descriptor/deadline or issuing approval. - with trusted_providers(directory) as provider: - native,journal,binding,call,compiler,descriptor=install_exact_owner_descriptor(owner,window_seconds=window_seconds) - snapshot={path:compiler.read(Path(path)) for path in compiler.pins} - entry=compiler._catalogue_entry(snapshot) - approval_id=str(uuid.uuid4()) - entry=replace(entry,approval={**entry.approval,'authorization_id':approval_id}) - request=_expected_request(provider.cfg,entry,'execute-ccr',execution_contract=ExecutionContract.parse(descriptor)) - from secrets_engine.task_execution_handoff import prepare_task_approval - preparation=prepare_task_approval(provider.cfg,entry,execution_contract=ExecutionContract.parse(descriptor)) - normalization=preparation.as_owner_handoff() - assert normalization['original_request']==request - assert 'attributes' not in request['subject'] - assert normalization['approval_binding_digest']!=normalization['submitted_request_digest'] - now=datetime.now(timezone.utc) - body={'id':approval_id,'binding':normalization['native_binding'],'pdp_digest':normalization['approval_binding_digest'], - 'pdp_path':True,'human_control':True,'required_count':1, - 'validity':{'not_before':(now-timedelta(minutes=1)).isoformat(),'expires_at':(now+timedelta(minutes=3)).isoformat()}} - assert http(provider.cfg.approval_url,'POST','/v1/approvals',body,'synthetic-whole-service')[0]==201 - assert http(provider.cfg.approval_url,'POST','/v1/approvals/'+approval_id+'/entries',{},provider.human_token)[0]==200 - yield SimpleNamespace(native=native,journal=journal,call=call,compiler=compiler,descriptor=descriptor, - entry=entry,provider=provider,cfg=provider.cfg,approval_id=approval_id,request=request,normalization=normalization,preparation=preparation) - - -def invoke(fixture): - return run_native_fixture_task(compiler=fixture.compiler,journal=fixture.journal, - plan_request=fixture.native.meta['request'],owner_descriptor=fixture.native.admission.descriptor, - secrets_cfg=fixture.cfg,entry=fixture.entry,confirmation=fixture.provider.confirmation, - native_admission=fixture.native.admission) - - -def untouched(fixture): - assert fixture.call('GET','sys/policies/acl/'+fixture.native.policy_name)==(404,{'errors':[]}) - for ordinal in [1,4]: - path=fixture.compiler.before_call(fixture.native.plan,ordinal).path - assert fixture.call('GET',path)==(404,{'errors':[]}) - - -def test_actual_successor_allow_strict_cas_confirm_six_calls_readback_cleanup(owner,tmp_path,monkeypatch): - with whole(owner,tmp_path) as fixture: - from secrets_engine.task_execution_handoff import consume_task_execution - with pytest.raises(Exception):consume_task_execution(fixture.cfg,fixture.preparation) - assert not any(item==('consumer','POST','/v1/approvals/'+fixture.approval_id+'/consume') for item in fixture.provider.calls) - trace=[];private_task=[];issued_native=[] - original=NativeFixtureAdapter._http - def observed(native,method,path,token,body=b''): - result=original(native,method,path,token,body) - trace.append((method,path,result[0])) - if method=='POST' and path=='auth/token/create' and result[0]==200: - private_task.append((result[1]['auth']['accessor'],result[1]['auth']['client_token'])) - issued_native.append(native) - return result - monkeypatch.setattr(NativeFixtureAdapter,'_http',observed) - try:result=invoke(fixture) - except Exception as error: - from urllib.error import HTTPError - cause=error.__cause__ - if isinstance(cause,HTTPError): - raw=cause.read(4097) - known={b'trusted Approval source refused\n':'approval-source-correspondence', - b'service task execution input refused\n':'service-input-correspondence'} - diagnostic={'status':cause.code,'constant_reason':known.get(raw,'other-closed-classification'), - 'native_operations':fixture.journal.db.execute('SELECT COUNT(*) FROM operations').fetchone()[0]} - (tmp_path/'closed-source-refusal.json').write_text(json.dumps(diagnostic)) - print(json.dumps(diagnostic)) - raise - assert result['native_apply']=='verified' and result['native_calls_completed']==6 - assert result['cleanup_confirmed'] and result['business_state']=='cleanup-confirmed' - assert fixture.provider.engine.get(fixture.approval_id).status=='consumed' - records=[json.loads(line) for line in fixture.provider.record.read_bytes().splitlines()] - assert len(records)==2 and [x['effect'] for x in records]==['deny','allow'] - assert all(x['signature']['mode']=='signed' for x in records) - normalization_record,record=records - assert normalization_record['id']==fixture.normalization['decision_id'] - assert record['id']==result['decision_id'] - assert record['lifetime']['expires_at']==fixture.descriptor['operation_deadline'] - assert record['provenance']['policy_package']=='secrets-engine.task-execution.service-pilot' - assert record['provenance']['policy_version']=='v2' - assert record['provenance']['approval_observation']['source_id']=='synthetic-whole-actual-approval' - assert record['binding']['context']['execution_contract']==fixture.descriptor - native_calls=fixture.journal.db.execute('SELECT ordinal,outcome FROM calls ORDER BY ordinal').fetchall() - assert native_calls[:2]==[(0,'complete'),(1,'complete')] and len(native_calls)==13 - assert all(value=='complete' for _,value in native_calls) - assert fixture.provider.calls.count(('consumer','POST','/v1/approvals/'+fixture.approval_id+'/consume'))==1 - assert fixture.provider.calls.count(('consumer','GET','/v1/approvals/'+fixture.approval_id))==1 - assert any(item==('source','GET','/v1/approvals/'+fixture.approval_id+'/claim') for item in fixture.provider.calls) - expected=fixture.compiler.proposal()['operations'] - assert [(method,path) for method,path,_ in trace if path in {item['path'] for item in expected}]==[(x['method'],x['path']) for x in expected] - assert len(private_task)==1 - assert issued_native[0]._task_wall_deadline 2026-10-11: demoted to `flavor: residual` / `backlog` by founder-authorized triage — off the -> critical path (identity → authz → secrets → SecurityCase execution). Tasks are unchanged; -> promote back when demand or risk appears. See the-custodian -> `history/20261011-netkingdom-security-progress-assessment.md`. - -## Window preparation — 2026-10-14 - -The founder chose to prepare attended windows for T02 and T03 (D5 `2fd5849c` already -authorizes the outage exercise; each window still needs its own GO). The read-only -preflight had drifted: audit-core's attest-chain CronJob pods share the app label, so -the single-pod check failed, and the reviewed digest pin was stale. The preflight now -ignores Job-owned pods and pins audit-core's current deploy digest `ec15f63d…` -(audit-core `deploy/audit-core.yaml`, descendant of the `abd22fa` expiry prerequisite). -Both preflights then pass every automated check with no secret value observed; only -operator gates remain. The review contract's preflight hash is refreshed, so owner -receipts must be renewed against it. Unrelated pre-existing test failures (credential -change, factory audit delivery, informed_decision import) are unchanged. - -Owner review round 2026-10-14 (contract b31836ca): railiance-platform approved; -audit-core, rapp-postgres, railiance-cluster and railiance-infra requested changes -(hub messages 570ca050, a190118e, 4e9d4e58, e3b7bb24). T02 fixes applied: the live -harness now uses the preflight's serving-pod filter and refuses while an attest-chain -Job is active or within 20 minutes of its 03:17 UTC schedule (the Job mounts the same -database Secret). rapp-postgres confirms revocation terminates the role's sessions, so -readiness does drop. audit-core registered the T02 load driver (8c8bcf4, receipt -2de1c6e7). Still open for T02: a T02 synthetic-sender projection lane (no generic one -exists; the -02 Whitehat procedure must not be copied). Open for T03: the reboot -checklist revisions requested by cluster/infra/postgres/platform (full-cluster recovery -checks, pre-reboot failing-pod baseline, all CNPG clusters, firewall/boot-id/time sync, -named console path, infra abort ownership, ops-warden login revision re-acceptance and -stop-on-credential-output rule, PVC/cluster UID capture). - -Revision for the second owner-review round (2026-10-14): -- T02: attended synthetic-sender lane `scripts/audit_core_synthetic_sender_lane.py` - (+ Makefile targets, tests) projects exactly one receipt-named - `tenant:trial:` identity, restarts audit-core before the baseline and cleans up only - after recorded evidence; the lease harness now requires the receipt's - `synthetic_sender` block. The lane reuses helpers from the dated -02 script; move - them to a shared module before that script is retired. -- T03: `docs/railiance01-coordinated-reboot.md` and the node-reboot preflight cover - named core workloads, a failing-pod baseline diff, every CNPG cluster (UID, PVC UID, - systemID), all stores/ExternalSecrets, firewall/tunnel/boot-id, NTP, Forgejo pull - lag, infra abort ownership, a same-day `RAIL01-CONSOLE-` receipt - (railiance-infra docs/railiance01-console-access.md), new scenario/decision ids and - an accepted ops-warden revision; running-pod image digest is now checked too. -- ops-warden: re-acceptance first rejected HEAD 11b2a9d (b4c1d39 had dropped the - login-output fail-closed check); fixed locally in ops-warden 244f1ae/6e14300 and - accepted at 6e14300 (reviews/RPF-WP-0017-ops-warden-reacceptance-2026-10-14.json). - The OIDC stderr prompt is an allowlisted relaxation. WARDEN-WP-0036-T02's - 2026-09-01 live op ran while the relaxed check was in source (railiance-infra follow-up). -Remaining before any window: second-round owner approvals; console access proven -(RAIL-HO-WP-0011-T09); fresh backups or waivers for state-hub-db, user-engine-pg, -net-kingdom-pg, target-revenue-pg; stuck PVC platform-pg-drill/platform-pg-drill-1; -ops-warden 6e14300 published and installed; then window ids, abort operator, snapshot -receipt and 2-of-3 unseal quorum. - -Round 3 (2026-10-14): all five owners approved contract 1c08ba7a (railiance-platform -8cfbf64): audit-core e7c36b17, rapp-postgres b7c4939a, railiance-cluster c9435491, -railiance-infra 28d6a25a, railiance-platform 61b97f96. Procedure approval only. -Founder checklist and remaining window gates: docs/rpf-wp-0015-attended-windows.md. -T02 and T03 stay `wait` until their attended windows run. - -Round 4 (2026-10-14, railiance-platform 311aa86, contract 96deddba): T02 now runs only -inside the contained ops-warden login (the harness had pointed at the retracted public -OpenBao URL) via one silent `scripts/t02-attended-window.py`. All five owners approved: -audit-core 5fe769a7, rapp-postgres f13ec9dd, railiance-cluster fb8f25df, -railiance-infra 248e6f4d, railiance-platform 18e59587. ops-warden 6e14300 is pushed -and installed; the installed-warden gate verifies (33 modules). Non-blocking hardening -follow-ups (to batch with the next reviewed change): minimal driver environment; -catch-all in the window `main()` so it can never print; do not echo child stderr into -`--abort-reason`; distinct remote port; runbook: check `abort_cleanup_failed` after any -non-zero exit; failing-pod diff by owning controller; namespaced SecretStores in the ESO -inventory. - -T02 attended window 2026-10-14 (approval rpf-wp-0015-t02-20261014t1615z-founder-go, -founder GO in session, window 16:15–16:30Z, founder as abort operator): one contained -login; projection and port-forward succeeded (audit-core restart #1), then the exercise -refused before revocation because `database/creds/audit-core-runtime` had two live -handles (ESO refresh 15m vs lease TTL 15m/max 1h overlap). Abort cleanup removed the -one synthetic identity, restarted audit-core (1/1) and shredded the bearer; no lease was -revoked and no secret value observed. Two earlier same-day launches failed closed before -any action (a standing `OPENBAO_TOKEN` exported in the operator's shell profile, then a -start before the window). Evidence: docs/evidence/2026-10-14-rpf-wp-0015-t02-window/. -Next: select the handle coherent with the Secret refresh instead of requiring exactly -one (owner re-review), then a new window. Follow-ups: ops-warden must also strip -OPENBAO_TOKEN for attended children; the standing shell-profile token should be revoked -and removed (founder). - -Round 5 (2026-10-14, railiance-platform `044df6f`, contract `ea3f53d6`): the harness -selects the one lease issued for the mounted Secret refresh and leaves older -predecessors. Current artifact hashes match the owner receipts. All five owners -approved: audit-core `b3c564a6`, rapp-postgres `08a84caf`, railiance-cluster -`4bb0d7af`, railiance-infra `0300d069`, railiance-platform `0f8f0692`. This is -procedure approval only. T02 stays `wait` until a new attended window. The T03 -checklist bytes in that contract are the round-4 bytes. - -**T02 done (2026-10-14).** Attended window rpf-wp-0015-t02-20261014t1631z-founder-go -(16:31–16:46Z, founder GO and abort operator, one contained ops-warden login; started -~2 min after an ESO refresh so the pod mounted only the selected lease). Owner contract -ea3f53d6 approved by all five (audit-core b3c564a6, rapp-postgres 08a84caf, -railiance-platform 0f8f0692, railiance-cluster 4bb0d7af, railiance-infra 0300d069). -Result: the handle issued for the mounted refresh (fingerprint 9dd61a51945e) was revoked; -/healthz 200 and /readyz 503 during failure; synthetic sender saw retryable 503; forced -ESO refresh changed the Secret resource version and mount generation; readiness -recovered; the retried synthetic event returned duplicate; same pod UID and unchanged -restart count. Cleanup removed the one synthetic identity, audit-core 1/1, bearer -shredded, no secret value observed. Independent post-window preflight green. -Evidence: docs/evidence/2026-10-14-rpf-wp-0015-t02-window/accepted-1631/. -Follow-ups (owners, not blocking T02): guard revocation on Secret refreshTime + ~120s < -container startedAt (audit-core/rapp-postgres); ESO refresh interval equals role TTL -(audit-core/ESO); ops-warden strip OPENBAO_TOKEN; founder: revoke/remove the standing -shell-profile OPENBAO_TOKEN. - -T03 pre-window gates cleared 2026-10-14 (founder GO in session): -- Stuck drill PVC `platform-pg-drill/platform-pg-drill-1` (rapp-postgres drill scratch, - deletion requested 2026-08-13, namespace and CNPG owner gone, no pod or attachment): - the API refused to patch an object in a missing namespace, so the empty namespace was - recreated, the pvc-protection finalizer removed, the claim and its local-path PV - (reclaim Delete) released, and the namespace deleted again. The node-reboot preflight - now passes its terminating-PVC check without `--accept-terminating-pvc`. -- Founder waiver: the fresh-backup recommendation for `state-hub-db`, `user-engine-pg`, - `net-kingdom-pg` and `target-revenue-pg` (no CNPG backup configured) is waived for the - T03 reboot window. A reboot keeps their local-path PVCs; the preflight's per-cluster - UID/PVC-UID/systemID comparison detects any re-bootstrap. platform-pg's fresh backup - remains required. Configuring backups for those clusters stays with their owners. - -Mutation freeze `bcd2f6c5` (2026-10-14 18:18Z) covers the founder window -`rpf-wp-0015-t03-window-20261014-01`. At 20:20Z the node was still Ready, with -that Ready condition unchanged since 2026-07-16, so the reboot had not happened. -No deployment, migration, credential change, backup restore, or other engagement -runs until the all-clear. The approved procedure for this window is contract -`3817c1df` (receipts audit-core `7c4019f2`, rapp-postgres `02f285e0`, -railiance-platform `b9ea904d`, railiance-cluster `2087b9ac`, railiance-infra -`a234288a`), recorded in `reviews/RPF-WP-0015-T03-founder-go-20261014.json`. -That decision defers the synthetic E2. The sender-lane E2 mode is the follow-up -window, and it refuses a non-duplicate replay, an unclean fixture cleanup, or a -changed pod UID or restart count. It is not approved as part of contract -`3817c1df` and must not be run inside the deferred window. - -**T03 coordinated reboot executed 2026-10-14** (founder GO -rpf-wp-0015-t03-founder-go-20261014-01, scenario RPF-WP-0015-T03-REBOOT-20261014-01, -window rpf-wp-0015-t03-window-20261014-01; owner contract 3817c1df approved by all -five; window console receipt RAIL01-CONSOLE-20261014T175624Z). -- Hold point: ops-warden 6e14300 installed and verified; attended Raft snapshot - (second run; the first failed closed on the escrow key's identity-file format, - nothing uploaded) encrypted to the offsite recipient that the founder's offline - railiance-backup.key derives, decrypt-verified in memory (founder decision), uploaded - to railiance-backups/openbao/ (201), plaintext shredded from tmpfs; pre-reboot chain - intact (508 events, head c99426d8…); preflight ready_for_live_execution true. -- Reboot issued 18:18:04Z after a hub freeze broadcast; host back 18:21:48Z; boot id - 31b5be76 → eaf7763f, exactly one boot; NTP synced; UFW matches declaration; tunnel up. - SSH recovered, so the provider console was not needed. -- OpenBao initialized and sealed as expected; founder-attended 2-of-3 unseal completed - 18:25:46Z outside this transcript; post-unseal checks OK; Raft applied index ahead of - the snapshot (no data loss). -- Post-reboot comparison passed 18:27:57Z: no CNPG identity change across all 8 clusters, - no new failing pods (same 6 as baseline), all 32 stores Valid and 46 ExternalSecrets - synced without an ESO restart, audit-core 1/1 same pod UID on the reviewed image - (container restarted once with the node; no manual restart). -Evidence: docs/evidence/2026-10-14-rpf-wp-0015-t03-reboot/. -Post-reboot verify-chain/attest-chain (founder-run 21:30:55Z): intact, 508 events, -pre-reboot head c99426d8… unchanged (post-reboot-chain-head.json). Mutation freeze lifted. -Still open before T03 can be marked done: the deferred synthetic E2 via the reviewed sender lane in E2 mode -(railiance-platform dd003c9, owner round 8 pending). - -Deferred synthetic E2 (2026-10-14/15): the E2 mode was approved by all five owners -in round 9 (contract 014627d6; railiance-platform 748fe6c: first send must be a fresh -`accepted`, replay `duplicate`; the event stays in the append-only chain). Two E2 -receipts were issued (windows 22:31Z and 22:40Z) but no run started: the contained -`openbao-platform-admin-login` returned `login_failed` (exit 10) at 22:35Z, 02:07Z and -02:10Z (warden access-audit). The founder's KeyCape session showed `assurance aal1` -(password without OTP), so the OpenBao admin OIDC login, which needs MFA, was refused -and warden failed closed with self-revocation. Nothing was projected and audit-core was -untouched. Next: sign out of KeyCape (or use a fresh private window), complete -password+OTP (aal2), reissue a near E2 receipt and run the launcher once. T03 stays -`wait` until that E2 evidence exists. diff --git a/workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md b/workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md index 2a2ef18..8194126 100644 --- a/workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md +++ b/workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md @@ -58,14 +58,14 @@ predecessor revocation, and sanitized evidence. ```task id: RPF-WP-0027-T03 status: wait +needs_human: true +decision_id: 62aa95e4-3ccc-4867-8806-5e3db005b483 blocked_on: message-from:net-kingdom -blocking_reason: "Owner work first: NetKingdom packages the complete resolver sequence as one receipt-producing command; then it enters the attended-window checklist (D1 standing rule)." +blocking_reason: "Attended resolver run: postchecks outstanding; NetKingdom asked to package the complete sequence as one receipt-producing command for the next attended session." priority: high state_hub_task_id: "714ae011-903d-55e2-ac47-801b8ef879d1" ``` -2026-10-11: not founder-ready (no packaged command yet); returned to owner. See the-custodian docs/attended-window/2026-10-11-checklist.md. - KeyCape supplied source revision `93704fd2424503007c20b458b62a7f7d994bb288`, post-rotation JWKS SHA-256 `c6faac5dfeef2453daf9cfc14671f62b535dd321befdf6014e3ee5c2cf1f1156`, and @@ -131,14 +131,13 @@ status, timestamps, and cleanup receipts. ```task id: RPF-WP-0027-T06 status: wait -blocking_reason: "Owner work first: the OpenBao owner confirms mount/path/fields and writer/reader authority and publishes the non-secret routing receipt; the handoff enters the attended window only when a command exists." +needs_human: true +decision_id: 62aa95e4-3ccc-4867-8806-5e3db005b483 +blocking_reason: "Platform/OpenBao owner must publish the non-secret routing receipt for both lanes through the attended handoff; values are never inferred." priority: high -blocked_on: message-from:rapp-openbao state_hub_task_id: "3b9748c4-2906-5ba7-9d34-0a7067a59283" ``` -2026-10-11: not founder-ready; returned to owner. - The platform/OpenBao owner must publish a non-secret receipt for both routing lanes: canonical mount/path, field name, KV version semantics, least-privilege policy and auth method, expiry/rotation/revocation semantics, and the approved diff --git a/workplans/RPF-WP-0035-credential-lane-implementation.md b/workplans/RPF-WP-0035-credential-lane-implementation.md index 89d8fc6..06323a0 100644 --- a/workplans/RPF-WP-0035-credential-lane-implementation.md +++ b/workplans/RPF-WP-0035-credential-lane-implementation.md @@ -8,7 +8,7 @@ status: blocked flavor: implementation owner: codex created: "2026-09-05" -updated: "2026-10-21" +updated: "2026-10-04" related: - RPF-WP-0032 - RPF-WP-0033 @@ -44,19 +44,15 @@ the signing dependency belongs to the proposed FLEX-WP-0020-T05 cutover. ```task id: RPF-WP-0035-T02 status: wait +needs_human: true decision_id: 62aa95e4-3ccc-4867-8806-5e3db005b483 -depends_on: [KEY-WP-0039-T02, MASON-WP-0009-T05] -blocking_reason: "Agent work, not a human gate: claim refusals (KEY-WP-0039-T02 probe) and capabilities-self deny, then ops-mason MASON-WP-0009-T05." +depends_on: [KEY-WP-0037-T02, KEY-WP-0037-T03, KEY-WP-0037-T04] +blocked_on: message-from:secrets-engine +blocking_reason: "KEY-WP-0037 now owns protected custody, real registration and native issuance. Platform issuer-reader source CCR-2026-0033 is prepared. secrets-engine must accept exact consumer host/file/delivery/rotation coordinates before the separate custody child is finalized; KeyCape exact-primary identity repair and separate live go remain." priority: high state_hub_task_id: "e0c82ea9-05a5-537e-8fbf-67534263572e" ``` -2026-10-17: KEY-WP-0039-T02 owner returns prepared from source (not admitted): -`docs/proposals/2026-10-17-key39-t02-owner-returns.md`. Bao origin and role -leeway are not declared in source and need attended sanitized owner reads. - -2026-10-11: requalified — the remaining waits are other owners' tasks, not a human gate. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - Supersedes RPF-WP-0032-T02. Design: `docs/credential-lane-designs/secrets-engine-service-jwt.md`. @@ -76,42 +72,6 @@ issuer/audience/subject/claims and unrelated secret access fail, bounded TTL and revocation are proven, the consumer explicitly opts into the verified contract, and rollback/cleanup receipts contain no secret material. -2026-10-09 (T03 source): founder ruled the tenant segment stays `coulomb` -(ops-mason `b72ff77d`). New CCR request type `workload-kv-operator-matrix` -(`scripts/operator_matrix_lane.py`): exact per-entry capabilities, OIDC role on -the existing `netkingdom` mount only, and exact agent denies on all four data -and metadata paths appended to the live `agent-high-risk-boundary` (live content -preserved; partial presence refused). `scripts/fluid_telegram_custody_move.py` -copies the four undocumented `infotech` entries with CAS 0, verifies all, then -metadata-deletes the sources. `scripts/kv_duplicate_retire.py` retires -`activity-core/default/llm-connect-provider-secrets` only if no live non-admin -policy grants it. Founder session (each helper prints nothing; read the receipts): - -```bash -cd ~/railiance-platform -export BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 -W='warden access openbao-platform-admin-login --exec --' -# 1. dry-runs (no value reads, no writes) -$W python3 scripts/fluid_telegram_custody_move.py --dry-run --confirm 'MOVE fluid-telegram hall-of-helix to coulomb' --receipt ~/ft-move-dry.json -$W python3 scripts/kv_duplicate_retire.py --dry-run --confirm 'RETIRE activity-core/default/llm-connect-provider-secrets' --receipt ~/kv-retire-dry.json -# 2. lane: approve, confirm binding, apply -python3 scripts/credential-change.py approve CCR-2026-0039 --reviewer "platform-owner, fluid-telegram-owner, key-cape-owner (founder Bernd Worsch)" --comment "Approve ops-mason plan fluid-telegram-operator-credential-lane (spec 41a0ca27): four-entry matrix at coulomb/fluid-telegram/hall-of-helix, OIDC role on netkingdom, agent denies." \ - && python3 scripts/credential-change.py confirm-binding CCR-2026-0039 --reviewer founder --comment "groups=[fluid-telegram-operators] (LLDAP, member tegwick) and /assurance/level=aal2 match key-cape b2ed890f." \ - && $W python3 scripts/credential-change.py applier-apply CCR-2026-0039 --confirm "DELEGATED APPLY CCR-2026-0039" --actor founder --record-state-hub --quiet -# 3. custody move, then duplicate retirement -$W python3 scripts/fluid_telegram_custody_move.py --confirm 'MOVE fluid-telegram hall-of-helix to coulomb' --receipt ~/ft-move.json -$W python3 scripts/kv_duplicate_retire.py --confirm 'RETIRE activity-core/default/llm-connect-provider-secrets' --receipt ~/kv-retire.json -``` - -2026-10-09 (T03 founder session): CCR-2026-0039 applied at 10:30Z; readback -matched for the 8 boundary denies, policy and role. Custody move `moved`: all -four entries copied at coulomb v1 (operator-session from infotech v4) and the -infotech entries metadata-deleted. Duplicate -`activity-core/default/llm-connect-provider-secrets` retired; its first dry-run -refused on mount-wide `platform/metadata/*` survey grants, after which the check -was narrowed to data-path grants (`64ae594`). Receipts: -`docs/evidence/2026-10-09-fluid-telegram-t03-founder-session.json`. - 2026-10-03: the historical D1 routing was withdrawn because provision/delivery already completed. Owner routing now uses decision `ab85ae8a-12a0-4945-ae64-1d3d86d5b26f`, specifically independent archive readback and coordinated interim audit bearer revocation acceptance. `needs_human` and the wait remain; resolving a decision requires owner receipt re-check, not automatic completion. 2026-10-03 (CUST-WP-0075): gate check. SECRETS-WP-0007-T04 is done (2026-09-27), so the gate named in the design is open. It is not the last one: the design says to confirm the exact live client registration and protected client-side custody before writing, and positive-login proof needs a real KeyCape service JWT. KeyCape's contract (`openbao-service-auth-contract.md`) and KEY-WP-0009 claim source registration only; the live registration of `secrets-engine-openbao` and its client-credential custody are "a separate approved custody action" that no workplan task owns yet (SECRETS-WP-0008-T06 waits on this task). Re-qualified with `blocked_on: message-from:key-cape`; key-cape was asked for the owning task id, which will replace it as `depends_on`. The design stays proposed and no child command was written. Not approvable in D1 yet. @@ -146,48 +106,18 @@ rotation/revocation ownership, live order and rollback. Attended custody child founder decision id recorded in the approved CCR. Nothing executed, generated or applied; nothing added to active GitOps. The founder approval it needs is T09. -2026-10-09: KEY-WP-0037 finished 2026-10-07 (registration live, issuance -verified 10/10, key-cape e0b5fae); CCR-2026-0033/0034 applied. Source for this -lane is now complete as **CCR-2026-0038** (proposed, -`credential-change-requests/CCR-2026-0038-secrets-engine-service-jwt-login.yaml`). -The delegated applier gained a narrow `service-jwt-login` request type -(`scripts/service_jwt_lane.py`): auth mount fixed to `keycape-services`, policy -must equal the generated login-self policy, role/config keys allow-listed, -TTLs at most 15m, uses 1..16, `token_no_default_policy`, refusal if the mount -exists with another type, and a value-free readback compare after the writes. -Policy `secrets-engine-login-self` now also carries explicit `deny` on -`platform/{data,metadata}/workloads/keycape/secrets-engine-openbao` so -ops-mason invariant (ii) (no capability on the client-secret path) holds even -if identity groups were ever attached. Tests: `tests/test_service_jwt_lane.py` -(8) plus the updated contract test; full suite unchanged apart from 5 -pre-existing failures unrelated to this lane. - -Founder apply (one line; approval, binding confirmation and silent apply): - -```bash -cd /home/worsch/railiance-platform && python3 scripts/credential-change.py approve CCR-2026-0038 --reviewer "platform-owner, key-cape-owner, secrets-engine-owner (founder Bernd Worsch)" --comment "Approve service-JWT login lane: mount keycape-services, role secrets-engine-openbao, policy secrets-engine-login-self (self endpoints + deny on the client-secret path)." && python3 scripts/credential-change.py confirm-binding CCR-2026-0038 --reviewer founder --comment "Bound claims match KeyCape registration: aud secrets-engine-openbao, sub service:secrets-engine, principal_type service, tenant tenant:platform, roles secrets-engine, scope openbao:login; issuer https://kc.coulomb.social RS256." && warden access openbao-platform-admin-login --exec -- python3 /home/worsch/railiance-platform/scripts/credential-change.py applier-apply CCR-2026-0038 --confirm "DELEGATED APPLY CCR-2026-0038" --actor founder --record-state-hub --quiet -``` - -Done after that: login proof with a real KeyCape service JWT -(SECRETS-WP-0012-T03 probe-openbao; policies exactly -[secrets-engine-login-self], capabilities-self deny on the guarded paths, -revoke-self), negative claim checks, then publish the consumer contract file -and ops-mason MASON-WP-0009-T05 rechecks invariant (ii). - ## Implement the platform operator-write CCR contract and Fluid lane ```task id: RPF-WP-0035-T03 status: wait +needs_human: true decision_id: 62aa95e4-3ccc-4867-8806-5e3db005b483 -blocking_reason: "Founder session done 2026-10-09; waits on ops-mason verification (MASON-WP-0005-T05) and the FT-WP-0002 owner salt CAS probe." +blocking_reason: "MASON-WP-0005-T02 source contract is done (ops-mason message 79570eb8). The attended policy/schema apply remains with the open provisioning decision." priority: high -depends_on: [MASON-WP-0005-T05, FT-WP-0002] state_hub_task_id: "f47e5bc5-6f3c-54bb-aa35-260ad03892ba" ``` -2026-10-11: requalified — founder part done; remaining waits are owner tasks. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - Supersedes RPF-WP-0033-T02. Design: `docs/credential-lane-designs/fluid-telegram-operator-kv.md`. @@ -363,15 +293,13 @@ Rotation is a distinct, version-guarded operation. ```task id: RPF-WP-0035-T07 status: wait +needs_human: true decision_id: 1591daa1-6e85-4dfd-aadd-5a9fe1ef47fa -blocking_reason: "Hold kept by D3 until SECRETS-WP-0006-T06 moves publishing off the legacy whynot-design npm path; then rotate and prove a fresh publish." +blocking_reason: "Founder HOLD (2026-09-21) on the legacy whynot-design npm pointer; custody disposition is an attended decision." priority: high -depends_on: [SECRETS-WP-0006-T06] state_hub_task_id: "0dd7c9a5-65a0-53fc-b6c3-5ad96ee56f7b" ``` -2026-10-11: requalified after founder decision D3 (trust root, build-mode default) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - Found by the attended session on 2026-09-10 while settling the npm field question. `secret/coulomb/whynot-design/npm/publish` exists at version 1, created 2026-07-03T15:00:44Z and never updated — five days after the governed @@ -415,15 +343,15 @@ never record the value, fingerprint, length or shape. ```task id: RPF-WP-0035-T06 -status: done +status: wait +needs_human: true decision_id: 62aa95e4-3ccc-4867-8806-5e3db005b483 +blocking_reason: "INFD-WP-0001-T07/T08 terminal; remaining gate is human approval of the scoped delivery proof; front door stays non-resolvable." priority: high intervention_note: "" state_hub_task_id: "68bff751-e48b-548b-8fb4-dfb3b16210c2" ``` -2026-10-11: requalified after founder decision D1 (attended window standing rule) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - **Current return, 2026-09-10:** CCR-2026-0020 is cancelled on the requesting Approval Engine owner's explicit withdrawal, source `849c75bb094613ff6ac1a1d4cda56a745520c5a1:docs/keycape-service-registrations.md`. @@ -541,17 +469,15 @@ into this client-identity grant. ```task id: RPF-WP-0035-T08 status: wait +needs_human: true decision_id: "ab85ae8a-12a0-4945-ae64-1d3d86d5b26f" -blocking_reason: "Approved conditionally (ab85ae8a, standing change rule): executes once independent archive readback and the interim bearer revocation receipt exist; owner work with audit-core." +blocking_reason: "Provisioned and delivered 2026-09-11 (CCR-2026-0021/0022 applied); do not repeat. Receiver rollout with evidence_kind and native producer acceptance passed 2026-09-11. Remaining human acceptance: independent archive readback and revocation of the interim audit bearer tokens." priority: high assignee: railiance-platform intervention_note: null -blocked_on: message-from:audit-core state_hub_task_id: "0ef52c26-2669-5cd1-8149-b1f6fcdda6cb" ``` -2026-10-11: requalified — ab85ae8a resolved under the standing build-mode change rule. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - 2026-10-03 correction (CUST-WP-0075, informed-decision sitting draft `55fb078`): the D1 routing was wrong. First provision and delivery already ran on 2026-09-11, with seed and delivery receipts, and the review packet says not to repeat them. The task is not a provisioning-window act. It was taken off decision D1, and its blocking_reason now names what actually remains. railiance-platform should re-qualify it further if the receiver rollout has since happened. 2026-10-03 (CUST-WP-0075): re-qualified. The receiver rollout already happened: Audit Core c82e0442de0f carries evidence_kind and native producer accepted/duplicate and scope/read-refusal checks passed 2026-09-11 10:42 UTC (`docs/credential-lane-designs/factory-audit-senders-review.md`, `factory-native-acceptance.md`). Remaining: independent archive readback and audit bearer-revocation acceptance. Still not a provisioning-window act. @@ -840,11 +766,12 @@ T08 now explicitly waits for formerly-valid audit-bearer revocation acceptance f ```task id: RPF-WP-0035-T09 -status: done -needs_human: false +status: wait +needs_human: true priority: high decision_id: 2ab717e1-7881-491e-aed4-28946550c289 depends_on: [] +blocking_reason: "Waits for founder approvals: A1 ops-mason design (keycape-secrets-engine-openbao-client-custody, MASON-WP-0009); A2 revised CCR-2026-0033 plus sibling operator reader CCR-2026-0034 (platform-owner, key-cape-owner, net-kingdom-owner reviews; bound claims confirmed for both readers incl. groups=net-kingdom-admins and /assurance/level=aal2); A3 the exact custody-child decision id recorded in the CCR-2026-0033 review (not boundary decision 632a8893). secrets-engine D1-D4 are superseded (no consumer copy)." state_hub_task_id: "7c753be6-42f1-58a6-a4a4-a7ed686f461f" ``` @@ -859,590 +786,3 @@ sso/keycape-secrets-engine-openbao-client exists) → NetKingdom env patch immediately → live issuance check (KEY-WP-0037-T04) → OpenBao JWT role/login proof (RPF-WP-0035-T02). Each live step after the custody run needs its own go. - -2026-10-11 evidence reconciliation: T09's approval obligation is complete. -Authoritative founder decision 2ab717e1-7881-491e-aed4-28946550c289 is resolved, -decided by Bernd Worsch at2026-10-06T13:07:18.627943Z, and explicitly covers -A1 design, A2 CCR33/CCR34 plus owner reviews/bound claims, and A3 one custody run. -The approval comments in both CCRs record those exact grants; both source records -are applied. MASON-WP-0009-T02/T03 additionally record the October8 approval/owner-delivery -reconciliation and reference KeyCape -`docs/evidence/2026-10-07-secrets-engine-openbao-issuance.json`. Closing this approval task clears -its obsolete human gate. It grants no new run, credential access, rotation or -service execution: RPF35T02/T12, MASON9T05 and SECRETS8T06 retain their actual -native/adoption obligations. Historical per-step exclusions remain above. - -## Retire the approval-engine-operator client delivery and custody (CCR-2026-0018) - -```task -id: RPF-WP-0035-T10 -status: wait -priority: medium -blocked_on: message-from:approval-engine -blocking_reason: "Blue lane under the standing change rule (removing access, with reviewed order, rollback and receipts): executes once approval-engine confirms there is no presenter. No founder window needed." -state_hub_task_id: "af2d4dba-f83a-5819-9a8b-671bae799364" -``` - -2026-10-11: access removal is blue lane (canon build-mode-change-authorization_v0.1); no founder window. - -key-cape proposed a joint disablement on 2026-10-10 (message `7f2128fb`). -Precondition: approval-engine reconfirms there is no presenter. The order is the -reverse of provisioning, so KeyCape never starts with a client whose secret is -missing. key-cape removes the registration and restarts (1), checks that -`invalid_client` is returned (2), and net-kingdom removes -`KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET` from Deployment `sso/keycape` -(3). Platform steps, amended from the proposal: - -4. **Delivery.** In one commit, remove ExternalSecret - `sso/keycape-approval-engine-operator-client` from - `argocd/platform-addons/openbao-secretstore/keycape-approval-clients.externalsecrets.yaml` - and ClusterSecretStore `openbao-keycape-approval-engine-operator` from - `openbao-keycape-approval-clients.clustersecretstore.yaml`. Keep the - secrets-engine-approval objects. Then bump `targetRevision` of - `argocd/railiance01/applications/openbao-secretstore.application.yaml`. - The app is pinned and has no automated sync, so nothing changes live until - an attended `argocd app sync openbao-secretstore --prune` runs after step 3. - Verify, value-free, that the ExternalSecret, the store and Secret - `sso/keycape-approval-engine-operator-client` are gone. The target is - `creationPolicy: Owner` and `deletionPolicy: Retain`, so the Secret should - follow its owner; delete it explicitly if it does not. Then delete OpenBao - role `auth/kubernetes/role/external-secrets-keycape-approval-engine-operator` - and policy `workload-kv-read-keycape-approval-engine-operator`, and remove - their source files. Also remove policy - `workload-kv-read-approval-engine-operator-client` (CCR-2026-0020, cancelled) - if it exists live. -5. **Custody.** Run the metadata delete of - `platform/workloads/approval-engine/operator-client`, the destruction - foreseen in `docs/credential-lane-designs/keycape-approval-clients.md`. A - disabled client's secret has no recovery value; a narrowed successor gets a - fresh secret through the mint-secret lane at `platform/workloads/keycape/`. - Only after step 2's `invalid_client` receipt. Keep the agent boundary denies - for that path until the delete is verified, then drop them. - -2026-10-10: key-cape agreed the amended steps 4 and 5 (message `59304e33`, -KEY-WP-0013 c528daa). Waits on approval-engine confirming there is no presenter, -and on the founder's window. - -The manifest edits wait for the scheduled window, so a stray `targetRevision` -bump cannot remove KeyCape's secret before step 3. - - -## Complete independent service-JWT source preparation and execution design - -```task -id: RPF-WP-0035-T11 -status: done -priority: high -state_hub_task_id: "7952c8cd-c066-5b72-a025-78d24341c1ab" -``` - -2026-10-11: Platform-scoped agent loaded this checkout's AGENTS.md and INTENT, -RPF35-T02, actual CCR38/login contract/typed applier and Secrets Engine's native -claim/PDP/CAS/provider/apply flow. Frozen Secrets v2 manifest and all named staged -bytes/proof were hash-verified. Independent tests reproduced three source -counterexamples while retaining the frozen files: close exception can coexist -with native_verified=true; successful result needs no source/session fingerprints; -HTTP200 mixed errors+data envelope is accepted. Original 37 tests plus the three -counterexample tests pass (40 cases, synthetic/local only). Proposed owner design -`docs/credential-lane-designs/secrets-engine-task-execution-entitlement.md` binds -an explicit metadata-only execute-ccr action, immutable typed owner bodies, -CAS/journal replay control, 120s/12-use owner-held task token, independent payload -validation, native ACL endpoint bounds, revoke/readback and recovery. This completed -preparation record is established before the formal source review. It grants no -live authority and does not close T02 or represent acceptance by another owner. -Formal review artifact follows under this work item; all implementation/live -obligations are retained in T12 and existing T02 rather than a finished residual. - -## Implement and prove the separately approved task execution entitlement - -```task -id: RPF-WP-0035-T12 -status: progress -priority: high -depends_on: [RPF-WP-0035-T11] -state_hub_task_id: "914bf383-c13f-5247-9af0-91d7e3a4784a" -``` - -Review the proposed task-execution-grant schema and exact metadata-only execute-ccr -backend with Secrets Engine/access-engine/approval-engine; implement trusted service -subject and served consume verification, durable one-shot reservation, bounded -owner-held token issuer/middleware and exact source/payload enforcement. Obtain -separate exact founder approval/attendance before provisioning or live actions. -Prove disposable intended result and identity/action/target/body/time/replay/crash/ -cleanup negatives, and return owner-native acceptance. Existing service login policy -and defaults stay unchanged. T02 still owns login/claim/TTL/use/MASON acceptance; -Secrets T06 separately owns dispatcher adoption and steady-state default migration. - - -2026-10-16 T12 wave 5 source preparation: disposable adapter now binds owner -canonical before/after metadata snapshots to existing descriptor hashes, rejects -per-target baseline/readback mismatch, and records readback/verified evidence. -Mediator reports cleanup-confirmed only after independent fixed cleanup receipts. -Complete role metadata and policy name/HCL/version/cas are compared; validated -modification timestamp alone is excluded. This is sequential fixture acceptance, -not aggregate CAS, concurrent-writer exclusion or native production completion. -Role conflict may follow a policy write. Restart refuses dispatch and cannot -recover memory-only token/accessor cleanup handles. T12 remains progress; actual -fresh CCR/catalog/grant, source/trust/caller/policy admission, isolated owner custody, -scoped recovery and attended native acceptance remain explicit prerequisites. - - -2026-10-11 T11 refinement (same source-review trial): the exact Secrets Engine -v3 manifest `6bb05bac74e4939c3e42db2ae95c5f9fb4616bb20cc285811a315c26669ebce1` -and all named bytes/proof were verified and independently re-reviewed. The three -v2 counterexamples are corrected: close exceptions cannot pass through stale true -flags, complete typed source/session/attempted/revoke evidence is required, and -mixed HTTP200 errors/data fails closed. 49 maker/current-provider tests plus three -independent regressions passed (52, synthetic/local only). Platform source review -`docs/reviews/2026-10-11-secrets-service-jwt-v3-platform-review.json` approves only -the corrected boundary verifier and truthful packet. Prior changes-requested v2 -review is retained; this is not a second native success or changed authority. -No native or founder go is granted. T02, T12 and Secrets T06 remain open; the -metadata-only execute-ccr pilot is explicitly insufficient for generic lifecycle -adoption. All consume-verification/journal/action/body/backend implementation and -native acceptance gates remain real prerequisites, not architecture promises. - - -2026-10-11 T11 readiness refinement: independently reviewed frozen consumer -manifest `709b9dddd1c8f00a995b1ff14264323fbe080ed95df244270ce9607528878fa7`. -Platform source review `docs/reviews/2026-10-11-secrets-execution-readiness-v2-platform-review.json` -approves the local fail-closed guard and proposed consumer contract direction only. -All ten current protected backend handlers pass provider args through readiness -before authorization/consume; session revoke checks before handle reads. Configured -auto/explicit service-jwt therefore cannot burn a protected-action approval or -open the insufficient login-self backend. Explicit env/bootstrap and self-only -probes retain their existing contracts; local compromise/reactivate and disabled -destroy remain separate. Existing ambient-token route readiness reads are unchanged, -not claimed governed by this guard. 58 isolated tests passed: original36 plus20 -provider/action cases, handler enumeration and actual session-revoke ordering. -Prior v2 changes-requested and v3 boundary source reviews remain unchanged, same -T11 trial. No runtime grant, native acceptance, new Access/Approval semantics, -backend entitlement or full Lifecycle adoption is approved. T02/T12/SecretsT06 and -WP13 implementation/native gates remain open; the metadata pilot substitutes for -none of the ordinary generic lifecycle outcomes. - - -## Complete independent KEY39 v4 lifecycle source review preparation - -```task -id: RPF-WP-0035-T13 -status: done -priority: high -state_hub_task_id: "8f6966fa-6e9a-58b2-8b40-c47340ec19d1" -``` - -2026-10-11: Platform agent loaded its owner instructions/INTENT and RPF35-T02, -actual applied CCR38, exact native role/policy source and frozen KEY39 lifecycle -v4 manifest `d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8`. -All staged entry hashes were verified without changing the reviewed inputs. -Source role omits expiration_leeway: its observed actual default must be confirmed -natively, with -1→0 and 0→150 normalization preserved as a bounded model. Source -claims and login-only policy are unchanged. The supplied 41 lifecycle tests pass; -five independent cases also pass, including a new exceptional-response-close -elapsed-bound counterexample and four current-leeway normalization checks. -A 10ms HTTP request can take over70ms because exceptional finally response.close -runs outside the hard elapsed timer. Unknown native login is correctly recorded, -but source total elapsed enforcement is incomplete. This completed owner -preparation record exists before the formal exact source review entry. No -credential was requested/read, native action run, owner checkout edited or native -session grant inherited. T02 retains native role/policy/leeway, negative claims, -cleanup acceptance and MASON gates; KEY39 producer handoff and founder attendance -remain prerequisites. Source correction/re-review is required, not a native success. - - -2026-10-11 T13 correction preparation (same trial): exact KEY39 v5 manifest -`d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285` -and all entry hashes verified. The v4 exceptional-close finding is addressed: -one 20ms abort is clipped to original remaining time and the nonresettable -aggregate cleanup budget; zero calls after aggregate expiry; failed abort stays -unconfirmed/unknown with signal state restored. 46 supplied lifecycle checks and -five independent regressions pass; the unchanged runner's earlier17 tests remain -valid against identical source SHA. Existing T13 preparation record is retained -before the formal v5 exact source review. V4 changes-requested review and earlier -failed history remain retained, with no native success, inherited grant or scope -promotion. Actual private producer admission, fresh installed role/leeway/policy -observation, authenticated founder window and native refusal/cleanup remain gates. - - -2026-10-11 T11 journal preparation refinement (source-only): independently -inspected frozen root-maker v2 manifest -`341cac1de945d2fbe8f516119909bd44fc37e85dccfb38b451981d4de2d402e7`, -verified all input hashes, and ran14 supplied tests plus5 independent checks. -The actual four-process race admits once; a separately forked process exiting -immediately after pending intent leaves durable unknown/nonresumable state. -Private SQLite modes, lock admission and binding/replay protections remain guarded. -Three independent counterexamples show after_call accepts False,0.0 or text'0' -as persisted integer ordinal0, contrary to before_call's exact integer validation. -Completed preparation under this existing done ownerT11 record precedes formal -review; require identical ordinal/call-digest validation before confirmation so -malformed calls cannot clear pending outcome. RPF35-T12 retains caller/PDP/consume -validation, exact body compiler/adapter and owner-private cleanup/recovery after -unknown; this unintegrated primitive proves none of those or full contract/native -acceptance. Inputs/prior reviews are preserved; no privileged/native action occurred. - - -2026-10-11 T11 journal v3 correction preparation (same trial): frozen manifest -`905ff6666d05df6a19d165a535579d917d7de31a43e066c2afc34a57e8cbdc9d` -verified against every file. Strict after_call integer0..63 and64hex string digest -validation now precedes SQLite confirmation; False,0.0 or text'0' cannot clear -pending external outcome.15 supplied tests plus5 independent regressions pass, -including realfork abrupt exit after durable intent and privatefile checks. -This existing completed ownerT11 refinement precedes the formal corrected-source -review. Prior v2 refusal is retained; no extra success/authority trial is counted. -Acceptance is only unintegrated journal source. Actual caller/PDP/consume validation, -independently compiled body/ordinal plan, restricted adapter, owner-private unknown -cleanup recovery and native/whole-contract adoption remain live RPF35-T12 gates. - - -2026-10-11 T13 node-route source preparation refinement (same trial): exact -successor manifest `23a972abc5175e121e594726d2b7f7a28c62109253f58f12623eaf2e5564a035` -verified against all entries. Platform Service openbao-ui-gateway8080 and its -nginx /v1 proxy agree with the proposed bounded founder-held node port-forward; -workstation tunnel/DNS assumptions are not reused as node evidence.54 supplied -lifecycle/route checks plus6 independent public-health/abort checks pass, offline. -Initial execute/recovery health pin and repeated pre-create check fail before -native/Secret effects; public-preflight reads neither stdin nor Kubernetes. -Completed ownT13 preparation precedes this fresh exact source review, not reuse -of v5 acknowledgment. Main execute currently reads private stdin before execute's -health gate; the packet's pre-token-input phrase is not accepted. Private producer -handoff must preserve actual ordering or move the health gate before credential -input when implemented. Public cluster digest detects drift, not authenticated -estate identity. No route was opened or observed; no source packet authenticates -a founder approval. Native role/plugin version and raw/effective expiration, -clock-skew and not-before leeways require actual owner observation/semantic proof. -Issuer readiness_state/Argo ownership is not established by the inspected source; -unmapped means production-tier. If adopted/production-approved, temporary direct -CAS requires an explicit reviewed one-use maintenance exception and close-down -entry, plus reconciliation, before native go. No helper/source acknowledgment -waives that gate. Private token producer/callback/client/custody admission and -actual native negative/revoke/restoration/cleanup remain prerequisites. - - -2026-10-11 T13 v7 node-route preparation refinement (same trial): exact frozen -manifest `90aabec336008d7d51e37c04b5ff96ba8ed6630821cca1a4c333d803367247f7` -and all entries verified.58 supplied lifecycle/route checks plus9 independent -checks pass offline. Actual main now verifies source/public health before private -stdin; execute preserves the original wall/monotonic window rather than renewing -it. Actual child-process partial-pipe counterexample shows unbounded stdin.read -remains blocked beyond a synthetic20ms original window (still alive after100ms -without EOF). No credential/native/provider access is involved. Before private -producer admission, require byte-capped, deadline-bounded JSON input and EOF within -the original window, with fail-closed fixed receipt and no protected effects. -This completed ownT13 preparation precedes formal v7 source changes-requested -review. Retain v6 qualified health-gate agreement and all prior reviews; neither -v7 health ordering nor any source acknowledgment establishes custody completion, -actual route/plugin/leeway/readiness/Argo identity or founder/runtime authority. -Native and private-producer gates remain open; no owner checkout write occurred. - - -2026-10-11 T13 v8 bounded-input correction preparation (same trial): exact -frozen manifest `316fd6005cc4cb7fe14ea69a5fd408aed13e6122afef60afd13d97952b27cf4a` -and all entry hashes verified.65 supplied tests plus12 fresh independent checks -pass offline, including a standard buffered stdin pipe with complete JSON/noEOF -interrupted within original20ms window and restored SIGALRM state; exact64KiB -JSON/EOF accepted and extra byte refused, UTF8 token bytecap enforced, original -wall deadline clips longer monotonic budget. Original preinput window is retained. -Completed ownT13 preparation precedes fresh exact source review; v7 refusal and -v6 qualified agreement/prior reviews retained. This accepts child bounded-input -source only. No parent-ready handshake exists: producer must not eagerly pipe -private inputs, and producer/session/window readiness protocol remains an -unimplemented admission gate. No custody/native completion, sourcepacket approval -authentication, route/plugin/leeway/readiness/Argo observation or inherited grant. -Native prerequisites and producer handoff remain open; no owner checkout changed. - - -2026-10-11 T11 journal v4 canonical-format preparation refinement (same trial): -exact frozen manifest `22c6d225d393b994ab7b44a43904ced1b6d7f74e0e2bebf71ffe99f87191ef4c` -and entry hashes verified. Actual access-engine pkg/api/canonical.go RequestDigest/ -CanonicalDigest, Approval binding.py and Secrets authorization/approval_consume -all use sha256:<64 lowercase hex> request digests. V4 now preserves this exact -request value without prefix stripping; plan/call/evidence content hashes remain -explicit bare64hex.15 supplied checks plus15 fresh independent checks pass, -including malformed prefix/bare/uppercase/trailing whitespace refusal before -reservation, exact persistent value/reopen/mismatch replay, private-file mode, -real child crash after pending intent, ill-typed confirmations and pending-unknown -refusal. Completed ownT11 preparation precedes formal v4 source review; prior v2 -refusal/v3 approval and T13 v7 refusal/v8 source acknowledgment retained. -Acceptance is unintegrated primitive source only; no data migration/native use -of v3 journal has occurred. RPF35-T12 caller/PDP/consume validation, independent -owner plan compiler/restricted adapter, private runtime custody, bounded execution -and owner-private unknown cleanup recovery remain open. No checkout/native writes. - - -2026-10-11 T13 paired ready-child/launcher preparation refinement (same trial): -exact NetV9 manifest `84542ddcff00932fc7ce22dcb2626be8942a3c006c5098a62fdd6b2478d0cd14` -and KeyproducerV2 `55a8e794af983ca2202e7a0054d0415a120d2c95018fb03fdb5f28313a109d58` -verified against all entries.74 Net checks plus31 Key checks and10 independent -checks pass offline including a truthful-unknown counterexample: adapter uncertain -private delivery with confirmed=false is copied into launcher receipt, but -consumer_outcome_unknown incorrectly remains false after local SSH teardown. -Complete bytes may have reached remote child before adapter timeout/EOF; local -terminate does not establish remote no-effects. Require sticky propagation of -uncertain delivery into consumer outcome before early return, retain unknown -unless independently established native outcome/recovery. Bad markers/source -pins/injection reject before private reads; extra marker authority/lines reject; -prohibited child output never forwards. Child-ready synchronization is not -authentication/approval. Proposed founder directSSH profile is incompatible with -warden quiet-output lane and not admitted here. Completed ownT13 preparation -precedes formal paired source review; preserve v7 refusal/v8 acknowledgment and -journal histories. No transport grant, coding-agent/private custody admission, -live SSH, credential/native action or owner checkout write. All native role/plugin/ -leeway/route/issuer readiness/Argo/maintenanceexception gates remain prerequisites. - - -2026-10-11 T13 paired producerV4 correction preparation (same trial): exact -producer manifest `d6f641dc1e4aae7d36658d4199d89af14d2fd7a4bd4e526856eb340234f2d33e` -and unchanged NetV9 `84542ddcff00932fc7ce22dcb2626be8942a3c006c5098a62fdd6b2478d0cd14` -entry hashes verified.74 Net tests,40 producer checks,9 actual local fakeSSH/ -realwrapper/child synthetic roundtrip regressions and12 fresh Platform checks -pass (135 total). V2 deliveryunknown now remains sticky consumerunknown with -completion=false after earlyreturn/localteardown. V3 Net FIFO acquisition finding -and originalbudgetreset are corrected: actualCLI starts original5s timer before -regular/nofollow/bytebounded contract acquisition, each later preparation phase -clips remaining time. ActualCLI FIFO refuses promptly; expired original budget -never validates/spawns/reads private files. Completed ownT13 preparation precedes -formal corrected pair source agreement. Preserve V2 Platform refusal, Key old -exactV2 acknowledgment and NetV3 refusal; no old acknowledgment repinned. -Agreement is only reviewed source synchronization/launcher behavior, not admission. -Founder authenticated SSH host/profile/channel/runtime pins, exclusive sameUID -file custody and explicit tmpfs file allowance/cleanup, verified human and -coding-agent producer/client/source, native role/plugin/leeway/route/issuer -readiness/Argo/maintenanceexception, refusal/revoke/restoration/readback and -unknown owner recovery remain prerequisites. Warden quietlane cannot carry ready -controlstdout; no transport/custody/native grant. No owner checkout/liveSSH/native. - - -2026-10-11 RPF35-T12 source implementation progress: durable journal source v4 -was independently accepted/committed; owner-derived typed plan compiler component -v2 is prepared at immutable manifest -`41cda74ab32384c789ea8a760475671aadad4e5121335c8f60567ff53d66665b` -with40 offline tests. Completed own bounded source-preparation ADHOCT02 anchor -was persisted at7adbcb4 before the qualified Platform contract/readiness receipt. -T12 progresses for actual source implementation underway, not native acceptance -or full descriptor/authority readiness. The compiler derives local CCR/HCL/role -bodies, matches actual independently pinned Secrets catalogue and revalidates -source/body before fixed calls; no body/target chosen by service is dispatched. -Independent technical integration review and exact full ExecutionContract/owner -grant/baseline/estate/deadline matching, initiating human/User/Tenant PIP/transport -representation/service identity, served PDP/claim/consume correspondence, journal -call dispatch/restricted adapter, native token TTL/uses/custody/revoke/cleanup and -owner-private unknown recovery remain unimplemented integration/native gates. -Actual CCR42 has no eligible approved pilot/catalogue; no source metadata is -invented. T02 and original negative/capability/native evidence remain unchanged; -T11/T13 history retained, no defaultflip/nativego or SecretsT06 closure follows. - - -2026-10-13 T12 source correction: the compiler hashed the catalogue sources but -used cached Python modules with matching paths, which did not establish executing -those exact bytes. A failing regression reproduced the substitution. Catalogue, -package and dependencies now execute from the verified snapshot in temporary -private modules; ambient modules remain untouched and cleanup runs on refusal. -45 compiler plus 15 journal tests pass. The v2 source review remains historical; -this change needs its own integration review. T12 retains all existing full -contract, identity/consume, dispatch, custody and native acceptance requirements. -RPF-IN-0012 remains open for the independent contract/wrapped-delivery return; -this code correction does not authorize either operation. - -2026-10-16 T12 source preparation (isolated owner checkout; not owner delivery): -added durable precommitted cleanup obligations and separate outcome evidence to -the existing Journal, plus a fixed three-method owner cleanup driver. Same live -postconsume admission can clean after a business timeout/failed/unknown outcome; -cleanup closes business dispatch and cannot replay or restore admission after a -crash. Empty obligations no longer produce cleanup-confirmed. Real SQLite -multiprocess races and abrupt process death before/after a separately durable -synthetic effect verify refusal on reopen. Current compiler/journal/cleanup suite: -73 passed, local source/fixture only. Details: docs/task-execution-cleanup.md. -No native adapter or production mediator exists; full grant/request/cleanup binding, -authenticated consume/identity, ephemeral token issuance, deadline/use checks, -native effect/revoke/absence, audit and protected recovery remain prerequisites. -T12 remains progress. No CCR38 default/entitlement or live grant was changed. - -2026-10-16 T12 second source package: Compiler Plans now bind deterministic cleanup -commitments and can bind the complete independently installed engine execution -descriptor. A fixed disposable-loopback native adapter actually materializes exact -per-operation metadata ACL/task token, executes six source-derived metadata calls, -and verifies self-revocation/accessor invalidation and policy retirement. Each -cleanup request has its own durable intent. Native fault tests cover lost response -after real write and policy deletion while task revoke fails. All initiating -identity/approval/consume/grant/state/source admission in this fixture is synthetic; -no production authority or complete native owner acceptance is claimed. Exact -remaining owner schema/admission and mediator work: docs/task-execution-native-fixture.md. -T12 stays progress; no live grant, credential/default change or owner sync performed. - -2026-10-16 T12 third source package: implemented proposed task-execution-grant -immutable projection and exact compiler/native-fixture integration through the -existing descriptor grant ID/SHA fields. Only typed review.comments/evidence.events -are excluded; separate append-only checks reject removal/rewrite/reordering and -unknown guessed evidence exclusions refuse. Owner-pinned grant bytes and validator -source must match independently reconstructed descriptor/source/cleanup/ACL/token -bodies before a native fixture can materialize. Proposed source records gain no -execution authority; no real grant, policy or legacy applier registration added. -Details and remaining production gates: docs/task-execution-grant-projection.md. -T12 remains progress; no owner sync or production effect performed by this package. - -2026-10-16 T12 fourth source package: connected the Secrets-owned strict task -handoff to actual Approval ExecutionConfirmationClient independent GET before -consume-confirmed/native fixture work. Platform accepts no supplied authorization -boolean/object/callback and preserves its exact owner descriptor/grant/Plan join. -Durable CAS/GET intents precede native ordinals through the repaired journal -prefix API. Actual source-server fixture uses signed Access/enforced synthetic -TokenReview, real Approval WSGI/SQLite with distinct synthetic human/service -identities, actual strict Secrets signature/CAS validation and disposable native -OpenBao. Untouched actual task policy remains signed DENY; separately named -synthetic fixture policy is never production admission. Read-scope denial after -CAS records consumed/unknown with zero native effects. Exact dependency/source -hashes, tests and remaining gates are retained with the source package and in -docs/task-execution-mediator-seam.md. No real grant/custody, policy default, owner -sync or native production acceptance is performed; T12 stays progress. - -2026-10-16 T12 recovery successor source preparation: optional independently -installed fixture custody durably retains operation-bound private accessors and -issuance checkpoints. Fresh cleanup-only owner permits can reconcile after actual -executor process death using fixed revoke/readback and policy delete/readback, -without restoring business/consume admission. Separate durable attempt evidence -retains original unknown outcomes. Lost token-create response remains unresolved -when no accessor was committed. Source-only process-loss fixtures are not native -production custody or recovery-role admission; T12 remains progress. - -2026-10-16 T12 recovery R2 source refinement retains R1's overbroad 404 failure: -policy cleanup now requires exact native empty-errors absence before/after delete; -HCL uses only documented single optional terminal LF normalization. Private -capsules are owner-key HMAC authenticated, strict nested schemas/ordered cleanup -commitments reject damage, and journal history prevents stale prepared capsules -from hiding issuance. Fixture process-loss source proof remains separate from -unadmitted production custody, integrity-key storage and recovery-role scope. - -2026-10-16 T12 independently reviewable budget/source slice: bound native HTTP -header/body/close, compiler work and cleanup through fixed owner main-thread -absolute budgets, with actual task expiry narrowing global authority. Actual v2 -mediator now verifies each independently installed source_bundle byte hash before -authorization/effects. No deadline/TTL extension, remint or generic service added. -Retained registry actual-provider proof still refuses Approval correspondence -before CAS/native; normalized pre-approval + precise immediate CAS whole-stack -positive remains live owner follow-up under CUST-WP-0080. T12 stays progress. - -2026-10-16 T12 actual v2 successor source proof: retained registry signed DENY -normalization from the real Secrets prepare_task_approval builder precedes actual -Approval issue/human approval. Actual Access v2 then signs ALLOW; strict Secrets -CAS and independent confirmation GET precede six disposable native calls, exact -readback and confirmed token/policy cleanup. Approval precision source permits -immediate CAS without sleeps/backdating. The 60-second immutable deadline, -30-second backend TTL, 120-second synthetic observation and existing cleanup -reserve remain bound; issued credential expiry narrows business time. Source, -caller, revoke, source-reader, registry drift and expiry refusals cause no native -effects; revoke between ALLOW/CAS retains unknown without business dispatch. -This owner fixture uses separately installed synthetic identity/key/trust and -loopback OpenBao, grants no production authority and keeps T12 progress. Prior -failed temporal/normalization proofs remain retained. - -2026-10-16 T12 signer custody R3 source preparation rebases the proposal to -integrated Access919ce25 and preserves current workplan state. Added protected -in-memory Ed25519 CAS/readback primitive with durable closed metadata receipts -and fixed disposable-only owner adapter. Actual synthetic OpenBao bootstrap, -rotation, conflict/unknown/readback/receipt/private-output and admission failures -are tested. Production direct entry refuses: exact CCR/founder scope, protected -executor/fencing/receipt custody, independent consumer public admission, migration -debt and native deployment prerequisites remain open. No live key/grant or CCR -admission; RPF-WP-0035-T12 remains progress. - -R3 source metadata correction aligns proposal IDs across JSON/docs/Secret -annotation and documents existing base64url-ASCII public fingerprint hashing. -Frozen R2 source/evidence is preserved; no writer behavior or admission changes. - -2026-10-16 T12 native signer preparation: proposed CCR-2026-0050 and generated -read-only HCL pass real schema checks; current proposed status/unconfirmed claims -keep apply refused. Native candidate uses existing external-secrets-* role -convention with dedicated SA unchanged and projected private field only. Exact -attended CAS0/metadata/fence/receipt/forward-recovery action plan is separate from -read-grant authority; production adapter implementation/independent tests and -founder plus distinct owner reviews remain necessary. Access observed zero PVCs; -new outbox storage/retention/runtime bounds remain separate Access/cluster scope. -No live credentials, grants, roles, key or deployment changed. T12 stays progress. - -2026-10-16 T12 native signer adapter source: silent fixed-scope attended child -checks exact independent admission/source/CCR/image/caller/trust/identity inputs, -actual backend/reader role/policy, signer fence and private exclusive receipt -custody before fixed metadata/CAS0 effects. Separate fresh read-only recovery -reconciles real process loss after native write without remint/retry/new effects. -Disposable tests exercise actual KV while owner reviews/identity/fence facts are -explicitly synthetic. Current warden plan dispatch selects structure-survey, not -OIDC admin login; mismatch remains a live refusal. Canonical proposed CCR, native -owner/founder approval and Access/cluster/outbox/PVC gates stay open; no live -key, credential read, grant, role, storage or deployment action performed. - -2026-10-16 T12 native signer R2 source correction preserves R1 failures: exact -phase-aware read-only recovery classifies current metadata-only/absent states, -never admits a key or retry and preserves original uncertainty. Fresh recovery -explicitly binds historical execution plus current source; current primitive -plan/source/receipt join is enforced and old pin labeled historical. Real fork -metadata-loss and denied/ambiguous tests added.120second expiry is unchanged; -external reviews and unbounded OIDC require a pre-reviewed exact scheduled -window, otherwise refuse/new explicit revision. No native or owner approval -inferred; no live action. - - -2026-10-16 T13 bounded source-review preparation note (no status/scope closure): -Native signer owner-boundary R3/R4 preparation replaces conflated -cluster-gitops-owner with cluster-runtime-storage-owner. Platform retains -OpenBao and existing GitOps Applications/sync-freeze integration; Access owns -workload/Helm release semantics; cluster reviews runtime/storage/headroom; -Enablement participates only for actual ArgoCD installation/controller changes. -Source facts reconcile implemented/tested native adapter a3e8eb64, canonical -local draft package d52f811/tree d4fbad7 (frozen source2b/current05a) and -ordinary CI402 published candidate84c78e0/image09a83b with OS lock/64MiB/startup -hardening. Prior R1/R2/R3 and packetP1/P2 reviews/failures remain frozen. -No missing-local-declaration waiver, remote placement, actual stopped fence, -PV retention, native custody, independent post-key trust or founder/owner -approval is inferred. Exact successor source/CCR/plan pins and fresh scoped -reviews remain required; T12 native gates and historical T13 outcome unchanged. - -2026-10-16 T12 bounded native plan review: Platform independently approves the -exact P4/R2 custody/read-lane and existing manual Argo Application freeze/recovery -proposal only, per docs/evidence/2026-10-16-flex-auth-native-plan-review.json. -Current observed inventory has no signer Application; root and informed-decision -manual sync are held during ceremony, while shared openbao-secretstore and legacy -eso-token-renewer stay unchanged. Exact CCR read grant and one-use custody writer -remain separate. Actual CCR/founder/entity/expiry/window/fence, native ESO/readback, -independent Secrets public trust and Access/cluster runtime proofs remain gates. -No authority/admission field changes; T12 remains progress. - - -## Founder approval of the flex-auth envelope signing key custody scope (CCR-2026-0050) - -```task -id: RPF-WP-0035-T14 -status: wait -priority: high -needs_human: true -blocking_reason: "Founder resolves decision 18731e84 approving the exact CCR-2026-0050 custody scope (KV document, one CAS0 bootstrap, read policy and ESO role)" -depends_on: [RPF-WP-0035-T12] -decision_id: 18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508 -state_hub_task_id: "e310197f-d55c-5732-b606-8b0d88a69836" -``` - -Human gate for custody route `flex-auth-decision-envelope-signing-key`. The -founder resolves hub decision `18731e84-a6ab-4ac5-bf0e-7e7c3a9c4508`, which -states the exact new scope: KV document -`platform/data/workloads/flex-auth/envelope-signing` (seed, public key, kid; -CAS-required, max 2 versions), one attended CAS0 bootstrap with kid -`flex-auth-lifecycle-20261016-r1`, read policy -`workload-kv-read-flex-auth-envelope-signing` and Kubernetes role -`external-secrets-flex-auth-envelope-signing` (SA `flex-auth-envelope-signing-eso`, -namespace `flex-auth`, audience `openbao`, 15m). Approval is scope only; each -live step in `docs/runbooks/flex-auth-envelope-signing-attended-creation.md` -still needs the founder's go at the time. - -2026-10-21 state: railiance-platform-owner approved (runbook review); -secrets-engine-owner approved with two receipt conditions (hub message -e73c9ffc, recorded in the CCR); access-engine-owner approved for Access scope -(hub message 6529bacf; its rollout go is separate decision 4fab263d); -cluster-runtime-storage-owner (railiance-cluster) review requested in hub -message f8b4b229. The warden routing defect from 2026-10-16 no longer -reproduces (`docs/evidence/2026-10-21-flex-auth-signer-warden-route.json`). -The native adapter's owner-review digest no longer binds `expires_at`, so -reviews can be given ahead of the 120 s attended window; the expiry check is -unchanged. Recorded as a build-mode change in the-custodian -`docs/security-close-down-checklist.md`. Nothing generated, written or applied. - -Done when: the decision is resolved, `sync-decision` copies it into the CCR, -all five dispositions are recorded, and the runbook preconditions hold. diff --git a/workplans/RPF-WP-0036-platform-service-assurance.md b/workplans/RPF-WP-0036-platform-service-assurance.md index aa231a3..da17dda 100644 --- a/workplans/RPF-WP-0036-platform-service-assurance.md +++ b/workplans/RPF-WP-0036-platform-service-assurance.md @@ -63,25 +63,14 @@ and package dependency rather than a blanket new-cluster project here. ```task id: RPF-WP-0036-T03 status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e depends_on: [ACTIVITY-WP-0042] -needs_human: false -blocking_reason: "D2 accepts the activity-core scheduling role; ACTIVITY-WP-0042 must supply the implemented bounded scheduler and scoped custody/execution proof before recurring backup/restore work can execute. No new founder go-ahead is required." +blocking_reason: "activity-core replied (not accepted yet): role acceptance is the founder decision ACTIVITY-WP-0042-T01 (D2); then ACTIVITY-WP-0042 bounded operation, S3 OpenBao credential route, repaired openbao-activity-core store and per-run approval-engine approval." priority: high state_hub_task_id: "d64446fb-870b-5ae9-9628-f1fb9d06c5a4" ``` -2026-10-27: the OpenBao Raft snapshot schedule slice is tracked as RPF-WP-0066-T05 -for NetKingdom NK-WP-0055; this task keeps the cross-service scope. - -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - -2026-10-11 dependency audit: D2 resolved the scheduling-role decision. The -remaining blocker is the existing ACTIVITY-WP-0042 owner implementation and -scoped runtime proof, not candidate promotion or a repeated human gate. Current -service-assurance source already classifies metadata signals and adapts pinned -recovery receipts; it explicitly reports recurring cadence/delivery as pending. - Inventory authoritative CNPG backup/PITR, OpenBao snapshot/isolated restore, encrypted off-host copy and custody-recovery receipts. Reuse existing validators and package status commands. Define cadence/expiry from T02; return distinct diff --git a/workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md b/workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md index d8a1baa..d4d8eb8 100644 --- a/workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md +++ b/workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md @@ -8,7 +8,7 @@ status: blocked flavor: implementation owner: codex created: "2026-09-06" -updated: "2026-10-13" +updated: "2026-10-03" state_hub_workstream_id: "7beec1a7-aa82-5a36-9a66-6b60008a2455" --- @@ -73,13 +73,13 @@ removed. Evidence: `docs/evidence/forgejo-scaleway-restore-2026-09-06.json`. ```task id: RPF-WP-0038-T04 status: wait -blocking_reason: "Durable scheduled caller, verified inventory and quota/retention gates remain open; September 15 prohibits expiration and scheduled secondary cutover." +needs_human: true +decision_id: 62aa95e4-3ccc-4867-8806-5e3db005b483 +blocking_reason: "Operator chooses the independent archive prefix and confirms retention/caller contract and bounded worker credential delivery before the primary archive retrieval." priority: high state_hub_task_id: "a4807df0-ec96-58f1-9cbd-42b1dcde0d6f" ``` -2026-10-11: requalified — founder chose prefix .../application-archives/forgejo/, retention 7d+4w+3m via lifecycle, Nextcloud essentials-only secondary. If issuing the prefix-scoped credential needs the founder's Scaleway console or OpenBao login, add one exact command to the attended-window checklist. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md (Round 3). - Choose an independent archive prefix and confirm storage retention/caller contract, bounded worker credential delivery and multipart transfer/abort. Then retrieve a primary full archive and execute isolated application recovery. diff --git a/workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md b/workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md index 79fbb80..cd4e035 100644 --- a/workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md +++ b/workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md @@ -134,15 +134,13 @@ are placeholders and must be filled from the attended read. ```task id: RPF-WP-0043-T03 status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e -blocking_reason: "Steps 1 and 3 done 2026-10-11 (docs/evidence/2026-10-11-policy-nexus-adoption-readonly-precheck.json, no stop condition). Step 2 render diff needs the railiance-apps values path from RAPP-POLICY-NEXUS-WP-0002-T02; agents can run it read-only via the k3s tunnel." +blocking_reason: "Draft is prepared. The attended read-only helm history / kubectl diff remains. This session did not touch the cluster." priority: high -depends_on: [RAPP-POLICY-NEXUS-WP-0002-T02] state_hub_task_id: "2afb1aac-66dd-5e1e-92e1-fc020a59a927" ``` -2026-10-11: read-only checks are agent-runnable (k3s-api-railiance01 tunnel); no longer an attended item. - Draft the Application under `docs/`, **not** under `argocd/applications/` on `main`. The single-source sketch is superseded by the tenant's multi-source shape. The draft is `docs/drafts/policy-nexus-application.yaml`: chart @@ -174,15 +172,14 @@ render lacks but that carries the tracking label, or a digest mismatch. ```task id: RPF-WP-0043-T04 status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e depends_on: [RPF-WP-0043-T03, RPF-WP-0044-T07] -blocking_reason: "Merge approved by D2; runs once T03 diff and root automation (RPF-WP-0044-T07) are proven." +blocking_reason: "Founder go-ahead: the merge is a live production change (CONSTRUCT @ railiance-platform, activation=APPROVED); also needs the root automation restored in RPF-WP-0044-T07." priority: high state_hub_task_id: "8638fa73-439d-5dcd-85c7-81aa125170e1" ``` -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - **Waits on the founder's go-ahead.** This is a `CONSTRUCT @ railiance-platform` change with `activation=APPROVED`. The merge is the gate, and it is a live production change. Do not take it on the basis of this plan alone. diff --git a/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md b/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md index 4694d9d..380b6d8 100644 --- a/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md +++ b/workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md @@ -9,7 +9,7 @@ flavor: planning owner: railiance-platform topic_slug: railiance created: "2026-09-21" -updated: "2026-10-11" +updated: "2026-10-03" related: [RPF-WP-0043, RPF-WP-0022] state_hub_workstream_id: "98140775-3b9a-5cf9-9af6-722502d487dc" --- @@ -185,14 +185,14 @@ Rollback: ```task id: RPF-WP-0044-T03 -status: done +status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e +blocking_reason: "Adopted 2026-09-21; the follow-on step (issue-core adoption, then automation) waits on the founder's go-ahead." priority: high state_hub_task_id: "6d5fc801-e361-579b-bcd8-6fe719a82e94" ``` -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - **Adopted 2026-09-21** (founder go-ahead; custodian session): diff at `d2dbc19` rc=0; child merged in `182e788`; root synced at `182e788`; child manual sync (apply strategy, prune off) Succeeded at `d2dbc19`, Synced/Healthy, automated off. `ClusterSecretStore/openbao` Valid with tracking annotation; `issue-core-runtime` SecretSynced. Status `progress` = in the 24h proving period; `selfHeal` needs a second go-ahead. Evidence: `docs/evidence/2026-09-21-openbao-secretstore-argocd-adoption.json`. One object, @@ -233,74 +233,48 @@ Rollback (R1): `git revert` the merge commit and push, then 2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here. -2026-10-11: **24h adoption proof verified; T03 done.** -`scripts/argocd_adoption_proof.py` freshly verifies estate IP 92.205.62.239, -local-cluster destination, exact source pin `d2dbc19`, one tracked -ClusterSecretStore/openbao, manual idle policy, successful Sep21 apply/prune-off -sync, exact ownership annotation and uninterrupted Ready conditions on the store -and issue-core consumer since Aug19. The completed proving period is 1,735,984 -seconds. Current Application is Synced/Healthy; the nonblocking -OrphanedResourceWarning is retained and no broader namespace adoption is claimed. - -Both server-side and client-side native pinned-render diffs return **0** after -canonical Argo v3 annotation injection. This is bound to the observed v3.5.3 -controller, `argocd-cm` tracking configuration and the Application destination -namespace. The cluster-scoped store's tracking ID uses `external-secrets` as -Argo's destination-namespace fallback; no instance label is injected in annotation -mode. Same-source-API-version server dry-runs show **no changed JSON paths**. -Only source-derived desired fields and Argo's exact tracking annotation are -rendered; no live values are copied into the desired source, and unknown or -substantive diffs fail closed. Upstream implementation: -https://github.com/argoproj/argo-cd/blob/v3.5.3/util/argo/resource_tracking.go -(`UnstructuredToAppInstanceValue`, `SetAppInstance`). - -Evidence: `docs/evidence/2026-10-11-openbao-adoption-proof/`: -`observation-argo-normalized.json`, `proof-argo-normalized-final.json`, and -`validation.json`. The proof binds the verifier and observation digests. Thirteen -meaningful fixture tests and compilation pass. Earlier empty-namespace and raw -source diff refusals remain visible: raw source omits Argo-owned tracking and -last-applied metadata; an initial API-version comparison also used the API -default rather than the pinned source version. No native mutation, Secret read -or annotation value/error output occurred. This closes the existing adoption's -proving period without changing sync/automation policy or using a new human gate. - ## Adopt issue-core (from rapp-issue-core) ```task id: RPF-WP-0044-T04 -status: done -intervention_note: "Orange attended Platform OpenBao OIDC/MFA login for the already D2-approved exact repository reader; no second source-scope approval." +status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e +blocked_on: message-from:rapp-issue-core +blocking_reason: "Founder go-ahead plus rapp-issue-core's agreement (hub message) that ArgoCD owns production and make deploy stops being a production path." priority: high state_hub_task_id: "0e619658-0453-51ed-b9a0-bed28b5544fd" ``` -D2 already authorizes this exact private repository read-only reader and the ordered adoption. No renewed founder go-ahead is required. The remaining human act is the existing Platform OpenBao attended OIDC/MFA login; route it through the single attended-window checklist. +**Waits on the founder's go-ahead**, and on two preconditions: -Source preparation now disables wrapper direct deployment/rollback and verifies Argo ownership using the exact HTTPS or SSH source URL. The fixed credential executor is `scripts/provision_argocd_issue_core_repository.py`, reviewed in `docs/evidence/2026-10-11-issue-core-credential/owner-review.json`. It creates one repository-only read-only SSH deploy key, stores it at `platform/operators/argocd/repositories/rapp-issue-core`, response-wraps for 60 seconds, unwraps once, proves consumed-token denial, and creates only `argocd/rapp-issue-core-repository`. Tests and installed Bao synthetic checks pass; native issuance/adoption have not run. The contract example is incomplete and cannot authorize execution. +- `rapp-issue-core` agrees (hub message) that ArgoCD owns production and its + `make deploy` stops being a production path; it would fight self-heal. + Image releases become commits to `manifests/30-runtime.yaml`. +- A CCR for the ArgoCD repository credential, source at + `platform/operators/argocd/repositories/rapp-issue-core`, read-only deploy + token. Applied with `make argocd-repo-apply`; no value is handled in this + plan. -Public host trust and exact project admission are prepared in owner source and the bounded `scripts/argocd_public_transport.py`; its server dry-run passed. Preserve each exact attempt and any partial failure. Finish those prerequisites, seal the owner-reviewed contract, use the attended login, then pin the newly committed wrapper source in the Application, inspect client-side native diff, manually sync with prune/force disabled, and run the bounded owner verifier. Image releases and rollback use Git revisions plus Argo; direct `make rollback` is disabled. Never adopt the stale wrapper revision that predates the ExternalSecret metadata guard. +Zero diff against live for both the raw `manifests/` and the rendered output +(digest `sha256:a56c80cc…b92cf`). All kinds are in the `railiance-tenants` +whitelist. Live check: `rapp-issue-core make verify-live`. Workload restore: +`rapp-issue-core make rollback`. -2026-10-20 (CUST-WP-0079-T06 attended-window prep, agent): prerequisites done. Host trust and exact project admission were executed 2026-10-11 (the-custodian `docs/evidence/2026-10-11-issue-core-public-transport/execution.json`); a fresh dry-run refused with `target_preimage_changed` (already applied, kept as `docs/evidence/2026-10-11-issue-core-transport/2026-10-20-dry-run-r1.json`) and `--action verify` passed (`2026-10-20-verify-r1.json`). ssh-keyscan from railiance01 matches the pinned provider key. Sealed contract `docs/evidence/2026-10-11-issue-core-credential/contract-sealed-2026-10-20.json` (sha256 `1ce9d2fdc39395407a01e358bfd0179f83f35c0a448e6658eb8f5aab351d3d3a`, expires 2026-10-27) passes the executor's `validate_contract`; 17 executor/transport tests pass. Wrapper pin target is rapp-issue-core `186c817b67d3919a64acdddbd3889e539841f946`. The only remaining step is the founder's attended login running the executor; exact commands and the agent follow-on (pin, diff, manual sync, verifier) are in `docs/runbooks/rpf-wp-0044-t04-attended.md`. No secret, credential or OpenBao call was made. - -2026-10-20 (adoption, agent, founder explicit go + D2): the journal `contract_sha256` 0fd6d6fa... is `sha256(json.dumps(contract, sort_keys=True))` of the sealed contract (recomputed, exact match; the raw file hash 1ce9d2fd... was enforced at launch). Child placed in `argocd/railiance01/applications/issue-core.application.yaml` (SSH repoURL, 186c817) in `d70422c` and applied directly, because `railiance-apps-root` is also OutOfSync on activity-core, informed-decision-requester and state-hub-memo-dispatch (`make argocd-pin-drift`: all ok). Argo fetched the SSH source at 186c817 without ComparisonError; Healthy, OutOfSync (8 untracked objects). Client-side diff of the pinned render (T05 method): rc=0; the render holds no credential objects. **Manual sync not performed**: the agent harness auto-mode classifier denied the Application sync-operation patch. Nothing was rolled back because no workload changed; the new Application has no automated sync. Remaining: the founder runs (or permits) runbook step 4 sync, then the step 5 verifier. Receipt: `docs/evidence/2026-10-20-issue-core-argocd-adoption.json`. - -2026-10-20 (attended window, CUST-WP-0079-T06): founder completed the Platform OpenBao OIDC/MFA login and the fixed executor at 10:37Z. Journal `docs/evidence/2026-10-11-issue-core-credential/execution-journal-r1.json`: state `delivered_not_adopted`, consumed-wrap replay denied (400), no secret values observed by the agent, custody version 1, destination `argocd/rapp-issue-core-repository`. The human gate is cleared. Remaining agent steps per `docs/runbooks/rpf-wp-0044-t04-attended.md`: pin the Application, client-side diff, manual sync (prune/force off), bounded verifier. The journal's `contract_sha256` (`0fd6d6fa…`) differs from the sealed file's sha256 (`1ce9d2fd…`). Reconcile this before the adoption sync. - -2026-10-20 adoption complete. The founder ran the manual sync (apply strategy, prune/force off). Argo reports issue-core Synced/Healthy at `186c817`, operation Succeeded. The bounded verifier `make verify-live` passed with all checks true: healthz 200, unauthenticated ingest 401, immutable image, ExternalSecret Ready, no secret values observed. Receipt: `docs/evidence/2026-10-20-issue-core-verify-live.json`. The runbook's ssh quoting was fixed. Automated sync is still off; enabling it is a separate decision. T04 done. +2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here. ## Adopt target-revenue ```task id: RPF-WP-0044-T05 -status: done +status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e +blocking_reason: "Adopted 2026-09-21; remaining preconditions and the next step wait on the founder's go-ahead." priority: medium state_hub_task_id: "d418068a-6fb0-5416-aac5-d23c93924d9c" ``` -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - **Adopted 2026-09-21** (founder go-ahead; custodian session). The *server-side* diff at `f1109d5` conflicted on `resources.limits.cpu` (live `1` owned by `kubectl-client-side-apply` vs declared `1000m`: the same quantity); the *client-side* diff, which is what the apply strategy uses, was rc=0 for all five non-hook objects. Child merged in `6eeb826`; manual sync with the `apply` strategy, prune off: Succeeded, Synced/Healthy, **0 Jobs created**, same pod (36d, no restart), CNPG healthy 1/1, ExternalSecret SecretSynced, `https://revenue.coulomb.social/healthz` 200 (the live check path is `/healthz`, not `/health`). Status `progress` = proving period. Automated sync still needs target-revenue's confirmation on the two hook Jobs (asked 2026-09-21). Evidence: `docs/evidence/2026-09-21-target-revenue-argocd-adoption.json`. Original preconditions: T01 item 2 (CNPG @@ -317,45 +291,19 @@ target-revenue commit. 2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here. - -2026-10-11: **manual adoption proving complete**. Fresh read-only proof at 2026-10-11T19:51:12.812423+00:00 -verified exact pin `a3a8a27a8cda32ae3c7b55353ee16a131c50a0a0`, manual apply strategy, -prune/force off and no pending operation. The last successful pinned apply finished -2026-09-28T14:23:27Z; 1142865 seconds of proving exceeds 24 hours. -All five non-hook objects are tracked and Synced; Deployment Available (generation observed), -ExternalSecret Ready and CNPG Ready (1/1). ArgoCD remains Synced/Healthy; normalized native -client-side diff is zero and `https://revenue.coulomb.social/healthz` returns 200. Both declared -Sync-hook Jobs are absent and excluded from the apply-strategy diff. No live mutation or -Secret read was performed. Receipt: `docs/evidence/2026-10-11-target-revenue-adoption-proof/receipt.json`; -sanitized observation alongside it. Reusable check: `scripts/workload_adoption_proof.py`; -35 new failure/normalization tests plus 13 existing OpenBao adoption tests pass. - -This closes T05 adoption/proving only. Child automated sync stays off. The existing -target-revenue owner handoff for hook re-execution safety remains a prerequisite to future -automation; it is not silently resolved and no new residual is invented here. The observed -OrphanedResourceWarning is non-blocking; other Application condition types fail verification. - ## Adopt external-secrets (highest risk) ```task id: RPF-WP-0044-T06 status: wait -decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e -priority: medium -depends_on: [RPF-WP-0044-T04, RPF-WP-0044-T05] needs_human: true -blocking_reason: "Attended founder run of scripts/rpf_wp_0044_t06_attended.sh (KUBECTL_ROUTE=founder-sudo): fresh pre-checks, Application create, typed ADOPT, manual SSA sync prune/force off, post-checks. Runbook docs/runbooks/rpf-wp-0044-t06-eso-adoption.md." +decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e +blocking_reason: "Founder go-ahead for the ESO adoption after adding CRD Prune=false,Delete=false and re-running the diff." +priority: medium state_hub_task_id: "4ca54868-c31f-5e3d-b7d3-a15b84c15f80" ``` -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - -**Authorized by D2; waits on predecessor automated proof, not another founder go.** -The draft already carries CRD prune/delete protection (verified preparation -2026-09-27; 39 objects, 20 protected CRDs, metadata-only diff). That historical -receipt is preparation, not current adoption proof. The deterministic -`scripts/eso_adoption_readiness.py` checks the draft and bound predecessor -receipts before a fresh native render/diff is considered. ESO serves every +**Waits on the founder's go-ahead.** Zero diff, but ESO serves every `ExternalSecret` on the cluster (25 ClusterSecretStores). Before the merge, add `crds.annotations: {argocd.argoproj.io/sync-options: "Prune=false,Delete=false"}` to the values and re-run the diff; pruning a CRD would delete every @@ -363,33 +311,27 @@ to the values and re-run the diff; pruning a CRD would delete every Watch for drift on `caBundle` fields injected by the cert-controller; if ArgoCD shows it, add `ignoreDifferences` before enabling self-heal. After adoption, retire the stale `pending-upgrade` Helm release record (its -`sh.helm.release.v1.external-secrets.v*` Secrets) under a separate reviewed cleanup; +`sh.helm.release.v1.external-secrets.v*` Secrets) under a separate go-ahead; ArgoCD does not track them. Live check: all ClusterSecretStores keep their current `Valid` state (two, `openbao-activity-core` and -`openbao-email-connect`, are already `ValidationFailed`). Workload restore: revert the Platform Application to the previously reviewed -chart 0.16.1 values in Git, then manually sync through ArgoCD; never prune/delete -CRDs or silently replace the Helm release record as part of adoption. +`openbao-email-connect`, are already `ValidationFailed`). Workload restore: +`helm template` 0.16.1 with the same values, `kubectl apply --server-side`. 2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here. -2026-10-21 (preparation, agent, no live mutation): `scripts/eso_adoption_readiness.py` passes the source check (chart 0.16.1, 20/20 CRDs Prune=false,Delete=false, ServerSideApply, manual, no finalizer, no Force/Replace) but reports T04 and T05 missing. The 2026-10-20 issue-core verify-live receipt has no authenticated smoke statuses (it ran without `--smoke`, and `--smoke` creates and closes a Forgejo issue, so it is not read-only) and is outside the 300 s window. A fresh read-only target-revenue proof failed because `ssh railiance01` (tegwick) can no longer read the k3s kubeconfig after the CUST-WP-0073 cutover. Offline render of 0.16.1 with the draft values: 39 objects, no ClusterSecretStore or ExternalSecret rendered, webhooks declare no caBundle, the webhook-cert holder is metadata-only; no ignoreDifferences added yet. The fresh live client-side diff and the store baseline were not run (no agent cluster read path). Receipts: `docs/evidence/2026-10-21-eso-adoption-prep/`. Runbook with the exact founder sync command: `docs/runbooks/rpf-wp-0044-t06-eso-adoption.md`. - -2026-10-21 (founder decisions, agent preparation): the founder will run ONE attended script from their own terminal through their admin path, and allowed the issue-core `--smoke` controlled write in that pre-check. Agent RBAC is not widened. New `scripts/kube_route.py` adds `KUBECTL_ROUTE` (`agent` default, `founder-sudo` = `ssh railiance01 sudo -n …`; no `ssh -t`, since `sudo -n` keeps pipes clean). It is used by the transport, adoption-proof, workload-proof, activity-readiness, pin-drift and service-observation scripts and by rapp-issue-core `tools/verify_live.py`. `scripts/rpf_wp_0044_t06_attended.sh` runs the fresh proofs, readiness gate, render, client-side diff, structured classification and store baseline, stops on a no-go, then creates the Application, requires typed `ADOPT`, syncs (prune/force off, SSA) and runs the post-checks and refresh sample; on failure it prints the rollback and never prunes. Evaluator `scripts/eso_attended_checks.py`; credential-kind objects are split out before any live read. Offline validation: shellcheck clean, `--dry-run` prints commands only, 118 tests pass in the affected files (new: test_kube_route, test_eso_attended_checks; rapp-issue-core: test_kube_route). Not verified live: `sudo -n` pipe behaviour under the host `use_pty` default; the `--precheck-only` run will show it. - ## Turn the root back to the production lane ```task id: RPF-WP-0044-T07 status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e depends_on: [RPF-WP-0044-T03, RPF-WP-0044-T04, RPF-WP-0044-T05, RPF-WP-0044-T06] -blocking_reason: "Restore prune/selfHeal once T03-T06 have automated proof; no further founder go-ahead (D2)." +blocking_reason: "Founder go-ahead to restore automated prune/selfHeal on railiance-apps-root once T03-T06 are proven." priority: medium state_hub_task_id: "844ff115-def2-55ca-ab01-413ba7e6857d" ``` -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - **Waits on the founder's go-ahead.** After T03–T06 are proven, restore `automated: {prune: true, selfHeal: true}` on `railiance-apps-root`, as the change-gate decision's production row expects. From then on a merge to the @@ -402,15 +344,14 @@ railiance01 path is a live production change, and `RPF-WP-0043-T04` ```task id: RPF-WP-0044-T08 -status: done +status: wait +needs_human: true +decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e +blocking_reason: "Planning only; needs an operator read-only check of coulombcore's ArgoCD, outside agent session scope." priority: low state_hub_task_id: "55d1382f-5862-5321-a1c9-96767764ba43" ``` -2026-10-11: done by host-side read-only check (docs/evidence/2026-10-11-coulombcore-argocd-frozen.md). k3s certificates expired 2026-08-31, so the old ArgoCD cannot reconcile or prune. Founder: coulombcore is retired and the machine reused; retirement moves to NK-WP-0022-T08. Do not rotate certs or restart k3s there. - -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - Planning only. Needs a read-only check of coulombcore's ArgoCD, outside this session's scope. Under Option A, retiring it also removes `argocd/applications/`. Also hand back to the cluster layer: the phase A install is not declared in diff --git a/workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md b/workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md index 35f01e8..d8240fa 100644 --- a/workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md +++ b/workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md @@ -8,7 +8,7 @@ status: finished owner: railiance-platform topic_slug: railiance created: "2026-09-24" -updated: "2026-10-23" +updated: "2026-09-24" related: [RPF-WP-0025, RMASTER-WP-0020] state_hub_workstream_id: "6ab44339-66d3-522b-9d34-fc9ff6f20d1b" --- @@ -34,11 +34,7 @@ priority: high state_hub_task_id: "caa019c6-dfdb-5f17-b860-67e1feaebd51" ``` -Moved on 2026-10-23 to `rapp-openbao` at `manifests/bao-notice/` (commit -`a1f5851`). This Application still attaches the page. It has not been synced, -so the cluster keeps rendering the copy from `9ee20967` until a hand sync. - -`argocd/platform-addons/bao-notice/` held, until that move: +`argocd/platform-addons/bao-notice/` holds: - A namespace `bao-notice` with a NetworkPolicy: ingress only from Traefik (kube-system) on 8080, no egress at all, so there is no path to OpenBao. diff --git a/workplans/RPF-WP-0048-activity-core-gitops-adoption.md b/workplans/RPF-WP-0048-activity-core-gitops-adoption.md index c7ffd93..bf8e9c9 100644 --- a/workplans/RPF-WP-0048-activity-core-gitops-adoption.md +++ b/workplans/RPF-WP-0048-activity-core-gitops-adoption.md @@ -8,7 +8,7 @@ status: blocked owner: codex topic_slug: railiance created: "2026-09-27" -updated: "2026-10-11" +updated: "2026-10-03" state_hub_workstream_id: "b8cf2fc2-60c2-5d4e-a60a-55f1304d9f54" --- @@ -41,14 +41,14 @@ health, resource inventory, schedules and original deployment generations. ```task id: RPF-WP-0048-T02 status: wait +needs_human: true decision_id: 89f1058f-33bc-47a8-8a49-0b44d426c67e -depends_on: [ACTIVITY-WP-0041-T03] +depends_on: [ACTIVITY-WP-0041] +blocking_reason: "24h soak after adoption, then founder confirmation of the scoped identity and checks before automated sync; ACTIVITY-WP-0041 owns publication and the promotion guard." priority: high state_hub_task_id: "63b44949-39f8-52fd-ab63-e618b67ef992" ``` -2026-10-11: requalified after founder decision D2 (ArgoCD owns production) resolved — see the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md. - Keep automated sync off for at least 24 hours after successful adoption. Record start and earliest eligibility in evidence. ACTIVITY-WP-0041 owns image publication, source validation and the promotion/rollback guard. Root-wide automation is outside @@ -58,36 +58,6 @@ activating bounded routine promotion. Destructive pruning remains disabled. 2026-10-03: human gate routed to hub decision 89f1058f-33bc-47a8-8a49-0b44d426c67e (D2: ArgoCD owns production: go-ahead sequence for railiance-apps-root, issue-core, policy-nexus and activity-core automation) by CUST-WP-0075; resolving that decision raises C-40 here. -2026-10-11: qualified once as an **owner-proof wait**, not a founder gate. -`scripts/activity_promotion_readiness.py` now checks the exact live source pin -`a86a143`, local estate/cluster identity, narrow AppProject, nine tracked runtime -objects, manual/no-prune policy, successful Oct4 sync, elapsed 24h eligibility and -all three Deployment generations/readiness. Native read-only checks pass. Elapsed -time and healthy snapshots are explicitly not gap-sensitive continuous-observation -proof. The gate requires authenticated owner acceptance for the dedicated release -principal, custody/revocation/denials, independently trusted receipts, real -continuous sampling, source CAS/single-flight/restart, retention, Temporal/audit -sink/invariants and successful failed-health rollback before returning ready. -Nine fixture tests (including forged trust and substantive scope failures) pass. -No release identity or trusted signer is created by this gate. - -Owner evidence is still absent: `activity-core/docs/release-broker.md` explicitly -records its modules as not activated in production, and -`activity-core/docs/gitops-release.md` retains the actual release-identity and -rollback requirements. ACTIVITY-WP-0042's accepted scheduling boundary remains -scheduling-only; `docs/platform-recovery-scheduling-plan.md` does not admit a -release principal or new mutating recovery operation. D2 resolves the founder -question but does not supply those machine proofs. Therefore wait on the existing -ACTIVITY-WP-0041-T03 once; no new `needs_human` flag or per-item permission request. - -Cross-owner case and checkpoint: -`docs/evidence/2026-10-11-activity-promotion-readiness/coordination-case.json`. -Read-only receipt: the same directory's `readiness.json` and `validation.json`. -No worker was injected or foreign task changed. Next action: owner supplies -authenticated native acceptance and trusted issuer binding, then rerun the gate -before bounded dispatcher activation. No child source/Argo automation candidate -was changed, and this wait does not authorize direct automated sync. - ## Make registry retention aware of digest-pinned releases ```task diff --git a/workplans/RPF-WP-0050-attended-provisioning-preparation.md b/workplans/RPF-WP-0050-attended-provisioning-preparation.md index 1f5f345..930c6fb 100644 --- a/workplans/RPF-WP-0050-attended-provisioning-preparation.md +++ b/workplans/RPF-WP-0050-attended-provisioning-preparation.md @@ -4,11 +4,11 @@ type: workplan title: "Prepare generated attended windows and explicit value-custody choice" domain: financials repo: railiance-platform -status: finished +status: blocked owner: codex topic_slug: platform-services created: "2026-10-03" -updated: "2026-10-10" +updated: "2026-10-04" flavor: implementation state_hub_workstream_id: "0c7e3b0c-1c36-568c-af51-b06f912a0f1d" --- @@ -44,8 +44,10 @@ P3 options and required review boundaries are drafted in the contract. Existing ```task id: RPF-WP-0050-T03 -status: cancel +status: wait priority: medium +depends_on: [RPF-WP-0051-T02, RPF-WP-0035-T02, USER-WP-0035-T02] +blocking_reason: "Platform preparation and founder design choice are complete. Independent informed-decision runtime source/publication, KeyCape custody/registration/issuance, secrets-engine consumer custody coordinates and provider setup-link handoff remain; dependency-requirements.md names the exact owner deliveries." state_hub_task_id: "1ae5a6b8-9c83-5813-982f-ca8ad24be8b5" ``` @@ -89,35 +91,3 @@ P4 accepted for platform-side source review and bounded scaffolding after Warden 2026-10-04 T04 implementation: closed platform metadata schema and `credential-change.py scaffold-lane` authored with deterministic proposed CCR, existing exact-path policy/auth rendering, namespace-restricted draft ESO with annotation containment, and non-executed owner evidence checklist. Shared synthetic platform/Warden pointer fixtures retain unknown workload identity and unconfirmed auth claims. Fourteen focused offline tests pass in isolated prepared source; cross-owner staging fixture acceptance remains before marking T04 done. No admission, credentials, human answer or production execution. 2026-10-04 T04 acceptance: actual applied-source scaffold tests 14/14 and existing window tests 7/7 pass, with CLI help and diff checks. Both actual owner CLIs produced matching cross-owner synthetic fixture drafts; platform independently verified Warden catalog/model loading, exact lane/owner/consumer/risk/CCR pointers and fixture digest, preserved unknown workload, unconfirmed prospective auth, proposed/unresolvable frontdoor, explicit ESO target annotations and non-executed evidence. Six platform artifacts match the independently generated second platform stage byte-for-byte. Receipt: `docs/evidence/2026-10-04-catalog-lane-scaffold-offline.json`. T04 is done for offline source/scaffold/fixture acceptance only. T02 remains the founder custody-choice wait; T03 remains the Custodian dated-window handoff. No admission, actual credentials, schedule or production execution was performed. - - -## Closure and consolidation — 2026-10-10 - -The bounded preparation is complete: checklist renderer (T01), founder choice -of batched attended custody (T02), and platform/Warden offline scaffold -acceptance (T04). The founder supplied the one-time 2026-10-04 10:00 Berlin -window and selected scope directly; CUST-WP-0076 records that cadence closure. -No recurring event or standing execution grant follows from it. - -T03 is cancelled as retired orchestration for that historical window, rather -than marked done: its packet was NOT READY and no eligible completed window -checklist is claimed. Under the founder's request to consolidate the open -security work, its actual owner acceptance obligations stay in their existing -live records: - -- RPF-WP-0051-T02/T03 now record source and native informed-decision requester - delivery acceptance; RPF-WP-0051 is finished, with issuer rotation handed to - RPF-IN-0004 and complete decision-product acceptance in INFD-WP-0007-T07 / - STATE-WP-0096-T03. -- RPF-WP-0035-T02 retains the service-JWT negative claims, unrelated-path - capability denies, bounded lifetime/revocation and consumer opt-in acceptance. - The positive login and custody proofs do not close those checks. -- USER-WP-0035-T02 retains the provider-supported setup-link delivery contract - and intended-recipient evidence. It is not a new platform credential lane. - -The dated packet and owner-return documents are historical preparation -evidence. They must not be used as current approval or replay authority. -No remaining acceptance requirement is removed, no residual demand is created -merely to duplicate those existing owners, and no native acceptance is inferred -from this preparation closure. A future attended run must regenerate and -revalidate its exact reviewed packet against the remaining owner records. diff --git a/workplans/RPF-WP-0053-coulomb-paired-backup-admission.md b/workplans/RPF-WP-0053-coulomb-paired-backup-admission.md deleted file mode 100644 index 22d62e0..0000000 --- a/workplans/RPF-WP-0053-coulomb-paired-backup-admission.md +++ /dev/null @@ -1,450 +0,0 @@ ---- -id: RPF-WP-0053 -type: workplan -title: "Admit Coulomb paired archives and independent email-ledger recovery" -domain: financials -repo: railiance-platform -status: active -owner: codex -topic_slug: coulomb-social -created: "2026-10-07" -updated: "2026-10-31" -state_hub_workstream_id: "cb55e474-c7a5-5c6f-bd72-ce65f853e3e3" ---- - -Consumer request from CSOC-WP-0008-T02. The app now has real paired -quiesced database/content capture and isolated restore evidence; local temporary -copies and database-only CNPG jobs do not provide its durable recovery contract. -This is a proposed platform admission record, not an owner acceptance or live -approval. Existing mechanism ownership stays with railiance-platform. - -Reviewable consumer implementation: coulomb-social/scripts/paired-backup.py, -scripts/snapshot-content-and-db.py and deploy/backup/*.service/*.timer. -Consumer runbook: coulomb-social/docs/release/paired-backup-operations-2026-10-07.md. -No credential was fetched, no timer enabled, no live upload/overwrite performed. - -## T01 — Review and accept the paired backup execution identity - -```task -id: RPF-WP-0053-T01 -status: done -priority: high -state_hub_task_id: "ceb58abf-77ef-5d75-a65b-fc67bfd9aa54" -``` - -Choose the platform execution owner: the prepared node systemd units or the -existing bounded activity-core execution lane. Review single-writer quiescence, -lock, termination/resume behavior, exact cluster UID, app digest and data scope. -Node systemd candidate uses root/k3s admin, guarded to coulomb-social and -coulomb_social_db; that authority is not a new credential grant and needs -explicit owner/founder admission before unattended operation. Prefer a scoped -platform identity where its execution contract supports coherent DB/PVC capture. -Do not mark the warden offsite workload identity resolved without a declaration. - -## T02 — Admit offsite prefix, retention and caller custody - -```task -id: RPF-WP-0053-T02 -status: progress -priority: high -intervention_note: "Publisher delivery, independent scoped API custody and protected app kit/last recovered point are native-verified. Retention/capacity and historical-copy disposition remain open; no repeat credential or schedule approval is needed." -state_hub_task_id: "b06f1a44-1a55-588c-9c6c-3c1e290544df" -``` - -2026-10-11: requalified — founder approved the prefix-scoped IAM credential and delivery path. If issuing it needs the founder's Scaleway console, add one exact command to the attended-window checklist. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md (Round 3). - -The original Nextcloud overwrite-slot proposal is withdrawn; its current workload -uploader is create-only and secondary. Reuse the platform primary contract: -s3://railiance-platform-pg-backup/platform-pg/application-archives/coulomb-social/, -Standard Multi-AZ nl-ams. Exact review package is -docs/credential-lane-designs/coulomb-paired-backup-primary.md. - -scripts/primary_application_archive.py implements conditional immutable multipart -completion, own unfinished-upload abort, version-pinned GET and ciphertext hash -verification. Independent retrieve needs only a retained public receipt, no -original archive/node. requirements/backup-primary.lock pins seven packages with -hashes in a dedicated backup-venv; SDK conditional-completion shape is verified. -Synthetic provider tests cover uncertain completion, replay without new upload, -corrupted bytes, changed versions, caller scope and node-loss retrieval. No live -provider rights or custody are inferred from these tests. - -CCR-2026-0012 identifies existing primary custody coordinates and its approved -rapp-postgres/databases delivery, not a new consumer. Its historical catalog id -backup-object-storage is unknown to the current local warden CLI. Owner must -reconcile the front door, choose/admit appropriately prefix-scoped IAM authority -and deliver AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY to root-owned mode 0600 -/etc/coulomb-social-backup/primary.env (parent mode 0700). Metadata-only node check found -that file absent. No Secret fetch/copy or namespace expansion occurred. -No bootstrap key or age identity belongs in the scheduled worker. - -Separate non-mutating application_archive_retention_plan.py proposes 14 daily/4 -weekly distinct generations and protects newest plus the exact last application- -recovered version/hash. A missing/changed proof refuses; foreign/unverified points -are ignored. This is a review, not a deletion executor or activated lifecycle. -Bind local/provider headroom, independently retained metadata receipts, protected -recovery and compatibility with the existing 30-day bucket lifecycle before -activation. No retained-object/version-delete rights belong in the publisher. - -## T03 — Activate and verify retrieval and application recovery - -```task -id: RPF-WP-0053-T03 -status: progress -priority: high -intervention_note: "The first coherent capture/publication and daily schedule are already approved. Complete capacity/retention preflight, independently retained exact-version retrieval and isolated application recovery before timer activation." -state_hub_task_id: "871c14da-2940-5be6-8ce8-953973422064" -``` - -2026-10-11: requalified — founder approved the schedule (daily 03:15 UTC, RPO 24 h, stale 26 h, RTO 60 min). Activation follows T02 under the standing change rule. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md (Round 3). - -Founder ADMINISTER @ realm:kubernetes/railiance01, activation=APPROVED, plus -explicit primary archive creation/version-read authority. Proposed UTC daily03:15 -(+up to 5min jitter), RPO target24h, hourly stale threshold 26h, provisional -RTO target60min for an admitted attended restore. These are proposal targets, -not measured promises. First capture/upload is bounded and app resumes before -encryption/upload. Retrieve using the independently retained public receipt, -compare encrypted hash, decrypt through recovery -escrow with consumer recover-paired-backup.py (strict three-file packet/hash and -capture/image binding), then run restore-paired-snapshot.py and compare -row/sequence/file hashes, actual rendering and denial. Record receipt paths, -counts/timing only; preserve the source recovery point on any failed check. -No production data restore is part of backup schedule activation. - -## T04 — Bind monitoring and recurring recovery custody - -```task -id: RPF-WP-0053-T04 -status: todo -priority: high -state_hub_task_id: "687fd36d-98a9-5c10-833e-b6bda51fa07b" -``` - -Connect non-secret freshness/provider receipts and failed units to the -established evidence owner (RPF-WP-0036-T03/T04), prove reception and failure -response; local journald failure alone is not monitored acceptance. Name the -recovery custodian and approve a monthly isolated drill with offsite readback. -Verify key recovery survives node/workstation loss. Record release-triggered -backup helper digest refresh and admitted generation/capacity/retention policy. -CSOC-WP-0008-T02 stays progress until operating evidence and service contract are accepted. - -## Implementation evidence — 2026-10-07 - -New primary/retention 30 cases and existing backup tier/archive lifecycle 23 cases -pass (53 total); no live provider/custody interaction. Consumer full make check -passes 150 tests/mandatory browser journeys and Ruff; real synthetic age recovery -roundtrip passes and all four systemd units verify. T01 is progress for the -prepared execution package; owner identity acceptance remains open. T02/T03 remain -wait for custody/admission and exact activation; no timer installed/enabled and -no productive ciphertext uploaded. Email-connect's separate transport profile -is not ledger capture/recovery coverage; CSOC-WP-0010-T04/T05 own that residual. - -## T05 — Admit the independent email-connect ledger archive caller - -```task -id: RPF-WP-0053-T05 -status: progress -priority: high -needs_human: true -intervention_note: "email-connect primary.env is delivered from the accepted reader. Retention, capacity, independent receipts, escrow and the first capture remain open. Coulomb node delivery was not requested." -state_hub_task_id: "3f744ce6-28e4-5496-8d21-7231271a7bc8" -``` - -Consumer EMAIL-WP-0007 implements default-preview quiesced SQLite backup, stable -age sealing, verified primary publication and attended isolated recovery. Its -email-connect profile is separate from Coulomb/Barman: -platform-pg/application-archives/email-connect/. Review packet: -email-connect/docs/ledger-backup-review-2026-10-08.md. No SMTP or ingest credential -belongs to the backup worker. Candidate delivery: root-owned mode 0600 -/etc/email-connect-backup/primary.env (parent mode 0700), only owner-delivered -AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY. Metadata-only node check: file absent. -Do not copy the databases backup Secret or Coulomb caller, widen ESO admission, -or give publisher retained-object/version-delete or age escrow authority. - -Bind appropriately prefix-scoped provider authority and current custody front -door; existing CCR-2026-0012 is a coordinate, not this consumer's grant. Use the -reviewed hash-locked platform backup runtime. Admit independent receipt/key/image -recovery and ledger growth/headroom, local/provider generation retention and -compatibility with existing 30-day lifecycle. Provisional daily 04:15 UTC, RPO 24h, -stale 26h, RTO 60min need owner acceptance; they are not measured promises. - -The SQLite snapshot preserves state only at capture. A later provider submission -or challenge consume/cancel can be lost by restoring an older point. Production -incident recovery must fence caller/outbox/challenge traffic and reconcile the -post-capture window with user-engine/identity/provider owners before release; -never auto-enable a recovered receiver or resend an unknown outcome. Exact -first pause/upload/schedule, actual offsite/recovery proof and failure reception -remain EMAIL-WP-0007-T04/CSOC-WP-0010-T04/T05 obligations. Current receiver image -and SMTP/sender/caller admission remain unchanged by source preparation. - - -## T06 — Prepare atomic pair custody and its admitted front door - -```task -id: RPF-WP-0053-T06 -status: done -priority: high -state_hub_task_id: "ce950b3c-1ca6-5b01-bf2a-d958cafaed25" -``` - -2026-10-08 delivery preparation exposed a concrete custody gap: Warden's generic -paste-once desk performs a single-field KV PUT, replacing the whole record. Two -runs at one path discard the first field. Prepare a platform-owned pair-aware -custody procedure and reviewed writer identity/front door for the two new records, -initial CAS=0, both AWS fields in one atomic write, no secret argv/output/files, -bounded auth/self-revocation and metadata-only KV-version evidence. Do not use -the read-only delivery role, the structure-only administration lane, a generic -agent read grant, a retired ops-mason builder grant or two single-field desk PUTs. -Design/implementation can progress independently; actual provider key provisioning -needs its exact owner-reviewed attended custody act. This is a live handoff from -WARDEN-WP-0043 and the credential-delivery preparation, not a hidden prose residual. - -## 2026-10-08 — Credential delivery preparation - -Draft CCR-2026-0036 and CCR-2026-0037 now bind separate application KV paths, -five-minute/four-use native readers, exact namespaces/ServiceAccount/audience and -root-owned private delivery files. Both remain draft/unconfirmed, with no reviews -or native admission evidence. Their read policies match the existing generator; -validator errors and delegated-applier scope violations are empty, with expected -unconfirmed-binding warnings. Proposed ServiceAccounts disable automount and have -no new RBAC grants. No existing credential, Secret or cluster object was read or -changed. - -scripts/application_backup_credentials.py previews without effects and performs -only admitted, source-hash/KV-version-pinned node reads; token revoked before atomic -no-overwrite private publication, readback equality, metadata-only receipts. It -refuses source/scope/version drift, dangerous files, injection, unconfirmed revoke -and a different existing file. A separate YAML-to-public-JSON exporter preserves -canonical source updates while keeping the node helper dependency-free. Thirty -synthetic delivery cases, four export cases and thirty primary/retention cases -pass; none establish live authorization. Review: -docs/credential-lane-designs/application-backup-delivery-2026-10-08.md. - -WARDEN-WP-0043 fixes administration/provisioning intent collisions with surveys; -installed Warden was rebuilt, phase-memory retained, current catalog hash -5e6b217aa1f6 verified. Surveys now point to ops-mason-survey per MASON-IN-0004. -Structure requests require attended OIDC; pair provisioning is only a generic -platform pointer until T06 supplies the correct custody contract. T01/T02/T03/T05 -remain open; no credentials delivered, productive capture/upload or timer enabled. - -## 2026-10-08 — Pair writer preview - -`scripts/application_backup_pair_custody.py` previews both draft CCRs. The -intended writer policy is `backup-pair-writer` and The founder admitted this writer on 2026-10-08, using the words -"bucket-pair-writer". The policy name remains `backup-pair-writer` -(`openbao/policies/backup-pair-writer.hcl`). The installed policy matches -that file, and `WRITER_ADMITTED` is true. Both backup-primary KV paths were absent. Version 2 is current on each path. Both pairs replaced the placeholder and -differ from each other. The values were not read out. The bucket policy now keeps those three statements and adds the coulomb-social -and email-connect publisher statements. Each new key aborted its own multipart -upload and was denied the sibling prefix, the apps-pg prefix, and object -delete. No completed object was stored. The bootstrap key cannot list -IAM applications (403). The live bucket -policy still names only application ee80333e-5bab-4a04-85a9-5bdb46b7b3c1 on -`platform-pg/*`. No publisher key was created. T05 stays wait for the two -console-created applications and their CAS=0 pairs. An admitted writer, covered -only by a synthetic transport, must present that one policy, TTL at most 5 -minutes and at most 2 uses, prove metadata absence, send both AWS fields in one -`cas: 0` body, and revoke. Receipts contain no values. The delivery reader and -a one-field desk remain refused. No IAM application, provider key, OpenBao -version or node file was created. Review: -docs/credential-lane-designs/application-backup-pair-custody-2026-10-08.md. -T05 stays wait for owner admission of the writer, prefix and escrow. T06 stays -progress until that attended act. - -## 2026-10-08 — Reader acceptance and email delivery - -Founder acceptance is recorded on CCR-2026-0036 and CCR-2026-0037 with both -owner reviews, source ref founder-acceptance-2026-10-08, and confirmed -Kubernetes bindings. Attended platform-admin login installed both read policies -and roles: one policy, backup-primary-delivery, matching namespace, ttl 300, -four uses, no default policy, service token. Both ServiceAccounts exist with -automount disabled. Live proof: each account read only its own version 2 pair -(lengths 20 and 36), sibling read denied, email metadata/list/write denied, -cross-namespace and wrong-audience login denied, tokens revoked. Email delivery -admission is true. Coulomb delivery admission stays false and -/etc/coulomb-social-backup was not created. email-connect primary.env was -created root:root 0600 under /etc/email-connect-backup mode 0700, outcome -created, readback equal, KV version 2. No capture, upload, escrow or timer. - - -## Coulomb-only node admission and delivery — 2026-10-27 - -User explicitly approved the Coulomb node executor and its existing governed -pair delivery: decision 9557e2b8-2b37-5b94-a5e6-e5fe08d0351e. T01 is done. -CCR-2026-0036 now records that approval and native provider/reader proofs. -Read version 2 under the exact five-minute/four-use reader, revoked tokens, -proved sibling data/metadata/list/admin/CAS0-write denial plus wrong-SA/audience -login denial. Provider own-prefix multipart creation/abort succeeds; sibling and -native prefixes, delete, bucket-policy and IAM listing deny 403. No completed -object or retained-object deletion occurred. Credentials were never printed. - -The exact source-hash/KV-version-pinned delivery helper created root:root 0600 -/etc/coulomb-social-backup/primary.env under 0700, equal readback, token revoked -before publication. Evidence: docs/evidence/2026-10-27-coulomb-backup-admission.json. -T02 remains progress for independent key/receipt/image recovery custody, -capacity/lifecycle/retention. First capture/application recovery, monitored -schedule and temporary app-pair copy disposition remain T03/T04. Earlier -unadmitted/absent statements above are historical; no timer was enabled here. - -## Coulomb independent recovery custody approved — 2026-10-28 - -User approved the additional Coulomb-only recovery custody in decision -27bbc1ac-e275-42be-ba34-6cf16eae4e78. Consumer review packet: -coulomb-social/docs/release/recovery-custody-review-2026-10-28.md at source -17d0eb67f397e3d1f5b9b63880df5054aa263c5a. Platform-root is the proposed -attended recovery/failure owner; establish a dedicated Coulomb age identity, -separate read-only recovery caller and protected off-node/workstation kit/last -recovered point outside rolling expiry. Reconcile lifecycle/headroom and native -positive/negative proof before acceptance. The existing publisher/executor and -03:15 UTC schedule approvals persist. No sibling private identity/reader reuse, -retained-object deletion, production restore or premature timer activation. - -Current execution preflight: no SCW operator environment or standard local -operator config and no live local browser-debug session. Warden route -scaleway-bootstrap remains draft/unresolvable and explicitly has no owner-fronted -execution; do not fetch its broad bootstrap key or substitute publisher/sibling -credentials. Existing email recovery proves the isolated-project mechanism, not -a grant for Coulomb. Resource-specific grants and independent kit custody still -need native execution through an attended platform operator front door. Approval -is recorded; implementation/verification remain T02/T03/T04. - -## One-time provider bootstrap, routine programmatic access — 2026-10-28 - -User instruction recorded as decision6139e02b-3fb7-4eff-be8a-efe127c45ce5: -the offered attended Scaleway Console login is bootstrap only; regular later -access must use scoped APIs. Provision governed publisher, independent archive -reader and custodian escrow API access within the already approved Coulomb -recovery custody scope. Their credentials/kit must survive primary-node and -workstation loss. Personal Console session state is never a scheduled worker or -regular recovery/drill credential. Retire the temporary session after native API -verification. No standing organization-wide operator key or publisher IAM admin -right follows from this instruction. First aborted attempt saved no state; fresh -attended browser is waiting for user confirmation, with no provider mutation. - -## Prepared deterministic API identity bootstrap — 2026-10-28 - -The first saved Console session could read the exact publisher metadata but -expired before provisioning; API access now401 and renewal400. No project, policy -or API key was created. The consumer records responsibility for the delay and -will prepare the complete bootstrap before another login. - -New scripts/application_recovery_identities.py previews without effects and -executes only through temporary auth on stdin. It binds the organization to the -exact existing Coulomb publisher, creates an isolated recovery project, three -separate applications (temporary construction, read-only archive reader, -custodian), and four project-only policies including the publisher's mirror -policy. No IAM/org-wide grants or replacement of the existing publisher pair/ -primary-bucket policy. The construction key has verified expiry at most two hours. -The custodian gets explicit SecretManagerSecretAccess plus metadata read and -limited object publication/read; archive reader has no secret access. Native -bucket policy/escrow denials and protected custody remain mandatory after this -identity stage; no archive, timer or operating acceptance follows automatically. - -Atomic owner-private output outside Git, fsync, effective-rule readback, explicit -uncertain-issuance refusal, and own newly issued key revocation on persistence -failure prevent silent duplicate/relabelled credentials. Retirement deletes only -the exact verified construction key and its private temporary pair, retaining -routine pairs and all backup material. Twelve synthetic cases pass; targeted -platform Ruff and Node syntax checks pass. Native provisioning remains unperformed. - -The committed bootstrap_coulomb_recovery_session.mjs validates source/private -output/locked Playwright before opening a browser, then executes the factory -immediately after confirmation, reading current in-memory auth. It keeps the -browser open for reviewed correction and API resource/boundary checks, then -retires construction authority and logs out on finish. It saves no personal -browser state. Run --preflight-only first; routine access uses the scoped API -pairs, never that personal session. The expired earlier capture is not a -credential lane. Existing independent recovery and service gates stay open. - -## Native scoped API custody established — 2026-10-29 - -The attended bootstrap created isolated project -06d107bc-9d84-4672-b2a0-95701cc0c07f, three dedicated applications and four -verified project-only policies. No organization-wide grant or publisher-pair -replacement. The two-hour construction key expired during an approval pause; -the refreshed existing browser revoked that exact expired key and issued only -its bounded replacement. Reader/custodian pairs were preserved. - -Two private versioned buckets now have owner-preserving allowlists. No lifecycle -expiry is enabled. Four protected nonexpiring secrets contain a synthetic access -canary, the dedicated age identity, reader pair and custodian pair. Native -custodian API readback equals all three real values; publisher/reader canary -access denies403. Thirty native checks include exact-version/hash/list reads, -publisher create/readback/own multipart abort, denied retained-version deletion, -policy administration, outside-prefix reads, protected-kit access by publisher, -reader writes and IAM administration. Only newly created synthetic objects were -used as destructive denial targets. No production capture, restore or timer. - -The construction key was revoked with native GET404 verification and its own -temporary pair removed. Personal context closed; no browser state saved. Routine -scripts/application_recovery_access.py independently retrieves/validates age and -both API pairs without personal login. Public contract and recovery procedure: -docs/credential-lane-designs/coulomb-recovery-api.{json,md}; receipt: -docs/evidence/2026-10-29-coulomb-recovery-api-bootstrap.json. Twenty-six synthetic -tests and targeted Ruff pass. Future routine keys expire2027-10-28; platform-root -owns renewal/read-back escrow before those recorded deadlines. - -Native corrections were narrow: respect the provider's ten-statement policy -limit, compare every policy field independent of array order, and isolate each -negative PUT transport from positive reads. No permission was broadened to make -a denial pass. T02 remains progress for full kit/last recovered app point, -retention/capacity and historical-copy disposition; T03/T04 remain open for first -application recovery and monitored scheduling. Earlier unperformed statements -above describe their dated preparation stage. - -Consumer delivered only the verified public recipient to the admitted worker's -root-owned0600 recipient.env with cluster UID/installed guard hash/readback checks. -Private age identity and recovery pairs remain outside the worker. Consumer -receipt: docs/release/backup-public-recipient-delivery-2026-10-29.json. - -Publication preflight classified the receipt's access-direction decision UUID -as generic-api-key because of its metadata field name. The exact finding was -line2, containing only the already public approval reference. Renamed that field -to programmatic_direction_reference; no scanner rule, ignore or bypass changed. -Only unpublished commits are amended; published history is preserved. - -## First paired and protected provider-only recovery — 2026-10-29 - -Admitted worker capture resumed the app before encryption/upload;54.3s pause/capture, -2,120,392byte ciphertext, immutable primary exact-version/hash readback. The new -application_recovery_mirror.py reuses the existing multipart/conditional-completion -engine through an exact Coulomb-only bucket/key adapter. Its independent reader -fetched the mirror; custodian escrow supplied only the dedicated age identity. -SQL restore matches13tables/112rows/sequences and82files. Actual packaged app views -render39pages; restored owner, viewer read/edit403 and ungranted404 checks pass. - -application_recovery_kit.py publishes only new hash-addressed kits and checks live -versioning/no lifecycle before publication. The full277,585,920byte kit includes -encrypted data, exact images/tools/SDK/receipts and no private identity/API pair. -application_recovery_drill.py independently downloads/verifies all2098members, -retrieves the separate key, loads the retained images without registry pulls, -decrypts/restores and repeats all actual app checks in30.232s monotonic. Original -node/plaintext not used. Existing Docker daemon and held API callers were used; -operator account recovery time/fresh-daemon proof and real-browser identity -journeys are excluded. No production restore/expiry/timer activation. - -The successful proof is itself hash-addressed/conditionally created under the -protected kits/receipts/ prefix and independently version/hash read back. The -API contract records that proof coordinate as well as the kit and recovered -primary point. Later automated drills retain the same independent proof. - -Exact protected point and kit coordinates are in the public API contract; receipt: -docs/evidence/2026-10-29-coulomb-provider-kit-recovery.json. Consumer CSOC-WP-0012-T02 -is done. Owner T02 retention/capacity/copy disposition and T03/T04 scheduled and -monitored operation remain open. Preserve isolated recovery fixtures for the -pending actual-data storage-migration rehearsal; do not treat their copies as -another independent offsite location or delete historical plaintext pairs. - -2026-10-31 corrected-date protected recovery refresh: the 07:13:52Z point is now -independently retained in kit4166eb8b/version1793432303941465 (276,019,200bytes) -and recovered using only the provider kit plus separate age escrow. All2098members, -13tables/114rows/sequences,86files and40owner renders/40outsider denials pass in -46.692s. The matching captured image is51281946; this point predates the revision19 -image update but includes the verified source-date correction. Current image522bcd7f -has separate same-store export/view proof. The successful recovery proof is itself -protected and independently read back at version1793432409572654. The fresh drill's -temporary identity file was removed; pre-existing custody and historical copies -remain untouched. No production restore, new grants, expiry or timer activation. -Public API coordinates now identify this verified point; the former pointer still -named October29 and was stale. Historical kits/proofs remain retained. Receipt: -docs/evidence/2026-10-31-coulomb-provider-kit-post-date-recovery.json. Existing -retention/capacity/monitoring/copy-disposition obligations remain open. diff --git a/workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md b/workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md deleted file mode 100644 index 3a207ee..0000000 --- a/workplans/RPF-WP-0054-keycape-client-secret-generate-lane.md +++ /dev/null @@ -1,347 +0,0 @@ ---- -id: RPF-WP-0054 -type: workplan -title: "Broker lane keycape-client-secret-generate: generate KeyCape client secrets in custody" -domain: financials -repo: railiance-platform -status: finished -owner: claude-code -created: "2026-10-09" -updated: "2026-10-10" -state_hub_workstream_id: "73694adc-12a0-5226-a814-ee096d158f60" ---- - -Demand: net-kingdom SecurityCaseType `oidc-browser-relying-party` 1.0 -(`net-kingdom/security-case-types/oidc-browser-relying-party/type.yaml`), -follow-through step `mint-secret`: owner railiance-platform, lane -`keycape-client-secret-generate`, `warden_executes: true`, `attended: false`, -`value_handling: generated_in_custody`. It must "generate a client secret inside -custody at `bao:platform/workloads/keycape/{client_id}` using the caller's -broker grant for the case's KV path, and never return the value". Requests: -ops-mason `789feb39` (2026-10-08) and ops-warden `d9f889b1` (2026-10-09); this -gates WARDEN-WP-0044-T02 and the Forgejo reference case NK-WP-0048-T06. - -Today the broker (`scripts/credential.py`: request, exec, status, revoke) only -leases credentials, and `credential-change.py` only applies or moves material -the caller provides. Nothing generates a value. ops-warden needs a command, a -grant shape and a verification that does not read the value. - -**Meaning of "in custody".** OpenBao KV cannot generate values, and -`sys/tools/random` returns bytes to its caller. The value is therefore generated -in the broker process (`secrets` module) and goes only to that process's memory -and the `bao` child's stdin, the same rule as the attended seed/move helpers. It -never appears in argv, stdout/stderr, files, receipts, State Hub or the caller's -environment. The caller receives a receipt holding the path, version, timestamps -and an outcome code. - -**Scope.** Only the first generation for a new client (KV version 1, CAS 0). -Rotation needs `update` on an existing entry plus coordination with the KeyCape -restart and ESO refresh. The case type marks it `automatable false until a run -has been proven`, so it is a separate, later task (T07), not part of this lane. -Delivery (`deliver-secret`, ESO) and the per-case read policies remain the -case's `client-secret-path` artifacts and are unchanged here. - -## T01 — Confirm the value and caller contract with key-cape and ops-warden - -```task -id: RPF-WP-0054-T01 -status: done -priority: high -state_hub_task_id: "38f92db0-317e-5b2b-8bbd-b4af3272cc21" -``` - -Confirm with key-cape: client_id pattern (proposed `^[a-z0-9][a-z0-9-]{2,62}$`), -field name (`CLIENT_SECRET`, single field), secret length and alphabet (proposed -48 random bytes, URL-safe base64 without padding, 64 characters), and that -KeyCape reads it only through the ESO-delivered Secret. Confirm with ops-warden -which identity calls the broker under its caller proxy, and how the accepted -case reference (case id + acceptance record digest) is passed. That reference -goes into the receipt and into OpenBao `custom_metadata` (non-secret). - -**Done when:** both replies are recorded here, with no open question about -format, field or caller. - -2026-10-09 ops-warden (`23985370`): the caller is the human operator who runs -`warden case run`. Warden runs `credential.py generate` as a child process in -the caller's environment and holds no token or grant, so the caller's own OIDC -session is the acting identity. Warden refuses the step under `WARDEN_AGENT_ID`. -The case reference is `@sha256:` over the case file's exact -bytes (`warden case plan` prints it). The platform must also take `--case-file`, -re-derive the digest, and check that status is `materialized` and that -`parameters.client_id` equals `--client-id`. The client_id pattern is taken from -the case type, `^[a-z0-9][a-z0-9-]{1,62}$` (authoritative; it replaces the -proposal above). Still open: key-cape confirmation of length and alphabet, -asked 2026-10-09. - -2026-10-10 key-cape (`4e0213b3`): format confirmed as proposed, 48 random bytes -as URL-safe base64 without padding (64 characters) in the single field -`CLIENT_SECRET`, read only through ESO Secret `sso/keycape-{client_id}-client`. -KeyCape compares secrets as opaque strings in constant time and imposes no -length or alphabet limit. It requires only a non-empty clientId; the pattern -belongs to the case type. T01 is closed. - -## T02 — Decide the grant shape - -```task -id: RPF-WP-0054-T02 -status: done -priority: high -state_hub_task_id: "4183abc4-4acc-5937-9f27-005feb181025" -``` - -Two options, both create-only (ACL `create` without `update`, so an existing -client's secret can never be overwritten through this lane): - -- **A — one templated grant (recommended).** Grant `keycape/client-secret-generate` - holds policy `keycape-client-secret-generate`: `create` on - `platform/data/workloads/keycape/+`, `read` on - `platform/metadata/workloads/keycape/+` (absence proof and version - verification, no value), and explicit `deny` on existing reserved entries - (today `keycape/secrets-engine-openbao`). The broker additionally refuses a - client_id with no accepted case reference and refuses if metadata already - exists. Each case needs no new policy or CCR; the reach is "create any new - KeyCape client path", and the caller cannot read values. -- **B — one exact grant per case.** Each case's `client-secret-path` artifact - adds a create-only stanza for its own path, applied through a CCR before - mint-secret. This keeps the tightest ACL, but every case needs an attended - policy apply, which conflicts with `attended: false` for this step. - -Recommendation: A. Its widest misuse is creating an unused entry for a client -that does not exist. That cannot leak or overwrite a value, and metadata audit -plus the case-reference check make it visible. - -**Done when:** the founder records the choice here (and on a State Hub decision -if one is opened). - -**Decision 2026-10-09 (founder): option A**, one templated create-only grant -`keycape/client-secret-generate` with the broker-side case-ref and -absence checks above. T04 implements it; per-case exact grants (B) are not -built. - -## T03 — Implement the broker generate operation - -```task -id: RPF-WP-0054-T03 -status: done -priority: high -depends_on: [RPF-WP-0054-T01, RPF-WP-0054-T02] -state_hub_task_id: "3e159e92-f5ee-5a92-b887-e06daf7dfbdb" -``` - -Add `credential.py generate --grant keycape/client-secret-generate --client-id - --case-ref [--receipt ] [--dry-run]`: - -1. Validate the grant, the client_id pattern, the reserved-entry denylist and the - case-ref shape. Refuse wildcard, parent or extra segments. -2. Obtain the grant's short-lived token through the existing lease path. The - caller's identity is used, and the token is never printed. -3. Prove absence: metadata read returns 404. Anything else refuses (exit 3 if - present). -4. Generate in process and write - `{"options":{"cas":0},"data":{"CLIENT_SECRET":...}}` through stdin. -5. Verify by metadata only: current_version 1, not deleted. Never read the data - path. The case-ref goes in the receipt and the hub metadata, not in OpenBao - `custom_metadata`: writing that needs metadata `update`, which would also let - the token change `delete_version_after`/`max_versions` of other clients' - entries (decided while doing T04, 2026-10-09). -6. Revoke the lease. Write the receipt (0600, O_EXCL, versions, booleans and - outcome only). stdout/stderr stay silent apart from the fixed outcome code. - -Tests with a fake `bao`: success, already-present refusal, CAS-0 race loser, -reserved path, bad client_id, missing case-ref, write failure, verify mismatch, -silence contract, and an assertion that no data-path read occurs. No secret -text appears in any receipt. - -2026-10-09 progress: `scripts/keycape_client_secret_generate.py`, reached as -`credential.py generate --grant keycape/client-secret-generate --client-id ---case-file --case-ref [--receipt f] [--dry-run] -[--record-state-hub]`. It refuses agents, root, and callers without the issuer -policy. It verifies the case file as ops-warden specified, mints the 5m grant -token, and makes every KV call with that token only. It proves absence, writes -once with CAS 0, verifies by metadata (version 1), revokes the token, and prints -nothing. There are 8 offline tests. Remaining before done: key-cape's format -confirmation (`SECRET_BYTES` is the proposed 48 bytes, 64 URL-safe characters). -2026-10-10: key-cape confirmed the format; `SECRET_BYTES` stays as is. T03 is done. -The live probe passed under T05. The first real run (Forgejo) needs the case -`net-kingdom/security-cases/forgejo-netkingdom-oidc.yaml` to reach `materialized` -(today `accepted`; ops-mason's step). - -## T04 — Grant, policy and token role source plus CCR - -```task -id: RPF-WP-0054-T04 -status: done -priority: high -depends_on: [RPF-WP-0054-T02] -state_hub_task_id: "74eb1309-df16-5163-9840-ffb88072d09e" -``` - -Add the grant to `credential-grants/catalog.yaml` (class per T02, TTL 5m, exec -delivery only, no response-wrap). Add the policy under `openbao/policies/`, the -issuing token role, and a CCR for the attended apply. Agent boundary: -coding-agent identities keep `deny` on the keycape data paths. Check whether -the existing `agent-high-risk-boundary` lane entries need a `keycape/+` deny -and run `make agent-high-risk-boundary-check`. `make credential-grants-validate` -and the token-grant dry-runs must pass. - -2026-10-09 done: CCR-2026-0040 (request type `custody-generate-grant`, -`scripts/broker_grant_lane.py`) writes the grant policy -`keycape-client-secret-generate` (create on `keycape/+` data, read on `keycape/+` -metadata, deny on `keycape/secrets-engine-openbao`, revoke-self), the issuer -policy `credential-broker-keycape-client-secret-generate-issuer` and the token -role (5m, orphan, non-renewable, no default policy), then reads all three back. -The catalog grant is `pilot`/`approval-required`, exec-env only. The apply binds -the issuer policy to no identity, so it is inert until T01 names the caller. -Agent boundary: no coding-agent policy grants any `keycape/` path, and the -create-only grant is never attached to an agent identity. No new boundary deny -is needed. - -## T05 — Attended apply and disposable probe - -```task -id: RPF-WP-0054-T05 -status: done -priority: high -depends_on: [RPF-WP-0054-T03, RPF-WP-0054-T04] -state_hub_task_id: "a21d1915-fee9-5c31-89e7-a4560c645950" -``` - -The CCR apply can run any time; it is inert until the issuer binding exists: - -```bash -cd ~/railiance-platform -export BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 -python3 scripts/credential-change.py approve CCR-2026-0040 --reviewer "platform-owner, key-cape-owner (founder Bernd Worsch)" --comment "Approve create-only broker grant keycape/client-secret-generate (RPF-WP-0054-T02 option A)." \ - && warden access openbao-platform-admin-login --exec -- python3 scripts/credential-change.py applier-apply CCR-2026-0040 --confirm "DELEGATED APPLY CCR-2026-0040" --actor founder --record-state-hub --quiet -``` - -2026-10-09: CCR-2026-0040 applied by the founder at 17:19Z; readback matched for -both policies and the token role. The run then printed a traceback from the -State Hub evidence step (KeyError on `openbao.auth` for a prefix lane), so warden -revoked the session. That was fixed and the hub event was posted afterwards. The -OpenBao objects were complete before the crash. - -2026-10-10: the founder chose `net-kingdom-admins` + `aal2` for the caller. -CCR-2026-0043 (request type `broker-issuer-binding`, -`scripts/issuer_binding_lane.py`) adds OIDC role -`credential-broker-keycape-client-secret-generate` on `auth/netkingdom`. It -carries only the issuer policy and has loopback callbacks, a 15m TTL, 8 uses and -no default policy. The apply refuses unless the live issuer policy matches source. - -Founder session (probe uses a synthetic, founder-written case file; the real -Forgejo run waits for key-cape's format confirmation): - -```bash -cd ~/railiance-platform -export BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 -# 1. binding -python3 scripts/credential-change.py approve CCR-2026-0043 --reviewer "platform-owner, key-cape-owner (founder Bernd Worsch)" --comment "Approve issuer binding for keycape-client-secret-generate: net-kingdom-admins + aal2 (RPF-WP-0054)." \ - && python3 scripts/credential-change.py confirm-binding CCR-2026-0043 --reviewer founder --comment "groups=[net-kingdom-admins] and /assurance/level=aal2, same binding as CCR-2026-0034." \ - && warden access openbao-platform-admin-login --exec -- python3 scripts/credential-change.py applier-apply CCR-2026-0043 --confirm "DELEGATED APPLY CCR-2026-0043" --actor founder --record-state-hub --quiet -# 2. probe case (synthetic, materialized, never registered in KeyCape) -P=probe-secret-generate-20261010 -cat > ~/$P-case.yaml </dev/null 2>&1" -``` - -2026-10-10 first probe: CCR-2026-0043 applied cleanly and the OIDC login -succeeded, but all three `generate` runs exited 4 (`grant_token_create_failed`) -and the operator's `revoke-self` returned 403. Nothing was written. Causes: (1) -the role set `token_num_uses: 8`, and OpenBao refuses child-token creation from a -limited-use token. The role now uses 0, which the lane validator enforces. (2) -The issuer policy lacked `auth/token/revoke-self`; it is now added. Re-apply -CCR-2026-0040 (issuer policy) before CCR-2026-0043 (role), because the binding -apply refuses unless the live issuer policy matches source. - -2026-10-10 second probe passed after re-applying both CCRs (09:15Z). The -caller token held only the issuer policy with num_uses 0. Results: dry-run -`dry_run_would_generate`; generate `generated`, kv_version 1; second run exit 3 -`already_present`. The grant token was revoked every time, operator -`revoke-self` succeeded, and no value was read. The probe entry was then -metadata-deleted. Receipts: `docs/evidence/2026-10-10-keycape-client-secret-generate-probe.json`. - -The policy denials (data read, update, delete, other paths) were already proven -by the CCR-2026-0040 readback and its policy tests. The probe proves the live -create-once behaviour. - -Record the receipts under `docs/evidence/`. - -## T06 — Hand the lane to ops-warden and net-kingdom - -```task -id: RPF-WP-0054-T06 -status: done -priority: medium -depends_on: [RPF-WP-0054-T05] -state_hub_task_id: "d7015584-3429-5f32-b4bc-0a9b64017c15" -``` - -Reply to ops-warden `d9f889b1` and ops-mason `789feb39` with the command, the -grant shape and the verification (metadata version plus capabilities, no value -read). ops-warden catalogs the lane (pointer-only entry); net-kingdom can then -plan NK-WP-0048-T06. Consider declaring the `c3.runtime-secrets` capability so -the case type can compose mint-secret instead of treating it as a follow-through -step (net-kingdom case README "Known limits"). - -2026-10-10: ops-warden catalogued the lane as draft (ops-warden 0ae8a0e, -playbook `wiki/playbooks/keycape-client-secret-generate.md`) and activates it -when platform confirms. Probe receipt sent. Activation is held until key-cape -confirms the secret format (T01/T03). - -2026-10-10: ops-warden activated the lane (ops-warden 6414252). The playbook -leads with the issuer-role login, and `warden case check-type` reports -oidc-browser-relying-party 1.0 as plannable. WARDEN-WP-0044-T03 now waits only -on the Forgejo case reaching `materialized`. The `c3.runtime-secrets` -capability is left for a later declaration; mint-secret works as a -follow-through step. T06 is done. - -## T07 — Rotation lane (separate, later) - -```task -id: RPF-WP-0054-T07 -status: cancel -priority: low -depends_on: [RPF-WP-0054-T05] -state_hub_task_id: "d5f0beb6-54dc-502b-9c13-0cb6892ab87d" -``` - -Design `keycape-client-secret-rotate`: `update` with CAS = current version, an -attended window coordinated with ESO refresh and the KeyCape restart, and -rollback to the previous version. Only after one proven first-generation run. -Not part of the mint-secret gate. - - -## Closure — 2026-10-10 - -The first-generation lane is complete: T01–T06 record owner contract acceptance, -source implementation, CCR-2026-0040/0043 attended apply, the disposable CAS-0 -probe and self-revocation/cleanup receipts, and ops-warden activation at 6414252. -The live proof is `docs/evidence/2026-10-10-keycape-client-secret-generate-probe.json`. -This closes the lane itself; it does not claim the Forgejo reference case is live -or replace its independent MASON-WP-0010 / WARDEN-WP-0044 acceptance. - -T07 was explicitly outside this lane's scope (separate, later rotation). It is -cancelled here as transferred, not implemented. The actionable follow-up is -RPF-WP-0055, `flavor: residual`, `origin_ref: RPF-WP-0054`; no demand or risk -promotion is claimed. Rotation requires its own reviewed CAS/update authority, -ESO and KeyCape coordination, verification and rollback. diff --git a/workplans/RPF-WP-0055-keycape-client-secret-rotation-residual.md b/workplans/RPF-WP-0055-keycape-client-secret-rotation-residual.md deleted file mode 100644 index 3ccb603..0000000 --- a/workplans/RPF-WP-0055-keycape-client-secret-rotation-residual.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -id: RPF-WP-0055 -type: workplan -title: "KeyCape client-secret rotation after first-generation lane acceptance" -domain: financials -repo: railiance-platform -status: backlog -owner: codex -created: "2026-10-10" -updated: "2026-10-10" -flavor: residual -origin: residual -origin_ref: RPF-WP-0054 -depends_on: [] -related_repos: [key-cape, net-kingdom, ops-warden] -state_hub_workstream_id: "5517b448-3721-55d8-b80d-d61e5e137089" ---- - -Transferred from RPF-WP-0054-T07 before closing its first-generation lane. -This is a separately scoped later capability, not a blocker of CAS-0 generation -and not promoted demand. Do not implement unless demand or risk promotes it. - -## Design, admit and verify a coordinated rotation lane - -```task -id: RPF-WP-0055-T01 -status: todo -priority: low -state_hub_task_id: "4b9c94fb-1251-5f12-b227-1d9d999cd8ed" -``` - -After one proven first-generation case run, design -`keycape-client-secret-rotate`: exact-path `update` with CAS = current version, -an attended window coordinated with ESO refresh and the KeyCape restart, and -rollback to the previous version. Obtain the independent owner contracts and -exact-action authorization before live changes. Verify the replacement in the -consumer, scoped delivery, cleanup/revocation and rollback; publish only -non-secret receipts. The existing create-only grant must not be widened merely -to perform this residual. diff --git a/workplans/RPF-WP-0056-playwright-consumer-admission.md b/workplans/RPF-WP-0056-playwright-consumer-admission.md deleted file mode 100644 index 80fc213..0000000 --- a/workplans/RPF-WP-0056-playwright-consumer-admission.md +++ /dev/null @@ -1,203 +0,0 @@ ---- -id: RPF-WP-0056 -type: workplan -title: "Admit the Playwright pilot and establish scoped consumer group access" -domain: financials -repo: railiance-platform -status: blocked -owner: codex -topic_slug: railiance -created: "2026-10-13" -updated: "2026-10-14" -state_hub_workstream_id: "31933d08-c877-5fda-aff9-6220c3705c92" ---- - -Incoming owner request: State Hub c82d75f3-3191-410d-a8c8-fd647bf7f3ef, -superseding publication return a282c300-eaef-4cd6-99b0-f4fb255203b3; -consumer RAPP-PLAYWRIGHT-WP-0004-T01. The user selected establishing a group -in this session. Platform owns lifecycle admission; implementation remains in -rapp-playwright. Publication is complete, and must not remain a blocker. - -## Review the immutable package and pilot boundary - -```task -id: RPF-WP-0056-T01 -status: done -priority: high -state_hub_task_id: "648b7b85-2da1-56a6-8a6b-e2f9212095a8" -``` - -Reviewed rapp-playwright revision 70268154c06cf78c5d9e1f4eb900213ffd436d1b: -declaration, release, workload, admission and operations contract. The published -image is sha256:8d6fd5a8172cf906bb503642ed620761945c5138b86f6396c36f65cbe6def441. -Three provider contract/network/proxy tests pass independently in this session. -One Recreate replica requests 100m CPU/256Mi memory, limits 1 CPU/1Gi, and uses -a 2Gi transient artifact PVC with one-hour retention. Non-root, read-only root, -no service-account token, dropped capabilities, loopback listener, denied ingress -and public HTTPS/DNS egress match the proposed trusted-target pilot. -Chromium's OS sandbox is disabled; this is no hostile-page or cross-tenant -isolation acceptance. Initial targets remain the bundled fixture and public -https://onco.cc; no private preview, source import or data-license grant follows. - -## Establish and verify the consumer group RBAC boundary - -```task -id: RPF-WP-0056-T02 -status: done -priority: high -state_hub_task_id: "8e6f124a-8364-5a12-b39f-957f9bb5524d" -``` - -User requested a group; use exact Kubernetes group `playwright-consumers`. -Platform binding: helm/playwright-consumer-access.yaml, referring to the package's -existing namespace Role `playwright-consumer`. Bootstrap only its reviewed -Namespace and Role, then bind the group. No cluster-wide role, Secret access, -pod execution, workload mutation, credential issuance or group membership grant. -Verify live allowed discovery/port-forward and denied execution/Secrets/foreign -namespace/workload changes, plus nonmember denial. Impersonated authorization -checks prove RBAC only, not actual consumer authentication or connectivity. - -## Connect platform identities and prove managed consumer operation - -```task -id: RPF-WP-0056-T03 -status: wait -priority: high -blocked_on: message-from:railiance-cluster -blocking_reason: "User admitted all identified platform humans and coding agents; their supported Kubernetes authentication/group-mapping route remains unimplemented." -state_hub_task_id: "a82762eb-bb6d-5600-89ca-a81ada2eba9f" -``` - -The session's initial readback found only built-in/system user/group bindings; -the cluster documentation describes an administrator kubeconfig. A group subject -in RBAC does not create an identity-provider group or convey membership. The user admitted all identified platform humans and coding agents; establish -their existing-owner authentication and group-mapping route, -including expiration/revocation. Do not distribute the admin kubeconfig or infer -that a KeyCape group is already understood by the Kubernetes API. - -The immutable pilot is now deployed after fresh capacity, server dry-run and -diff: 1/1 Ready, 2Gi PVC Bound and matching imageID. A real group-identity trial -is still required; administrator impersonation is recorded separately. -Retain screenshots/keyboard receipts, wrong-token/assertion/target negatives, -cancellation and restart/interrupted/completed-artifact recovery. One available -release supports same-image recovery only; cross-version rollback stays unproven. -Keep RAPP-PLAYWRIGHT-WP-0004-T01 open until its owner accepts those receipts. - -2026-10-13 owner returns narrow the remaining gap. rapp-playwright `2cb2b654` -and Custodian `2cb2b654e03e64ea28f1b369a89d7eaeb36c26fb` record same-image abrupt -recovery, including the managed SIGKILL trial. That does not prove cross-version -rollback or a real consumer identity. Registration preparation is published in -NetKingdom `276b95f6` (NK-WP-0050-T02 still in progress). Platform acceptance -stays here: the Kubernetes authentication and `playwright-consumers` group -mapping route is still unimplemented. Custody and per-agent delivery remain -`docs/playwright-identity-handoff.md`. No new membership decision is open. - - -## October 13 native implementation - -ADR-0006 records the group decision. Namespace, package Role and Platform binding -are applied on the pinned railiance01 cluster. Thirteen native authorization -checks pass: intended discovery/forwarding is allowed; Secrets, exec, writes, -foreign namespaces, foreign deployment and nonmember access are denied. The -initial verifier's kubectl schema discovery needed unrelated CRD access; using -the native SelfSubjectAccessReview endpoint corrected the verifier without any -permission expansion. Re-run scripts/verify_playwright_access.py with --output. - -Fresh capacity before deployment: 3460m/4000m CPU requested, 9472Mi memory -requested and 126Gi free storage. Adding 100m/256Mi plus a 2Gi PVC fits that -scheduling headroom; aggregate CPU/memory limits were already overcommitted. -The declared single-browser limits remain in force. Full package server dry-run -and diff passed and the published immutable image reached Ready. - -Initial fixture trial through administrator-impersonated group port-forward -passed desktop/narrow keyboard and screenshot checks. This demonstrates native -RBAC and browser operation; it does not turn the administrative SSH identity -into a generally available consumer authentication route. T03 retains that gap. - -## Activate the bounded pilot and verify operator-driven recovery - -```task -id: RPF-WP-0056-T04 -status: done -priority: high -state_hub_task_id: "223df123-f674-56a3-8727-a03ee42a9d76" -``` - -The published package is running. Thirteen RBAC checks and six native negative/ -health checks pass, including wrong receipt/artifact token, deliberate assertion -failure, bounded cancellation and private-target refusal. Actual Deployment -restart reached Ready with a new pod UID, the same immutable image and the same -PVC; all four completed artifacts were retrieved and their hashes matched. -Evidence: docs/evidence/2026-10-13-playwright-managed/ and -docs/evidence/2026-10-13-playwright-rbac.json. This is same-image recovery, not -cross-version rollback or abrupt-interruption proof. T03 remains open for actual -consumer identity/group mapping and the remaining package-owner acceptance. - -Group decision mirrored to State Hub d2426028-0b04-4ea2-b199-d7f9e19e38e6. - -## Prepare consumer login delivery and owner activation inputs - -```task -id: RPF-WP-0056-T05 -status: done -priority: high -state_hub_task_id: "5549e2c0-4939-5d0f-903c-fd85b3e34583" -``` - -User authorized continuing with identity-owner implementation. Added -scripts/playwright_consumer.py: isolated kubeconfig setup, public-CA/destination -pinning before credential delivery, private KeyCape token-file consumption and -actual SelfSubjectReview plus nine allowed/denied access checks. Verification -refuses modified/impersonating configurations and administrator identities. -Nine focused tests pass, including misuse, file permission, wrong-destination, -expired/wrong-audience token and excessive-permission refusals; installed kubectl -parses the generated configuration. These synthetic tests do not prove native -JWT authentication or live identity access. - -config/playwright/ contains the public cluster CA and review candidates for the -native JWT authenticator and public PKCE client. docs/playwright-consumer-access.md -now provides login/setup/verify/forward/trial commands; the exact owner activation -contract is docs/playwright-identity-handoff.md. Existing workstation API tunnel -uses its own SSH actor and is not a general per-consumer transport entitlement. -Dedicated coding-agent registration and governed secret delivery remain pending. - -T03 stays wait. KeyCape accepted the public candidate in source only -(2026-10-14, `0e65b962`); no client is registered. Cluster and NetKingdom -activation replies have not arrived, the authenticator candidate has not been -natively validated or activated, and neither a real human nor agent consumer -receipt exists. Do not update provider -managed availability to ready from this preparation. No new membership approval -is required. Evidence: docs/evidence/2026-10-13-playwright-consumer-preparation.json. - -## Obtain Custodian coordination of self-service consumer delivery - -```task -id: RPF-WP-0056-T06 -status: done -priority: high -state_hub_task_id: "ef6c21bc-0048-5b73-976e-130cd9af538d" -``` - -User authorized forwarding the requirements to the-custodian. The requirements -and requested return are in docs/playwright-identity-handoff.md under Custodian -coordination request. Initial eligibility is already decided: all identified -platform humans and coding agents. Request one coordinator, owner task links, -dependency order and first executable milestone, reusing the existing owner -requests. Platform's live consumer acceptance stays in T03. - -Track this task until the Custodian returns a source-backed coordination task and -accountable owner. Message delivery alone does not complete T06 or T03. General -availability requires real human and coding-agent trials and provider acceptance; -no further user membership decision is needed. - -Request delivered to the-custodian through State Hub on 2026-10-13 at 19:31 UTC, -message `1b279bad-a159-43da-8709-1167882e62f8`. - -Accepted 2026-10-13 at 20:04 UTC, message `f761e801-2111-41f3-af0d-a5411a37ffab`. -Accountable coordinator: the-custodian / custodian-codex. Source task: -CUST-WP-0080-T05, Hub `ed8b909a-23bf-5e5f-b600-e477fbae136b`, published at -Custodian `3a850beb` and refreshed through `f5925c62`. Provider recovery and -the consumer acceptance command are recorded on rapp-playwright `2cb2b654` and -`5b8e7984`. T06 is done because that source-backed coordination return exists. -T03 still owns platform acceptance. Live identity, custody, native authenticator -and per-caller transport are not delivered by this acceptance. diff --git a/workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md b/workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md deleted file mode 100644 index 440351d..0000000 --- a/workplans/RPF-WP-0057-sign-in-diagnosis-caller-proof.md +++ /dev/null @@ -1,112 +0,0 @@ ---- -id: RPF-WP-0057 -type: workplan -title: "Let the hub-writer caller prove its own reach and pick the workload role in the OpenBao UI" -domain: financials -repo: railiance-platform -status: active -owner: claude -topic_slug: railiance -created: "2026-10-14" -updated: "2026-10-15" -state_hub_workstream_id: "04f7c4aa-0a24-5d61-994d-2487ca6bfb91" ---- - -Demand: NetKingdom NK-WP-0051-T06 (`net-kingdom/workplans/NK-WP-0051-sign-in-diagnosis.md`). -Contract: `net-kingdom/docs/sign-in-diagnosis.md`. Paired ops-mason work: -MASON-WP-0012. Background: MASON-WP-0011 and CCR-2026-0044. - -## Boundaries - -- Initial preparation was repo-only. Founder approved CCR-2026-0045 in both - required roles and the overlay deployment on 2026-10-15; live results below. -- `ops-mason-survey` is not touched. -- `workload-kv-read-reuse-surface-runtime` is not attached to entity - `4a1f663c-0621-0483-edd0-f852274f4b6b`. -- No `verbose_oidc_logging`. No token, code, email or OTP in any file or log. - -## Generate a capabilities-self grant for the lane policy - -```task -id: RPF-WP-0057-T01 -status: done -priority: high -state_hub_task_id: "704c5809-dc92-5774-932e-1d7797450a51" -``` - -`scripts/credential-change.py` gains the boolean `openbao.token_capabilities_self`. -When true, the generated policy adds `sys/capabilities-self` with `update` -(the endpoint is a POST; it returns capability names only). Proposed -CCR-2026-0045 sets it for the reuse-surface hub-writer caller and supersedes -CCR-2026-0044 once applied. `openbao/policies/workload-kv-read-reuse-surface-runtime.hcl` -matches the generated body. Tests in `tests/test_credential_change.py`. - -Source done 2026-10-14; live policy applied 2026-10-15 under CCR-2026-0045. - -## Apply CCR-2026-0045 - -```task -id: RPF-WP-0057-T02 -status: done -depends_on: [RPF-WP-0057-T01] -priority: high -state_hub_task_id: "d3236de5-a3b5-5373-8a3b-725294e94fd6" -``` - -Needs platform-operator and reuse-surface-owner approval, then the founder's -delegated metadata apply (`credential-change.py applier-apply CCR-2026-0045`). -No KV write. Afterwards ops-mason re-runs its member and non-member proofs -with capabilities-self only, and this CCR records that evidence. - -## Let the OpenBao UI choose the workload role - -```task -id: RPF-WP-0057-T03 -status: done -priority: medium -state_hub_task_id: "afa14a91-1ebf-5a45-a28b-380f9a933336" -``` - -`helm/openbao-ui-overlay/login.{html,js,css}` and `presets.json`: the main -button stays role `platform-admin`; a "Sign in with a workload role" section -offers only the roles in `presets.json` `workloadRoles` -(`reuse-surface-hub-writer-workload-kv-read`). Test: -`tests/test_openbao_ui_role_select.py` (node harness). - -Source done 2026-10-14; live overlay deployed 2026-10-15. - -## Deploy the overlay - -```task -id: RPF-WP-0057-T04 -status: progress -depends_on: [RPF-WP-0057-T03] -priority: medium -state_hub_task_id: "94cdfd1c-c639-5c5e-9bd6-7db823ab0265" -``` - -Operator runs `make openbao-overlay-apply` then -`make openbao-verify-login-overlay`, and one attended browser login per role -(the workload role in a private window). - - -### Live acceptance — 2026-10-15 - -T02 done: founder approved as platform-operator and reuse-surface-owner in the -NK-WP-0051 sitting. Validate/dry-run/plan-only passed without warnings. Contained -`platform-root` login (`net-kingdom-admins`, `aal2`) applied metadata and exited -0 with session self-revocation. Both subsequent ops-mason proofs passed with -capabilities-self only; member token self-revocation succeeded. No KV values read. -[Acceptance and sanitized receipts](../docs/evidence/2026-10-15-sign-in-diagnosis/acceptance.json). - -T04 in progress: `make openbao-overlay-apply` succeeded on cluster UID -`a553c742-0115-43d4-99a4-a5ca56fe0786`; gateway rollout passed. -`make openbao-verify-login-overlay` passed after a transient connection reset -during restart. Admin and workload roles were exercised through native attended -OIDC logins. Separate browser clickthrough of the deployed UI role-selection -controls remains in this task; no UI clickthrough is claimed from CLI logins. - -Validation: 64 platform tests passed; two unrelated existing failures concern -CCR-2026-0035 schema and the core-hub expected resource inventory. The target -CCR validates without warnings. NK-WP-0051's native diagnosis/refusal proof is -accepted; this owner task retains the remaining UI control check. diff --git a/workplans/RPF-WP-0058-reuse-private-source-reader.md b/workplans/RPF-WP-0058-reuse-private-source-reader.md deleted file mode 100644 index e079978..0000000 --- a/workplans/RPF-WP-0058-reuse-private-source-reader.md +++ /dev/null @@ -1,181 +0,0 @@ ---- -id: RPF-WP-0058 -type: workplan -title: Prepare the exact seven private-source reader for reuse-surface -domain: financials -repo: railiance-platform -status: active -owner: codex -topic_slug: platform -created: '2026-10-15' -updated: '2026-10-15' -flavor: implementation -depends_on: [] -state_hub_workstream_id: "b55a8aec-7457-563b-990b-4a55ca04d2f6" ---- - -Custody/Q1 work under platform INTENT.md: dependable shared secret custody and -stable delivery with reviewed, delegated apply and revocation. Founder metadata -consent is recorded separately and grants no credential or live rollout authority. -Preparation began in an isolated clone; no own supervision record means supervised -preparation only. CCR-2026-0046 and RPF-WP-0058 are now allocated and published in -the authoritative platform source: restricted-reader preparation at -6e8955fcd09b278a6f16c14beffd076b72d10c95 and attended-runner preparation at -635daec6b4265ccbd066b96b53228919ac4cbd79. Both exact commits were verified on -Forge main and the State Hub primary/railiance01 projection. - -## Prepare bounded custody change - -```task -id: RPF-WP-0058-T01 -status: done -priority: high -state_hub_task_id: "f9b738e1-6d88-5dde-9fc5-b05b3adf2549" -``` - -Prepare CCR-2026-0046, exact seven-source bootstrap contract, silent bounded -provisioner, tests and exact ESO policy delta. Preparation itself performed no live action; the separately founder-attended -credential execution is now verified in T03 and its dated custody evidence. - -## Independent owner review and founder disposition - -```task -id: RPF-WP-0058-T02 -status: done -priority: high -state_hub_task_id: "41d16c6e-9e0b-543b-b600-9288d5762879" -``` - -Independent preparation reviews are recorded within their owner scopes: -platform custody and attended-runner R3; Forge restricted-reader R2 and -attended-runner R3; reuse consumer CCR boundary R2 and hardened native consumer; -apps release contract R2. These approve preparation/integration, not live action. -Founder execution decision 9833715e-a22a-45c0-ab6c-881f0f4f1a0e is resolved; -the approved exact credential revision executed through the attended owner lane. -Installed-role proof preceded conditional state transition, and CCR is approved -with confirmed claims at the exact prebound SHA256 23039fae6137e7bdd71de0ed3278596b1ed60ae636018eb4bfcc83e53c4ada02. -This authority is separate from the earlier metadata disclosure consent. -The published hardened image digest is -sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0; -actual deployment of that reviewed image plus the exact URL allowlist and native -verification remain mandatory before reader delivery. The existing vulnerable -runtime cannot receive this credential. - -## Execute and verify restricted reader custody - -```task -id: RPF-WP-0058-T03 -status: done -priority: high -state_hub_task_id: "58caf089-9af1-51fd-a94d-aba9abf14a53" -``` - -After review and disposition, attended platform lane executes silent bootstrap. -Read-only preflight checks all seven private repos, restricted non-admin private -user, exact code-read team flags, no unrelated members/teams/private grants and -PAT/KV drift. Mutation intent and observed/ambiguous outcome are fsynced. No -uncertain mint is retried. CAS=0 never overwrites existing KV. Noop still verifies -PAT metadata binding, all seven pinned index digests and excluded-private/admin/ -Actions denial. Raw provider output and credentials remain suppressed. - -## Owner delivery and hosted completion - -```task -id: RPF-WP-0058-T04 -status: done -priority: high -state_hub_task_id: "9c483fd9-68a5-56b0-813d-c965a11b60d2" -``` - -Apps owner integrates proposed data item and exact fourteen approved URL config, -performs approved fixed-image rollout after CPU/PVC preflight and safe diff/dryrun. -Confirm existing ESO SA reuse/reuse-surface-eso, role, audience and namespace -condition from live metadata. Consumer owner native verification precedes private -registration; Custodian final fresh hosted audit must prove all70 exactly once. -Removing reader grants and delivery must preserve shared writer/webhook fields. - -Completed and verified 2026-10-15. The actual hardened phase-B runtime was Ready -on image sha256:2364b7e7859daccf9340a063654e2b09eed1bc27e0adf7b23cbb4b30135a7db0 -with reader key present and the exact allowlist verified. Native authenticated -fetch at03:30:20Z verified all seven pinned index hashes and seven entry hashes -without cache. All seven private registrations were applied and fully read back. -Forced-fresh hosted composition at03:31:48Z proves all70 reviewed IDs exactly -once, correct source repository, no missing/duplicate IDs and no federation -warning. Scoped writer attempt 8f23bb90-aef9-405d-81b6-bf550400dd93 ended with -Warden0 and confirmed revocation. - -Authoritative external evidence is published in the-custodian commit -e5aaa0db51edc4c9f935d03ab58808b86e340823, under -`docs/assessments/netkingdom-2026-10-14/`: `phase-b-runtime-verification-r1.json`, -`private-native-fetch-verification-r1.json` and -`hosted-private-registration-verification-r1.json`. Their hashes and checked -results are retained locally in -`docs/evidence/2026-10-15-reuse-private-reader-hosted-establishment-r1.json`. -The root architecture/reuse goal is closed; root residual CUST-IN-0018 is live -separately. This platform workplan remains active solely for the existing manual -maintenance task T05, which stays wait with the actual Jan13 deadline. No rotation, -reprovisioning, new testing or live change is performed by this closeout. - - -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 was followed by actual founder execution. -T03 is now done: policy attempt 93578ba93d6153fdb07c65db ended successfully -at 2026-10-15T03:06:13.283133Z; bootstrap attempt bb2693a7640f00b4e401467e -ended provisioned/verified with delivery pending at 03:06:12.816932Z. -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. Actual silent founder execution verified exact seven-source -reader scope, created PAT metadata ID72/team5, and stored the reader at its separate -KV path. Credentials stayed inside the reviewed process; no value was printed. -Warden audits at03:06:13Z report outcome ok/exit0, including session revocation -and helper cleanup. See docs/evidence/2026-10-15-reuse-private-reader-custody-r1.json. - - -## Rotate or revoke dedicated seven-source reader within 90 days - -```task -id: RPF-WP-0058-T05 -status: wait -priority: high -due_date: "2027-01-13" -state_hub_task_id: "f0780e75-b0bd-5bf5-b926-c8413abb885c" -``` - -Manual platform-owned maintenance for Forgejo identity `reuse-surface-source`, -team `reuse-surface-source-readers`, its exact named seven-repository PAT and -OpenBao KV `platform/workloads/reuse/reuse-surface/forgejo-source-read`, field -`FORGEJO_SOURCE_TOKEN`. Forgejo PATs do not expire automatically; this live task -is a human-attended rotation/revocation obligation, not an expiry mechanism or -scheduled automation. - -The deadline is now bound to actual verified custody activation, not an assumed -date: 2026-10-15T03:06:09.579844+00:00. The manual deadline is exactly -2027-01-13T03:06:09.579844+00:00 (task due_date 2027-01-13), actual activation plus 90 days. -Activation proof is bootstrap attempt bb2693a7640f00b4e401467e, verified kv_create; -receipt SHA256 f5ddd86299cdf31ca895f76b84d7fc14d2ac791e5982632c5c989a32221a0171. -The owner/task binding is RPF-WP-0058-T05, UUID -f0780e75-b0bd-5bf5-b926-c8413abb885c, and is retained in the dated custody receipt. -Source and indexed task description must carry this actual date before delivery. -The structured Hub due_date cache remains a documented limitation; no automatic -reminder or server-side expiry is claimed. - -At maintenance time prepare a separately approved bounded rotation/CAS plan, -including expected existing KV version, sibling preservation, exact dedicated -PAT identity, predecessor retention/cutover and native verification. Alternatively -use the separately approved exact-token recovery helper for Basic-auth revocation -with process-memory password and verified token inventory absence. Preserve the -shared runtime writer/webhook fields and public registrations. Never remint or -recover orphan/ambiguous requests automatically; reconcile authoritative state -before any new approved action. No rotation/revocation is executed by this record. diff --git a/workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md b/workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md deleted file mode 100644 index 71c56ea..0000000 --- a/workplans/RPF-WP-0059-hourly-cadence-receiver-custody.md +++ /dev/null @@ -1,96 +0,0 @@ ---- -id: RPF-WP-0059 -type: workplan -title: "Admit hourly receiver cadence and exact Qonto collector custody" -domain: financials -repo: railiance-platform -status: active -owner: codex -topic_slug: railiance-platform -created: "2026-10-15" -updated: "2026-10-15" -origin: cross-repo -origin_ref: NK-WP-0035 -state_hub_workstream_id: "d57eeafb-c406-5e1e-841c-5d1288aacabf" ---- - -# Hourly receiver cadence and Qonto collector custody - -## Prepare exact bounded source packet - -```task -id: RPF-WP-0059-T01 -status: done -priority: high -state_hub_task_id: "a464dc7e-59c2-5efa-b560-e21e50c1e0e0" -``` - -Prepare CCR-2026-0047, exact-only reader policy, namespace-restricted ESO -projection and silent attended owner runner. Preserve Approval credentials and -all other senders while admitting its source cadence and the new Qonto sender. -Focused model tests cover CAS0, CAS-preserving merge, version1 resume, -interruption, provenance/scope drift, exact ESO resume/template refusal and -no-native proposed refusal. Details: docs/cadence-receiver-custody.md. - -## Review new grant and complete concrete packet - -```task -id: RPF-WP-0059-T02 -status: done -priority: high -state_hub_task_id: "fd338306-bdff-5335-ae1f-2c54f277f8e8" -``` - -Explicit approval required from platform operator, Audit Core, rapp-qonto and -qonto-assistant owners. CCR0047 is approved by the explicit user response "Approve packet and proceed", recorded with all four owner roles on 2026-10-15. Prior timing rollout approval -is not a new bearer grant. Review the post-approval exact packet SHA256 and -immutable compatible receiver image before attended execution. - -## Execute attended custody, delivery and receiver readback - -```task -id: RPF-WP-0059-T03 -status: done -priority: high -state_hub_task_id: "c4e69d60-030e-592b-ac56-9f77468f7da9" -``` - -After T02 and the reviewed receiver release, use Warden platform-admin --exec. -Record independent session exit/self-revocation and native command receipt. -Run exact grant/negative checks and ESO/runtime registry equality; use durable -resume on interruption. No live effect or secret read occurred in preparation. - -## Collect actual producer and independent observer acceptance - -```task -id: RPF-WP-0059-T04 -status: progress -priority: high -state_hub_task_id: "7d8e5315-9470-5596-a865-d451284f6f50" -``` - -Coordinate QONTO-WP-0006-T02, AUDIT-WP-0013-T05 and KG-WP-0005-T03. Preserve -source/tenant/instance scope and real elapsed-hour periodic evidence, isolated -wrong-namespace denial, actual formerly-valid bearer revocation, independent -protected readback, missing-class/lag/divergence, restart/scale-zero evidence. -Never infer profile acceptance from a model test or registry projection. - - -### Native admission progress — 2026-10-15 - -Root executed the explicit approved packet in an attended platform-admin -session. Wrapper exit0 and self-revocation confirmed; receiver-custody-live.json -records registry15→16 CAS, durable version1 custody, preserved Approval -credentials/other senders, reader scope/negative/revoke checks, ESO projection -and actual loaded registry equality, and Ready receiver reload. CCR0047 is -applied, not verified/active. An initial ambient OPENBAO_TOKEN attempt was -correctly refused before custody mutation; that session also self-revoked. -Retry removed token aliases before launching OIDC, without relaxing guards. - -Collector native receipt proves heartbeat1/deny1 own-instance reconciliation, -duplicate200, wrong-source400/wrong-tenant400/archive-read403/invalid-bearer401, -pending0; actual collector pod restart preserved first202 receipts and pending0. -Exact root receipts copied to docs/evidence/2026-10-15-cadence-receiver/. -The real hourly observation started19:36 UTC with authorized health polling; -it is still running. No elapsed-hour or independent observer acceptance is -claimed. T04 remains progress for actual timed capture and protected readback. diff --git a/workplans/RPF-WP-0060-browser-case-platform-bindings.md b/workplans/RPF-WP-0060-browser-case-platform-bindings.md deleted file mode 100644 index 6332064..0000000 --- a/workplans/RPF-WP-0060-browser-case-platform-bindings.md +++ /dev/null @@ -1,118 +0,0 @@ ---- -id: RPF-WP-0060 -type: workplan -title: "Implement Platform bindings for the browser SecurityCase successor" -domain: financials -repo: railiance-platform -status: active -owner: codex -topic_slug: netkingdom -created: "2026-10-17" -updated: "2026-10-17" -flavor: implementation -origin: cross-repo -origin_ref: NK-WP-0048-T16 -state_hub_workstream_id: "43110f82-ef9f-5a46-a305-f0e287b7b226" ---- - -Demand: Platform's source review of the proposed browser SecurityCase 1.3 -requires four concrete owner obligations before owner-descriptor acceptance. -The review labels `RPF-1.3-T1` through `T4` were not live work records; this -workplan makes them ordinary owner tasks without changing RPF-WP-0054's -completed first-generation lane or implementing its residual rotation work. - -Review: `the-custodian/docs/changes/netkingdom-target-2026-10-17/nk48-1.3-owner-review/owner-review-railiance-platform.json`. -The 1.3 type remains proposed. This workplan grants no native access, case -acceptance, owner acknowledgment, catalog activation or policy change. - -## T01 — Bind the broker to reviewed acknowledged type sources - -```task -id: RPF-WP-0060-T01 -status: progress -priority: high -state_hub_task_id: "afec2053-1995-506d-a112-cda1de552c1c" -``` - -Implements review obligation RPF-1.3-T1. The broker's explicit source map admits -only its existing 1.1 revision. Exact type and producer bytes, parameter -fingerprint, acknowledgment state and case schema are checked before any Bao -call. Source-only 1.2 and proposed 1.3 remain refused. The fixed installation -layout is `railiance-platform` and `net-kingdom` as real sibling directories; -missing, symlinked or drifted selected sources refuse rather than fall back. - -2026-10-17: source hardening and catalog regressions independently reviewed in -`the-custodian/docs/changes/netkingdom-target-2026-10-17/platform-broker-review-r1/`. -The exact case reference binds caller-supplied bytes; schema validation does -not authenticate case acceptance. Existing Warden admission/native evidence -must establish that independently. This task remains open for the real binding -and explicit 1.3 source/parameter pins after genuine acknowledgment and review. -No speculative successor pin or acknowledgment is added. - -Done when the actual admitted-case binding is verified, the genuinely -acknowledged successor is explicitly selected, and refusal tests cover unknown, -unacknowledged, wrong-case and drifted-source inputs before native effects. - -## T02 — Correct the documented mint caller and issuer binding - -```task -id: RPF-WP-0060-T02 -status: done -priority: high -state_hub_task_id: "1a764c10-3a41-5eff-af96-c9e33a6e2c8b" -``` - -Implements review obligation RPF-1.3-T2. `credential-grants/catalog.yaml` now -names human-operator only and the exact existing issuer role -`auth/netkingdom/role/credential-broker-keycape-client-secret-generate`. -Regression checks bind this statement to CCR-2026-0043's role source, -net-kingdom-admins membership and aal2 claim. No OpenBao role, policy, grant, -credential or live configuration changes. - -## T03 — Implement mint, guarded destruction and authenticated evidence - -```task -id: RPF-WP-0060-T03 -status: todo -priority: high -state_hub_task_id: "b5d73ec6-c593-50a9-8367-f899edec6f5f" -``` - -Implements review obligation RPF-1.3-T3. Bind the actual generator to the closed -`railiance-platform.custody-mint.v1` projection and authenticated publication. -Capture grant accessor and revocation evidence and revoke the issuer session. -Destruction uses attended platform-admin authority and compares the original -CAS-0 version-1 creation time, current version and absence of other versions -before metadata deletion. No standing family-wide delete grant is introduced. -Unavailable authority, uncertain effects or failed revocation remain UNCERTAIN -for owner reconciliation. Verify with the frozen adapter, then obtain the -required native authority and actual execution receipts. - -## T04 — Implement and admit the delivery lane and capability - -```task -id: RPF-WP-0060-T04 -status: todo -priority: high -state_hub_task_id: "84f8b0a4-8450-5623-8608-dc277978383b" -``` - -Implements review obligation RPF-1.3-T4. Reuse the fixed proposed issuer Store -contract; validate the preexisting Store and only create the case-derived -ExternalSecret in `sso`. Refuse conflicts; report NO_CHANGE only for matching -preexisting configuration; establish current Ready status before success. -Return the closed metadata-only `netkingdom.deliver-secret.v1` receipt and use -UID and resourceVersion preconditions to remove only authenticated case-created -resources. Never read Secret values or create the Store through this lane. - -Bind real human net-kingdom-admins/aal2 admission, per-attempt session and -revocation, durable pre-intent/recovery journal and authenticated owner evidence. -The separate command-mechanics candidate is preparation, not this capability; -its production factory refuses. Review freshness/defaulting/transport failure -cases before integrating it. Obtain ops-warden's catalog routing acceptance for -`platform-secret-delivery` through an authorized handoff before recording owner -acceptance. No routing handoff or native action is implied by this work record. - -The parent stays active until these obligations and their required owner -acceptance are proved. Source tests do not complete native acceptance or -KEY-WP-0039's negative/expiry/cleanup exercise. diff --git a/workplans/RPF-WP-0061-self-service-ccr-intake.md b/workplans/RPF-WP-0061-self-service-ccr-intake.md deleted file mode 100644 index c6a9bfc..0000000 --- a/workplans/RPF-WP-0061-self-service-ccr-intake.md +++ /dev/null @@ -1,198 +0,0 @@ ---- -id: RPF-WP-0061 -type: workplan -title: "Self-service CCR intake: clients file and progress CCRs without a platform worker" -domain: financials -repo: railiance-platform -status: active -owner: codex -created: "2026-10-18" -updated: "2026-10-18" -flavor: implementation -origin: cross-repo -origin_ref: ops-mason daa119e6 / 049afa1f; MASON-IN-0005 -state_hub_workstream_id: "bad0cc9b-e8ad-572b-8d0d-fb3adb208b4a" ---- - -Demand: every credential change request today waits on a railiance-platform -worker session to allocate the CCR number, copy the client's draft and policy -file into this repo, run `credential-change.py validate`, write the platform -owner review and relay the founder command. For routine lanes the worker adds -no judgement: ops-mason already produces drafts that pass `validate` -(MASON-WP-0010-T03, MASON-WP-0014), and from MASON-IN-0005 ops-mason can apply -`workload-kv-read-*` policies and `external-secrets-*` roles itself. The worker -has become a relay, and clients stall whenever no platform session runs. - -Goal: a client can take a standard CCR from draft to approved and applied -using only repo tooling, CI and the State Hub decision flow. Platform judgement -is moved into a versioned, reviewed *standard lane profile*; only requests that -fall outside it are routed to a platform worker. - -Invariants (unchanged by this plan): - -- CCRs remain non-secret repo files in `credential-change-requests/`; the repo - stays authoritative and the hub stays a read model. -- Founder approval is never automated. Bound claims still need - `confirm-binding` against native state before apply. -- Nothing here widens ops-mason-construct or any applier grant; secret-id, - token creation and value reads stay with the founder and applier. - -## Design - -1. **Submit.** The client runs, in a railiance-platform checkout, - `scripts/credential-change.py submit --ccr --policy --slug - --requester --push`. It copies the CCR and policy file, - allocates the next free `CCR-YYYY-NNNN` from the files in the checkout, - sets `status: proposed`, clears client-written review comments, validates, - commits on branch `ccr/-` and pushes. Only profile `submitters` - may submit (ops-mason only, founder decision 2026-10-18). A race produces - two files with the same id; `check-ids` in CI refuses the second, which - re-submits. Git is the allocator, so no central counter or agent is needed. -2. **Platform review as policy-as-code.** `credential-change.py - review-standard ` checks the standard lane profile - (`registry/ccr-standard-profile.yaml`): `workload-kv-read`; kubernetes auth; - exactly one KV path under `platform/workloads//` or - `platform/suppliers//`; policy file byte-equal to the - generated HCL (read on the data path plus lookup/revoke-self only); ttl and - max ttl ≤ 15m; `token_no_default_policy: true`; one SA in one namespace; - no other CCR in `proposed|approved|active` reading the same path. A pass - appends the `railiance-platform-owner` review comment with the profile - version and digest. A fail appends a `needs_platform_review` review - comment with the reasons and, with `--notify`, sends one hub message to - `railiance-platform`. Either way the CCR status stays `proposed`; the - automated comment never sets `approved`. -3. **CI gate.** A Forgejo Action on `ccr/*` branches runs `validate`, - `review-standard` and the secret scan; only a green branch fast-forwards to - `main`. The client merges its own green branch. -4. **Founder decision.** `submit` records a State Hub decision for the CCR - (templates exist in `decision-templates`). The founder resolves it; - anyone runs `sync-decision` to move the file to `approved`/`needs_changes`. -5. **Apply and evidence.** In-profile CCRs are applied by ops-mason under - ops-mason-construct, or by the applier, after `confirm-binding`; - `record-evidence` closes the CCR to `active`. -6. **Platform oversight.** Platform owns the profile and reviews changes to it - like code. A weekly `credential-change.py audit-standard` lists every - auto-reviewed CCR so a platform session can spot-check after the fact. - -## T01 — Define the standard lane profile - -```task -id: RPF-WP-0061-T01 -status: done -priority: high -state_hub_task_id: "c18752a6-6126-5410-bbb6-1799ccd63558" -``` - -Write `registry/ccr-standard-profile.yaml` from the existing approved ESO -lanes (CCR-2026-0033, -0035, -0041, -0050, -0051 shapes). Record which past -CCRs would and would not have passed it. - -Done 2026-10-18: `registry/ccr-standard-profile.yaml` v1. Calibrated on all 51 -CCRs: only CCR-2026-0051 passes; the profile comment lists the first failing -rule for every other CCR. Stricter than past practice by design. - -## T02 — `submit` and `review-standard` subcommands - -```task -id: RPF-WP-0061-T02 -status: done -priority: high -state_hub_task_id: "5dd5305f-e1e4-52f5-88aa-f0090e072d0f" -``` - -Add both subcommands to `scripts/credential-change.py` with tests covering -id allocation, duplicate-path refusal, policy-file mismatch and every profile -rule. `review-standard` never touches OpenBao. - -Done 2026-10-18: `scripts/ccr_intake.py` adds `submit`, `review-standard` -(`--check`, `--notify`) and `check-ids` to `credential-change.py`; -`tests/test_ccr_intake.py` has 20 tests against a scratch repo root. Two -existing failures in `tests/test_credential_change.py` (CCR-2026-0035 schema -gaps, core-hub database policy assertion) predate this work. - -## T03 — CI gate on `ccr/*` branches - -```task -id: RPF-WP-0061-T03 -status: done -priority: medium -state_hub_task_id: "0fd97924-1bc7-5aa1-a520-c6c5737bf09b" -``` - -Forgejo Action running validate, review-standard and the secret scan; confirm -which identities may push `ccr/*` (ops-mason first). - -2026-10-18: `.forgejo/workflows/ccr-intake.yaml` runs on `ccr/**` and `main` -from exact-revision archives. On `ccr/*` it runs the intake tests and -`credential-change.py check-branch`. That command refuses the branch unless it -adds exactly one new CCR plus its new policy file and changes nothing else -(not the profile, not an existing policy). The CCR must validate, be -`proposed`, come from a listed submitter, re-pass the profile in CI and carry -exactly the policy-as-code approval for the current profile digest. On `main` -it runs `check-ids`. 26 tests. CCR-2026-0051 received the first automated -platform-owner review (still `proposed`). - -Remaining, founder/Forgejo admin: a protected-branch rule on `ccr/*` that -allows pushes only from ops-mason's Forgejo identity. Forgejo cannot require -a check on main without blocking every direct push, so merging is a -convention backed by detection: ops-mason fast-forwards only a green branch, -and the main job plus `check-branch` evidence detect violations after the -fact. - -## T04 — Hub decision on submit and client-run `sync-decision` - -```task -id: RPF-WP-0061-T04 -status: done -priority: medium -state_hub_task_id: "15ac9eae-9890-5140-b1ca-cd0ad3ca77ac" -``` - -2026-10-18: `credential-change.py request-decision ` opens the founder -decision for a `proposed` CCR whose latest review is a platform-owner -approval, refuses a second decision or a decision returned already resolved, -and writes `state_hub.decision_id` into the CCR. The existing `sync-decision` -moves the file. First use: CCR-2026-0051, decision e633607e. The CI gate -passed on main at 3d97117 after adding the jsonschema pin. - -Open, needs a founder choice: the hub does not authenticate decisions. -`decided_by` on resolve is free text, and `POST /decisions/` accepts an -already-resolved decision. With self-service, the requester could resolve its -own CCR decision and run `sync-decision`. This gap existed before (the -platform worker never verified the resolver) but self-service makes it -cheaper to exploit. Until it is closed, `sync-decision` on a self-service CCR -stays a platform/founder step. - -Founder decision 2026-10-18: option A. Clients submit, pass CI, get the -automated review and open the decision themselves; the founder resolves it; -`sync-decision` stays with a platform session or the founder. CCR-2026-0051 went -through this path: founder resolved e633607e at 10:46:17Z; synced at 45c8d93. -Follow-ups, not part of this plan: (B) founder approval provable through an -MFA-backed OIDC login so clients can sync themselves; (C) separate Forgejo and -OS identities per agent so `ccr/*` can be restricted. Both stem from the -finding that every workstation agent pushes as Forgejo user `tegwick` with the -key `workstation-automation`. -(D, ops-mason point 5) Self-service teardown: deactivate lifecycle event filed -by the client and applied under ops-mason-construct. Founder to decide whether a -deactivate needs a decision. Until then teardown goes through a platform session. -ops-mason points 1-4 were adopted in 7e7ca8b (`plan_ref`, `render-policy`, -single hub decision, readback before `record-evidence`). - -## T05 — Client documentation and first self-service CCR - -```task -id: RPF-WP-0061-T05 -status: wait -priority: medium -state_hub_task_id: "27797fa5-3d47-5037-a98c-2d269198d1c4" -``` - -Document the flow in `docs/credential-change-approval.md` and notify -ops-mason and ops-warden. Acceptance (narrowed by option A): ops-mason submits -one CCR, passes the CI gate, merges and opens the decision with no -railiance-platform worker session; only the post-approval sync is platform/founder. - -2026-10-18: guide added as "Self-Service Intake" in -`docs/credential-change-approval.md`; ops-mason and ops-warden notified. Waiting -for ops-mason's first self-service submission, which also gives the first live -`ccr/*` CI run. diff --git a/workplans/RPF-WP-0062-custodian-agent-observer-identity.md b/workplans/RPF-WP-0062-custodian-agent-observer-identity.md deleted file mode 100644 index b99aabf..0000000 --- a/workplans/RPF-WP-0062-custodian-agent-observer-identity.md +++ /dev/null @@ -1,122 +0,0 @@ ---- -id: RPF-WP-0062 -type: workplan -title: "Custodian agent observer identity: read-only ServiceAccount and curated ClusterRole on railiance01" -domain: financials -repo: railiance-platform -status: finished -owner: claude -created: "2026-10-20" -updated: "2026-10-20" -flavor: implementation -origin: cross-repo -origin_ref: the-custodian CUST-WP-0073-T02 (founder decision 2026-10-20) -state_hub_workstream_id: "2cda239a-c071-5234-a728-7d19c3eff306" ---- - -Demand: CUST-WP-0073-T02 needs a supervised agent identity on railiance01 that -can observe cluster state without reaching credentials or writing anything. -This repo owns the RBAC half. The earlier health-only candidate -(`the-custodian/docs/changes/CUST-WP-0073/credential-boundary-r1/identity-rbac.yaml`) -is superseded by the founder's 2026-10-20 decision. - -Founder decision (Bernd Worsch, 2026-10-20): - -- Namespace `custodian-agents`, ServiceAccount `custodian-agent`, - `automountServiceAccountToken: false`. No static token Secret. -- Credentials are short-lived TokenRequest tokens issued by the supervisor. -- ClusterRole `custodian-supervised-observer`: get/list/watch on namespaces, - nodes, pods, deployments/statefulsets/daemonsets/replicasets, services, - endpoints/endpointslices, ingresses, events (core + events.k8s.io), - persistentvolumeclaims, Argo CD applications/appprojects, CNPG - clusters/backups/scheduledbackups; get on `/version /healthz /livez /readyz`. -- Explicitly not: secrets, configmaps, pods/log, pods/exec, pods/attach, - pods/portforward, serviceaccounts/token, nodes/proxy, any write verb, any - wildcard. Privileged actions go through the separate gated executor. - -Layout: - -- Manifests: `argocd/platform-addons/custodian-agents/` -- Child Application (manual sync, pinned, no finalizer): - `argocd/railiance01/applications/custodian-agents.application.yaml`, - project `railiance-platform-addons` (already whitelists Namespace, - ClusterRole, ClusterRoleBinding, ServiceAccount). -- Offline guard: `tests/test_custodian_agent_rbac.py`. - -The railiance01 root `railiance-apps-root` has no `automated` block, so a push -to `main` changes nothing live. Going live is an attended founder action. - -## Token issuance (supervisor only) - -Recommended TTL 24h (shorter is fine; the API server may cap it via -`--service-account-max-token-expiration`). Audience is the API server's own -service-account audience, so the token works against the API and nothing else: - -```bash -kubectl -n custodian-agents create token custodian-agent \ - --duration=24h --audience=https://kubernetes.default.svc.cluster.local -``` - -The token is handed to the agent's restricted kubeconfig by the supervisor; it -is never committed, logged, or stored in the State Hub. It cannot be renewed by -the agent (no `serviceaccounts/token` grant). - -## Place the identity manifests and offline guard - -```task -id: RPF-WP-0062-T01 -status: done -priority: high -state_hub_task_id: "889272c3-d025-5e1b-b43a-ddd053ee5482" -``` - -Namespace, ServiceAccount, ClusterRole and ClusterRoleBinding written; the -pytest guard parses the kustomization and asserts read-only verbs, the exact -curated resource set, no forbidden resource, no subresource, no wildcard, no -Secret, no automount, and a manual pinned Application. - -## Place the pinned child Application - -```task -id: RPF-WP-0062-T02 -status: done -priority: high -state_hub_task_id: "e248f206-2b56-5336-a79b-d0a496da6098" -``` - -`custodian-agents.application.yaml` pins `targetRevision` to the commit that -introduced the manifests. No `automated` block, no finalizer. - -## Attended sync and identity proof on railiance01 - -```task -id: RPF-WP-0062-T03 -status: done -priority: high -state_hub_task_id: "c1d2bf34-791e-5bfa-beda-94532448c7b3" -``` - -2026-10-20 (recorded 2026-10-21): the founder issued the 24h ServiceAccount token for `custodian-agents/custodian-agent` (CUST-WP-0073 `issue-agent-credentials.sh kube`). Token-based proof from the agent account: identity `custodian-agents:custodian-agent`, no system:masters, curated `get pods` allowed, 597/597 negative probes denied across 48 namespaces (the-custodian `docs/evidence/2026-10-20-custodian-agent-kube-denials-receipt.json`, passed=true). Done. - -2026-10-20: the founder ran the attended `custodian-agents` child sync, pinned to `567d2c6` with the apply strategy and prune/force off. The operation Succeeded and the app is Synced/Healthy. It created exactly four objects: Namespace `custodian-agents`, ServiceAccount `custodian-agent`, ClusterRole `custodian-supervised-observer` and its ClusterRoleBinding. A pre-check confirmed none of these names existed before the sync. - -Impersonated `kubectl auth can-i --as=system:serviceaccount:custodian-agents:custodian-agent`: -- Allowed (yes): get pods, list deployments, list Argo applications, list CNPG clusters. -- Refused (no): get configmaps, create deployments, delete pods, patch applications. -- Refused (no), checked with `--subresource`: pods log/exec/attach/portforward, serviceaccounts token, nodes proxy. - -Without `--subresource`, a `pods/log` argument is parsed as a pod named "log" and reports yes. That result is not a grant. - -The secret-read probe is blocked for agents by the guard hook. A binding audit found only the observer binding and the standard `system:basic-user`, `system:discovery`, `system:public-info-viewer` and issuer-discovery bindings applying to this account; none of them grants secret access. Remaining: issuing the token. - -2026-10-20: the founder ran the attended `railiance-apps-root` sync, pinned to `9d5fc42` with the apply strategy and prune/force off. The operation Succeeded and the root is Synced. The `custodian-agents` Application now exists (OutOfSync/Missing, no automated sync), and its namespace, ServiceAccount and RBAC are not created yet. The same sync also recorded activity-core, informed-decision-requester, issue-core and state-hub-memo-dispatch as Synced; a client-side diff showed no spec change for any of them. Remaining T03 steps: child sync, token, can-i proof. - -1. `make argocd-pin-drift` — stop on any `DRIFT` line. -2. Sync only this child out of the root at the reviewed revision, then the child - (commands in the CUST-WP-0073-T02 handoff / this repo's commit log). -3. Issue a token (above) and, as the agent identity, prove: - `kubectl auth can-i get secrets -A` → no; `can-i create pods/exec -A` → no; - `can-i get pods/log -A` → no; `can-i get configmaps -A` → no; - `can-i list pods -A` → yes; `kubectl auth whoami` shows - `system:serviceaccount:custodian-agents:custodian-agent`, no `system:masters`. -4. Report the result to CUST-WP-0073-T02. diff --git a/workplans/RPF-WP-0063-wave2-resource-right-sizing.md b/workplans/RPF-WP-0063-wave2-resource-right-sizing.md deleted file mode 100644 index f04dd54..0000000 --- a/workplans/RPF-WP-0063-wave2-resource-right-sizing.md +++ /dev/null @@ -1,103 +0,0 @@ ---- -id: RPF-WP-0063 -type: workplan -title: "Wave-2 resource right-sizing from the 2026-10-21 allocation review" -domain: financials -repo: railiance-platform -status: finished -flavor: implementation -owner: claude -created: "2026-10-21" -updated: "2026-10-21" -related: - - CUST-WP-0071 - - RESOURCE-WP-0008 -state_hub_workstream_id: "1fa22509-dc6e-5e5b-a0d4-70bb02898683" ---- - -# RPF-WP-0063 — Wave-2 resource right-sizing from the 2026-10-21 allocation review - -Receiving workplan in `railiance-platform` for the wave-2 request changes below. The external-secrets workloads currently run with no requests at all (BestEffort). - -Origin: the 2026-10-21 node allocation review (`RESOURCE-WP-0008-T03`, feeding `CUST-WP-0071-T03`). Per the founder's 2026-10-21 delivery interview (`the-custodian/docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md`) the small adjustments form wave 2: one receiving task per owner repo, applied later in a batch. Applying wave 2 needs a separate founder approval, so every cluster-write task here is a human-gated `wait`. This file changes no manifest. - -## Apply the reviewed request changes - -```task -id: RPF-WP-0063-T01 -status: done -priority: medium -state_hub_task_id: "cea0abec-1e32-506b-aeb6-69b00037d2c8" -``` - -Values are pod-aggregate figures from the budget file (7-day window ending 2026-10-21T06:08Z). Limits stay unchanged unless stated. - -| Workload (ns/name) | Pods | CPU request | CPU limit | CPU p95/peak 7d (m) | Memory request | Memory limit | Mem p95/peak (Mi) | Throttle | Action | -|---|---:|---:|---:|---:|---:|---:|---:|---:|---| -| `external-secrets/external-secrets` | 1 | 0m → **10m** | 0m (1 container(s) without CPU limit) | 4.1/5.5 | 0Mi → **64Mi** | 0Mi | 51.2/53.3 | None | increase + memory up | -| `external-secrets/external-secrets-cert-controller` | 1 | 0m → **10m** | 0m (1 container(s) without CPU limit) | 1.0/1.5 | 0Mi → **64Mi** | 0Mi | 42.2/51.4 | None | increase + memory up | -| `external-secrets/external-secrets-webhook` | 1 | 0m → **10m** | 0m (1 container(s) without CPU limit) | 0.5/0.6 | 0Mi → **80Mi** | 0Mi | 63.2/111.9 | None | increase + memory up | -| `openbao/railiance-platform` | 1 | 25m → **10m** | 200m | 0.2/0.5 | 32Mi → **32Mi** | 128Mi | 4.8/4.8 | 0.0 | decrease | -| `playwright/rapp-playwright` | 1 | 100m → **65m** | 1000m | 41.5/62.7 | 256Mi → **336Mi** | 1024Mi | 285.1/437.2 | 0.238 | decrease + review CPU limit (throttled) + memory up | - -Source and container detail: - -- `external-secrets/external-secrets`: external-secrets controller Deployment (helm chart, Application draft `argocd/railiance01/drafts/external-secrets.application.yaml`); add `resources.requests` in the chart values — no requests are set today. -- `external-secrets/external-secrets-cert-controller`: same chart, `certController.resources.requests`. -- `external-secrets/external-secrets-webhook`: same chart, `webhook.resources.requests` (memory peak 112Mi). -- `openbao/railiance-platform`: Deployment `railiance-platform` in namespace `openbao`; source not located by a quick search — identify it before changing. -- `playwright/rapp-playwright`: Deployment `playwright`, container `service`, `rapp-playwright/manifests/workload.yaml` (deployed by rapp-playwright). 23.8% of CFS periods throttled at the 1000m limit: review the CPU limit too (RPF-WP-0056 covers admission, not sizing). - -Evidence: resource-control `docs/evidence/2026-10-21-node-allocation-review/2026-10-21-node-budget.md` (+ `.json`, commit `3d9b1bd`). Rule: CPU = max(floor, p95x1.5, peakx0.5) rounded up to 5m; memory request = 7d p95 working set x1.15 (only when the change is at least 64Mi). - -Steps: - -1. Map the pod-aggregate proposal onto the named container(s) in the source file; where a workload has several containers or pods, split by the per-pod figures in `2026-10-21-workload-usage.json` and keep the sum at the proposed value. -2. Prepare the source change as a reviewed commit (no push, no sync) and record the before values here. -3. Apply only inside the batched wave-2 rollout, after founder approval, through the owner's normal deploy path (ArgoCD sync or documented helm/kubectl apply). - -Verification: `kubectl -n get pod -o jsonpath='{..resources}'` shows the new requests; pods Ready with no new restarts or OOMKilled over 24h; for CPU decreases the throttle ratio and request latency stay at or below the 7d baseline; node request total drops by the expected delta. - -Prepared 2026-10-22 (wave-2 worker W3; no apply, cluster read-only). Each -render was diffed against live with `kubectl diff` (Secret objects skipped): - -- `external-secrets` x3 — live source is Helm release `external-secrets` - (rev 8, chart 0.16.1, not Argo; the Argo Application is still a draft). - New `helm/external-secrets-resources.values.yaml` (force-added; `helm/*.yaml` - is gitignored); the draft Application values mirror it. Render = chart 0.16.1 - + `installCRDs: true` + `serviceAccount` (draft values minus the Argo CRD - annotation, which live does not carry) + this file: diff = only the three - Deployments' `resources.requests.{cpu,memory}`. Apply: - `helm upgrade external-secrets external-secrets --repo https://charts.external-secrets.io --version 0.16.1 -n external-secrets --reuse-values -f helm/external-secrets-resources.values.yaml`. - Restarts controller, cert-controller and webhook (brief ExternalSecret - admission/refresh pause; no ingress impact). -- `openbao/railiance-platform` = Deployment `openbao-ui-gateway` (label - part-of railiance-platform), container `nginx`, `helm/openbao-ui-overlay-k8s.yaml`: - CPU request 25m -> 10m. Rendered through `scripts/openbao-ui-overlay-apply.sh` - with apply turned into diff: only `resources.requests.cpu`; ConfigMaps clean. - Apply: `make openbao-overlay-apply` (it always does a rollout restart of the - private UI gateway; OpenBao itself is untouched). -- `databases/forgejo-db` (CNPG, `helm/forgejo-db-cluster.yaml`): requests - 200m/512Mi -> 105m/288Mi, limits 1/1Gi unchanged. `shared_buffers` is not - set (PostgreSQL default 128MB), well below the 288Mi request and the - unchanged 1Gi limit, so memory is lowered as proposed. Diff of the full - `forgejo-db-deploy` set: only `Cluster/forgejo-db` `spec.resources.requests`. - Apply: `make forgejo-db-deploy`. CNPG restarts the single instance: - Forgejo database outage for the restart; do it in the one-at-a-time DB - window with a health check after. -- `playwright/rapp-playwright`: live source is `rapp-playwright/manifests/workload.yaml` - (commit `1557742` there). Request 100m/256Mi -> 65m/336Mi; CPU limit 1 -> 2 - (throttle rule: new limit = max(2x current, 1000m); 2000m is 32x the 7d - peak 62.7m). Server-side diff with field manager `rapp-playwright` (the live - owner): only the three resource keys (NetworkPolicy: generation bump only). - **Drift:** the repo's `make deploy` used the default field manager and would - conflict on these fields; the Makefile now passes `--field-manager=rapp-playwright`. - Apply (local kubectl): `make -C rapp-playwright render IMAGE_REFERENCE=` - then `kubectl apply --server-side --field-manager=rapp-playwright -f rapp-playwright/.rendered/workload.yaml`. - Strategy Recreate: the playwright pod is down for the restart (internal service only). - -Rollback: revert the source commit and re-sync/re-apply; the before values in the table above are the rollback target. Keep before/after `kubectl get -o yaml` captures as the receipt. - -## Applied — 2026-10-22 (RPF-WP-0063-T01) - -Applied under the founder's wave-2 authorization (the-custodian docs/decisions/2026-10-21-cust-wp-0071-delivery-interview.md), after a full-render diff against live that showed resources only. ESO Helm revision 9; openbao UI gateway; forgejo-db restarted at 22:37Z with 105m/288Mi and is healthy (the forge stayed reachable); playwright (rapp-playwright) has a 2-core CPU limit, applied with field manager rapp-playwright. Evidence: the-custodian docs/evidence/2026-10-22-wave2-apply.md. diff --git a/workplans/RPF-WP-0064-isolated-original-openbao-restore.md b/workplans/RPF-WP-0064-isolated-original-openbao-restore.md deleted file mode 100644 index cef42b8..0000000 --- a/workplans/RPF-WP-0064-isolated-original-openbao-restore.md +++ /dev/null @@ -1,808 +0,0 @@ ---- -id: RPF-WP-0064 -type: workplan -title: "Isolated original-OpenBao restore for secrets-engine consumer continuity" -domain: financials -repo: railiance-platform -status: active -flavor: implementation -owner: claude -created: "2026-10-23" -updated: "2026-10-28" -related: - - SECRETS-WP-0015 - - NK-WP-0049 - - RPF-WP-0015 - - RPF-WP-0036 - - RPF-WP-0035 - - RAPP-OPENBAO-WP-0001 -state_hub_workstream_id: "e19808db-52d6-508b-b70c-6775c4f78c4f" ---- - -# RPF-WP-0064 — Isolated original-OpenBao restore for secrets-engine consumer continuity - -Receiving workplan for hub message `41e9af3e` from net-kingdom (case -`secrets15-nk49-readiness`). The founder accepted on 2026-10-23 that -railiance-platform owns this as source preparation. - -NK-WP-0049-T18 (net-kingdom `a09bf20`, verified receipt sha256 `16d72f7c…1651`, -secrets-engine `bfa1b3b`) proved in isolation that the original KeyCape issuer, its -JWKS key and the archived `secrets-engine-openbao` registration are intact. -SECRETS-WP-0015-T04 also needs the original OpenBao, recovered on its own into an -isolated, network-contained target. That recovery uses the original custody and -unseal path. It must show the KV registration -`platform/workloads/keycape/secrets-engine-openbao`, the narrow secrets-engine -JWT role and the operator auth path. There is no production redirect, no fresh -backend standing in for the original and no injected secret. - -Scope of this plan: the restore and its evidence only. The secrets-engine -consumer run (attended in-memory secret source, KeyCape exchange, OpenBao JWT -login, `openbao:admin` refusal, session revocation) is a separate -founder-admitted attempt owned by secrets-engine. This plan prepares its target. - -Why not RPF-WP-0036-T03: that task owns *recurring* backup/restore assurance and -waits on the activity-core scheduler (ACTIVITY-WP-0042). This is a one-off -attended restore with no scheduler, so a separate plan avoids taking on that -dependency. Its evidence can later feed T03 as one isolated restore receipt. -RPF-WP-0015 keeps the live snapshot experiments. Its snapshot receipts authorize -no restore. - -Nothing in this plan claims restore capability or consumer continuity. Every -live step stays `wait` until attended evidence exists. - -## T01 — Source survey and recorded findings - -```task -id: RPF-WP-0064-T01 -status: done -priority: high -state_hub_task_id: "02b16f20-8ab3-547b-b8ac-1334139d3450" -``` - -### Findings (2026-10-23, read-only source survey; no cluster, OpenBao or snapshot access) - -1. **Snapshot capture and location.** The procedure is `docs/openbao.md` §Backup: - `bao operator raft snapshot save` on `openbao-0`, then age encryption before the - snapshot leaves the operator machine. The latest recorded capture is the - attended RPF-WP-0015-T03 hold point on 2026-10-14. That snapshot was encrypted - to the offsite recipient derived from the founder's offline - `railiance-backup.key`, decrypt-verified in memory and uploaded to - `railiance-backups/openbao/` (HTTP 201), and the plaintext was shredded from - tmpfs. An earlier 2026-08-23 drill receipt is at - `reviews/WARDEN-WP-0027-T02-DRILL-20260822-01-openbao-snapshot-receipt.json`. - Receipts are validated against `docs/openbao-reboot-snapshot-receipt.example.json` - (24 h freshness, cluster id, applied index). Those receipts authorize no - restore. -2. **Freshness against the needed objects.** The KV path was corrected to - `platform/workloads/keycape/secrets-engine-openbao` on 2026-10-06, and the JWT - role was proven live on 2026-10-10 (CCR-2026-0038). The 2026-10-14 snapshot - should therefore contain both. This is an inference until the restored - metadata readback confirms it. If they are missing, T07 stops; it does not - repair anything. -3. **Version pin.** Chart `openbao/openbao` `0.28.2` (`Makefile` - `OPENBAO_CHART_VERSION`); image `quay.io/openbao/openbao:2.5.4` - (`helm/openbao-values.yaml`; `docs/openbao.md` still says app `v2.5.3`, which - is stale doc text). The restore target must use the same image, pinned by - digest at rehearsal time. Storage is integrated Raft. -4. **Unseal and custody model in use.** Shamir 2-of-3, unsealed by the founder in - attendance (the 2026-10-14 post-reboot unseal was done outside the transcript). - The transit auto-unseal stanza is still commented out. A Raft snapshot restore - brings back the *original* barrier keyring, so the restored target unseals only - with the original shares. That is the "original unseal path" the request asks - for. Restoring needs a throwaway initialization of the empty target first. Its - throwaway keys and token stay in process memory, are never recorded, and stop - working once `snapshot restore -force` completes. This is not a "fresh backend" - in the request's sense, because no state from it survives. -5. **Role, KV and auth definitions are owned here, not in rapp-openbao.** The - sources are `openbao/auth/secrets-engine-jwt-role.json`, - `openbao/auth/keycape-services-config.json` (mount `keycape-services`), - `docs/contracts/secrets-engine-openbao-jwt-login.yaml`, the operator path - `openbao/auth/netkingdom-oidc-config.json` plus - `netkingdom-platform-admin-role.json` (mount `netkingdom`, role - `platform-admin`), and the workload KV lane (`docs/workload-kv-access-lanes.md`, - KeyCape provisioning window 2026-10-04). rapp-openbao's SCOPE excludes policy, - custody and workload KV lanes. It owns only the package (chart, values, UI - overlay), and neither of its workplans has a restore task. rapp-openbao only - needs to confirm the image/chart pin used by the target. -6. **Isolation constraint found.** `keycape-services` validates JWTs through - `jwks_url: https://kc.coulomb.social/jwks`, and `netkingdom` uses OIDC - discovery at `https://kc.coulomb.social`. A networkless OpenBao can restore, - unseal and read back configuration and metadata. However, a live JWT login - (the consumer run) needs that hostname to resolve inside the contained network - to the *isolated* NK49 KeyCape, with a trusted TLS chain. It must never reach - the production KeyCape. An interactive OIDC operator login is out of scope for - the isolated target. The operator path is proven by restored config and role - readback, plus root-less login refusal where applicable. -7. **Reusable patterns.** These are reused as patterns, not copied: - - Existing in-repo template `docs/openbao-restore-drill-evidence.example.json` - and its validator `make openbao-validate-restore-evidence`. - - net-kingdom `docs/bootstrap-recovery-ceremony.md` and - `tools/bootstrap-recovery/isolated-*.py`: attended, hidden-key, fail-closed - setup with a Docker `--network none` target, a safe receipt and an - independent verifier. - - secrets-engine `docs/founder-admission.md` for the founder SSH-signed - admission with a time window. - -## T02 — Isolated target and adapter design - -```task -id: RPF-WP-0064-T02 -status: done -priority: high -depends_on: [RPF-WP-0064-T01] -state_hub_task_id: "51adc483-5102-5723-ab64-e7a2698befe8" -``` - -Design `scripts/openbao-isolated-restore/` (adapter plus doc). Fixed properties: - -- **Target:** a disposable Docker container on the founder workstation, running - the T01 image digest with an in-container Raft data directory on tmpfs. It is - never on railiance01 or the cluster, and it has no kubeconfig, no host - networking and no published ports. -- **Network containment:** phase A (restore + readback) uses `--network none`. - Phase B (handed to the consumer run) uses a single `--internal` Docker network - that has only the isolated KeyCape from NK49 and this OpenBao. In phase B, - `kc.coulomb.social` resolves to the isolated KeyCape, and egress is checked - to be refused before admission. Phase B is offered, not run, by this plan. -- **Custody inputs**, all founder-attended and never through argv, files in the - repo, env dumps or logs: - - (a) the encrypted snapshot object from `railiance-backups/openbao/`, named by - its ciphertext sha256; - - (b) the offline `railiance-backup.key` age identity, used for decryption - straight into the tmpfs stream; - - (c) 2 of 3 original Shamir shares, entered through a hidden prompt. - The restore needs no root token and no secret value is read. Readback uses a - token created in the restored target by an attended `generate-root`, used in - memory only and revoked before teardown, or uses the restored operator path. - Which of the two is decided in T02 review. -- **Readback (metadata only):** `sys/seal-status`, Raft applied index and cluster - id; the mounts `keycape-services` and `netkingdom` and their configs (issuer - and JWKS URL); role `secrets-engine-openbao` (bound claims and policies compared - to the source JSON hash); `platform/workloads/keycape/secrets-engine-openbao` - KV **metadata** (versions and timestamps, never `data`); and policy names. No - KV `data` read. -- **Timeout/abort/cleanup:** a hard wall-clock limit of 45 minutes from the first - custody input. Any fail-closed check, a missing object, an unexpected network - interface, or the founder pressing abort leads to `docker rm -f`, tmpfs - unmount, shredding of the decrypted stream, and a recorded `aborted` receipt. - Teardown also runs on success, unless phase B was admitted in the same window. - The plaintext snapshot never touches persistent disk. - -### Delivered (2026-10-23) - -`scripts/openbao-isolated-restore/` holds `run-isolated-restore.sh` (the -attended launcher), `preflight.py` (metadata-only, runs before any prompt) and -`restore_adapter.py` (the session plus a reaper). The design document is -`docs/openbao-isolated-restore.md`. Implemented as specified above, with these -recorded choices: - -- **Readback credential: generate-root from the two shares already entered.** - The OTP is decoded in memory, and the credential is revoked and verified dead - before teardown. The operator OIDC path cannot work without a network. The - admission names the choice (`readback_credential`), so the founder confirms it - by signing T05. -- OpenBao 2.5 disables unauthenticated generate-root by default. The target - enables it on its loopback-only listener, inside `--network none`. The image's - VOLUME paths (`/openbao/file`, `/openbao/logs`) are overlaid with tmpfs. That - is the only way to get no Docker volumes, and the first native run - failed closed on exactly this. -- Custody uses hidden TTY prompts. The age identity sits in a sealed memfd - (`/dev/fd/N` for `age`), and the decrypted snapshot sits in a sealed memfd - streamed to `snapshot restore -force /dev/stdin`. Shares are held in wiped - buffers. No custody value appears in argv, env, files or logs. -- The admission namespace is `railiance-openbao-restore-attempt`, with anchor - `/etc/railiance-openbao-restore/allowed_signers`. Original material refuses - if the anchor is writable by the runtime account, or if - `PINNED_IMAGE_DIGEST` (T06) is unset or differs from the admission. -- The 45-minute deadline is enforced in-session (every subprocess timeout) and - by a reaper on parent loss or at hard deadline + 60 s. The reaper removes - the container and writes the `aborted` receipt. -- Phase B is a design stub only (doc §Phase B). - -## T03 — Safe receipt schema and independent verifier - -```task -id: RPF-WP-0064-T03 -status: done -priority: high -depends_on: [RPF-WP-0064-T02] -state_hub_task_id: "69ef4a3b-7b0a-5b7b-b49a-c688f9d19554" -``` - -Extend `docs/openbao-restore-drill-evidence.example.json` into a v2 receipt: -- attempt id and admission hash; -- image digest; -- ciphertext sha256 of the snapshot and the source cluster id and applied index - (from the RPF-WP-0015 receipt); -- restored cluster id and index; -- a network-namespace proof (interfaces and the egress-refusal result); -- presence and source-hash match for each required object; -- result `restored | refused | aborted`; -- the teardown proof; -- `no_secret_material_recorded`. - -Add a verifier that runs separately from the adapter. It rejects placeholders, -unknown fields, missing teardown, `data` fields, and anything shaped like a -token or share. Validate it with adversarial fixtures. - -### Delivered (2026-10-23) - -- The v2 receipt shape is `docs/openbao-restore-receipt-v2.example.json` - (schema `railiance-openbao-restore-receipt/v2`, extends the drill template). - The admission shape is `docs/openbao-restore-admission.example.json`. All - digests are typed `sha256:`, and no key name sits next to a bare hex value. - gitleaks 8.30.1 finds nothing in any new file. -- The independent verifier is - `scripts/openbao-isolated-restore/verify-restore-receipt.py`, run with - `make openbao-verify-restore-receipt`. It does not import the adapter. It - takes a clean checkout at `adapter_commit` for the source-hash comparison, - and accepts `--not-after` and `--expect-result`. -- 17 offline tests in `tests/test_openbao_isolated_restore.py` - (`make openbao-isolated-restore-test`) cover: - - 28 adversarial receipt fixtures: unknown or missing fields, missing or - reaper-only teardown, `data` fields, KV `/data/` paths, - credential/share/hex/age/PEM shapes, placeholders, untyped digests, - missing claims, deadline overrun, unprotected anchor for original material, - result inconsistencies, phase B, source hash/commit mismatch, duplicate keys - and the `--not-after` window; - - admission refusals and signature namespace/byte binding; - - launcher and preflight refusals before any prompt. - -## T04 — Staged rehearsal with synthetic material - -```task -id: RPF-WP-0064-T04 -status: done -priority: high -depends_on: [RPF-WP-0064-T03] -state_hub_task_id: "9c10caca-d772-5608-954e-6e9f78d59a39" -``` - -Run the full adapter against a synthetic OpenBao, initialized on purpose with -generated Shamir shares and seeded with stand-in mounts, role and KV metadata -that mirror the T01 source files. Snapshot it, age-encrypt it to a generated -identity, restore it into the T02 target, unseal it with the synthetic shares, -read it back, and tear it down. Rehearse each abort path (timeout, wrong -ciphertext hash, network interface present, missing object). This step uses no -real material. - -### Rehearsal result (2026-10-23): native, generated material only - -`make openbao-isolated-restore-rehearsal` ran 12 tests, all passing, in about -156 s, against the real `quay.io/openbao/openbao@sha256:436eaf97…c115` image -(the local pull of tag 2.5.4; T06 still confirms the production digest). Every -container ran with `--network none` and tmpfs storage. - -Each run generates its own material: - -- an SSH admission key and anchor; -- an age identity; -- a 3-share, threshold-2 Shamir init; -- root credentials; -- a KV value. - -A source OpenBao is seeded from the real source JSON/HCL shapes. The JWT and -OIDC configs are written with `skip_jwks_validation`, because the source has -no network. That source is snapshotted, age-encrypted, restored through the -adapter into a fresh target, unsealed with shares 1 and 3, read back, and torn -down. Each receipt is checked by the independent verifier, and its text is -checked to hold none of the generated credentials, shares, identity or KV value. - -**Native coverage:** - -- `restored` end to end: sealed after restore, original shares unseal it, - cluster id and index match, the throwaway init is invalidated, the readback - credential is revoked; -- wrong age identity (aborts before any target exists); -- wrong ciphertext hash; -- wrong plaintext hash; -- wrong share (`unseal_refused`, target removed); -- missing JWT role and KV registration (`refused`/`object_missing`, not - repaired); -- source drift (`refused`/`object_mismatch`); -- in-session timeout (`deadline_exceeded`); -- operator Ctrl-C (`operator_abort`); -- parent loss (the reaper removes a real container and writes `parent_lost`); -- the reaper hard deadline (kills the session, `deadline_exceeded`). - -**Mocked (reduced coverage):** only the network-interface abort. A wrapper -around `docker` adds a `bridge` entry to the inspect output of a real -networkless container. Attaching a real network is out of bounds. The adapter -aborts `network_containment_failed`, and the verifier refuses any receipt that -claims such a network. - -**Not covered:** the founder TTY prompt itself, `run-isolated-restore.sh` past -its pre-prompt refusals, and `preflight.py` past admission refusal. All three -need a protected root-owned anchor and the T06 pin. The rehearsal drives the -same `Session` with injected prompts. - -After the run, no `rpf64-*` container, Docker volume or `/dev/shm` workdir -remained, and all generated material was deleted. - -**Retained rehearsal receipt** (synthetic material, outside Git): -`~/rpf64-rehearsal-20261023/rehearsal-receipt.json` on the workstation, -`sha256:5764de67e6ed1a998838ace7843f96b3be0e4da37c8e17b8bb078372245feeae`. It is -bound to `adapter_commit` `a61a07cf158320bf5fe4eb3b8f50dc8ced9c0714`, and its -result is `restored`/`all_required_objects_match`. Re-verify it with: - -```bash -make openbao-verify-restore-receipt RECEIPT= SOURCE_ROOT= -``` - -## T05 — Founder-signed attempt admission - -```task -id: RPF-WP-0064-T05 -status: wait -priority: high -needs_human: true -depends_on: [RPF-WP-0064-T04] -blocking_reason: "Founder signs (ssh-keygen -Y sign, namespace railiance-openbao-restore-attempt; allowed_signers at a root-owned /etc/railiance-openbao-restore/) the v1 admission in docs/openbao-restore-admission.example.json: attempt id, phase A only, window (<=24h) + 2700 s deadline, abort operator, T06 image digest, adapter commit + source_sha256 closure, snapshot ciphertext/plaintext sha256 + bytes + age recipient + source cluster id/applied index + source receipt sha256 (RPF-WP-0015-T03), unseal 2-of-3, readback via generate-root from entered shares. T04 rehearsal done; waits on T06 pin." -state_hub_task_id: "a954eaf9-855b-5458-9f13-c6cfed289934" -``` - -2026-10-23: the founder accepted the three T02 design choices before signing: (1) readback through -a temporary root credential generated from the two entered shares, revoked and checked dead before -teardown; (2) unauthenticated generate-root enabled only on the target's own loopback listener inside -the networkless container; (3) strict match of restored role/policy objects against current source, -so a snapshot older than a later source edit yields `refused`. Signing T05 still confirms them per attempt. - -## T06 — Owner confirmation of package pin - -```task -id: RPF-WP-0064-T06 -status: done -priority: low -depends_on: [RPF-WP-0064-T02] -state_hub_task_id: "cb689628-9a5a-5f2b-a3ec-be37bd6cdb37" -``` - -2026-10-24: done from the live runtime. The founder read `openbao-0` read-only: spec image -`quay.io/openbao/openbao:2.5.4`, container imageID -`docker.io/openbao/openbao@sha256:436eaf9778cad75507ff70ea26ace30dcbe15606e619ac3823495663d7f7c115`, -equal to the T04 rehearsal pull (digests are registry-independent). Written to `PINNED_IMAGE_DIGEST`; -the offline suite (17) passes. This is the current production digest. That the same digest ran at the -2026-10-14 snapshot is not separately proven; the attended run will refuse on any restore incompatibility -rather than repair. rapp-openbao was informed and may object; no rapp-openbao change requested. - -## T07 — Attended original restore (phase A) - -```task -id: RPF-WP-0064-T07 -status: done -priority: high -needs_human: true -depends_on: [RPF-WP-0064-T05, RPF-WP-0064-T06] -state_hub_task_id: "96e88167-c484-527c-84aa-0464dd0f5597" -``` - -2026-10-24 attempt `RPF-WP-0064-T07-20261024-01` (admission `796414c2…a3b9`, adapter `1701a3f`, window -08:00Z–14:00Z): `aborted`, reason `unseal_refused`, after 168 s; the receipt `1a7de4a4…abe6` (kept outside Git) -was independently verified from a clean clone at `1701a3f` as a genuine aborted result. It proved the off-host -ciphertext `8069449c…8c41` (375618 bytes) intact, decryption with the offline age identity, plaintext -`a6312444…590b`, and that the founder's two original Shamir shares unseal the restored barrier -(`unsealed_with_original_shares: true`); networkless containment and full teardown held, with no volumes. -The abort came after unseal: `sys/leader` never reported an active self with an applied index within 60 s, and the -adapter reused the `unseal_refused` reason. The first hypothesis (target raft node id `isolated-restore` versus -production voter `openbao-0`) was disproved by a native rehearsal, which restored and elected a leader anyway. -The adapter now aligns the node id with production, waits up to 180 s, aborts as `leader_not_elected`, and -records bounded typed `sys/leader`/`sys/seal-status` fields and a redacted first error line in -`restored.leader_wait`. Offline 19 and native 12 pass. A new `-02` admission is needed for the new adapter commit. - -2026-10-24 attempt `-02` (admission `33c8aab2…75ab`, adapter `624843e`): two runs refused at the first prompt -(`custody_identity_refused`, clipboard content; the key file and a shape-only paste check were correct), then a -third run reached `aborted`, `leader_not_elected` after 236 s. Its `leader_wait` diagnostics showed the cause: -after the original shares unsealed the node, seal-status read `sealed: true` and `sys/leader` returned API errors -for 180 s. Production declares a file audit device at `/openbao/audit/openbao-audit.log` (rapp-openbao values); -the restored audit table re-enables it on unseal, the target had no writable `/openbao/audit`, and OpenBao sealed -itself again. Both `-02` aborted receipts (`cfb2c2af…`, `4c5cd362…`, `904a9d95…` incl. the refusals) verified -independently from a clean clone at `624843e`. Fix: an in-memory `/openbao/audit` on the target. The rehearsal -source now carries production's file audit device, and a native regression case without that mount reproduces the -reseal (`leader_not_elected`, `sealed: true`), while the full restore passes with it. Offline 19 and native 13 pass. -A `-03` admission is needed for the new adapter commit. - -2026-10-24 attempt `-03` (admission `3ef17fd1…4cc9`, adapter `7084b57`, founder attended): **`restored`, -`all_required_objects_match`** in 46 s. Independently verified from a clean clone at `7084b57`: receipt -`sha256:4e048deedda21aa35ea4ff811e437f9c0e546fbefe4eb8a2025a74bf65e2579d`, retained outside Git on the founder -workstation (mode 0400). Original cluster id `fd28df5d…` matched, applied index 6040604 (not behind 6040594), -leader elected in 2 s, the throwaway init was invalidated, the original shares unsealed, and all nine required -objects were present and equal to the committed sources: auth mounts, secrets mounts, `keycape-services` JWT -config and role `secrets-engine-openbao`, `netkingdom` OIDC config and role `platform-admin`, policy -`secrets-engine-login-self`, and KV metadata `platform/workloads/keycape/secrets-engine-openbao` (no KV data read). -The readback credential was revoked; networkless (`lo` only), no volumes, full teardown. Claims not made: consumer -continuity, live JWT login, phase B network, owner acceptance, recurring restore assurance. -T08 handoff: recorded in secrets-engine SECRETS-WP-0015-T04 and net-kingdom NK-WP-0049-T18 the same day. -RPF-WP-0036-T03 may point to this receipt as a one-off; it is not recurring assurance. - -Run the admitted adapter once. The outcome is a T03 receipt verified -independently. The receipt is kept outside Git and referenced by its hash. A -missing object or failed check is a valid `refused` outcome; it is not repaired -in this attempt. - -## T08 — Bounded evidence handoff - -```task -id: RPF-WP-0064-T08 -status: done -priority: medium -depends_on: [RPF-WP-0064-T07] -state_hub_task_id: "94139cb1-037a-58ef-883c-dde71fe6bfde" -``` - -Hand to secrets-engine (SECRETS-WP-0015-T04) and net-kingdom (NK-WP-0049-T18) -the receipt hash, the verifier result, the image digest and the recovered-object -list. Also hand over the phase B offer terms (contained network with the -isolated KeyCape, same custody, its own admission). The handoff makes no -continuity claim. A pointer to the receipt goes to RPF-WP-0036-T03 as one -isolated restore receipt; it does not count as recurring assurance. - -## Phase B plan and confirmation (2026-10-27, reply to net-kingdom message 5da58a3d) - -Source: net-kingdom `docs/nk49-phase-b-plan.md` (NK-WP-0049-T43); founder decision of 2026-10-27 names -railiance-platform as creator and tearer-down of the single `--internal` network and as keeper of the restored -OpenBao for the admitted window. **Preparation only.** This confirmation authorizes nothing, starts nothing and -creates no network. Every start needs a new founder-signed window; the `-03` admission (`3ef17fd1…4cc9`) and the -T40 window carry nothing over. - -**Confirmed:** railiance-platform can own (a) network create/inspect/egress-refusal proof/removal, and (b) the -phase A restore followed by keeping OpenBao up unsealed on that network for the admitted window, with a phase B -receipt. Subject to the changes and reviews below. - -**What the adapter does today (`7084b57`, attempt -03):** `--network none`, `lo` only, listener -`127.0.0.1:8200` with `tls_disable`, `api_addr`/`cluster_addr` on loopback, generate-root enabled -unauthenticated on that loopback listener, `check_network` requires `none`, receipt schema lists -`phase_b_network` as a not-claimed field, the verifier rejects phase B, teardown always runs (or by reaper), 45 -minute deadline from first custody input, no volumes. - -**What must change (new mode, phase A path stays byte-identical in behaviour):** -1. Admission and preflight: new signed phase B admission naming the network name, subnet, OpenBao alias - (`bao-restored`), admitted container set, window and adapter commit; preflight refuses a pre-existing - network or stale `rpf64-*` objects. -2. Network lifecycle: create `docker network create --internal` with a label naming the attempt; prove - `Internal=true`, no gateway, empty attached set; prove egress refused (public address, non-admitted name) - from a probe container before admission continues; at the end remove the network last and show it gone. The - reaper removes only objects carrying this attempt's label, never a network it did not create. -3. Listener: OpenBao must listen on the internal address (`0.0.0.0` inside the namespace, reachable only via the - internal network), `api_addr` accordingly, `--network --network-alias bao-restored`. Raft - `cluster_addr` stays loopback (single node). -4. `check_network` variant: exactly one named internal network, inspected `Internal=true`, attached set equals - the admitted containers, subnet route and no default route; the `lo`-only check is replaced for this mode. -5. Readback credential: phase A uses generate-root from the entered shares. In phase B the - unauthenticated-generate-root setting must **not** be exposed on the internal listener. Plan: keep it on a - separate loopback-only listener used for readback, or disable it after readback; the credential is revoked and - shown dead before the consumer is admitted to the network. The consumer only ever uses the JWT role. -6. Lifetime: do not tear down after readback; stay up until the founder window ends, a consumer-done signal, or - abort. The 45 minute limit runs from first custody input, so OpenBao starts last among long-lived pieces - (as the plan orders), and the consumer run follows at once. Hard deadline plus reaper stay. -7. Receipt schema v-next, verifier and adversarial fixtures for the phase B receipt (network facts, egress - refusal, revocation before consumer admission, teardown including network removal); no secret values, no KV - data. `phase_b_network` moves from not-claimed to claimed only in that mode. -8. TLS trust (net-kingdom item 4): OpenBao must trust the ISRG Root X1 chain to fetch - `https://kc.coulomb.social/jwks`; verify in the native rehearsal whether the pinned image's CA bundle suffices - and do not assume it. No self-signed substitute and no injected trust file without owner review. -9. Rehearsal: native run with synthetic material on a throwaway internal network plus a stand-in JWKS server; - offline tests extended. - -**Needs review:** all of the above is a new adapter commit and a new pin (T06 owner confirmation); the listener -and generate-root change (item 5) is the security-sensitive point and wants the same review depth as the phase A -adapter; admission schema change; independent verification of the phase B receipt from a clean clone. Network -subnet and container naming must be agreed with net-kingdom so the attached-set check matches. - -**Objections / conditions:** -- OpenBao stays up with an unsealed backend reachable by the consumer for hours versus the 45 minute phase A - bound; the founder must accept the longer exposure explicitly in the window (24 h maximum proposed; I suggest - a much shorter window). -- Ordering: network exists before the isolated KeyCape starts; whichever party starts second must not alter the - attached set. If NetKingdom's KeyCape cannot attach to a pre-created network, the plan fails closed. -- Docker is the only supported runtime here; the consumer runner is attached by the consumer owner and checked - by me by inspection only. -- No live action, no network creation, no custody input until the window, the new admission and pin exist. - -```task -id: RPF-WP-0064-T09 -status: todo -priority: medium -depends_on: [RPF-WP-0064-T08] -state_hub_task_id: "ef4ded2e-af0c-5e3f-8b8c-aad80e8be074" -``` - -Phase B source preparation: implement items 1-9 above as a separate mode, rehearse natively with synthetic -material, obtain owner review and the new pin, then wait for a new founder-signed phase B window (T05-style -admission). No live run is part of this task. - -## Owner answers to NK-WP-0049-T43 (2026-10-27, reply to net-kingdom message 43a545e2) - -Source and value-free evidence only. No docker, kubectl or OpenBao action; no value read. - -**1. `keycape-services` JWT trust fields (presence only).** - -| Field | Committed source (`openbao/auth/keycape-services-config.json`) | Restored original (T07 `-03` receipt) | -| --- | --- | --- | -| `jwks_url` | present | **proven** present and equal to source | -| `jwks_ca_pem` | absent | not proven either way | -| `oidc_discovery_url` | absent | inferred absent | -| `oidc_discovery_ca_pem` | absent | not proven either way | - -- Proven: the source file has three keys only (`bound_issuer`, `jwks_url`, `jwt_supported_algs`). The - `-03` receipt matched the restored config to the source, but `restore_adapter.project()` compares only - keys present in the source file. Extra restored fields are not covered by the receipt. -- Inferred, high confidence: the CCR-2026-0038 applier writes the whole file - (`bao write auth/keycape-services/config @file`, `scripts/service_jwt_lane.py`). A config write replaces the - stored config, so both CA fields were unset when it was applied. OpenBao also refuses `jwks_url` together - with `oidc_discovery_url`, so the proven `jwks_url` excludes a discovery URL. A later out-of-band write is not - excluded by the evidence. The `netkingdom` mount (operator path, not used in phase B) uses - `oidc_discovery_url` and has no `oidc_discovery_ca_pem` in source. -- Consequence: JWKS fetch trust comes from the image's system CA bundle. - -**ISRG Root X1 in the pinned image (`sha256:436eaf97…c115`).** Not proven, and the bundle was not inspected. -Inferred: the production pod used this config, with no CA field, for the live login proof on 2026-10-10. Writing -the config with JWKS validation also needs a TLS fetch. So the production image trusted the chain then served -for `kc.coulomb.social` through its system roots. This assumes the chain is Let's Encrypt, which is not recorded -in this repo, and that the 2026-10-10 image digest equals the T06 pin, which is not proven. -Settle both in the phase B native rehearsal (T09 item 8): run a value-free `/jwks` fetch from the pinned image -against the real `letsencrypt-prod` chain served on the internal network, and record the result, the chain's -root subject and whether a CA field is set. Add a presence-only check of the four fields to the phase B readback. - -**2. Proposed phase B names, so the attached-set check can match.** - -| Item | Live window | Synthetic rehearsal | -| --- | --- | --- | -| Network (`--internal`, IPv4 only, label `railiance.rpf64.attempt=`) | `rpf64-nk49-pb` | `rpf64-nk49-pb-rh` | -| Subnet | `10.249.64.0/28` | `10.249.65.0/28` | -| Restored OpenBao: container, alias, IP | `rpf64-nk49-pb-openbao`, `bao-restored`, `.2` | `rpf64-nk49-pb-rh-openbao`, same alias | -| Isolated KeyCape worker (NetKingdom): container, alias, IP | `nk49-pb-keycape`, `kc.coulomb.social`, `.3` | `nk49-pb-rh-keycape` | -| Consumer runner (secrets-engine): container, IP | `secrets15-pb-consumer`, `.4` | `secrets15-pb-rh-consumer` | -| Egress probe (railiance-platform; removed before KeyCape attaches) | `rpf64-nk49-pb-probe` | `rpf64-nk49-pb-rh-probe` | - -- Admitted attached set after the probe is removed: exactly the three named containers, in the order network, - probe and removal, KeyCape, OpenBao, consumer. -- KeyCape's dependent containers share the worker namespace (`container:`), so they are not attached - separately. -- Fixed IPs (`--ip`) make the attached-set and route checks exact. The subnet avoids Docker's default pools and - k3s `10.42/10.43`; preflight refuses on any overlap with a host route. -- `--internal` still gives the host bridge an address. Whether to add - `com.docker.network.bridge.inhibit_ipv4=true` ("no gateway") is decided in the rehearsal. Until then, the - check is no default route plus refused egress. -- Names are proposals. Each owner's admission binds them. - -**3. Phase A evidence as T15's OpenBao dependency (platform-owner view, builder mode).** -Acceptable. Original material restored in isolation: cluster id matched, applied index not behind, the founder's -original shares unsealed it, and all nine required objects matched source (mounts, both auth configs, both -roles, policy, KV metadata). The receipt was independently verified from a clean clone, with containment and -teardown proven. That is proportionate for a builder-mode estate. - -Named residuals: -- (R1) Config match is a projection over source keys; extra fields such as the CA fields are unproven. -- (R2) No live JWT login against the restored target. That is phase B, T09. -- (R3) The operator OIDC path was checked by config and role readback only, with no interactive login. -- (R4) KV metadata only, by design. Value integrity is not shown. -- (R5) The snapshot dates from 2026-10-14. That the image digest at snapshot time equals the pin is not proven. -- (R6) This was one attended restore, not recurring assurance (RPF-WP-0036-T03). -- (R7) The receipt is a single copy outside Git on the founder workstation, referenced by hash `4e048dee…579d`. -- (R8) Restore depends on the file audit-device mount (found and fixed in `-02`/`-03`). Any future target needs it. - -None of these blocks T15's OpenBao line. R1/R2 close in phase B. This is not owner acceptance of the full T15 -packet or of the other three services. - -## T10 — Break-glass drill: generate-root ceremony and held root-token check (NK-WP-0055-T13) - -```task -id: RPF-WP-0064-T10 -status: done -priority: high -depends_on: [RPF-WP-0064-T08] -state_hub_task_id: "5facf182-91fb-58bc-a2dd-c6724049e70f" -``` - -Source: NetKingdom NK-WP-0055-T13. The ceremony is written in `docs/openbao.md`, section "Break-glass: -OpenBao with SSO down". The founder runs this drill against an **isolated restored OpenBao only, never -production**. KeyCape and Authelia are not used, which simulates SSO being down. - -**Drill mode (new adapter mode; the phase A path keeps its current behaviour).** - -The drill runs as a separate `--mode break-glass-drill` with its own signed admission -(`drill: "break-glass"`, attempt id `RPF-WP-0064-T10-`). It does the phase A restore and unseal, then: - -1. **Held root-token check.** A hidden prompt takes the founder's held initial root token. The drill calls - `auth/token/lookup-self` with it and records only `valid`, `policies`, `ttl == 0`, `expire_time == null` - and `orphan`. It never revokes or otherwise uses the held token. -2. **Fresh ceremony.** A `sys/generate-root/attempt` is started with a server-generated OTP. The founder - enters two shares **again** at hidden prompts, so the unseal shares are not reused silently as in phase A. - The token is decoded in memory and `lookup-self` must show `["root"]`. -3. **Minimal action.** One read-only action: `sys/auth` metadata. -4. **Revoke.** `revoke-self` must return 204, and a follow-up `lookup-self` must return 403. -5. **Cancelled attempt.** Start an attempt and cancel it (`DELETE sys/generate-root/attempt`), so the - cancel step is drilled too. - -**What the adapter must allow.** - -- **The isolated listener.** The adapter keeps `disable_unauthed_generate_root_endpoints = false` on the - `--network none` loopback listener only, as it does today. -- **`ALLOWED_WRITES`.** `sys/generate-root/attempt` needs both PUT and DELETE, and a second - `sys/generate-root/update` sequence must be allowed. -- **`ALLOWED_READS`.** `GET sys/generate-root/attempt` (status) must be added. `auth/token/lookup-self` - must be allowed with the held token and with the generated token. -- **Prompts.** One extra hidden prompt each for the held token and for the two re-entered shares. -- **Deadline.** Everything stays inside the 45 minute deadline, with the same teardown. -- **Unchanged rules.** No network, no KV `/data/` reads, no change to production. - -**Receipt fields (all free of secret values).** The drill writes a receipt v2 extension with: - -- `drill: "break-glass"` -- `held_root_token`: `valid`, `policies`, `ttl_zero` and `expire_time_null` -- `generate_root`: `started`, `shares_entered: 2`, `complete`, `otp_decode_ok`, `policies: ["root"]`, - `minimal_action_path` and `minimal_action_ok` -- `revocation`: `revoke_status: 204` and `post_revoke_lookup: 403` -- `cancel_drill`: `started_then_cancelled` -- `claims_not_made`, which additionally lists: - - `production_root_token_validity`: the result is valid only as of the snapshot. Name the snapshot (the - 2026-10-14 snapshot or a newer RPF-WP-0066 scheduled one) and its plaintext sha256. - - `production_generate_root_availability` - - `sso_down_access_path` - -The verifier gets matching adversarial fixtures that reject token, accessor, OTP, nonce, encoded-token and -share shapes. Offline tests and a native rehearsal use synthetic material and a fake held token. - -**Founder-run SSO-down walk (production, read-only).** Over `k3s-api-railiance01` with the admin kubeconfig, -the founder runs: - -- `kubectl -n openbao get pod openbao-0` -- `bao status` (a seal check) -- a `kubectl port-forward` reachability check - -There is no unseal and no generate-root on production. Record the result in the NK-WP-0055-T13 post-event -review record. - -**Owner decision for rapp-openbao.** The production listener has unauthenticated generate-root disabled -(OpenBao 2.5.4 default). This decision is a precondition for the ceremony to work on production without a -valid token. The choices are: - -- **(a)** an incident-time ConfigMap edit plus a restart -- **(b)** a standing loopback-only listener (`127.0.0.1:8210`) reachable only through `kubectl - port-forward` or `exec` - -**Follow-up (founder decision).** If the held initial root token is valid, revoke it on production after -the ceremony drill succeeds, or record why it is kept. - -**Done when:** - -- the drill mode is implemented and reviewed, with tests -- the native rehearsal passes -- a founder-signed admission exists for the window -- the founder has run the drill and the independent verifier passes on the receipt -- NetKingdom has a value-free post-event review record and the rapp-openbao listener decision - -No live run is part of the source preparation. - -### Implementation (2026-10-28, source only; no live action) - -`--mode break-glass-drill` is in `scripts/openbao-isolated-restore/restore_adapter.py`, with `--mode` in -`preflight.py` and `--break-glass-drill` in `run-isolated-restore.sh`. The phase A path is unchanged. - -- **Admission.** The v1 admission plus `drill: "break-glass"`, `snapshot_label` (lowercase object name - containing its date) and `readback_credential: "generate-root-from-reentered-shares"`. The attempt id is - `RPF-WP-0064-T10-*`. Phase A and drill admissions are disjoint and refuse each other's mode. -- **Allowed surfaces.** One added read: `GET sys/generate-root/attempt`. One added delete: - `ALLOWED_DELETES = {sys/generate-root/attempt}`. The writes `sys/generate-root/attempt` and - `sys/generate-root/update` were already allowed, as were `auth/token/lookup-self` and `sys/auth`. These are - unchanged: `--network none`, the loopback listener, 2700 s, teardown, the reaper and no `/data/` reads. -- **Order.** Phase A restore and unseal. The unseal shares are then wiped. Next come the held-token - `lookup-self`, the generate-root attempt (refused if one is already in progress), the two re-entered shares, - the OTP decode in memory, `lookup-self` = `["root"]`, `GET sys/auth`, `revoke-self` (204: the CLI prints no - body) and `lookup-self` (403). Last, an attempt is started, deleted, and the status shows `started: false`. -- **Held-token outcome.** An invalid held token (403) is a finding, `valid: false`, not an abort. -- **Results.** The result is `drilled` (`break_glass_drill_complete`) or `aborted`. The new abort reasons are - `held_token_refused`, `generate_root_failed`, `minimal_action_failed`, `revocation_failed` and - `cancel_drill_failed`. -- **Receipt.** The receipt adds `drill`, `held_root_token`, `generate_root`, `revocation`, `cancel_drill` and - `claim_limits.production_root_token_validity {as_of_snapshot, snapshot_plaintext_sha256}`. It also adds three - `claims_not_made` entries. `objects` is `[]`. -- **Verifier.** The verifier types every extension field and binds `snapshot_plaintext_sha256` to - `snapshot.plaintext_sha256`. It rejects these keys: `token`, `accessor`, `client_token`, `otp`, `nonce`, - `encoded_*`, `share(s)` and `unseal_keys*`. It also rejects any 20+ character mixed-case alphanumeric run, - which covers tokens, accessors, OTPs and encoded tokens. -- **Tests.** Offline (`python3 -m unittest tests.test_openbao_isolated_restore - tests.test_openbao_break_glass_drill`): 34 pass, 15 of them new in `tests/test_openbao_break_glass_drill.py`. They use a fake OpenBao and adversarial token, accessor, OTP, - nonce, encoded-token and share fixtures. Native (`make openbao-isolated-restore-rehearsal`): 16 pass, 3 of - them new. The new native cases are a synthetic held root token (`drilled`, valid, `["root"]`, ttl 0), a - fake held token (`drilled`, `valid: false`) and a wrong re-entered share (`aborted`, `generate_root_failed`). - The native rehearsal uses local Docker with `--network none`, the pinned 2.5.4 image and generated material. - -### Drill result (2026-10-28) - -Attempt `RPF-WP-0064-T10-20261027-01`, admission `sha256:741fa571…ce7a`, adapter `3727f8b`, founder-run: -**`drilled`** (`break_glass_drill_complete`) in 101 s. Receipt -`sha256:e4591346e99b23e405f1cb69f6f5158dbdb7b5d69b2d1d7709c563a74cc18397` (outside Git, mode 0400) verified -`PASS` in-run and again from a clean clone at `3727f8b`. Held initial root token valid as of the 2026-10-14 -snapshot (root, ttl 0, no expiry, orphan); fresh generate-root complete with re-entered shares, `sys/auth` -read-only action ok, revoke-self 204, post-revoke lookup 403, cancel drilled; teardown complete, no volumes. -The snapshot ciphertext was retrieved again from Storage Share (`/dev/shm` had been cleared) and the -admission was rebuilt from the template with the `-03` receipt's snapshot facts. NetKingdom post-event -record: `net-kingdom/docs/evidence/2026-10-28-king-path-drill.md`. Production SSO-down walk (2026-10-28, founder-run, read-only, static-key tunnel plus admin kubeconfig): -`openbao-0` Running, OpenBao 2.5.4 initialized and unsealed, threshold 2 of 3, HA enabled; break-glass listener -8210 `generate-root` status **200**, main listener 8200 **405**. Root-token disposition: founder decision -`7ce684ec`, escrowed until production entry, then revoked with `scripts/openbao_revoke_initial_root_token.py` -(NK-WP-0055-T16). T10 is done. - -### Founder runbook: drill (isolated restore on the workstation, never production) - -Run each block in your own terminal. Nothing below needs the cluster. Snapshot facts are reused from the -signed T07 `-03` admission (the 2026-10-14 snapshot, plaintext `a6312444…590b`). If a newer RPF-WP-0066 -scheduled snapshot is used instead, put its facts in the admission and its name in `snapshot_label`. - -```bash -# 0. Clean checkout at the reviewed commit; offline tests pass -cd ~/railiance-platform && git pull --ff-only && git status --porcelain # must print nothing -python3 -m unittest tests.test_openbao_isolated_restore tests.test_openbao_break_glass_drill -COMMIT=$(git rev-parse HEAD); echo "$COMMIT" - -# 1. Build the drill admission from the T07 -03 admission (same snapshot facts) -W=/dev/shm/rpf64-t10; mkdir -m 700 -p "$W" -T07_ADM=/path/to/RPF-WP-0064-T07-20261024-03.json # your signed -03 admission -CIPHERTEXT=/path/to/the-2026-10-14-snapshot.age # same file as T07 (8069449c…8c41, 375618 bytes) -LABEL=openbao-raft-20261014... # its object name in railiance-backups/openbao/, lowercase -ID=RPF-WP-0064-T10-$(date -u +%Y%m%d)-01 -NB=$(date -u +%Y-%m-%dT%H:%M:00Z); NA=$(date -u -d '+6 hours' +%Y-%m-%dT%H:%M:00Z) -SRC=$(python3 -c 'import importlib.util as u,json;s=u.spec_from_file_location("a","scripts/openbao-isolated-restore/restore_adapter.py");m=u.module_from_spec(s);s.loader.exec_module(m);print(json.dumps(m.source_digests(m.ROOT)))') -jq --arg id "$ID" --arg nb "$NB" --arg na "$NA" --arg c "$COMMIT" --arg label "$LABEL" --argjson src "$SRC" \ - '.attempt_id=$id | .not_before=$nb | .not_after=$na | .adapter_commit=$c | .source_sha256=$src - | .drill="break-glass" | .snapshot_label=$label | .readback_credential="generate-root-from-reentered-shares"' \ - "$T07_ADM" > "$W/$ID.json" -chmod 600 "$W/$ID.json"; jq '{attempt_id, drill, snapshot_label, not_before, not_after, adapter_commit}' "$W/$ID.json" - -# 2. Sign it (founder key in /etc/railiance-openbao-restore/allowed_signers; fixed namespace) -ssh-keygen -Y sign -f ~/.ssh/ -n railiance-openbao-restore-attempt "$W/$ID.json" -chmod 600 "$W/$ID.json.sig" -ADM_SHA=sha256:$(sha256sum "$W/$ID.json" | cut -d' ' -f1); echo "$ADM_SHA" - -# 3. Run the drill (hidden prompts, in this order: backup age identity; unseal share 1; unseal share 2; -# held initial root token; share 1 AGAIN; share 2 AGAIN). Clear the clipboard after each paste. -mkdir -m 700 -p ~/rpf64-receipts -bash scripts/openbao-isolated-restore/run-isolated-restore.sh --break-glass-drill \ - "$W/$ID.json" "$W/$ID.json.sig" "$ADM_SHA" "$CIPHERTEXT" ~/rpf64-receipts - -# 4. Verify independently from a clean clone at the admitted commit, then show the value-free facts -RECEIPT=$(ls -t ~/rpf64-receipts/rpf64-break-glass-drill-*.json | head -1); chmod 400 "$RECEIPT" -rm -rf /dev/shm/rpf64-verify && git clone -q ~/railiance-platform /dev/shm/rpf64-verify \ - && git -C /dev/shm/rpf64-verify checkout -q "$COMMIT" -python3 /dev/shm/rpf64-verify/scripts/openbao-isolated-restore/verify-restore-receipt.py \ - --receipt "$RECEIPT" --source-root /dev/shm/rpf64-verify --expect-result drilled -jq '{attempt_id, result, reason, elapsed_seconds, restored: {cluster_id_match: .restored.cluster_id_match, - applied_index: .restored.applied_index}, held_root_token, generate_root, revocation, cancel_drill, - claim_limits, claims_not_made, teardown}' "$RECEIPT" -rm -rf "$W" /dev/shm/rpf64-verify -``` - -**Paste back:** - -- the admission sha256 (`$ADM_SHA`) and the commit; -- the verifier line, which is `PASS: drilled receipt sha256:…` or `FAIL: `; -- the `jq` output from step 4. - -Never paste the receipt path contents beyond that, nor any token, share or OTP. A `FAIL` or `aborted` is still -evidence: paste the same lines. The receipt stays outside Git (mode 0400). - -### Founder runbook: production SSO-down walk (read-only) - -The walk uses the static-key `k3s-api-railiance01` tunnel and the admin kubeconfig. Local port 18200 is taken by -a tunnel, so the port-forward maps the break-glass listener 8210 to 18210 and the main listener 8200 to 18201 -(RAPP-OPENBAO-WP-0003, live since 2026-10-28). There is no unseal, no generate-root attempt and no token on -production. - -```bash -bridge status | grep k3s-api-railiance01 # up; restore with: bridge up k3s-api-railiance01 -export KUBECONFIG=~/.kube/config-railiance01 -kubectl -n openbao get pod openbao-0 -o wide -kubectl -n openbao exec openbao-0 -- bao status -format=json \ - | jq '{initialized, sealed, t, n, version, ha_enabled}' # exit 2 only means sealed -kubectl -n openbao port-forward pod/openbao-0 18210:8210 18201:8200 >/dev/null 2>&1 & PF=$!; sleep 3 -curl -sS http://127.0.0.1:18201/v1/sys/seal-status | jq '{initialized, sealed, t, n}' -curl -sS -o /dev/null -w 'break-glass listener generate-root status GET: %{http_code}\n' \ - http://127.0.0.1:18210/v1/sys/generate-root/attempt # expect 200: ceremony available -curl -sS -o /dev/null -w 'main listener generate-root status GET: %{http_code}\n' \ - http://127.0.0.1:18201/v1/sys/generate-root/attempt # expect 405: refused without a token -kill "$PF"; unset KUBECONFIG -``` - -**Paste back** the pod line, the two `jq` outputs and both `generate-root status GET` codes. Record them in the -NK-WP-0055-T13 post-event review record. The GET only reads status and starts nothing. diff --git a/workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md b/workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md deleted file mode 100644 index 5129241..0000000 --- a/workplans/RPF-WP-0065-netkingdom-role-claim-bindings.md +++ /dev/null @@ -1,196 +0,0 @@ ---- -id: RPF-WP-0065 -type: workplan -title: "NetKingdom OpenBao role claim bindings: coding-agent principal_type and platform-admin aal2" -domain: financials -repo: railiance-platform -status: proposed -flavor: implementation -owner: claude -created: "2026-10-24" -updated: "2026-10-27" -related: - - NK-WP-0054 - - RPF-WP-0022 - - RPF-WP-0035 -state_hub_workstream_id: "a042bef1-3370-5731-9f62-a974fa0cb7be" ---- - -# RPF-WP-0065 — Role claim bindings from the NK-WP-0054 review - -Receiving workplan for hub message `8da287e2` from net-kingdom (NK-WP-0054, net-kingdom -`b774927`; standard `canon/standards/service-identity-privilege_v0.1.md` draft-3, -review `history/2026-10-24-security-identity-and-agent-review.md`). These are -proposals from net-kingdom. This workplan is source preparation only; nothing is applied live. - -## Evidence - -**F-1 coding agent.** The token is minted by KeyCape `client_credentials` for client -`codex-railiance-platform` (`docs/coding-agent-openbao-identity.md`, KeyCape -`docs/openbao-service-auth-contract.md`). KeyCape `src/internal/server/oidc/token.go` -(~line 382) hardcodes `principal_type: "service"` and an `assurance` of `aal1`/`client_secret`, -with no `agent` object. So today's real tokens say `service`; IAM Profile v0.3 "Agent Principal -Flow" requires `principal_type: agent` plus `agent.{id,mode}`. Flipping the role to `agent` -alone would reject every real token. The role is a migration target: no client secret is -delivered and no JWT exchange is established (docs section "Source clarification — 2026-10-11"); -the operational identity is the AppRole. - -**aal2 on the platform-admin path.** Humans log in via client `openbao-admin` (authorization -code, public PKCE). KeyCape `token.go` sets `claims["assurance"]` for that path with -`level: aal2` only when MFA was verified in that authorization. KeyCape -`docs/secrets-engine-openbao-owner-packet.md` states the same and recommends the binding. -Live evidence: KeyCape `docs/evidence/2026-10-07-secrets-engine-openbao-issuance.json` -records the founder's OIDC login through `openbao-admin` under bound `/assurance/level aal2` -(role `credential-broker-...` family, same client and mount). Conclusion: verified from source and -prior live evidence; one residual risk is that this exact role has not been exercised with the claim, -so the rollout below gates the apply on a machine-checked aal2 login and keeps a fresh-login -rollback plus the break-glass path. - -## Tasks - -```task -id: RPF-WP-0065-T01 -status: done -priority: high -state_hub_task_id: "1e8f9164-dc8e-5f75-8fb8-58182092347a" -``` -**Coding-agent transition (option a).** `coding-agent-jwt-role.json` now binds -`principal_type` to `["service", "agent"]` (OpenBao list = any-of). Sub, audience, tenant, roles, -TTL 15m, 8 uses, no default policy are unchanged. Doc table updated. -Exception record (compensating controls while `service` is accepted): the exact `sub` -`service:codex:railiance-platform`, audience, tenant and `coding-agent` role stay bound; 15-minute, -eight-use token without default policy (inside the SI-14 60-minute ceiling); `agent-high-risk-boundary` -deny wins over the issue-core read policy. Removal condition: drop `"service"` once -(1) KeyCape emits `principal_type: agent` with `agent.id` and `agent.mode` for this client, -(2) a value-safe login with such a token succeeded, and (3) the AppRole path is no longer the -standing identity. At removal also bind `/agent/mode` to the agreed mode(s). Review by 2026-12-31. - -```task -id: RPF-WP-0065-T02 -status: done -priority: high -state_hub_task_id: "67b76351-eb4c-5236-af9f-c5c9eca4d862" -``` -**platform-admin aal2 (founder decision 2026-10-24).** `netkingdom-platform-admin-role.json` -now binds `groups: [net-kingdom-admins]` and `"/assurance/level": "aal2"`, as the credential-broker -role does. Closes exception SI-X4 once applied. - -```task -id: RPF-WP-0065-T03 -status: done -priority: medium -state_hub_task_id: "b7bfdb19-a806-589b-b287-fb3abffc2e78" -``` -**Tests.** `tests/test_netkingdom_role_files.py` (new) and the bound-claims assertion in -`tests/test_openbao_platform_admin_callback_prune.py` now follow the declaration: -`python3 -m pytest -q tests/test_netkingdom_role_files.py tests/test_openbao_platform_admin_callback_prune.py`. - -```task -id: RPF-WP-0065-T04 -status: done -priority: high -state_hub_task_id: "abd78db2-03ad-5942-a9ef-12bbada54b28" -``` -**Founder live step: apply platform-admin aal2 (needs the founder).** See runbook below, rebuilt - -2026-10-27, founder-run through the warden lane with `scripts/openbao-platform-admin-aal2.sh`: prepare (22:19Z, role backup plus `/assurance/level` claim mapping), apply (22:21Z, gate passed; `bound_claims` read back as `{"groups":["net-kingdom-admins"],"/assurance/level":"aal2"}`), verify (22:21Z, fresh MFA login). Value-free receipts at `~/.local/state/railiance/rpf-wp-0065-t04/` on the founder workstation. Two repeated `prepare` runs refused on the existing receipt path before contacting OpenBao. SI-X4 closed in net-kingdom. -2026-10-27 for the silent warden lane (`scripts/openbao-platform-admin-aal2.sh`). The declaration -now also maps `/assurance/level` to token metadata `assurance_level` for the pre-apply gate. - -```task -id: RPF-WP-0065-T05 -status: wait -priority: medium -state_hub_task_id: "e0e8d3f5-24c2-5106-a2d7-3ebe0845f840" -``` -**Coding-agent: issuer first, then role.** Blocked on the KeyCape owner (see rollout order). -Then the founder applies the role and later removes `service`. - -## Runbook for the founder (T04) - -Rebuilt 2026-10-27 for the warden lane. `warden access openbao-platform-admin-login --exec -- ` -runs one reviewed command after one OIDC/MFA login, fails closed on any child output (even -whitespace) and self-revokes the token on every path. There is no interactive or standing session, -so every step below is its own login, every child is silent, and rollback never assumes an open -terminal. The previous runbook (bao read/write printing output, rollback from a still-open session) -cannot work in this lane and is retired; the founder's `--exec -- bash` attempt changed nothing. - -The child is `scripts/openbao-platform-admin-aal2.sh` (Python: `scripts/openbao_platform_admin_aal2.py`, -tests `tests/test_openbao_platform_admin_aal2.py`). It reuses the role read/payload/write helpers -of `openbao_operator_loopback_callback.py`, prints nothing, writes one `0600` receipt per run -(refuses an existing path before touching OpenBao) and exits non-zero on any refusal or mismatch. -Receipts hold role configuration and the session's policy set, role name and assurance level only; -never a token, accessor, email or username. `scripts/openbao-platform-admin-check.sh` remains the -read-only policy/self-revocation check; it does not compare bound claims, so it is optional here. - -**Why three logins.** OpenBao token metadata carries no assurance level today, so the only -value-free proof that the founder's `admin` login is `aal2` needs a claim mapping first: -`prepare` backs up the live role and adds only `claim_mappings["/assurance/level"] = "assurance_level"` -(no new binding, nobody is locked out). `apply` then refuses unless *its own* login's token metadata -shows `role=platform-admin` and `assurance_level=aal2`, and only then writes the declared role and -checks the readback, including `bound_claims == {"groups":["net-kingdom-admins"],"/assurance/level":"aal2"}`. -`verify` proves a fresh login is accepted under the new role. In parallel, the KeyCape sign-in -diagnosis page for `openbao-admin` (NK-WP-0051-T02) shows `assurance` for each login: it must read -`aal2` at steps 2 and 3. A reused KeyCape/Authelia session can be `aal1` even after earlier MFA; if -the page shows `aal1`, step 2 refuses with `gate_login_not_aal2` and nothing changes; report to -KeyCape and do not pin MFA on the client without its own go. - -Preconditions: workstation, `openbao-ui-railiance01` tunnel up (`bridge status`), browser opener -working, you are `admin` in `net-kingdom-admins` and complete the one-time code at every login. - -```bash -set +x; umask 077; unset BAO_TOKEN VAULT_TOKEN -cd ~/railiance-platform -python3 -m pytest -q tests/test_openbao_platform_admin_aal2.py tests/test_netkingdom_role_files.py -R=~/.local/state/railiance/rpf-wp-0065-t04; mkdir -p -m 700 "$R" -S="$PWD/scripts/openbao-platform-admin-aal2.sh" - -# 1 back up the live role and add the assurance mapping (no new restriction) -warden access openbao-platform-admin-login --exec -- "$S" prepare --receipt "$R/1-prepare.json" -# 2 gate + apply: refuses unless this login is aal2 (diagnosis page must show aal2) -warden access openbao-platform-admin-login --exec -- "$S" apply --receipt "$R/2-apply.json" -# 3 fresh login under the new role: aal2, platform-admin + operator-custody, role == declaration -warden access openbao-platform-admin-login --exec -- "$S" verify --receipt "$R/3-verify.json" - -jq -r '.mode + " " + .status' "$R"/*.json # prepare prepared / apply applied / verify verified -``` - -Each `warden` call must exit 0. On a non-zero exit read only the receipt's `status` (`jq -r .status`) -before anything else; codes 11-14 do not prove that nothing changed. Step 1 or 2 refused: the role -has at most the harmless mapping; fix the cause and rerun with a new receipt name. - -**Rollback A, login still works** (step 3 failed with warden exit 11, e.g. `role_not_declared`, -`token_policies_missing`): restore the backed-up role with a fresh lane login. - -```bash -warden access openbao-platform-admin-login --exec -- \ - "$S" rollback --backup "$R/1-prepare.json" --receipt "$R/4-rollback.json" -jq -r .status "$R/4-rollback.json" # rolled_back -``` - -**Rollback B, login itself is refused** (step 3 exits 10 `login_failed`, or OpenBao names the bound -claim): no OpenBao session can be obtained, so use the break-glass operator path of -`docs/openbao.md` (`kubectl exec` with the offline-escrowed initial token, "Bootstrap Ceremony" -step 7). The role body is derived locally from the step 1 backup and contains no secret. The -break-glass token is typed at a hidden prompt inside the pod, never in argv, history, a file or a receipt. - -```bash -"$S" rollback-body --backup "$R/1-prepare.json" --out "$R/rollback-body.json" -kubectl exec -i -n openbao openbao-0 -- sh -c 'cat > /tmp/pa-rollback.json' < "$R/rollback-body.json" -kubectl exec -it -n openbao openbao-0 -- sh -c 'printf "break-glass token: "; stty -echo; read -r BAO_TOKEN; stty echo; echo; export BAO_TOKEN; bao write auth/netkingdom/role/platform-admin @/tmp/pa-rollback.json >/dev/null && echo rolled-back; rm -f /tmp/pa-rollback.json' -``` - -Then confirm logins work with a lane `verify` under a new receipt name: warden exit 11 with -`role_not_declared` (not exit 10) is the expected result after rollback. If the initial token was -revoked rather than escrowed, this is a break-glass event under `docs/openbao.md` -(custody owners, recovery shares): stop and raise it, do not improvise. Record the outcome as a -progress event; receipts stay in `$R`, never in Git. - -## Rollout order for the coding-agent role (T05) - -1. KeyCape owner changes the issuer: `client_credentials` tokens for `codex-railiance-platform` carry - `principal_type: agent` and `agent.id`, `agent.mode` (IAM v0.3). This must come first. -2. Apply the transitional role (accepts both), value-safe check per `docs/coding-agent-openbao-identity.md`: - `bao write auth/netkingdom/role/coding-agent @openbao/auth/coding-agent-jwt-role.json`. -3. After a successful agent-token login, remove `"service"` and add the `/agent/mode` binding, apply, re-verify. -Rollback: `bao delete auth/netkingdom/role/coding-agent` (AppRole remains the operational identity). diff --git a/workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md b/workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md deleted file mode 100644 index 1757123..0000000 --- a/workplans/RPF-WP-0066-openbao-durable-audit-snapshots-conformance.md +++ /dev/null @@ -1,201 +0,0 @@ ---- -id: RPF-WP-0066 -type: workplan -title: "OpenBao durable audit shipping, scheduled Raft snapshots and live conformance receipt" -domain: financials -repo: railiance-platform -status: proposed -flavor: implementation -owner: claude -created: "2026-10-27" -updated: "2026-10-27" -related: - - NK-WP-0055 - - RPF-WP-0036 - - RPF-WP-0064 - - AUDIT-WP-0013 - - AUDIT-WP-0015 - - ACTIVITY-WP-0042 -state_hub_workstream_id: "f1eadbc2-48fd-57de-ab90-e3c24d6e20a5" ---- - -# RPF-WP-0066 — OpenBao durable audit, scheduled snapshots, live conformance - -Receiving workplan for NetKingdom NK-WP-0055-T11 (audit sink) and the OpenBao part of -NK-WP-0055-T12 (live conformance receipts). Source: net-kingdom -`docs/evidence/2026-10-27-production-readiness-ledger.md` sections 4 (OpenBao) and 9 -(Audit sink). - -## Evidence - -- The declarative `audit "file" "file"` stanza in `helm/openbao-values.yaml` writes - `/openbao/audit/openbao-audit.log` on the audit PVC. `docs/openbao.md` states the PVC - "is not a durable audit sink". Durable shipping was deferred when RAIL-PL-WP-0002 - closed, and no live workplan owned it until now. -- `make openbao-reboot-snapshot` is an attended Raft snapshot -> age -> verify -> offsite - path with a receipt. RPF-WP-0064-T07/T08 proved one isolated original restore. Neither - is a schedule. RPF-WP-0036-T03 owns recurring backup and restore evidence for all S3 - data services and waits on ACTIVITY-WP-0042 (bounded scheduler). -- `make openbao-verify-authenticated` already runs non-mutating checks of audit devices, - `platform/`, and the `kubernetes/`, `netkingdom/`, `keycape/` auth mounts. It does not - compare the live state to `openbao/policies/` and does not write a dated receipt. - -## Constraints - -- Source preparation only. Every live step (Helm rollout, enabling an audit device, - installing a schedule, the drills) is founder-run, with a guarded helper and the exact - command, and keeps its own execution gate. -- Audit records leave OpenBao with its default HMAC-SHA256 hashing of secret-bearing - request and response fields. No `log_raw`, and no `hmac_accessor=false`. Any - `audit_non_hmac_*_keys` tuning on a mount needs a separate reviewed change. The shipper - never logs record bodies and never holds an OpenBao token. -- OpenBao blocks requests when every enabled audit device fails. The file device stays - enabled as the local, blocking device. Shipping must be asynchronous, so a sink - outage cannot stop OpenBao. - -## Choose the OpenBao audit forwarding design - -```task -id: RPF-WP-0066-T01 -status: todo -priority: high -state_hub_task_id: "1f2cced8-7851-51bf-9f73-b9504fd69eff" -``` - -Compare the options and record the decision in `docs/openbao.md` (or an ADR if it -changes the platform boundary): - -1. **File device plus a tailing sidecar shipper (preferred candidate).** A shipper such as - Vector or Fluent Bit in the `openbao` pod reads the PVC log, keeps a disk checkpoint, - maps records to the audit-core envelope and posts them to the audit-core ingest API - as a registered sender. It retries with backoff from the PVC backlog. The PVC is the - buffer; size it and set a rotation that cannot drop unshipped lines. Rotation would - use SIGHUP to reopen the log. -2. **Socket device to an in-cluster collector.** This has lower latency, but a socket - failure counts against the "all devices failed" rule. It is acceptable only as a - second, non-sole device, and its `fallback` semantics need checking. -3. **Separate durable store** (an encrypted object store with object lock). Use this only - if audit-core cannot accept OpenBao volume. It then needs its own retention and its - own restore drill. - -**Done when:** one option is chosen with volume and backlog sizing, and its failure modes -are recorded: sink down, shipper down, PVC full, rotation. - -## Agree the audit-core sender contract and the correlation id - -```task -id: RPF-WP-0066-T02 -status: wait -priority: high -blocked_on: message-from:audit-core -state_hub_task_id: "96f632d0-4a0d-5b1f-9857-4ddee2a33b35" -``` - -Needed from audit-core (AUDIT-WP-0013 sender admission): - -- an OpenBao sender registration and its credential route (OpenBao-issued, no secret - in Git); -- an envelope mapping for OpenBao `request`/`response` records, with HMAC'd fields - passed as opaque; -- idempotency and dedup keys (OpenBao `request.id` plus record type); -- the expected volume and retention class; -- an acknowledgement and backpressure contract. - -Correlation: the NetKingdom-owned cross-stream correlation id (NK-WP-0055-T11) has to -reach OpenBao requests. Set a request header such as `X-Correlation-Id`. If the -listener allows it, record it through the auth mount `audit_non_hmac_request_keys` / -`passthrough_request_headers` or the listener header settings. Otherwise derive it from -the OIDC `jti`/session claim in entity metadata. Verify which of these OpenBao records -unhashed without exposing a secret. - -**Done when:** a written contract exists in both repos, and NetKingdom's correlation-id -definition is cited. - -## Implement the shipper source and its tests - -```task -id: RPF-WP-0066-T03 -status: todo -priority: high -depends_on: [RPF-WP-0066-T01, RPF-WP-0066-T02] -state_hub_task_id: "8dd8ee8a-e94f-5af8-8d80-40f517c230d0" -``` - -Add the shipper configuration to `helm/openbao-values.yaml`, along with the checkpoint -volume, log rotation, and the mapping and redaction config. Test against synthetic -fixtures: - -- records stay HMAC'd end to end; -- no token or accessor appears in the clear; -- dedup holds on replay; -- the backlog drains after a simulated sink outage; -- correlation-id propagation works. - -Prepare a founder-run rollout helper with a post-rollout check: the audit-core record -count rises for a probe request carrying a known correlation id. - -**Done when:** tests pass and the helper and its exact command are documented. The live -rollout is a separate attended step. - -## Set retention and drill audit restore - -```task -id: RPF-WP-0066-T04 -status: wait -priority: medium -depends_on: [RPF-WP-0066-T03] -state_hub_task_id: "6a87f79d-eddd-5393-b4f0-fc0633557b58" -``` - -Record retention per store: the PVC (short, buffer only), audit-core (its retention -class), and any separate archive. The recurring audit-core restore drill belongs to -audit-core (NK-WP-0055-T11 / AUDIT-WP-0015-T01). This task supplies the OpenBao part: -after a restore, an OpenBao record found by correlation id and `request.id` verifies -against the hash chain. If option 3 is chosen in T01, this repo owns the full restore -drill of that store. - -**Done when:** a dated restore receipt shows OpenBao records recovered and verified. - -## Schedule OpenBao Raft snapshots - -```task -id: RPF-WP-0066-T05 -status: wait -priority: high -depends_on: [RPF-WP-0036-T03, ACTIVITY-WP-0042] -state_hub_task_id: "6098801d-f776-50d8-a4ac-c192b9ceee49" -``` - -This is the OpenBao slice of RPF-WP-0036-T03, split out so NK-WP-0055 can track it. -Turn `scripts/openbao-reboot-snapshot.py` (snapshot -> age -> verify -> offsite -> -receipt) into an unattended, bounded operation run by the accepted scheduler -(ACTIVITY-WP-0042). It needs: - -- a snapshot-only OpenBao policy, `sys/storage/raft/snapshot` read and nothing more; -- a scoped credential route; -- a cadence and expiry that RPF-WP-0036-T02's freshness signal can check; -- a stale or failed run that reaches a named operator. - -A periodic isolated restore, reusing the RPF-WP-0064 isolated-restore path, is the -recurring proof. A dated snapshot alone does not pass. - -**Done when:** the schedule is installed through the execution owner, and at least two -consecutive scheduled snapshot receipts and one scheduled isolated restore receipt exist. - -## Record a dated live check of auth methods and mounts against committed policies - -```task -id: RPF-WP-0066-T06 -status: todo -priority: medium -state_hub_task_id: "797d6df8-9dcf-5252-b91d-909cd12ce202" -``` - -Extend `make openbao-verify-authenticated`, or add a sibling read-only target, to emit a -dated, non-secret receipt. The receipt diffs `bao auth list`, `bao secrets list`, -`bao audit list` and `bao policy list`, plus per-policy content hashes, against -`openbao/policies/*.hcl` and the expected mounts list. It lists unexpected mounts, -missing mounts and policy drift. The founder runs it; the receipt is committed under -`docs/evidence/`. - -**Done when:** one dated receipt is committed and cited in NK-WP-0055-T12. diff --git a/workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md b/workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md deleted file mode 100644 index b10f609..0000000 --- a/workplans/RPF-WP-0067-netkingdom-identity-receipt-and-metadata-answers.md +++ /dev/null @@ -1,150 +0,0 @@ ---- -id: RPF-WP-0067 -type: workplan -title: "NetKingdom owner answers: execution-attribution receipt emitter and service-identity metadata export" -domain: financials -repo: railiance-platform -status: active -flavor: implementation -owner: claude -created: "2026-10-28" -updated: "2026-10-28" -related: - - NK-WP-0040 - - NK-WP-0054 - - AUDIT-WP-0014 -state_hub_workstream_id: "b69630d9-5efb-511b-aa0d-346a372a113a" ---- - -# RPF-WP-0067 — NetKingdom owner answers (NK-WP-0040-T02, NK-WP-0054-T04) - -Receiving workplan for two NetKingdom briefs (State Hub messages -`521e3dba-59db-46a8-ae7a-fa8ae2a4c9a7` and `f5d366f5-853f-4d3c-9570-a3275d27df25`). -Answers are from railiance-platform source only. No live, cluster or OpenBao -action was taken; no Secret value was read. - -## Answer the execution-attribution receipt emitter questions - -```task -id: RPF-WP-0067-T01 -status: done -priority: high -state_hub_task_id: "38db40fd-e42b-5a9b-8b67-9da593acafd2" -``` - -Question source: net-kingdom `canon/standards/playbook-capability-contract_v0.2.md` -§"Execution Attribution Receipt (proposed)"; audit-core admission requirements in -audit-core `docs/cross-repo-evidence-admission.md` §"Execution-attribution receipts — -AUDIT-WP-0014". Emission is not implemented here. - -1. **Emitting component — not confirmable from this repository; none exists.** - railiance-platform contains no Railiance playbook runner or executor and no - `PlaybookCapability` declaration (search of `scripts/`, `tools/`, `lib/`, - `helm/`, `argocd/`, `Makefile`: the only `playbook` hits are unrelated). The - contract's reference declaration is - `railiance-infra/capabilities/playbooks/railiance-infra.bootstrap-host.yaml` - (S1 Ansible; contract v0.2 lines 375-385), and the contract itself says - "a separate playbook runner" executes (line 261). railiance-platform is the - S3 platform-services layer; the emitter must be named by the repository that - owns the runner (railiance-infra for S1; S2/S4 owners for theirs). This repo - should not be recorded as the emitter. -2. **Wire binding — what this repo can state.** The only audit-core sender - binding in this repo is the existing event-sender pattern, which a future - emitter should reuse rather than invent: - - transport: HTTP `POST /v1/events` to in-cluster - `http://audit-core.audit-core.svc.cluster.local:8080` - (`scripts/native_factory_acceptance.py` line 48, - `scripts/native_factory_probe/producer.py` lines 30-36, 127); - - idempotency: `Idempotency-Key` header equal to the envelope `id`, with - `correlation_id` carrying the run id (`producer.py` lines 33-34, 130, 174-176); - - sender identity: a per-sender bearer from the audit-core sender registry at - OpenBao `platform/workloads/audit-core/senders`, projected by ESO to a - mounted credential file (`producer.py` line 31; - `scripts/audit_core_synthetic_sender_lane.py` docstring); - the envelope `source` must equal the sender name, tenant is declared; - - signing: none at the message level. Authenticity is the bearer plus - audit-core's own chaining; no detached or per-receipt signature exists in - this repo. - A playbook-runner sender identity does not exist in the registry; adding one - is a CCR through this repo once the runner owner names the sender. -3. **When emitted — not confirmable.** No emitter exists, so no success/failure/ - partial guarantee exists. Recommendation for the runner owner: emit on every - terminal outcome (success, failure, partial/aborted) with a stable `run_id` - as the idempotency key, and record absence of a receipt as an explicit gap. -4. **Fields a platform-side emitter could not populate truthfully today:** - `initiator.iss`/`initiator.sub` and `delegation` (runs are started by an - operator shell or ssh; no IAM Profile token is presented to a runner); - `executing_workload` (Tenancy Posture `workload_identity` is still proposed); - `artifact_digest` for Ansible runs (no packaged artifact; would need a - computed tree digest); `decision_refs` (no machine link from CCR/approval - records to a run); `time_interval.bound` (no established skew bound, so it - must stay absent). `runtime_principal`, `tenant`, `environment`, `run_id`, - `source_commit`, `target`, `action`, `time_interval.start/end/clock_source` - are populatable by a runner that records them. - -Open: runner owner (railiance-infra and any S2/S4 runner) names the emitter; -NetKingdom ships the receipt schema; audit-core implements AUDIT-WP-0014-T02. - -## Provide value-safe OpenBao and PostgreSQL identity metadata - -```task -id: RPF-WP-0067-T02 -status: done -priority: high -state_hub_task_id: "5b35310f-3aaa-5657-8983-d1279ffb55bd" -``` - -`scripts/service_identity_metadata_export.py` (tests: -`tests/test_service_identity_metadata_export.py`, `make service-identity-metadata-tests`). -Output schema `railiance-platform.service-identity-metadata.v1`. OpenBao: auth -mounts, allowlisted role fields (bound claims/audiences/subject, service-account -bindings, token policies, TTLs in seconds, token type/uses), policy rules and -their SHA-256, and per-role `effective_rules` (exact paths merged, deny wins). -PostgreSQL: `pg_roles` attributes, memberships and database owners. Role-ids, -secret-ids, KV data, `pg_authid` and passwords are never read; secret-shaped -output refuses the export and reports only a count. - -Proven from source for net-kingdom `tools/service-identity/declarations/openbao.yaml`: -- `fluid-telegram-operators` grants: `openbao/auth/fluid-telegram-hall-of-helix-oidc-role.json` - lines 12-20 and `openbao/policies/workload-kv-fluid-telegram-hall-of-helix-operator.hcl` - lines 1-16 — read/create/update on four exact KV data entries under - `platform/workloads/coulomb/fluid-telegram/hall-of-helix/`, no metadata, list or - delete, 15-minute token, 30-minute explicit max. -- `coding-agent` grants: `openbao/auth/coding-agent-jwt-role.json` lines 14-19 and - `openbao/auth/coding-agent-approle.json` lines 5-11 attach - `agent-high-risk-boundary` and `workload-kv-read-issue-core-runtime`; the boundary - denies the same data path the second policy grants - (`agent-high-risk-boundary.hcl` line 25 vs `workload-kv-read-issue-core-runtime.hcl` - line 1), and deny wins. Effective: no KV data read; metadata read only where the boundary allows it; token self-lookup/revoke and `sys/capabilities-self`. -- `coding-agent` `agent.mode`: not provable. The JWT role binds no agent-mode - claim (`coding-agent-jwt-role.json` lines 8-13) and accepts `principal_type` - `service` or `agent` (line 10). The mode is issued by KeyCape (KEY-WP-0043). - -## Run the live export (founder) - -```task -id: RPF-WP-0067-T03 -status: wait -priority: medium -blocking_reason: "Live read-only export needs the founder's OpenBao platform-admin login and cluster access." -state_hub_task_id: "23ea4894-120a-556e-bf24-4b01cb2ddcfb" -``` - -```bash -cd ~/railiance-platform -bridge up openbao-ui-railiance01 -BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \ - warden access openbao-platform-admin-login --exec -- \ - python3 scripts/service_identity_metadata_export.py \ - --output ~/nk-wp-0054-t04-openbao-identity-metadata.json openbao - -SQL="$(python3 scripts/service_identity_metadata_export.py postgresql-sql)" -P=$(ssh railiance01 kubectl -n databases get cluster net-kingdom-pg -o jsonpath='{.status.currentPrimary}') -ssh railiance01 "kubectl -n databases exec $P -c postgres -- psql -U postgres -d postgres -AtX -c $(printf %q "$SQL")" \ - | python3 scripts/service_identity_metadata_export.py \ - --output ~/nk-wp-0054-t04-postgresql-identity-metadata.json postgresql --cluster net-kingdom-pg -``` - -Exit 0 writes the file; exit 3 means a secret-shaped value was found and nothing -was written; exit 2 is a read failure. Return both JSON files to NetKingdom; they -hold no secret values. diff --git a/workplans/RPF-WP-0068-email-connect-platform-smtp.md b/workplans/RPF-WP-0068-email-connect-platform-smtp.md deleted file mode 100644 index 233d107..0000000 --- a/workplans/RPF-WP-0068-email-connect-platform-smtp.md +++ /dev/null @@ -1,116 +0,0 @@ ---- -id: RPF-WP-0068 -type: workplan -title: "Admit the email-connect platform mailbox consumer" -domain: financials -repo: railiance-platform -status: active -owner: codex -topic_slug: railiance -created: "2026-10-31" -updated: "2026-10-31" -state_hub_workstream_id: "c042789b-4d7f-5c55-8d49-39dd16500f41" ---- - -Owner custody dependency of CSOC-WP-0015-T02. Existing platform mailbox readers -remain telemetry and Authelia. User decision9392ef52 names platform-root with -Bernd as the human reply/bounce operator; it does not admit this additional reader. -CCR-2026-0056 is proposed. Preserve its approval and binding gates. - -## T01 — Prepare the fixed reader contract and guarded owner helper - -```task -id: RPF-WP-0068-T01 -status: done -priority: high -state_hub_task_id: "feb33534-d87b-531d-9e80-e9026d3fbe08" -``` - -CCR-2026-0056, exact read-only policy and email_connect_smtp_delivery.py are -prepared. Twenty-nine SMTP tests pass, including fifteen new admission tests. -The offline proposal binds functional CCR scope, seven source files and the -observed OpenBao cluster identity. Execution requires approved CCR, confirmed -native service-account claims and exact digest. The silent helper preserves the -mailbox and existing consumer metadata, refuses drift and records append-only -private checkpoints before admission. No live owner call was executed. -Evidence: docs/evidence/email-connect-smtp-admission-preparation-2026-10-31.json. - -## T02 — Prepare supervised projection and native binding verification - -```task -id: RPF-WP-0068-T02 -status: done -priority: high -state_hub_task_id: "de0ec1f9-cc11-593f-88c5-5a3972969f01" -``` - -Complete the attended foundation/projection controller for the consumer's three -server-dry-run-validated resources: dedicated service account, namespace store -and password-only projection. Bind current cluster/namespace identities and -resource preimages, journal exact progress, and prove the native SA/namespace/ -audience claims before owner confirm-binding. Prepare positive workload login, -other-consumer/wrong-audience denial, exact capabilities, revocation and Ready -readback after admission without printing values or reading Kubernetes Secrets. -Use coulomb-social/docs/release/platform-mail-sender-2026-10-31/candidate.json. -Do not deploy the application sender here or reuse an existing consumer's grant. - -Preparation completed 2026-10-31: email_connect_smtp_foundation.py binds the -server-defaulted three-object bundle and pinned primary/namespace/application -identities. It performs native TokenReview before canonical binding confirmation, -guarded owner admission, positive/negative workload login with token revocation, -exclusive projection creation and Ready/ownership checks. PartialObjectMetadata -queries avoid Secret values; fresh native query confirms target absent. All41 -SMTP tests pass (12 foundation cases). No live creation/admission executed. -Native execution results belong to T03/T04, not this preparation completion. -Exact packet: docs/email-connect-smtp-foundation-review-2026-10-31.md. - -## T03 — Obtain exact admission and execute through the attended owner lane - -```task -id: RPF-WP-0068-T03 -status: wait -priority: high -needs_human: true -intervention_note: "Review CCR-2026-0056 and docs/email-connect-smtp-foundation-review-2026-10-31.md as platform owner/founder; owner decision sync and attended Warden execution remain required. No live reader admitted." -depends_on: -- RPF-WP-0068-T02 -state_hub_task_id: "50df1d39-28be-564d-b93a-678877a03399" -``` - -After the complete concrete packet, obtain the exact CCR/platform/consumer review -and founder decision. The shared reader is outside automatic single-reader -approval. Sync the founder decision through the existing owner workflow; never -self-approve the request. The founder then uses the contained OpenBao admin lane -and reviewed digest. Success requires the complete private receipt, exact policy/ -role, unchanged shared mailbox and other readers, and Warden session revocation. -An uncertain attempt requires inspection; never overwrite its journal or blindly -rerun. A role metadata receipt alone does not establish actual workload login. - -The complete packet is committed and projected to primary/railiance01. This task -is flagged for human review in State Hub. No platform-owner review or founder -approval has been fabricated. The native Railiance topic is ca369340-a64e-442e- -98f1-a4fa7dc74a38, slug railiance; its initial missing Hub association was repaired -and the source slug corrected to that native topic. - -## T04 — Verify native consumer delivery and return the custody result - -```task -id: RPF-WP-0068-T04 -status: todo -priority: high -state_hub_task_id: "da56d175-3126-5eaa-90c5-dd1e9da050d5" -``` - -Verify actual scoped Kubernetes login and the named projection Ready, including -effective scope/TTL and negative boundaries. Record non-secret evidence in the -CCR, mark the active reader in the mailbox rotation inventory, and hand the -verified custody result to CSOC-WP-0015-T02/email-connect. The application sender -rollout, selected-message authentication, inbox access, correlated return handling, -suppression operator and retention remain those live consumer obligations. - -## Completion - -Finish only on native admitted reader/projection proof and the explicit consumer -return; preserve live consumer work rather than treating custody as mail-flow -acceptance. No mailbox rotation, value copying, IMAP grant or arbitrary caller -admission is in this workplan. diff --git a/workplans/RPF-WP-0069-bounded-executor-lanes.md b/workplans/RPF-WP-0069-bounded-executor-lanes.md deleted file mode 100644 index 5cc1cfb..0000000 --- a/workplans/RPF-WP-0069-bounded-executor-lanes.md +++ /dev/null @@ -1,160 +0,0 @@ ---- -id: RPF-WP-0069 -type: workplan -title: "Bounded executor lanes: write-next-version type, journey read and audit sender (NK-WP-0056)" -domain: financials -repo: railiance-platform -status: active -flavor: implementation -owner: claude -topic_slug: railiance -created: "2026-10-31" -updated: "2026-10-31" -related: - - NK-WP-0056 - - NK-WP-0040 - - RPF-WP-0067 -state_hub_workstream_id: "6eb39172-babd-57d1-8a3b-f23c41347c22" ---- - -# RPF-WP-0069 — Bounded executor lanes (NK-WP-0056-T02 owner answer) - -Receiving workplan for NetKingdom's owner request (State Hub message -`9d08288d-e866-4e1c-9c0b-f1cc0968bd4d`; net-kingdom -`docs/proposals/2026-10-31-nk56-t02-owner-requests.md`). Owner answer: -`docs/credential-lane-designs/bounded-executor.md`. No live, cluster or OpenBao action is -part of T01; every CCR stays `proposed` until its approvers act. - -## Answer the request and add the write-next-version request type - -```task -id: RPF-WP-0069-T01 -status: done -priority: high -state_hub_task_id: "27432a77-e111-5aa9-9c23-34fb4c9a2984" -``` - -Answer doc written; new contract-only request type `workload-kv-write-next-version` -(`scripts/workload_kv_write_next_version.py`, `tests/test_workload_kv_write_next_version.py`, -dispatch in `scripts/credential-change.py`); CCR-2026-0058 drafted `proposed` with policy artifact -`openbao/policies/net-kingdom-bind-executor-svc-authelia-write.hcl`. - -## Review CCR-2026-0058 (founder gate) - -```task -id: RPF-WP-0069-T02 -status: done -priority: high -needs_human: true -decision_id: c29c6eda-fa13-4d53-8293-17261eb437c1 -blocked_on: human -blocking_reason: "Founder approval of CCR-2026-0058 and choice of standing or window-scoped write role (hub decision c29c6eda)" -state_hub_task_id: "3d4c818e-0c5f-5a14-9735-8056ff9fc7c3" -``` - -platform-owner and net-kingdom-owner approve or change CCR-2026-0058. The founder chooses -standing or window-scoped write role (recommendation: standing for the pilot). Binding is -confirmed only after `sso/nk-bind-executor` exists from the reviewed rapp-* package. - -## Attended setup and verification of CCR-2026-0058 - -```task -id: RPF-WP-0069-T03 -status: wait -priority: high -depends_on: [RPF-WP-0069-T02] -state_hub_task_id: "6b456804-c408-53bf-8e1d-49723108fca6" -``` - -After T02: an attended platform-admin (aal2) session sets `cas_required` on the svc-authelia key, -re-checks the coding-agent fence, applies policy and role, and records value-free positive and -negative receipts. This needs an owner setup script modelled on net-kingdom `bind-lane.py` -setup/verify; write it once approval exists. The founder runs it. - -## Journey-secret read lane for the executor (NK-WP-0056-T10) - -```task -id: RPF-WP-0069-T04 -status: done -priority: medium -state_hub_task_id: "5615be47-2b25-5f7d-a1c2-d4391b1377d0" -``` - -Draft a `workload-kv-read` CCR (native login, data read only, 15m) for -`platform/workloads/net-kingdom/synthetic-journey/syn-journey`, stating that it widens -CCR-2026-0057's `runtime_access: false`. Waits for T10 scheduling and the ServiceAccount. - -**2026-10-31, drafted** at the founder's request (net-kingdom session): - -- `CCR-2026-0059-net-kingdom-bind-executor-syn-journey-read.yaml` and the policy - `openbao/policies/net-kingdom-bind-executor-syn-journey-read.hcl`: read on the data path, plus - `lookup-self` and `revoke-self`. -- Role `net-kingdom-bind-executor-syn-journey-read`: `sso/nk-bind-executor`, audience `openbao`, 15m, exactly one - policy. It is separate from the CCR-2026-0058 write role. -- `supersedes_boundary` names the CCR-2026-0057 change. -- `lifecycle.role_window` proposes a standing reader (founder decides; window-scoped is the alternative). -- `bound_claims_confirmed: false` until the ServiceAccount exists. Validates with only that warning. The - pre-existing failures (CCR-2026-0035 and 2 tests) are unrelated. - -**Done 2026-10-31.** - -- Approved by the founder (`831a0f9`). The binding was confirmed after the rapp deploy (`b4bafb0`). -- The founder ran net-kingdom `tools/service-binds/executor-lanes.py`: - - `setup`: the policy and standing role were applied; the CCR-2026-0058 policy and `cas_required` too. - - `verify`: exact policy, lease of at most 15m, reads only its path, and the negatives held. -- CCR-2026-0059 is `applied`. It becomes `verified` after the first journey run through the role (NK-WP-0056-T10). -- Finding: platform-admin has no `patch` on `platform/*`. KV metadata changes therefore use a write that names only the - changed field. - -## Executor audit-core sender (NK-WP-0040) - -```task -id: RPF-WP-0069-T05 -status: wait -priority: medium -blocked_on: dependency -blocking_reason: "audit-core admits and names the sender (AUDIT-WP-0018-T02)" -state_hub_task_id: "5c582aef-ee9d-56ab-a4eb-2019933f00ac" -``` - -After audit-core admits the sender and names it, draft a `workload-kv-read` CCR in the -CCR-2026-0021/0022 shape: custody `platform/workloads/net-kingdom/bounded-executor/audit-sender`, -ESO into `sso/nk-bind-executor-audit`, ClusterSecretStore restricted to `sso`. - -## LLDAP credential custody for the executor - -```task -id: RPF-WP-0069-T06 -status: cancel -priority: medium -needs_human: true -decision_id: 3d0c8f86-7b9c-456b-8951-5ae889829118 -blocked_on: human -blocking_reason: "Founder chooses bind self-rotation (no account needed) or a lldap_password_manager account" -state_hub_task_id: "1456b467-f7c9-529d-9f3e-39404aaa36c6" -``` - -After secrets-engine answers the lane shape for the executor's LLDAP credential (its Q2), -draft the custody CCR here. - -## Founder decisions 2026-10-31 - -Approved as `ready`. CCR-2026-0058 amended to a window-scoped role (created per admitted run, deleted after the -receipt; hub decision c29c6eda) and approved by platform-owner and net-kingdom-owner. T06 is cancelled: the bind -rotates its own password (hub decision 3d0c8f86), so the executor needs no LLDAP custody lane. - -## Approve tool refuses the second approval for workload-kv-write-next-version - -```task -id: RPF-WP-0069-T07 -status: todo -priority: medium -state_hub_task_id: "b458e369-73b4-515e-ae01-e8afc0dd35eb" -``` - -`credential-change.py approve` sets `status: approved` after the first reviewer; for this request type, validation -then requires every role approval, so the second `approve` is refused before it can record itself (CCR-2026-0058's -net-kingdom-owner approval was recorded by hand in the tool's format). Make validation accept an `approved` request -that is still collecting role approvals, or have `approve` set `approved` only once all required roles are present, -with a test. -