The runner was executable with no shebang, so a shell handed its path tried to interpret Python as shell script. That is what a broken command line hit, and it would have hit anyone invoking the file directly. Also adds `make openbao-open-questions`, which is short enough that terminal wrapping cannot split it, refuses a receipt path that is missing or already exists, and clears OPENBAO_TOKEN/VAULT_TOKEN/BAO_TOKEN before invoking the attended envelope so the identity comes from the login rather than a standing token. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WLUjpv3ssxNRAEPPgLFnEB Assistant: claude-code Assistant-Model: opus Assistant-Process: 1275505@bnt-lap001 Assistant-Session: 97265baa-f08f-4032-b290-a1e2965a69c5
7.5 KiB
Attended OpenBao session — four open custody questions
One founder-attended session that settles the questions currently blocking four owner threads. Everything in it is read-only against OpenBao. It provisions nothing, approves nothing, and rotates nothing.
Prepared 2026-09-09 under RPF-WP-0035. Not yet run.
Why a session rather than a reply
Four owner threads are each blocked on a fact that only an authenticated read can establish, and each has been answered so far with "I will not guess":
| Question | Thread | What is undecidable from repo source |
|---|---|---|
| Q1 | secrets-engine 546403e4 |
Whether the legacy path secret/coulomb/whynot-design/npm/publish still exists and holds a real token |
| Q2 | secrets-engine 546403e4, ops-warden b6212006 |
Which field backs the authoritative npm lane. Two dated live receipts here name NPM_AUTH_TOKEN; secrets-engine says npm_token and ops-warden has already changed their catalog to it. Both can be true if the path carries both fields. Field names are not visible in KV v2 metadata |
| Q3 | internal | The 2026-09-09 activation receipt records policy_applied: false for both KeyCape approval lanes while role_applied: true. Reads verified, so the policies must exist — but existence and repo agreement were never confirmed |
| Q4 | CCR-2026-0019 | Which netkingdom OIDC roles and bound group claims already exist, as input to the operator group claim that lane is missing |
| Q5 | risk-nexus ee702ac9 |
Which fields the governed backup lane actually carries, as RPF-WP-0029-T02 context |
Authority and containment
Authority is the governed openbao-platform-admin-login lane: founder_required,
attended OIDC through bao login -method=oidc -path=netkingdom role=platform-admin.
The owner command is never run directly.
make openbao-open-questions SESSION_RECEIPT=/tmp/<new-unique-name>.json
Use the Make target rather than typing the underlying command: it is short enough
not to be broken by terminal wrapping, it refuses a SESSION_RECEIPT that already
exists or is missing, and it clears any ambient token before invoking the
envelope.
scripts/openbao-attended-exec.py supplies the WSL browser launcher and then
execs into warden access openbao-platform-admin-login --exec, so Warden keeps
the envelope: it captures both streams, owns the temporary token helper and
self-revokes when the command returns. Use a receipt path that does not exist —
the writer opens it O_EXCL at mode 0600 and refuses to overwrite.
The runner prints nothing. Its entire output is the receipt.
To settle the npm field alone, run only the two questions that bear on it:
make openbao-open-questions SESSION_QUESTIONS=Q1,Q2 SESSION_RECEIPT=/tmp/<new-unique-name>.json
--questions defaults to all five. Q5 is the only step that reads the backup
lane, so naming a subset is also how to exclude it.
Ambient-token guard. The Make target clears these before invoking the
envelope; the runner independently refuses to start if OPENBAO_TOKEN,
BAO_TOKEN or VAULT_TOKEN is set in the environment. A standing token would
let these reads succeed without the attended login, producing a receipt that
looks attended and is not. Unset it first; the envelope supplies the identity.
--allow-ambient-token overrides, and then the receipt records
attended_identity: false — which is the honest label for that read, not a
formality.
What the runner reads, and the one honest caveat
| Step | Call | Emitted |
|---|---|---|
| Q1 | secrets list, then kv metadata get on the legacy path if the mount exists |
mount present, path present, current version, created/updated time |
| Q2 | kv get on platform/workloads/coulomb/whynot-design/npm-publish |
sorted field names only, plus KV version |
| Q3 | policy read for both KeyCape approval policies |
present, matches-repo-source, sha256 of each normalized text |
| Q4 | list auth/netkingdom/role, then read each |
role names, bound claims, groups/user claim, token policies, TTL |
| Q5 | kv get on platform/workloads/railiance/backup/offsite-lane |
sorted field names only, plus KV version |
The caveat, stated plainly: Q2 and Q5 are data reads. Field names cannot be
obtained from KV v2 metadata, so the value transits the runner's memory. It is
never printed, never passed on argv, never written to disk, and never placed in
the receipt — field_names() returns key names and the values go out of scope
with the function. If that is not acceptable for the backup lane in particular,
drop Q5: it is context for RISK-F-0010, not a dependency of it.
For Q5 the receipt records predecessor_material_recorded: false, and no value,
fingerprint, length or shape of any predecessor credential is captured — which is
the constraint risk-nexus set.
What each answer unblocks
- Q1 + Q2 → secrets-engine and ops-warden. Q2 is now the more urgent of the
two. ops-warden changed their catalog and playbook from
NPM_AUTH_TOKENtonpm_tokenon secrets-engine's statement, while this repository holds two dated live receipts — including an attended founder fetch that exited zero — namingNPM_AUTH_TOKENas the field. If those receipts are right, that change points the front door at a field that does not exist and the lane breaks on next use. Q2 enumerates the real field names:npm_tokenonly,NPM_AUTH_TOKENonly, or both, and each outcome names exactly one side that has to move, as a reviewed lane change carrying its own approval. If Q1 finds a populated legacy path, that is a stale duplicate holding a real token and becomes its own disposition item — do not delete it inside this session. - Q3 → internal. Either the policies match the repo, and the activation's
policy_applied: falsemeant "no change needed", or they drift and that is a finding to fix under RPF-WP-0035 before anyone treats the verifier lanes as clean. - Q4 → CCR-2026-0019. Existing bound claims show what the estate already uses for operator lanes. This is input, not confirmation — the authorized group claim is NetKingdom's and KeyCape's to state, and the receipt says so in-line so a later reader cannot mistake the listing for an approval.
- Q5 → risk-nexus. Confirms the governed lane is populated with the expected field names, supporting the fail-closed argument already made on that thread.
Explicitly out of scope
- The rapp-qonto KeyCape client rotation (ops-warden
c1aafba4). It is a two-custodian CAS write with a service restart and needs its own reviewed request first. Do not fold it into a read-only session because the operator happens to be authenticated. - Predecessor share invalidation for RPF-WP-0029-T02. That is a provider-side action in the Nextcloud UI, not an OpenBao operation, and it needs the provider owner. It can share the same sitting, but it is a separate step with a separate receipt.
- Any write, seed, rotation or policy apply. The runner contains no mutating verb; if a question turns out to need one, it comes back as a request.
- Client-side reads for the approval clients. CCR-2026-0019/0020 remain in_flight; this session does not advance them beyond Q4's input.
After the session
- Store the receipt under
docs/evidence/with the run date, values absent. - Reply to secrets-engine (Q1/Q2), risk-nexus (Q5) and, if Q3 drifts, record the finding against RPF-WP-0035.
- If Q1 finds a populated legacy path, open a disposition item for it rather than acting on it in the moment.