Finalize proposed CCR-2026-0033 and its attended custody child (source only).
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s

Correct the KV path into platform/workloads/, record secrets-engine consumer
coordinates (D1-D4 pending), rotation ownership, live order and rollback, and
add a decision-gated custody child with fake-only tests. Nothing executed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 132360@bnt-lap001
Assistant-Session: 769b1afd-4330-4c86-96b2-e510e26db42b
This commit is contained in:
codex 2026-10-06 13:46:12 +02:00
parent 01e0576132
commit df24a77482
9 changed files with 538 additions and 10 deletions

View file

@ -5,7 +5,7 @@ request_type: workload-kv-read
title: KeyCape secrets-engine-openbao issuer workload reader
status: proposed
created: '2026-10-04'
updated: '2026-10-04'
updated: '2026-10-06'
requester:
agent: key-cape
reason: KEY-WP-0037-T02 owner packet; proposed issuer-side read of separately approved
@ -26,7 +26,7 @@ target:
through sso-only ESO
openbao:
mount: platform
kv_path: platform/services/keycape/secrets-engine-openbao
kv_path: platform/workloads/keycape/secrets-engine-openbao
fields:
- CLIENT_SECRET
policy_name: workload-kv-read-keycape-secrets-engine-openbao-eso
@ -80,7 +80,7 @@ scaffold:
draft_only: true
consumer_repo: key-cape
owner_repo: railiance-platform
request_sha256: 380a5cbb6a5be887d77e7c9aa57eda18da8a7059568b2c5143e2555ed8ab9fdb
request_sha256: ade2e10b0f65341051b955900c68931bdd56fd3a81bf452ae81229cc2390ad63
catalog_request_sha256: null
state_hub:
workplan_id: RPF-WP-0035
@ -91,4 +91,80 @@ delivery:
target: sso/keycape-secrets-engine-openbao-client key client-secret; issuer-side
delivery only
review_bundle: docs/provisioning-windows/2026-10-04-1000/keycape
consumer_file_delivery: not selected; requires secrets-engine source acceptance
consumer_file_delivery: same-process attended custody child; no client-side OpenBao
read
boundary_decision:
id: 632a8893-0d6a-4591-a50b-40bbdb798f32
approves: custody boundary only (platform authoritative KV and least-privilege issuer
delivery; secrets-engine-owned mode-0600 consumer file outside every worktree)
does_not_approve: generation, KV write, fetch, delivery, registration or deployment
path_correction: 'Candidate path platform/services/keycape/secrets-engine-openbao
from the KeyCape packet is outside the platform-mount applier scope (WARDEN-WP-0028:
platform/workloads/ only). Corrected to platform/workloads/keycape/secrets-engine-openbao,
same field CLIENT_SECRET. Consumers bind to the sso Secret and the consumer file,
not the KV path.'
custody_child:
script: scripts/keycape_secrets_engine_openbao_custody.py
tests: tests/test_keycape_secrets_engine_openbao_custody.py
status: source-only; never executed
requires:
- --approval-decision <founder decision id for this exact custody child>; refused
when absent, when equal to the boundary decision, or not in the exact CCR review
- CCR status approved with platform-owner, key-cape-owner and net-kingdom-owner
approvals and bound_claims_confirmed true
- attended warden platform-admin envelope; no BAO_TOKEN/VAULT_TOKEN in environment
- umask 077, shell tracing off
generation: one secrets.token_urlsafe(48) value in process memory (64 chars); never
in argv, environment, stdout, stderr, logs, Git or the hub
authoritative_write: KV-v2 write with options.cas=0 (absent) or cas=<observed current
version> (rotation); value passed on bao stdin only; metadata-only preflight
consumer_write: parent 0700 created or verified (owner = effective user); O_CREAT|O_EXCL|O_WRONLY
temp file 0600 in the parent; write, fsync, rename, directory fsync; refused if
the KV write did not succeed
consumer_validation: secrets-engine service-auth check-secret-file --path <file>
--json; only ok/check names recorded
receipt: O_EXCL 0600 JSON with booleans, KV version, request id, CCR/decision ids,
source digest and check-secret-file pass/fail only
failure_cleanup: removes only its own exact temp file; a written KV version is retained
and recorded (recovery is a fresh version-guarded run, never a KV read)
retry: a prior failed receipt with kv_written and no consumer file permits one new
version with cas=<that version>; no import or reuse of the earlier value
consumer_copy:
owner: secrets-engine (SECRETS-WP-0012, commit a3897c2)
host: operator workstation (WSL2) - founder decision D1, recommended workstation
only
os_user: worsch - founder decision D2, recommended worsch
file: /home/worsch/.secrets-engine/keycape/secrets-engine-openbao.client-secret
mode: '0600 regular file, one token; parent /home/worsch/.secrets-engine/keycape
0700 worsch'
storage: persistent ext4 home - founder decision D3, recommended persistent and
excluded from backup tools
rotation_cadence: founder decision D4, recommended event-driven plus 90 days
decisions_pending: SECRETS-WP-0012-T02 (needs_human)
rotation_revocation:
generation_and_authoritative_version: railiance-platform (custody child, version-guarded)
issuer_pickup: key-cape / net-kingdom (ESO refresh then KeyCape restart)
consumer_copy_replacement: secrets-engine file, executed inside platform child after
issuer reload confirmed
disable_on_compromise: key-cape / net-kingdom remove registration first
retire_versions: railiance-platform per separate approval
remove_consumer_copy: secrets-engine operator
live_order:
- 1 founder approves this CCR (status approved, three reviews, bound claims confirmed)
and the exact custody-child decision; secrets-engine D1-D4 resolved
- 2 attended custody child (this repo) writes KV v1 and consumer file; receipt
- 3 platform applies issuer reader policy/role and sso-only ClusterSecretStore + ExternalSecret;
sso Secret keycape-secrets-engine-openbao-client/client-secret exists (metadata
receipt only)
- 4 net-kingdom env patch secrets-engine-openbao-client.proposed.patch.yaml (NK-WP-0046)
- 5 KeyCape registration merge (KEY-WP-0037-T03)
- 6 KeyCape restart (rollout helper)
- 7 KeyCape live issuance check (KEY-WP-0037-T04)
- 8 platform OpenBao JWT role/policy secrets-engine-openbao and login proof (RPF-WP-0035-T02)
rollback:
before_step_4: delete ExternalSecret (uid precondition), detach role policies; consumer
file removed by secrets-engine operator (exact file, then rmdir parent only if
empty); KV version retained pending separate destroy approval
after_step_4: net-kingdom rollout helper restores prior config/Deployment first,
then the before_step_4 sequence
cleanup_receipts: native, value-free, per step owner

