docs: preserve reviewed NetKingdom session artifacts
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Reject generated-only commits / generated-commit-guard (push) Successful in 11s

This commit is contained in:
codex 2026-11-01 23:34:27 +01:00
parent c2b9f674e1
commit a9e64d05fe
45 changed files with 12119 additions and 0 deletions

View file

@ -0,0 +1,62 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — the-custodian
**Domain:** infotech
**Last synced:** 2026-10-15 23:45 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### NetKingdom target security architecture and SEC-DSM implementation
Progress: 2/6 done | workplan_id: `dfadb19b-445e-59c6-bcd6-48e87b912c52`
**Open tasks:**
- ► Close source defects and reconcile uncovered lifecycle gaps `87637402`
- ► Coordinate self-service Playwright identity delivery `ed8b909a`
- ► Establish the target architecture and drive the restructuring `6aca534c`
- · Verify native closure against every requirement `82378b0f`
### Separate agent credentials and establish supervised privileged execution
Progress: 2/5 done | workplan_id: `a98a9f34-83b4-5c8c-8107-e05f7806d50d`
**Open tasks:**
- ! Build and hand out the agent identity `4b88b0b7`
*(wait: Await GLAS-WP-0012 (glas-harness) production acceptance of the local supervised profile; then railiance-platform RBAC, railiance-enablement k3s config, railiance-infra principal mapping and ops-warden certificate issuance.)*
- ! Fleet guidance and harness guards `90725511`
*(wait: Await CUST-WP-0073-T02: the verified agent identity and execution path must exist before it can be documented; Codex/Grok read-denial guard needs glas-harness or harness-owner delivery.)*
- ! Rotate what was exposed `1b41b532`
*(wait: Deferred by founder decision 2026-09-24; reopened only by the founder on one of the three triggers below, in one attended rotation window.)*
### Size Railiance workloads from actual demand and establish a weekly allocation review
Progress: 1/5 done | workplan_id: `2249bddb-7524-5add-bd5c-c4163a6ca0f3`
**Open tasks:**
- ! Measure the Vergabe prototype under representative pilot use `82192370`
*(wait: Await RAPPS-WP-0014-T03 (blocked, needs_human): populated two-user/document workflow run by Bernd and the company contact, giving representative load for p95 <= 2s / zero failures.)*
- ! Review ecosystem allocations and reserve operating room `6dc67558`
*(wait: Await CUST-WP-0071-T02 representative sample; Forgejo/runner demand and zero-request workload review need owner input (railiance-cluster, railiance-forge, resource-control).)*
- ! Apply accepted sizing changes and verify their effects `944f98e2`
*(wait: Await reviewed allocation proposals and applicable workload-owner authority.)*
- ! Establish the regular weekly workload-demand and allocation assessment `20ddd4d7`
*(wait: Await the repeatable observation and accepted sizing/review procedure.)*
### Deterministic security flow: standing authorizations, flow report, and agent re-engagement
Progress: 3/7 done | workplan_id: `ab288f92-d364-5c24-9362-aee451f266c3`
**Open tasks:**
- ! Schedule the flow report and decision sync `1be0569c`
*(wait: Activity Core accepted the schedule in ACTIVITY-WP-0047. Await claiming rein workstation/hub acceptance, owner activation and the first unassisted fire with report/checkpoint evidence.)*
- ! Encode the standing change rule as a policy-nexus guardrail `5dd52156`
*(wait: policy-nexus owns guardrail publication; handoff written as PNEX-WP-0006.)*
- ! Stop agent-authored index and sync commits `8a3dc219`
*(wait: Local guard and repeatable shared-hook installation are implemented. Fleet generator adoption and the required full week of clean history remain unverified; repo-manager owner acceptance is needed.)*
- ! Attended window #1 `e3da807f`
*(wait: Partly done 2026-10-11: list cut from 14 to 3; U05-U07 accepted, U08 waits on KEY-WP-0040-T01. Remaining founder acts: USER-WP-0026-T03 readback, RPF-WP-0044-T08 coulombcore check, the U08 re-run, and Sol's added RPF-WP-0044-T04 issuer login.)*
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View file

@ -0,0 +1,536 @@
# Work Records — the-custodian
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | CUST-WP-ADHOC-2026-05-02 | done | — | workplans/ADHOC-2026-05-02.md |
| workplan | CUST-WP-ADHOC-2026-07-02 | finished | — | workplans/ADHOC-2026-07-02.md |
| workplan | CUST-WP-ADHOC-2026-08-25-RETIRED | finished | — | workplans/ADHOC-2026-08-25-retired.md |
| workplan | CUST-WP-ADHOC-2026-08-25 | finished | — | workplans/ADHOC-2026-08-25.md |
| workplan | CUST-WP-ADHOC-2026-09-06 | finished | — | workplans/ADHOC-2026-09-06.md |
| workplan | CUST-WP-0000 | finished | — | workplans/CUST-WP-0000-state-hub-v0.1-build-deploy.md |
| workplan | CUST-WP-0000b | finished | — | workplans/CUST-WP-0000b-state-hub-v0.2-decisions-dependencies.md |
| workplan | CUST-WP-0001 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
| workplan | CUST-WP-0002 | finished | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| workplan | CUST-WP-0004 | finished | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| workplan | CUST-WP-0005 | finished | — | workplans/CUST-WP-0005-dynamic-domains.md |
| workplan | CUST-WP-0006 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| workplan | CUST-WP-0007 | finished | — | workplans/CUST-WP-0007-gems-migration.md |
| workplan | CUST-WP-0008 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| workplan | CUST-WP-0009 | finished | — | workplans/CUST-WP-0009-human-interventions.md |
| workplan | CUST-WP-0010 | finished | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| workplan | CUST-WP-0010b | finished | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
| workplan | CUST-WP-0013 | finished | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| workplan | CUST-WP-0015 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| workplan | CUST-WP-0016 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| workplan | CUST-WP-0017 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| workplan | CUST-WP-0018 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| workplan | CUST-WP-0019 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| workplan | CUST-WP-0020 | done | — | workplans/CUST-WP-0020-test-suite.md |
| workplan | CUST-WP-0021 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| workplan | CUST-WP-0022 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| workplan | CUST-WP-0023 | done | — | workplans/CUST-WP-0023-tpsc.md |
| workplan | CUST-WP-0024 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| workplan | CUST-WP-0026 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| workplan | CUST-WP-0027 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| workplan | CUST-WP-0029 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| workplan | CUST-WP-0030 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| workplan | CUST-WP-0031 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| workplan | CUST-WP-0032 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| workplan | CUST-WP-0033 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| workplan | CUST-WP-0039 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| workplan | CUST-WP-0040 | finished | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| workplan | CUST-WP-0041 | done | — | workplans/CUST-WP-0041-api-performance.md |
| workplan | CUST-WP-0043 | finished | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| workplan | CUST-WP-0044 | finished | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| workplan | CUST-WP-0045 | finished | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| workplan | CUST-WP-0069 | finished | — | workplans/CUST-WP-0045-cutover-runbook.md |
| workplan | CUST-WP-0046 | finished | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| workplan | CUST-WP-0047 | finished | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| workplan | CUST-WP-0048 | finished | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| workplan | CUST-WP-0049 | finished | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| workplan | CUST-WP-0050 | finished | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| workplan | CUST-WP-0051 | finished | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| workplan | CUST-WP-0052 | finished | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| workplan | CUST-WP-0053 | finished | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| workplan | CUST-WP-0055 | finished | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| workplan | CUST-WP-0056 | finished | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| workplan | CUST-WP-0059 | finished | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| workplan | CUST-WP-0060 | finished | — | workplans/CUST-WP-0060-work-record-canonization.md |
| workplan | CUST-WP-0061 | finished | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| workplan | CUST-WP-0062 | finished | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| workplan | CUST-WP-0063 | finished | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| workplan | CUST-WP-0064 | finished | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| workplan | CUST-WP-0065 | finished | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| workplan | CUST-WP-0066 | finished | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| workplan | CUST-WP-0067 | finished | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| workplan | CUST-WP-0068 | finished | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| workplan | CUST-WP-0070 | finished | — | workplans/CUST-WP-0070-publication-repo-category.md |
| workplan | CUST-WP-0071 | blocked | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| workplan | CUST-WP-0072 | finished | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| workplan | CUST-WP-0073 | blocked | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| workplan | CUST-WP-0074 | finished | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| workplan | CUST-WP-0075 | finished | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| workplan | CUST-WP-0076 | finished | — | workplans/CUST-WP-0076-provisioning-window-cadence.md |
| workplan | CUST-WP-0077 | finished | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| workplan | CUST-WP-0078 | finished | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| workplan | CUST-WP-0079 | blocked | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| workplan | CUST-WP-0080 | active | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| workplan | CUST-WP-0081 | finished | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| workplan | THE-WP-0001 | finished | — | workplans/THE-WP-0001-federation-interface.md |
| task | CUST-WP-ADHOC-2026-05-02-T01 | done | — | workplans/ADHOC-2026-05-02.md |
| task | CUST-WP-ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md |
| task | ADHOC-2026-08-25-T01 | done | — | workplans/ADHOC-2026-08-25-retired.md |
| task | CUST-WP-ADHOC-2026-08-25-T01 | done | — | workplans/ADHOC-2026-08-25.md |
| task | CUST-WP-ADHOC-2026-09-06-T01 | done | — | workplans/ADHOC-2026-09-06.md |
| task | CUST-WP-0001-T01 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
| task | CUST-WP-0001-T02 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
| task | CUST-WP-0002-T01 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T02 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T03 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T04 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T05 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T06 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T07 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T08 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T09 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T10 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T11 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T12 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T13 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T14 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T15 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0004-T01 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T02 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T03 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T04 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T05 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T06 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T07 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T08 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T09 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T10 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0005-T01 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T02 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T03 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T04 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T05 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T06 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T07 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T08 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T09 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T10 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T11 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | T01 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T02 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T03 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T04 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T05 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T01 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T02 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T03 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T04 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T05 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T06 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T07 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T08 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T09 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T10 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T11 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T12 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T13 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T14 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T15 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T16 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T17 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | CUST-WP-0008-T01 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0008-T02 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0008-T03 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0008-T04 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0009-T1 | done | — | workplans/CUST-WP-0009-human-interventions.md |
| task | CUST-WP-0009-T2 | done | — | workplans/CUST-WP-0009-human-interventions.md |
| task | CUST-WP-0009-T3 | done | — | workplans/CUST-WP-0009-human-interventions.md |
| task | CUST-WP-0010-T1 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| task | CUST-WP-0010-T2 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| task | CUST-WP-0010-T3 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| task | CUST-WP-0010b-T01 | done | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
| task | CUST-WP-0010b-T02 | done | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
| task | CUST-WP-0013-T01 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T02 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T03 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T04 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T05 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T06 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T07 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T08 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T09 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T10 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T11 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T12 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T13 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T14 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0015-T01 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T02 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T03 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T04 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T05 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0016-T01 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T02 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T03 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T04 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T05 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0017-T01 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T02 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T03 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T04 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T05 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T06 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0018-T01 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T02 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T03 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T04 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T05 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T06 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T07 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0019-T01 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| task | CUST-WP-0019-T02 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| task | CUST-WP-0019-T03 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| task | CUST-WP-0020-T01 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T02 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T03 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T04 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T05 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0021-T01 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T02 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T03 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T04 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T05 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0022-T01 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T02 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T03 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T04 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T05 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T06 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T07 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T08 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T09 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T10 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0023-T01 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T02 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T03 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T04 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T05 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T06 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T07 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T08 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T09 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T10 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0024-T01 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T02 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T03 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T04 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T05 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0026-T01 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T02 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T03 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T04 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T05 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0027-T01 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T02 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T03 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T04 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T05 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0029-T01 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T02 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T03 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T04 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T05 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T06 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T07 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T08 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | T01 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T02 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T03 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T04 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T05 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T06 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T07 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T08 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T09 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T01 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T02 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T03 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T04 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T05 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T06 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T07 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | CUST-WP-0032-T01 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T02 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T03 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T04 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T05 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T06 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T07 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T08 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T09 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T10 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T11 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T12 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T13 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T14 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T15 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0033-T01 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T02 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T03 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T04 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T05 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T06 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0039-T1 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T2 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T3 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T4 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T5 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T6 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | T01 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T02 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T03 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T04 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T05 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T06 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | CUST-WP-0041-T1 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0041-T2 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0041-T3 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0041-T4 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0043-T01 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T02 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T03 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T04 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T05 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T06 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T07 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T08 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T09 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0044-T01 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T02 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T03 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T04 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T05 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T06 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0045-T01 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T02 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T03 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T04 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T05 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T06 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T07 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T08 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0046-T01 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T02 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T03 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T04 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T05 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T06 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T07 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0047-T01 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T02 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T03 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T04 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T05 | cancel | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T06 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T07 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0048-T01 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T02 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T03 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T04 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T05 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T06 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T07 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T08 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0049-T01 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T02 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T03 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T04 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T05 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T06 | cancel | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0050-T01 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T02 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T03 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T04 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T05 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T06 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T07 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T08 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T09 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T10 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T11 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0051-T01 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T02 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T03 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T04 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T05 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T06 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T07 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T08 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0052-T01 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T02 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T03 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T04 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T05 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T06 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T07 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0053-T01 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T02 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T03 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T04 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T05 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0055-T01 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T02 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T03 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T04 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T05 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T06 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T07 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T08 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0056-T01 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| task | CUST-WP-0056-T02 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| task | CUST-WP-0056-T03 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| task | CUST-WP-0059-T01 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T02 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T03 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T04 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T05 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T06 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0060-T01 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T02 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T03 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T04 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T05 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T06 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T07 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0061-T01 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T02 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T03 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T04 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T05 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T06 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0062-T01 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T02 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T03 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T04 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T05 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T06 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0063-T01 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T02 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T03 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T04 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T05 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0064-T01 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T02 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T03 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T04 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0065-T01 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0065-T02 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0065-T03 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0065-T04 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0066-T01 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| task | CUST-WP-0066-T02 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| task | CUST-WP-0066-T03 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| task | CUST-WP-0067-T01 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T02 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T03 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T04 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T05 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T06 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T07 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T08 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T09 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T10 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0068-T01 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T02 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T03 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T04 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T05 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T06 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T07 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T08 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T09 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0070-T01 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
| task | CUST-WP-0070-T02 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
| task | CUST-WP-0070-T03 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
| task | CUST-WP-0071-T01 | done | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T02 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T03 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T04 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T05 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0072-T01 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0072-T02 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0072-T03 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0072-T04 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0073-T01 | done | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T02 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T03 | done | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T04 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T05 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0074-T01 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T02 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T03 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T04 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T05 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T06 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0075-T01 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T02 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T03 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T04 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T05 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T06 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0076-T01 | done | — | workplans/CUST-WP-0076-provisioning-window-cadence.md |
| task | CUST-WP-0077-T01 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T02 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T03 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T04 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T05 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0078-T01 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0078-T02 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0078-T03 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0078-T04 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0079-T01 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T02 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T03 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T04 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T05 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T06 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T07 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0080-T01 | done | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T02 | done | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T03 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T04 | todo | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T05 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T06 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0081-T01 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| task | CUST-WP-0081-T02 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| task | CUST-WP-0081-T03 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| task | THE-WP-0001-T01 | done | — | workplans/THE-WP-0001-federation-interface.md |
| task | THE-WP-0001-T02 | done | — | workplans/THE-WP-0001-federation-interface.md |
| intake | CUST-IN-0001 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0002 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0003 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0004 | routed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0005 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0006 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0007 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0008 | closed | blue | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0009 | closed | blue | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0010 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0011 | routed | red | intakes.md |
| intake | CUST-IN-0012 | closed | green | intakes.md |
| intake | CUST-IN-0013 | closed | blue | intakes.md |
| intake | CUST-IN-0014 | closed | blue | intakes.md |
| intake | CUST-IN-0015 | open | blue | intakes.md |
| intake | CUST-IN-0016 | open | green | intakes.md |
| intake | CUST-IN-0017 | open | green | intakes.md |
| intake | CUST-IN-0018 | open | green | intakes.md |

View file

@ -0,0 +1,105 @@
{
"reason": "Owner disclosure consent recorded for all seven exact source revisions; restricted hub-side reader, independent technical reviews and native authenticated index/entry fetch verification remain pending. Repositories stay private.",
"repos": [
"rapp-openbao",
"rapp-policy-nexus",
"rapp-postgres",
"rapp-sbom-nexus",
"rapp-telemetry",
"rapp-tenant-engine",
"rapp-user-engine"
],
"draft_registrations_not_ready": [
{
"method": "POST",
"path": "/v1/repos",
"body": {
"repo": "rapp-openbao",
"url": "https://forgejo.coulomb.social/coulomb/rapp-openbao/raw/branch/main/registry/indexes/capabilities.yaml",
"domain": "infotech",
"enabled": true,
"required": false,
"registered_by": "codex-CUST-WP-0081"
}
},
{
"method": "POST",
"path": "/v1/repos",
"body": {
"repo": "rapp-policy-nexus",
"url": "https://forgejo.coulomb.social/coulomb/rapp-policy-nexus/raw/branch/main/registry/indexes/capabilities.yaml",
"domain": "infotech",
"enabled": true,
"required": false,
"registered_by": "codex-CUST-WP-0081"
}
},
{
"method": "POST",
"path": "/v1/repos",
"body": {
"repo": "rapp-postgres",
"url": "https://forgejo.coulomb.social/coulomb/rapp-postgres/raw/branch/main/registry/indexes/capabilities.yaml",
"domain": "infotech",
"enabled": true,
"required": false,
"registered_by": "codex-CUST-WP-0081"
}
},
{
"method": "POST",
"path": "/v1/repos",
"body": {
"repo": "rapp-telemetry",
"url": "https://forgejo.coulomb.social/coulomb/rapp-telemetry/raw/branch/main/registry/indexes/capabilities.yaml",
"domain": "infotech",
"enabled": true,
"required": false,
"registered_by": "codex-CUST-WP-0081"
}
},
{
"method": "POST",
"path": "/v1/repos",
"body": {
"repo": "rapp-tenant-engine",
"url": "https://forgejo.coulomb.social/coulomb/rapp-tenant-engine/raw/branch/main/registry/indexes/capabilities.yaml",
"domain": "infotech",
"enabled": true,
"required": false,
"registered_by": "codex-CUST-WP-0081"
}
},
{
"method": "POST",
"path": "/v1/repos",
"body": {
"repo": "rapp-user-engine",
"url": "https://forgejo.coulomb.social/coulomb/rapp-user-engine/raw/branch/main/registry/indexes/capabilities.yaml",
"domain": "infotech",
"enabled": true,
"required": false,
"registered_by": "codex-CUST-WP-0081"
}
},
{
"method": "POST",
"path": "/v1/repos",
"body": {
"repo": "rapp-sbom-nexus",
"url": "https://forgejo.coulomb.social/coulomb/rapp-sbom-nexus/raw/branch/main/registry/indexes/capabilities.yaml",
"domain": "infotech",
"enabled": true,
"required": false,
"registered_by": "codex-CUST-WP-0081"
}
}
],
"needed_fields": [
"auth_env name for an already admitted hub-side source-read credential",
"proof that the credential is limited to the intended repository read scope",
"successful hub-side index fetch without value disclosure"
],
"consent_receipt": "private-source-owner-consent-r1.json",
"registration_preparation": "hub-private-registration-preparation-r1.json"
}

View file

@ -0,0 +1,18 @@
{
"workplan_id": "e8992815-389a-5c40-8569-a2b0ee3945f0",
"id": "5cd22087-e5d8-5d5e-a252-610a6ac13653",
"topic_id": "cee7bedf-2b48-46ef-8601-006474f2ad7a",
"title": "Complete scoped owner provisioning for reuse-surface discovery",
"description": "Execution/evidence gate for CUST-WP-0081-T02. Public batch applied and verified (63/70 hosted). Founder metadata disclosure consent for all seven private source revisions recorded separately as decision d8f8f216-ac94-4295-97f3-825fd21254ea; do not request it again. Remaining: independent technical reviews, restricted hub-only repository reader through railiance-platform reviewed custody/ESO delivery, sanitized auth_env and native fetch receipts, seven registrations and final all-70 audit. This gate stays open until verified provisioning; metadata consent is not a credential or rollout grant. See docs/assessments/netkingdom-2026-10-14/private-source-owner-consent-r1.json and scoped-access-handoff.json.",
"decision_type": "pending",
"status": "open",
"rationale": "All 70 owner records published; 63/70 verified hosted. Owner disclosure consent satisfied; seven-source reader and native fetch evidence pending.",
"decided_by": null,
"decided_at": null,
"deadline": null,
"escalation_note": null,
"superseded_by": null,
"created_at": "2026-10-14T07:51:34.426143Z",
"updated_at": "2026-10-14T22:31:45.188478Z",
"workstream_id": "e8992815-389a-5c40-8569-a2b0ee3945f0"
}

View file

@ -0,0 +1,546 @@
[
{
"repo": "access-engine",
"branch": "main",
"local_head": "62ac2b4e99dec2005c7c407fe633612578a7907d",
"remote_head": "62ac2b4e99dec2005c7c407fe633612578a7907d",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "activity-core",
"branch": "main",
"local_head": "f9f8df039258a0d8b6fc29dc1f7c4bcc75fdc8c0",
"remote_head": "f9f8df039258a0d8b6fc29dc1f7c4bcc75fdc8c0",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "agentic-resources",
"branch": "main",
"local_head": "c4cedab945e047de068428fb943f346f093b22dc",
"remote_head": "c4cedab945e047de068428fb943f346f093b22dc",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "approval-engine",
"branch": "main",
"local_head": "db850aded5fc3b5d35938d26e8084e436a83943b",
"remote_head": "db850aded5fc3b5d35938d26e8084e436a83943b",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "audit-core",
"branch": "main",
"local_head": "8d4bde6ece39ce0df3a3cc49da38613123e8f215",
"remote_head": "8d4bde6ece39ce0df3a3cc49da38613123e8f215",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "coordination-engine",
"branch": "main",
"local_head": "16de8fc3f70020df646f1ebdca705a1bf88485a0",
"remote_head": "16de8fc3f70020df646f1ebdca705a1bf88485a0",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "emergent-security",
"branch": "main",
"local_head": "6eead2a922ae2eb81593213f54bf1f35e1c9970f",
"remote_head": "6eead2a922ae2eb81593213f54bf1f35e1c9970f",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "fin-hub",
"branch": "main",
"local_head": "cace0fda7474304cc42028287f7ba1327ab07d00",
"remote_head": "cace0fda7474304cc42028287f7ba1327ab07d00",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "gate-house",
"branch": "main",
"local_head": "ab1a299674679ccfefa8df84b877614ac44c9d38",
"remote_head": "ab1a299674679ccfefa8df84b877614ac44c9d38",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "glas-harness",
"branch": "main",
"local_head": "dc88f302044c7c2b41795958527e64be8a83e3ab",
"remote_head": "dc88f302044c7c2b41795958527e64be8a83e3ab",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "info-tech-canon",
"branch": "main",
"local_head": "9b9be2ec8e846ceaa05fa644323b02d8603e39b6",
"remote_head": "9b9be2ec8e846ceaa05fa644323b02d8603e39b6",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "informed-decision",
"branch": "main",
"local_head": "faae9cb0dbaf4c8d23f838183f513c302ad3b9f0",
"remote_head": "faae9cb0dbaf4c8d23f838183f513c302ad3b9f0",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "issue-core",
"branch": "main",
"local_head": "1427d268a09b9334a98a167230ec7dd1d4a82053",
"remote_head": "1427d268a09b9334a98a167230ec7dd1d4a82053",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "key-cape",
"branch": "main",
"local_head": "8c6c071ad9c73865793a075ed1b34b93e5075436",
"remote_head": "44b243410006a2df32adcd4cae2ab5e6f806da82",
"same_head": false,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "kings-guard",
"branch": "main",
"local_head": "6aea834dc556067672f31ed762f44a39bad988e3",
"remote_head": "6aea834dc556067672f31ed762f44a39bad988e3",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "maturity-engine",
"branch": "main",
"local_head": "2f1de542cc8f24a79452f906d99f95be39d8b6a9",
"remote_head": "363e8160a4910766e9380fabee68a8642dd45ccf",
"same_head": false,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "net-kingdom",
"branch": "main",
"local_head": "2cc09ceb05c6f2132c6fc3478901c4ee1085fa1b",
"remote_head": "8f0a5902004c47415846d818e400eb5d5e1e037d",
"same_head": false,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "ops-bridge",
"branch": "main",
"local_head": "2246656f43fa5eae3ae917f63ea1dfa3f53f4a00",
"remote_head": "2246656f43fa5eae3ae917f63ea1dfa3f53f4a00",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "ops-mason",
"branch": "main",
"local_head": "97d1fed9496aacb1836982096b8aecb6da13cf0d",
"remote_head": "97d1fed9496aacb1836982096b8aecb6da13cf0d",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "ops-warden",
"branch": "main",
"local_head": "1baeb0821cb6304464fab78932d8c6501f8c4cc6",
"remote_head": "1baeb0821cb6304464fab78932d8c6501f8c4cc6",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "policy-nexus",
"branch": "main",
"local_head": "c14561b2e5318e9637ba93e4c96c06a2db92d0c4",
"remote_head": "c14561b2e5318e9637ba93e4c96c06a2db92d0c4",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rail-kubernetes",
"branch": "main",
"local_head": "5fad47fdfb7d4cabc21cf10f52ac733ef59803b0",
"remote_head": "5fad47fdfb7d4cabc21cf10f52ac733ef59803b0",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-apps",
"branch": "main",
"local_head": "72bcad86d7531bcc27f0e465a9385be559ba0c9f",
"remote_head": "72bcad86d7531bcc27f0e465a9385be559ba0c9f",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "railiance-bootstrap",
"branch": "main",
"local_head": "70bd14d3b941a49c78bbc51d8faab5ab1da1df76",
"remote_head": "70bd14d3b941a49c78bbc51d8faab5ab1da1df76",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-cluster",
"branch": "main",
"local_head": "750c3e5b9b4623c52bce2b8fbd758f423f81f6c1",
"remote_head": "750c3e5b9b4623c52bce2b8fbd758f423f81f6c1",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-enablement",
"branch": "main",
"local_head": "ce9f6d1be8ff73afdedd6d666a8485e6c7348069",
"remote_head": "ce9f6d1be8ff73afdedd6d666a8485e6c7348069",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-fabric",
"branch": "main",
"local_head": "095342c72ab6f02c72dd5d40e869dcba536bc5c1",
"remote_head": "095342c72ab6f02c72dd5d40e869dcba536bc5c1",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-forge",
"branch": "main",
"local_head": "6e8f657e55e96b4e9f3d9d74a27b420b68467e61",
"remote_head": "6e8f657e55e96b4e9f3d9d74a27b420b68467e61",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "railiance-infra",
"branch": "main",
"local_head": "32f64b54fd94a5e6a145ef7c2c8faf051f721a71",
"remote_head": "99bc66f8c98a5404339074bf5bb11271d9f6d58d",
"same_head": false,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-master",
"branch": "main",
"local_head": "a0be0d3142b5392f1fb6271cf91325ce91a0f9d4",
"remote_head": "a0be0d3142b5392f1fb6271cf91325ce91a0f9d4",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-platform",
"branch": "main",
"local_head": "0f0282a43646edbb5743318bc923749af1c0cea2",
"remote_head": "b797594a21fb0969ec987e633521245e0277267b",
"same_head": false,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "railiance-telemetry",
"branch": "main",
"local_head": "8cdb2d11dda03bd6839750d059b37932f069e66a",
"remote_head": "8cdb2d11dda03bd6839750d059b37932f069e66a",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rapp-openbao",
"branch": "main",
"local_head": "35672be138036c8433a5ac5de91e068c03c2b124",
"remote_head": "35672be138036c8433a5ac5de91e068c03c2b124",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rapp-playwright",
"branch": "main",
"local_head": "4f76a87f67fdd9dab19564bb84235397e2f2470a",
"remote_head": "4f76a87f67fdd9dab19564bb84235397e2f2470a",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rapp-policy-nexus",
"branch": "main",
"local_head": "088b78ac03b0cebeec34a69aae3569eb7ec2f9c3",
"remote_head": "088b78ac03b0cebeec34a69aae3569eb7ec2f9c3",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rapp-postgres",
"branch": "main",
"local_head": "a9f569c96d74aa384ac8d3ac4aa960e4061550b5",
"remote_head": "a9f569c96d74aa384ac8d3ac4aa960e4061550b5",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rapp-telemetry",
"branch": "main",
"local_head": "f158307e24b86bab88a1af4830ff6b4f85afdeb5",
"remote_head": "f158307e24b86bab88a1af4830ff6b4f85afdeb5",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rapp-tenant-engine",
"branch": "main",
"local_head": "9bdc86572728783a5ed94266f04f7b12d414c557",
"remote_head": "9bdc86572728783a5ed94266f04f7b12d414c557",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rapp-user-engine",
"branch": "main",
"local_head": "f35108f9e1b8702fb6c526fe42b2fec5ca032738",
"remote_head": "f35108f9e1b8702fb6c526fe42b2fec5ca032738",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "reef-railiance",
"branch": "main",
"local_head": "33b840faac109f134bf1115d631d5b1242052d9a",
"remote_head": "33b840faac109f134bf1115d631d5b1242052d9a",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "rein-aharness",
"branch": "main",
"local_head": "a58d11ce8ae9ea07baf0682c4f860caa251692b3",
"remote_head": "a58d11ce8ae9ea07baf0682c4f860caa251692b3",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "rein-openweights",
"branch": "main",
"local_head": "a2e428a76592d79982ea3c4eba5f137a12fb44e4",
"remote_head": "a2e428a76592d79982ea3c4eba5f137a12fb44e4",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "repo-manager",
"branch": "main",
"local_head": "97ea2489257de08911cc5da1e1b84d8e15780dc3",
"remote_head": "97ea2489257de08911cc5da1e1b84d8e15780dc3",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "resource-control",
"branch": "main",
"local_head": "ac4e1c43e6d82ecd81108f3b006454ac6aa76722",
"remote_head": "ac4e1c43e6d82ecd81108f3b006454ac6aa76722",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "reuse-surface",
"branch": "main",
"local_head": "03cd049773478e9de7e38035a49deaa82772a357",
"remote_head": "03cd049773478e9de7e38035a49deaa82772a357",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "risk-nexus",
"branch": "main",
"local_head": "0cb565e1e498002b0fe71975e7654b641a4c24d2",
"remote_head": "0cb565e1e498002b0fe71975e7654b641a4c24d2",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "role-engine",
"branch": "main",
"local_head": "c9d6d0763542f632f1d9b799d63b6b85cf8eaaab",
"remote_head": "c9d6d0763542f632f1d9b799d63b6b85cf8eaaab",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "runbook-tutorials",
"branch": "main",
"local_head": "f0ea260895e8ad8e60f24bf64a89be063b7f8e13",
"remote_head": "f0ea260895e8ad8e60f24bf64a89be063b7f8e13",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "sand-boxer",
"branch": "main",
"local_head": "7ddf92070f2fa267d79f8cad0aa07d16f0cd6410",
"remote_head": "7ddf92070f2fa267d79f8cad0aa07d16f0cd6410",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "sbom-nexus",
"branch": "main",
"local_head": "db76194330b9502e9a1085ae25cdd27ce264ec7b",
"remote_head": "db76194330b9502e9a1085ae25cdd27ce264ec7b",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "secrets-engine",
"branch": "main",
"local_head": "6bd7129eac94131b4cf689e47cbdc9899418beb3",
"remote_head": "6bd7129eac94131b4cf689e47cbdc9899418beb3",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "security-canon",
"branch": "main",
"local_head": "c31c3008a53bcff07be09d0507d4693d27b40a5a",
"remote_head": "c31c3008a53bcff07be09d0507d4693d27b40a5a",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "state-hub",
"branch": "main",
"local_head": "2a2ac2eb195a073633f4db0f166b927ca1877d67",
"remote_head": "2a2ac2eb195a073633f4db0f166b927ca1877d67",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "tenant-engine",
"branch": "main",
"local_head": "79a61905957bd78d3f4c93da42d7034ad3e3c8de",
"remote_head": "79a61905957bd78d3f4c93da42d7034ad3e3c8de",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "the-custodian",
"branch": "main",
"local_head": "0e7bcd128bb80d3630d84c2ba1bedf88578ccde1",
"remote_head": "0e7bcd128bb80d3630d84c2ba1bedf88578ccde1",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": [
".custodian-brief.md"
]
},
{
"repo": "user-engine",
"branch": "main",
"local_head": "e9534ef00f02f2b4772cd80f3b7c7aad5f9dae23",
"remote_head": "e9534ef00f02f2b4772cd80f3b7c7aad5f9dae23",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "whitehat-security",
"branch": "main",
"local_head": "42fe7a12c7d9d64142ec0e820ba4e975ffeac593",
"remote_head": "42fe7a12c7d9d64142ec0e820ba4e975ffeac593",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
},
{
"repo": "zone-engine",
"branch": "main",
"local_head": "7e7e3f307ca996c373e91009ed1b9b0cbc6fcb9e",
"remote_head": "7e7e3f307ca996c373e91009ed1b9b0cbc6fcb9e",
"same_head": true,
"remote_read_exit": 0,
"staged_paths": []
}
]

View file

@ -0,0 +1,67 @@
{
"workplan": "CUST-WP-0081",
"user_disposition": "Arrange scoped owner access",
"status": "Public batch verified; founder metadata disclosure consent recorded for all seven sources. Restricted hub reader and native fetch verification remain pending.",
"owner_repo": "railiance-platform",
"write_access": {
"route": "reuse-surface-hub-write-token",
"kv_path": "platform/workloads/reuse/reuse-surface/runtime-secrets",
"field": "REUSE_SURFACE_TOKEN",
"existing_read_policy": "workload-kv-read-reuse-surface-runtime",
"request": "Owner to admit the caller through the existing scoped route and verify its metadata lookup and selected-field read. Retain existing denial evidence; do not grant administrative or unrelated workload access.",
"delivery": "warden access --exec, without exporting or recording the value",
"approval_record": "CCR-2026-0005 is active and was previously approved by platform-operator and reuse-surface-owner; it does not name the caller principal.",
"principal_binding": "Identify the caller OpenBao OIDC entity/accessor without recording any token. If a binding change is needed, record approval for that exact principal with both required approvers before applying the existing policy.",
"existing_policy": "workload-kv-read-reuse-surface-runtime",
"exact_allowed_paths": [
"platform/data/workloads/reuse/reuse-surface/runtime-secrets",
"platform/metadata/workloads/reuse/reuse-surface/runtime-secrets"
],
"negative_check": "An unrelated sibling path must remain denied. Do not broaden policy contents or attach administrative/ESO-only permissions.",
"verified_execution_receipt": "hosted-public-registration-verification.json"
},
"private_source_access": {
"repositories": [
"rapp-openbao",
"rapp-policy-nexus",
"rapp-postgres",
"rapp-sbom-nexus",
"rapp-telemetry",
"rapp-tenant-engine",
"rapp-user-engine"
],
"request": "Owner to provision a repository-read credential at the federation hub, confirm source-owner consent to public capability metadata, and verify hub-side index and entry fetches.",
"return_only": [
"configured auth_env name",
"auth_header name if nondefault",
"scope and verification receipts"
],
"visibility_change_authorized": false,
"credential_design": "A new dedicated Forgejo identity/PAT restricted to repository-read for exactly these seven sources; verify a denied-write/admin negative. Keep it distinct from the hub writer token and from other workload credentials.",
"delivery": "Owner-reviewed OpenBao custody and ESO delivery to the reuse-surface hub runtime, then configure the registration auth_env with the environment variable name only.",
"approval": "Platform operator and each relevant source owner must approve the limited private capability-metadata disclosure and reader scope before provisioning.",
"metadata_disclosure_consent": {
"status": "approved",
"receipt": "private-source-owner-consent-r1.json",
"decision_id": "d8f8f216-ac94-4295-97f3-825fd21254ea",
"proposal_sha256": "46ac4fe49a08054cc215aade2fbaf3b36ee334520997857ed84f530b52da9d35"
},
"consumer_delivery_gate": "Reuse owner reports native authenticated redirect forwarding hazard. Require owner-reviewed native fetch fix and verified fixed hub image before introducing source-reader PAT; initial same-image assumption is superseded by private-reader-owner-assembly-receipt.json."
},
"resume": "Update and retain the exact registration proposal revision with admitted auth_env references, verify source parity, execute bounded registrations, then require all 70 reviewed IDs exactly once in a fresh hosted federation.",
"secret_values_requested_in_chat": false,
"owner_message_sent": true,
"attended_sequence": [
"Review CCR-2026-0005 and identify the actual intended caller principal. The existing OIDC identity is not named in the CCR.",
"If needed, obtain and record platform-operator plus reuse-surface-owner approval for binding that exact identity to the existing read-only policy. Verify read on the two exact paths and denial on a sibling path.",
"Use warden access reuse-surface-hub-write-token --exec --field REUSE_SURFACE_TOKEN with a silent helper. Do not use --fetch or print/export the field.",
"After the public-only helper receipt confirms 31 registrations, request the distinct seven-repository read-only source credential and owner disclosure consent. Return only the auth_env name, scope and sanitized verification receipts."
],
"public_batch_prepared": {
"proposal": "hub-public-registration-proposal-r1.json",
"plan": "hub-public-resume-plan.json",
"helper": "register_public_hub.py",
"repositories": 31,
"status": "31 registrations applied, readback verified, hosted public capabilities verified, login session revoked"
}
}

