feat: verify live KeyCape custody and preserve versions on resume
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

Assistant: codex
Assistant-Model: gpt-5.6-luna
Assistant-Session: 01a07ff8-19d0-7820-b4d0-1353833cb7fc
This commit is contained in:
codex 2026-09-09 02:18:12 +02:00
parent b7861de20e
commit c6dc4286e2
8 changed files with 382 additions and 57 deletions

View file

@ -3,9 +3,9 @@ kind: credential-change-request
schema_version: 1 schema_version: 1
request_type: workload-kv-read request_type: workload-kv-read
title: KeyCape verifier custody for the secrets-engine-approval confidential client title: KeyCape verifier custody for the secrets-engine-approval confidential client
status: approved status: verified
created: '2026-09-08' created: '2026-09-08'
updated: '2026-09-08' updated: '2026-09-09'
requester: requester:
agent: claude agent: claude
reason: "KEY-WP-0013-T02 (State Hub message 278a3ebe-b529-49f6-bd1a-e3ebcf318260)\ reason: "KEY-WP-0013-T02 (State Hub message 278a3ebe-b529-49f6-bd1a-e3ebcf318260)\
@ -140,7 +140,29 @@ verification:
- Policy, Kubernetes auth role and ClusterSecretStore applied before the ExternalSecret; - Policy, Kubernetes auth role and ClusterSecretStore applied before the ExternalSecret;
sync confirmed before the KeyCape image is rolled out. sync confirmed before the KeyCape image is rolled out.
- Positive and negative results recorded with non-secret request ids or timestamps. - Positive and negative results recorded with non-secret request ids or timestamps.
evidence: [] evidence:
- at: '2026-09-09T00:14:27+00:00'
actor: the-custodian (codex)
kind: verifier_custody_and_rollout
result: passed
details:
- Named user approval recorded for platform-operator and key-cape-owner on 2026-09-09.
- Initial CAS=0 custody created version 1. Verified rollback/resume retained the
same values and versions.
- Both stores Valid and ExternalSecrets SecretSynced; exact native read, sibling/listing
and wrong service account/namespace/store-use denials passed; reader revocation
and coding-agent deny precedence passed.
- Pinned image 7ff54c54e63e has one ready replica; protected previous config/image
retained and signing key unchanged.
- Both clients passed live JWKS and exact audience/subject/tenant/role/scope/900-second
lifetime checks, excess-scope and wrong-secret denials; human client consume
denied.
- Native verifier from pinned image ran in the attended owner process with memory-only
credentials. Pod network policy remains unchanged. Future iat bound is the existing
30-second contract; expiry has zero leeway.
- Fresh post-rollout OpenBao OIDC login succeeded and self-revoked. No client-side
fetch front door or factory spending was admitted.
- 'Receipt: docs/evidence/2026-09-09-keycape-verifier-admission.json'
lifecycle: lifecycle:
deactivate: KeyCape disables the secrets-engine-approval registration; platform deactivate: KeyCape disables the secrets-engine-approval registration; platform
detaches the policy from role external-secrets-keycape-secrets-engine-approval detaches the policy from role external-secrets-keycape-secrets-engine-approval

View file