View file

@ -22,7 +22,7 @@ target:
through sso-only ESO
openbao:
mount: platform
kv_path: platform/services/keycape/secrets-engine-openbao
kv_path: platform/workloads/keycape/secrets-engine-openbao
fields:
- CLIENT_SECRET
policy_name: workload-kv-read-keycape-secrets-engine-openbao-eso

View file

@ -44,8 +44,14 @@ the refusal. This is separate from actual provider registration/custody.
## Implemented platform issuer-reader preparation
The KeyCape packet's candidate path is accepted here for proposed source review:
`platform/services/keycape/secrets-engine-openbao`, field CLIENT_SECRET.
The KeyCape packet's candidate path `platform/services/keycape/secrets-engine-openbao`
is outside the platform-mount applier scope (`platform/workloads/` only) and was
corrected on 2026-10-06 to `platform/workloads/keycape/secrets-engine-openbao`,
field CLIENT_SECRET. Consumers bind to the sso Secret and the consumer file, not
the KV path. The attended custody child
`scripts/keycape_secrets_engine_openbao_custody.py` (source only, never run) and
the secrets-engine consumer copy (SECRETS-WP-0012, a3897c2) are recorded in
CCR-2026-0033 with the explicit live order.
Proposed CCR-2026-0033 and input
`docs/credential-lane-designs/keycape-secrets-engine-openbao-issuer-input.yaml`
declare only the issuer reader. Policy