View file

@ -0,0 +1,62 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — the-custodian
**Domain:** infotech
**Last synced:** 2026-10-15 23:45 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### NetKingdom target security architecture and SEC-DSM implementation
Progress: 2/6 done | workplan_id: `dfadb19b-445e-59c6-bcd6-48e87b912c52`
**Open tasks:**
- ► Close source defects and reconcile uncovered lifecycle gaps `87637402`
- ► Coordinate self-service Playwright identity delivery `ed8b909a`
- ► Establish the target architecture and drive the restructuring `6aca534c`
- · Verify native closure against every requirement `82378b0f`
### Separate agent credentials and establish supervised privileged execution
Progress: 2/5 done | workplan_id: `a98a9f34-83b4-5c8c-8107-e05f7806d50d`
**Open tasks:**
- ! Build and hand out the agent identity `4b88b0b7`
*(wait: Await GLAS-WP-0012 (glas-harness) production acceptance of the local supervised profile; then railiance-platform RBAC, railiance-enablement k3s config, railiance-infra principal mapping and ops-warden certificate issuance.)*
- ! Fleet guidance and harness guards `90725511`
*(wait: Await CUST-WP-0073-T02: the verified agent identity and execution path must exist before it can be documented; Codex/Grok read-denial guard needs glas-harness or harness-owner delivery.)*
- ! Rotate what was exposed `1b41b532`
*(wait: Deferred by founder decision 2026-09-24; reopened only by the founder on one of the three triggers below, in one attended rotation window.)*
### Size Railiance workloads from actual demand and establish a weekly allocation review
Progress: 1/5 done | workplan_id: `2249bddb-7524-5add-bd5c-c4163a6ca0f3`
**Open tasks:**
- ! Measure the Vergabe prototype under representative pilot use `82192370`
*(wait: Await RAPPS-WP-0014-T03 (blocked, needs_human): populated two-user/document workflow run by Bernd and the company contact, giving representative load for p95 <= 2s / zero failures.)*
- ! Review ecosystem allocations and reserve operating room `6dc67558`
*(wait: Await CUST-WP-0071-T02 representative sample; Forgejo/runner demand and zero-request workload review need owner input (railiance-cluster, railiance-forge, resource-control).)*
- ! Apply accepted sizing changes and verify their effects `944f98e2`
*(wait: Await reviewed allocation proposals and applicable workload-owner authority.)*
- ! Establish the regular weekly workload-demand and allocation assessment `20ddd4d7`
*(wait: Await the repeatable observation and accepted sizing/review procedure.)*
### Deterministic security flow: standing authorizations, flow report, and agent re-engagement
Progress: 3/7 done | workplan_id: `ab288f92-d364-5c24-9362-aee451f266c3`
**Open tasks:**
- ! Schedule the flow report and decision sync `1be0569c`
*(wait: Activity Core accepted the schedule in ACTIVITY-WP-0047. Await claiming rein workstation/hub acceptance, owner activation and the first unassisted fire with report/checkpoint evidence.)*
- ! Encode the standing change rule as a policy-nexus guardrail `5dd52156`
*(wait: policy-nexus owns guardrail publication; handoff written as PNEX-WP-0006.)*
- ! Stop agent-authored index and sync commits `8a3dc219`
*(wait: Local guard and repeatable shared-hook installation are implemented. Fleet generator adoption and the required full week of clean history remain unverified; repo-manager owner acceptance is needed.)*
- ! Attended window #1 `e3da807f`
*(wait: Partly done 2026-10-11: list cut from 14 to 3; U05-U07 accepted, U08 waits on KEY-WP-0040-T01. Remaining founder acts: USER-WP-0026-T03 readback, RPF-WP-0044-T08 coulombcore check, the U08 re-run, and Sol's added RPF-WP-0044-T04 issuer login.)*
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View file

@ -0,0 +1,536 @@
# Work Records — the-custodian
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | CUST-WP-ADHOC-2026-05-02 | done | — | workplans/ADHOC-2026-05-02.md |
| workplan | CUST-WP-ADHOC-2026-07-02 | finished | — | workplans/ADHOC-2026-07-02.md |
| workplan | CUST-WP-ADHOC-2026-08-25-RETIRED | finished | — | workplans/ADHOC-2026-08-25-retired.md |
| workplan | CUST-WP-ADHOC-2026-08-25 | finished | — | workplans/ADHOC-2026-08-25.md |
| workplan | CUST-WP-ADHOC-2026-09-06 | finished | — | workplans/ADHOC-2026-09-06.md |
| workplan | CUST-WP-0000 | finished | — | workplans/CUST-WP-0000-state-hub-v0.1-build-deploy.md |
| workplan | CUST-WP-0000b | finished | — | workplans/CUST-WP-0000b-state-hub-v0.2-decisions-dependencies.md |
| workplan | CUST-WP-0001 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
| workplan | CUST-WP-0002 | finished | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| workplan | CUST-WP-0004 | finished | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| workplan | CUST-WP-0005 | finished | — | workplans/CUST-WP-0005-dynamic-domains.md |
| workplan | CUST-WP-0006 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| workplan | CUST-WP-0007 | finished | — | workplans/CUST-WP-0007-gems-migration.md |
| workplan | CUST-WP-0008 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| workplan | CUST-WP-0009 | finished | — | workplans/CUST-WP-0009-human-interventions.md |
| workplan | CUST-WP-0010 | finished | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| workplan | CUST-WP-0010b | finished | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
| workplan | CUST-WP-0013 | finished | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| workplan | CUST-WP-0015 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| workplan | CUST-WP-0016 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| workplan | CUST-WP-0017 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| workplan | CUST-WP-0018 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| workplan | CUST-WP-0019 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| workplan | CUST-WP-0020 | done | — | workplans/CUST-WP-0020-test-suite.md |
| workplan | CUST-WP-0021 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| workplan | CUST-WP-0022 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| workplan | CUST-WP-0023 | done | — | workplans/CUST-WP-0023-tpsc.md |
| workplan | CUST-WP-0024 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| workplan | CUST-WP-0026 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| workplan | CUST-WP-0027 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| workplan | CUST-WP-0029 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| workplan | CUST-WP-0030 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| workplan | CUST-WP-0031 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| workplan | CUST-WP-0032 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| workplan | CUST-WP-0033 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| workplan | CUST-WP-0039 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| workplan | CUST-WP-0040 | finished | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| workplan | CUST-WP-0041 | done | — | workplans/CUST-WP-0041-api-performance.md |
| workplan | CUST-WP-0043 | finished | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| workplan | CUST-WP-0044 | finished | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| workplan | CUST-WP-0045 | finished | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| workplan | CUST-WP-0069 | finished | — | workplans/CUST-WP-0045-cutover-runbook.md |
| workplan | CUST-WP-0046 | finished | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| workplan | CUST-WP-0047 | finished | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| workplan | CUST-WP-0048 | finished | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| workplan | CUST-WP-0049 | finished | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| workplan | CUST-WP-0050 | finished | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| workplan | CUST-WP-0051 | finished | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| workplan | CUST-WP-0052 | finished | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| workplan | CUST-WP-0053 | finished | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| workplan | CUST-WP-0055 | finished | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| workplan | CUST-WP-0056 | finished | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| workplan | CUST-WP-0059 | finished | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| workplan | CUST-WP-0060 | finished | — | workplans/CUST-WP-0060-work-record-canonization.md |
| workplan | CUST-WP-0061 | finished | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| workplan | CUST-WP-0062 | finished | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| workplan | CUST-WP-0063 | finished | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| workplan | CUST-WP-0064 | finished | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| workplan | CUST-WP-0065 | finished | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| workplan | CUST-WP-0066 | finished | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| workplan | CUST-WP-0067 | finished | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| workplan | CUST-WP-0068 | finished | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| workplan | CUST-WP-0070 | finished | — | workplans/CUST-WP-0070-publication-repo-category.md |
| workplan | CUST-WP-0071 | blocked | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| workplan | CUST-WP-0072 | finished | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| workplan | CUST-WP-0073 | blocked | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| workplan | CUST-WP-0074 | finished | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| workplan | CUST-WP-0075 | finished | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| workplan | CUST-WP-0076 | finished | — | workplans/CUST-WP-0076-provisioning-window-cadence.md |
| workplan | CUST-WP-0077 | finished | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| workplan | CUST-WP-0078 | finished | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| workplan | CUST-WP-0079 | blocked | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| workplan | CUST-WP-0080 | active | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| workplan | CUST-WP-0081 | finished | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| workplan | THE-WP-0001 | finished | — | workplans/THE-WP-0001-federation-interface.md |
| task | CUST-WP-ADHOC-2026-05-02-T01 | done | — | workplans/ADHOC-2026-05-02.md |
| task | CUST-WP-ADHOC-2026-07-02-T01 | done | — | workplans/ADHOC-2026-07-02.md |
| task | ADHOC-2026-08-25-T01 | done | — | workplans/ADHOC-2026-08-25-retired.md |
| task | CUST-WP-ADHOC-2026-08-25-T01 | done | — | workplans/ADHOC-2026-08-25.md |
| task | CUST-WP-ADHOC-2026-09-06-T01 | done | — | workplans/ADHOC-2026-09-06.md |
| task | CUST-WP-0001-T01 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
| task | CUST-WP-0001-T02 | done | — | workplans/CUST-WP-0001-custodian-agent-runtime.md |
| task | CUST-WP-0002-T01 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T02 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T03 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T04 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T05 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T06 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T07 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T08 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T09 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T10 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T11 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T12 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T13 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T14 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0002-T15 | done | — | workplans/CUST-WP-0002-contribution-tracking-sbom.md |
| task | CUST-WP-0004-T01 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T02 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T03 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T04 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T05 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T06 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T07 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T08 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T09 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0004-T10 | done | — | workplans/CUST-WP-0004-ep-td-tracking.md |
| task | CUST-WP-0005-T01 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T02 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T03 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T04 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T05 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T06 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T07 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T08 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T09 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T10 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | CUST-WP-0005-T11 | done | — | workplans/CUST-WP-0005-dynamic-domains.md |
| task | T01 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T02 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T03 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T04 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T05 | done | — | workplans/CUST-WP-0006-gems-state-hub.md |
| task | T01 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T02 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T03 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T04 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T05 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T06 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T07 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T08 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T09 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T10 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T11 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T12 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T13 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T14 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T15 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T16 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | T17 | done | — | workplans/CUST-WP-0007-gems-migration.md |
| task | CUST-WP-0008-T01 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0008-T02 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0008-T03 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0008-T04 | done | — | workplans/CUST-WP-0008-consistency-engine.md |
| task | CUST-WP-0009-T1 | done | — | workplans/CUST-WP-0009-human-interventions.md |
| task | CUST-WP-0009-T2 | done | — | workplans/CUST-WP-0009-human-interventions.md |
| task | CUST-WP-0009-T3 | done | — | workplans/CUST-WP-0009-human-interventions.md |
| task | CUST-WP-0010-T1 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| task | CUST-WP-0010-T2 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| task | CUST-WP-0010-T3 | done | — | workplans/CUST-WP-0010-workstream-lifecycle-docs.md |
| task | CUST-WP-0010b-T01 | done | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
| task | CUST-WP-0010b-T02 | done | — | workplans/CUST-WP-0010b-domain-and-repo-goals.md |
| task | CUST-WP-0013-T01 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T02 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T03 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T04 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T05 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T06 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T07 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T08 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T09 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T10 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T11 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T12 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T13 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0013-T14 | done | — | workplans/CUST-WP-0013-sbom-infra-expansion.md |
| task | CUST-WP-0015-T01 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T02 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T03 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T04 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0015-T05 | done | — | workplans/CUST-WP-0015-agent-inbox.md |
| task | CUST-WP-0016-T01 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T02 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T03 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T04 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0016-T05 | done | — | workplans/CUST-WP-0016-kaizen-agentic-integration.md |
| task | CUST-WP-0017-T01 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T02 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T03 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T04 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T05 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0017-T06 | done | — | workplans/CUST-WP-0017-scope-md-agent-and-coverage.md |
| task | CUST-WP-0018-T01 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T02 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T03 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T04 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T05 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T06 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0018-T07 | done | — | workplans/CUST-WP-0018-api-hardening-code-quality.md |
| task | CUST-WP-0019-T01 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| task | CUST-WP-0019-T02 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| task | CUST-WP-0019-T03 | done | — | workplans/CUST-WP-0019-dashboard-ux-polish.md |
| task | CUST-WP-0020-T01 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T02 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T03 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T04 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0020-T05 | done | — | workplans/CUST-WP-0020-test-suite.md |
| task | CUST-WP-0021-T01 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T02 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T03 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T04 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0021-T05 | done | — | workplans/CUST-WP-0021-multi-host-repo-paths.md |
| task | CUST-WP-0022-T01 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T02 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T03 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T04 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T05 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T06 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T07 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T08 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T09 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0022-T10 | done | — | workplans/CUST-WP-0022-capability-requests.md |
| task | CUST-WP-0023-T01 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T02 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T03 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T04 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T05 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T06 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T07 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T08 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T09 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0023-T10 | done | — | workplans/CUST-WP-0023-tpsc.md |
| task | CUST-WP-0024-T01 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T02 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T03 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T04 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0024-T05 | done | — | workplans/CUST-WP-0024-repo-doi-gate.md |
| task | CUST-WP-0026-T01 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T02 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T03 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T04 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0026-T05 | done | — | workplans/CUST-WP-0026-distributed-consistency.md |
| task | CUST-WP-0027-T01 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T02 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T03 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T04 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0027-T05 | done | — | workplans/CUST-WP-0027-capability-request-dispute.md |
| task | CUST-WP-0029-T01 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T02 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T03 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T04 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T05 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T06 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T07 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | CUST-WP-0029-T08 | done | — | workplans/CUST-WP-0029-token-consumption-tracking.md |
| task | T01 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T02 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T03 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T04 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T05 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T06 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T07 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T08 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T09 | done | — | workplans/CUST-WP-0030-dashboard-entity-list-ux.md |
| task | T01 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T02 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T03 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T04 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T05 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T06 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | T07 | done | — | workplans/CUST-WP-0031-capability-registry.md |
| task | CUST-WP-0032-T01 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T02 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T03 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T04 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T05 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T06 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T07 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T08 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T09 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T10 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T11 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T12 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T13 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T14 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0032-T15 | done | — | workplans/CUST-WP-0032-haskell-build-machine.md |
| task | CUST-WP-0033-T01 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T02 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T03 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T04 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T05 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0033-T06 | done | — | workplans/CUST-WP-0033-interface-change-registry.md |
| task | CUST-WP-0039-T1 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T2 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T3 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T4 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T5 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | CUST-WP-0039-T6 | done | — | workplans/CUST-WP-0039-dashboard-poll-optimization.md |
| task | T01 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T02 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T03 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T04 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T05 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | T06 | done | — | workplans/CUST-WP-0040-state-hub-nats-activity-core-integration.md |
| task | CUST-WP-0041-T1 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0041-T2 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0041-T3 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0041-T4 | done | — | workplans/CUST-WP-0041-api-performance.md |
| task | CUST-WP-0043-T01 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T02 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T03 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T04 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T05 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T06 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T07 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T08 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0043-T09 | done | — | workplans/CUST-WP-0043-state-hub-repo-extraction.md |
| task | CUST-WP-0044-T01 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T02 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T03 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T04 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T05 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0044-T06 | done | — | workplans/CUST-WP-0044-daily-statehub-wsjf-triage.md |
| task | CUST-WP-0045-T01 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T02 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T03 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T04 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T05 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T06 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T07 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0045-T08 | done | — | workplans/CUST-WP-0045-activity-core-daily-triage-runner.md |
| task | CUST-WP-0046-T01 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T02 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T03 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T04 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T05 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T06 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0046-T07 | done | — | workplans/CUST-WP-0046-hourly-recently-on-scope-activity-core.md |
| task | CUST-WP-0047-T01 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T02 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T03 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T04 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T05 | cancel | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T06 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0047-T07 | done | — | workplans/CUST-WP-0047-ops-hub-service-inventory-now-view.md |
| task | CUST-WP-0048-T01 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T02 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T03 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T04 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T05 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T06 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T07 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0048-T08 | done | — | workplans/CUST-WP-0048-hub-core-state-hub-import-refactor.md |
| task | CUST-WP-0049-T01 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T02 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T03 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T04 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T05 | done | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0049-T06 | cancel | — | workplans/CUST-WP-0049-interhub-bootstrap-access-lane.md |
| task | CUST-WP-0050-T01 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T02 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T03 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T04 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T05 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T06 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T07 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T08 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T09 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T10 | cancel | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0050-T11 | done | — | workplans/CUST-WP-0050-repo-classification-registration-redesign.md |
| task | CUST-WP-0051-T01 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T02 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T03 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T04 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T05 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T06 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T07 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0051-T08 | done | — | workplans/CUST-WP-0051-infrastructure-stabilization-metaplan.md |
| task | CUST-WP-0052-T01 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T02 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T03 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T04 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T05 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T06 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0052-T07 | done | — | workplans/CUST-WP-0052-core-hub-fos-reset.md |
| task | CUST-WP-0053-T01 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T02 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T03 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T04 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0053-T05 | done | — | workplans/CUST-WP-0053-coordination-hygiene-improvements.md |
| task | CUST-WP-0055-T01 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T02 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T03 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T04 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T05 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T06 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T07 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0055-T08 | done | — | workplans/CUST-WP-0055-workplan-terminology-fleet-refactor.md |
| task | CUST-WP-0056-T01 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| task | CUST-WP-0056-T02 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| task | CUST-WP-0056-T03 | done | — | workplans/CUST-WP-0056-daily-todo-md-stale-review.md |
| task | CUST-WP-0059-T01 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T02 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T03 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T04 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T05 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0059-T06 | done | — | workplans/CUST-WP-0059-forgejo-ci-publish-cutover-tail.md |
| task | CUST-WP-0060-T01 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T02 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T03 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T04 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T05 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T06 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0060-T07 | done | — | workplans/CUST-WP-0060-work-record-canonization.md |
| task | CUST-WP-0061-T01 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T02 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T03 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T04 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T05 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0061-T06 | done | — | workplans/CUST-WP-0061-work-record-stage3-intake-promotion-index.md |
| task | CUST-WP-0062-T01 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T02 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T03 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T04 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T05 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0062-T06 | done | — | workplans/CUST-WP-0062-sbom-nexus-daily-catchup.md |
| task | CUST-WP-0063-T01 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T02 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T03 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T04 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0063-T05 | done | — | workplans/CUST-WP-0063-inbox-governance-packets.md |
| task | CUST-WP-0064-T01 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T02 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T03 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0064-T04 | done | — | workplans/CUST-WP-0064-sbom-controlled-scan-inputs.md |
| task | CUST-WP-0065-T01 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0065-T02 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0065-T03 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0065-T04 | done | — | workplans/CUST-WP-0065-reclassify-repos-and-guidance-docs-to-the-new-sector-domain.md |
| task | CUST-WP-0066-T01 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| task | CUST-WP-0066-T02 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| task | CUST-WP-0066-T03 | done | — | workplans/CUST-WP-0066-repository-qualified-ad-hoc-identifiers.md |
| task | CUST-WP-0067-T01 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T02 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T03 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T04 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T05 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T06 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T07 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T08 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T09 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0067-T10 | done | — | workplans/CUST-WP-0067-hub-authority-target-resolution.md |
| task | CUST-WP-0068-T01 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T02 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T03 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T04 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T05 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T06 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T07 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T08 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0068-T09 | done | — | workplans/CUST-WP-0068-cache-only-work-record-recovery.md |
| task | CUST-WP-0070-T01 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
| task | CUST-WP-0070-T02 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
| task | CUST-WP-0070-T03 | done | — | workplans/CUST-WP-0070-publication-repo-category.md |
| task | CUST-WP-0071-T01 | done | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T02 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T03 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T04 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0071-T05 | wait | — | workplans/CUST-WP-0071-measured-workload-sizing-and-weekly-review.md |
| task | CUST-WP-0072-T01 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0072-T02 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0072-T03 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0072-T04 | done | — | workplans/CUST-WP-0072-fleet-flavor-and-depends-on-backfill.md |
| task | CUST-WP-0073-T01 | done | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T02 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T03 | done | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T04 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0073-T05 | wait | — | workplans/CUST-WP-0073-agent-credential-separation.md |
| task | CUST-WP-0074-T01 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T02 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T03 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T04 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T05 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0074-T06 | done | — | workplans/CUST-WP-0074-qualified-wait-states.md |
| task | CUST-WP-0075-T01 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T02 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T03 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T04 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T05 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0075-T06 | done | — | workplans/CUST-WP-0075-dependency-graph-driven-unblocking.md |
| task | CUST-WP-0076-T01 | done | — | workplans/CUST-WP-0076-provisioning-window-cadence.md |
| task | CUST-WP-0077-T01 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T02 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T03 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T04 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0077-T05 | done | — | workplans/CUST-WP-0077-dependency-root-execution-wave.md |
| task | CUST-WP-0078-T01 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0078-T02 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0078-T03 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0078-T04 | done | — | workplans/CUST-WP-0078-security-collaboration-consolidation.md |
| task | CUST-WP-0079-T01 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T02 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T03 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T04 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T05 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T06 | wait | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0079-T07 | done | — | workplans/CUST-WP-0079-deterministic-security-flow.md |
| task | CUST-WP-0080-T01 | done | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T02 | done | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T03 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T04 | todo | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T05 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0080-T06 | progress | — | workplans/CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md |
| task | CUST-WP-0081-T01 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| task | CUST-WP-0081-T02 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| task | CUST-WP-0081-T03 | done | — | workplans/CUST-WP-0081-netkingdom-architecture-review-and-reuse.md |
| task | THE-WP-0001-T01 | done | — | workplans/THE-WP-0001-federation-interface.md |
| task | THE-WP-0001-T02 | done | — | workplans/THE-WP-0001-federation-interface.md |
| intake | CUST-IN-0001 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0002 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0003 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0004 | routed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0005 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0006 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0007 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0008 | closed | blue | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0009 | closed | blue | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0010 | closed | green | intake-legacy-suggestions-migration.md |
| intake | CUST-IN-0011 | routed | red | intakes.md |
| intake | CUST-IN-0012 | closed | green | intakes.md |
| intake | CUST-IN-0013 | closed | blue | intakes.md |
| intake | CUST-IN-0014 | closed | blue | intakes.md |
| intake | CUST-IN-0015 | open | blue | intakes.md |
| intake | CUST-IN-0016 | open | green | intakes.md |
| intake | CUST-IN-0017 | open | green | intakes.md |
| intake | CUST-IN-0018 | open | green | intakes.md |

View file

@ -0,0 +1,10 @@
[
{
"path": ".custodian-brief.md",
"index": "100644 6aad3a5788fad3abd60c0f1270f160062dc0168b 0\t.custodian-brief.md"
},
{
"path": "WORK-RECORDS.md",
"index": "100644 d535e0f2dcc3d454e041a8f1b547b5dea4493380 0\tWORK-RECORDS.md"
}
]

View file

@ -0,0 +1,45 @@
{
"exit_code": 0,
"commit": "16236ef479135df4361686aa69e9eeb987f33129",
"base": "90490002c16d353d675f4295f395d1917d30b9ef",
"files": [
{
"path": "docs/netkingdom-target-implementation.md",
"sha256": "798093602f9c11d086af960b97a97f1a1d116962136e01de1b336df8a9603cec"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/publication-summary.json",
"sha256": "69f2797c42f6ce33b731956457fc958f189202463845ae7a7c6cc930673651aa"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/public-digest-refinement.json",
"sha256": "770a5831999df8a8dc1b66dc65b738f58bedd4eb0dab56127f8d37b2b10107b2"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/publication-refinement.json",
"sha256": "957d1347771eecb16cb50713dc3cbaa43e96c3b3e162eb357be57bf12632a721"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/dirty-workplan-refinement.json",
"sha256": "c90e021d41a3218f8a1e3b13af00dc7cf3a383a8bf0efe52c583c7f0d63dfc97"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/metadata-reconstruction-failure.json",
"sha256": "ab943a848c811e22ec7ef4273bf2097ec20ac76902947491de01852c270b26b4"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/reconstruct_public_metadata.py",
"sha256": "ec20b3378ece29dcf7faf4ad5e610e1e85bfa260121f2b8c537665f9530466e0"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/recover_pending.py",
"sha256": "1cfad4dd82966efcea13dfe02d702a873a0fa6a422e654551fc1c9f530052a0b"
},
{
"path": "docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/verify_current.py",
"sha256": "8aaa076520f5c954ebca6b1cf5b28df7223b0162e4edba4c7307a5a80cff6c43"
}
],
"stdout": "[main 16236ef] Record verified consumer source and exact public evidence provenance\n 9 files changed, 793 insertions(+)\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/dirty-workplan-refinement.json\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/metadata-reconstruction-failure.json\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/public-digest-refinement.json\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/publication-refinement.json\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/publication-summary.json\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/reconstruct_public_metadata.py\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/recover_pending.py\n create mode 100644 docs/changes/netkingdom-target-2026-10-17/owner-followthrough-publication-r1/verify_current.py\n",
"stderr": "{\"blocked_blob_ids\": [], \"scanned_blobs\": 9, \"scanner_sha256\": \"88f91962aa2f93ac6ab281d553b9e125f5197bbbce38f9f2437f7299c32e5509\", \"scanner_version\": \"8.30.1\", \"scope\": \"changed-staged-blobs\", \"status\": \"clean\"}\n"
}

View file

@ -0,0 +1,47 @@
{
"head": "90490002c16d353d675f4295f395d1917d30b9ef",
"completed": [
{
"batch": 1,
"commit": "518369dbee08b7f7690ee70d0cbc405ea4158397",
"files": 18,
"publication": "verified as unchanged ancestor of the exact projected notes commit"
},
{
"batch": 2,
"commit": "1513dad6528ab766a7631e88d9c04be4bc605f42",
"files": 2,
"required_consistency": "passed"
},
{
"batch": 3,
"commit": "6548540fd46c7fcd365f399516a5d36e38d1e7fd",
"files": 18
},
{
"batch": 4,
"commit": "14670441cd567621ca86e466b113944ec8363798",
"files": 18
},
{
"batch": 5,
"commit": "11a485f1dd9267cc63f9beb0436aa17c0d3317cb",
"files": 8
},
{
"batch": 6,
"commit": "31f5cbf7b52c909ceebfdfc39ef0100908f63d5b",
"files": 1
},
{
"batch": 7,
"commit": "0caed1f5300c1f5a6ededcca7948b120a126b85d",
"files": 18
},
{
"batch": 8,
"commit": "90490002c16d353d675f4295f395d1917d30b9ef",
"files": 13
}
]
}

View file

@ -0,0 +1,120 @@
"""Verify this source checkpoint, current primary bindings and original edits."""
from pathlib import Path
import datetime
import hashlib
import json
import re
import subprocess
import urllib.request
ROOT = Path('/home/worsch/the-custodian')
HERE = Path(__file__).resolve().parent
summary = json.loads((HERE / 'publication-summary.json').read_text())
def git(root, *args):
return subprocess.check_output(['git', '-C', str(root), *args],
stderr=subprocess.PIPE, timeout=45).decode().strip()
def get(path):
with urllib.request.urlopen('http://127.0.0.1:8000' + path, timeout=20) as response:
return json.load(response)
snapshot = summary['root_commit'].removeprefix('git:')
head = git(ROOT, 'rev-parse', 'HEAD')
projection = json.loads((HERE / 'close-sync.json').read_text())
receipt = projection['receipt']
assert projection['ok'] and projection['status'] in ('applied', 'noop')
assert projection['commit'] == receipt['expected_commit'] == receipt['derived_commit'] == head
assert (receipt['instance_role'], receipt['instance_label']) == ('primary', 'railiance01')
assert receipt['outcome']['counts']['retired'] == 0
assert git(ROOT, 'ls-remote', 'origin', 'refs/heads/main').split()[0] == head
exceptions = {row['path']: row['sha256'] for row in summary['current_source_exceptions']}
for row in summary['files']:
committed = subprocess.check_output(['git', '-C', str(ROOT), 'show', snapshot + ':' + row['path']])
assert 'sha256:' + hashlib.sha256(committed).hexdigest() == row['sha256']
current_expected = exceptions.get(row['path'], row['sha256'])
assert 'sha256:' + hashlib.sha256((ROOT / row['path']).read_bytes()).hexdigest() == current_expected
current_committed = subprocess.check_output(['git', '-C', str(ROOT), 'show', head + ':' + row['path']])
assert 'sha256:' + hashlib.sha256(current_committed).hexdigest() == current_expected
protected = json.loads((HERE.parent / 'source-corrections-publication-r1/protected-state.json').read_text())
for row in protected:
assert hashlib.sha256((ROOT / row['path']).read_bytes()).hexdigest() == row['sha256']
assert git(ROOT, 'ls-files', '--stage', '--', row['path']) == row['index']
assert set(git(ROOT, 'diff', '--name-only', 'HEAD').splitlines()) == {r['path'] for r in protected}
for owner, expected in summary['expected_owner_heads'].items():
path = ROOT.parent / owner
assert git(path, 'rev-parse', 'HEAD') == expected.removeprefix('git:')
assert git(path, 'ls-remote', 'origin', 'refs/heads/main').split()[0] == expected.removeprefix('git:')
checks = []
for owner, filename, count in [
('the-custodian', 'CUST-WP-0080-netkingdom-security-blueprint-and-gap-closure.md', 6),
('ops-warden', 'WARDEN-WP-0044-security-case-follow-through.md', 4),
('net-kingdom', 'NK-WP-0048-security-case-types.md', 39),
('key-cape', 'KEY-WP-0039-openbao-negative-probe-tokens.md', 3),
('railiance-platform', 'RPF-WP-0060-browser-case-platform-bindings.md', 4),
]:
text = (ROOT.parent / owner / 'workplans' / filename).read_text()
wp = re.search(r'state_hub_workstream_id: "([^"]+)"', text).group(1)
tasks = get('/tasks/?workplan_id=' + wp)
assert len(tasks) == count
by_uuid = {t['id']: t for t in tasks}
rows = []
for block in re.findall(r'```task\n.*?```', text, re.S):
record = re.search(r'^id: (.+)$', block, re.M).group(1)
uid = re.search(r'state_hub_task_id: "([^"]+)"', block).group(1)
status = re.search(r'^status: (.+)$', block, re.M).group(1)
actual = by_uuid[uid]
assert (actual['record_id'], actual['status'], actual['workplan_id']) == (record, status, wp)
rows.append({'record_id': record, 'uuid': uid, 'status': status})
assert len(rows) == count
item = {'owner': owner, 'task_count': count, 'workplan_uuid': wp, 'bindings': rows}
if owner == 'net-kingdom':
selected = {row['record_id']: row['uuid'] for row in rows}
dependencies = get('/workplans/' + wp + '/dependencies/')
matching = [d for d in dependencies if d.get('from_task_id') == selected['NK-WP-0048-T39']
and d.get('to_task_id') == selected['NK-WP-0048-T38']]
assert len(matching) == 1 and matching[0]['relationship_type'] == 'starts_after'
# Existing source edges retain their creation commit. Reconciliation
# only creates/removes edges; a status-only commit does not rewrite it.
creation = '6201a1d2de282a218fde8d81b32f790a526942ef'
current = summary['expected_owner_heads'][owner].removeprefix('git:')
assert matching[0]['source_owner'] == 'forge:net-kingdom'
assert matching[0]['source_commit'] == creation
assert git(ROOT.parent / owner, 'merge-base', creation, current) == creation
wp_path = 'workplans/' + filename
created_text = subprocess.check_output(
['git', '-C', str(ROOT.parent / owner), 'show', creation + ':' + wp_path]).decode()
def dependency_declaration(value):
blocks = re.findall(r'```task\n.*?```', value, re.S)
task = next(b for b in blocks if 'id: NK-WP-0048-T39\n' in b)
return re.search(r'^depends_on: (.+)$', task, re.M).group(1)
assert dependency_declaration(text) == dependency_declaration(created_text) == '[NK-WP-0048-T38]'
first_projection = json.loads((HERE.parent / 'nk48-consumer-gate-implementation-r1/projection.json').read_text())
current_projection = json.loads((HERE.parent / 'nk48-release-build-progress-r1/projection.json').read_text())
for evidence, commit in ((first_projection, creation), (current_projection, current)):
assert evidence['ok'] and evidence['status'] == 'applied'
assert evidence['commit'] == evidence['receipt']['expected_commit'] == evidence['receipt']['derived_commit'] == commit
assert evidence['receipt']['instance_role'] == 'primary'
assert evidence['receipt']['instance_label'] == 'railiance01'
assert current_projection['receipt']['outcome']['counts']['created_edges'] == 0
assert current_projection['receipt']['outcome']['counts']['removed_edges'] == 0
item['release_dependency'] = matching[0]
checks.append(item)
for name in [r['path'] for r in [json.loads((HERE / 'public-digest-refinement.json').read_text()),
*json.loads((HERE / 'public-digest-refinement.json').read_text())['additional_metadata_derivatives']]]:
subprocess.run(['python3', str(HERE / 'reconstruct_public_metadata.py'),
str((ROOT / name).relative_to(HERE.parent))], check=True, stdout=subprocess.DEVNULL)
result = {'status': 'verified', 'root_commit': 'git:' + head,
'frozen_publication_snapshot': 'git:' + snapshot, 'snapshot_files_verified': len(summary['files']),
'current_document_delta_verified': list(exceptions), 'source_primary_bindings': checks,
'task_bindings_verified': sum(c['task_count'] for c in checks), 'protected_original_paths': len(protected),
'owner_heads': summary['expected_owner_heads'], 'public_original_metadata_reconstructions': 4,
'all_sixteen_native_gates': 'open', 'goal': 'active',
'observed_at': datetime.datetime.now(datetime.timezone.utc).isoformat()}
(HERE / 'final-verification-r2.json').write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps({'status': 'verified', 'root_commit': head, 'files': len(summary['files']),
'task_bindings': result['task_bindings_verified'], 'preserved_original_paths': len(protected),
'public_original_metadata_reconstructions': 4, 'goal': 'active'}))