@ -3,9 +3,9 @@ kind: credential-change-request
schema_version: 1 schema_version: 1
request_type: workload-kv-read request_type: workload-kv-read
title: KeyCape verifier custody for the approval-engine-operator confidential client title: KeyCape verifier custody for the approval-engine-operator confidential client
status: approved status: verified
created: '2026-09-08' created: '2026-09-08'
updated: '2026-09-08' updated: '2026-09-09'
requester: requester:
agent: claude agent: claude
reason: 'Second of the two registrations in KEY-WP-0013-T02 (State Hub message 278a3ebe-b529-49f6-bd1a-e3ebcf318260): reason: 'Second of the two registrations in KEY-WP-0013-T02 (State Hub message 278a3ebe-b529-49f6-bd1a-e3ebcf318260):
@ -122,7 +122,29 @@ verification:
- Policy, Kubernetes auth role and ClusterSecretStore applied before the ExternalSecret; - Policy, Kubernetes auth role and ClusterSecretStore applied before the ExternalSecret;
sync confirmed before the KeyCape rollout. sync confirmed before the KeyCape rollout.
- Positive and negative results recorded with non-secret request ids or timestamps. - Positive and negative results recorded with non-secret request ids or timestamps.
evidence: [] evidence:
- at: '2026-09-09T00:14:27+00:00'
actor: the-custodian (codex)
kind: verifier_custody_and_rollout
result: passed
details:
- Named user approval recorded for platform-operator and key-cape-owner on 2026-09-09.
- Initial CAS=0 custody created version 1. Verified rollback/resume retained the
same values and versions.
- Both stores Valid and ExternalSecrets SecretSynced; exact native read, sibling/listing
and wrong service account/namespace/store-use denials passed; reader revocation
and coding-agent deny precedence passed.
- Pinned image 7ff54c54e63e has one ready replica; protected previous config/image
retained and signing key unchanged.
- Both clients passed live JWKS and exact audience/subject/tenant/role/scope/900-second
lifetime checks, excess-scope and wrong-secret denials; human client consume
denied.
- Native verifier from pinned image ran in the attended owner process with memory-only
credentials. Pod network policy remains unchanged. Future iat bound is the existing
30-second contract; expiry has zero leeway.
- Fresh post-rollout OpenBao OIDC login succeeded and self-revoked. No client-side
fetch front door or factory spending was admitted.
- 'Receipt: docs/evidence/2026-09-09-keycape-verifier-admission.json'
lifecycle: lifecycle:
deactivate: KeyCape disables the approval-engine-operator registration; platform deactivate: KeyCape disables the approval-engine-operator registration; platform
detaches the policy from role external-secrets-keycape-approval-engine-operator detaches the policy from role external-secrets-keycape-approval-engine-operator

View file

@ -1,45 +1,28 @@
# KeyCape approval-client custody: review packet # KeyCape approval-client custody: accepted verifier delivery
Prepared 2026-09-09 by the-custodian. Both requests are **approved** by the user as platform operator and KeyCape Both CCR-2026-0017 and CCR-2026-0018 have the user's explicit approval as platform
owner, explicitly recorded on 2026-09-09. The admission receipt is operator and KeyCape owner. Both are now **verified**. Their existing decision
[here](../evidence/2026-09-09-keycape-approval-admission.json). This is the review record for IDs remain resolved; no renewed review is needed for this completed scope.
RPF-WP-0035-T05, consumed by HFACT-WP-0001-T03.
| Request | Secret path and field | Client authority | Resolved decision | [Combined live receipt](../evidence/2026-09-09-keycape-verifier-admission.json) records version-1 custody, both ESO
| --- | --- | --- | --- | stores and Secrets, exact native scope/auth/namespace denials, reader revocation,
| [CCR-2026-0017](../../credential-change-requests/CCR-2026-0017-keycape-secrets-engine-approval-client.yaml) | `platform/workloads/secrets-engine/approval-client`, `CLIENT_SECRET` | `approval:read`, `approval:consume` | `b533a271-b704-4c5c-98a2-9a5951aadfb6` | compatible pinned KeyCape deployment and both service-client verification runs.
| [CCR-2026-0018](../../credential-change-requests/CCR-2026-0018-keycape-approval-engine-operator-client.yaml) | `platform/workloads/approval-engine/operator-client`, `CLIENT_SECRET` | create/read/approve/revoke/supersede/observe/emit; no consume | `efa90517-0cae-4eb6-a68d-5b0489c84d65` | The existing human OpenBao login passed before and after the rollout.
Both named **platform-operator and key-cape-owner** reviews are approved. Each has its own The two Warden fetch selectors remain unresolved: these CCRs authorize verifier
exact-path policy, Kubernetes auth role and ClusterSecretStore, bound to copies only. Client-side reads, audit custody, natural JWT expiry, real predecessor
`external-secrets/external-secrets`, limited to `sso`. ESO owns the resulting rotation and factory operating/spend admission remain with the existing owner
Secret; authentication tokens have a 15-minute TTL. Client secrets require records. See [the owner sequence](keycape-approval-clients.md).
explicit rotation or registration disablement; token expiry does not revoke them.
The approved requests authorize verifier-side custody only. Client-side The exercised procedure is `scripts/keycape_approval_custody.py`: `activate` for
retrieval, audit sender/receiver custody, operator `approval:consume`, adoption first provision, `resume-activate` only with a completed rollback receipt, and
of the Qonto Secret and factory spending remain outside these two requests. `verify` for existing custody. The selected kubeconfig must identify railiance01;
on this workstation it is `/home/worsch/.kube/config-railiance01`. Use the
Warden attended wrapper, a unique metadata receipt and protected recovery file.
The pinned native verifier is extracted from image digest `7ff54c54e63e...` and
hash-checked; its child environment carries credentials only during execution.
No Kubernetes egress policy changes are needed.
Technical review completed: both CCRs validate, their generated policies match Validation: eight local OpenBao checks, ten config/rollback/clock checks, 53
the two source HCL files, and the delivery manifests map exactly to the declared existing credential-change tests, plus a disposable pinned-image HTTPS exercise
paths and fields. The signed upstream issuer is verified; NetKingdom's exact live that runs the exact native verifier with synthetic keys and client credentials.
pin is independently read back at Secret revision `58713343`. The compatible
KeyCape image and rollout patch are prepared. These checks are evidence for the
review, not substitutes for either named approval.
Execution still follows the [owner rollout sequence](keycape-approval-clients.md)
through the founder-attended Warden/OpenBao envelope: policy/roles, CAS=0 initial
custody, Valid stores, SecretSynced delivery, compatible config/image replacement,
then positive and negative acceptance and metadata-only receipts. The contained owner command is now `scripts/keycape_approval_custody.py activate`.
It preflights both config and deployment changes, seeds with CAS=0, verifies native
and namespace boundaries, then calls NetKingdom's contained compatible rollout.
A failure restores the prior config/image pair before detaching verifier delivery;
initial KV versions remain in custody. The four new exact data/metadata deny
stanzas extend the coding-agent boundary without changing any grant.
Validation: six checks against a disposable local OpenBao, eight config/rollback
tests, and real HTTP signature/claim/scope acceptance against the pinned KeyCape
image with synthetic keys. Production in-pod CLI and existing-human-login checks
remain part of the live window. The image exercise omits the in-pod CLI explicitly.
Use a unique private receipt and protected owner recovery path; a fresh attended
login after the rollout establishes the existing human path on the new build.