View file

@ -50,5 +50,5 @@ spec:
data:
- secretKey: client-secret
remoteRef:
key: services/keycape/secrets-engine-openbao
key: workloads/keycape/secrets-engine-openbao
property: CLIENT_SECRET

View file

@ -5,7 +5,7 @@
"status": "not-executed",
"draft_only": true,
"verified": false,
"request_sha256": "380a5cbb6a5be887d77e7c9aa57eda18da8a7059568b2c5143e2555ed8ab9fdb",
"request_sha256": "ade2e10b0f65341051b955900c68931bdd56fd3a81bf452ae81229cc2390ad63",
"required_owner_checks": [
"Exact revision/digest and required owner reviews",
"Identity/auth claims confirmed separately; no inferred workload identity",

View file

@ -1,3 +1,3 @@
path "platform/data/services/keycape/secrets-engine-openbao" {
path "platform/data/workloads/keycape/secrets-engine-openbao" {
capabilities = ["read"]
}

View file

@ -0,0 +1,223 @@
#!/usr/bin/env python3
"""Attended custody child for CCR-2026-0033: KeyCape `secrets-engine-openbao` client secret.
SOURCE ONLY until the founder approves this exact child. One run generates one
value in process memory, compare-before-writes it as the authoritative KV-v2
version, writes the secrets-engine consumer copy in the same process, runs the
consumer's value-free acceptance check and writes a metadata-only receipt.
It never reads the KV value, never prints the value, never puts it in argv or
the environment, and does not touch policies, roles, ESO, KeyCape or the
cluster (later live steps, see CCR-2026-0033 live_order).
"""
from __future__ import annotations
import argparse
import hashlib
import importlib.util
import json
import os
from pathlib import Path
import re
import secrets
import stat
import sys
from datetime import datetime, timezone
sys.path.insert(0, str(Path(__file__).resolve().parent))
from state_hub_preflight_lane import ROOT, LaneError, bao, command, data # noqa: E402
CCR_ID = 'CCR-2026-0033'
CCR_PATH = ROOT / 'credential-change-requests/CCR-2026-0033.yaml'
BOUNDARY_DECISION = '632a8893-0d6a-4591-a50b-40bbdb798f32'
KV_PATH = 'platform/workloads/keycape/secrets-engine-openbao'
KV_DATA = 'platform/data/workloads/keycape/secrets-engine-openbao'
KV_METADATA = 'platform/metadata/workloads/keycape/secrets-engine-openbao'
FIELD = 'CLIENT_SECRET'
# secrets-engine coordinates (SECRETS-WP-0012, a3897c2); D1-D4 pending founder choice.
CONSUMER_FILE = Path('/home/worsch/.secrets-engine/keycape/secrets-engine-openbao.client-secret')
APPROVERS = ('platform-owner', 'key-cape-owner', 'net-kingdom-owner')
UUID_RE = re.compile(r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$')
def require(condition, reason):
if not condition:
raise LaneError(reason)
def attended_envelope():
return (Path.home().parent.name == '.warden-attended-login'
and not os.environ.get('BAO_TOKEN') and not os.environ.get('VAULT_TOKEN'))
def contract(decision_id):
require(isinstance(decision_id, str) and UUID_RE.match(decision_id), 'approval_decision_id_required')
require(decision_id != BOUNDARY_DECISION, 'boundary_decision_does_not_approve_custody_child')
spec = importlib.util.spec_from_file_location('credential_change', ROOT / 'scripts/credential-change.py')
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
ccr, errors, _ = module.validate_ccr(CCR_PATH)
require(not errors and ccr['id'] == CCR_ID, 'valid_ccr_required')
require(ccr['status'] == 'approved', 'approved_ccr_required')
comments = ccr['review']['comments']
for role in APPROVERS:
require(any(c.get('decision') == 'approved' and role in c.get('reviewer', '') for c in comments),
'all_named_reviews_required')
require(any(c.get('decision') == 'approved' and decision_id in str(c.get('comment', '')) for c in comments),
'custody_child_decision_not_recorded_in_ccr')
openbao = ccr['openbao']
require(openbao['kv_path'] == KV_PATH and openbao['fields'] == [FIELD]
and openbao['auth']['bound_claims_confirmed'] is True
and ccr['custody_child']['script'] == 'scripts/keycape_secrets_engine_openbao_custody.py'
and ccr['consumer_copy']['file'] == str(CONSUMER_FILE), 'exact_approved_contract_required')
return hashlib.sha256(CCR_PATH.read_bytes()).hexdigest()
def kv_current_version():
"""Metadata only. None when the path has never been written."""
result = bao(['read', '-format=json', KV_METADATA], allow_failure=True)
if result.returncode != 0:
require(b'404' in result.stderr or b'No value found' in result.stderr, 'absence_not_proven')
return None
meta = data(result)['data']
current = meta['current_version']
version = meta['versions'][str(current)]
require(not version.get('destroyed') and not version.get('deletion_time'), 'current_version_not_live')
return current
def write_kv(value, cas):
result = data(bao(['write', '-format=json', KV_DATA, '-'],
payload={'options': {'cas': cas}, 'data': {FIELD: value}}))
require(result['data']['version'] == cas + 1, 'unexpected_kv_version')
return result['data']['version'], result.get('request_id')
def fsync_dir(path):
fd = os.open(path, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(fd)
finally:
os.close(fd)
def prepare_parent(target):
parent = target.parent
grand = os.lstat(parent.parent)
require(stat.S_ISDIR(grand.st_mode), 'consumer_grandparent_invalid')
try:
info = os.lstat(parent)
except FileNotFoundError:
os.mkdir(parent, 0o700)
fsync_dir(parent.parent)
info = os.lstat(parent)
require(stat.S_ISDIR(info.st_mode) and info.st_uid == os.geteuid()
and stat.S_IMODE(info.st_mode) == 0o700, 'consumer_parent_must_be_owned_0700_directory')
require(not os.path.lexists(target), 'consumer_file_exists_rotation_needs_separate_approval')
def write_consumer(target, value, receipt):
parent = target.parent
temp = parent / ('.' + target.name + '.' + secrets.token_hex(8) + '.tmp')
fd = os.open(temp, os.O_CREAT | os.O_EXCL | os.O_WRONLY | os.O_NOFOLLOW, 0o600)
try:
try:
os.write(fd, (value + '\n').encode())
os.fsync(fd)
finally:
os.close(fd)
os.rename(temp, target)
fsync_dir(parent)
except BaseException:
try:
os.unlink(temp)
receipt['consumer_temp_removed'] = True
except FileNotFoundError:
pass
raise
info = os.lstat(target)
require(stat.S_ISREG(info.st_mode) and stat.S_IMODE(info.st_mode) == 0o600
and info.st_uid == os.geteuid(), 'consumer_file_readback_failed')
def check_consumer(target, secrets_engine):
result = command([secrets_engine, 'service-auth', 'check-secret-file', '--path', str(target), '--json'],
allow_failure=True)
try:
ok = bool(json.loads(result.stdout).get('ok'))
except (ValueError, AttributeError):
ok = False
return result.returncode == 0 and ok
def retry_cas(prior_path, current):
prior = json.loads(Path(prior_path).read_text())
require(prior.get('ccr') == CCR_ID and prior.get('status') == 'failed' and prior.get('kv_written')
and not prior.get('consumer_written'), 'retryable_partial_receipt_required')
require(current is not None and prior.get('kv_version') == current, 'kv_version_changed_since_prior_receipt')
return current
def run(args, receipt):
receipt['approval_decision_id'] = args.approval_decision
receipt['ccr_sha256'] = contract(args.approval_decision)
require(attended_envelope(), 'attended_warden_envelope_required')
os.umask(0o077)
identity = data(bao(['token', 'lookup', '-format=json']))['data']
require('platform-admin' in identity['policies'] and 'root' not in identity['policies'],
'attended_platform_admin_required')
current = kv_current_version()
if args.prior_receipt:
cas = retry_cas(args.prior_receipt, current)
receipt['retry_of_kv_version'] = cas
else:
require(current is None, 'existing_custody_requires_retry_receipt_or_rotation_approval')
cas = 0
prepare_parent(CONSUMER_FILE)
receipt['phase'] = 'kv_write'
value = secrets.token_urlsafe(48)
try:
version, request_id = write_kv(value, cas)
receipt.update(kv_written=True, kv_version=version, kv_request_id=request_id, kv_cas=cas)
receipt['phase'] = 'consumer_write'
write_consumer(CONSUMER_FILE, value, receipt)
receipt['consumer_written'] = True
finally:
value = None
receipt['phase'] = 'consumer_check'
receipt['consumer_check_ok'] = check_consumer(CONSUMER_FILE, args.secrets_engine)
require(receipt['consumer_check_ok'], 'consumer_check_failed')
receipt['status'] = 'custody_written_pending_issuer_delivery'
receipt['phase'] = 'done'
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument('action', choices=['provision'])
parser.add_argument('--approval-decision', required=True,
help='founder decision id approving this exact custody child')
parser.add_argument('--receipt', required=True)
parser.add_argument('--prior-receipt', help='failed receipt with kv_written and no consumer file')
parser.add_argument('--secrets-engine', default='secrets-engine', help='secrets-engine executable')
args = parser.parse_args(argv)
receipt = {'schema': 'platform.keycape-secrets-engine-openbao-custody.v1', 'ccr': CCR_ID,
'status': 'failed', 'phase': 'preflight',
'started_at': datetime.now(timezone.utc).isoformat(),
'credential_values_emitted': False, 'client_side_read_admitted': False,
'kv_written': False, 'consumer_written': False}
fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
try:
run(args, receipt)
except Exception as exc:
receipt['status'] = 'failed'
receipt['error'] = str(exc) if isinstance(exc, LaneError) else 'contained_operation_failed'
finally:
receipt['finished_at'] = datetime.now(timezone.utc).isoformat()
with os.fdopen(fd, 'w') as out:
json.dump(receipt, out, indent=2)
out.write('\n')
return 0 if receipt['status'] != 'failed' else 1
if __name__ == '__main__':
raise SystemExit(main())

View file

@ -0,0 +1,194 @@
"""CCR-2026-0033 custody child: synthetic values and fakes only; no OpenBao, cluster or real file."""
import contextlib
import io
import json
import os
from pathlib import Path
import sys
import tempfile
from types import SimpleNamespace
import unittest
from unittest.mock import patch
import yaml
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'scripts'))
import keycape_secrets_engine_openbao_custody as child # noqa: E402
DECISION = '00000000-0000-4000-8000-000000000033'
SYNTHETIC = 'synthetic-test-value-' + 'x' * 43
def result(code=0, stdout=b'', stderr=b''):
return SimpleNamespace(returncode=code, stdout=stdout, stderr=stderr)
class FakeBao:
def __init__(self, current=None, fail_write=False):
self.current, self.fail_write, self.calls, self.payloads = current, fail_write, [], []
def __call__(self, args, *, payload=None, token=None, allow_failure=False):
self.calls.append(list(args))
if args[:2] == ['token', 'lookup']:
return result(stdout=json.dumps({'data': {'policies': ['default', 'platform-admin']}}).encode())
if args[0] == 'read' and args[-1] == child.KV_METADATA:
if self.current is None:
return result(2, stderr=b'No value found at ' + child.KV_METADATA.encode())
return result(stdout=json.dumps({'data': {'current_version': self.current,
'versions': {str(self.current): {'destroyed': False, 'deletion_time': ''}}}}).encode())
if args[0] == 'write' and args[2] == child.KV_DATA:
self.payloads.append(payload)
if self.fail_write or payload['options']['cas'] != (self.current or 0):
raise child.LaneError('command_failed')
self.current = (self.current or 0) + 1
return result(stdout=json.dumps({'request_id': 'req-1', 'data': {'version': self.current}}).encode())
raise AssertionError('unexpected bao call: ' + ' '.join(args[:2]))
class CustodyChild(unittest.TestCase):
def setUp(self):
self.tmp = tempfile.TemporaryDirectory()
base = Path(self.tmp.name)
os.chmod(base, 0o755)
self.consumer = base / 'home/.secrets-engine/keycape/secrets-engine-openbao.client-secret'
self.consumer.parent.parent.mkdir(parents=True)
ccr = yaml.safe_load((ROOT / 'credential-change-requests/CCR-2026-0033.yaml').read_text())
ccr['status'] = 'approved'
ccr['openbao']['auth']['bound_claims_confirmed'] = True
ccr['consumer_copy']['file'] = str(self.consumer)
ccr['review']['comments'] = [
{'reviewer': r, 'decision': 'approved', 'date': '2026-10-06',
'comment': 'custody child approved by decision ' + DECISION} for r in child.APPROVERS]
self.ccr = base / 'CCR-2026-0033.yaml'
self.ccr.write_text(yaml.safe_dump(ccr, sort_keys=False))
checker = base / 'check.py'
checker.write_text('#!' + sys.executable + '\nimport json,os,stat,sys\np=sys.argv[sys.argv.index("--path")+1]\n'
'ok=os.path.isfile(p) and stat.S_IMODE(os.stat(p).st_mode)==0o600\n'
'print(json.dumps({"ok":ok}))\nsys.exit(0 if ok else 1)\n')
checker.chmod(0o700)
self.checker = str(checker)
self.bao = FakeBao()
self.patches = [patch.object(child, 'CCR_PATH', self.ccr), patch.object(child, 'CONSUMER_FILE', self.consumer),
patch.object(child, 'bao', self.bao), patch.object(child, 'attended_envelope', lambda: True),
patch.object(child.secrets, 'token_urlsafe', lambda n: SYNTHETIC)]
for p in self.patches:
p.start()
self.umask = os.umask(0o022)
def tearDown(self):
os.umask(self.umask)
for p in reversed(self.patches):
p.stop()
self.tmp.cleanup()
def run_child(self, *extra, decision=DECISION):
receipt = Path(self.tmp.name) / ('receipt-%d.json' % len(list(Path(self.tmp.name).glob('receipt-*'))))
out, err = io.StringIO(), io.StringIO()
argv = ['provision', '--receipt', str(receipt), '--secrets-engine', self.checker, *extra]
if decision is not None:
argv += ['--approval-decision', decision]
with contextlib.redirect_stdout(out), contextlib.redirect_stderr(err):
code = child.main(argv)
text = receipt.read_text()
for blob in (text, out.getvalue(), err.getvalue(), json.dumps(self.bao.calls)):
self.assertNotIn(SYNTHETIC, blob)
self.assertEqual(oct(receipt.stat().st_mode & 0o777), oct(0o600))
return code, json.loads(text), receipt
def test_refuses_without_or_with_wrong_decision(self):
with self.assertRaises(SystemExit), contextlib.redirect_stderr(io.StringIO()):
child.main(['provision', '--receipt', str(Path(self.tmp.name) / 'r.json')])
for decision, reason in (('not-a-uuid', 'approval_decision_id_required'),
(child.BOUNDARY_DECISION, 'boundary_decision_does_not_approve_custody_child'),
('11111111-1111-4111-8111-111111111111', 'custody_child_decision_not_recorded_in_ccr')):
code, receipt, _ = self.run_child(decision=decision)
self.assertEqual((code, receipt['error']), (1, reason))
self.assertEqual(self.bao.calls, [])
def test_committed_proposed_ccr_is_refused(self):
with patch.object(child, 'CCR_PATH', ROOT / 'credential-change-requests/CCR-2026-0033.yaml'):
code, receipt, _ = self.run_child()
self.assertEqual((code, receipt['error']), (1, 'approved_ccr_required'))
self.assertEqual(self.bao.calls, [])
def test_refuses_outside_attended_envelope(self):
with patch.object(child, 'attended_envelope', lambda: False):
code, receipt, _ = self.run_child()
self.assertEqual(receipt['error'], 'attended_warden_envelope_required')
self.assertEqual(self.bao.calls, [])
def test_provision_writes_kv_and_consumer_in_one_process(self):
code, receipt, _ = self.run_child()
self.assertEqual(code, 0)
self.assertEqual(receipt['status'], 'custody_written_pending_issuer_delivery')
self.assertEqual((receipt['kv_version'], receipt['kv_cas']), (1, 0))
self.assertTrue(receipt['consumer_written'] and receipt['consumer_check_ok'])
self.assertEqual(self.bao.payloads, [{'options': {'cas': 0}, 'data': {'CLIENT_SECRET': SYNTHETIC}}])
self.assertNotIn(['read', '-format=json', child.KV_DATA], self.bao.calls)
self.assertEqual(self.consumer.read_text(), SYNTHETIC + '\n')
self.assertEqual(self.consumer.stat().st_mode & 0o777, 0o600)
self.assertEqual(self.consumer.parent.stat().st_mode & 0o777, 0o700)
self.assertEqual(sorted(p.name for p in self.consumer.parent.iterdir()), [self.consumer.name])
def test_existing_custody_or_consumer_file_is_refused(self):
self.bao.current = 1
code, receipt, _ = self.run_child()
self.assertEqual(receipt['error'], 'existing_custody_requires_retry_receipt_or_rotation_approval')
self.bao.current = None
self.consumer.parent.mkdir(mode=0o700)
self.consumer.write_text('old\n')
code, receipt, _ = self.run_child()
self.assertEqual(receipt['error'], 'consumer_file_exists_rotation_needs_separate_approval')
self.assertEqual(self.bao.payloads, [])
def test_shared_parent_is_refused(self):
self.consumer.parent.mkdir(mode=0o755)
os.chmod(self.consumer.parent, 0o755)
code, receipt, _ = self.run_child()
self.assertEqual(receipt['error'], 'consumer_parent_must_be_owned_0700_directory')
self.assertEqual(self.bao.payloads, [])
def test_failed_kv_write_writes_no_consumer_file(self):
self.bao.fail_write = True
code, receipt, _ = self.run_child()
self.assertEqual((code, receipt['kv_written'], receipt['consumer_written']), (1, False, False))
self.assertFalse(self.consumer.exists())
self.assertEqual(list(self.consumer.parent.iterdir()), [])
def test_consumer_failure_cleans_temp_and_retry_preserves_version(self):
with patch.object(child.os, 'rename', side_effect=OSError('synthetic')):
code, first, path = self.run_child()
self.assertEqual((code, first['kv_written'], first['kv_version'], first['consumer_written']), (1, True, 1, False))
self.assertTrue(first['consumer_temp_removed'])
self.assertEqual(list(self.consumer.parent.iterdir()), [])
code, receipt, _ = self.run_child()
self.assertEqual(receipt['error'], 'existing_custody_requires_retry_receipt_or_rotation_approval')
code, second, _ = self.run_child('--prior-receipt', str(path))
self.assertEqual((code, second['kv_version'], second['kv_cas'], second['retry_of_kv_version']), (0, 2, 1, 1))
self.assertEqual([p['options']['cas'] for p in self.bao.payloads], [0, 1])
self.bao.current = 3
Path(self.consumer).unlink()
code, third, _ = self.run_child('--prior-receipt', str(path))
self.assertEqual(third['error'], 'kv_version_changed_since_prior_receipt')
def test_failed_consumer_check_fails_receipt(self):
bad = Path(self.tmp.name) / 'bad.py'
bad.write_text('#!' + sys.executable + '\nimport sys\nprint(\'{"ok": false}\')\nsys.exit(1)\n')
bad.chmod(0o700)
self.checker = str(bad)
code, receipt, _ = self.run_child()
self.assertEqual((code, receipt['consumer_written'], receipt['consumer_check_ok'], receipt['error']),
(1, True, False, 'consumer_check_failed'))
def test_receipt_contains_only_metadata_keys(self):
_, receipt, _ = self.run_child()
self.assertTrue(set(receipt) <= {
'schema', 'ccr', 'status', 'phase', 'started_at', 'finished_at', 'credential_values_emitted',
'client_side_read_admitted', 'kv_written', 'consumer_written', 'approval_decision_id', 'ccr_sha256',
'kv_version', 'kv_request_id', 'kv_cas', 'consumer_check_ok', 'retry_of_kv_version',
'consumer_temp_removed', 'error'})
if __name__ == '__main__':
unittest.main()

View file

@ -96,6 +96,16 @@ remains pending its scoped historical identity repair. Requirements and acceptan
boundaries: `docs/provisioning-windows/2026-10-04-1000/dependency-requirements.md`.
This workplan was already blocked and remains blocked on its open owner/live gates.
2026-10-06: secrets-engine returned consumer custody source acceptance
(message 680136e2, SECRETS-WP-0012, commit a3897c2). CCR-2026-0033 finalized as
**proposed**: KV path corrected to `platform/workloads/keycape/secrets-engine-openbao`
(applier scope), consumer copy coordinates with D1-D4 pending founder choice,
rotation/revocation ownership, live order and rollback. Attended custody child
`scripts/keycape_secrets_engine_openbao_custody.py` with fake-only tests
(`tests/test_keycape_secrets_engine_openbao_custody.py`) refuses without an exact
founder decision id recorded in the approved CCR. Nothing executed, generated or
applied; nothing added to active GitOps. The founder approval it needs is T09.
## Implement the platform operator-write CCR contract and Fluid lane
```task
@ -751,3 +761,22 @@ neither source reconciliation nor the historical pilot is that evidence.
## Loose-end review — 2026-09-27
T08 now explicitly waits for formerly-valid audit-bearer revocation acceptance from AUDIT-WP-0009-T09/T11. Native delivery, producer checks and independent readback already passed; do not reopen login, seed or review gates. T02/T03/T06/T07 retain their latest exact service registration, signed-in human acceptance, owner delivery/disposition and governed-consumer migration dependencies. No credential value was read or changed.
## Founder approval of the CCR-2026-0033 custody child
```task
id: RPF-WP-0035-T09
status: wait
needs_human: true
priority: high
depends_on: [SECRETS-WP-0012-T02]
blocking_reason: "Founder must approve proposed CCR-2026-0033 (platform-owner, key-cape-owner, net-kingdom-owner reviews; bound claims confirmed) and the exact attended custody child run with a decision id recorded in the CCR review. Boundary decision 632a8893 does not cover generation, write or delivery. secrets-engine D1-D4 resolve first."
```
Packet: `credential-change-requests/CCR-2026-0033.yaml` (custody_child,
consumer_copy, live_order, rollback). Run after approval, attended:
`python3 scripts/keycape_secrets_engine_openbao_custody.py provision
--approval-decision <id> --receipt <new path>` inside the warden platform-admin
envelope. Later live steps (ESO, NetKingdom patch, registration, restart,
KEY-WP-0037-T04 issuance, T02 JWT login proof) each need their own go.