CUST-WP-0075: tenant-engine sender seed helper; re-qualify RPF-WP-0035-T02/T08
Some checks failed
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Has been cancelled

- scripts/tenant_engine_audit_core_sender_seed.py: silent attended row-7 seed
  for MASON-WP-0007-T05 (wrap-migrate technique, CAS 0, never overwrites,
  metadata-only --dry-run, documented exit codes), with offline fake-bao tests.
- Lane review doc with the exact founder sequence and the read-only ESO
  token-create can-i check for MASON-WP-0007-T06.
- RPF-WP-0035-T02: SECRETS-WP-0007-T04 is done; real blocker is live KeyCape
  registration and client custody of secrets-engine-openbao (blocked_on
  key-cape). Design stays proposed.
- RPF-WP-0035-T08: receiver rollout with evidence_kind already accepted
  2026-09-11; moved the misplaced D1 correction note here from T02.

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

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 3775@bnt-lap001
Assistant-Session: cfacb6a5-ad5b-4f79-ac67-053e3b0d34a3
This commit is contained in:
codex 2026-10-03 08:28:32 +02:00
parent 401765f184
commit e7eb9db57e
6 changed files with 512 additions and 3 deletions

View file

@ -120,3 +120,12 @@ then perform the positive/negative/expiry/use-limit/revocation checks above.
Do not print login responses. Publish the consumer contract only after those
checks pass. A conflicting existing mount/role requires review, not overwrite.
No service-client secret is created, moved or requested by this source return.
## Gate check — 2026-10-03 (CUST-WP-0075)
Still proposed. SECRETS-WP-0007-T04 is done (2026-09-27), so the gate named
above is open, but the "before writing" preconditions are not: KeyCape has not
claimed a live registration of `secrets-engine-openbao`, and its client-credential
custody has no owning task (KeyCape contract: "a separate approved custody
action"). No reviewed child is written. RPF-WP-0035-T02 waits on key-cape naming
that task.

View file

@ -0,0 +1,93 @@
# tenant-engine audit-core sender: attended seed
Status: ready for review, not run. Owner: railiance-platform (OpenBao side) for
ops-mason MASON-WP-0007-T05 (plan row 7). Intake RPF-IN-0002. Prepared
2026-10-03 under CUST-WP-0075.
## What the founder approves
One attended sitting, run from a railiance-platform checkout at the reviewed
commit, with these three children in order. Each child prints nothing; read
warden's printed line, not its exit code (orientation §5).
```sh
cd ~/railiance-platform
export BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200
# 1. policy + Kubernetes-auth role (commit 8c44256). Exit 3 = existing object differs.
warden access openbao-platform-admin-login --exec -- \
sh scripts/openbao-tenant-engine-audit-core-sender-eso-attended.sh
# 2. seed dry-run: KV metadata only, no value read or written.
warden access openbao-platform-admin-login --exec -- \
python3 scripts/tenant_engine_audit_core_sender_seed.py --dry-run \
--confirm 'SEED tenant-engine audit-core-sender' \
--receipt docs/evidence/tenant-engine-sender-seed-dry-run.json
# 3. seed: only if step 2's receipt says outcome dry_run_would_write (exit 0).
warden access openbao-platform-admin-login --exec -- \
python3 scripts/tenant_engine_audit_core_sender_seed.py \
--confirm 'SEED tenant-engine audit-core-sender' \
--receipt docs/evidence/tenant-engine-sender-seed.json
```
Each `warden access` is its own login. Receipts are created mode 0600, refused
if the file exists, and hold only paths, versions, booleans and an outcome
code. Commit them as evidence.
## What the seed does
`scripts/tenant_engine_audit_core_sender_seed.py` is the user-engine /
audit-core wrap-migrate technique (ops-mason
`plans/user-engine-openbao-runtime-custody.md` §6; audit-core runbook,
"wrap-migrated on 2026-08-14"): one platform-admin process reads the source and
writes the target, and the value travels only through process memory and the
`bao` child's stdin. Never argv, stdout, stderr, a file or the receipt.
1. Refuses unless `--confirm` is exact, HOME is a warden attended envelope, no
`BAO_TOKEN`/`VAULT_TOKEN` is set, and the identity has `platform-admin` and
not `root`.
2. Reads `platform/workloads/audit-core/senders` (KV v2, field `senders.json`)
and selects the one row named `tenant-engine`. Refuses if the row is
missing or duplicated, holds zero or more than one token (a rotation
overlap is in progress: finish it first), or is not write-only
(`may_write` true, `may_read` false).
3. If `platform/workloads/tenant-engine/audit-core-sender` exists: exit 0 when
it holds exactly `{token: <that token>}`, otherwise exit 3. Never overwrites.
4. Otherwise writes `{token}` with `cas=0`, reads it back (must be version 1 and
equal), and checks that the registry version did not move during the run.
The registry is never written. Exit codes: 0 ok (written or already
identical; dry-run: would write), 1 contained internal error, 2 precondition
refused before any write, 3 target exists and differs, 4 write failed,
5 verify failed, 6 dry-run found the target already present.
Tests: `tests/test_tenant_engine_audit_core_sender_seed.py` runs the helper as
a subprocess against a fake `bao` and asserts empty stdout/stderr, the token
never in `bao` argv or the receipt, CAS 0, idempotence, refusal of a differing
or deleted target, every precondition, and distinct write/verify codes.
## Operator check for MASON-WP-0007-T06 (read-only, not run)
Before the tenant-engine ClusterSecretStore goes live, the ESO controller must
be allowed to create ServiceAccount tokens in `tenant-engine`. Confirm the node
IP is `92.205.62.239` first (`kubectl get nodes -o wide`), then:
```sh
kubectl auth can-i create serviceaccounts --subresource=token \
-n tenant-engine --as=system:serviceaccount:external-secrets:external-secrets
```
`yes` is required. This is a SubjectAccessReview: it reads no object. A `no`
is a railiance-cluster RBAC matter, not a reason to widen the OpenBao role.
## Follow-ups outside the seed
- `openbao/policies/agent-high-risk-boundary.hcl` denies coding agents the
sender registry, but not the new
`platform/{data,metadata}/workloads/tenant-engine/audit-core-sender` paths.
Adding those deny lines is a separate reviewed policy change.
- Rotation stays overlap-first (plan §4): rotate in the registry, finish the
overlap so the sender holds one token, then re-seed. Because this helper
never overwrites, a rotated token needs a reviewed CAS-update variant. That
variant is not built.

View file

@ -48,6 +48,9 @@ description: >-
audience openbao, 15m ttl/max/explicit max, no default policy). Not applied.
Open: attended apply, then the founder-attended KV seed (plan row 7).
No RBAC change needed here (see message).
2026-10-03 (CUST-WP-0075): row-7 seed helper scripts/tenant_engine_audit_core_sender_seed.py
(silent, CAS 0, never overwrites, metadata-only --dry-run) and lane review
docs/credential-lane-designs/tenant-engine-audit-core-sender-seed.md added; not run.
state_hub_intake_id: "01a0fedd-7af5-7126-a263-647d438b068d"
```

View file

@ -0,0 +1,216 @@
#!/usr/bin/env python3
"""MASON-WP-0007-T05 row 7: attended, silent seed of tenant-engine's sender token.
Copies the one token of sender ``tenant-engine`` from the audit-core sender
registry (KV v2 ``platform/workloads/audit-core/senders``, field
``senders.json``) to the dedicated KV v2 path
``platform/workloads/tenant-engine/audit-core-sender`` (one field ``token``),
which the ESO role ``tenant-engine-audit-core-sender-eso`` reads. This is the
user-engine / audit-core wrap-migrate technique: one platform-admin process
reads the source and writes the target; the value only ever travels through
this process's memory and the ``bao`` child's stdin, never argv, stdout,
stderr, a file or the receipt.
Run from a railiance-platform checkout at the reviewed commit, after
``scripts/openbao-tenant-engine-audit-core-sender-eso-attended.sh``:
BAO_ADDR=http://127.0.0.1:18200 VAULT_ADDR=http://127.0.0.1:18200 \\
warden access openbao-platform-admin-login --exec -- \\
python3 scripts/tenant_engine_audit_core_sender_seed.py \\
--confirm 'SEED tenant-engine audit-core-sender' \\
--receipt "$HOME/te-sender-seed-receipt.json"
Add ``--dry-run`` to touch only KV metadata (no value is read or written).
The process prints nothing: stdout and stderr are redirected to /dev/null at
start (warden fails closed on any child output). The optional ``--receipt``
(created mode 0600, refused if it already exists) holds only versions,
booleans and a fixed outcome code.
Exit codes:
0 seed: written at version 1, or target already holds exactly this token
dry-run: target absent, registry present; a seed would write
1 unexpected internal error (contained; nothing is printed)
2 precondition refused before any write: missing/wrong --confirm, not in a
warden attended envelope, identity not platform-admin (or is root),
registry absent or malformed, sender missing, sender holding zero or more
than one token (rotation overlap in progress), sender read-capable,
absence not provable, receipt path exists
3 target exists and differs (other value, extra fields, deleted/destroyed
current version); never overwritten
4 write failed (CAS 0 refused or bao error)
5 verify failed: readback differs or registry changed during the run
6 dry-run only: target already exists (a seed run compares the value)
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import subprocess
import sys
SOURCE = 'workloads/audit-core/senders'
TARGET = 'workloads/tenant-engine/audit-core-sender'
SENDER = 'tenant-engine'
FIELD = 'token'
CONFIRM = 'SEED tenant-engine audit-core-sender'
SCHEMA = 'platform.tenant-engine-audit-core-sender-seed.v1'
class Exit(Exception):
def __init__(self, code: int, reason: str):
super().__init__(reason)
self.code = code
self.reason = reason
def bao(args, payload=None):
# Output is always captured; nothing from bao reaches the terminal.
return subprocess.run(['bao', *args],
input=None if payload is None else json.dumps(payload).encode(),
capture_output=True, timeout=60)
def read_json(path, *, absent_ok, code=2):
"""Return parsed ``data`` of a read, None if provably absent, else refuse."""
result = bao(['read', '-format=json', path])
if result.returncode == 0:
try:
return json.loads(result.stdout)['data']
except (ValueError, KeyError, TypeError):
raise Exit(code, 'unparseable_read') from None
if absent_ok and (b'404' in result.stderr or b'No value found' in result.stderr):
return None
raise Exit(code, 'read_failed_or_absence_not_proven')
def live_version(meta):
"""Current version number if it is live, else None (deleted/destroyed)."""
cv = meta.get('current_version')
if type(cv) is not int or cv < 1:
return None
entry = (meta.get('versions') or {}).get(str(cv)) or {}
if entry.get('destroyed') or entry.get('deletion_time'):
return None
return cv
def registry_token():
raw = read_json('platform/data/' + SOURCE, absent_ok=False)
try:
version = raw['metadata']['version']
rows = json.loads(raw['data']['senders.json'])
except (KeyError, TypeError, ValueError):
raise Exit(2, 'registry_shape_invalid') from None
if type(version) is not int or not isinstance(rows, list):
raise Exit(2, 'registry_shape_invalid')
match = [r for r in rows if isinstance(r, dict) and r.get('name') == SENDER]
if len(match) != 1:
raise Exit(2, 'sender_missing_or_duplicate')
row = match[0]
tokens = row.get('tokens') or ([row['token']] if row.get('token') else [])
if not isinstance(tokens, list) or len(tokens) != 1:
raise Exit(2, 'sender_token_count_not_one')
token = tokens[0]
if not isinstance(token, str) or not token or token != token.strip():
raise Exit(2, 'sender_token_shape')
if row.get('may_read') is not False or row.get('may_write') is not True:
raise Exit(2, 'sender_not_write_only')
return version, token
def registry_version():
meta = read_json('platform/metadata/' + SOURCE, absent_ok=True)
if meta is None or live_version(meta) is None:
raise Exit(2, 'registry_absent')
return meta['current_version']
def run(args, receipt):
if args.confirm != CONFIRM:
raise Exit(2, 'exact_confirmation_required')
if (Path.home().parent.name != '.warden-attended-login'
or os.environ.get('BAO_TOKEN') or os.environ.get('VAULT_TOKEN')):
raise Exit(2, 'attended_warden_envelope_required')
ident = read_json('auth/token/lookup-self', absent_ok=False)
policies = ident.get('policies') or []
if 'platform-admin' not in policies or 'root' in policies:
raise Exit(2, 'attended_platform_admin_required')
receipt['registry_version_before'] = registry_version()
target_meta = read_json('platform/metadata/' + TARGET, absent_ok=True)
receipt['target_present_before'] = target_meta is not None
if args.dry_run:
receipt['value_reads'] = 0
if target_meta is not None:
receipt['target_version_before'] = target_meta.get('current_version')
raise Exit(6, 'dry_run_target_present')
return 'dry_run_would_write'
version, token = registry_token()
if version != receipt['registry_version_before']:
raise Exit(5, 'registry_changed_during_run')
if target_meta is not None:
if live_version(target_meta) is None:
raise Exit(3, 'target_current_version_not_live')
current = read_json('platform/data/' + TARGET, absent_ok=False)
if (current or {}).get('data') != {FIELD: token}:
raise Exit(3, 'target_differs')
receipt['target_version'] = target_meta['current_version']
return 'already_identical'
result = bao(['write', '-format=json', 'platform/data/' + TARGET, '-'],
payload={'options': {'cas': 0}, 'data': {FIELD: token}})
if result.returncode != 0:
raise Exit(4, 'write_failed')
back = read_json('platform/data/' + TARGET, absent_ok=False, code=5)
if (back or {}).get('data') != {FIELD: token} or back['metadata'].get('version') != 1:
raise Exit(5, 'readback_mismatch')
if registry_version() != version:
raise Exit(5, 'registry_changed_during_run')
receipt['target_version'] = 1
return 'written'
def main(argv=None):
devnull = os.open(os.devnull, os.O_WRONLY)
os.dup2(devnull, 1)
os.dup2(devnull, 2)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--confirm', default='')
parser.add_argument('--receipt', type=Path)
parser.add_argument('--dry-run', action='store_true')
try:
args = parser.parse_args(argv)
except SystemExit:
return 2
receipt = {'schema': SCHEMA, 'mode': 'dry-run' if args.dry_run else 'seed',
'source': 'platform/' + SOURCE, 'target': 'platform/' + TARGET,
'sender': SENDER, 'credential_values_emitted': False}
fd = None
if args.receipt is not None:
try:
fd = os.open(args.receipt, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
except OSError:
return 2
try:
receipt['outcome'] = run(args, receipt)
code = 0
except Exit as exc:
receipt['outcome'] = exc.reason
code = exc.code
except Exception:
receipt['outcome'] = 'contained_internal_error'
code = 1
receipt['exit_code'] = code
if fd is not None:
os.write(fd, (json.dumps(receipt, indent=2, sort_keys=True) + '\n').encode())
os.close(fd)
return code
if __name__ == '__main__':
sys.exit(main())

View file

@ -0,0 +1,183 @@
"""Offline tests for the tenant-engine sender seed helper against a fake ``bao``.
The helper runs as a real subprocess with a fake ``bao`` first on PATH, so the
silence contract (empty stdout and stderr) is tested end to end.
"""
import json
import os
from pathlib import Path
import subprocess
import sys
import tempfile
import textwrap
import unittest
ROOT = Path(__file__).resolve().parents[1]
SCRIPT = ROOT / 'scripts/tenant_engine_audit_core_sender_seed.py'
CONFIRM = 'SEED tenant-engine audit-core-sender'
TOKEN = 'te-' + 'A1b2' * 12
OTHER = 'op-' + 'Z9y8' * 12
SRC = 'workloads/audit-core/senders'
DST = 'workloads/tenant-engine/audit-core-sender'
FAKE_BAO = textwrap.dedent('''\
#!/usr/bin/env python3
import json, os, sys
sf = os.environ['FAKE_BAO_STATE']
st = json.load(open(sf))
st['calls'].append(sys.argv[1:])
def save(): json.dump(st, open(sf, 'w'))
def out(obj): save(); print(json.dumps(obj)); sys.exit(0)
def missing(): save(); sys.stderr.write('No value found at x\\n'); sys.exit(2)
a = sys.argv[1:]
print('Success! noise', file=sys.stderr)
if a[0] == 'read':
p = a[-1]
if p == 'auth/token/lookup-self':
out({'data': st['identity']})
mount, kind, rest = p.split('/', 2)
e = st['kv'].get(rest)
if not e: missing()
cv = len(e['versions'])
if kind == 'metadata':
out({'data': {'current_version': cv, 'versions': {str(i + 1): v['meta'] for i, v in enumerate(e['versions'])}}})
v = e['versions'][-1]
if v['meta'].get('deletion_time'): missing()
out({'data': {'data': v['data'], 'metadata': {'version': cv}}})
if a[0] == 'write':
p = a[-2].split('/', 2)[2]
body = json.loads(sys.stdin.read())
st['stdin'].append(body)
if st.get('fail_write'): save(); sys.exit(2)
e = st['kv'].setdefault(p, {'versions': []})
if body['options']['cas'] != len(e['versions']): save(); sys.exit(2)
e['versions'].append({'data': body['data'], 'meta': {}})
if st.get('corrupt_after_write'): e['versions'][-1]['data'] = {'token': 'x'}
out({'data': {'version': len(e['versions'])}})
save(); sys.exit(9)
''')
def registry(rows):
return {'versions': [{'data': {'senders.json': json.dumps(rows), 'other': 'keep'}, 'meta': {}}] * 9}
def sender(name, tokens, may_read=False):
return {'name': name, 'tokens': tokens, 'sources': [name], 'may_write': True, 'may_read': may_read}
class SeedHelper(unittest.TestCase):
def setUp(self):
self.tmp = Path(tempfile.mkdtemp())
(self.tmp / 'bin').mkdir()
fake = self.tmp / 'bin/bao'
fake.write_text(FAKE_BAO)
fake.chmod(0o755)
self.home = self.tmp / '.warden-attended-login' / 'session'
self.home.mkdir(parents=True)
self.state = {'calls': [], 'stdin': [], 'identity': {'policies': ['default', 'platform-admin']},
'kv': {SRC: registry([sender('operator', [OTHER], may_read=True),
sender('tenant-engine', [TOKEN])])}}
def run_helper(self, *extra, confirm=CONFIRM, home=None, env_extra=None, receipt=None):
sf = self.tmp / 'state.json'
sf.write_text(json.dumps(self.state))
preexisting = receipt is not None
receipt = receipt or self.tmp / f'receipt-{len(list(self.tmp.glob("receipt-*")))}.json'
env = {'PATH': f'{self.tmp / "bin"}:/usr/bin:/bin', 'HOME': str(home or self.home),
'FAKE_BAO_STATE': str(sf), **(env_extra or {})}
args = [sys.executable, str(SCRIPT), '--receipt', str(receipt), *extra]
if confirm is not None:
args += ['--confirm', confirm]
r = subprocess.run(args, capture_output=True, env=env, timeout=60)
self.assertEqual((r.stdout, r.stderr), (b'', b''), 'helper must be silent')
self.state = json.loads(sf.read_text())
text = receipt.read_text() if receipt.exists() and not preexisting else '{}'
self.assertNotIn(TOKEN, text)
self.assertNotIn(OTHER, text)
for call in self.state['calls']:
self.assertNotIn(TOKEN, ' '.join(call), 'value must never be in bao argv')
if receipt.exists() and not preexisting:
self.assertEqual(receipt.stat().st_mode & 0o777, 0o600)
return r.returncode, json.loads(text)
def test_seed_writes_cas0_and_is_idempotent(self):
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome'], rec['target_version']), (0, 'written', 1))
self.assertEqual(self.state['stdin'], [{'options': {'cas': 0}, 'data': {'token': TOKEN}}])
self.assertEqual(self.state['kv'][DST]['versions'][0]['data'], {'token': TOKEN})
self.assertEqual(len(self.state['kv'][SRC]['versions']), 9, 'registry untouched')
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (0, 'already_identical'))
self.assertEqual(len(self.state['stdin']), 1, 'no second write')
def test_dry_run_reads_metadata_only(self):
code, rec = self.run_helper('--dry-run')
self.assertEqual((code, rec['outcome'], rec['value_reads']), (0, 'dry_run_would_write', 0))
paths = [c[-1] for c in self.state['calls']]
self.assertTrue(all(p.startswith('platform/metadata/') or p == 'auth/token/lookup-self' for p in paths), paths)
self.assertFalse(any(c[0] == 'write' for c in self.state['calls']))
self.state['kv'][DST] = {'versions': [{'data': {'token': TOKEN}, 'meta': {}}]}
code, rec = self.run_helper('--dry-run')
self.assertEqual((code, rec['outcome']), (6, 'dry_run_target_present'))
def test_existing_different_target_is_refused_not_overwritten(self):
for data in ({'token': OTHER}, {'token': TOKEN, 'extra': '1'}):
self.state['kv'][DST] = {'versions': [{'data': data, 'meta': {}}]}
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (3, 'target_differs'))
self.assertEqual(self.state['stdin'], [])
def test_deleted_current_version_is_refused(self):
self.state['kv'][DST] = {'versions': [{'data': {'token': TOKEN}, 'meta': {'deletion_time': '2026-10-01T00:00:00Z'}}]}
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (3, 'target_current_version_not_live'))
def test_preconditions_refuse_before_any_bao_call_or_write(self):
code, rec = self.run_helper(confirm='yes')
self.assertEqual((code, rec['outcome'], self.state['calls']), (2, 'exact_confirmation_required', []))
code, rec = self.run_helper(home=self.tmp)
self.assertEqual((code, rec['outcome']), (2, 'attended_warden_envelope_required'))
code, rec = self.run_helper(env_extra={'BAO_TOKEN': 'x'})
self.assertEqual((code, rec['outcome']), (2, 'attended_warden_envelope_required'))
for policies in (['default'], ['platform-admin', 'root']):
self.state['identity'] = {'policies': policies}
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (2, 'attended_platform_admin_required'))
self.assertFalse(any(c[0] == 'write' for c in self.state['calls']))
def test_sender_shape_refusals(self):
cases = {
'sender_missing_or_duplicate': [sender('operator', [OTHER], may_read=True)],
'sender_token_count_not_one': [sender('tenant-engine', [TOKEN, TOKEN + 'b'])],
'sender_not_write_only': [sender('tenant-engine', [TOKEN], may_read=True)],
}
for reason, rows in cases.items():
self.state['kv'][SRC] = registry(rows)
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (2, reason))
self.assertNotIn(DST, self.state['kv'])
del self.state['kv'][SRC]
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (2, 'registry_absent'))
def test_write_and_verify_failures_have_distinct_codes(self):
self.state['fail_write'] = True
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (4, 'write_failed'))
self.state['fail_write'] = False
self.state['corrupt_after_write'] = True
code, rec = self.run_helper()
self.assertEqual((code, rec['outcome']), (5, 'readback_mismatch'))
def test_existing_receipt_is_never_overwritten(self):
receipt = self.tmp / 'receipt-0.json'
receipt.write_text('keep')
code, _ = self.run_helper(receipt=receipt)
self.assertEqual(code, 2)
self.assertEqual(receipt.read_text(), 'keep')
self.assertEqual(self.state['calls'], [])
if __name__ == '__main__':
unittest.main()

View file

@ -46,7 +46,8 @@ id: RPF-WP-0035-T02
status: wait
needs_human: true
decision_id: 62aa95e4-3ccc-4867-8806-5e3db005b483
blocking_reason: "KEY-WP-0009 finished; remaining gate is human: attended platform provisioning of the exact JWT mount/role/policy; KeyCape issuer/JWKS and service registration under KEY-WP-0009."
blocked_on: message-from:key-cape
blocking_reason: "SECRETS-WP-0007-T04 done 2026-09-27, so that gate is open. Real blocker: live KeyCape registration of client secrets-engine-openbao and its protected client-credential custody (contract: separate approved custody action; no owning task id yet, asked key-cape 2026-10-03). The design requires both before any write; then a reviewed child and the attended founder apply."
priority: high
state_hub_task_id: "e0c82ea9-05a5-537e-8fbf-67534263572e"
```
@ -72,7 +73,7 @@ contract, and rollback/cleanup receipts contain no secret material.
2026-10-03: human gate routed to hub decision 62aa95e4-3ccc-4867-8806-5e3db005b483 (D1: Attended OpenBao provisioning window: one session clears the pending credential lanes) by CUST-WP-0075; resolving that decision raises C-40 here.
2026-10-03 correction (CUST-WP-0075, informed-decision sitting draft `55fb078`): the D1 routing was wrong. First provision and delivery already ran on 2026-09-11, with seed and delivery receipts, and the review packet says not to repeat them. The task is not a provisioning-window act. It was taken off decision D1, and its blocking_reason now names what actually remains. railiance-platform should re-qualify it further if the receiver rollout has since happened.
2026-10-03 (CUST-WP-0075): gate check. SECRETS-WP-0007-T04 is done (2026-09-27), so the gate named in the design is open. It is not the last one: the design says to confirm the exact live client registration and protected client-side custody before writing, and positive-login proof needs a real KeyCape service JWT. KeyCape's contract (`openbao-service-auth-contract.md`) and KEY-WP-0009 claim source registration only; the live registration of `secrets-engine-openbao` and its client-credential custody are "a separate approved custody action" that no workplan task owns yet (SECRETS-WP-0008-T06 waits on this task). Re-qualified with `blocked_on: message-from:key-cape`; key-cape was asked for the owning task id, which will replace it as `depends_on`. The design stays proposed and no child command was written. Not approvable in D1 yet.
## Implement the platform operator-write CCR contract and Fluid lane
@ -439,13 +440,17 @@ into this client-identity grant.
id: RPF-WP-0035-T08
status: wait
needs_human: true
blocking_reason: "Provisioned and delivered 2026-09-11 (CCR-2026-0021/0022 applied); do not repeat. Remaining: native delivery/receiver-ingestion acceptance (receiver image must carry evidence_kind; Approval Engine namespace is its owner prerequisite), archive readback, and revocation of the interim audit bearer tokens."
blocking_reason: "Provisioned and delivered 2026-09-11 (CCR-2026-0021/0022 applied); do not repeat. Receiver rollout with evidence_kind and native producer acceptance passed 2026-09-11. Remaining human acceptance: independent archive readback and revocation of the interim audit bearer tokens."
priority: high
assignee: railiance-platform
intervention_note: null
state_hub_task_id: "0ef52c26-2669-5cd1-8149-b1f6fcdda6cb"
```
2026-10-03 correction (CUST-WP-0075, informed-decision sitting draft `55fb078`): the D1 routing was wrong. First provision and delivery already ran on 2026-09-11, with seed and delivery receipts, and the review packet says not to repeat them. The task is not a provisioning-window act. It was taken off decision D1, and its blocking_reason now names what actually remains. railiance-platform should re-qualify it further if the receiver rollout has since happened.
2026-10-03 (CUST-WP-0075): re-qualified. The receiver rollout already happened: Audit Core c82e0442de0f carries evidence_kind and native producer accepted/duplicate and scope/read-refusal checks passed 2026-09-11 10:42 UTC (`docs/credential-lane-designs/factory-audit-senders-review.md`, `factory-native-acceptance.md`). Remaining: independent archive readback and audit bearer-revocation acceptance. Still not a provisioning-window act.
Consume AUDIT-WP-0009-T09/T11's exact declarations for approval-engine and
informed-decision: tenant:platform, write-only, load-bearing, redact. Source
preparation supplies two approved CCRs, independent exact-path policies/ESO