View file

@ -1,14 +1,20 @@
# KeyCape approval-engine client custody admission # KeyCape approval-engine client custody admission
**2026-09-09 accepted:** verifier custody and the compatible image/config are live;
both service verifiers and a fresh existing-human OpenBao login passed. Both CCRs
are verified. [Live evidence](../evidence/2026-09-09-keycape-verifier-admission.json) supersedes the preparation
status below. Client-side reads and factory operating grants remain separate.
Answer to KEY-WP-0013-T02 (State Hub message Answer to KEY-WP-0013-T02 (State Hub message
`278a3ebe-b529-49f6-bd1a-e3ebcf318260`, KeyCape packet `278a3ebe-b529-49f6-bd1a-e3ebcf318260`, KeyCape packet
`key-cape: docs/approval-engine-provisioning-request.yaml`). Tracked here as `key-cape: docs/approval-engine-provisioning-request.yaml`). Tracked here as
RPF-WP-0035-T05. Requests: [CCR-2026-0017](../../credential-change-requests/CCR-2026-0017-keycape-secrets-engine-approval-client.yaml), RPF-WP-0035-T05. Requests: [CCR-2026-0017](../../credential-change-requests/CCR-2026-0017-keycape-secrets-engine-approval-client.yaml),
[CCR-2026-0018](../../credential-change-requests/CCR-2026-0018-keycape-approval-engine-operator-client.yaml). [CCR-2026-0018](../../credential-change-requests/CCR-2026-0018-keycape-approval-engine-operator-client.yaml).
Nothing below is an activation. Both CCRs are `proposed`; no value has been Both CCRs are **verified** following explicit user approval in both reviewer
generated, no KV version written, no manifest applied. Source preparation is not roles. Version-1 custody, ESO delivery, the compatible KeyCape rollout and the
live completion. fresh existing-human login all passed. The contract below defines the completed
verifier-side scope; client-side reads remain a separate admission.
## a) Custody paths and field names ## a) Custody paths and field names
@ -53,7 +59,7 @@ keycape-rapp-qonto-client, key: client-secret}`. Read-only observation
2026-09-08, and it confirms KeyCape's own statement that the image carries only 2026-09-08, and it confirms KeyCape's own statement that the image carries only
that one client reference. that one client reference.
Manifests are written and client-validated but unapplied: Both delivery manifests are applied and live-verified:
`argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstore.yaml` `argocd/platform-addons/openbao-secretstore/openbao-keycape-approval-clients.clustersecretstore.yaml`
and `keycape-approval-clients.externalsecrets.yaml`. and `keycape-approval-clients.externalsecrets.yaml`.
@ -146,9 +152,10 @@ ExternalSecrets, detach the policies from the roles. The KV versions are retaine
until KeyCape confirms whether the registrations stay; if they are abandoned, until KeyCape confirms whether the registrations stay; if they are abandoned,
KeyCape disables the registrations first and platform then destroys the versions. KeyCape disables the registrations first and platform then destroys the versions.
**Date is not set here.** It depends on the founder's availability, which is not The attended window completed on **2026-09-09** under the user's explicit
mine to schedule. Propose a slot from 2026-09-10 and I will confirm the approval. Failed checks restored the compatible config/image and detached ESO
platform side; the window needs roughly 6090 minutes with both owners present. delivery. The final resume preserved both initial KV versions and passed all
service checks plus a fresh existing-human OpenBao login.
## What is not admitted ## What is not admitted
@ -184,4 +191,4 @@ approval is performed by this preflight.
2026-09-09: the live issuer pin is complete. The next review is captured in 2026-09-09: the live issuer pin is complete. The next review is captured in
[keycape-approval-clients-review.md](keycape-approval-clients-review.md), with [keycape-approval-clients-review.md](keycape-approval-clients-review.md), with
one pending Hub decision per existing CCR and both required reviewer roles. the resolved Hub decisions, both recorded reviewer roles and completed live verification.

View file

@ -0,0 +1,220 @@
{
"schema": "helixforge.keycape-verifier-admission.v1",
"recorded_at": "2026-09-09T00:14:27.155338+00:00",
"approval": {
"schema": "railiance.keycape-custody-user-approval.v1",
"recorded_at": "2026-09-08T23:05:19.886216+00:00",
"user_response": "I approve, go on.",
"approved_question": "Do you approve CCR-2026-0017 and CCR-2026-0018, as platform operator and KeyCape owner, for the verifier-side credential delivery described in the review packet?",
"review_packet_revision": "52b24eab9a8aff3396e71b2296d0240a44f3b0db",
"roles": [
"platform-operator",
"key-cape-owner"
],
"scope": "Two verifier-side KeyCape client credential custody requests, including their governed attended provisioning and compatible rollout sequence.",
"client_side_read_authorized": false,
"factory_spending_authorized": false,
"requests": [
{
"id": "CCR-2026-0017",
"decision_id": "b533a271-b704-4c5c-98a2-9a5951aadfb6",
"status": "approved",
"decision_status": "resolved",
"reviewed_roles": [
"platform-operator",
"key-cape-owner"
]
},
{
"id": "CCR-2026-0018",
"decision_id": "efa90517-0cae-4eb6-a68d-5b0489c84d65",
"status": "approved",
"decision_status": "resolved",
"reviewed_roles": [
"platform-operator",
"key-cape-owner"
]
}
]
},
"activation": {
"schema": "platform.keycape-approval-custody.v1",
"status": "custody_and_service_acceptance_passed_pending_fresh_human_login",
"lanes": [
{
"ccr": "CCR-2026-0017",
"source_sha256": "dc2bbd86e213edecc2f2f91a99d0f51136e9a0853a90e55f65dda2b942305416",
"custody_seeded": false,
"existing_version_reused": true,
"kv_version": 1,
"initial_request_id": "94c98373-33fa-591d-c201-d36dddf9879b",
"policy_applied": false,
"role_applied": true,
"native_reader_verified": true,
"cross_path_denied": true,
"parent_listing_denied": true,
"auth_ttl": 900,
"reader_revocation_verified": true,
"wrong_service_account_denied": true,
"coding_agent_deny_wins": true,
"store_ready": true,
"external_secret_ready": true,
"delivery_matches": true,
"secret_uid": "eaa28bdf-04af-4e4e-a1fc-fe395c7689a7",
"secret_resource_version": "58747058",
"wrong_namespace_denied": true,
"outside_namespace_store_denied": true
},
{
"ccr": "CCR-2026-0018",
"source_sha256": "99148133ddf1eb15dce3c03862b51dc001818ca3121045035a0b0e5897c55dad",
"custody_seeded": false,
"existing_version_reused": true,
"kv_version": 1,
"initial_request_id": "a62ee5c5-b5ba-b7dc-0f51-e6fd8a6dde32",
"policy_applied": false,
"role_applied": true,
"native_reader_verified": true,
"cross_path_denied": true,
"parent_listing_denied": true,
"auth_ttl": 900,
"reader_revocation_verified": true,
"wrong_service_account_denied": true,
"coding_agent_deny_wins": true,
"store_ready": true,
"external_secret_ready": true,
"delivery_matches": true,
"secret_uid": "599a61a7-8244-4618-8c44-6473195bbe4e",
"secret_resource_version": "58747062",
"wrong_namespace_denied": true,
"outside_namespace_store_denied": true
}
],
"started_at": "2026-09-09T00:09:28.852280+00:00",
"credential_values_emitted": false,
"client_side_read_admitted": false,
"keycape_rollout_completed": true,
"issuer_pin_revision": "58746187",
"phase": "awaiting_fresh_human_login",
"custody_versions_unchanged": true,
"namespace_probe_cleanup_requested": true,
"keycape": {
"existing_human_login_before": true,
"protected_recovery_retained": true,
"deployment_uid": "99ddd83c-cb3f-4847-bcf8-35f1aa87627f",
"generation": 38,
"pod_uid": "8d62e972-d466-413e-a2e4-08b9a08016bc",
"single_ready_replica": true,
"acceptance_phase": "passed",
"clients": [
{
"client_id": "secrets-engine-approval",
"live_jwks_signature_verified": true,
"exact_claims_verified": true,
"lifetime_seconds": 900,
"maximum_future_iat_seconds": 30,
"expiry_leeway_seconds": 0,
"excess_scope_denied": true,
"wrong_secret_denied": true,
"pinned_artifact_verifier_passed": true,
"verifier_location": "attended owner process",
"verifier_sha256": "4bf93bbe9afe0bf2e21d51c03373a7e2b4be6864586bcc2d472eeb9abc913d92",
"real_predecessor_rotation_tested": false,
"observed_wall_clock_expiry": false
},
{
"client_id": "approval-engine-operator",
"live_jwks_signature_verified": true,
"exact_claims_verified": true,
"lifetime_seconds": 900,
"maximum_future_iat_seconds": 30,
"expiry_leeway_seconds": 0,
"excess_scope_denied": true,
"wrong_secret_denied": true,
"pinned_artifact_verifier_passed": true,
"verifier_location": "attended owner process",
"verifier_sha256": "4bf93bbe9afe0bf2e21d51c03373a7e2b4be6864586bcc2d472eeb9abc913d92",
"real_predecessor_rotation_tested": false,
"observed_wall_clock_expiry": false
}
],
"acceptance_client": "approval-engine-operator",
"human_client_consume_denied": true,
"status": "service_acceptance_passed_pending_fresh_human_login",
"image": "forgejo.coulomb.social/coulomb/key-cape@sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611",
"config_resource_version": "58747126",
"signing_key_unchanged": true,
"unrelated_config_bytes_preserved": true,
"existing_human_login_after": false
},
"finished_at": "2026-09-09T00:10:09.099481+00:00"
},
"post_rollout_login": {
"schema": "netkingdom.keycape-approval-rollout.v1",
"status": "service_and_existing_human_login_acceptance_passed",
"values_emitted": false,
"deployment_uid": "99ddd83c-cb3f-4847-bcf8-35f1aa87627f",
"generation": 38,
"pod_uid": "8d62e972-d466-413e-a2e4-08b9a08016bc",
"single_ready_replica": true,
"acceptance_phase": "passed",
"clients": [
{
"client_id": "secrets-engine-approval",
"live_jwks_signature_verified": true,
"exact_claims_verified": true,
"lifetime_seconds": 900,
"maximum_future_iat_seconds": 30,
"expiry_leeway_seconds": 0,
"excess_scope_denied": true,
"wrong_secret_denied": true,
"pinned_artifact_verifier_passed": true,
"verifier_location": "attended owner process",
"verifier_sha256": "4bf93bbe9afe0bf2e21d51c03373a7e2b4be6864586bcc2d472eeb9abc913d92",
"real_predecessor_rotation_tested": false,
"observed_wall_clock_expiry": false
},
{
"client_id": "approval-engine-operator",
"live_jwks_signature_verified": true,
"exact_claims_verified": true,
"lifetime_seconds": 900,
"maximum_future_iat_seconds": 30,
"expiry_leeway_seconds": 0,
"excess_scope_denied": true,
"wrong_secret_denied": true,
"pinned_artifact_verifier_passed": true,
"verifier_location": "attended owner process",
"verifier_sha256": "4bf93bbe9afe0bf2e21d51c03373a7e2b4be6864586bcc2d472eeb9abc913d92",
"real_predecessor_rotation_tested": false,
"observed_wall_clock_expiry": false
}
],
"acceptance_client": "approval-engine-operator",
"human_client_consume_denied": true,
"existing_human_login_after": true,
"receipt_written_at": "2026-09-09T00:11:59.113549+00:00"
},
"attended_envelope": {
"all_attempted_sessions_revoked": true,
"successful_activation_exit_code": 0,
"post_rollout_login_exit_code": 0
},
"validation": {
"local_openbao_tests": 8,
"configuration_and_recovery_tests": 10,
"credential_change_tests": 53,
"pinned_image_synthetic_https_and_native_verifier": "passed"
},
"failed_attempt_receipts": [
"net-kingdom:docs/evidence/2026-09-09-keycape-activation-attempts.json"
],
"limits": {
"client_side_read_admitted": false,
"factory_spending_admitted": false,
"wall_clock_jwt_expiry_observed": false,
"actual_predecessor_rotation_observed": false
},
"temporary_probe_namespaces_remaining": 0,
"owner_manifest_api_defaults_match_live": true
}

View file

@ -62,6 +62,36 @@ def contracts():
return result return result
def resume_provision(lanes, receipt, prior_path):
prior = json.loads(Path(prior_path).read_text())
require(prior.get('status') == 'failed' and prior.get('verifier_delivery_disabled_custody_versions_retained')
and prior.get('keycape', {}).get('compatible_pair_restored'), 'verified_partial_activation_receipt_required')
old_rows = prior['lanes']
require(len(old_rows) == len(lanes), 'partial_receipt_lane_mismatch')
for lane, old in zip(lanes, old_rows):
require(old['ccr'] == lane['ccr'] and (old.get('custody_seeded') or old.get('existing_version_reused')) and old.get('kv_version') == 1,
'initial_version_provenance_required')
metadata = read_optional(lane['metadata'])
require(metadata is not None and metadata['current_version'] == 1
and not metadata['versions']['1'].get('destroyed') and not metadata['versions']['1'].get('deletion_time'),
'initial_custody_version_changed')
role = read_optional('auth/kubernetes/role/' + lane['role'])
require(role is not None and role.get('token_policies') == []
and role_matches(dict(role, token_policies=[lane['policy']]), lane), 'disabled_role_drift')
require(read_optional('sys/policies/acl/' + lane['policy'])['policy'] == lane['hcl'], 'resume_policy_drift')
require(all(re.search(r'path\s+"' + re.escape(path) + r'"\s*\{\s*capabilities\s*=\s*\["deny"\]\s*\}', read_optional(BOUNDARY)['policy'])
for path in (lane['kv'], lane['metadata'])), 'resume_boundary_drift')
# Only reattach the same reviewed reader roles. No KV write, import or rotation.
for lane, old in zip(lanes, old_rows):
bao(['write', 'auth/kubernetes/role/' + lane['role'], '-'], payload=lane['role_payload'])
require(role_matches(read_optional('auth/kubernetes/role/' + lane['role']), lane), 'resumed_role_readback_failed')
receipt['lanes'].append({'ccr': lane['ccr'], 'source_sha256': lane['source_sha256'],
'custody_seeded': False, 'existing_version_reused': True, 'kv_version': 1,
'initial_request_id': old.get('initial_request_id', old.get('request_id')), 'policy_applied': False, 'role_applied': True})
receipt['custody_versions_unchanged'] = True
def read_optional(path): def read_optional(path):
result = bao(['read', '-format=json', path], allow_failure=True) result = bao(['read', '-format=json', path], allow_failure=True)
if result.returncode == 0: if result.returncode == 0:
@ -264,8 +294,9 @@ def run(args, receipt):
check = data(command(['python3', '-B', '/home/worsch/net-kingdom/sso-mfa/k8s/keycape/openbao-client-config.py', 'issuer-check-live'], env=env)) check = data(command(['python3', '-B', '/home/worsch/net-kingdom/sso-mfa/k8s/keycape/openbao-client-config.py', 'issuer-check-live'], env=env))
require(check['issuer_matches'] and check['verified_issuer'] == 'https://auth.coulomb.social', 'verified_issuer_pin_required') require(check['issuer_matches'] and check['verified_issuer'] == 'https://auth.coulomb.social', 'verified_issuer_pin_required')
receipt['issuer_pin_revision'] = check['before']['resource_version'] receipt['issuer_pin_revision'] = check['before']['resource_version']
rollout = load_rollout() if args.action == 'activate' else None rollout = load_rollout() if args.action in {'activate', 'resume-activate'} else None
if rollout: if rollout:
rollout.verify_artifact()
# Exercise config construction and API admission before any custody write. # Exercise config construction and API admission before any custody write.
secret = rollout.get(kube, 'secret', 'keycape-config') secret = rollout.get(kube, 'secret', 'keycape-config')
dep = rollout.get(kube, 'deployment', 'keycape') dep = rollout.get(kube, 'deployment', 'keycape')
@ -282,6 +313,9 @@ def run(args, receipt):
receipt['phase'] = 'provision' receipt['phase'] = 'provision'
if args.action in {'provision', 'activate'}: if args.action in {'provision', 'activate'}:
provision(lanes, receipt) provision(lanes, receipt)
elif args.action == 'resume-activate':
require(args.prior_receipt, 'prior_receipt_required')
resume_provision(lanes, receipt, args.prior_receipt)
else: else:
receipt['lanes'] = [{'ccr': lane['ccr']} for lane in lanes] receipt['lanes'] = [{'ccr': lane['ccr']} for lane in lanes]
receipt['phase'] = 'native_verification' receipt['phase'] = 'native_verification'
@ -307,10 +341,11 @@ def run(args, receipt):
def main(): def main():
parser = argparse.ArgumentParser(description=__doc__) parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('action', choices=['provision', 'verify', 'activate']) parser.add_argument('action', choices=['provision', 'verify', 'activate', 'resume-activate'])
parser.add_argument('--kubeconfig', required=True) parser.add_argument('--kubeconfig', required=True)
parser.add_argument('--receipt', required=True) parser.add_argument('--receipt', required=True)
parser.add_argument('--recovery') parser.add_argument('--recovery')
parser.add_argument('--prior-receipt')
args = parser.parse_args() args = parser.parse_args()
receipt = {'schema': 'platform.keycape-approval-custody.v1', 'status': 'failed', 'lanes': [], receipt = {'schema': 'platform.keycape-approval-custody.v1', 'status': 'failed', 'lanes': [],
'started_at': datetime.now(timezone.utc).isoformat(), 'credential_values_emitted': False, 'started_at': datetime.now(timezone.utc).isoformat(), 'credential_values_emitted': False,

View file

@ -1,6 +1,8 @@
"""Opt-in local OpenBao exercise; no production API or credential helper used.""" """Opt-in local OpenBao exercise; no production API or credential helper used."""
import importlib.util import importlib.util
import json import json
import copy
import tempfile
import os import os
from pathlib import Path from pathlib import Path
import secrets import secrets
@ -78,6 +80,7 @@ class CustodyExercise(unittest.TestCase):
self.assertTrue(values[0] != values[1]) self.assertTrue(values[0] != values[1])
self.assertTrue(all(x['kv_version'] == 1 for x in receipt['lanes'])) self.assertTrue(all(x['kv_version'] == 1 for x in receipt['lanes']))
self.assertFalse(any(value in json.dumps(receipt) for value in values)) self.assertFalse(any(value in json.dumps(receipt) for value in values))
type(self).initial_receipt = copy.deepcopy(receipt)
def test_02_retry_refuses_existing_custody(self): def test_02_retry_refuses_existing_custody(self):
with self.assertRaisesRegex(lane.LaneError, 'existing_custody_requires'): with self.assertRaisesRegex(lane.LaneError, 'existing_custody_requires'):
@ -110,5 +113,26 @@ class CustodyExercise(unittest.TestCase):
with self.assertRaisesRegex(lane.LaneError, 'attended_warden_envelope_required'): with self.assertRaisesRegex(lane.LaneError, 'attended_warden_envelope_required'):
lane.run(None, {}) lane.run(None, {})
def test_07_resume_reattaches_readers_without_rewriting_values(self):
before = [lane.read_optional(x['metadata']) for x in self.lanes]
for item in self.lanes:
lane.bao(['write', 'auth/kubernetes/role/' + item['role'], '-'], payload=dict(item['role_payload'], policies=[]))
prior = dict(self.initial_receipt, status='failed', verifier_delivery_disabled_custody_versions_retained=True,
keycape={'compatible_pair_restored': True})
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'prior.json'; path.write_text(json.dumps(prior))
receipt = {'lanes': []}
lane.resume_provision(self.lanes, receipt, path)
self.assertTrue(receipt['custody_versions_unchanged'])
self.assertTrue(all(x['existing_version_reused'] and not x['custody_seeded'] for x in receipt['lanes']))
self.assertEqual(before, [lane.read_optional(x['metadata']) for x in self.lanes])
self.assertTrue(all(lane.role_matches(lane.read_optional('auth/kubernetes/role/' + x['role']), x) for x in self.lanes))
def test_08_resume_requires_completed_rollback(self):
with tempfile.TemporaryDirectory() as directory:
path = Path(directory) / 'prior.json'; path.write_text(json.dumps({'status':'failed'}))
with self.assertRaisesRegex(lane.LaneError, 'verified_partial_activation_receipt_required'):
lane.resume_provision(self.lanes, {'lanes': []}, path)
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()

View file

@ -7,7 +7,7 @@ repo: railiance-platform
status: blocked status: blocked
owner: codex owner: codex
created: "2026-09-05" created: "2026-09-05"
updated: "2026-09-08" updated: "2026-09-09"
related: related:
- RPF-WP-0032 - RPF-WP-0032
- RPF-WP-0033 - RPF-WP-0033
@ -228,6 +228,18 @@ procedure is exercised and the admitted attended rollout is carried through.
Verifier-side scope, separate client-side/audit lanes and live acceptance remain Verifier-side scope, separate client-side/audit lanes and live acceptance remain
as defined in the reviewed requests. as defined in the reviewed requests.
2026-09-09 verifier-side return: both CCRs are verified after recorded user approval,
CAS=0 version-1 custody, native read/auth denials and revocation, Valid stores,
SecretSynced delivery, compatible KeyCape rollout and live positive/negative
service checks. Existing human OpenBao login passed again on the new image.
Receipt: `docs/evidence/2026-09-09-keycape-verifier-admission.json`. Failed attempts restored the compatible
configuration/image and detached delivery; final resume reused version 1.
T05 remains progress for the separately admitted client-side read lanes and
associated owner handoffs. Neither Warden fetch selector is resolvable through
these verifier-only CCRs. Do not re-request the completed two named reviews or
reseed these paths. Rotation is a distinct, version-guarded operation.
## Dependency review — 2026-09-06 ## Dependency review — 2026-09-06
SECRETS-WP-0008-T02 now records the local PIP claim/validation join implemented SECRETS-WP-0008-T02 now records the local PIP claim/validation join implemented