View file

@ -0,0 +1,144 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — railiance-platform
**Domain:** financials
**Last synced:** 2026-10-17 02:33 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Prepare the exact seven private-source reader for reuse-surface
Progress: 4/5 done | workplan_id: `b55a8aec-7457-563b-990b-4a55ca04d2f6`
**Open tasks:**
- ! Rotate or revoke dedicated seven-source reader within 90 days `f0780e75`
### Coordinate audit-core temporary custody and recovery exercises
Progress: 3/4 done | workplan_id: `f4640325-e89c-591d-b58e-ec6b087900ac`
**Open tasks:**
- ! 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.)*
### Admit hourly receiver cadence and exact Qonto collector custody
Progress: 3/4 done | workplan_id: `d57eeafb-c406-5e1e-841c-5d1288aacabf`
**Open tasks:**
- ► Collect actual producer and independent observer acceptance `7d8e5315`
### Let the hub-writer caller prove its own reach and pick the workload role in the OpenBao UI
Progress: 3/4 done | workplan_id: `04f7c4aa-0a24-5d61-994d-2487ca6bfb91`
**Open tasks:**
- ► Deploy the overlay `94cdfd1c`
### 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: Owner work first: NetKingdom packages the complete resolver sequence as one receipt-producing command; then it enters the attended-window checklist (D1 standing rule).)*
- ! T06 — Publish the Railiance/OpenBao custody handoff `3b9748c4`
*(wait: 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.)*
### Admit the Playwright pilot and establish scoped consumer group access
Progress: 5/6 done | workplan_id: `31933d08-c877-5fda-aff9-6220c3705c92`
**Open tasks:**
- ! Connect platform identities and prove managed consumer operation `a82762eb`
*(wait: User admitted all identified platform humans and coding agents; their supported Kubernetes authentication/group-mapping route remains unimplemented.)*
### ArgoCD phase B: adopt the four existing Applications on railiance01
Progress: 5/8 done | workplan_id: `98140775-3b9a-5cf9-9af6-722502d487dc`
**Open tasks:**
- ! Adopt issue-core (from rapp-issue-core) `0e619658`
- ! Adopt external-secrets (highest risk) `4ca54868`
*(wait: Ordered D2 adoption: issue-core T04 must provide automated Argo health/diff and private/authenticated smoke proof; target-revenue T05 is already proven. No further founder approval is required.)*
- ! Turn the root back to the production lane `844ff115`
*(wait: Restore prune/selfHeal once T03-T06 have automated proof; no further founder go-ahead (D2).)*
### 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: Merge approved by D2; runs once T03 diff and root automation (RPF-WP-0044-T07) are proven.)*
- ! Prepare the Application and the dry-run evidence `2afb1aac`
*(wait: 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.)*
### 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: Durable scheduled caller, verified inventory and quota/retention gates remain open; September 15 prohibits expiration and scheduled secondary cutover.)*
### 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: 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.)*
- ! 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`
### Implement reviewed credential lanes with separate owner gates
Progress: 7/13 done | workplan_id: `975db491-5412-5e27-8e34-14a2417bb039`
**Open tasks:**
- ! Accept and provision secrets-engine service JWT login `e0c82ea9`
*(wait: Agent work, not a human gate: claim refusals (KEY-WP-0039-T02 probe) and capabilities-self deny, then ops-mason MASON-WP-0009-T05.)*
- ! Implement the platform operator-write CCR contract and Fluid lane `f47e5bc5`
*(wait: Founder session done 2026-10-09; waits on ops-mason verification (MASON-WP-0005-T05) and the FT-WP-0002 owner salt CAS probe.)*
- ! Dispose of the ungoverned whynot-design npm duplicate `0dd7c9a5`
*(wait: 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.)*
- ! Admit the two factory audit-sender custody lanes `0ef52c26`
*(wait: Approved conditionally (ab85ae8a, standing change rule): executes once independent archive readback and the interim bearer revocation receipt exist; owner work with audit-core.)*
- ! Retire the approval-engine-operator client delivery and custody (CCR-2026-0018) `af2d4dba`
*(wait: 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.)*
- ► Implement and prove the separately approved task execution entitlement `914bf383`
### 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:** 14 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.

View file

@ -0,0 +1,176 @@
# Work Records — railiance-platform
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| 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-0015 | active | — | 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 |
| workplan | RPF-WP-0035 | blocked | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
| workplan | RPF-WP-0036 | blocked | — | workplans/RPF-WP-0036-platform-service-assurance.md |
| workplan | RPF-WP-0038 | blocked | — | workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md |
| workplan | RPF-WP-0039 | finished | — | workplans/RPF-WP-0039-vergabe-demo-company-database.md |
| workplan | RPF-WP-0040 | finished | — | workplans/RPF-WP-0040-keycape-factor-credential-custody.md |
| workplan | RPF-WP-0041 | finished | — | workplans/RPF-WP-0041-state-hub-105m-not-provisioning.md |
| workplan | RPF-WP-0042 | finished | — | workplans/RPF-WP-0042-informed-decision-sitting-requester.md |
| workplan | RPF-WP-0043 | blocked | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md |
| workplan | RPF-WP-0044 | blocked | — | workplans/RPF-WP-0044-argocd-phase-b-adopt-existing-applications.md |
| workplan | RPF-WP-0045 | finished | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| workplan | RPF-WP-0046 | finished | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| 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-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 | proposed | — | 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 |
| 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-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-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 |
| task | RPF-WP-0025-T02 | done | — | workplans/RPF-WP-0025-openbao-operator-only-access.md |
| task | RPF-WP-0025-T03 | done | — | workplans/RPF-WP-0025-openbao-operator-only-access.md |
| task | RPF-WP-0027-T01 | done | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md |
| task | RPF-WP-0027-T02 | done | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md |
| task | RPF-WP-0027-T03 | wait | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md |
| task | RPF-WP-0027-T04 | done | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md |
| task | RPF-WP-0027-T05 | done | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md |
| task | RPF-WP-0027-T06 | wait | — | workplans/RPF-WP-0027-keycape-live-secret-exposure-recovery.md |
| task | RPF-WP-0029-T01 | done | — | workplans/RPF-WP-0029-backup-credential-default-removal.md |
| task | RPF-WP-0029-T02 | done | — | workplans/RPF-WP-0029-backup-credential-default-removal.md |
| task | RPF-WP-0029-T03 | done | — | workplans/RPF-WP-0029-backup-credential-default-removal.md |
| task | RPF-WP-0035-T01 | done | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
| task | RPF-WP-0035-T02 | wait | — | workplans/RPF-WP-0035-credential-lane-implementation.md |
| 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-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-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 |
| task | RPF-WP-0036-T04 | wait | — | workplans/RPF-WP-0036-platform-service-assurance.md |
| task | RPF-WP-0036-T05 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md |
| task | RPF-WP-0036-T06 | wait | — | workplans/RPF-WP-0036-platform-service-assurance.md |
| task | RPF-WP-0036-T07 | done | — | workplans/RPF-WP-0036-platform-service-assurance.md |
| task | RPF-WP-0038-T01 | done | — | workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md |
| task | RPF-WP-0038-T02 | done | — | workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md |
| task | RPF-WP-0038-T03 | done | — | workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md |
| task | RPF-WP-0038-T04 | wait | — | workplans/RPF-WP-0038-forgejo-scaleway-primary-coverage.md |
| task | RPF-WP-0039-T01 | done | — | workplans/RPF-WP-0039-vergabe-demo-company-database.md |
| task | RPF-WP-0039-T02 | done | — | workplans/RPF-WP-0039-vergabe-demo-company-database.md |
| task | RPF-WP-0040-T01 | done | — | workplans/RPF-WP-0040-keycape-factor-credential-custody.md |
| task | RPF-WP-0040-T02 | done | — | workplans/RPF-WP-0040-keycape-factor-credential-custody.md |
| task | RPF-WP-0040-T03 | done | — | workplans/RPF-WP-0040-keycape-factor-credential-custody.md |
| task | RPF-WP-0040-T04 | done | — | workplans/RPF-WP-0040-keycape-factor-credential-custody.md |
| task | RPF-WP-0041-T01 | done | — | workplans/RPF-WP-0041-state-hub-105m-not-provisioning.md |
| task | RPF-WP-0042-T01 | done | — | workplans/RPF-WP-0042-informed-decision-sitting-requester.md |
| task | RPF-WP-0042-T02 | done | — | workplans/RPF-WP-0042-informed-decision-sitting-requester.md |
| task | RPF-WP-0043-T01 | done | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md |
| task | RPF-WP-0043-T02 | done | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md |
| task | RPF-WP-0043-T03 | wait | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md |
| task | RPF-WP-0043-T04 | wait | — | workplans/RPF-WP-0043-policy-nexus-argocd-onboarding.md |
| 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 | wait | — | 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-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-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 |
| task | RPF-WP-0045-T04 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| task | RPF-WP-0045-T05 | cancel | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| task | RPF-WP-0045-T06 | done | — | workplans/RPF-WP-0045-eso-kubernetes-auth-activity-core-email-connect.md |
| task | RPF-WP-0046-T01 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T02 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T03 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T04 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T05 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0046-T06 | done | — | workplans/RPF-WP-0046-eso-database-token-renewal.md |
| task | RPF-WP-0047-T01 | done | — | workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md |
| task | RPF-WP-0047-T02 | done | — | workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md |
| task | RPF-WP-0047-T03 | done | — | workplans/RPF-WP-0047-bao-coulomb-social-notice-page.md |
| task | RPF-WP-0048-T01 | done | — | workplans/RPF-WP-0048-activity-core-gitops-adoption.md |
| task | RPF-WP-0048-T02 | wait | — | workplans/RPF-WP-0048-activity-core-gitops-adoption.md |
| task | RPF-WP-0048-T03 | done | — | workplans/RPF-WP-0048-activity-core-gitops-adoption.md |
| task | RPF-WP-0049-T01 | done | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md |
| task | RPF-WP-0049-T02 | done | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md |
| task | RPF-WP-0049-T03 | done | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md |
| task | RPF-WP-0049-T04 | done | — | workplans/RPF-WP-0049-platform-root-custody-record-delivery.md |
| 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-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 | progress | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T02 | todo | — | workplans/RPF-WP-0053-coulomb-paired-backup-admission.md |
| task | RPF-WP-0053-T03 | todo | — | 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 |
| intake | RPF-IN-0001 | closed | — | intakes/intakes.md |
| intake | RPF-IN-0002 | open | — | intakes/intakes.md |
| intake | RPF-IN-0003 | open | — | intakes/intakes.md |
| intake | RPF-IN-0004 | open | — | intakes/intakes.md |

View file

@ -0,0 +1,10 @@
[
{
"path": ".custodian-brief.md",
"index": "100644 1c9e635012d170c76646bf76d5202f6de163a643 0\t.custodian-brief.md"
},
{
"path": "WORK-RECORDS.md",
"index": "100644 dc7f3aab6de945558ddb9e48a482a9d92d734c3b 0\tWORK-RECORDS.md"
}
]

View file

@ -0,0 +1,50 @@
# Platform delivery backend source preparation r1
Prepared for existing NK-WP-0048-T16 / RPF-1.3-T4. The exact patch targets
railiance-platform `104ed90f42a9a86bf9fda343b49b65a85c9ee4b9`. No canonical
Platform, NetKingdom or Warden source was changed; no commit, native action,
credential read, grant, authority, acknowledgement or owner acceptance occurred.
`railiance-platform.patch` adds a concrete command implementation and meaningful
offline regressions, and extends the existing contract. `prepared-source/`
contains the exact resulting files. `source-pins.json` pins inputs, reviewed
facilities and output hashes. `regressions.json` records final runs and the one
initial test assertion failure. Both final suites pass: 17 command regressions
and 8 existing fixed-contract tests. Delivery tests clear the environment, forbid
socket creation and intercept subprocesses; no HOME, ambient credentials or
network supply fixture authority.
The code creates only the fixed ExternalSecret after validating a preexisting
fixed Store, reports matching existing objects unchanged, refuses conflicting
objects, waits boundedly for Ready, captures public UID/resourceVersion, and
deletes only verified instance-created objects with UID **and** resourceVersion
API preconditions. Lost responses and uncertain cleanup preserve reconciliation
obligations. The receipt has the existing closed metadata-only adapter shape.
Receipt construction by these private mechanics is not authenticated evidence.
| Facility | Existing source and actual status |
| --- | --- |
| Fixed Store/ExternalSecret metadata | Real `keycape_issuer_eso.documents` and `require_fixed_store`; reused without Store creation or caller overrides. Template SHA-256 remains `e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743`. |
| Contained command execution | Real `state_hub_preflight_lane.command`; reused for actual kubectl argv, fixed error containment and process timeout. Source-preparation transport has empty env and `/dev/null` kubeconfig. |
| Safe remote transport | Real `remote_exec.run_remote` and custody-projection Operator exist for attended procedures; reviewed, deliberately not borrowed as an authenticated delivery session. |
| Caller case verification | Real mint broker `check_case` checks exact bytes/reference, materialized/confidential/client match and @1.1. It does not acknowledge @1.3 or authenticate a delivery caller. **Missing for this delivery binding.** |
| Current human aal2 delivery caller/session | Mint-lane OpenBao lookup-self and CCR-0043 issuer binding are real but mint-specific. No scoped Kubernetes delivery credential/session issuer exists here. **Missing; production factory refuses before dispatch.** |
| Durable admission/recovery journal | `task_execution_journal.Journal` exists and explicitly cannot authorize native work. No lane-specific trusted admission/consume binding or creation-to-evidence durable transaction exists. **Missing for this lane; not replaced with in-memory provenance.** |
| Evidence publication/authentication | Credential-change evidence writes and State Hub progress logging exist for their original purpose; neither authenticates delivery adapter payloads. **Missing.** No always-true verifier or local receipt oracle is added. |
| Per-attempt delivery session revocation | OpenBao revoke-self exists in other lanes, but there is no actual delivery session/accessor/owner identity binding to revoke or prove revoked. **Missing.** |
| Adapter integration | Existing frozen deliver-secret adapter still expects owner deliver/remove/publish/authenticate/revoke_session. `production_bindings()` deliberately refuses rather than provide any forged binding. Descriptor/source digest remains unchanged. |
This is a concrete backend algorithm prepared for owner review, **not completion
of RPF-1.3-T4**. Real authenticated callable production capabilities remain open.
Future work must supply the missing existing-obligation facilities with reviewed
authority, exact admitted case/client binding, one-shot durable journaling,
authenticated evidence and actual per-attempt revocation. Removing the deliberate
transport restrictions alone would not satisfy those obligations.
The shared ESO service account/role pattern is grounded only in CCR-0041. No
family-wide role grant, new Store, service, admission authority or native access
is inferred. Target Secret cleanup by Kubernetes/ESO remains outside these
commands; no direct Secret read/list/delete occurs. A lost create response with
unknown UID requires owner reconciliation at the fixed resource name, never a
blind delete. Process restart never recreates deletion provenance from a supplied
revision or unsigned receipt.

View file

@ -0,0 +1,114 @@
# 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.
## Prepared delivery mechanics (still unavailable as a production lane)
`scripts/keycape_issuer_delivery.py` now contains concrete command mechanics for
the existing RPF-1.3-T4 obligation. It reuses `keycape_issuer_eso.documents()` and
`require_fixed_store()` and the existing contained `state_hub_preflight_lane.command()`
transport. Only exact named public ESO resources are read. It proves absence
with successful empty `get --ignore-not-found`, creates only the ExternalSecret,
compares an existing object's full spec, and polls at most ten times for Ready
with matching spec and stable UID. Each request has a 5s kubectl request timeout
inside the existing 60s process bound; nine sleeps of 1s bound each poll loop.
Controller resourceVersion updates during successful readiness are captured.
Removal is permitted only for that instance's verified case-created revision,
rechecks spec/UID/resourceVersion, uses Kubernetes DeleteOptions with both UID
and resourceVersion preconditions, and confirms absence with bounded polling.
Existing matching objects return changed=false and are never rollback candidates.
Creation response loss, readiness failure after creation, replacement, delete
conflict or unconfirmed deletion raises a fixed uncertainty diagnostic and keeps
the last verified revision when known. No retry adopts an uncertain create; no
replacement is deleted. An unknown create outcome requires owner reconciliation
using the fixed resource name even when no UID could be recovered. Target Secret
garbage collection by ESO/Kubernetes is outside this helper; it never reads or
directly deletes Secret objects. There is no claim that deletion proves target
Secret disappearance.
The callable `production_bindings()` **always refuses before any command**.
The private mechanics use empty environment and `/dev/null` kubeconfig and
therefore cannot borrow the caller's native session. They are source preparation,
not an adapter/evidence authority. Tests intercept the actual subprocess call;
they do not provide production callbacks or attestations. A real lane requires
an owner-reviewed authenticated human aal2 session, acknowledged case revision
and caller binding, scoped Kubernetes credentials, one-shot durable admission
journaling and authenticated evidence publication/verification, plus real
per-attempt session revocation and recovery. None exists for this lane at the
pinned source. The generic task_execution_journal module is real storage but
explicitly does not admit native work; it is not silently repurposed here.
The mint broker's case checker and OpenBao lookup-self are real for its existing
mint lane, not delivery authentication. The attended custody-projection SSH
transport and credential-change evidence writer likewise confer no delivery
lane authority. No unsigned receipt or `True` oracle replaces these facilities.
Consequently RPF-1.3-T4 remains open for real capability binding and owner review;
this patch completes only concrete ESO command/state mechanics and regressions.
No ESO role, policy, Store, admission service, grant, catalog acceptance, native
permission or owner descriptor acceptance is created or inferred.

View file

@ -0,0 +1,182 @@
"""RPF-1.3-T4 concrete source-preparation mechanics; NO production admission.
Only public ESO objects are fetched. The production factory refuses before
dispatch: no authenticated delivery session/evidence service currently exists.
Private mechanics are exercised by fake-command tests, never a grant boundary.
"""
from __future__ import annotations
from copy import deepcopy
import json
import re
import time
from keycape_issuer_eso import documents, require_fixed_store, Refused
from state_hub_preflight_lane import command, LaneError
POLL_COUNT = 10
POLL_DELAY = 1
API = '/apis/external-secrets.io/v1'
class Uncertain(Refused):
"""No success receipt: retain exact metadata for owner reconciliation."""
def __init__(self, reason, revision=None):
super().__init__(reason)
self.revision = deepcopy(revision)
def production_bindings(*args, **kwargs):
# Neither Warden invocation fields, an operator's kubeconfig, an unsigned
# local receipt nor mint-lane lookup-self authenticates THIS lane/caller.
raise Refused('delivery_admission_session_journal_and_evidence_unavailable')
def _revision(obj):
meta = obj.get('metadata', {})
result = {'uid': meta.get('uid'), 'resource_version': meta.get('resourceVersion')}
if any(not isinstance(x, str) or not re.fullmatch(r'[A-Za-z0-9._:-]{1,192}', x)
for x in result.values()):
raise Refused('public_revision_invalid')
return result
class _DeliveryMechanics:
"""Concrete fixed kubectl algorithm, not an authenticated owner capability.
No caller resource, remoteRef, namespace, store or path argument is accepted.
The deliberately unauthenticated transport cannot load ambient credentials.
A reviewed session transport must replace it only after real lane admission,
one-shot durable journaling, evidence and revocation are implemented.
"""
def __init__(self, client_id):
self.client_id = client_id
self.store, self.external = documents(client_id)
self.created = None
self.pending = False
def _call(self, args, payload=None):
try:
result = command(['kubectl', '--kubeconfig=/dev/null',
'--request-timeout=5s', *args],
payload=payload, env={})
if len(result.stdout) > 262144:
raise Refused('public_response_too_large')
if not result.stdout.strip():
return None
value = json.loads(result.stdout)
if not isinstance(value, dict):
raise Refused('public_response_shape')
return value
except (LaneError, OSError, ValueError, TimeoutError):
raise Refused('public_command_failed') from None
except Exception:
# Includes subprocess timeouts; never expose stdout/stderr or argv.
raise Refused('public_command_failed') from None
def _get(self, store=False):
obj = self.store if store else self.external
resource = 'clustersecretstores.external-secrets.io' if store else 'externalsecrets.external-secrets.io'
args = [] if store else ['-n', 'sso']
return self._call([*args, 'get', resource, obj['metadata']['name'],
'--ignore-not-found=true', '-o', 'json'])
def _match(self, obj):
if not isinstance(obj, dict):
raise Refused('delivery_missing')
metadata = obj.get('metadata', {})
if (obj.get('apiVersion') != self.external['apiVersion']
or obj.get('kind') != 'ExternalSecret'
or metadata.get('name') != self.external['metadata']['name']
or metadata.get('namespace') != 'sso'
or metadata.get('deletionTimestamp')
or obj.get('spec') != self.external['spec']):
raise Refused('delivery_conflict')
return _revision(obj)
@staticmethod
def _ready(obj):
status = obj.get('status', {})
conditions = status.get('conditions', []) if isinstance(status, dict) else []
return isinstance(conditions, list) and any(
isinstance(c, dict) and c.get('type') == 'Ready' and c.get('status') == 'True'
for c in conditions)
def _receipt(self, operation, before, after):
return {'schema': 'netkingdom.deliver-secret.v1', 'operation': operation,
'namespace': 'sso', 'name': self.external['metadata']['name'],
'custody_reference': 'platform/workloads/keycape/' + self.client_id,
'key_names': ['client-secret'], 'values_emitted': False,
'changed': before is None if operation == 'deliver' else True,
'before': before, 'after': after}
def deliver(self):
if self.pending or self.created is not None:
raise Refused('attempt_already_used_or_unreconciled')
# A successful empty response proves absence; errors never do.
store = self._get(store=True)
require_fixed_store(store, self.client_id)
if store.get('metadata', {}).get('deletionTimestamp'):
raise Refused('store_terminating')
old = self._get()
before = self._match(old) if old is not None else None
uid = before['uid'] if before else None
if old is None:
# Set before dispatch: a lost create response MUST NOT be retried,
# adopted, or claimed as a pre-existing no-change result.
self.pending = True
try:
created = self._call(['-n', 'sso', 'create', '-f', '-', '-o', 'json'], self.external)
self.created = self._match(created)
uid = self.created['uid']
except Refused:
raise Uncertain('create_outcome_unverified') from None
try:
for index in range(POLL_COUNT):
current = self._get()
after = self._match(current)
if after['uid'] != uid:
raise Refused('delivery_replaced')
if self._ready(current):
if self.created is not None:
self.created = after
self.pending = False
return self._receipt('deliver', before, after)
if index + 1 < POLL_COUNT:
time.sleep(POLL_DELAY)
raise Refused('delivery_ready_timeout')
except Refused:
if self.created is not None:
raise Uncertain('created_delivery_requires_reconciliation', self.created) from None
raise
def remove(self, revision):
# An arbitrary adapter argument alone is never deletion provenance.
if self.pending or self.created is None or revision != self.created:
raise Refused('no_verified_case_creation')
try:
if self._match(self._get()) != self.created:
raise Refused('created_revision_changed')
except Refused:
raise Uncertain('created_revision_changed', self.created) from None
uri = API + '/namespaces/sso/externalsecrets/' + self.external['metadata']['name']
options = {'apiVersion': 'v1', 'kind': 'DeleteOptions',
'preconditions': {'uid': self.created['uid'],
'resourceVersion': self.created['resource_version']}}
prior = deepcopy(self.created)
self.pending = True
try:
self._call(['delete', '--raw', uri, '-f', '-'], options)
for index in range(POLL_COUNT):
current = self._get()
if current is None:
self.created = None
self.pending = False
return self._receipt('remove', prior, None)
if _revision(current)['uid'] != prior['uid']:
raise Refused('replacement_preserved')
if index + 1 < POLL_COUNT:
time.sleep(POLL_DELAY)
raise Refused('delete_unconfirmed')
except Refused:
raise Uncertain('cleanup_requires_reconciliation', prior) from None

View file

@ -0,0 +1,196 @@
"""Offline command-level regressions. No HOME/env/network/credential access."""
from copy import deepcopy
import json
import os
from pathlib import Path
import subprocess
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
import keycape_issuer_delivery as lane
class DeliveryTests(unittest.TestCase):
def setUp(self):
self.environment = patch.dict(os.environ, {}, clear=True)
self.environment.start()
self.addCleanup(self.environment.stop)
self.network = patch('socket.socket', side_effect=AssertionError('network forbidden'))
self.network.start()
self.addCleanup(self.network.stop)
self.m = lane._DeliveryMechanics('forgejo')
self.store = self.resource(self.m.store)
self.obj = self.resource(self.m.external)
self.calls = []
self.responses = []
self.native = patch('subprocess.run', side_effect=self.run_command)
self.native.start()
self.addCleanup(self.native.stop)
self.sleep = patch.object(lane.time, 'sleep')
self.sleep.start()
self.addCleanup(self.sleep.stop)
def resource(self, template, uid='uid-1', rv='10', ready=True):
obj = deepcopy(template)
obj['metadata'].update(uid=uid, resourceVersion=rv)
obj['status'] = {'conditions': [{'type': 'Ready', 'status': str(ready)}]}
return obj
def run_command(self, argv, **kw):
self.calls.append((deepcopy(argv), deepcopy(kw)))
self.assertEqual(kw['env'], {})
self.assertEqual(argv[:3], ['kubectl', '--kubeconfig=/dev/null', '--request-timeout=5s'])
self.assertTrue(kw['capture_output'])
self.assertEqual(kw['timeout'], 60)
if not self.responses:
self.fail('unexpected command dispatch')
result = self.responses.pop(0)
if isinstance(result, Exception):
raise result
if result == 'error':
return subprocess.CompletedProcess(argv, 1, b'PRIVATE PROVIDER VALUE', b'PRIVATE ERROR')
return subprocess.CompletedProcess(argv, 0, b'' if result is None else json.dumps(result).encode(), b'')
def create(self):
self.responses += [self.store, None, self.obj, self.obj]
return self.m.deliver()
def test_production_factory_fails_before_dispatch(self):
with self.assertRaisesRegex(lane.Refused, 'unavailable'):
lane.production_bindings(case_source=b'arbitrary', admitted=object())
self.assertEqual(self.calls, [])
def test_absent_create_ready_and_uid_rv_precondition_delete(self):
receipt = self.create()
self.assertTrue(receipt['changed'])
self.assertIsNone(receipt['before'])
self.assertEqual(receipt['after'], {'uid': 'uid-1', 'resource_version': '10'})
self.responses += [self.obj, {'kind': 'Status'}, None]
removed = self.m.remove(receipt['after'])
self.assertEqual(removed['operation'], 'remove')
delete = next(c for c in self.calls if 'delete' in c[0])
options = json.loads(delete[1]['input'])
self.assertEqual(options['preconditions'], {'uid': 'uid-1', 'resourceVersion': '10'})
self.assertEqual(delete[0][5], '/apis/external-secrets.io/v1/namespaces/sso/externalsecrets/keycape-forgejo-client')
self.assertFalse(any('secret' == a or 'secrets' == a for c in self.calls for a in c[0]))
def test_matching_existing_no_change_never_delete(self):
self.responses = [self.store, self.obj, self.obj]
receipt = self.m.deliver()
self.assertFalse(receipt['changed'])
with self.assertRaises(lane.Refused):
self.m.remove(receipt['after'])
self.assertFalse(any('create' in a or 'delete' in a for a, k in self.calls))
def test_conflicting_existing_untouched(self):
drift = deepcopy(self.obj)
drift['spec']['data'][0]['remoteRef']['key'] = 'elsewhere'
self.responses = [self.store, drift]
with self.assertRaises(lane.Refused):
self.m.deliver()
self.assertEqual(len(self.calls), 2)
def test_missing_drifted_unready_store_never_create(self):
for kind in ('missing', 'spec', 'ready', 'namespace', 'terminating'):
with self.subTest(kind=kind):
store = deepcopy(self.store)
if kind == 'missing': store = None
if kind == 'spec': store['spec']['provider']['vault']['path'] = 'wrong'
if kind == 'ready': store['status']['conditions'] = []
if kind == 'namespace': store['metadata']['namespace'] = 'sso'
if kind == 'terminating': store['metadata']['deletionTimestamp'] = 'now'
self.responses = [store]
with self.assertRaises(lane.Refused):
lane._DeliveryMechanics('forgejo').deliver()
self.assertFalse(any('create' in a for a,k in self.calls))
def test_get_error_not_absence_and_output_contained(self):
self.responses = [self.store, 'error']
with self.assertRaisesRegex(lane.Refused, '^public_command_failed$'):
self.m.deliver()
self.assertEqual(len(self.calls), 2)
def test_create_lost_response_or_race_uncertain_no_retry(self):
self.responses = [self.store, None, 'error']
with self.assertRaises(lane.Uncertain): self.m.deliver()
with self.assertRaises(lane.Refused): self.m.deliver()
self.assertEqual(len(self.calls), 3)
def test_ready_poll_is_bounded_and_created_provenance_retained(self):
obj = deepcopy(self.obj)
obj['status']['conditions'] = []
self.responses = [self.store, None, obj] + [obj] * lane.POLL_COUNT
with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
self.assertEqual(error.exception.revision['uid'], 'uid-1')
self.assertEqual(len(self.calls), lane.POLL_COUNT + 3)
with self.assertRaises(lane.Refused): self.m.remove(error.exception.revision)
def test_readiness_updates_resource_version(self):
fresh = self.resource(self.m.external, rv='11')
self.responses = [self.store, None, self.obj, fresh]
receipt = self.m.deliver()
self.assertEqual(receipt['after']['resource_version'], '11')
def test_replacement_during_ready_not_adopted(self):
other = self.resource(self.m.external, uid='other')
self.responses = [self.store, None, self.obj, other]
with self.assertRaises(lane.Uncertain): self.m.deliver()
def test_drift_and_replacement_never_deleted(self):
for field in ('uid', 'resourceVersion'):
with self.subTest(field=field):
self.m = lane._DeliveryMechanics('forgejo')
receipt = self.create()
changed = deepcopy(self.obj)
changed['metadata'][field] = 'different'
self.responses = [changed]
count = len(self.calls)
with self.assertRaises(lane.Uncertain): self.m.remove(receipt['after'])
self.assertEqual(len(self.calls), count + 1)
def test_delete_response_loss_and_conflict_uncertain(self):
receipt = self.create()
self.responses = [self.obj, 'error']
with self.assertRaises(lane.Uncertain): self.m.remove(receipt['after'])
with self.assertRaises(lane.Refused): self.m.remove(receipt['after'])
def test_delete_unconfirmed_or_replaced_never_repeated(self):
for replacement in (False, True):
with self.subTest(replacement=replacement):
self.m = lane._DeliveryMechanics('forgejo')
receipt = self.create()
tail = [self.resource(self.m.external, uid='new')] if replacement else [self.obj] * lane.POLL_COUNT
self.responses = [self.obj, {'kind': 'Status'}] + tail
with self.assertRaises(lane.Uncertain): self.m.remove(receipt['after'])
def test_no_caller_paths_or_injection(self):
for client in ('a/../../secret', 'x;echo', 'a' * 49, 'A', None):
with self.subTest(client=client):
with self.assertRaises(lane.Refused): lane._DeliveryMechanics(client)
self.assertEqual(self.calls, [])
def test_forged_revision_and_fresh_process_have_no_delete_provenance(self):
receipt = self.create()
fresh = lane._DeliveryMechanics('forgejo')
with self.assertRaises(lane.Refused): fresh.remove(receipt['after'])
with self.assertRaises(lane.Refused): self.m.remove({'uid': 'uid-1', 'resource_version': '11'})
self.assertFalse(any('delete' in a for a,k in self.calls))
def test_malformed_create_or_poll_never_returns_success(self):
for malformed in ({}, {'kind': 'Status'}):
with self.subTest(malformed=malformed):
self.m = lane._DeliveryMechanics('forgejo')
self.responses = [self.store, None, malformed]
with self.assertRaises(lane.Uncertain): self.m.deliver()
def test_poll_timeout_contains_provider_details(self):
self.responses = [self.store, None, self.obj,
subprocess.TimeoutExpired('PRIVATE PROVIDER', 60, output='PRIVATE VALUE')]
with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
self.assertNotIn('PRIVATE', str(error.exception))
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,436 @@
--- /dev/null
+++ b/scripts/keycape_issuer_delivery.py
@@ -0,0 +1,182 @@
+"""RPF-1.3-T4 concrete source-preparation mechanics; NO production admission.
+
+Only public ESO objects are fetched. The production factory refuses before
+dispatch: no authenticated delivery session/evidence service currently exists.
+Private mechanics are exercised by fake-command tests, never a grant boundary.
+"""
+from __future__ import annotations
+
+from copy import deepcopy
+import json
+import re
+import time
+
+from keycape_issuer_eso import documents, require_fixed_store, Refused
+from state_hub_preflight_lane import command, LaneError
+
+POLL_COUNT = 10
+POLL_DELAY = 1
+API = '/apis/external-secrets.io/v1'
+
+
+class Uncertain(Refused):
+ """No success receipt: retain exact metadata for owner reconciliation."""
+ def __init__(self, reason, revision=None):
+ super().__init__(reason)
+ self.revision = deepcopy(revision)
+
+
+def production_bindings(*args, **kwargs):
+ # Neither Warden invocation fields, an operator's kubeconfig, an unsigned
+ # local receipt nor mint-lane lookup-self authenticates THIS lane/caller.
+ raise Refused('delivery_admission_session_journal_and_evidence_unavailable')
+
+
+def _revision(obj):
+ meta = obj.get('metadata', {})
+ result = {'uid': meta.get('uid'), 'resource_version': meta.get('resourceVersion')}
+ if any(not isinstance(x, str) or not re.fullmatch(r'[A-Za-z0-9._:-]{1,192}', x)
+ for x in result.values()):
+ raise Refused('public_revision_invalid')
+ return result
+
+
+class _DeliveryMechanics:
+ """Concrete fixed kubectl algorithm, not an authenticated owner capability.
+
+ No caller resource, remoteRef, namespace, store or path argument is accepted.
+ The deliberately unauthenticated transport cannot load ambient credentials.
+ A reviewed session transport must replace it only after real lane admission,
+ one-shot durable journaling, evidence and revocation are implemented.
+ """
+ def __init__(self, client_id):
+ self.client_id = client_id
+ self.store, self.external = documents(client_id)
+ self.created = None
+ self.pending = False
+
+ def _call(self, args, payload=None):
+ try:
+ result = command(['kubectl', '--kubeconfig=/dev/null',
+ '--request-timeout=5s', *args],
+ payload=payload, env={})
+ if len(result.stdout) > 262144:
+ raise Refused('public_response_too_large')
+ if not result.stdout.strip():
+ return None
+ value = json.loads(result.stdout)
+ if not isinstance(value, dict):
+ raise Refused('public_response_shape')
+ return value
+ except (LaneError, OSError, ValueError, TimeoutError):
+ raise Refused('public_command_failed') from None
+ except Exception:
+ # Includes subprocess timeouts; never expose stdout/stderr or argv.
+ raise Refused('public_command_failed') from None
+
+ def _get(self, store=False):
+ obj = self.store if store else self.external
+ resource = 'clustersecretstores.external-secrets.io' if store else 'externalsecrets.external-secrets.io'
+ args = [] if store else ['-n', 'sso']
+ return self._call([*args, 'get', resource, obj['metadata']['name'],
+ '--ignore-not-found=true', '-o', 'json'])
+
+ def _match(self, obj):
+ if not isinstance(obj, dict):
+ raise Refused('delivery_missing')
+ metadata = obj.get('metadata', {})
+ if (obj.get('apiVersion') != self.external['apiVersion']
+ or obj.get('kind') != 'ExternalSecret'
+ or metadata.get('name') != self.external['metadata']['name']
+ or metadata.get('namespace') != 'sso'
+ or metadata.get('deletionTimestamp')
+ or obj.get('spec') != self.external['spec']):
+ raise Refused('delivery_conflict')
+ return _revision(obj)
+
+ @staticmethod
+ def _ready(obj):
+ status = obj.get('status', {})
+ conditions = status.get('conditions', []) if isinstance(status, dict) else []
+ return isinstance(conditions, list) and any(
+ isinstance(c, dict) and c.get('type') == 'Ready' and c.get('status') == 'True'
+ for c in conditions)
+
+ def _receipt(self, operation, before, after):
+ return {'schema': 'netkingdom.deliver-secret.v1', 'operation': operation,
+ 'namespace': 'sso', 'name': self.external['metadata']['name'],
+ 'custody_reference': 'platform/workloads/keycape/' + self.client_id,
+ 'key_names': ['client-secret'], 'values_emitted': False,
+ 'changed': before is None if operation == 'deliver' else True,
+ 'before': before, 'after': after}
+
+ def deliver(self):
+ if self.pending or self.created is not None:
+ raise Refused('attempt_already_used_or_unreconciled')
+ # A successful empty response proves absence; errors never do.
+ store = self._get(store=True)
+ require_fixed_store(store, self.client_id)
+ if store.get('metadata', {}).get('deletionTimestamp'):
+ raise Refused('store_terminating')
+ old = self._get()
+ before = self._match(old) if old is not None else None
+ uid = before['uid'] if before else None
+ if old is None:
+ # Set before dispatch: a lost create response MUST NOT be retried,
+ # adopted, or claimed as a pre-existing no-change result.
+ self.pending = True
+ try:
+ created = self._call(['-n', 'sso', 'create', '-f', '-', '-o', 'json'], self.external)
+ self.created = self._match(created)
+ uid = self.created['uid']
+ except Refused:
+ raise Uncertain('create_outcome_unverified') from None
+ try:
+ for index in range(POLL_COUNT):
+ current = self._get()
+ after = self._match(current)
+ if after['uid'] != uid:
+ raise Refused('delivery_replaced')
+ if self._ready(current):
+ if self.created is not None:
+ self.created = after
+ self.pending = False
+ return self._receipt('deliver', before, after)
+ if index + 1 < POLL_COUNT:
+ time.sleep(POLL_DELAY)
+ raise Refused('delivery_ready_timeout')
+ except Refused:
+ if self.created is not None:
+ raise Uncertain('created_delivery_requires_reconciliation', self.created) from None
+ raise
+
+ def remove(self, revision):
+ # An arbitrary adapter argument alone is never deletion provenance.
+ if self.pending or self.created is None or revision != self.created:
+ raise Refused('no_verified_case_creation')
+ try:
+ if self._match(self._get()) != self.created:
+ raise Refused('created_revision_changed')
+ except Refused:
+ raise Uncertain('created_revision_changed', self.created) from None
+ uri = API + '/namespaces/sso/externalsecrets/' + self.external['metadata']['name']
+ options = {'apiVersion': 'v1', 'kind': 'DeleteOptions',
+ 'preconditions': {'uid': self.created['uid'],
+ 'resourceVersion': self.created['resource_version']}}
+ prior = deepcopy(self.created)
+ self.pending = True
+ try:
+ self._call(['delete', '--raw', uri, '-f', '-'], options)
+ for index in range(POLL_COUNT):
+ current = self._get()
+ if current is None:
+ self.created = None
+ self.pending = False
+ return self._receipt('remove', prior, None)
+ if _revision(current)['uid'] != prior['uid']:
+ raise Refused('replacement_preserved')
+ if index + 1 < POLL_COUNT:
+ time.sleep(POLL_DELAY)
+ raise Refused('delete_unconfirmed')
+ except Refused:
+ raise Uncertain('cleanup_requires_reconciliation', prior) from None
--- /dev/null
+++ b/tests/test_keycape_issuer_delivery.py
@@ -0,0 +1,196 @@
+"""Offline command-level regressions. No HOME/env/network/credential access."""
+from copy import deepcopy
+import json
+import os
+from pathlib import Path
+import subprocess
+import sys
+import unittest
+from unittest.mock import patch
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
+import keycape_issuer_delivery as lane
+
+
+class DeliveryTests(unittest.TestCase):
+ def setUp(self):
+ self.environment = patch.dict(os.environ, {}, clear=True)
+ self.environment.start()
+ self.addCleanup(self.environment.stop)
+ self.network = patch('socket.socket', side_effect=AssertionError('network forbidden'))
+ self.network.start()
+ self.addCleanup(self.network.stop)
+ self.m = lane._DeliveryMechanics('forgejo')
+ self.store = self.resource(self.m.store)
+ self.obj = self.resource(self.m.external)
+ self.calls = []
+ self.responses = []
+ self.native = patch('subprocess.run', side_effect=self.run_command)
+ self.native.start()
+ self.addCleanup(self.native.stop)
+ self.sleep = patch.object(lane.time, 'sleep')
+ self.sleep.start()
+ self.addCleanup(self.sleep.stop)
+
+ def resource(self, template, uid='uid-1', rv='10', ready=True):
+ obj = deepcopy(template)
+ obj['metadata'].update(uid=uid, resourceVersion=rv)
+ obj['status'] = {'conditions': [{'type': 'Ready', 'status': str(ready)}]}
+ return obj
+
+ def run_command(self, argv, **kw):
+ self.calls.append((deepcopy(argv), deepcopy(kw)))
+ self.assertEqual(kw['env'], {})
+ self.assertEqual(argv[:3], ['kubectl', '--kubeconfig=/dev/null', '--request-timeout=5s'])
+ self.assertTrue(kw['capture_output'])
+ self.assertEqual(kw['timeout'], 60)
+ if not self.responses:
+ self.fail('unexpected command dispatch')
+ result = self.responses.pop(0)
+ if isinstance(result, Exception):
+ raise result
+ if result == 'error':
+ return subprocess.CompletedProcess(argv, 1, b'PRIVATE PROVIDER VALUE', b'PRIVATE ERROR')
+ return subprocess.CompletedProcess(argv, 0, b'' if result is None else json.dumps(result).encode(), b'')
+
+ def create(self):
+ self.responses += [self.store, None, self.obj, self.obj]
+ return self.m.deliver()
+
+ def test_production_factory_fails_before_dispatch(self):
+ with self.assertRaisesRegex(lane.Refused, 'unavailable'):
+ lane.production_bindings(case_source=b'arbitrary', admitted=object())
+ self.assertEqual(self.calls, [])
+
+ def test_absent_create_ready_and_uid_rv_precondition_delete(self):
+ receipt = self.create()
+ self.assertTrue(receipt['changed'])
+ self.assertIsNone(receipt['before'])
+ self.assertEqual(receipt['after'], {'uid': 'uid-1', 'resource_version': '10'})
+ self.responses += [self.obj, {'kind': 'Status'}, None]
+ removed = self.m.remove(receipt['after'])
+ self.assertEqual(removed['operation'], 'remove')
+ delete = next(c for c in self.calls if 'delete' in c[0])
+ options = json.loads(delete[1]['input'])
+ self.assertEqual(options['preconditions'], {'uid': 'uid-1', 'resourceVersion': '10'})
+ self.assertEqual(delete[0][5], '/apis/external-secrets.io/v1/namespaces/sso/externalsecrets/keycape-forgejo-client')
+ self.assertFalse(any('secret' == a or 'secrets' == a for c in self.calls for a in c[0]))
+
+ def test_matching_existing_no_change_never_delete(self):
+ self.responses = [self.store, self.obj, self.obj]
+ receipt = self.m.deliver()
+ self.assertFalse(receipt['changed'])
+ with self.assertRaises(lane.Refused):
+ self.m.remove(receipt['after'])
+ self.assertFalse(any('create' in a or 'delete' in a for a, k in self.calls))
+
+ def test_conflicting_existing_untouched(self):
+ drift = deepcopy(self.obj)
+ drift['spec']['data'][0]['remoteRef']['key'] = 'elsewhere'
+ self.responses = [self.store, drift]
+ with self.assertRaises(lane.Refused):
+ self.m.deliver()
+ self.assertEqual(len(self.calls), 2)
+
+ def test_missing_drifted_unready_store_never_create(self):
+ for kind in ('missing', 'spec', 'ready', 'namespace', 'terminating'):
+ with self.subTest(kind=kind):
+ store = deepcopy(self.store)
+ if kind == 'missing': store = None
+ if kind == 'spec': store['spec']['provider']['vault']['path'] = 'wrong'
+ if kind == 'ready': store['status']['conditions'] = []
+ if kind == 'namespace': store['metadata']['namespace'] = 'sso'
+ if kind == 'terminating': store['metadata']['deletionTimestamp'] = 'now'
+ self.responses = [store]
+ with self.assertRaises(lane.Refused):
+ lane._DeliveryMechanics('forgejo').deliver()
+ self.assertFalse(any('create' in a for a,k in self.calls))
+
+ def test_get_error_not_absence_and_output_contained(self):
+ self.responses = [self.store, 'error']
+ with self.assertRaisesRegex(lane.Refused, '^public_command_failed$'):
+ self.m.deliver()
+ self.assertEqual(len(self.calls), 2)
+
+ def test_create_lost_response_or_race_uncertain_no_retry(self):
+ self.responses = [self.store, None, 'error']
+ with self.assertRaises(lane.Uncertain): self.m.deliver()
+ with self.assertRaises(lane.Refused): self.m.deliver()
+ self.assertEqual(len(self.calls), 3)
+
+ def test_ready_poll_is_bounded_and_created_provenance_retained(self):
+ obj = deepcopy(self.obj)
+ obj['status']['conditions'] = []
+ self.responses = [self.store, None, obj] + [obj] * lane.POLL_COUNT
+ with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
+ self.assertEqual(error.exception.revision['uid'], 'uid-1')
+ self.assertEqual(len(self.calls), lane.POLL_COUNT + 3)
+ with self.assertRaises(lane.Refused): self.m.remove(error.exception.revision)
+
+ def test_readiness_updates_resource_version(self):
+ fresh = self.resource(self.m.external, rv='11')
+ self.responses = [self.store, None, self.obj, fresh]
+ receipt = self.m.deliver()
+ self.assertEqual(receipt['after']['resource_version'], '11')
+
+ def test_replacement_during_ready_not_adopted(self):
+ other = self.resource(self.m.external, uid='other')
+ self.responses = [self.store, None, self.obj, other]
+ with self.assertRaises(lane.Uncertain): self.m.deliver()
+
+ def test_drift_and_replacement_never_deleted(self):
+ for field in ('uid', 'resourceVersion'):
+ with self.subTest(field=field):
+ self.m = lane._DeliveryMechanics('forgejo')
+ receipt = self.create()
+ changed = deepcopy(self.obj)
+ changed['metadata'][field] = 'different'
+ self.responses = [changed]
+ count = len(self.calls)
+ with self.assertRaises(lane.Uncertain): self.m.remove(receipt['after'])
+ self.assertEqual(len(self.calls), count + 1)
+
+ def test_delete_response_loss_and_conflict_uncertain(self):
+ receipt = self.create()
+ self.responses = [self.obj, 'error']
+ with self.assertRaises(lane.Uncertain): self.m.remove(receipt['after'])
+ with self.assertRaises(lane.Refused): self.m.remove(receipt['after'])
+
+ def test_delete_unconfirmed_or_replaced_never_repeated(self):
+ for replacement in (False, True):
+ with self.subTest(replacement=replacement):
+ self.m = lane._DeliveryMechanics('forgejo')
+ receipt = self.create()
+ tail = [self.resource(self.m.external, uid='new')] if replacement else [self.obj] * lane.POLL_COUNT
+ self.responses = [self.obj, {'kind': 'Status'}] + tail
+ with self.assertRaises(lane.Uncertain): self.m.remove(receipt['after'])
+
+ def test_no_caller_paths_or_injection(self):
+ for client in ('a/../../secret', 'x;echo', 'a' * 49, 'A', None):
+ with self.subTest(client=client):
+ with self.assertRaises(lane.Refused): lane._DeliveryMechanics(client)
+ self.assertEqual(self.calls, [])
+
+ def test_forged_revision_and_fresh_process_have_no_delete_provenance(self):
+ receipt = self.create()
+ fresh = lane._DeliveryMechanics('forgejo')
+ with self.assertRaises(lane.Refused): fresh.remove(receipt['after'])
+ with self.assertRaises(lane.Refused): self.m.remove({'uid': 'uid-1', 'resource_version': '11'})
+ self.assertFalse(any('delete' in a for a,k in self.calls))
+
+ def test_malformed_create_or_poll_never_returns_success(self):
+ for malformed in ({}, {'kind': 'Status'}):
+ with self.subTest(malformed=malformed):
+ self.m = lane._DeliveryMechanics('forgejo')
+ self.responses = [self.store, None, malformed]
+ with self.assertRaises(lane.Uncertain): self.m.deliver()
+
+ def test_poll_timeout_contains_provider_details(self):
+ self.responses = [self.store, None, self.obj,
+ subprocess.TimeoutExpired('PRIVATE PROVIDER', 60, output='PRIVATE VALUE')]
+ with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
+ self.assertNotIn('PRIVATE', str(error.exception))
+
+
+if __name__ == '__main__':
+ unittest.main()
--- a/docs/keycape-issuer-eso-contract.md
+++ b/docs/keycape-issuer-eso-contract.md
@@ -66,3 +66,49 @@
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.
+
+## Prepared delivery mechanics (still unavailable as a production lane)
+
+`scripts/keycape_issuer_delivery.py` now contains concrete command mechanics for
+the existing RPF-1.3-T4 obligation. It reuses `keycape_issuer_eso.documents()` and
+`require_fixed_store()` and the existing contained `state_hub_preflight_lane.command()`
+transport. Only exact named public ESO resources are read. It proves absence
+with successful empty `get --ignore-not-found`, creates only the ExternalSecret,
+compares an existing object's full spec, and polls at most ten times for Ready
+with matching spec and stable UID. Each request has a 5s kubectl request timeout
+inside the existing 60s process bound; nine sleeps of 1s bound each poll loop.
+Controller resourceVersion updates during successful readiness are captured.
+
+Removal is permitted only for that instance's verified case-created revision,
+rechecks spec/UID/resourceVersion, uses Kubernetes DeleteOptions with both UID
+and resourceVersion preconditions, and confirms absence with bounded polling.
+Existing matching objects return changed=false and are never rollback candidates.
+Creation response loss, readiness failure after creation, replacement, delete
+conflict or unconfirmed deletion raises a fixed uncertainty diagnostic and keeps
+the last verified revision when known. No retry adopts an uncertain create; no
+replacement is deleted. An unknown create outcome requires owner reconciliation
+using the fixed resource name even when no UID could be recovered. Target Secret
+garbage collection by ESO/Kubernetes is outside this helper; it never reads or
+directly deletes Secret objects. There is no claim that deletion proves target
+Secret disappearance.
+
+The callable `production_bindings()` **always refuses before any command**.
+The private mechanics use empty environment and `/dev/null` kubeconfig and
+therefore cannot borrow the caller's native session. They are source preparation,
+not an adapter/evidence authority. Tests intercept the actual subprocess call;
+they do not provide production callbacks or attestations. A real lane requires
+an owner-reviewed authenticated human aal2 session, acknowledged case revision
+and caller binding, scoped Kubernetes credentials, one-shot durable admission
+journaling and authenticated evidence publication/verification, plus real
+per-attempt session revocation and recovery. None exists for this lane at the
+pinned source. The generic task_execution_journal module is real storage but
+explicitly does not admit native work; it is not silently repurposed here.
+The mint broker's case checker and OpenBao lookup-self are real for its existing
+mint lane, not delivery authentication. The attended custody-projection SSH
+transport and credential-change evidence writer likewise confer no delivery
+lane authority. No unsigned receipt or `True` oracle replaces these facilities.
+
+Consequently RPF-1.3-T4 remains open for real capability binding and owner review;
+this patch completes only concrete ESO command/state mechanics and regressions.
+No ESO role, policy, Store, admission service, grant, catalog acceptance, native
+permission or owner descriptor acceptance is created or inferred.

View file

@ -0,0 +1,15 @@
{
"schema": "platform.delivery-source-regressions.v1",
"initial_regression": "First 14-test run had one incorrect test assertion: delete argv URI index 4 instead of 5. Corrected test; no backend behavior changed for that failure.",
"final_runs": {
"test_keycape_issuer_delivery.py": {
"exit_code": 0,
"output": "test_absent_create_ready_and_uid_rv_precondition_delete (test_keycape_issuer_delivery.DeliveryTests.test_absent_create_ready_and_uid_rv_precondition_delete) ... ok\ntest_conflicting_existing_untouched (test_keycape_issuer_delivery.DeliveryTests.test_conflicting_existing_untouched) ... ok\ntest_create_lost_response_or_race_uncertain_no_retry (test_keycape_issuer_delivery.DeliveryTests.test_create_lost_response_or_race_uncertain_no_retry) ... ok\ntest_delete_response_loss_and_conflict_uncertain (test_keycape_issuer_delivery.DeliveryTests.test_delete_response_loss_and_conflict_uncertain) ... ok\ntest_delete_unconfirmed_or_replaced_never_repeated (test_keycape_issuer_delivery.DeliveryTests.test_delete_unconfirmed_or_replaced_never_repeated) ... ok\ntest_drift_and_replacement_never_deleted (test_keycape_issuer_delivery.DeliveryTests.test_drift_and_replacement_never_deleted) ... ok\ntest_forged_revision_and_fresh_process_have_no_delete_provenance (test_keycape_issuer_delivery.DeliveryTests.test_forged_revision_and_fresh_process_have_no_delete_provenance) ... ok\ntest_get_error_not_absence_and_output_contained (test_keycape_issuer_delivery.DeliveryTests.test_get_error_not_absence_and_output_contained) ... ok\ntest_malformed_create_or_poll_never_returns_success (test_keycape_issuer_delivery.DeliveryTests.test_malformed_create_or_poll_never_returns_success) ... ok\ntest_matching_existing_no_change_never_delete (test_keycape_issuer_delivery.DeliveryTests.test_matching_existing_no_change_never_delete) ... ok\ntest_missing_drifted_unready_store_never_create (test_keycape_issuer_delivery.DeliveryTests.test_missing_drifted_unready_store_never_create) ... ok\ntest_no_caller_paths_or_injection (test_keycape_issuer_delivery.DeliveryTests.test_no_caller_paths_or_injection) ... ok\ntest_poll_timeout_contains_provider_details (test_keycape_issuer_delivery.DeliveryTests.test_poll_timeout_contains_provider_details) ... ok\ntest_production_factory_fails_before_dispatch (test_keycape_issuer_delivery.DeliveryTests.test_production_factory_fails_before_dispatch) ... ok\ntest_readiness_updates_resource_version (test_keycape_issuer_delivery.DeliveryTests.test_readiness_updates_resource_version) ... ok\ntest_ready_poll_is_bounded_and_created_provenance_retained (test_keycape_issuer_delivery.DeliveryTests.test_ready_poll_is_bounded_and_created_provenance_retained) ... ok\ntest_replacement_during_ready_not_adopted (test_keycape_issuer_delivery.DeliveryTests.test_replacement_during_ready_not_adopted) ... ok\n\n----------------------------------------------------------------------\nRan 17 tests in 0.186s\n\nOK\n"
},
"test_keycape_issuer_eso.py": {
"exit_code": 0,
"output": "test_delivery_document_is_only_external_and_detached (test_keycape_issuer_eso.FixedIssuerContract.test_delivery_document_is_only_external_and_detached) ... ok\ntest_external_drift_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_external_drift_refuses) ... ok\ntest_independent_forgejo_binding (test_keycape_issuer_eso.FixedIssuerContract.test_independent_forgejo_binding) ... ok\ntest_missing_or_unready_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_missing_or_unready_refuses) ... ok\ntest_no_caller_store_or_path_override (test_keycape_issuer_eso.FixedIssuerContract.test_no_caller_store_or_path_override) ... ok\ntest_store_drift_refuses (test_keycape_issuer_eso.FixedIssuerContract.test_store_drift_refuses) ... ok\ntest_type_admitted_trailing_dash_is_preserved (test_keycape_issuer_eso.FixedIssuerContract.test_type_admitted_trailing_dash_is_preserved) ... ok\ntest_valid_ready_metadata_allows_extra_controller_metadata (test_keycape_issuer_eso.FixedIssuerContract.test_valid_ready_metadata_allows_extra_controller_metadata) ... ok\n\n----------------------------------------------------------------------\nRan 8 tests in 0.316s\n\nOK\n"
}
},
"scope": "offline only, subprocess mocked; environment cleared and socket.socket forbidden by delivery tests; no native credentials or network"
}

View file

@ -0,0 +1,40 @@
{
"railiance-platform": {
"commit": "104ed90f42a9a86bf9fda343b49b65a85c9ee4b9",
"files": {
"AGENTS.md": "sha256:8d2f1a165e3f4eecf2e6e1d6a19c4bac9e8dcb64a76650e7185d55722f233c8b",
"INTENT.md": "sha256:12c6540735af591f7a4a9fe85ff10d3cd00eec55b5b6b1ddf148abf2d3ac9a9c",
"scripts/keycape_issuer_eso.py": "sha256:21c5797280c2a7825dbda8ac970d34a0cdef4c6458f68bce0b1c12407f122a87",
"manifests/templates/keycape-issuer-eso.yaml": "sha256:e17498bd8a4c820041a4105e4efe9c60c6a50953db8f621ff54887080065c743",
"scripts/state_hub_preflight_lane.py": "sha256:8bf63beda028de7c2375084afb7c22fb00a731c54ebf0b3ba369a699d920e0f5",
"scripts/keycape_client_secret_generate.py": "sha256:9576cd211cfc06526ec4cc9bd0a5b06f0f59287a68931bb2d6e3592a951ddc7d",
"scripts/task_execution_journal.py": "sha256:604cf285286a8a1be23d9bc0d8a547bd1195b7f80286778e1d4f30c711fd6a7e",
"scripts/custody-projection.py": "sha256:d5ca1017b4877a12c7fa24535e6b497492cd63e5e99c0674a8e9fc3ad93ead87",
"scripts/remote_exec.py": "sha256:4d60c1bf3f6aa3a0c6867fadb137abf23699b4cf7ebeea317e4ce794bacc7968",
"scripts/repair_eso_kubernetes_auth.py": "sha256:dd58e4ab3f88c777aacb44368c2c099b6e32637ce76f5157e7a874cfbe9d5f80",
"scripts/credential-change.py": "sha256:54ca1bc33ed4bb7807917f71ed45b6b35da978bc789e013386e1e90a20bee06d"
}
},
"net-kingdom": {
"commit": "befb0bc68cd25b625804fce559476eefd702521d",
"files": {
"sso-mfa/k8s/keycape/deliver-secret-adapter.py": "sha256:cd1a515a1ae2def2093660ed2a810a9c66d1d4cf7126f573ee7e9c4c42afb98d"
}
},
"ops-warden": {
"commit": "b593c879831378db7dc62adfabdfedf2d44ddd38",
"files": {
"registry/routing/catalog.yaml": "sha256:ba9634a92e584dac8c42e8624ab52adce06e79a285209cb64c0daa4dac9cd7d8"
}
},
"owner_review": {
"path": "/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/nk48-1.3-owner-review/owner-review-railiance-platform.json",
"sha256": "d3f523b60924b530376b7aac4459cecda5da183848dd6e35889c1013c73439d9"
},
"prepared_files": {
"scripts/keycape_issuer_delivery.py": "sha256:bd0fbbbdd0b08037e0d39e76f458e143663afa7e1e4ea9697639863c74e27ef7",
"tests/test_keycape_issuer_delivery.py": "sha256:1d35c24a2ddaab17ef6419afd52d2c9b45a72fc1f107a504f5d5ed1d33fd456f",
"docs/keycape-issuer-eso-contract.md": "sha256:ac26dd1bf204fba691da4c34308c6051f1cf23d8e19c989c51672de74817c044"
},
"patch_sha256": "9b9b5a81bfc65693112e136fff44ce98c35a91151dc645659d80e6a5b2da86ca"
}

View file

@ -0,0 +1,130 @@
# Platform delivery backend source preparation r2
Same candidate trial as r1. **Source preparation only; production dispatch
remains refused.** No canonical Platform/NetKingdom/Warden files, commits,
credentials, grants, owner acceptance, native cluster operations or authenticated
delivery evidence were created. Original r1 and its adverse independent review
remain preserved in their existing directories.
`railiance-platform.patch` applies to Platform
`104ed90f42a9a86bf9fda343b49b65a85c9ee4b9`.
`r1-to-r2.patch` is an alternative incremental patch after the original r1
candidate. Do not apply both. Both were checked and applied only in scratch;
the resulting files match `prepared-source/` byte for byte. The fixed template
is unchanged. These artifacts do not authorize canonical integration.
## Source-grounded corrections
Official ESO v0.16.1 resolves to commit
`8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af`. Pinned public upstream source,
URLs and digests are retained in `upstream/` and `upstream-sources.json`.
- The controller's syncedResourceVersion is **generation plus metadata hash**,
rather than Kubernetes resourceVersion or an observedGeneration field.
The exact [upstream metadata token](https://github.com/external-secrets/external-secrets/blob/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/controllers/util/util.go)
hashes a Go-formatted annotations/labels struct using the
[upstream ObjectHash algorithm](https://github.com/external-secrets/external-secrets/blob/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/utils/utils.go).
r2 matches this token, including metadata changes. Five actual standalone
Go extraction vectors cover nil/empty maps, map ordering, delimiters,
newlines, Unicode, NUL and maximum signed generation; Python matches all.
- `_ready` additionally requires one Ready=True/SecretSynced condition with
the successful-sync message, and a valid refreshTime within the fixed
five-minute interval and not in the future. The
[actual controller](https://github.com/external-secrets/external-secrets/blob/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/controllers/externalsecret/externalsecret_controller.go)
sets refreshTime to reconcile start. Its Retain/missing-provider branch can
also set Ready=True with the same reason, but uses a different message;
r2 refuses that branch. lastTransitionTime cannot replace this check:
unchanged Ready conditions retain it across reconciles.
- Only the three cached v1 CRD remoteRef defaults are normalized:
conversionStrategy=Default, decodingStrategy=None, metadataPolicy=None.
Alternate values and any other extra/spec/path/identity changes are refused.
The helper remains a metadata contract; the runtime freshness gate is in
delivery mechanics.
- Nested metadata, generation, maps and collection shapes are checked before
use. Malformed create/poll/remove responses yield fixed refusal/uncertainty
without provider detail. Valid create-response identity is retained even if
later shape/spec validation fails; pending state still bars blind cleanup.
- Reads use explicit raw v1 paths. A fixed field-selected ExternalSecret
collection GET provides structured successful absence; nonzero/error output
is never interpreted as absence. Returned namespace/name/API/kind, item
count and continuation are checked. There is no Secret read/list or Store
mutation. This changes future session requirements to permit a fixed
field-selected **ExternalSecret** list; no such permission is supplied here.
- `bounded_public_command.py` enforces an aggregate 262144-byte stdout+stderr
cap while reading, retains no stderr, and handles input concurrently.
Commands have a six-second maximum; an operation has a 30-second monotonic
deadline and refuses success after it. Each cleanup has a separate one-second
reap budget. The process group is killed on success, overflow, timeout and
failure, before the leader is reaped, avoiding reuse of its process-group ID.
Unconfirmed cleanup is refusal. Tests launch only local synthetic Python
children, including descendants holding pipes or closing them early.
## Store freshness and the remaining race
The [actual Store controller](https://github.com/external-secrets/external-secrets/blob/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/controllers/secretstore/common.go)
publishes no generation-bound readiness token. r2 requires its fixed spec,
one Valid/validated Ready condition, valid UID/RV, no termination and
**generation 1**. Under Kubernetes generation/status-subresource semantics,
that excludes a previous spec revision; any later generation gets the specific
`store_ready_generation_unverifiable` refusal. No observedGeneration is invented
and a changed Store cannot simply be accepted because Ready stayed true.
The sealed Store UID/RV/spec is observed again before create, during readiness
and immediately before returning a receipt. These observations detect drift
that is visible at those times. They **do not** atomically bind the
ExternalSecret create or ESO's Store lookup to that Store revision. A Store
change/restoration entirely between observations is undetectable. One negative
test deliberately performs this interleaving and observes that private mechanics
can still return a receipt. Another changes the Store in the create window and
proves that mutation already happened before subsequent refusal.
Accordingly, actual owner-controlled exclusion/admission remains a production
blocker. Double reads are not described as exclusion, and this preparation
does not create an admission service or confer new authority. Remove uses
ExternalSecret UID **and** RV server preconditions and does not require a
working Store, so safe reconciliation is not coupled to Store availability.
## Evidence and limits
`regressions.log` records **42/42** passing tests on an isolated tree produced
by applying the full patch: 9 runner abuse/process tests, 25 delivery tests and
8 existing fixed-contract tests. `assert_corrected_regressions.py` exercises
desired assertions against both versions: untouched r1 produces two failures
and two errors; r2 passes all four. The predecessor failure log is retained.
`patch-runs.json` records both alternative patch check/application results.
The initial 33-test run had one fixture assertion failure: a descendant was
still scheduled in state R immediately after SIGKILL. Its log is preserved.
The verification now allows up to 200ms for the scheduler to expose termination;
it accepts only disappearance or zombie state, never a still-running child.
No authority or runtime promotion is scored from this correction.
Run `python3 reproduce.py` to assemble from pinned local Git objects and rerun
the final 42 plus four corrected assertions. Tests clear inherited environment,
use an empty scratch HOME and deny socket construction. Actual child fixtures
also deny socket creation within their own interpreters; no kubectl or other
owner executable is launched. Public upstream retrieval occurred separately
with no authentication. Go vectors use only local standard-library compilation
with GOPROXY/GOSUMDB disabled.
Supported claims are source-level exact payload/endpoint generation, synthetic
current-metadata controller-report checks, bounded collection and conservative
unknown-outcome handling. Server enforcement of delete preconditions, installed
ESO version, trusted status custody, runtime credentials, present Secret contents
and native outcomes have not been tested. Hash tokens are not authentication.
Recent refresh checks assume a sane local clock. Store generation 1 does not
prove current provider availability or solve cross-object exclusion.
Process cleanup covers ordinary descendants in the command's process group;
it is not a sandbox against a program escaping the group/session. The leader
is reaped within the cleanup budget or cleanup is explicitly unconfirmed;
orphan descendants may be zombies awaiting their actual parent/reaper. Kernel
process creation and scheduling are not a hard real-time guarantee. A production
runtime still needs its actual reviewed isolation and executable/session binding.
`production_bindings()` continues refusing missing actual source/case/client
authorization, owner exclusion/admission, scoped caller/session, durable one-shot
admission/recovery journal, authenticated evidence publication/verification and
per-attempt revocation. Local one-shot state, source hashes, unsigned receipts,
public source retrieval and passing tests supply none of those. This is not
completion of RPF-1.3-T4 and does not make dispatch operational.

View file

@ -0,0 +1,47 @@
"""Desired assertions: original r1 must fail, corrected r2 must pass. No dispatch."""
from copy import deepcopy
import os
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(sys.argv.pop(1)).resolve() / 'scripts'))
import keycape_issuer_delivery as lane
class CorrectedContract(unittest.TestCase):
def setUp(self):
p = patch.dict(os.environ, {}, clear=True); p.start(); self.addCleanup(p.stop)
p = patch('socket.socket', side_effect=AssertionError('network forbidden'))
p.start(); self.addCleanup(p.stop)
self.m = lane._DeliveryMechanics('forgejo')
self.obj = deepcopy(self.m.external)
self.obj['metadata'].update(uid='fixture', resourceVersion='10', generation=2)
self.obj['status'] = {'conditions': [{'type': 'Ready', 'status': 'True'}],
'syncedResourceVersion': '1-stale',
'refreshTime': '2020-01-01T00:00:00Z'}
def ready(self, value):
if hasattr(lane, '_ready'): return lane._ready(value)
return self.m._ready(value)
def test_stale_generation_is_not_ready(self):
self.assertFalse(self.ready(self.obj))
def test_retained_provider_absence_is_not_successful_sync(self):
self.obj['status']['conditions'][0].update(
reason='SecretSynced', message='secret retained due to DeletionPolicy=Retain')
self.assertFalse(self.ready(self.obj))
def test_actual_crd_defaults_match_fixed_external(self):
self.obj['spec']['data'][0]['remoteRef'].update(
conversionStrategy='Default', decodingStrategy='None', metadataPolicy='None')
self.assertEqual(self.m._match(self.obj)['uid'], 'fixture')
def test_malformed_metadata_is_a_fixed_refusal(self):
self.obj['metadata'] = None
with self.assertRaises(lane.Refused): self.m._match(self.obj)
if __name__ == '__main__': unittest.main(verbosity=2)

View file

@ -0,0 +1,42 @@
[
{
"generation": 1
},
{
"generation": 2,
"annotations": {},
"labels": {}
},
{
"generation": 3,
"annotations": {
"b": "2",
"a": "1"
},
"labels": {
"z": "last",
"c": "first"
}
},
{
"generation": 4,
"annotations": {
"a:b": "c d",
"a b": "[]:%+v"
},
"labels": {
"newline\n": "tab\t"
}
},
{
"generation": 9223372036854775807,
"annotations": {
"é": "好",
"🦊": "😀"
},
"labels": {
"": "",
"null": "\u0000"
}
}
]

View file

@ -0,0 +1,39 @@
// Standalone extraction of ESO v0.16.1 HashMeta/ObjectHash formatting.
// Upstream 8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af is Apache-2.0;
// original licensed source and exact hashes are retained under upstream/.
package main
import (
"crypto/md5"
"encoding/json"
"fmt"
"os"
)
type input struct {
Generation int64 `json:"generation"`
Annotations map[string]string `json:"annotations"`
Labels map[string]string `json:"labels"`
}
func main() {
var fixtures []input
if err := json.NewDecoder(os.Stdin).Decode(&fixtures); err != nil {
panic("fixture invalid")
}
results := []string{}
for _, fixture := range fixtures {
type meta struct {
annotations map[string]string
labels map[string]string
}
textualVersion := fmt.Sprintf("%+v", meta{
annotations: fixture.Annotations,
labels: fixture.Labels,
})
results = append(results, fmt.Sprintf("%d-%x", fixture.Generation, md5.Sum([]byte(textualVersion))))
}
if err := json.NewEncoder(os.Stdout).Encode(results); err != nil {
panic("fixture output failed")
}
}

View file

@ -0,0 +1,22 @@
[
{
"label": "full-check",
"exit_code": 0
},
{
"label": "full-apply-in-scratch",
"exit_code": 0
},
{
"label": "full-patch-applied-regressions",
"exit_code": 0
},
{
"label": "incremental-check",
"exit_code": 0
},
{
"label": "incremental-apply-in-scratch",
"exit_code": 0
}
]

View file

@ -0,0 +1,175 @@
# 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.
## Prepared delivery mechanics (still unavailable as a production lane)
`scripts/keycape_issuer_delivery.py` now contains concrete command mechanics for
the existing RPF-1.3-T4 obligation. It reuses `keycape_issuer_eso.documents()` and
`require_fixed_store()` with the byte-bounded `bounded_public_command.command()`
transport. Only the fixed public ESO resources are read using raw v1 paths.
A successful field-selected collection response proves observed absence.
The helper creates only the ExternalSecret, compares its fixed spec with only
the three pinned defaults normalized, and polls at most ten times for a recent
current-metadata controller report with stable UID. Request, command and total
attempt deadlines apply as described below. Controller resourceVersion updates
during successful readiness are captured.
Removal is permitted only for that instance's verified case-created revision,
rechecks spec/UID/resourceVersion, uses Kubernetes DeleteOptions with both UID
and resourceVersion preconditions, and confirms absence with bounded polling.
Existing matching objects return changed=false and are never rollback candidates.
Creation response loss, readiness failure after creation, replacement, delete
conflict or unconfirmed deletion raises a fixed uncertainty diagnostic and keeps
the last verified revision when known. No retry adopts an uncertain create; no
replacement is deleted. An unknown create outcome requires owner reconciliation
using the fixed resource name even when no UID could be recovered. Target Secret
garbage collection by ESO/Kubernetes is outside this helper; it never reads or
directly deletes Secret objects. There is no claim that deletion proves target
Secret disappearance.
The callable `production_bindings()` **always refuses before any command**.
The private mechanics use empty environment and `/dev/null` kubeconfig and
therefore cannot borrow the caller's native session. They are source preparation,
not an adapter/evidence authority. Mechanics tests intercept command dispatch;
runner tests launch only synthetic local children. Neither provides production
callbacks or attestations. A real lane requires
an owner-reviewed authenticated human aal2 session, acknowledged case revision
and caller binding, scoped Kubernetes credentials, one-shot durable admission
journaling and authenticated evidence publication/verification, plus real
per-attempt session revocation and recovery. None exists for this lane at the
pinned source. The generic task_execution_journal module is real storage but
explicitly does not admit native work; it is not silently repurposed here.
The mint broker's case checker and OpenBao lookup-self are real for its existing
mint lane, not delivery authentication. The attended custody-projection SSH
transport and credential-change evidence writer likewise confer no delivery
lane authority. No unsigned receipt or `True` oracle replaces these facilities.
Consequently RPF-1.3-T4 remains open for real capability binding and owner review;
this patch prepares concrete ESO command/state mechanics and regressions.
No ESO role, policy, Store, admission service, grant, catalog acceptance, native
permission or owner descriptor acceptance is created or inferred.
## r2 source mechanics: current controller report and containment
This preparation is not production admission or owner acceptance. The factory
continues to refuse absent actual case/client authorization, scoped caller and
session, owner exclusion/admission, durable admission/recovery, authenticated
evidence and per-attempt revocation. Empty transport environment and /dev/null
kubeconfig deliberately remain unusable for ambient production access.
The pinned ESO v0.16.1 controller (commit
8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af) sets syncedResourceVersion to
its metadata generation plus '-' plus the MD5 of its Go-formatted annotations
and labels struct. That token is not Kubernetes resourceVersion or authenticated
evidence. r2 compares the exact token, a unique Ready=True/SecretSynced condition
with the controller's successful 'secret synced' message, and a valid refreshTime
no older than the fixed five-minute refresh interval and not in the future.
The controller sets refreshTime to reconcile start. Its Retain/missing-provider
branch also sets Ready but uses a different message; r2 refuses that branch.
These checks prove a recent controller report for current object metadata under
the pinned implementation, not present Secret contents, provider availability,
a live credential outcome or runtime authentication. Deployed-version matching,
trusted API/status custody and a sane local clock remain production prerequisites.
Store status has no generation binding. r2 accepts only generation 1 with the
fixed spec and unique Valid/'store validated' Ready condition, and records its
UID/resourceVersion. A later generation is refused with
store_ready_generation_unverifiable. Repeated exact Store observations before
create, during readiness and before a receipt detect observed drift; they cannot
atomically bind create or ESO's Store lookup to a Store revision. A wholly
interleaved change/restoration remains invisible. Actual owner-controlled
exclusion/admission is missing and remains a production blocker. Cleanup of a
verified ExternalSecret uses its own UID/RV preconditions and does not depend on
Store availability.
All reads use explicit raw v1 paths. The fixed ExternalSecret collection query
includes an exact metadata.name field selector and limit 2 to obtain structured
successful absence. Its response must be a v1 ExternalSecretList with at most
one fixed namespace/name item and no continuation. Errors are never absence.
This needs field-selected ExternalSecret list access in any future real session;
no such access is granted by this code. No Kubernetes Secret is read or listed.
The new POSIX runner bounds combined stdout+stderr during collection to 262144
bytes, discards stderr, uses a six-second maximum command deadline, sends input
without a pipe deadlock, and kills the process group before reaping its leader
on success or failure. Wait/reap has a separate one-second cleanup budget;
unconfirmed cleanup is refusal. Operations have a 30-second monotonic deadline
and cannot return success once it has elapsed. Descendant tests verify ordinary
same-group children with held and closed pipes. This is process-group cleanup,
not a sandbox guarantee against a program escaping its group/session. Privileged
runtime isolation is not established by this runner or its tests.
The three actual v1 remoteRef defaults (conversionStrategy=Default,
decodingStrategy=None, metadataPolicy=None) compare equally when omitted or
explicit. Other spec changes remain conflicts. The metadata-only contract helper
is not itself the runtime freshness gate; r2 delivery adds the checks above.
The fixed template bytes remain unchanged. Unknown mutation responses preserve
pending reconciliation, including any valid returned UID/RV, and never gain a
blind delete/retry path. Local one-shot state remains non-durable.

View file

@ -0,0 +1,107 @@
"""POSIX byte/time bounded subprocess collection; no credential/session authority."""
from __future__ import annotations
import json
import os
import selectors
import signal
import subprocess
import time
MAX_OUTPUT = 262144 # combined stdout + stderr; stderr is counted, never retained
MAX_INPUT = 65536
CLEANUP_TIMEOUT = 1
class CommandRefused(Exception):
"""Only fixed diagnostics escape this transport."""
def command(argv, *, payload=None, timeout=6):
if not isinstance(timeout, (float, int)) or not 0 < timeout <= 6:
raise CommandRefused('command_budget_invalid')
try:
body = b'' if payload is None else json.dumps(payload).encode()
if len(body) > MAX_INPUT:
raise CommandRefused('command_input_bound')
except (ValueError, TypeError):
raise CommandRefused('command_input_invalid') from None
process = None
selector = selectors.DefaultSelector()
output = bytearray()
total = 0
offset = 0
failure = None
code = None
deadline = time.monotonic() + timeout
try:
process = subprocess.Popen(argv, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={},
start_new_session=True, close_fds=True, shell=False)
for stream in (process.stdout, process.stderr, process.stdin):
os.set_blocking(stream.fileno(), False)
selector.register(process.stdout, selectors.EVENT_READ, 'stdout')
selector.register(process.stderr, selectors.EVENT_READ, 'stderr')
if body:
selector.register(process.stdin, selectors.EVENT_WRITE, 'stdin')
else:
process.stdin.close()
while True:
remaining = deadline - time.monotonic()
if remaining <= 0:
raise CommandRefused('command_timeout')
# WNOWAIT deliberately leaves the leader unreaped so its PID/PGID
# cannot be recycled before final process-group cleanup.
state = os.waitid(os.P_PID, process.pid,
os.WEXITED | os.WNOHANG | os.WNOWAIT)
if state is not None and not selector.get_map():
break
for key, _ in selector.select(min(remaining, .05)):
stream = key.fileobj
if key.data == 'stdin':
try:
offset += os.write(stream.fileno(), body[offset:offset + 4096])
except BlockingIOError:
continue
if offset == len(body):
selector.unregister(stream); stream.close()
continue
try:
block = os.read(stream.fileno(), 4096)
except BlockingIOError:
continue
if not block:
selector.unregister(stream); stream.close()
continue
total += len(block)
if total > MAX_OUTPUT:
raise CommandRefused('command_output_bound')
if key.data == 'stdout':
output.extend(block)
except CommandRefused as error:
failure = str(error)
except Exception:
failure = 'command_failed'
finally:
selector.close()
if process is not None:
# Also kills descendants that kept pipes open or closed all pipes.
# The leader has not been reaped, avoiding PGID reuse races.
try:
os.killpg(process.pid, signal.SIGKILL)
except ProcessLookupError:
pass
except Exception:
failure = 'command_cleanup_unconfirmed'
for stream in (process.stdin, process.stdout, process.stderr):
if stream is not None:
stream.close()
try:
code = process.wait(timeout=CLEANUP_TIMEOUT)
except Exception:
failure = 'command_cleanup_unconfirmed'
if failure is not None:
raise CommandRefused(failure) from None
if code != 0:
raise CommandRefused('command_failed') from None
return bytes(output)

View file

@ -0,0 +1,284 @@
"""RPF-1.3-T4 source mechanics; production admission deliberately unavailable."""
from __future__ import annotations
from copy import deepcopy
from datetime import datetime, timezone
import hashlib
import json
import re
import time
from keycape_issuer_eso import documents, require_fixed_store, Refused, canonical_external_spec
from bounded_public_command import command, CommandRefused
POLL_COUNT = 10
POLL_DELAY = 1
ATTEMPT_TIMEOUT = 30
API = '/apis/external-secrets.io/v1'
class Uncertain(Refused):
def __init__(self, reason, revision=None):
super().__init__(reason)
self.revision = deepcopy(revision)
def production_bindings(*args, **kwargs):
raise Refused('delivery_admission_exclusion_session_journal_evidence_revocation_unavailable')
def _metadata(obj):
if not isinstance(obj, dict) or not isinstance(obj.get('metadata'), dict):
raise Refused('public_metadata_invalid')
return obj['metadata']
def _revision(obj):
meta = _metadata(obj)
result = {'uid': meta.get('uid'), 'resource_version': meta.get('resourceVersion')}
if any(not isinstance(x, str) or not re.fullmatch(r'[A-Za-z0-9._:-]{1,192}', x)
for x in result.values()):
raise Refused('public_revision_invalid')
return result
def _generation(obj):
value = _metadata(obj).get('generation')
if type(value) is not int or not 1 <= value <= 2**63 - 1:
raise Refused('public_generation_invalid')
return value
def _synced_version(obj):
"""ESO v0.16.1 util.GetResourceVersion + utils.ObjectHash, not metadata RV.
Go fmt %+v on the actual two-field struct uses sorted string maps, raw
keys/values, and map[] for both nil and empty maps. Tests compare this
implementation to a standalone Go extraction of that exact upstream code.
This MD5 is the controller's revision token, never authentication evidence.
"""
meta = _metadata(obj)
maps = []
for name in ('annotations', 'labels'):
value = meta.get(name)
if value is None:
value = {}
if not isinstance(value, dict) or any(
not isinstance(k, str) or not isinstance(v, str) for k, v in value.items()):
raise Refused('public_metadata_map_invalid')
try:
textual = 'map[' + ' '.join(k + ':' + value[k] for k in sorted(value)) + ']'
textual.encode('utf-8', errors='strict')
except UnicodeError:
raise Refused('public_metadata_map_invalid') from None
maps.append(name + ':' + textual)
hashed = hashlib.md5(('{' + ' '.join(maps) + '}').encode(), usedforsecurity=False).hexdigest()
return str(_generation(obj)) + '-' + hashed
def _ready(obj):
status = obj.get('status')
if not isinstance(status, dict):
return False
conditions = status.get('conditions')
if not isinstance(conditions, list) or any(not isinstance(c, dict) for c in conditions):
return False
ready = [c for c in conditions if c.get('type') == 'Ready']
if len(ready) != 1 or any(ready[0].get(k) != v for k, v in {
'status': 'True', 'reason': 'SecretSynced', 'message': 'secret synced'}.items()):
return False
if status.get('syncedResourceVersion') != _synced_version(obj):
return False
stamp = status.get('refreshTime')
if not isinstance(stamp, str) or not re.fullmatch(
r'\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?(?:Z|[+-]\d\d:\d\d)', stamp):
return False
try:
refreshed = datetime.fromisoformat(stamp.replace('Z', '+00:00'))
age = (datetime.now(timezone.utc) - refreshed).total_seconds()
return 0 <= age <= 300
except (ValueError, OverflowError):
return False
class _DeliveryMechanics:
"""No authority boundary: injected calls in tests, unusable ambient transport.
Store observations detect drift; they cannot exclude an ABA race between
observations or bind ESO's store lookup atomically. Production needs real
owner exclusion/admission in addition to authenticated caller facilities.
"""
def __init__(self, client_id):
self.client_id = client_id
self.store, self.external = documents(client_id)
self.created = None
self.pending = False
self.used = False
self.deadline = None
def _call(self, args, payload=None):
try:
remaining = self.deadline - time.monotonic()
if remaining <= 0:
raise Refused('attempt_deadline')
raw = command(['kubectl', '--kubeconfig=/dev/null',
'--request-timeout=5s', *args],
payload=payload, timeout=min(6, remaining))
if time.monotonic() >= self.deadline:
raise Refused('attempt_deadline')
# Runner enforces the same bound during collection, not just here.
if not isinstance(raw, bytes) or len(raw) > 262144:
raise Refused('public_response_bound')
if not raw.strip():
return None
value = json.loads(raw)
if not isinstance(value, dict):
raise Refused('public_response_shape')
return value
except Refused:
raise
except Exception:
raise Refused('public_command_failed') from None
def _get(self, store=False):
if store:
return self._call(['get', '--raw', API + '/clustersecretstores/' + self.store['metadata']['name']])
# Raw GET fixes API version and avoids kubectl mapper fallback. A fixed
# field-selected collection provides structured successful absence;
# stderr/nonzero is never interpreted as NotFound. No Secret is listed.
uri = (API + '/namespaces/sso/externalsecrets?fieldSelector=metadata.name%3D'
+ self.external['metadata']['name'] + '&limit=2')
listing = self._call(['get', '--raw', uri])
if (not isinstance(listing, dict) or listing.get('apiVersion') != 'external-secrets.io/v1'
or listing.get('kind') != 'ExternalSecretList'
or not isinstance(listing.get('metadata'), dict)
or listing['metadata'].get('continue') not in (None, '')
or not isinstance(listing.get('items'), list) or len(listing['items']) > 1):
raise Refused('public_collection_invalid')
if not listing['items']:
return None
value = listing['items'][0]
meta = _metadata(value)
if (value.get('apiVersion') != 'external-secrets.io/v1'
or value.get('kind') != 'ExternalSecret'
or meta.get('name') != self.external['metadata']['name']
or meta.get('namespace') != 'sso'):
raise Refused('public_collection_identity_invalid')
return value
def _store_revision(self, sealed=None):
store = self._get(store=True)
require_fixed_store(store, self.client_id)
if _metadata(store).get('deletionTimestamp'):
raise Refused('store_terminating')
revision = _revision(store)
# Actual Store status has no generation binding. Generation 1 excludes
# a prior spec update under Kubernetes generation semantics. For any
# later generation Ready cannot establish current-spec validation.
if _generation(store) != 1:
raise Refused('store_ready_generation_unverifiable')
conditions = store.get('status', {}).get('conditions', [])
ready = [c for c in conditions if isinstance(c, dict) and c.get('type') == 'Ready']
if len(ready) != 1 or ready[0].get('reason') != 'Valid' or ready[0].get('message') != 'store validated':
raise Refused('store_validation_unverified')
if sealed is not None and sealed != revision:
raise Refused('store_revision_changed')
return revision
def _match(self, obj):
metadata = _metadata(obj)
if (obj.get('apiVersion') != self.external['apiVersion']
or obj.get('kind') != 'ExternalSecret'
or metadata.get('name') != self.external['metadata']['name']
or metadata.get('namespace') != 'sso' or metadata.get('deletionTimestamp')
or canonical_external_spec(obj.get('spec')) != canonical_external_spec(self.external['spec'])):
raise Refused('delivery_conflict')
_generation(obj)
return _revision(obj)
def _receipt(self, operation, before, after):
return {'schema': 'netkingdom.deliver-secret.v1', 'operation': operation,
'namespace': 'sso', 'name': self.external['metadata']['name'],
'custody_reference': 'platform/workloads/keycape/' + self.client_id,
'key_names': ['client-secret'], 'values_emitted': False,
'changed': before is None if operation == 'deliver' else True,
'before': before, 'after': after}
def deliver(self):
if self.used or self.pending or self.created is not None:
raise Refused('attempt_already_used_or_unreconciled')
self.used = True # local one-shot only, never a durable admission journal
self.deadline = time.monotonic() + ATTEMPT_TIMEOUT
sealed_store = self._store_revision()
old = self._get()
before = self._match(old) if old is not None else None
uid = before['uid'] if before else None
self._store_revision(sealed_store)
if old is None:
self.pending = True
try:
created = self._call(['-n', 'sso', 'create', '-f', '-', '-o', 'json'], self.external)
# Retain response identity even if admission changed its spec.
# Pending prevents deletion until exact verified delivery.
self.created = _revision(created)
self._match(created)
uid = self.created['uid']
except Exception:
raise Uncertain('create_outcome_unverified', self.created) from None
try:
for index in range(POLL_COUNT):
self._store_revision(sealed_store)
current = self._get()
after = self._match(current)
if after['uid'] != uid:
raise Refused('delivery_replaced')
if _ready(current):
self._store_revision(sealed_store)
if self.created is not None:
self.created = after
self.pending = False
return self._receipt('deliver', before, after)
if index + 1 < POLL_COUNT:
remaining = self.deadline - time.monotonic()
if remaining <= 0:
raise Refused('attempt_deadline')
time.sleep(min(POLL_DELAY, remaining))
raise Refused('delivery_current_ready_timeout')
except Exception:
if self.pending or self.created is not None:
raise Uncertain('created_delivery_requires_reconciliation', self.created) from None
raise Refused('delivery_verification_failed') from None
def remove(self, revision):
if self.pending or self.created is None or revision != self.created:
raise Refused('no_verified_case_creation')
self.deadline = time.monotonic() + ATTEMPT_TIMEOUT
try:
if self._match(self._get()) != self.created:
raise Refused('created_revision_changed')
except Exception:
raise Uncertain('created_revision_changed', self.created) from None
uri = API + '/namespaces/sso/externalsecrets/' + self.external['metadata']['name']
options = {'apiVersion': 'v1', 'kind': 'DeleteOptions',
'preconditions': {'uid': self.created['uid'],
'resourceVersion': self.created['resource_version']}}
prior = deepcopy(self.created)
self.pending = True
try:
self._call(['delete', '--raw', uri, '-f', '-'], options)
for index in range(POLL_COUNT):
current = self._get()
if current is None:
self.created = None
self.pending = False
return self._receipt('remove', prior, None)
if _revision(current)['uid'] != prior['uid']:
raise Refused('replacement_preserved')
if index + 1 < POLL_COUNT:
remaining = self.deadline - time.monotonic()
if remaining <= 0:
raise Refused('attempt_deadline')
time.sleep(min(POLL_DELAY, remaining))
raise Refused('delete_unconfirmed')
except Exception:
raise Uncertain('cleanup_requires_reconciliation', prior) from None

View file

@ -0,0 +1,90 @@
"""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 canonical_external_spec(spec):
"""Normalize only the three pinned ESO v0.16.1 remoteRef defaults.
Omitted and explicit defaults are equivalent; all other fields remain exact.
This says nothing about freshness, admission authority or Store continuity.
"""
value = deepcopy(spec)
if isinstance(value, dict) and isinstance(value.get('data'), list):
for item in value['data']:
if isinstance(item, dict) and isinstance(item.get('remoteRef'), dict):
for name, default in (('conversionStrategy', 'Default'),
('decodingStrategy', 'None'),
('metadataPolicy', 'None')):
item['remoteRef'].setdefault(name, default)
return value
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 (canonical_external_spec(observed.get('spec'))
!= canonical_external_spec(expected['spec'])
if expected['kind'] == 'ExternalSecret'
else 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])

View file

@ -0,0 +1,57 @@
[
{
"metadata": {
"generation": 1
},
"expected": "1-2f2c636d06132f67992c15a1daeb0a5f"
},
{
"metadata": {
"generation": 2,
"annotations": {},
"labels": {}
},
"expected": "2-2f2c636d06132f67992c15a1daeb0a5f"
},
{
"metadata": {
"generation": 3,
"annotations": {
"b": "2",
"a": "1"
},
"labels": {
"z": "last",
"c": "first"
}
},
"expected": "3-7ad4f86f5aba928cde01913c7b9d962a"
},
{
"metadata": {
"generation": 4,
"annotations": {
"a:b": "c d",
"a b": "[]:%+v"
},
"labels": {
"newline\n": "tab\t"
}
},
"expected": "4-48e35b520c38b2944d461645440cc3cd"
},
{
"metadata": {
"generation": 9223372036854775807,
"annotations": {
"é": "好",
"🦊": "😀"
},
"labels": {
"": "",
"null": "\u0000"
}
},
"expected": "9223372036854775807-da6b2f7c16875cb96b3a00d83378eda3"
}
]

View file

@ -0,0 +1,95 @@
import os
from pathlib import Path
import sys
import tempfile
import time
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
import bounded_public_command as runner
# Every launched fixture denies socket creation in its own interpreter as well
# as the parent. They only perform controlled local stdout/stderr/process work.
PREFIX = "import socket; socket.socket=lambda *a,**k: (_ for _ in ()).throw(AssertionError('network forbidden')); "
class BoundedCommand(unittest.TestCase):
def setUp(self):
p = patch.dict(os.environ, {}, clear=True); p.start(); self.addCleanup(p.stop)
p = patch('socket.socket', side_effect=AssertionError('network forbidden'))
p.start(); self.addCleanup(p.stop)
def call(self, code, timeout=2, payload=None):
return runner.command(['/usr/bin/python3', '-c', PREFIX + code], timeout=timeout, payload=payload)
def test_empty_environment_and_input_roundtrip(self):
raw = self.call("import os,sys,json; b=sys.stdin.buffer.read(); print(json.dumps({'keys':sorted(os.environ),'body':json.loads(b)}))", payload={'fixed': 'metadata'})
import json
response = json.loads(raw)
# CPython may inject LC_CTYPE during startup even though exec env={};
# neither HOME nor credential variables are inherited.
self.assertFalse(set(response['keys']) - {'LC_CTYPE'})
self.assertEqual(response['body'], {'fixed': 'metadata'})
def test_stdout_flood_stopped_with_fixed_error(self):
with self.assertRaisesRegex(runner.CommandRefused, '^command_output_bound$'):
self.call("import os; [os.write(1,b'PRIVATE'*4096) for _ in range(1000)]")
def test_stderr_flood_stopped_with_fixed_error(self):
with self.assertRaisesRegex(runner.CommandRefused, '^command_output_bound$'):
self.call("import os; [os.write(2,b'PRIVATE'*4096) for _ in range(1000)]")
def test_combined_cap_not_independent_per_stream(self):
with self.assertRaisesRegex(runner.CommandRefused, '^command_output_bound$'):
self.call("import os; os.write(1,b'x'*140000); os.write(2,b'y'*140000)")
def test_error_output_never_escapes(self):
with self.assertRaisesRegex(runner.CommandRefused, '^command_failed$') as error:
self.call("import sys; print('PRIVATE'); print('PRIVATE',file=sys.stderr); sys.exit(7)")
self.assertNotIn('PRIVATE', str(error.exception))
def test_sleep_timeout_is_real_and_bounded(self):
start = time.monotonic()
with self.assertRaisesRegex(runner.CommandRefused, '^command_timeout$'):
self.call('import time; time.sleep(60)', timeout=.15)
self.assertLess(time.monotonic() - start, 2)
def test_descendant_holding_pipes_is_killed(self):
with tempfile.TemporaryDirectory(prefix='delivery-runner-') as scratch:
pidfile = Path(scratch) / 'descendant'
code = ("import os,time; pid=os.fork(); "
"(time.sleep(60) if pid==0 else "
f"(open({str(pidfile)!r},'w').write(str(pid)),os._exit(0)))")
with self.assertRaisesRegex(runner.CommandRefused, '^command_timeout$'):
self.call(code, timeout=.2)
self.assertTrue(pidfile.exists())
pid = pidfile.read_text()
status = Path('/proc') / pid / 'stat'
for _ in range(20):
if not status.exists() or status.read_text().split()[2] == 'Z': break
time.sleep(.01)
if status.exists(): self.assertEqual(status.read_text().split()[2], 'Z')
def test_descendant_with_closed_pipes_is_killed_on_success(self):
with tempfile.TemporaryDirectory(prefix='delivery-runner-') as scratch:
pidfile = Path(scratch) / 'descendant'
code = ("import os,time; pid=os.fork(); "
"(os.close(0),os.close(1),os.close(2),time.sleep(60)) if pid==0 else "
f"(open({str(pidfile)!r},'w').write(str(pid)),print('ok'))")
self.assertEqual(self.call(code), b'ok\n')
status = Path('/proc') / pidfile.read_text() / 'stat'
# Kill delivery may race the proc-state observation; wait boundedly.
for _ in range(20):
if not status.exists() or status.read_text().split()[2] == 'Z': break
time.sleep(.01)
if status.exists(): self.assertEqual(status.read_text().split()[2], 'Z')
def test_input_bound_and_spawn_failure_fixed(self):
with self.assertRaisesRegex(runner.CommandRefused, '^command_input_bound$'):
self.call('pass', payload={'data': 'x'*65536})
with self.assertRaisesRegex(runner.CommandRefused, '^command_failed$'):
runner.command(['/nonexistent-fixture-binary'], timeout=.1)
if __name__ == '__main__': unittest.main()

View file

@ -0,0 +1,284 @@
from copy import deepcopy
from datetime import datetime, timedelta, timezone
import json
import os
from pathlib import Path
import sys
import unittest
from unittest.mock import patch
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
import keycape_issuer_delivery as lane
from bounded_public_command import CommandRefused
class Mechanics(unittest.TestCase):
def setUp(self):
p = patch.dict(os.environ, {}, clear=True); p.start(); self.addCleanup(p.stop)
p = patch('socket.socket', side_effect=AssertionError('network forbidden'))
p.start(); self.addCleanup(p.stop)
self.m = lane._DeliveryMechanics('forgejo')
self.store = self.resource(self.m.store, store=True)
self.obj = None
self.calls = []
self.store_reads = 0
self.hook = lambda args: None
self.fail_create = False
self.fail_delete = False
self.create_mutate = lambda obj: obj
self.force_list = None
self.poll_object = None
self.clock = 0
self.clock_step = 0
p = patch.object(lane, 'command', side_effect=self.command)
p.start(); self.addCleanup(p.stop)
p = patch.object(lane.time, 'sleep'); p.start(); self.addCleanup(p.stop)
p = patch.object(lane.time, 'monotonic', side_effect=lambda: self.clock)
p.start(); self.addCleanup(p.stop)
def resource(self, doc, store=False):
value = deepcopy(doc)
value['metadata'].update(uid='store-uid' if store else 'created-uid', resourceVersion='10', generation=1)
if store:
value['status'] = {'conditions': [{'type': 'Ready', 'status': 'True',
'reason': 'Valid', 'message': 'store validated'}]}
else:
value['spec'] = lane.canonical_external_spec(value['spec'])
value['status'] = {'conditions': [{'type': 'Ready', 'status': 'True',
'reason': 'SecretSynced', 'message': 'secret synced'}],
'syncedResourceVersion': lane._synced_version(value),
'refreshTime': datetime.now(timezone.utc).isoformat()}
return value
def command(self, args, *, payload=None, timeout=6):
self.assertEqual(args[:3], ['kubectl', '--kubeconfig=/dev/null', '--request-timeout=5s'])
self.assertGreater(timeout, 0); self.assertLessEqual(timeout, 6)
self.calls.append((deepcopy(args), deepcopy(payload)))
self.clock += self.clock_step
self.hook(args)
if 'create' in args:
if self.fail_create:
raise CommandRefused('PRIVATE PROVIDER VALUE')
self.obj = self.create_mutate(self.resource(self.m.external))
value = self.obj
elif 'delete' in args:
if self.fail_delete:
raise CommandRefused('PRIVATE PROVIDER VALUE')
revision = lane._revision(self.obj)
if payload['preconditions'] != {'uid': revision['uid'], 'resourceVersion': revision['resource_version']}:
raise CommandRefused('conditional conflict fixture')
self.obj = None
value = {'kind': 'Status', 'status': 'Success'}
elif '/clustersecretstores/' in args[-1]:
self.store_reads += 1
value = self.store
else:
self.assertIn('get', args); self.assertIn('--raw', args)
self.assertEqual(args[-1], lane.API + '/namespaces/sso/externalsecrets?fieldSelector=metadata.name%3Dkeycape-forgejo-client&limit=2')
value = self.force_list if self.force_list is not None else {
'apiVersion': 'external-secrets.io/v1', 'kind': 'ExternalSecretList',
'metadata': {}, 'items': [] if self.obj is None else [self.poll_object or self.obj]}
return json.dumps(deepcopy(value)).encode()
def test_actual_defaults_create_current_ready_and_conditional_delete(self):
delivered = self.m.deliver()
self.assertTrue(delivered['changed']); self.assertIsNone(delivered['before'])
self.assertGreaterEqual(self.store_reads, 4)
removed = self.m.remove(delivered['after'])
self.assertEqual(removed['after'], None)
args, payload = next(c for c in self.calls if 'delete' in c[0])
self.assertEqual(args[5], lane.API + '/namespaces/sso/externalsecrets/keycape-forgejo-client')
self.assertEqual(payload['preconditions'], {'uid': 'created-uid', 'resourceVersion': '10'})
def test_existing_no_change_cannot_be_deleted_or_reused(self):
self.obj = self.resource(self.m.external)
delivered = self.m.deliver()
self.assertFalse(delivered['changed'])
with self.assertRaises(lane.Refused): self.m.remove(delivered['after'])
with self.assertRaises(lane.Refused): self.m.deliver()
self.assertFalse(any('create' in args or 'delete' in args for args, _ in self.calls))
def test_stale_current_spec_and_metadata_hash_never_ready(self):
obj = self.resource(self.m.external)
for field, value in [('generation', 2), ('labels', {'new': 'metadata'}), ('annotations', {'new': 'metadata'})]:
with self.subTest(field=field):
changed = deepcopy(obj); changed['metadata'][field] = value
self.assertFalse(lane._ready(changed))
def test_exact_go_upstream_revision_vectors(self):
vectors = json.loads((Path(__file__).parent / 'eso-hash-vectors.json').read_text())
for vector in vectors:
self.assertEqual(lane._synced_version({'metadata': vector['metadata']}), vector['expected'])
def test_wrong_default_strategies_and_identity_never_match(self):
for field, value in [('conversionStrategy', 'Unicode'), ('decodingStrategy', 'Base64'),
('metadataPolicy', 'Fetch'), ('key', 'elsewhere'), ('extra', 'value')]:
obj = self.resource(self.m.external)
obj['spec']['data'][0]['remoteRef'][field] = value
with self.assertRaises(lane.Refused): self.m._match(obj)
for field, value in [('namespace', 'default'), ('name', 'other'), ('generation', True)]:
obj = self.resource(self.m.external); obj['metadata'][field] = value
with self.assertRaises(lane.Refused): self.m._match(obj)
def test_invalid_metadata_maps_never_produce_hash(self):
for value in ([], 'str', {'key': None}, {'key': '\ud800'}):
obj = self.resource(self.m.external); obj['metadata']['labels'] = value
with self.assertRaises(lane.Refused): lane._synced_version(obj)
def test_client_override_injection_refused_before_command(self):
for client in ('x/../../secret', 'x;echo', 'A', None, 'a'*49):
with self.assertRaises(lane.Refused): lane._DeliveryMechanics(client)
self.assertEqual(self.calls, [])
def test_retained_missing_stale_future_duplicate_status_not_ready(self):
for variant in ('retained', 'missing', 'old', 'future', 'duplicate', 'invalid_time'):
with self.subTest(variant=variant):
obj = self.resource(self.m.external)
if variant == 'retained': obj['status']['conditions'][0]['message'] = 'secret retained due to DeletionPolicy=Retain'
if variant == 'missing': obj['status'].pop('syncedResourceVersion')
if variant in ('old', 'future'):
delta = -301 if variant == 'old' else 60
obj['status']['refreshTime'] = (datetime.now(timezone.utc) + timedelta(seconds=delta)).isoformat()
if variant == 'duplicate': obj['status']['conditions'] *= 2
if variant == 'invalid_time': obj['status']['refreshTime'] = 'PRIVATE MALFORMED'
self.assertFalse(lane._ready(obj))
def test_stale_delivery_times_out_with_reconciliation_provenance(self):
self.create_mutate = lambda obj: {**obj, 'status': {'conditions': [{'type': 'Ready', 'status': 'True'}]}}
with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
self.assertEqual(error.exception.revision['uid'], 'created-uid')
with self.assertRaises(lane.Refused): self.m.remove(error.exception.revision)
self.assertLessEqual(len(self.calls), 2 * lane.POLL_COUNT + 4)
def test_store_prior_generation_refused_before_create(self):
self.store['metadata']['generation'] = 2
with self.assertRaisesRegex(lane.Refused, 'generation_unverifiable'): self.m.deliver()
self.assertEqual(len(self.calls), 1)
def test_store_replaced_before_precreate_read_prevents_create(self):
def hook(args):
if '/externalsecrets?' in args[-1]: self.store['metadata']['uid'] = 'replacement'
self.hook = hook
with self.assertRaisesRegex(lane.Refused, 'store_revision_changed'): self.m.deliver()
self.assertFalse(any('create' in args for args, _ in self.calls))
def test_store_replaced_inside_create_window_detected_after_mutation(self):
def hook(args):
if 'create' in args: self.store['metadata']['uid'] = 'replacement'
self.hook = hook
with self.assertRaises(lane.Uncertain): self.m.deliver()
self.assertIsNotNone(self.obj) # double-read did not exclude mutation
self.assertTrue(self.m.pending)
def test_store_aba_race_is_not_claimed_excluded(self):
# A mutation and restoration wholly between observations is invisible.
seen = []
def hook(args):
if 'create' in args:
original = deepcopy(self.store)
self.store['metadata']['uid'] = 'transient-replacement'
seen.append(self.store['metadata']['uid'])
self.store = original
seen.append(self.store['metadata']['uid'])
self.hook = hook
self.assertTrue(self.m.deliver()['changed'])
self.assertEqual(len(seen), 2)
self.assertEqual(seen, ['transient-replacement', 'store-uid'])
def test_malformed_create_preserves_fixed_unknown_outcome(self):
for metadata in (None, [], {}, {'uid': 'created-uid', 'resourceVersion': None}):
with self.subTest(metadata=metadata):
self.m = lane._DeliveryMechanics('forgejo'); self.obj = None
self.create_mutate = lambda obj: {**obj, 'metadata': metadata}
with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
self.assertEqual(str(error.exception), 'create_outcome_unverified')
self.assertTrue(self.m.pending)
def test_unknown_create_and_delete_never_blindly_retry(self):
self.fail_create = True
with self.assertRaises(lane.Uncertain): self.m.deliver()
with self.assertRaises(lane.Refused): self.m.deliver()
self.fail_create = False; self.m = lane._DeliveryMechanics('forgejo')
delivered = self.m.deliver(); self.fail_delete = True
with self.assertRaises(lane.Uncertain) as error: self.m.remove(delivered['after'])
self.assertNotIn('PRIVATE', str(error.exception))
with self.assertRaises(lane.Refused): self.m.remove(delivered['after'])
def test_delete_race_uses_server_preconditions_and_preserves_replacement(self):
delivered = self.m.deliver()
def hook(args):
if 'delete' in args: self.obj['metadata']['uid'] = 'replacement'
self.hook = hook
with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
self.assertEqual(self.obj['metadata']['uid'], 'replacement')
def test_status_rv_changed_before_remove_no_delete(self):
delivered = self.m.deliver(); self.obj['metadata']['resourceVersion'] = '11'
with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
self.assertFalse(any('delete' in args for args, _ in self.calls))
def test_fresh_process_and_forged_revision_never_delete(self):
delivered = self.m.deliver()
with self.assertRaises(lane.Refused): lane._DeliveryMechanics('forgejo').remove(delivered['after'])
with self.assertRaises(lane.Refused): self.m.remove({'uid': 'fake', 'resource_version': '10'})
self.assertFalse(any('delete' in args for args, _ in self.calls))
def test_poll_rv_is_captured(self):
reads = []
def hook(args):
if '/externalsecrets?' in args[-1] and self.obj is not None:
self.obj['metadata']['resourceVersion'] = '11'
reads.append(True)
self.hook = hook
delivered = self.m.deliver()
self.assertEqual(delivered['after']['resource_version'], '11')
self.assertTrue(reads)
def test_unconfirmed_delete_is_bounded_and_preserves_reconciliation(self):
delivered = self.m.deliver()
prior = deepcopy(self.obj)
terminating = deepcopy(prior); terminating['metadata']['deletionTimestamp'] = 'now'
with patch.object(self.m, '_get', side_effect=[prior] + [terminating]*lane.POLL_COUNT):
with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
self.assertTrue(self.m.pending)
with self.assertRaises(lane.Refused): self.m.remove(delivered['after'])
def test_lost_delete_after_server_removal_is_still_uncertain(self):
delivered = self.m.deliver(); self.fail_delete = True
def hook(args):
if 'delete' in args: self.obj = None
self.hook = hook
with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
self.assertIsNone(self.obj)
self.assertIsNotNone(self.m.created)
self.assertTrue(self.m.pending)
def test_collection_foreign_object_cannot_be_adopted(self):
self.obj = self.resource(self.m.external); self.obj['metadata']['name'] = 'other'
with self.assertRaisesRegex(lane.Refused, 'collection_identity'): self.m.deliver()
self.assertFalse(any('create' in args for args, _ in self.calls))
def test_unknown_conflicting_or_unbounded_collection_never_absence(self):
for listing in ({}, {'kind': 'Status'}, {'apiVersion': 'external-secrets.io/v1',
'kind': 'ExternalSecretList', 'metadata': {'continue': 'more'}, 'items': []}):
with self.subTest(listing=listing):
self.m = lane._DeliveryMechanics('forgejo'); self.force_list = listing
with self.assertRaises(lane.Refused): self.m.deliver()
self.assertFalse(any('create' in args for args, _ in self.calls))
def test_total_attempt_deadline_blocks_late_success(self):
self.clock_step = 6
with self.assertRaises(lane.Uncertain): self.m.deliver()
self.assertLessEqual(len(self.calls), 6)
def test_no_secret_or_store_mutation_and_factory_refuses(self):
self.m.deliver()
for args, payload in self.calls:
self.assertFalse(set(args) & {'secret', 'secrets'})
if payload and 'create' in args: self.assertEqual(payload['kind'], 'ExternalSecret')
before = len(self.calls)
with self.assertRaises(lane.Refused): lane.production_bindings(authenticated=True)
self.assertEqual(len(self.calls), before)
if __name__ == '__main__': unittest.main()

View file

@ -0,0 +1,993 @@
--- a/docs/keycape-issuer-eso-contract.md
+++ b/docs/keycape-issuer-eso-contract.md
@@ -66,3 +66,110 @@
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.
+
+## Prepared delivery mechanics (still unavailable as a production lane)
+
+`scripts/keycape_issuer_delivery.py` now contains concrete command mechanics for
+the existing RPF-1.3-T4 obligation. It reuses `keycape_issuer_eso.documents()` and
+`require_fixed_store()` with the byte-bounded `bounded_public_command.command()`
+transport. Only the fixed public ESO resources are read using raw v1 paths.
+A successful field-selected collection response proves observed absence.
+The helper creates only the ExternalSecret, compares its fixed spec with only
+the three pinned defaults normalized, and polls at most ten times for a recent
+current-metadata controller report with stable UID. Request, command and total
+attempt deadlines apply as described below. Controller resourceVersion updates
+during successful readiness are captured.
+
+Removal is permitted only for that instance's verified case-created revision,
+rechecks spec/UID/resourceVersion, uses Kubernetes DeleteOptions with both UID
+and resourceVersion preconditions, and confirms absence with bounded polling.
+Existing matching objects return changed=false and are never rollback candidates.
+Creation response loss, readiness failure after creation, replacement, delete
+conflict or unconfirmed deletion raises a fixed uncertainty diagnostic and keeps
+the last verified revision when known. No retry adopts an uncertain create; no
+replacement is deleted. An unknown create outcome requires owner reconciliation
+using the fixed resource name even when no UID could be recovered. Target Secret
+garbage collection by ESO/Kubernetes is outside this helper; it never reads or
+directly deletes Secret objects. There is no claim that deletion proves target
+Secret disappearance.
+
+The callable `production_bindings()` **always refuses before any command**.
+The private mechanics use empty environment and `/dev/null` kubeconfig and
+therefore cannot borrow the caller's native session. They are source preparation,
+not an adapter/evidence authority. Mechanics tests intercept command dispatch;
+runner tests launch only synthetic local children. Neither provides production
+callbacks or attestations. A real lane requires
+an owner-reviewed authenticated human aal2 session, acknowledged case revision
+and caller binding, scoped Kubernetes credentials, one-shot durable admission
+journaling and authenticated evidence publication/verification, plus real
+per-attempt session revocation and recovery. None exists for this lane at the
+pinned source. The generic task_execution_journal module is real storage but
+explicitly does not admit native work; it is not silently repurposed here.
+The mint broker's case checker and OpenBao lookup-self are real for its existing
+mint lane, not delivery authentication. The attended custody-projection SSH
+transport and credential-change evidence writer likewise confer no delivery
+lane authority. No unsigned receipt or `True` oracle replaces these facilities.
+
+Consequently RPF-1.3-T4 remains open for real capability binding and owner review;
+this patch prepares concrete ESO command/state mechanics and regressions.
+No ESO role, policy, Store, admission service, grant, catalog acceptance, native
+permission or owner descriptor acceptance is created or inferred.
+
+
+## r2 source mechanics: current controller report and containment
+
+This preparation is not production admission or owner acceptance. The factory
+continues to refuse absent actual case/client authorization, scoped caller and
+session, owner exclusion/admission, durable admission/recovery, authenticated
+evidence and per-attempt revocation. Empty transport environment and /dev/null
+kubeconfig deliberately remain unusable for ambient production access.
+
+The pinned ESO v0.16.1 controller (commit
+8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af) sets syncedResourceVersion to
+its metadata generation plus '-' plus the MD5 of its Go-formatted annotations
+and labels struct. That token is not Kubernetes resourceVersion or authenticated
+evidence. r2 compares the exact token, a unique Ready=True/SecretSynced condition
+with the controller's successful 'secret synced' message, and a valid refreshTime
+no older than the fixed five-minute refresh interval and not in the future.
+The controller sets refreshTime to reconcile start. Its Retain/missing-provider
+branch also sets Ready but uses a different message; r2 refuses that branch.
+These checks prove a recent controller report for current object metadata under
+the pinned implementation, not present Secret contents, provider availability,
+a live credential outcome or runtime authentication. Deployed-version matching,
+trusted API/status custody and a sane local clock remain production prerequisites.
+
+Store status has no generation binding. r2 accepts only generation 1 with the
+fixed spec and unique Valid/'store validated' Ready condition, and records its
+UID/resourceVersion. A later generation is refused with
+store_ready_generation_unverifiable. Repeated exact Store observations before
+create, during readiness and before a receipt detect observed drift; they cannot
+atomically bind create or ESO's Store lookup to a Store revision. A wholly
+interleaved change/restoration remains invisible. Actual owner-controlled
+exclusion/admission is missing and remains a production blocker. Cleanup of a
+verified ExternalSecret uses its own UID/RV preconditions and does not depend on
+Store availability.
+
+All reads use explicit raw v1 paths. The fixed ExternalSecret collection query
+includes an exact metadata.name field selector and limit 2 to obtain structured
+successful absence. Its response must be a v1 ExternalSecretList with at most
+one fixed namespace/name item and no continuation. Errors are never absence.
+This needs field-selected ExternalSecret list access in any future real session;
+no such access is granted by this code. No Kubernetes Secret is read or listed.
+
+The new POSIX runner bounds combined stdout+stderr during collection to 262144
+bytes, discards stderr, uses a six-second maximum command deadline, sends input
+without a pipe deadlock, and kills the process group before reaping its leader
+on success or failure. Wait/reap has a separate one-second cleanup budget;
+unconfirmed cleanup is refusal. Operations have a 30-second monotonic deadline
+and cannot return success once it has elapsed. Descendant tests verify ordinary
+same-group children with held and closed pipes. This is process-group cleanup,
+not a sandbox guarantee against a program escaping its group/session. Privileged
+runtime isolation is not established by this runner or its tests.
+
+The three actual v1 remoteRef defaults (conversionStrategy=Default,
+decodingStrategy=None, metadataPolicy=None) compare equally when omitted or
+explicit. Other spec changes remain conflicts. The metadata-only contract helper
+is not itself the runtime freshness gate; r2 delivery adds the checks above.
+The fixed template bytes remain unchanged. Unknown mutation responses preserve
+pending reconciliation, including any valid returned UID/RV, and never gain a
+blind delete/retry path. Local one-shot state remains non-durable.
--- /dev/null
+++ b/scripts/bounded_public_command.py
@@ -0,0 +1,107 @@
+"""POSIX byte/time bounded subprocess collection; no credential/session authority."""
+from __future__ import annotations
+
+import json
+import os
+import selectors
+import signal
+import subprocess
+import time
+
+MAX_OUTPUT = 262144 # combined stdout + stderr; stderr is counted, never retained
+MAX_INPUT = 65536
+CLEANUP_TIMEOUT = 1
+
+
+class CommandRefused(Exception):
+ """Only fixed diagnostics escape this transport."""
+
+
+def command(argv, *, payload=None, timeout=6):
+ if not isinstance(timeout, (float, int)) or not 0 < timeout <= 6:
+ raise CommandRefused('command_budget_invalid')
+ try:
+ body = b'' if payload is None else json.dumps(payload).encode()
+ if len(body) > MAX_INPUT:
+ raise CommandRefused('command_input_bound')
+ except (ValueError, TypeError):
+ raise CommandRefused('command_input_invalid') from None
+ process = None
+ selector = selectors.DefaultSelector()
+ output = bytearray()
+ total = 0
+ offset = 0
+ failure = None
+ code = None
+ deadline = time.monotonic() + timeout
+ try:
+ process = subprocess.Popen(argv, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE, env={},
+ start_new_session=True, close_fds=True, shell=False)
+ for stream in (process.stdout, process.stderr, process.stdin):
+ os.set_blocking(stream.fileno(), False)
+ selector.register(process.stdout, selectors.EVENT_READ, 'stdout')
+ selector.register(process.stderr, selectors.EVENT_READ, 'stderr')
+ if body:
+ selector.register(process.stdin, selectors.EVENT_WRITE, 'stdin')
+ else:
+ process.stdin.close()
+ while True:
+ remaining = deadline - time.monotonic()
+ if remaining <= 0:
+ raise CommandRefused('command_timeout')
+ # WNOWAIT deliberately leaves the leader unreaped so its PID/PGID
+ # cannot be recycled before final process-group cleanup.
+ state = os.waitid(os.P_PID, process.pid,
+ os.WEXITED | os.WNOHANG | os.WNOWAIT)
+ if state is not None and not selector.get_map():
+ break
+ for key, _ in selector.select(min(remaining, .05)):
+ stream = key.fileobj
+ if key.data == 'stdin':
+ try:
+ offset += os.write(stream.fileno(), body[offset:offset + 4096])
+ except BlockingIOError:
+ continue
+ if offset == len(body):
+ selector.unregister(stream); stream.close()
+ continue
+ try:
+ block = os.read(stream.fileno(), 4096)
+ except BlockingIOError:
+ continue
+ if not block:
+ selector.unregister(stream); stream.close()
+ continue
+ total += len(block)
+ if total > MAX_OUTPUT:
+ raise CommandRefused('command_output_bound')
+ if key.data == 'stdout':
+ output.extend(block)
+ except CommandRefused as error:
+ failure = str(error)
+ except Exception:
+ failure = 'command_failed'
+ finally:
+ selector.close()
+ if process is not None:
+ # Also kills descendants that kept pipes open or closed all pipes.
+ # The leader has not been reaped, avoiding PGID reuse races.
+ try:
+ os.killpg(process.pid, signal.SIGKILL)
+ except ProcessLookupError:
+ pass
+ except Exception:
+ failure = 'command_cleanup_unconfirmed'
+ for stream in (process.stdin, process.stdout, process.stderr):
+ if stream is not None:
+ stream.close()
+ try:
+ code = process.wait(timeout=CLEANUP_TIMEOUT)
+ except Exception:
+ failure = 'command_cleanup_unconfirmed'
+ if failure is not None:
+ raise CommandRefused(failure) from None
+ if code != 0:
+ raise CommandRefused('command_failed') from None
+ return bytes(output)
--- /dev/null
+++ b/scripts/keycape_issuer_delivery.py
@@ -0,0 +1,284 @@
+"""RPF-1.3-T4 source mechanics; production admission deliberately unavailable."""
+from __future__ import annotations
+
+from copy import deepcopy
+from datetime import datetime, timezone
+import hashlib
+import json
+import re
+import time
+
+from keycape_issuer_eso import documents, require_fixed_store, Refused, canonical_external_spec
+from bounded_public_command import command, CommandRefused
+
+POLL_COUNT = 10
+POLL_DELAY = 1
+ATTEMPT_TIMEOUT = 30
+API = '/apis/external-secrets.io/v1'
+
+
+class Uncertain(Refused):
+ def __init__(self, reason, revision=None):
+ super().__init__(reason)
+ self.revision = deepcopy(revision)
+
+
+def production_bindings(*args, **kwargs):
+ raise Refused('delivery_admission_exclusion_session_journal_evidence_revocation_unavailable')
+
+
+def _metadata(obj):
+ if not isinstance(obj, dict) or not isinstance(obj.get('metadata'), dict):
+ raise Refused('public_metadata_invalid')
+ return obj['metadata']
+
+
+def _revision(obj):
+ meta = _metadata(obj)
+ result = {'uid': meta.get('uid'), 'resource_version': meta.get('resourceVersion')}
+ if any(not isinstance(x, str) or not re.fullmatch(r'[A-Za-z0-9._:-]{1,192}', x)
+ for x in result.values()):
+ raise Refused('public_revision_invalid')
+ return result
+
+
+def _generation(obj):
+ value = _metadata(obj).get('generation')
+ if type(value) is not int or not 1 <= value <= 2**63 - 1:
+ raise Refused('public_generation_invalid')
+ return value
+
+
+def _synced_version(obj):
+ """ESO v0.16.1 util.GetResourceVersion + utils.ObjectHash, not metadata RV.
+
+ Go fmt %+v on the actual two-field struct uses sorted string maps, raw
+ keys/values, and map[] for both nil and empty maps. Tests compare this
+ implementation to a standalone Go extraction of that exact upstream code.
+ This MD5 is the controller's revision token, never authentication evidence.
+ """
+ meta = _metadata(obj)
+ maps = []
+ for name in ('annotations', 'labels'):
+ value = meta.get(name)
+ if value is None:
+ value = {}
+ if not isinstance(value, dict) or any(
+ not isinstance(k, str) or not isinstance(v, str) for k, v in value.items()):
+ raise Refused('public_metadata_map_invalid')
+ try:
+ textual = 'map[' + ' '.join(k + ':' + value[k] for k in sorted(value)) + ']'
+ textual.encode('utf-8', errors='strict')
+ except UnicodeError:
+ raise Refused('public_metadata_map_invalid') from None
+ maps.append(name + ':' + textual)
+ hashed = hashlib.md5(('{' + ' '.join(maps) + '}').encode(), usedforsecurity=False).hexdigest()
+ return str(_generation(obj)) + '-' + hashed
+
+
+def _ready(obj):
+ status = obj.get('status')
+ if not isinstance(status, dict):
+ return False
+ conditions = status.get('conditions')
+ if not isinstance(conditions, list) or any(not isinstance(c, dict) for c in conditions):
+ return False
+ ready = [c for c in conditions if c.get('type') == 'Ready']
+ if len(ready) != 1 or any(ready[0].get(k) != v for k, v in {
+ 'status': 'True', 'reason': 'SecretSynced', 'message': 'secret synced'}.items()):
+ return False
+ if status.get('syncedResourceVersion') != _synced_version(obj):
+ return False
+ stamp = status.get('refreshTime')
+ if not isinstance(stamp, str) or not re.fullmatch(
+ r'\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d(?:\.\d{1,9})?(?:Z|[+-]\d\d:\d\d)', stamp):
+ return False
+ try:
+ refreshed = datetime.fromisoformat(stamp.replace('Z', '+00:00'))
+ age = (datetime.now(timezone.utc) - refreshed).total_seconds()
+ return 0 <= age <= 300
+ except (ValueError, OverflowError):
+ return False
+
+
+class _DeliveryMechanics:
+ """No authority boundary: injected calls in tests, unusable ambient transport.
+
+ Store observations detect drift; they cannot exclude an ABA race between
+ observations or bind ESO's store lookup atomically. Production needs real
+ owner exclusion/admission in addition to authenticated caller facilities.
+ """
+ def __init__(self, client_id):
+ self.client_id = client_id
+ self.store, self.external = documents(client_id)
+ self.created = None
+ self.pending = False
+ self.used = False
+ self.deadline = None
+
+ def _call(self, args, payload=None):
+ try:
+ remaining = self.deadline - time.monotonic()
+ if remaining <= 0:
+ raise Refused('attempt_deadline')
+ raw = command(['kubectl', '--kubeconfig=/dev/null',
+ '--request-timeout=5s', *args],
+ payload=payload, timeout=min(6, remaining))
+ if time.monotonic() >= self.deadline:
+ raise Refused('attempt_deadline')
+ # Runner enforces the same bound during collection, not just here.
+ if not isinstance(raw, bytes) or len(raw) > 262144:
+ raise Refused('public_response_bound')
+ if not raw.strip():
+ return None
+ value = json.loads(raw)
+ if not isinstance(value, dict):
+ raise Refused('public_response_shape')
+ return value
+ except Refused:
+ raise
+ except Exception:
+ raise Refused('public_command_failed') from None
+
+ def _get(self, store=False):
+ if store:
+ return self._call(['get', '--raw', API + '/clustersecretstores/' + self.store['metadata']['name']])
+ # Raw GET fixes API version and avoids kubectl mapper fallback. A fixed
+ # field-selected collection provides structured successful absence;
+ # stderr/nonzero is never interpreted as NotFound. No Secret is listed.
+ uri = (API + '/namespaces/sso/externalsecrets?fieldSelector=metadata.name%3D'
+ + self.external['metadata']['name'] + '&limit=2')
+ listing = self._call(['get', '--raw', uri])
+ if (not isinstance(listing, dict) or listing.get('apiVersion') != 'external-secrets.io/v1'
+ or listing.get('kind') != 'ExternalSecretList'
+ or not isinstance(listing.get('metadata'), dict)
+ or listing['metadata'].get('continue') not in (None, '')
+ or not isinstance(listing.get('items'), list) or len(listing['items']) > 1):
+ raise Refused('public_collection_invalid')
+ if not listing['items']:
+ return None
+ value = listing['items'][0]
+ meta = _metadata(value)
+ if (value.get('apiVersion') != 'external-secrets.io/v1'
+ or value.get('kind') != 'ExternalSecret'
+ or meta.get('name') != self.external['metadata']['name']
+ or meta.get('namespace') != 'sso'):
+ raise Refused('public_collection_identity_invalid')
+ return value
+
+ def _store_revision(self, sealed=None):
+ store = self._get(store=True)
+ require_fixed_store(store, self.client_id)
+ if _metadata(store).get('deletionTimestamp'):
+ raise Refused('store_terminating')
+ revision = _revision(store)
+ # Actual Store status has no generation binding. Generation 1 excludes
+ # a prior spec update under Kubernetes generation semantics. For any
+ # later generation Ready cannot establish current-spec validation.
+ if _generation(store) != 1:
+ raise Refused('store_ready_generation_unverifiable')
+ conditions = store.get('status', {}).get('conditions', [])
+ ready = [c for c in conditions if isinstance(c, dict) and c.get('type') == 'Ready']
+ if len(ready) != 1 or ready[0].get('reason') != 'Valid' or ready[0].get('message') != 'store validated':
+ raise Refused('store_validation_unverified')
+ if sealed is not None and sealed != revision:
+ raise Refused('store_revision_changed')
+ return revision
+
+ def _match(self, obj):
+ metadata = _metadata(obj)
+ if (obj.get('apiVersion') != self.external['apiVersion']
+ or obj.get('kind') != 'ExternalSecret'
+ or metadata.get('name') != self.external['metadata']['name']
+ or metadata.get('namespace') != 'sso' or metadata.get('deletionTimestamp')
+ or canonical_external_spec(obj.get('spec')) != canonical_external_spec(self.external['spec'])):
+ raise Refused('delivery_conflict')
+ _generation(obj)
+ return _revision(obj)
+
+ def _receipt(self, operation, before, after):
+ return {'schema': 'netkingdom.deliver-secret.v1', 'operation': operation,
+ 'namespace': 'sso', 'name': self.external['metadata']['name'],
+ 'custody_reference': 'platform/workloads/keycape/' + self.client_id,
+ 'key_names': ['client-secret'], 'values_emitted': False,
+ 'changed': before is None if operation == 'deliver' else True,
+ 'before': before, 'after': after}
+
+ def deliver(self):
+ if self.used or self.pending or self.created is not None:
+ raise Refused('attempt_already_used_or_unreconciled')
+ self.used = True # local one-shot only, never a durable admission journal
+ self.deadline = time.monotonic() + ATTEMPT_TIMEOUT
+ sealed_store = self._store_revision()
+ old = self._get()
+ before = self._match(old) if old is not None else None
+ uid = before['uid'] if before else None
+ self._store_revision(sealed_store)
+ if old is None:
+ self.pending = True
+ try:
+ created = self._call(['-n', 'sso', 'create', '-f', '-', '-o', 'json'], self.external)
+ # Retain response identity even if admission changed its spec.
+ # Pending prevents deletion until exact verified delivery.
+ self.created = _revision(created)
+ self._match(created)
+ uid = self.created['uid']
+ except Exception:
+ raise Uncertain('create_outcome_unverified', self.created) from None
+ try:
+ for index in range(POLL_COUNT):
+ self._store_revision(sealed_store)
+ current = self._get()
+ after = self._match(current)
+ if after['uid'] != uid:
+ raise Refused('delivery_replaced')
+ if _ready(current):
+ self._store_revision(sealed_store)
+ if self.created is not None:
+ self.created = after
+ self.pending = False
+ return self._receipt('deliver', before, after)
+ if index + 1 < POLL_COUNT:
+ remaining = self.deadline - time.monotonic()
+ if remaining <= 0:
+ raise Refused('attempt_deadline')
+ time.sleep(min(POLL_DELAY, remaining))
+ raise Refused('delivery_current_ready_timeout')
+ except Exception:
+ if self.pending or self.created is not None:
+ raise Uncertain('created_delivery_requires_reconciliation', self.created) from None
+ raise Refused('delivery_verification_failed') from None
+
+ def remove(self, revision):
+ if self.pending or self.created is None or revision != self.created:
+ raise Refused('no_verified_case_creation')
+ self.deadline = time.monotonic() + ATTEMPT_TIMEOUT
+ try:
+ if self._match(self._get()) != self.created:
+ raise Refused('created_revision_changed')
+ except Exception:
+ raise Uncertain('created_revision_changed', self.created) from None
+ uri = API + '/namespaces/sso/externalsecrets/' + self.external['metadata']['name']
+ options = {'apiVersion': 'v1', 'kind': 'DeleteOptions',
+ 'preconditions': {'uid': self.created['uid'],
+ 'resourceVersion': self.created['resource_version']}}
+ prior = deepcopy(self.created)
+ self.pending = True
+ try:
+ self._call(['delete', '--raw', uri, '-f', '-'], options)
+ for index in range(POLL_COUNT):
+ current = self._get()
+ if current is None:
+ self.created = None
+ self.pending = False
+ return self._receipt('remove', prior, None)
+ if _revision(current)['uid'] != prior['uid']:
+ raise Refused('replacement_preserved')
+ if index + 1 < POLL_COUNT:
+ remaining = self.deadline - time.monotonic()
+ if remaining <= 0:
+ raise Refused('attempt_deadline')
+ time.sleep(min(POLL_DELAY, remaining))
+ raise Refused('delete_unconfirmed')
+ except Exception:
+ raise Uncertain('cleanup_requires_reconciliation', prior) from None
--- a/scripts/keycape_issuer_eso.py
+++ b/scripts/keycape_issuer_eso.py
@@ -37,6 +37,23 @@
return deepcopy(documents(client_id)[1])
+def canonical_external_spec(spec):
+ """Normalize only the three pinned ESO v0.16.1 remoteRef defaults.
+
+ Omitted and explicit defaults are equivalent; all other fields remain exact.
+ This says nothing about freshness, admission authority or Store continuity.
+ """
+ value = deepcopy(spec)
+ if isinstance(value, dict) and isinstance(value.get('data'), list):
+ for item in value['data']:
+ if isinstance(item, dict) and isinstance(item.get('remoteRef'), dict):
+ for name, default in (('conversionStrategy', 'Default'),
+ ('decodingStrategy', 'None'),
+ ('metadataPolicy', 'None')):
+ item['remoteRef'].setdefault(name, default)
+ return value
+
+
def _matching_public_resource(observed: dict, expected: dict) -> None:
if not isinstance(observed, dict):
raise Refused('public resource observation required')
@@ -50,7 +67,10 @@
or observed.get('kind') != expected['kind']
or metadata.get('name') != expected['metadata']['name']
or not namespace_matches
- or observed.get('spec') != expected['spec']):
+ or (canonical_external_spec(observed.get('spec'))
+ != canonical_external_spec(expected['spec'])
+ if expected['kind'] == 'ExternalSecret'
+ else observed.get('spec') != expected['spec'])):
raise Refused('fixed issuer resource differs')
status = observed.get('status')
conditions = status.get('conditions', []) if isinstance(status, dict) else []
--- /dev/null
+++ b/tests/eso-hash-vectors.json
@@ -0,0 +1,57 @@
+[
+ {
+ "metadata": {
+ "generation": 1
+ },
+ "expected": "1-2f2c636d06132f67992c15a1daeb0a5f"
+ },
+ {
+ "metadata": {
+ "generation": 2,
+ "annotations": {},
+ "labels": {}
+ },
+ "expected": "2-2f2c636d06132f67992c15a1daeb0a5f"
+ },
+ {
+ "metadata": {
+ "generation": 3,
+ "annotations": {
+ "b": "2",
+ "a": "1"
+ },
+ "labels": {
+ "z": "last",
+ "c": "first"
+ }
+ },
+ "expected": "3-7ad4f86f5aba928cde01913c7b9d962a"
+ },
+ {
+ "metadata": {
+ "generation": 4,
+ "annotations": {
+ "a:b": "c d",
+ "a b": "[]:%+v"
+ },
+ "labels": {
+ "newline\n": "tab\t"
+ }
+ },
+ "expected": "4-48e35b520c38b2944d461645440cc3cd"
+ },
+ {
+ "metadata": {
+ "generation": 9223372036854775807,
+ "annotations": {
+ "é": "好",
+ "🦊": "😀"
+ },
+ "labels": {
+ "": "",
+ "null": "\u0000"
+ }
+ },
+ "expected": "9223372036854775807-da6b2f7c16875cb96b3a00d83378eda3"
+ }
+]
--- /dev/null
+++ b/tests/test_bounded_public_command.py
@@ -0,0 +1,95 @@
+import os
+from pathlib import Path
+import sys
+import tempfile
+import time
+import unittest
+from unittest.mock import patch
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
+import bounded_public_command as runner
+
+# Every launched fixture denies socket creation in its own interpreter as well
+# as the parent. They only perform controlled local stdout/stderr/process work.
+PREFIX = "import socket; socket.socket=lambda *a,**k: (_ for _ in ()).throw(AssertionError('network forbidden')); "
+
+
+class BoundedCommand(unittest.TestCase):
+ def setUp(self):
+ p = patch.dict(os.environ, {}, clear=True); p.start(); self.addCleanup(p.stop)
+ p = patch('socket.socket', side_effect=AssertionError('network forbidden'))
+ p.start(); self.addCleanup(p.stop)
+
+ def call(self, code, timeout=2, payload=None):
+ return runner.command(['/usr/bin/python3', '-c', PREFIX + code], timeout=timeout, payload=payload)
+
+ def test_empty_environment_and_input_roundtrip(self):
+ raw = self.call("import os,sys,json; b=sys.stdin.buffer.read(); print(json.dumps({'keys':sorted(os.environ),'body':json.loads(b)}))", payload={'fixed': 'metadata'})
+ import json
+ response = json.loads(raw)
+ # CPython may inject LC_CTYPE during startup even though exec env={};
+ # neither HOME nor credential variables are inherited.
+ self.assertFalse(set(response['keys']) - {'LC_CTYPE'})
+ self.assertEqual(response['body'], {'fixed': 'metadata'})
+
+ def test_stdout_flood_stopped_with_fixed_error(self):
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_output_bound$'):
+ self.call("import os; [os.write(1,b'PRIVATE'*4096) for _ in range(1000)]")
+
+ def test_stderr_flood_stopped_with_fixed_error(self):
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_output_bound$'):
+ self.call("import os; [os.write(2,b'PRIVATE'*4096) for _ in range(1000)]")
+
+ def test_combined_cap_not_independent_per_stream(self):
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_output_bound$'):
+ self.call("import os; os.write(1,b'x'*140000); os.write(2,b'y'*140000)")
+
+ def test_error_output_never_escapes(self):
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_failed$') as error:
+ self.call("import sys; print('PRIVATE'); print('PRIVATE',file=sys.stderr); sys.exit(7)")
+ self.assertNotIn('PRIVATE', str(error.exception))
+
+ def test_sleep_timeout_is_real_and_bounded(self):
+ start = time.monotonic()
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_timeout$'):
+ self.call('import time; time.sleep(60)', timeout=.15)
+ self.assertLess(time.monotonic() - start, 2)
+
+ def test_descendant_holding_pipes_is_killed(self):
+ with tempfile.TemporaryDirectory(prefix='delivery-runner-') as scratch:
+ pidfile = Path(scratch) / 'descendant'
+ code = ("import os,time; pid=os.fork(); "
+ "(time.sleep(60) if pid==0 else "
+ f"(open({str(pidfile)!r},'w').write(str(pid)),os._exit(0)))")
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_timeout$'):
+ self.call(code, timeout=.2)
+ self.assertTrue(pidfile.exists())
+ pid = pidfile.read_text()
+ status = Path('/proc') / pid / 'stat'
+ for _ in range(20):
+ if not status.exists() or status.read_text().split()[2] == 'Z': break
+ time.sleep(.01)
+ if status.exists(): self.assertEqual(status.read_text().split()[2], 'Z')
+
+ def test_descendant_with_closed_pipes_is_killed_on_success(self):
+ with tempfile.TemporaryDirectory(prefix='delivery-runner-') as scratch:
+ pidfile = Path(scratch) / 'descendant'
+ code = ("import os,time; pid=os.fork(); "
+ "(os.close(0),os.close(1),os.close(2),time.sleep(60)) if pid==0 else "
+ f"(open({str(pidfile)!r},'w').write(str(pid)),print('ok'))")
+ self.assertEqual(self.call(code), b'ok\n')
+ status = Path('/proc') / pidfile.read_text() / 'stat'
+ # Kill delivery may race the proc-state observation; wait boundedly.
+ for _ in range(20):
+ if not status.exists() or status.read_text().split()[2] == 'Z': break
+ time.sleep(.01)
+ if status.exists(): self.assertEqual(status.read_text().split()[2], 'Z')
+
+ def test_input_bound_and_spawn_failure_fixed(self):
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_input_bound$'):
+ self.call('pass', payload={'data': 'x'*65536})
+ with self.assertRaisesRegex(runner.CommandRefused, '^command_failed$'):
+ runner.command(['/nonexistent-fixture-binary'], timeout=.1)
+
+
+if __name__ == '__main__': unittest.main()
--- /dev/null
+++ b/tests/test_keycape_issuer_delivery.py
@@ -0,0 +1,284 @@
+from copy import deepcopy
+from datetime import datetime, timedelta, timezone
+import json
+import os
+from pathlib import Path
+import sys
+import unittest
+from unittest.mock import patch
+
+sys.path.insert(0, str(Path(__file__).resolve().parents[1] / 'scripts'))
+import keycape_issuer_delivery as lane
+from bounded_public_command import CommandRefused
+
+
+class Mechanics(unittest.TestCase):
+ def setUp(self):
+ p = patch.dict(os.environ, {}, clear=True); p.start(); self.addCleanup(p.stop)
+ p = patch('socket.socket', side_effect=AssertionError('network forbidden'))
+ p.start(); self.addCleanup(p.stop)
+ self.m = lane._DeliveryMechanics('forgejo')
+ self.store = self.resource(self.m.store, store=True)
+ self.obj = None
+ self.calls = []
+ self.store_reads = 0
+ self.hook = lambda args: None
+ self.fail_create = False
+ self.fail_delete = False
+ self.create_mutate = lambda obj: obj
+ self.force_list = None
+ self.poll_object = None
+ self.clock = 0
+ self.clock_step = 0
+ p = patch.object(lane, 'command', side_effect=self.command)
+ p.start(); self.addCleanup(p.stop)
+ p = patch.object(lane.time, 'sleep'); p.start(); self.addCleanup(p.stop)
+ p = patch.object(lane.time, 'monotonic', side_effect=lambda: self.clock)
+ p.start(); self.addCleanup(p.stop)
+
+ def resource(self, doc, store=False):
+ value = deepcopy(doc)
+ value['metadata'].update(uid='store-uid' if store else 'created-uid', resourceVersion='10', generation=1)
+ if store:
+ value['status'] = {'conditions': [{'type': 'Ready', 'status': 'True',
+ 'reason': 'Valid', 'message': 'store validated'}]}
+ else:
+ value['spec'] = lane.canonical_external_spec(value['spec'])
+ value['status'] = {'conditions': [{'type': 'Ready', 'status': 'True',
+ 'reason': 'SecretSynced', 'message': 'secret synced'}],
+ 'syncedResourceVersion': lane._synced_version(value),
+ 'refreshTime': datetime.now(timezone.utc).isoformat()}
+ return value
+
+ def command(self, args, *, payload=None, timeout=6):
+ self.assertEqual(args[:3], ['kubectl', '--kubeconfig=/dev/null', '--request-timeout=5s'])
+ self.assertGreater(timeout, 0); self.assertLessEqual(timeout, 6)
+ self.calls.append((deepcopy(args), deepcopy(payload)))
+ self.clock += self.clock_step
+ self.hook(args)
+ if 'create' in args:
+ if self.fail_create:
+ raise CommandRefused('PRIVATE PROVIDER VALUE')
+ self.obj = self.create_mutate(self.resource(self.m.external))
+ value = self.obj
+ elif 'delete' in args:
+ if self.fail_delete:
+ raise CommandRefused('PRIVATE PROVIDER VALUE')
+ revision = lane._revision(self.obj)
+ if payload['preconditions'] != {'uid': revision['uid'], 'resourceVersion': revision['resource_version']}:
+ raise CommandRefused('conditional conflict fixture')
+ self.obj = None
+ value = {'kind': 'Status', 'status': 'Success'}
+ elif '/clustersecretstores/' in args[-1]:
+ self.store_reads += 1
+ value = self.store
+ else:
+ self.assertIn('get', args); self.assertIn('--raw', args)
+ self.assertEqual(args[-1], lane.API + '/namespaces/sso/externalsecrets?fieldSelector=metadata.name%3Dkeycape-forgejo-client&limit=2')
+ value = self.force_list if self.force_list is not None else {
+ 'apiVersion': 'external-secrets.io/v1', 'kind': 'ExternalSecretList',
+ 'metadata': {}, 'items': [] if self.obj is None else [self.poll_object or self.obj]}
+ return json.dumps(deepcopy(value)).encode()
+
+ def test_actual_defaults_create_current_ready_and_conditional_delete(self):
+ delivered = self.m.deliver()
+ self.assertTrue(delivered['changed']); self.assertIsNone(delivered['before'])
+ self.assertGreaterEqual(self.store_reads, 4)
+ removed = self.m.remove(delivered['after'])
+ self.assertEqual(removed['after'], None)
+ args, payload = next(c for c in self.calls if 'delete' in c[0])
+ self.assertEqual(args[5], lane.API + '/namespaces/sso/externalsecrets/keycape-forgejo-client')
+ self.assertEqual(payload['preconditions'], {'uid': 'created-uid', 'resourceVersion': '10'})
+
+ def test_existing_no_change_cannot_be_deleted_or_reused(self):
+ self.obj = self.resource(self.m.external)
+ delivered = self.m.deliver()
+ self.assertFalse(delivered['changed'])
+ with self.assertRaises(lane.Refused): self.m.remove(delivered['after'])
+ with self.assertRaises(lane.Refused): self.m.deliver()
+ self.assertFalse(any('create' in args or 'delete' in args for args, _ in self.calls))
+
+ def test_stale_current_spec_and_metadata_hash_never_ready(self):
+ obj = self.resource(self.m.external)
+ for field, value in [('generation', 2), ('labels', {'new': 'metadata'}), ('annotations', {'new': 'metadata'})]:
+ with self.subTest(field=field):
+ changed = deepcopy(obj); changed['metadata'][field] = value
+ self.assertFalse(lane._ready(changed))
+
+ def test_exact_go_upstream_revision_vectors(self):
+ vectors = json.loads((Path(__file__).parent / 'eso-hash-vectors.json').read_text())
+ for vector in vectors:
+ self.assertEqual(lane._synced_version({'metadata': vector['metadata']}), vector['expected'])
+
+ def test_wrong_default_strategies_and_identity_never_match(self):
+ for field, value in [('conversionStrategy', 'Unicode'), ('decodingStrategy', 'Base64'),
+ ('metadataPolicy', 'Fetch'), ('key', 'elsewhere'), ('extra', 'value')]:
+ obj = self.resource(self.m.external)
+ obj['spec']['data'][0]['remoteRef'][field] = value
+ with self.assertRaises(lane.Refused): self.m._match(obj)
+ for field, value in [('namespace', 'default'), ('name', 'other'), ('generation', True)]:
+ obj = self.resource(self.m.external); obj['metadata'][field] = value
+ with self.assertRaises(lane.Refused): self.m._match(obj)
+
+ def test_invalid_metadata_maps_never_produce_hash(self):
+ for value in ([], 'str', {'key': None}, {'key': '\ud800'}):
+ obj = self.resource(self.m.external); obj['metadata']['labels'] = value
+ with self.assertRaises(lane.Refused): lane._synced_version(obj)
+
+ def test_client_override_injection_refused_before_command(self):
+ for client in ('x/../../secret', 'x;echo', 'A', None, 'a'*49):
+ with self.assertRaises(lane.Refused): lane._DeliveryMechanics(client)
+ self.assertEqual(self.calls, [])
+
+ def test_retained_missing_stale_future_duplicate_status_not_ready(self):
+ for variant in ('retained', 'missing', 'old', 'future', 'duplicate', 'invalid_time'):
+ with self.subTest(variant=variant):
+ obj = self.resource(self.m.external)
+ if variant == 'retained': obj['status']['conditions'][0]['message'] = 'secret retained due to DeletionPolicy=Retain'
+ if variant == 'missing': obj['status'].pop('syncedResourceVersion')
+ if variant in ('old', 'future'):
+ delta = -301 if variant == 'old' else 60
+ obj['status']['refreshTime'] = (datetime.now(timezone.utc) + timedelta(seconds=delta)).isoformat()
+ if variant == 'duplicate': obj['status']['conditions'] *= 2
+ if variant == 'invalid_time': obj['status']['refreshTime'] = 'PRIVATE MALFORMED'
+ self.assertFalse(lane._ready(obj))
+
+ def test_stale_delivery_times_out_with_reconciliation_provenance(self):
+ self.create_mutate = lambda obj: {**obj, 'status': {'conditions': [{'type': 'Ready', 'status': 'True'}]}}
+ with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
+ self.assertEqual(error.exception.revision['uid'], 'created-uid')
+ with self.assertRaises(lane.Refused): self.m.remove(error.exception.revision)
+ self.assertLessEqual(len(self.calls), 2 * lane.POLL_COUNT + 4)
+
+ def test_store_prior_generation_refused_before_create(self):
+ self.store['metadata']['generation'] = 2
+ with self.assertRaisesRegex(lane.Refused, 'generation_unverifiable'): self.m.deliver()
+ self.assertEqual(len(self.calls), 1)
+
+ def test_store_replaced_before_precreate_read_prevents_create(self):
+ def hook(args):
+ if '/externalsecrets?' in args[-1]: self.store['metadata']['uid'] = 'replacement'
+ self.hook = hook
+ with self.assertRaisesRegex(lane.Refused, 'store_revision_changed'): self.m.deliver()
+ self.assertFalse(any('create' in args for args, _ in self.calls))
+
+ def test_store_replaced_inside_create_window_detected_after_mutation(self):
+ def hook(args):
+ if 'create' in args: self.store['metadata']['uid'] = 'replacement'
+ self.hook = hook
+ with self.assertRaises(lane.Uncertain): self.m.deliver()
+ self.assertIsNotNone(self.obj) # double-read did not exclude mutation
+ self.assertTrue(self.m.pending)
+
+ def test_store_aba_race_is_not_claimed_excluded(self):
+ # A mutation and restoration wholly between observations is invisible.
+ seen = []
+ def hook(args):
+ if 'create' in args:
+ original = deepcopy(self.store)
+ self.store['metadata']['uid'] = 'transient-replacement'
+ seen.append(self.store['metadata']['uid'])
+ self.store = original
+ seen.append(self.store['metadata']['uid'])
+ self.hook = hook
+ self.assertTrue(self.m.deliver()['changed'])
+ self.assertEqual(len(seen), 2)
+ self.assertEqual(seen, ['transient-replacement', 'store-uid'])
+
+ def test_malformed_create_preserves_fixed_unknown_outcome(self):
+ for metadata in (None, [], {}, {'uid': 'created-uid', 'resourceVersion': None}):
+ with self.subTest(metadata=metadata):
+ self.m = lane._DeliveryMechanics('forgejo'); self.obj = None
+ self.create_mutate = lambda obj: {**obj, 'metadata': metadata}
+ with self.assertRaises(lane.Uncertain) as error: self.m.deliver()
+ self.assertEqual(str(error.exception), 'create_outcome_unverified')
+ self.assertTrue(self.m.pending)
+
+ def test_unknown_create_and_delete_never_blindly_retry(self):
+ self.fail_create = True
+ with self.assertRaises(lane.Uncertain): self.m.deliver()
+ with self.assertRaises(lane.Refused): self.m.deliver()
+ self.fail_create = False; self.m = lane._DeliveryMechanics('forgejo')
+ delivered = self.m.deliver(); self.fail_delete = True
+ with self.assertRaises(lane.Uncertain) as error: self.m.remove(delivered['after'])
+ self.assertNotIn('PRIVATE', str(error.exception))
+ with self.assertRaises(lane.Refused): self.m.remove(delivered['after'])
+
+ def test_delete_race_uses_server_preconditions_and_preserves_replacement(self):
+ delivered = self.m.deliver()
+ def hook(args):
+ if 'delete' in args: self.obj['metadata']['uid'] = 'replacement'
+ self.hook = hook
+ with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
+ self.assertEqual(self.obj['metadata']['uid'], 'replacement')
+
+ def test_status_rv_changed_before_remove_no_delete(self):
+ delivered = self.m.deliver(); self.obj['metadata']['resourceVersion'] = '11'
+ with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
+ self.assertFalse(any('delete' in args for args, _ in self.calls))
+
+ def test_fresh_process_and_forged_revision_never_delete(self):
+ delivered = self.m.deliver()
+ with self.assertRaises(lane.Refused): lane._DeliveryMechanics('forgejo').remove(delivered['after'])
+ with self.assertRaises(lane.Refused): self.m.remove({'uid': 'fake', 'resource_version': '10'})
+ self.assertFalse(any('delete' in args for args, _ in self.calls))
+
+ def test_poll_rv_is_captured(self):
+ reads = []
+ def hook(args):
+ if '/externalsecrets?' in args[-1] and self.obj is not None:
+ self.obj['metadata']['resourceVersion'] = '11'
+ reads.append(True)
+ self.hook = hook
+ delivered = self.m.deliver()
+ self.assertEqual(delivered['after']['resource_version'], '11')
+ self.assertTrue(reads)
+
+ def test_unconfirmed_delete_is_bounded_and_preserves_reconciliation(self):
+ delivered = self.m.deliver()
+ prior = deepcopy(self.obj)
+ terminating = deepcopy(prior); terminating['metadata']['deletionTimestamp'] = 'now'
+ with patch.object(self.m, '_get', side_effect=[prior] + [terminating]*lane.POLL_COUNT):
+ with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
+ self.assertTrue(self.m.pending)
+ with self.assertRaises(lane.Refused): self.m.remove(delivered['after'])
+
+ def test_lost_delete_after_server_removal_is_still_uncertain(self):
+ delivered = self.m.deliver(); self.fail_delete = True
+ def hook(args):
+ if 'delete' in args: self.obj = None
+ self.hook = hook
+ with self.assertRaises(lane.Uncertain): self.m.remove(delivered['after'])
+ self.assertIsNone(self.obj)
+ self.assertIsNotNone(self.m.created)
+ self.assertTrue(self.m.pending)
+
+ def test_collection_foreign_object_cannot_be_adopted(self):
+ self.obj = self.resource(self.m.external); self.obj['metadata']['name'] = 'other'
+ with self.assertRaisesRegex(lane.Refused, 'collection_identity'): self.m.deliver()
+ self.assertFalse(any('create' in args for args, _ in self.calls))
+
+ def test_unknown_conflicting_or_unbounded_collection_never_absence(self):
+ for listing in ({}, {'kind': 'Status'}, {'apiVersion': 'external-secrets.io/v1',
+ 'kind': 'ExternalSecretList', 'metadata': {'continue': 'more'}, 'items': []}):
+ with self.subTest(listing=listing):
+ self.m = lane._DeliveryMechanics('forgejo'); self.force_list = listing
+ with self.assertRaises(lane.Refused): self.m.deliver()
+ self.assertFalse(any('create' in args for args, _ in self.calls))
+
+ def test_total_attempt_deadline_blocks_late_success(self):
+ self.clock_step = 6
+ with self.assertRaises(lane.Uncertain): self.m.deliver()
+ self.assertLessEqual(len(self.calls), 6)
+
+ def test_no_secret_or_store_mutation_and_factory_refuses(self):
+ self.m.deliver()
+ for args, payload in self.calls:
+ self.assertFalse(set(args) & {'secret', 'secrets'})
+ if payload and 'create' in args: self.assertEqual(payload['kind'], 'ExternalSecret')
+ before = len(self.calls)
+ with self.assertRaises(lane.Refused): lane.production_bindings(authenticated=True)
+ self.assertEqual(len(self.calls), before)
+
+
+if __name__ == '__main__': unittest.main()

View file

@ -0,0 +1,43 @@
"""Reproduce locally from pinned Git objects. No network or native owner actions."""
from pathlib import Path
import json
import os
import shutil
import subprocess
import tempfile
HERE = Path(__file__).resolve().parent
PLATFORM = Path('/home/worsch/railiance-platform')
BASE = '104ed90f42a9a86bf9fda343b49b65a85c9ee4b9'
ENV = {'PATH': '/usr/bin:/bin', 'PYTHONDONTWRITEBYTECODE': '1'}
def base_file(name):
return subprocess.check_output(['git', '-C', str(PLATFORM), 'show', BASE + ':' + name],
env=ENV, stderr=subprocess.DEVNULL)
def main():
with tempfile.TemporaryDirectory(prefix='platform-delivery-r2-') as scratch:
scratch = Path(scratch)
home = scratch / 'empty-home'; home.mkdir()
env = {**ENV, 'HOME': str(home)}
tree = scratch / 'corrected'
shutil.copytree(HERE / 'prepared-source', tree)
for name in ('manifests/templates/keycape-issuer-eso.yaml', 'tests/test_keycape_issuer_eso.py'):
target = tree / name; target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(base_file(name))
runs = []
commands = [
('corrected-regressions', ['/usr/bin/python3', '-m', 'unittest', 'discover', '-s', str(tree / 'tests'), '-v']),
('corrected-predecessor-assertions', ['/usr/bin/python3', str(HERE / 'assert_corrected_regressions.py'), str(tree)]),
]
for label, argv in commands:
result = subprocess.run(argv, env=env, capture_output=True)
print(label + ': ' + str(result.returncode))
print((result.stdout + result.stderr).decode())
runs.append(result.returncode)
if any(runs): raise SystemExit(1)
if __name__ == '__main__': main()

View file

@ -0,0 +1,37 @@
{
"eso_tag": "v0.16.1",
"eso_commit": "8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af",
"sources": [
{
"url": "https://raw.githubusercontent.com/external-secrets/external-secrets/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/controllers/externalsecret/externalsecret_controller.go",
"file": "upstream/eso-pkg_controllers_externalsecret_externalsecret_controller.go",
"sha256": "e6b18f869361227a19b74fb5d617014e7d8964fbfb6dc47402d128a35cd7bbf1"
},
{
"url": "https://raw.githubusercontent.com/external-secrets/external-secrets/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/controllers/externalsecret/util.go",
"file": "upstream/eso-pkg_controllers_externalsecret_util.go",
"sha256": "350cbef4a8fd56080003a141b3f6bb19af89de7c33999e77159c4f85f11adeef"
},
{
"url": "https://raw.githubusercontent.com/external-secrets/external-secrets/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/controllers/util/util.go",
"file": "upstream/eso-pkg_controllers_util_util.go",
"sha256": "d0337f622f83127cc7798fe493b11f26e9e1c0cb2bde7bfdecc57931edece67e"
},
{
"url": "https://raw.githubusercontent.com/external-secrets/external-secrets/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/controllers/secretstore/common.go",
"file": "upstream/eso-pkg_controllers_secretstore_common.go",
"sha256": "4f2d8ed21d9dd271f839a3d03fb91c1ad1e2ee695ce944770745dd2b52c0117a"
},
{
"url": "https://raw.githubusercontent.com/external-secrets/external-secrets/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/pkg/utils/utils.go",
"file": "upstream/eso-pkg_utils_utils.go",
"sha256": "17c722af9a16d07b1e8088b94783ce63ab6d86207b5b6039b1e4daebe0021764"
},
{
"url": "https://raw.githubusercontent.com/external-secrets/external-secrets/8d037d197c88eeb9c4fe489e906c9cb3ab9ee6af/apis/externalsecrets/v1/secretstore_types.go",
"file": "upstream/eso-apis_externalsecrets_v1_secretstore_types.go",
"sha256": "573f93672c18301c85d978b6184fe0286a8beb6fd3c3620057720ea95196a67d"
}
],
"readiness_limit": "These are source pins, not proof of deployed ESO version or native outcome."
}

View file

@ -0,0 +1,348 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// SecretStoreSpec defines the desired state of SecretStore.
type SecretStoreSpec struct {
// Used to select the correct ESO controller (think: ingress.ingressClassName)
// The ESO controller is instantiated with a specific controller name and filters ES based on this property
// +optional
Controller string `json:"controller,omitempty"`
// Used to configure the provider. Only one provider may be set
Provider *SecretStoreProvider `json:"provider"`
// Used to configure http retries if failed
// +optional
RetrySettings *SecretStoreRetrySettings `json:"retrySettings,omitempty"`
// Used to configure store refresh interval in seconds. Empty or 0 will default to the controller config.
// +optional
RefreshInterval int `json:"refreshInterval,omitempty"`
// Used to constraint a ClusterSecretStore to specific namespaces. Relevant only to ClusterSecretStore
// +optional
Conditions []ClusterSecretStoreCondition `json:"conditions,omitempty"`
}
// ClusterSecretStoreCondition describes a condition by which to choose namespaces to process ExternalSecrets in
// for a ClusterSecretStore instance.
type ClusterSecretStoreCondition struct {
// Choose namespace using a labelSelector
// +optional
NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty"`
// Choose namespaces by name
// +optional
// +kubebuilder:validation:items:MinLength:=1
// +kubebuilder:validation:items:MaxLength:=63
// +kubebuilder:validation:items:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
Namespaces []string `json:"namespaces,omitempty"`
// Choose namespaces by using regex matching
// +optional
NamespaceRegexes []string `json:"namespaceRegexes,omitempty"`
}
// SecretStoreProvider contains the provider-specific configuration.
// +kubebuilder:validation:MinProperties=1
// +kubebuilder:validation:MaxProperties=1
type SecretStoreProvider struct {
// AWS configures this store to sync secrets using AWS Secret Manager provider
// +optional
AWS *AWSProvider `json:"aws,omitempty"`
// AzureKV configures this store to sync secrets using Azure Key Vault provider
// +optional
AzureKV *AzureKVProvider `json:"azurekv,omitempty"`
// Akeyless configures this store to sync secrets using Akeyless Vault provider
// +optional
Akeyless *AkeylessProvider `json:"akeyless,omitempty"`
// BitwardenSecretsManager configures this store to sync secrets using BitwardenSecretsManager provider
// +optional
BitwardenSecretsManager *BitwardenSecretsManagerProvider `json:"bitwardensecretsmanager,omitempty"`
// Vault configures this store to sync secrets using Hashi provider
// +optional
Vault *VaultProvider `json:"vault,omitempty"`
// GCPSM configures this store to sync secrets using Google Cloud Platform Secret Manager provider
// +optional
GCPSM *GCPSMProvider `json:"gcpsm,omitempty"`
// Oracle configures this store to sync secrets using Oracle Vault provider
// +optional
Oracle *OracleProvider `json:"oracle,omitempty"`
// IBM configures this store to sync secrets using IBM Cloud provider
// +optional
IBM *IBMProvider `json:"ibm,omitempty"`
// YandexCertificateManager configures this store to sync secrets using Yandex Certificate Manager provider
// +optional
YandexCertificateManager *YandexCertificateManagerProvider `json:"yandexcertificatemanager,omitempty"`
// YandexLockbox configures this store to sync secrets using Yandex Lockbox provider
// +optional
YandexLockbox *YandexLockboxProvider `json:"yandexlockbox,omitempty"`
// Github configures this store to push Github Action secrets using Github API provider
// +optional
Github *GithubProvider `json:"github,omitempty"`
// GitLab configures this store to sync secrets using GitLab Variables provider
// +optional
Gitlab *GitlabProvider `json:"gitlab,omitempty"`
// Alibaba configures this store to sync secrets using Alibaba Cloud provider
// +optional
Alibaba *AlibabaProvider `json:"alibaba,omitempty"`
// OnePassword configures this store to sync secrets using the 1Password Cloud provider
// +optional
OnePassword *OnePasswordProvider `json:"onepassword,omitempty"`
// Webhook configures this store to sync secrets using a generic templated webhook
// +optional
Webhook *WebhookProvider `json:"webhook,omitempty"`
// Kubernetes configures this store to sync secrets using a Kubernetes cluster provider
// +optional
Kubernetes *KubernetesProvider `json:"kubernetes,omitempty"`
// Fake configures a store with static key/value pairs
// +optional
Fake *FakeProvider `json:"fake,omitempty"`
// Senhasegura configures this store to sync secrets using senhasegura provider
// +optional
Senhasegura *SenhaseguraProvider `json:"senhasegura,omitempty"`
// Scaleway
// +optional
Scaleway *ScalewayProvider `json:"scaleway,omitempty"`
// Doppler configures this store to sync secrets using the Doppler provider
// +optional
Doppler *DopplerProvider `json:"doppler,omitempty"`
// Previder configures this store to sync secrets using the Previder provider
// +optional
Previder *PreviderProvider `json:"previder,omitempty"`
// Onboardbase configures this store to sync secrets using the Onboardbase provider
// +optional
Onboardbase *OnboardbaseProvider `json:"onboardbase,omitempty"`
// KeeperSecurity configures this store to sync secrets using the KeeperSecurity provider
// +optional
KeeperSecurity *KeeperSecurityProvider `json:"keepersecurity,omitempty"`
// Conjur configures this store to sync secrets using conjur provider
// +optional
Conjur *ConjurProvider `json:"conjur,omitempty"`
// Delinea DevOps Secrets Vault
// https://docs.delinea.com/online-help/products/devops-secrets-vault/current
// +optional
Delinea *DelineaProvider `json:"delinea,omitempty"`
// SecretServer configures this store to sync secrets using SecretServer provider
// https://docs.delinea.com/online-help/secret-server/start.htm
// +optional
SecretServer *SecretServerProvider `json:"secretserver,omitempty"`
// Chef configures this store to sync secrets with chef server
// +optional
Chef *ChefProvider `json:"chef,omitempty"`
// Pulumi configures this store to sync secrets using the Pulumi provider
// +optional
Pulumi *PulumiProvider `json:"pulumi,omitempty"`
// Fortanix configures this store to sync secrets using the Fortanix provider
// +optional
Fortanix *FortanixProvider `json:"fortanix,omitempty"`
// +optional
PasswordDepot *PasswordDepotProvider `json:"passworddepot,omitempty"`
// +optional
Passbolt *PassboltProvider `json:"passbolt,omitempty"`
// Device42 configures this store to sync secrets using the Device42 provider
// +optional
Device42 *Device42Provider `json:"device42,omitempty"`
// Infisical configures this store to sync secrets using the Infisical provider
// +optional
Infisical *InfisicalProvider `json:"infisical,omitempty"`
// Beyondtrust configures this store to sync secrets using Password Safe provider.
// +optional
Beyondtrust *BeyondtrustProvider `json:"beyondtrust,omitempty"`
// CloudruSM configures this store to sync secrets using the Cloud.ru Secret Manager provider
// +optional
CloudruSM *CloudruSMProvider `json:"cloudrusm,omitempty"`
}
type CAProviderType string
const (
CAProviderTypeSecret CAProviderType = "Secret"
CAProviderTypeConfigMap CAProviderType = "ConfigMap"
)
// Used to provide custom certificate authority (CA) certificates
// for a secret store. The CAProvider points to a Secret or ConfigMap resource
// that contains a PEM-encoded certificate.
type CAProvider struct {
// The type of provider to use such as "Secret", or "ConfigMap".
// +kubebuilder:validation:Enum="Secret";"ConfigMap"
Type CAProviderType `json:"type"`
// The name of the object located at the provider type.
// +kubebuilder:validation:MinLength:=1
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
Name string `json:"name"`
// The key where the CA certificate can be found in the Secret or ConfigMap.
// +kubebuilder:validation:Optional
// +kubebuilder:validation:MinLength:=1
// +kubebuilder:validation:MaxLength:=253
// +kubebuilder:validation:Pattern:=^[-._a-zA-Z0-9]+$
Key string `json:"key,omitempty"`
// The namespace the Provider type is in.
// Can only be defined when used in a ClusterSecretStore.
// +optional
// +kubebuilder:validation:MinLength:=1
// +kubebuilder:validation:MaxLength:=63
// +kubebuilder:validation:Pattern:=^[a-z0-9]([-a-z0-9]*[a-z0-9])?$
Namespace *string `json:"namespace,omitempty"`
}
type SecretStoreRetrySettings struct {
MaxRetries *int32 `json:"maxRetries,omitempty"`
RetryInterval *string `json:"retryInterval,omitempty"`
}
type SecretStoreConditionType string
const (
SecretStoreReady SecretStoreConditionType = "Ready"
ReasonInvalidStore = "InvalidStoreConfiguration"
ReasonInvalidProviderConfig = "InvalidProviderConfig"
ReasonValidationFailed = "ValidationFailed"
ReasonStoreValid = "Valid"
StoreUnmaintained = "StoreUnmaintained"
)
type SecretStoreStatusCondition struct {
Type SecretStoreConditionType `json:"type"`
Status corev1.ConditionStatus `json:"status"`
// +optional
Reason string `json:"reason,omitempty"`
// +optional
Message string `json:"message,omitempty"`
// +optional
LastTransitionTime metav1.Time `json:"lastTransitionTime,omitempty"`
}
// SecretStoreCapabilities defines the possible operations a SecretStore can do.
type SecretStoreCapabilities string
const (
SecretStoreReadOnly SecretStoreCapabilities = "ReadOnly"
SecretStoreWriteOnly SecretStoreCapabilities = "WriteOnly"
SecretStoreReadWrite SecretStoreCapabilities = "ReadWrite"
)
// SecretStoreStatus defines the observed state of the SecretStore.
type SecretStoreStatus struct {
// +optional
Conditions []SecretStoreStatusCondition `json:"conditions,omitempty"`
// +optional
Capabilities SecretStoreCapabilities `json:"capabilities,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:storageversion
// SecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields.
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
// +kubebuilder:printcolumn:name="Capabilities",type=string,JSONPath=`.status.capabilities`
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:subresource:status
// +kubebuilder:metadata:labels="external-secrets.io/component=controller"
// +kubebuilder:resource:scope=Namespaced,categories={external-secrets},shortName=ss
type SecretStore struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SecretStoreSpec `json:"spec,omitempty"`
Status SecretStoreStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// SecretStoreList contains a list of SecretStore resources.
type SecretStoreList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []SecretStore `json:"items"`
}
// +kubebuilder:object:root=true
// +kubebuilder:storageversion
// ClusterSecretStore represents a secure external location for storing secrets, which can be referenced as part of `storeRef` fields.
// +kubebuilder:printcolumn:name="AGE",type="date",JSONPath=".metadata.creationTimestamp"
// +kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].reason`
// +kubebuilder:printcolumn:name="Capabilities",type=string,JSONPath=`.status.capabilities`
// +kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=="Ready")].status`
// +kubebuilder:subresource:status
// +kubebuilder:metadata:labels="external-secrets.io/component=controller"
// +kubebuilder:resource:scope=Cluster,categories={external-secrets},shortName=css
type ClusterSecretStore struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec SecretStoreSpec `json:"spec,omitempty"`
Status SecretStoreStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// ClusterSecretStoreList contains a list of ClusterSecretStore resources.
type ClusterSecretStoreList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []ClusterSecretStore `json:"items"`
}

