--- id: RPF-WP-0016 type: workplan title: "Version ephemeral custody projection and broker handoff" domain: financials repo: railiance-platform status: finished owner: codex topic_slug: railiance created: "2026-08-22" updated: "2026-08-22" related: - RAILIANCE-WP-0024 - WHITEHAT-WP-0001 - AUDIT-WP-0008 origin: run-review origin_ref: "WH-ENG-20260822-AUDIT-E2-02 terminal clean admission abort" state_hub_workstream_id: "26165dd5-dca5-5261-b4bf-183d186aad27" --- # RPF-WP-0016 — Version ephemeral custody projection and broker handoff ## Goal Replace engagement-specific credential scripts and prose handoffs with one versioned, value-safe, fail-closed lifecycle. A consumer must prove it can consume the projection receipt before platform custody mints anything. The platform then exposes direct `preflight`, `project`, `status`, `cleanup`, and expired-lease cleanup interfaces without transferring bearer values or collapsing owner approvals. The implementation is repository code and contracts only. It does not authorize or schedule a live credential projection, workload, probe, or unattended cleanup controller. ## T01 — Define projection, broker-readiness, and cleanup receipts ```task id: RPF-WP-0016-T01 status: done priority: high state_hub_task_id: "3d95a854-1c54-58c9-90d2-971b9a9ec5c6" ``` Publish versioned JSON Schemas and canonical validation for the projection contract, broker-readiness receipt, active projection receipt, and cleanup receipt. Bind every receipt to the exact engagement, target revision and image, runner and target contract digests, mounted identity handles, expiry, opaque lease id, and cleanup authority. Values and value-derived fingerprints are forbidden. Done when malformed, stale, mismatched, expired, and value-bearing documents fail closed and consumers can implement without interpreting prose. Implemented in four JSON Schemas plus `scripts/custody_contract.py`. Canonical projection ids cover all receipt content; validators bind target, contract, roles, mount paths, resource names and UIDs, and reject value-bearing fields. Broker approvals also pin the current hashes of all four interface schemas. ## T02 — Implement a data-driven custody lifecycle ```task id: RPF-WP-0016-T02 status: done priority: high state_hub_task_id: "783f74b8-d4a6-5ac5-9300-62acfedc53c6" ``` Replace copied per-engagement code and static manifests with one lifecycle that derives exact OpenBao and ESO resources from a validated engagement contract. Expose value-safe `preflight`, `project`, `status`, and idempotent `cleanup` commands. Preserve the existing two-identity sender overlay, application-level expiry, exact-key mount, readiness reload, and exact-scope rollback behavior. Done when a new engagement requires data only, not copied Python, HCL, or YAML, and an old engagement identifier cannot leak into generated resources. Implemented by `scripts/custody-projection.py` and the intentionally invalid placeholder template `docs/custody-projection-contract.example.json`. Policy, role, store, KV paths, ExternalSecret data mappings, sender overlays, expiry, and cleanup scope derive from one validated contract. Resource names use an engagement-id digest rather than a copied run suffix. ## T03 — Gate projection on a direct broker capability receipt ```task id: RPF-WP-0016-T03 status: done priority: high state_hub_task_id: "e2cced83-5beb-5fb1-919a-72bd6cb49dcf" ``` Provide a direct owner interface for `whitehat-security` to inspect, verify, approve, or request changes to the contract. Approval must identify the tested adapter revision and artifact digest and be posted directly to State Hub. The platform lifecycle must find a current matching approval before generating a credential. Done when an unconnected, stale, wrong-engagement, or wrong-contract broker is rejected before the first OpenBao or sender-registry mutation. Implemented by `scripts/wp0025-broker-readiness.py`. Its closed verification surface pins Whitehat's adapter and focused test paths, computes their commit and digest, runs only the named focused test, and posts the canonical receipt directly as `whitehat-security`. `project` checks State Hub before reading the platform-admin token file, generating bearer values, or touching live state. No adapter approval is fabricated by this implementation; a future Whitehat adapter must pass the interface itself. ## T04 — Harden execution and transactional cleanup ```task id: RPF-WP-0016-T04 status: done priority: high state_hub_task_id: "a14cb95b-6ef4-5872-a4ec-c7b51577cef1" ``` Centralize remote argv quoting, test shell-hostile arguments, and inject failures at each mutation boundary. Prove that partial projection invokes exact cleanup and that status never infers absence after a connectivity failure. Retire the copied `-01`/`-02` procedure from future-run instructions. Done when the SSH expansion defect from the `-02` run and every modeled partial failure have regression coverage. Implemented by `scripts/remote_exec.py` and focused tests covering `$`, shell substitution, semicolons, quotes, NUL refusal, and value-safe failure messages. The lifecycle uses a transaction whose rollback is exercised after every modeled mutation boundary. Connectivity failure cannot be interpreted as absence, and cleanup refuses a recreated same-name Kubernetes resource whose UID differs from the receipt. ## T05 — Add an approval-ready expired-lease cleanup entry point ```task id: RPF-WP-0016-T05 status: done priority: medium state_hub_task_id: "8e2840d1-b8e3-5f0f-8c94-f5019fff4e72" ``` Add a fail-closed `cleanup-expired` interface that accepts only a valid projection receipt after its expiry, removes only the receipt-bound resources, and emits the normal cleanup receipt. Do not install a scheduler or controller; deployment of unattended cleanup remains a separate owner decision. Done when an approved future reaper can call a deterministic command without receiving projected bearer values or reconstructing cleanup scope from chat. Implemented as `cleanup-expired`. It refuses before expiry, requires the exact canonical projection receipt and engagement confirmation, verifies live UIDs, uses the ordinary exact cleanup transaction, and emits the versioned cleanup receipt. No scheduler, controller, runtime identity, or unattended authority was deployed; those remain a separate owner decision. ## Acceptance - [x] No projection can begin without a current consumer broker receipt. - [x] A new engagement is configuration, not copied executable code. - [x] Projection and cleanup receipts are versioned, canonical, and value-safe. - [x] Remote command arguments survive shell-hostile templates unchanged. - [x] Every modeled partial mutation cleans only its receipt-bound scope. - [x] Expired cleanup is executable but no unattended scheduler is deployed. ## T06 — Export the broker receipt consumed by admission ```task id: RPF-WP-0016-T06 status: done priority: high state_hub_task_id: "d815e1b9-73fe-527c-85c9-c20e37cf510f" ``` The first fresh-run setup review found that `approve` posted the canonical broker-readiness receipt to State Hub but exposed no safe way to persist the raw receipt document required by Whitehat's `--broker-receipt` input. Redirecting the command output would save a CLI wrapper rather than the canonical document. `approve` and a ready `status` now accept `--receipt-out`, create parent directories, write only the canonical value-safe receipt, and force mode `0600`. Other commands fail closed when that option is supplied. Regression coverage proves the wrapper is excluded and the file mode is private.