View file

@ -0,0 +1,985 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package externalsecret
import (
"context"
"encoding/json"
"errors"
"fmt"
"maps"
"slices"
"strings"
"time"
"github.com/go-logr/logr"
"github.com/prometheus/client_golang/prometheus"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/equality"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/record"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
// Metrics.
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
ctrlmetrics "github.com/external-secrets/external-secrets/pkg/controllers/metrics"
"github.com/external-secrets/external-secrets/pkg/controllers/util"
"github.com/external-secrets/external-secrets/pkg/utils"
"github.com/external-secrets/external-secrets/pkg/utils/resolvers"
// Loading registered generators.
_ "github.com/external-secrets/external-secrets/pkg/generator/register"
// Loading registered providers.
_ "github.com/external-secrets/external-secrets/pkg/provider/register"
)
const (
fieldOwnerTemplate = "externalsecrets.external-secrets.io/%v"
// condition messages for "SecretSynced" reason.
msgSynced = "secret synced"
msgSyncedRetain = "secret retained due to DeletionPolicy=Retain"
// condition messages for "SecretDeleted" reason.
msgDeleted = "secret deleted due to DeletionPolicy=Delete"
// condition messages for "SecretMissing" reason.
msgMissing = "secret will not be created due to CreationPolicy=Merge"
// condition messages for "SecretSyncedError" reason.
msgErrorGetSecretData = "could not get secret data from provider"
msgErrorDeleteSecret = "could not delete secret"
msgErrorDeleteOrphaned = "could not delete orphaned secrets"
msgErrorUpdateSecret = "could not update secret"
msgErrorUpdateImmutable = "could not update secret, target is immutable"
msgErrorBecomeOwner = "failed to take ownership of target secret"
msgErrorIsOwned = "target is owned by another ExternalSecret"
// log messages.
logErrorGetES = "unable to get ExternalSecret"
logErrorUpdateESStatus = "unable to update ExternalSecret status"
logErrorGetSecret = "unable to get Secret"
logErrorPatchSecret = "unable to patch Secret"
logErrorSecretCacheNotSynced = "controller caches for Secret are not in sync"
logErrorUnmanagedStore = "unable to determine if store is managed"
// error formats.
errConvert = "error applying conversion strategy %s to keys: %w"
errRewrite = "error applying rewrite to keys: %w"
errDecode = "error applying decoding strategy %s to data: %w"
errGenerate = "error using generator: %w"
errInvalidKeys = "invalid secret keys (TIP: use rewrite or conversionStrategy to change keys): %w"
errFetchTplFrom = "error fetching templateFrom data: %w"
errApplyTemplate = "could not apply template: %w"
errExecTpl = "could not execute template: %w"
errMutate = "unable to mutate secret %s: %w"
errUpdate = "unable to update secret %s: %w"
errUpdateNotFound = "unable to update secret %s: not found"
errDeleteCreatePolicy = "unable to delete secret %s: creationPolicy=%s is not Owner"
errSecretCachesNotSynced = "controller caches for secret %s are not in sync"
// event messages.
eventCreated = "secret created"
eventUpdated = "secret updated"
eventDeleted = "secret deleted due to DeletionPolicy=Delete"
eventDeletedOrphaned = "secret deleted because it was orphaned"
eventMissingProviderSecret = "secret does not exist at provider using spec.dataFrom[%d]"
eventMissingProviderSecretKey = "secret does not exist at provider using spec.dataFrom[%d] (key=%s)"
)
// these errors are explicitly defined so we can detect them with `errors.Is()`.
var (
ErrSecretImmutable = fmt.Errorf("secret is immutable")
ErrSecretIsOwned = fmt.Errorf("secret is owned by another ExternalSecret")
ErrSecretSetCtrlRef = fmt.Errorf("could not set controller reference on secret")
ErrSecretRemoveCtrlRef = fmt.Errorf("could not remove controller reference on secret")
)
const indexESTargetSecretNameField = ".metadata.targetSecretName"
// Reconciler reconciles a ExternalSecret object.
type Reconciler struct {
client.Client
SecretClient client.Client
Log logr.Logger
Scheme *runtime.Scheme
RestConfig *rest.Config
ControllerClass string
RequeueInterval time.Duration
ClusterSecretStoreEnabled bool
EnableFloodGate bool
recorder record.EventRecorder
}
// Reconcile implements the main reconciliation loop
// for watched objects (ExternalSecret, ClusterSecretStore and SecretStore),
// and updates/creates a Kubernetes secret based on them.
func (r *Reconciler) Reconcile(ctx context.Context, req ctrl.Request) (result ctrl.Result, err error) {
log := r.Log.WithValues("ExternalSecret", req.NamespacedName)
resourceLabels := ctrlmetrics.RefineNonConditionMetricLabels(map[string]string{"name": req.Name, "namespace": req.Namespace})
start := time.Now()
syncCallsError := esmetrics.GetCounterVec(esmetrics.SyncCallsErrorKey)
// use closures to dynamically update resourceLabels
defer func() {
esmetrics.GetGaugeVec(esmetrics.ExternalSecretReconcileDurationKey).With(resourceLabels).Set(float64(time.Since(start)))
esmetrics.GetCounterVec(esmetrics.SyncCallsKey).With(resourceLabels).Inc()
}()
externalSecret := &esv1.ExternalSecret{}
err = r.Get(ctx, req.NamespacedName, externalSecret)
if err != nil {
if apierrors.IsNotFound(err) {
// NOTE: this does not actually set the condition on the ExternalSecret, because it does not exist
// this is a hack to disable metrics for deleted ExternalSecrets, see:
// https://github.com/external-secrets/external-secrets/pull/612
conditionSynced := NewExternalSecretCondition(esv1.ExternalSecretDeleted, v1.ConditionFalse, esv1.ConditionReasonSecretDeleted, "Secret was deleted")
SetExternalSecretCondition(&esv1.ExternalSecret{
ObjectMeta: metav1.ObjectMeta{
Name: req.Name,
Namespace: req.Namespace,
},
}, *conditionSynced)
return ctrl.Result{}, nil
}
log.Error(err, logErrorGetES)
syncCallsError.With(resourceLabels).Inc()
return ctrl.Result{}, err
}
// skip reconciliation if deletion timestamp is set on external secret
if !externalSecret.GetDeletionTimestamp().IsZero() {
log.V(1).Info("skipping ExternalSecret, it is marked for deletion")
return ctrl.Result{}, nil
}
// if extended metrics is enabled, refine the time series vector
resourceLabels = ctrlmetrics.RefineLabels(resourceLabels, externalSecret.Labels)
// skip this ExternalSecret if it uses a ClusterSecretStore and the feature is disabled
if shouldSkipClusterSecretStore(r, externalSecret) {
log.V(1).Info("skipping ExternalSecret, ClusterSecretStore feature is disabled")
return ctrl.Result{}, nil
}
// skip this ExternalSecret if it uses any SecretStore not managed by this controller
skip, err := shouldSkipUnmanagedStore(ctx, req.Namespace, r, externalSecret)
if err != nil {
log.Error(err, logErrorUnmanagedStore)
syncCallsError.With(resourceLabels).Inc()
return ctrl.Result{}, err
}
if skip {
log.V(1).Info("skipping ExternalSecret, uses unmanaged SecretStore")
return ctrl.Result{}, nil
}
// the target secret name defaults to the ExternalSecret name, if not explicitly set
secretName := externalSecret.Spec.Target.Name
if secretName == "" {
secretName = externalSecret.Name
}
// fetch the existing secret (from the partial cache)
// - please note that the ~partial cache~ is different from the ~full cache~
// so there can be race conditions between the two caches
// - the WatchesMetadata(v1.Secret{}) in SetupWithManager() is using the partial cache
// so we might receive a reconcile request before the full cache is updated
// - furthermore, when `--enable-managed-secrets-caching` is true, the full cache
// will ONLY include secrets with the "managed" label, so we cant use the full cache
// to reliably determine if a secret exists or not
secretPartial := &metav1.PartialObjectMetadata{}
secretPartial.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Secret"))
err = r.Get(ctx, client.ObjectKey{Name: secretName, Namespace: externalSecret.Namespace}, secretPartial)
if err != nil && !apierrors.IsNotFound(err) {
log.Error(err, logErrorGetSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
syncCallsError.With(resourceLabels).Inc()
return ctrl.Result{}, err
}
// if the secret exists but does not have the "managed" label, add the label
// using a PATCH so it is visible in the cache, then requeue immediately
if secretPartial.UID != "" && secretPartial.Labels[esv1.LabelManaged] != esv1.LabelManagedValue {
fqdn := fmt.Sprintf(fieldOwnerTemplate, externalSecret.Name)
patch := client.MergeFrom(secretPartial.DeepCopy())
if secretPartial.Labels == nil {
secretPartial.Labels = make(map[string]string)
}
secretPartial.Labels[esv1.LabelManaged] = esv1.LabelManagedValue
err = r.Patch(ctx, secretPartial, patch, client.FieldOwner(fqdn))
if err != nil {
log.Error(err, logErrorPatchSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
syncCallsError.With(resourceLabels).Inc()
return ctrl.Result{}, err
}
return ctrl.Result{Requeue: true}, nil
}
// fetch existing secret (from the full cache)
// NOTE: we are using the `r.SecretClient` which we only use for managed secrets.
// when `enableManagedSecretsCache` is true, this is a cached client that only sees our managed secrets,
// otherwise it will be the normal controller-runtime client which may be cached or make direct API calls,
// depending on if `enabledSecretCache` is true or false.
existingSecret := &v1.Secret{}
err = r.SecretClient.Get(ctx, client.ObjectKey{Name: secretName, Namespace: externalSecret.Namespace}, existingSecret)
if err != nil && !apierrors.IsNotFound(err) {
log.Error(err, logErrorGetSecret, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
syncCallsError.With(resourceLabels).Inc()
return ctrl.Result{}, err
}
// ensure the full cache is up-to-date
// NOTE: this prevents race conditions between the partial and full cache.
// we return an error so we get an exponential backoff if we end up looping,
// for example, during high cluster load and frequent updates to the target secret by other controllers.
if secretPartial.UID != existingSecret.UID || secretPartial.ResourceVersion != existingSecret.ResourceVersion {
err = fmt.Errorf(errSecretCachesNotSynced, secretName)
log.Error(err, logErrorSecretCacheNotSynced, "secretName", secretName, "secretNamespace", externalSecret.Namespace)
syncCallsError.With(resourceLabels).Inc()
return ctrl.Result{}, err
}
// refresh will be skipped if ALL the following conditions are met:
// 1. refresh interval is not 0
// 2. resource generation of the ExternalSecret has not changed
// 3. the last refresh time of the ExternalSecret is within the refresh interval
// 4. the target secret is valid:
// - it exists
// - it has the correct "managed" label
// - it has the correct "data-hash" annotation
if !shouldRefresh(externalSecret) && isSecretValid(existingSecret) {
log.V(1).Info("skipping refresh")
return r.getRequeueResult(externalSecret), nil
}
// update status of the ExternalSecret when this function returns, if needed.
// NOTE: we use the ability of deferred functions to update named return values `result` and `err`
// NOTE: we dereference the DeepCopy of the status field because status fields are NOT pointers,
// so otherwise the `equality.Semantic.DeepEqual` will always return false.
currentStatus := *externalSecret.Status.DeepCopy()
defer func() {
// if the status has not changed, we don't need to update it
if equality.Semantic.DeepEqual(currentStatus, externalSecret.Status) {
return
}
// update the status of the ExternalSecret, storing any error in a new variable
// if there was no new error, we don't need to change the `result` or `err` values
updateErr := r.Status().Update(ctx, externalSecret)
if updateErr == nil {
return
}
// if we got an update conflict, we should requeue immediately
if apierrors.IsConflict(updateErr) {
log.V(1).Info("conflict while updating status, will requeue")
// we only explicitly request a requeue if the main function did not return an `err`.
// otherwise, we get an annoying log saying that results are ignored when there is an error,
// as errors are always retried.
if err == nil {
result = ctrl.Result{Requeue: true}
}
return
}
// for other errors, log and update the `err` variable if there is no error already
// so the reconciler will requeue the request
log.Error(updateErr, logErrorUpdateESStatus)
if err == nil {
err = updateErr
}
}()
// retrieve the provider secret data.
dataMap, err := r.getProviderSecretData(ctx, externalSecret)
if err != nil {
r.markAsFailed(msgErrorGetSecretData, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, err
}
// if no data was found we can delete the secret if needed.
if len(dataMap) == 0 {
switch externalSecret.Spec.Target.DeletionPolicy {
// delete secret and return early.
case esv1.DeletionPolicyDelete:
// safeguard that we only can delete secrets we own.
// this is also implemented in the es validation webhook.
// NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
creationPolicy := externalSecret.Spec.Target.CreationPolicy
if creationPolicy != esv1.CreatePolicyOwner {
err = fmt.Errorf(errDeleteCreatePolicy, secretName, creationPolicy)
r.markAsFailed(msgErrorDeleteSecret, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, nil
}
// delete the secret, if it exists
if existingSecret.UID != "" {
err = r.Delete(ctx, existingSecret)
if err != nil && !apierrors.IsNotFound(err) {
r.markAsFailed(msgErrorDeleteSecret, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, err
}
r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1.ReasonDeleted, eventDeleted)
}
r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretDeleted, msgDeleted)
return r.getRequeueResult(externalSecret), nil
// In case provider secrets don't exist the kubernetes secret will be kept as-is.
case esv1.DeletionPolicyRetain:
r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretSynced, msgSyncedRetain)
return r.getRequeueResult(externalSecret), nil
// noop, handled below
case esv1.DeletionPolicyMerge:
}
}
// mutationFunc is a function which can be applied to a secret to make it match the desired state.
mutationFunc := func(secret *v1.Secret) error {
// get information about the current owner of the secret
// - we ignore the API version as it can change over time
// - we ignore the UID for consistency with the SetControllerReference function
currentOwner := metav1.GetControllerOf(secret)
ownerIsESKind := false
ownerIsCurrentES := false
if currentOwner != nil {
currentOwnerGK := schema.FromAPIVersionAndKind(currentOwner.APIVersion, currentOwner.Kind).GroupKind()
ownerIsESKind = currentOwnerGK.String() == esv1.ExtSecretGroupKind
ownerIsCurrentES = ownerIsESKind && currentOwner.Name == externalSecret.Name
}
// if another ExternalSecret is the owner, we should return an error
// otherwise the controller will fight with itself to update the secret.
// note, this does not prevent other controllers from owning the secret.
if ownerIsESKind && !ownerIsCurrentES {
return fmt.Errorf("%w: %s", ErrSecretIsOwned, currentOwner.Name)
}
// if the CreationPolicy is Owner, we should set ourselves as the owner of the secret
if externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyOwner {
err = controllerutil.SetControllerReference(externalSecret, secret, r.Scheme)
if err != nil {
return fmt.Errorf("%w: %w", ErrSecretSetCtrlRef, err)
}
}
// if the creation policy is not Owner, we should remove ourselves as the owner
// this could happen if the creation policy was changed after the secret was created
if externalSecret.Spec.Target.CreationPolicy != esv1.CreatePolicyOwner && ownerIsCurrentES {
err = controllerutil.RemoveControllerReference(externalSecret, secret, r.Scheme)
if err != nil {
return fmt.Errorf("%w: %w", ErrSecretRemoveCtrlRef, err)
}
}
// initialize maps within the secret so it's safe to set values
if secret.Annotations == nil {
secret.Annotations = make(map[string]string)
}
if secret.Labels == nil {
secret.Labels = make(map[string]string)
}
if secret.Data == nil {
secret.Data = make(map[string][]byte)
}
// get the list of keys that are managed by this ExternalSecret
keys, err := getManagedDataKeys(secret, externalSecret.Name)
if err != nil {
return err
}
// remove any data keys that are managed by this ExternalSecret, so we can re-add them
// this ensures keys added by templates are not left behind when they are removed from the template
for _, key := range keys {
delete(secret.Data, key)
}
// WARNING: this will remove any labels or annotations managed by this ExternalSecret
// so any updates to labels and annotations should be done AFTER this point
err = r.applyTemplate(ctx, externalSecret, secret, dataMap)
if err != nil {
return fmt.Errorf(errApplyTemplate, err)
}
// set the immutable flag on the secret if requested by the ExternalSecret
if externalSecret.Spec.Target.Immutable {
secret.Immutable = ptr.To(true)
}
// we also use a label to keep track of the owner of the secret
// this lets us remove secrets that are no longer needed if the target secret name changes
if externalSecret.Spec.Target.CreationPolicy == esv1.CreatePolicyOwner {
lblValue := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
secret.Labels[esv1.LabelOwner] = lblValue
} else {
// the label should not be set if the creation policy is not Owner
delete(secret.Labels, esv1.LabelOwner)
}
secret.Labels[esv1.LabelManaged] = esv1.LabelManagedValue
secret.Annotations[esv1.AnnotationDataHash] = utils.ObjectHash(secret.Data)
return nil
}
switch externalSecret.Spec.Target.CreationPolicy {
case esv1.CreatePolicyNone:
log.V(1).Info("secret creation skipped due to CreationPolicy=None")
err = nil
case esv1.CreatePolicyMerge:
// update the secret, if it exists
if existingSecret.UID != "" {
err = r.updateSecret(ctx, existingSecret, mutationFunc, externalSecret, secretName)
} else {
// if the secret does not exist, we wait until the next refresh interval
// rather than returning an error which would requeue immediately
r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretMissing, msgMissing)
return r.getRequeueResult(externalSecret), nil
}
case esv1.CreatePolicyOrphan:
// create the secret, if it does not exist
if existingSecret.UID == "" {
err = r.createSecret(ctx, mutationFunc, externalSecret, secretName)
} else {
// if the secret exists, we should update it
err = r.updateSecret(ctx, existingSecret, mutationFunc, externalSecret, secretName)
}
case esv1.CreatePolicyOwner:
// we may have orphaned secrets to clean up,
// for example, if the target secret name was changed
err = r.deleteOrphanedSecrets(ctx, externalSecret, secretName)
if err != nil {
r.markAsFailed(msgErrorDeleteOrphaned, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, err
}
// create the secret, if it does not exist
if existingSecret.UID == "" {
err = r.createSecret(ctx, mutationFunc, externalSecret, secretName)
} else {
// if the secret exists, we should update it
err = r.updateSecret(ctx, existingSecret, mutationFunc, externalSecret, secretName)
}
}
if err != nil {
// if we got an update conflict, we should requeue immediately
if apierrors.IsConflict(err) {
log.V(1).Info("conflict while updating secret, will requeue")
return ctrl.Result{Requeue: true}, nil
}
// detect errors indicating that we failed to set ourselves as the owner of the secret
// NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
if errors.Is(err, ErrSecretSetCtrlRef) {
r.markAsFailed(msgErrorBecomeOwner, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, nil
}
// detect errors indicating that the secret has another ExternalSecret as owner
// NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
if errors.Is(err, ErrSecretIsOwned) {
r.markAsFailed(msgErrorIsOwned, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, nil
}
// detect errors indicating that the secret is immutable
// NOTE: this error cant be fixed by retrying so we don't return an error (which would requeue immediately)
if errors.Is(err, ErrSecretImmutable) {
r.markAsFailed(msgErrorUpdateImmutable, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, nil
}
r.markAsFailed(msgErrorUpdateSecret, err, externalSecret, syncCallsError.With(resourceLabels))
return ctrl.Result{}, err
}
r.markAsDone(externalSecret, start, log, esv1.ConditionReasonSecretSynced, msgSynced)
return r.getRequeueResult(externalSecret), nil
}
// getRequeueResult create a result with requeueAfter based on the ExternalSecret refresh interval.
func (r *Reconciler) getRequeueResult(externalSecret *esv1.ExternalSecret) ctrl.Result {
// default to the global requeue interval
// note, this will never be used because the CRD has a default value of 1 hour
refreshInterval := r.RequeueInterval
if externalSecret.Spec.RefreshInterval != nil {
refreshInterval = externalSecret.Spec.RefreshInterval.Duration
}
// if the refresh interval is <= 0, we should not requeue
if refreshInterval <= 0 {
return ctrl.Result{}
}
// if the last refresh time is not set, requeue after the refresh interval
// note, this should not happen, as we only call this function on ExternalSecrets
// that have been reconciled at least once
if externalSecret.Status.RefreshTime.IsZero() {
return ctrl.Result{RequeueAfter: refreshInterval}
}
timeSinceLastRefresh := time.Since(externalSecret.Status.RefreshTime.Time)
// if the last refresh time is in the future, we should requeue immediately
// note, this should not happen, as we always refresh an ExternalSecret
// that has a last refresh time in the future
if timeSinceLastRefresh < 0 {
return ctrl.Result{Requeue: true}
}
// if there is time remaining, requeue after the remaining time
if timeSinceLastRefresh < refreshInterval {
return ctrl.Result{RequeueAfter: refreshInterval - timeSinceLastRefresh}
}
// otherwise, requeue immediately
return ctrl.Result{Requeue: true}
}
func (r *Reconciler) markAsDone(externalSecret *esv1.ExternalSecret, start time.Time, log logr.Logger, reason, msg string) {
oldReadyCondition := GetExternalSecretCondition(externalSecret.Status, esv1.ExternalSecretReady)
newReadyCondition := NewExternalSecretCondition(esv1.ExternalSecretReady, v1.ConditionTrue, reason, msg)
SetExternalSecretCondition(externalSecret, *newReadyCondition)
externalSecret.Status.RefreshTime = metav1.NewTime(start)
externalSecret.Status.SyncedResourceVersion = util.GetResourceVersion(externalSecret.ObjectMeta)
// if the status or reason has changed, log at the appropriate verbosity level
if oldReadyCondition == nil || oldReadyCondition.Status != newReadyCondition.Status || oldReadyCondition.Reason != newReadyCondition.Reason {
if newReadyCondition.Reason == esv1.ConditionReasonSecretDeleted {
log.Info("deleted secret")
} else {
log.Info("reconciled secret")
}
} else {
log.V(1).Info("reconciled secret")
}
}
func (r *Reconciler) markAsFailed(msg string, err error, externalSecret *esv1.ExternalSecret, counter prometheus.Counter) {
r.recorder.Event(externalSecret, v1.EventTypeWarning, esv1.ReasonUpdateFailed, err.Error())
conditionSynced := NewExternalSecretCondition(esv1.ExternalSecretReady, v1.ConditionFalse, esv1.ConditionReasonSecretSyncedError, msg)
SetExternalSecretCondition(externalSecret, *conditionSynced)
counter.Inc()
}
func (r *Reconciler) deleteOrphanedSecrets(ctx context.Context, externalSecret *esv1.ExternalSecret, secretName string) error {
ownerLabel := utils.ObjectHash(fmt.Sprintf("%v/%v", externalSecret.Namespace, externalSecret.Name))
// we use a PartialObjectMetadataList to avoid loading the full secret objects
// and because the Secrets partials are always cached due to WatchesMetadata() in SetupWithManager()
secretListPartial := &metav1.PartialObjectMetadataList{}
secretListPartial.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("SecretList"))
listOpts := &client.ListOptions{
LabelSelector: labels.SelectorFromSet(map[string]string{
esv1.LabelOwner: ownerLabel,
}),
Namespace: externalSecret.Namespace,
}
if err := r.List(ctx, secretListPartial, listOpts); err != nil {
return err
}
// delete all secrets that are not the target secret
for _, secretPartial := range secretListPartial.Items {
if secretPartial.GetName() != secretName {
err := r.Delete(ctx, &secretPartial)
if err != nil && !apierrors.IsNotFound(err) {
return err
}
r.recorder.Event(externalSecret, v1.EventTypeNormal, esv1.ReasonDeleted, eventDeletedOrphaned)
}
}
return nil
}
// createSecret creates a new secret with the given mutation function.
func (r *Reconciler) createSecret(ctx context.Context, mutationFunc func(secret *v1.Secret) error, es *esv1.ExternalSecret, secretName string) error {
fqdn := fmt.Sprintf(fieldOwnerTemplate, es.Name)
// define and mutate the new secret
newSecret := &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: secretName,
Namespace: es.Namespace,
},
Data: make(map[string][]byte),
}
if err := mutationFunc(newSecret); err != nil {
return err
}
// note, we set field owner even for Create
if err := r.Create(ctx, newSecret, client.FieldOwner(fqdn)); err != nil {
return err
}
// set the binding reference to the secret
// https://github.com/external-secrets/external-secrets/pull/2263
es.Status.Binding = v1.LocalObjectReference{Name: newSecret.Name}
r.recorder.Event(es, v1.EventTypeNormal, esv1.ReasonCreated, eventCreated)
return nil
}
func (r *Reconciler) updateSecret(ctx context.Context, existingSecret *v1.Secret, mutationFunc func(secret *v1.Secret) error, es *esv1.ExternalSecret, secretName string) error {
fqdn := fmt.Sprintf(fieldOwnerTemplate, es.Name)
// fail if the secret does not exist
// this should never happen because we check this before calling this function
if existingSecret.UID == "" {
return fmt.Errorf(errUpdateNotFound, secretName)
}
// set the binding reference to the secret
// https://github.com/external-secrets/external-secrets/pull/2263
es.Status.Binding = v1.LocalObjectReference{Name: secretName}
// mutate a copy of the existing secret with the mutation function
updatedSecret := existingSecret.DeepCopy()
if err := mutationFunc(updatedSecret); err != nil {
return fmt.Errorf(errMutate, updatedSecret.Name, err)
}
// if the secret does not need to be updated, return early
if equality.Semantic.DeepEqual(existingSecret, updatedSecret) {
return nil
}
// if the existing secret is immutable, we can only update the object metadata
if ptr.Deref(existingSecret.Immutable, false) {
// check if the metadata was changed
metadataChanged := !equality.Semantic.DeepEqual(existingSecret.ObjectMeta, updatedSecret.ObjectMeta)
// check if the immutable data/type was changed
var dataChanged bool
if metadataChanged {
// update the `existingSecret` object with the metadata from `updatedSecret`
// this lets us compare the objects to see if the immutable data/type was changed
existingSecret.ObjectMeta = *updatedSecret.ObjectMeta.DeepCopy()
dataChanged = !equality.Semantic.DeepEqual(existingSecret, updatedSecret)
// because we use labels and annotations to keep track of the secret,
// we need to update the metadata, regardless of if the immutable data was changed
// NOTE: we are using the `existingSecret` object here, as we ONLY want to update the metadata,
// and we previously copied the metadata from the `updatedSecret` object
if err := r.Update(ctx, existingSecret, client.FieldOwner(fqdn)); err != nil {
// if we get a conflict, we should return early to requeue immediately
// note, we don't wrap this error so we can handle it in the caller
if apierrors.IsConflict(err) {
return err
}
return fmt.Errorf(errUpdate, existingSecret.Name, err)
}
} else {
// we know there was some change in the secret (or we would have returned early)
// we know the metadata was NOT changed (metadataChanged == false)
// so, the only thing that could have changed is the immutable data/type fields
dataChanged = true
}
// if the immutable data was changed, we should return an error
if dataChanged {
return fmt.Errorf(errUpdate, existingSecret.Name, ErrSecretImmutable)
}
}
// update the secret
if err := r.Update(ctx, updatedSecret, client.FieldOwner(fqdn)); err != nil {
// if we get a conflict, we should return early to requeue immediately
// note, we don't wrap this error so we can handle it in the caller
if apierrors.IsConflict(err) {
return err
}
return fmt.Errorf(errUpdate, updatedSecret.Name, err)
}
r.recorder.Event(es, v1.EventTypeNormal, esv1.ReasonUpdated, eventUpdated)
return nil
}
// getManagedDataKeys returns the list of data keys in a secret which are managed by a specified owner.
func getManagedDataKeys(secret *v1.Secret, fieldOwner string) ([]string, error) {
return getManagedFieldKeys(secret, fieldOwner, func(fields map[string]any) []string {
dataFields := fields["f:data"]
if dataFields == nil {
return nil
}
df, ok := dataFields.(map[string]any)
if !ok {
return nil
}
return slices.Collect(maps.Keys(df))
})
}
func getManagedFieldKeys(
secret *v1.Secret,
fieldOwner string,
process func(fields map[string]any) []string,
) ([]string, error) {
fqdn := fmt.Sprintf(fieldOwnerTemplate, fieldOwner)
var keys []string
for _, v := range secret.ObjectMeta.ManagedFields {
if v.Manager != fqdn {
continue
}
fields := make(map[string]any)
err := json.Unmarshal(v.FieldsV1.Raw, &fields)
if err != nil {
return nil, fmt.Errorf("error unmarshaling managed fields: %w", err)
}
for _, key := range process(fields) {
if key == "." {
continue
}
keys = append(keys, strings.TrimPrefix(key, "f:"))
}
}
return keys, nil
}
func shouldSkipClusterSecretStore(r *Reconciler, es *esv1.ExternalSecret) bool {
return !r.ClusterSecretStoreEnabled && es.Spec.SecretStoreRef.Kind == esv1.ClusterSecretStoreKind
}
// shouldSkipUnmanagedStore iterates over all secretStore references in the externalSecret spec,
// fetches the store and evaluates the controllerClass property.
// Returns true if any storeRef points to store with a non-matching controllerClass.
func shouldSkipUnmanagedStore(ctx context.Context, namespace string, r *Reconciler, es *esv1.ExternalSecret) (bool, error) {
var storeList []esv1.SecretStoreRef
if es.Spec.SecretStoreRef.Name != "" {
storeList = append(storeList, es.Spec.SecretStoreRef)
}
for _, ref := range es.Spec.Data {
if ref.SourceRef != nil {
storeList = append(storeList, ref.SourceRef.SecretStoreRef)
}
}
for _, ref := range es.Spec.DataFrom {
if ref.SourceRef != nil && ref.SourceRef.SecretStoreRef != nil {
storeList = append(storeList, *ref.SourceRef.SecretStoreRef)
}
// verify that generator's controllerClass matches
if ref.SourceRef != nil && ref.SourceRef.GeneratorRef != nil {
_, obj, err := resolvers.GeneratorRef(ctx, r.Client, r.Scheme, namespace, ref.SourceRef.GeneratorRef)
if err != nil {
if apierrors.IsNotFound(err) {
// skip non-existent generators
continue
}
if errors.Is(err, resolvers.ErrUnableToGetGenerator) {
// skip generators that we can't get (e.g. due to being invalid)
continue
}
return false, err
}
skipGenerator, err := shouldSkipGenerator(r, obj)
if err != nil {
return false, err
}
if skipGenerator {
return true, nil
}
}
}
for _, ref := range storeList {
var store esv1.GenericStore
switch ref.Kind {
case esv1.SecretStoreKind, "":
store = &esv1.SecretStore{}
case esv1.ClusterSecretStoreKind:
store = &esv1.ClusterSecretStore{}
namespace = ""
default:
return false, fmt.Errorf("unsupported secret store kind: %s", ref.Kind)
}
err := r.Get(ctx, types.NamespacedName{
Name: ref.Name,
Namespace: namespace,
}, store)
if err != nil {
if apierrors.IsNotFound(err) {
// skip non-existent stores
continue
}
return false, err
}
class := store.GetSpec().Controller
if class != "" && class != r.ControllerClass {
return true, nil
}
}
return false, nil
}
func shouldRefresh(es *esv1.ExternalSecret) bool {
switch es.Spec.RefreshPolicy {
case esv1.RefreshPolicyCreatedOnce:
if es.Status.SyncedResourceVersion == "" || es.Status.RefreshTime.IsZero() {
return true
}
return false
case esv1.RefreshPolicyOnChange:
if es.Status.SyncedResourceVersion == "" || es.Status.RefreshTime.IsZero() {
return true
}
return es.Status.SyncedResourceVersion != util.GetResourceVersion(es.ObjectMeta)
case esv1.RefreshPolicyPeriodic:
return shouldRefreshPeriodic(es)
default:
return shouldRefreshPeriodic(es)
}
}
func shouldRefreshPeriodic(es *esv1.ExternalSecret) bool {
// if the refresh interval is 0, and we have synced previously, we should not refresh
if es.Spec.RefreshInterval.Duration <= 0 && es.Status.SyncedResourceVersion != "" {
return false
}
// if the ExternalSecret has been updated, we should refresh
if es.Status.SyncedResourceVersion != util.GetResourceVersion(es.ObjectMeta) {
return true
}
// if the last refresh time is zero, we should refresh
if es.Status.RefreshTime.IsZero() {
return true
}
// if the last refresh time is in the future, we should refresh
if es.Status.RefreshTime.Time.After(time.Now()) {
return true
}
// if the last refresh time + refresh interval is before now, we should refresh
return es.Status.RefreshTime.Add(es.Spec.RefreshInterval.Duration).Before(time.Now())
}
// isSecretValid checks if the secret exists, and it's data is consistent with the calculated hash.
func isSecretValid(existingSecret *v1.Secret) bool {
// if target secret doesn't exist, we need to refresh
if existingSecret.UID == "" {
return false
}
// if the managed label is missing or incorrect, then it's invalid
if existingSecret.Labels[esv1.LabelManaged] != esv1.LabelManagedValue {
return false
}
// if the data-hash annotation is missing or incorrect, then it's invalid
// this is how we know if the data has chanced since we last updated the secret
if existingSecret.Annotations[esv1.AnnotationDataHash] != utils.ObjectHash(existingSecret.Data) {
return false
}
return true
}
// SetupWithManager returns a new controller builder that will be started by the provided Manager.
func (r *Reconciler) SetupWithManager(mgr ctrl.Manager, opts controller.Options) error {
r.recorder = mgr.GetEventRecorderFor("external-secrets")
// index ExternalSecrets based on the target secret name,
// this lets us quickly find all ExternalSecrets which target a specific Secret
if err := mgr.GetFieldIndexer().IndexField(context.Background(), &esv1.ExternalSecret{}, indexESTargetSecretNameField, func(obj client.Object) []string {
es := obj.(*esv1.ExternalSecret)
// if the target name is set, use that as the index
if es.Spec.Target.Name != "" {
return []string{es.Spec.Target.Name}
}
// otherwise, use the ExternalSecret name
return []string{es.Name}
}); err != nil {
return err
}
// predicate function to ignore secret events unless they have the "managed" label
secretHasESLabel := predicate.NewPredicateFuncs(func(object client.Object) bool {
value, hasLabel := object.GetLabels()[esv1.LabelManaged]
return hasLabel && value == esv1.LabelManagedValue
})
return ctrl.NewControllerManagedBy(mgr).
WithOptions(opts).
For(&esv1.ExternalSecret{}).
// we cant use Owns(), as we don't set ownerReferences when the creationPolicy is not Owner.
// we use WatchesMetadata() to reduce memory usage, as otherwise we have to process full secret objects.
WatchesMetadata(
&v1.Secret{},
handler.EnqueueRequestsFromMapFunc(r.findObjectsForSecret),
builder.WithPredicates(predicate.ResourceVersionChangedPredicate{}, secretHasESLabel),
).
Complete(r)
}
func (r *Reconciler) findObjectsForSecret(ctx context.Context, secret client.Object) []reconcile.Request {
externalSecretsList := &esv1.ExternalSecretList{}
listOps := &client.ListOptions{
FieldSelector: fields.OneTermEqualSelector(indexESTargetSecretNameField, secret.GetName()),
Namespace: secret.GetNamespace(),
}
err := r.List(ctx, externalSecretsList, listOps)
if err != nil {
return []reconcile.Request{}
}
requests := make([]reconcile.Request, len(externalSecretsList.Items))
for i := range externalSecretsList.Items {
requests[i] = reconcile.Request{
NamespacedName: types.NamespacedName{
Name: externalSecretsList.Items[i].GetName(),
Namespace: externalSecretsList.Items[i].GetNamespace(),
},
}
}
return requests
}

View file

@ -0,0 +1,81 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package externalsecret
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
esv1 "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
"github.com/external-secrets/external-secrets/pkg/controllers/externalsecret/esmetrics"
)
// NewExternalSecretCondition a set of default options for creating an External Secret Condition.
func NewExternalSecretCondition(condType esv1.ExternalSecretConditionType, status v1.ConditionStatus, reason, message string) *esv1.ExternalSecretStatusCondition {
return &esv1.ExternalSecretStatusCondition{
Type: condType,
Status: status,
LastTransitionTime: metav1.Now(),
Reason: reason,
Message: message,
}
}
// GetExternalSecretCondition returns the condition with the provided type.
func GetExternalSecretCondition(status esv1.ExternalSecretStatus, condType esv1.ExternalSecretConditionType) *esv1.ExternalSecretStatusCondition {
for _, c := range status.Conditions {
if c.Type == condType {
return &c
}
}
return nil
}
// SetExternalSecretCondition updates the external secret to include the provided
// condition.
func SetExternalSecretCondition(es *esv1.ExternalSecret, condition esv1.ExternalSecretStatusCondition) {
currentCond := GetExternalSecretCondition(es.Status, condition.Type)
if currentCond != nil && currentCond.Status == condition.Status &&
currentCond.Reason == condition.Reason && currentCond.Message == condition.Message {
esmetrics.UpdateExternalSecretCondition(es, &condition, 1.0)
return
}
// Do not update lastTransitionTime if the status of the condition doesn't change.
if currentCond != nil && currentCond.Status == condition.Status {
condition.LastTransitionTime = currentCond.LastTransitionTime
}
es.Status.Conditions = append(filterOutCondition(es.Status.Conditions, condition.Type), condition)
if currentCond != nil {
esmetrics.UpdateExternalSecretCondition(es, currentCond, 0.0)
}
esmetrics.UpdateExternalSecretCondition(es, &condition, 1.0)
}
// filterOutCondition returns an empty set of conditions with the provided type.
func filterOutCondition(conditions []esv1.ExternalSecretStatusCondition, condType esv1.ExternalSecretConditionType) []esv1.ExternalSecretStatusCondition {
newConditions := make([]esv1.ExternalSecretStatusCondition, 0, len(conditions))
for _, c := range conditions {
if c.Type == condType {
continue
}
newConditions = append(newConditions, c)
}
return newConditions
}

View file

@ -0,0 +1,141 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package secretstore
import (
"context"
"fmt"
"time"
"github.com/go-logr/logr"
v1 "k8s.io/api/core/v1"
"k8s.io/client-go/tools/record"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
esapi "github.com/external-secrets/external-secrets/apis/externalsecrets/v1"
"github.com/external-secrets/external-secrets/pkg/controllers/secretstore/metrics"
)
const (
errStoreClient = "could not get provider client: %w"
errValidationFailed = "could not validate provider: %w"
errPatchStatus = "unable to patch status: %w"
errUnableCreateClient = "unable to create client"
errUnableValidateStore = "unable to validate store: %s"
msgStoreValidated = "store validated"
msgStoreNotMaintained = "store isn't currently maintained. Please plan and prepare accordingly."
)
type Opts struct {
ControllerClass string
GaugeVecGetter metrics.GaugeVevGetter
Recorder record.EventRecorder
RequeueInterval time.Duration
}
func reconcile(ctx context.Context, req ctrl.Request, ss esapi.GenericStore, cl client.Client, log logr.Logger, opts Opts) (ctrl.Result, error) {
if !ShouldProcessStore(ss, opts.ControllerClass) {
log.V(1).Info("skip store")
return ctrl.Result{}, nil
}
requeueInterval := opts.RequeueInterval
if ss.GetSpec().RefreshInterval != 0 {
requeueInterval = time.Second * time.Duration(ss.GetSpec().RefreshInterval)
}
// patch status when done processing
p := client.MergeFrom(ss.Copy())
defer func() {
err := cl.Status().Patch(ctx, ss, p)
if err != nil {
log.Error(err, errPatchStatus)
}
}()
// validateStore modifies the store conditions
// we have to patch the status
log.V(1).Info("validating")
err := validateStore(ctx, req.Namespace, opts.ControllerClass, ss, cl, opts.GaugeVecGetter, opts.Recorder)
if err != nil {
log.Error(err, "unable to validate store")
return ctrl.Result{}, err
}
storeProvider, err := esapi.GetProvider(ss)
if err != nil {
return ctrl.Result{}, err
}
isMaintained, err := esapi.GetMaintenanceStatus(ss)
if err != nil {
return ctrl.Result{}, err
}
annotations := ss.GetAnnotations()
_, ok := annotations["external-secrets.io/ignore-maintenance-checks"]
if !bool(isMaintained) && !ok {
opts.Recorder.Event(ss, v1.EventTypeWarning, esapi.StoreUnmaintained, msgStoreNotMaintained)
}
capStatus := esapi.SecretStoreStatus{
Capabilities: storeProvider.Capabilities(),
Conditions: ss.GetStatus().Conditions,
}
ss.SetStatus(capStatus)
opts.Recorder.Event(ss, v1.EventTypeNormal, esapi.ReasonStoreValid, msgStoreValidated)
cond := NewSecretStoreCondition(esapi.SecretStoreReady, v1.ConditionTrue, esapi.ReasonStoreValid, msgStoreValidated)
SetExternalSecretCondition(ss, *cond, opts.GaugeVecGetter)
return ctrl.Result{
RequeueAfter: requeueInterval,
}, err
}
// validateStore tries to construct a new client
// if it fails sets a condition and writes events.
func validateStore(ctx context.Context, namespace, controllerClass string, store esapi.GenericStore,
client client.Client, gaugeVecGetter metrics.GaugeVevGetter, recorder record.EventRecorder) error {
mgr := NewManager(client, controllerClass, false)
defer mgr.Close(ctx)
cl, err := mgr.GetFromStore(ctx, store, namespace)
if err != nil {
cond := NewSecretStoreCondition(esapi.SecretStoreReady, v1.ConditionFalse, esapi.ReasonInvalidProviderConfig, errUnableCreateClient)
SetExternalSecretCondition(store, *cond, gaugeVecGetter)
recorder.Event(store, v1.EventTypeWarning, esapi.ReasonInvalidProviderConfig, err.Error())
return fmt.Errorf(errStoreClient, err)
}
validationResult, err := cl.Validate()
if err != nil && validationResult != esapi.ValidationResultUnknown {
cond := NewSecretStoreCondition(esapi.SecretStoreReady, v1.ConditionFalse, esapi.ReasonValidationFailed, fmt.Sprintf(errUnableValidateStore, err))
SetExternalSecretCondition(store, *cond, gaugeVecGetter)
recorder.Event(store, v1.EventTypeWarning, esapi.ReasonValidationFailed, err.Error())
return fmt.Errorf(errValidationFailed, err)
}
return nil
}
// ShouldProcessStore returns true if the store should be processed.
func ShouldProcessStore(store esapi.GenericStore, class string) bool {
if store == nil || store.GetSpec().Controller == "" || store.GetSpec().Controller == class {
return true
}
return false
}

View file

@ -0,0 +1,37 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/external-secrets/external-secrets/pkg/utils"
)
func GetResourceVersion(meta metav1.ObjectMeta) string {
return fmt.Sprintf("%d-%s", meta.GetGeneration(), HashMeta(meta))
}
func HashMeta(m metav1.ObjectMeta) string {
type meta struct {
annotations map[string]string
labels map[string]string
}
return utils.ObjectHash(meta{
annotations: m.Annotations,
labels: m.Labels,
})
}