docs: preserve reviewed NetKingdom session artifacts
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
Reject generated-only commits / generated-commit-guard (push) Waiting to run

This commit is contained in:
codex 2026-11-01 23:24:43 +01:00
parent daeba9eeab
commit 24ae04fa80
50 changed files with 8375 additions and 0 deletions

View file

@ -0,0 +1,10 @@
{
"root_review_candidates": [
"glas-harness-driver-reference.patch",
"sand-boxer-current-driver-reference.patch"
],
"superseded_broad_sand_proposal": "sand-boxer-driver-reference.patch is retained but not recommended; it also changed historical prose references. Use the current-only patch instead.",
"preserve_historical_sand_prose": true,
"canonical_driver_edits_performed": false,
"frozen_receipts_unchanged": true
}

View file

@ -0,0 +1,450 @@
---
id: GLAS-WP-0012
type: workplan
title: "Prove the first local rein profile end to end"
domain: infotech
repo: glas-harness
status: blocked
flavor: implementation
owner: codex
topic_slug: first-local-profile-production-proof
created: "2026-09-05"
updated: "2026-09-27"
state_hub_workstream_id: "170bf1ae-337f-5553-8d1e-03b07100e08f"
---
# Prove the first local rein profile end to end
## Context and scope
Follow up the Claude-route portion of live residual `GLAS-IN-0002`, originating
in finished `GLAS-WP-0005`. Boundary hardening is committed at `63a7f9f` and
proved with a deterministic non-secret dispatch. That proof did not execute
the real rein CLI or make a model call.
Start from `harness.agent-dev-local@1.0.0` (rein-aharness,
`claude-sonnet-4-6`, `claude-code-cli`, `green-commit-only`). Review a new
versioned candidate after the owner runtime contract is available. Keep the
existing blocked selection as the rollback pin. The open-weight profile and
remote profile do not acquire readiness from this workplan.
Glas owns profile selection, its adapter, and acceptance evidence. Sand-boxer
owns runtime provisioning, execution environment, isolation, egress enforcement,
and teardown. The selected rein and credential owner own authentication and
credential acquisition. Use existing owner work `SAND-WP-0014-T05`; its current
OpenRouter dependency must be distinguished from this first Claude-route proof.
## Establish the exact local baseline and acceptance requirements
```task
id: GLAS-WP-0012-T01
status: done
priority: high
state_hub_task_id: "711aeb7e-4664-5078-8c65-3fe6f46f7999"
```
Completed 2026-09-05. Reviewed Glas `63a7f9f`, sand-boxer `b6655d8`, and
rein-aharness `1429db5`. Added a reproducible non-secret owner-execution runtime
inventory and the [acceptance contract](../docs/local-profile-acceptance.md).
The baseline can discover Python, Git, and a `claude` command. It cannot
discover `rein-aharness`, its Python module, or `llm_connect`. Command discovery
alone does not prove that Claude can start or authenticate. The namespace has
only loopback; declared egress and credential route references are empty.
Owner execution and workspace teardown pass. The baseline is therefore
insufficient for a real rein run.
Reviewed the plan against current implementation. Local preparation is done;
remaining execution waits on the owner inputs in T02.
## Resolve and verify the owner runtime, credential, and egress contract
```task
id: GLAS-WP-0012-T02
status: wait
depends_on: [SAND-WP-0014-T05]
blocking_reason: "Dependency: sand-boxer SAND-WP-0014-T05 with rein-aharness and its credential owner."
priority: high
state_hub_task_id: "4209f564-a9ee-57f9-9522-3708988801ef"
```
Dependency: sand-boxer `SAND-WP-0014-T05`, with rein-aharness and its credential
owner. Required deliverables are listed in `docs/local-profile-acceptance.md`:
- Exact runtime artifact/revision, in-sandbox entrypoints and dependencies,
sanitized environment, private writable state, and deployment/host identity.
- A Claude-compatible catalog credential route and owner implementation that
delivers it to the selected runtime. An environment label containing route
references is not credential delivery.
- An owner-defined sandbox profile with explicit enforced provider egress,
DNS/TLS requirements, and negative egress tests. Editing an egress list alone
does not provide connectivity in the current loopback-only implementation.
- Non-secret startup, identity, source-absence, and cleanup evidence through
the same manager/service path the gateway will use.
Done when these deliverables exist at reviewable revisions and their owner
proofs pass. No rein installation in the source checkout, host home mount,
credential injection from the interactive shell, or unrestricted network
substitution is an acceptance path.
2026-09-05 owner progress: SAND-WP-0015 implemented digest-pinned standalone
Python runtime mounting and private namespace state. Real rein CLI startup,
read-only runtime, source absence, clean worktree, state persistence across
exec calls, and teardown passed in sandbox d4de9531; sand-boxer lint and 132
tests pass. This completes the candidate runtime mechanism, not production
selection. Authentication lane, credential delivery/revocation, provider egress,
pinning the Claude executable, and deployed profile review remain open. The
credential catalog has no matching Anthropic/Claude workload lane; the user selected a dedicated Anthropic workload API key. See
[the concrete custody proposal](../docs/anthropic-workload-key.md) and owner
CCR-2026-0016 (in flight). Organization/key metadata is supplied and the user saved the key in OpenBao.
A metadata-only owner check confirmed live version 2 on 2026-09-05, without
reading the value. Custody handoff is complete. Verified machine binding,
protected runtime delivery, workspace scope/budget and provider egress remain
outstanding; provider authentication has not been tested.
See docs/local-profile-acceptance.md.
## Review and validate the versioned Glas proof candidate
```task
id: GLAS-WP-0012-T03
status: wait
depends_on: [GLAS-WP-0012-T02, SECRETS-WP-0009-T03]
priority: high
blocking_reason: "Await the rebuilt runtime artifact carrying profile 1.1.1 started through its owner on railiance01 (SECRETS-WP-0009-T03 step B2)."
state_hub_task_id: "c660808a-1724-555c-bc59-7318870d3591"
```
Depends on T02. Prepare `harness.agent-dev-local@1.1.0` only once the real
sandbox profile and credential route are known. Keep the rein/model route and
tool profile explicit; choose bounded proof limits no greater than the current
900 seconds / 60,000 tokens. Record runtime and sandbox revisions and the
validated host scope in evidence. Account for dependency versions that are not
represented directly in the Glas profile schema.
Use `unverified` only after known blockers are resolved, to label the bounded
real proof. Validate schema, capabilities, route propagation, timeout behavior,
and refusal of the old blocked version. Version coexistence must make an
unversioned selection ambiguous rather than silently changing consumers.
Done when the exact candidate resolves, the real runtime starts through its
owner, the focused tests and full suite pass, and rollback to the blocked
`harness.agent-dev-local@1.0.0` pin is documented.
2026-09-21 candidate: `profiles/harness.agent-dev-local-1.1.0.yaml`, readiness
`unverified` (evidence_ref GLAS-WP-0012-T03), `profile.bwrap-local`, no
credential route refs, claude-sonnet-4-6, 900 s / 60,000 tokens, max_turns 30,
max_budget_usd 5.0. The provider key stays with rein-aharness MessagesOwner
outside bwrap (metered-once); the owner bootstrap's sandbox, model and
credential-route checks accept it. max_budget_usd matches the operator-accepted
spend envelope hfact-glas-anthropic-2026-09 (Informed Decision
infd-20260921-b01, approval ba5ce2d8). Unpinned selection is now ambiguous and
1.0.0 stays blocked; rollback is pinning 1.0.0. 134 tests pass. Remaining for
done: the rebuilt runtime artifact carrying this profile starts through its
owner on railiance01 (SECRETS-WP-0009-T03 step B2).
2026-09-23 the candidate is now `harness.agent-dev-local@1.1.1` (`claude-sonnet-5`,
otherwise identical). 1.1.0 is blocked, and rollback is still pinning 1.0.0.
Reviewed 2026-09-24: the catalog resolves 1.1.1 as unverified, and 134 tests pass.
The done condition is unchanged. It needs runtime `2cfc7b5f` rebuilt with 1.1.1
and started through its owner.
## Prove one real bounded rein task inside the sandbox
```task
id: GLAS-WP-0012-T04
status: wait
depends_on: [GLAS-WP-0012-T03]
blocking_reason: "Depends on T03."
priority: high
state_hub_task_id: "3e791432-8f37-54bd-a44a-55eeff11ac19"
```
Depends on T03. Run the actual `ReinAharness` adapter and installed
`rein-aharness` CLI with Claude through the candidate profile on a disposable
Git fixture. Do not substitute deterministic dispatch or change the source
checkout to satisfy the task. Verify the acceptance assertions in the contract,
including exactly one intended commit, a clean final tree, unchanged/inaccessible
host source, exact identity, bounded duration, task cleanup, and destruction.
Collect and validate the sandbox artifact before gateway teardown removes the
workspace. Implement the smallest necessary proof observer in the acceptance
fixture; direct evidence must distinguish actual content validation from a
reported commit SHA. Preserve compact value-free evidence only.
Done when the bounded real run and relevant failure/cleanup checks pass with
the exact selected profile/runtime pins and credential route identified.
## Publish evidence and mark only the proven profile ready
```task
id: GLAS-WP-0012-T05
status: wait
depends_on: [GLAS-WP-0012-T04]
blocking_reason: "Depends on T04."
priority: high
state_hub_task_id: "f7635cf6-e044-52d4-8242-0512e0ccbd7b"
```
Depends on T04. Update only the proven candidate to `ready` with evidence that
identifies the host/runtime scope, credential route, egress policy, revisions,
limits, and rollback. Run catalog validation and regression checks. Do not
enable production schedules as a side effect of this proof.
Update `GLAS-IN-0002` with the Claude acceptance result. Keep its open-weight
portion live, or split that remaining work into a separately registered record
before closing the intake. Synchronize work records and log progress. Finish
this workplan only when the first profile is proven and residuals remain live.
## Prepare the real-rein acceptance runner
```task
id: GLAS-WP-0012-T06
status: done
priority: high
state_hub_task_id: "d0c26709-549f-540a-8250-1fd1b2e4960a"
```
Independent preparation for T04 while T02 remains blocked. Add a disposable
fixture that delegates actual dispatch to the catalog-built ReinAharness,
validates the commit and artifact through owner execution before teardown,
checks the host source and cleanup, and emits bounded evidence without model
output or credential values. Refuse blocked or unpinned profiles before
creating a sandbox. Test negative artifact cases and the refusal path. This
runner does not itself satisfy T04 or change readiness.
Completed 2026-09-05. `scripts/prove-local-profile.py` delegates real dispatch
and observes content/commit acceptance before teardown. It verifies exactly one
new commit, expected paths and committed/worktree content, a clean tree,
source absence, exact child identity, private task mode/removal, and host-source
integrity plus workspace destruction. It emits bounded evidence and has no
readiness override. `tests/test_local_profile_proof.py`: 20 focused tests pass;
full suite: 101 passed; catalog validation passes. A real CLI invocation against
`harness.agent-dev-local@1.0.0` returned `OperationallyBlockedProfileError`
without creating a sandbox. T02–T05 remain waiting on the owner dependencies;
no real model run or readiness promotion is claimed.
## Acceptance
One version-pinned local profile runs its actual rein/model task entirely in
the declared sandbox, produces validated local output, and tears down cleanly.
Its readiness describes the proven runtime scope. Other profile readiness and
consumer schedules remain independently governed.
## 2026-09-05 egress owner return
Owner-controlled HTTPS egress is implemented and live-tested through both the
extension and a persisted manager: provider TLS/HTTP response, undeclared-host
and direct-IP denial, isolated namespace and proxy/workspace cleanup passed.
See [egress evidence](../docs/evidence/GLAS-WP-0012-egress-2026-09-05.md).
T02 remains waiting on machine auth, protected credential delivery, the pinned
Claude executable and the combined production acceptance. Profiles remain blocked.
## 2026-09-05 credential owner return
Sand-boxer credential transport is implemented and proved with synthetic
provider sandbox 880f749e. Owner config binds exact profile/project/actor and
run id; unknown routes refuse, shell API-key fallback is stripped, only the
workload child receives the value, and output redaction precedes truncation.
Tests: sand-boxer171, secrets-engine226, Glas101 pass. Native data-only read
policy/AppRole proposal and activation now live in SECRETS-WP-0009. Its
production exec refuses before OpenBao because durable access-engine decision
records and scoped service authority remain unavailable. T02 remains waiting
on that activation, pinned Claude startup and real provider/task acceptance.
## 2026-09-06 credential owner update
Reviewed secrets-engine message `3bd23bc9-e863-4f1f-8c76-415812ec0656`.
Owner reports the local authorization join implemented at `627810b`. Remaining
native activation depends on approval-engine serving the durable
ActionAuthorization claim endpoint and access-engine serving Check, followed
by configuration, per-lane approval and positive/negative verification.
State Hub decisions are not a substitute for the durable authorization object.
SECRETS-WP-0009-T03 and this plan's T02 remain waiting; runtime pinning and
combined real proof remain required. No readiness change or real-key read.
## 2026-09-06 authorization contract and policy publication
Reviewed owner commits `7b4b9e3` and `083bee7` and FLEX-WP-0021. Gate-house
GH-DEC-2026-005 resolved the contract: secrets-engine now validates the
approval-claim and decision envelope separately and removed the incorrect
State Hub authority requirement. Flex-auth published
`secrets-engine.catalog-lane.lifecycle` v1; T01/T02 are done.
Remaining owner gates are real decision/digest verification (FLEX-WP-0021-T03),
consumer service deployment and handoff (T04/T05), approval-engine deployment,
production service identity and verified lane activation. Publication is not
deployment. T02 stays waiting, and runtime pinning and combined acceptance
remain required. No new owner inbox message or activation evidence was found
in this review.
## 2026-09-06 digest verification follow-up
Reviewed secrets-engine `6e9c152` and `925d028`: owner corrected correlation
field handling in the flex-auth digest and refreshed fixtures. Its tests also
record an unresolved approval/native action-vocabulary and embedded-claim
digest mismatch for dual-control requests. This is owner integration evidence,
not successful live activation; SECRETS-WP-0009-T03 remains wait. No new
activation receipt was found in the Glas inbox. T02 remains waiting.
## 2026-09-06 authorization-chain owner return
Reviewed secrets-engine `f62d3fe` / `64aeec9`. Owner reports claim → Check →
consume → backend integration against a throwaway OpenBao, including denial,
unreachable service, invalid claim, missing digest, consume conflict and action
mismatch refusal before backend access. Authorization transport is stubbed;
wire shapes use published flex-auth fixtures. Do not describe this as a live
production service or Anthropic delivery proof.
Owner now identifies deployment/configuration as the remaining live gate:
consumer PDP service and approval-engine rollout, production service
credentials, deployed policy pin and per-lane authorization id.
SECRETS-WP-0009-T03 remains wait. Earlier digest-defect notes are historical;
use this latest owner return for the integration status. Native lane positive/
negative access checks, pinned Claude startup, provider scope/budget inputs and
combined Glas acceptance remain outstanding. T02–T05 stay wait.
Review found no pending local changes or new inbox messages. Corrected stale
README wording that still called owner execution missing; the implemented
mechanism and its separate proofs do not change profile readiness.
## 2026-09-06 production deployment action
User authorized production deployment. Dedicated flex-auth-secrets-engine Helm
release is available 1/1 at revision 1; five policy fixtures and allowed/denied
network probes passed. CI image is digest-pinned; existing consumers unchanged.
Caller authentication remains warn pending identity adoption. See
[deployment evidence](../history/2026-09-06-174000-production-deployment.md).
Approval-engine remains undeployed: live KeyCape clients and audit sender are
absent, the tenant contract needs alignment, and a release digest is required.
Corrected its stale issuer/JWKS deployment inputs. No production credential
lane or real Claude run was activated; T02 remains wait.
## Production dependency coordination
GLAS-WP-0015 now tracks exact owner handoffs and return receipts from Glas.
The requests in docs/production-dependency-handoffs.md reuse existing owner
work: tenant agreement and client custody, KeyCape rollout, audit source
onboarding, approval release, native lane activation and pinned combined
runtime. Image/runtime preparation can proceed in parallel. The runner's
project glas-local-proof must be reconciled with the owner route example's
project glas-harness before the exact delivery binding is selected. Owner
handoffs were sent after explicit authorization; GLAS-WP-0015 records all seven
receipt IDs and tracks replies. This plan's T02 remains wait.
## Verified owner follow-up — 2026-09-06
Glas reviewed six owner acknowledgements in GLAS-WP-0015. Project binding is
resolved to agt/glas-local-proof at sand-boxer 23d0c2b; the combined profile
source is available, but Claude is not pinned. The deployed policy was found
to lack tenant denial at v1; the owner correction v2 is now deployed at Helm
revision 2 and six live Check fixtures pass, including wrong_tenant denial.
Tenant choice, workstation caller access, audit load-bearing schema/admission,
identity/custody and native activation remain gates. T02 remains wait.
Tenant decision update (2026-09-06): operator approved `tenant:platform` for the
approval dependency chain as the platform management/services tenant. See
`docs/platform-tenant-decision.md`. The choice blocker is resolved; exact owner
configuration alignment and verification remain part of T02 before readiness.
Later deployment progress (2026-09-06): KeyCape/approval source tenant alignment
verified, approval release published and pinned, audit T03 complete, dedicated
PDP revision 3 enforces adopted short-lived caller identity. See GLAS-WP-0015
and its evidence receipts. T02 remains wait until remaining owner admission and
runtime gates are verified; no real model run or readiness promotion.
## Pinned Claude startup progress — 2026-09-06
Advanced sand-boxer SAND-WP-0015-T05 independently of custody: runtime builder
now copies only an explicitly digest-pinned native Claude executable. Selected
installed Claude 2.1.263, binary digest
26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba;
complete candidate runtime digest
5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922.
Sandbox f8821ec2 passed actual Claude --version, rein CLI/import, read-only
runtime, private HOME/state persistence, source absence, clean tree and teardown
with owner proxy environment present. Lint and all 175 sand-boxer tests pass.
Evidence: docs/evidence/GLAS-WP-0015-claude-startup-2026-09-06.json.
This resolves candidate binary pin/startup, not protected runtime deployment,
Claude provider-request compatibility, credential delivery or a real model run.
The candidate is in /tmp; no production profile readiness changed. Existing
credential and acceptance tasks remain open. No new owner replies at session start.
## Protected local artifact return — 2026-09-08
SAND-WP-0015-T06 is done. The previously pinned combined artifact is installed
unchanged in owner UID 1000's mode-0700 runtime store on bnt-lap001:
`/home/worsch/.local/share/sandboxer/runtimes/5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`.
Complete artifact digest remains
`5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`.
This is the existing owner-controlled local artifact contract, not root-owned
system storage or Railiance placement. Do not rebuild this accepted candidate
merely to move it out of /tmp.
The installed-path sandbox `51b59587` proves actual Claude 2.1.263 and rein
startup, read-only runtime, source absence, clean Git, private state persistence,
loopback-only interfaces, and workspace/proxy removal. No credential references
or model request were used. Sand-boxer `make check`: lint clean, 188 tests pass.
Source return: 3e49a98a0e2c4a64539a5ccd674be8cd67ac9845; receipt in
`sand-boxer/docs/evidence/SAND-WP-0015-protected-local-install-2026-09-08.json`.
T02 remains wait for trusted owner execution configuration, native credentials
and real-provider evidence. T03-T06 retain candidate review, real-model run,
readiness and recovery obligations. No profile readiness or schedule changes.
2026-09-23 candidate superseded before use: operator chose `claude-sonnet-5`.
`harness.agent-dev-local@1.1.1` is identical to 1.1.0 except the model; 1.1.0 is
now `blocked`. Runtime `2cfc7b5f` bundles the earlier 1.1.0 copy (profile sha
`e84b9d0f…`) and must be rebuilt with this revision and placed again
(SECRETS-WP-0009-T03). Claude CLI 2.1.266 in that runtime lists
`claude-sonnet-5` in its model registry (static check). 134 tests pass.
## 2026-09-27 loose-ends review
Reviewed for locally finishable work. No new owner reply advances T02 beyond
the 2026-09-23 secrets-engine status already recorded in GLAS-WP-0015-T03.
T03's remaining done condition (runtime `2cfc7b5f` rebuilt with 1.1.1 and
started through its owner on railiance01, SECRETS-WP-0009-T03 step B2) and
T04/T05 all require that external owner delivery. Nothing in this repo can
advance them today; `status: blocked` stands unchanged. Re-review when the
runtime rebuild/placement or owner authentication lands.
## EMERGENT-WP-0002 proof requirements — 2026-10-03
Scoped review and local observed-model correction:
`docs/evidence/2026-10-03-emergent-session-readiness-review.md`.
T04 retains actual native-provider startup and continuity acceptance, including
reload after real resume/compaction, provider/repository changes and exact
available model/session metadata. The current lifecycle exposes no continuity
event API. Existing owner-delivery blockers and all task/readiness states remain;
local deterministic tests are not governed provider or isolation acceptance.
## 2026-10-16 NetKingdom isolated integration preparation
Prepared, without owner deployment or readiness promotion, a Glas change that
separates credential-free housekeeping commands from explicit rein workload
execution. Both concrete adapters use the workload path; SSH selections with
credential routes refuse because that transport has no owner delivery contract.
Full regression: 141 passed. Real disposable bwrap/Glas transport proof passes
synthetic host-file denial, inherited credential environment and subprocess
denial, workload-only delivery, redaction, subsequent-command absence and
teardown. No provider/model call or real credential read occurred.
Latest owner dependency sources supersede the stale missing-lane account:
September 27 native lanes were activated and revoked; corrected immutable
owner runtime is `818b1b7c…` (October 15 startup proof), not yet installed or
admitted on Railiance. HARNESS-WP-0008-T07 retains memo lifecycle acceptance,
installation, rehearsal, renewal/pin updates, native approvals and bounded
successful model/tool/commit acceptance. Any subsequent build containing this
patch has a different source/artifact pin requiring owner admission.
This is a reviewable patch in Custodian's NetKingdom change bundle; no canonical
source synchronization or workplan task completion is implied. CUST-WP-0073
continues to require admission of the actual interactive assignment, including
its existing host credential and external tool paths, not a disposable fixture.

View file

@ -0,0 +1,375 @@
---
id: SAND-WP-0015
type: workplan
title: "Provide a pinned bwrap rein runtime and private state"
domain: infotech
repo: sand-boxer
status: blocked
flavor: implementation
owner: codex
topic_slug: bwrap-runtime-and-private-state
created: "2026-09-05"
updated: "2026-10-04"
state_hub_workstream_id: "d3f12387-fd23-58f0-b979-9c811507614d"
---
# Provide a pinned bwrap rein runtime and private state
Implement the runtime prerequisites returned by Glas in `GLAS-WP-0012` and
`docs/local-profile-acceptance.md`. Continue owner work `SAND-WP-0014-T05` and
live residual `GLAS-IN-0002`. Runtime startup is a separate acceptance gate
from credential delivery, enforced provider egress, and a real model task.
## Mount an owner-selected, digest-pinned Python runtime
```task
id: SAND-WP-0015-T01
status: done
priority: high
state_hub_task_id: "93e4aad8-ba9b-5850-93d4-b5bf78ac8978"
```
Add exact artifact verification to trusted extension configuration and mount
the standalone runtime read-only at a fixed namespace path. Reject altered
content/modes, unexpected files, escaping symlinks, special files, and source
or workspace overlap. A caller cannot choose runtime paths through exec input.
Provide a non-editable rein-aharness/llm-connect bundle builder with recorded
source revisions and resolved dependency versions.
Completed 2026-09-05. `extensions/runtime.py` verifies complete artifact
contents/modes before a read-only mount; the builder installed committed
rein-aharness `1429db5` and llm-connect `0056094` without editable source paths.
Candidate digest and source/package evidence are in `docs/bwrap-runtime.md`.
## Keep writable runtime state outside the repository
```task
id: SAND-WP-0015-T02
status: done
priority: high
state_hub_task_id: "c8a689f8-bdf2-57a2-8521-a1c0ca44e750"
```
Create private mode-0700 HOME/config/cache/state/tmp directories within the
namespace, preserve them across exec requests, and remove them with teardown.
Keep the command environment sanitized and disable Python user-site/bytecode
writes. Fail closed on profile network/setup-credential declarations that the
current bwrap implementation cannot honor.
Completed 2026-09-05. Mode-0700 namespace HOME and XDG/TMP directories sit
outside the copied Git tree. Explicit runtime PATH selection remains owner
controlled. Unsupported egress/default-allow and setup credentials now refuse
before workspace creation; regression tests cover these boundaries.
## Verify the real rein runtime and owner regression paths
```task
id: SAND-WP-0015-T03
status: done
priority: high
state_hub_task_id: "1a3002f6-c6a8-59d0-9d38-942028419de5"
```
Run `make check`, the existing authenticated cross-request owner smoke, and a
real runtime startup smoke. Verify the actual rein CLI and adapter imports,
read-only runtime, private HOME, clean worktree, absent source, loopback-only
network, and teardown. Keep the production Glas profile blocked.
Completed 2026-09-05. `make check`: lint clean, 132 tests passed. Authenticated
owner API smoke `223db65b` returned HTTP 200 and proved exact consumer identity,
stdin delivery, absent source, loopback-only network and complete teardown.
Pinned runtime smoke `d4de9531` ran the actual rein CLI and imported its Claude
adapter, proved read-only runtime/private HOME/clean worktree, retained private
state across a second exec, and removed the workspace. No model call or
credential acquisition occurred; T04 remains waiting.
## Resolve Claude credentials, enforced egress, and production acceptance
```task
id: SAND-WP-0015-T04
status: wait
blocked_on: message-from:rein-aharness
blocking_reason: "Tracked as HARNESS-WP-0008-T07 (owner, attended): accept spend memo infd-20261014-b01 (up to three attempts within EUR 10, observed-usage settlement), rebuild/install ee3d0a53 on railiance01, rehearse, renew, then a successful attempt."
priority: high
state_hub_task_id: "58817ef6-76d9-5e34-908f-c024e6c99f93"
```
The 2026-09-05 routing lookup found no concrete Anthropic/Claude workload lane.
The generic OpenBao template is not a delegable lane; the OpenRouter AppRole
belongs to another rein. Credential owner railiance-platform/OpenBao plus
rein-aharness must establish a concrete Claude-compatible route and delivery/
revocation contract before values can be requested or supplied to the runtime.
Sand-boxer must then implement the matching enforced provider egress contract
(including DNS/TLS and denied-destination proof) and pin/deploy the actual
Claude executable. No unrestricted-network or interactive-login substitute is
permitted. Review the deployed runtime/profile with Glas, run its real-rein
acceptance fixture, and update only the proven profile's readiness. No
production schedule is enabled by the runtime-startup smoke.
This task and SAND-WP-0014-T05 remain open until those gates pass. The detailed
return contract and runtime proof are in `docs/bwrap-runtime.md`; the live
cross-repo residual remains `GLAS-IN-0002`.
## 2026-09-05 transport implementation evidence
T04 remains waiting on owner machine authentication, protected credential
delivery, pinned Claude startup and the real model proof. CCR-2026-0016 custody
is confirmed by a metadata-only check of live version 2; no value was read.
HTTPS egress is now implemented as an exact owner/profile allowlisted CONNECT
proxy while retaining an isolated network namespace. Non-secret live smoke
sandbox e290e788 verified provider TLS response, undeclared destination and
direct-IP denial, and proxy/workspace teardown. See docs/bwrap-egress.md.
## 2026-09-05 credential transport owner return
Owner-bound exec-env transport is implemented and proved with a synthetic
provider in real sandbox 0e5fb35a. Exact route/profile/project/actor/run binding,
child-only key injection, pre-truncation redaction, next-exec absence and teardown
pass. No real key read or production profile activation. See docs/bwrap-credentials.md.
SECRETS-WP-0009 now owns native AppRole adoption for CCR-2026-0016, including
a data-only consumer read policy. Its production exec refuses before OpenBao
until SECRETS-WP-0007-T04 and SECRETS-WP-0008-T02/T06 deliver canonical
authorization/consume and service authority. T04 remains waiting on that live
owner path, the pinned Claude executable and real model acceptance.
## 2026-09-06 combined-runtime candidate returned to Glas
Answered the GLAS-WP-0015 handoff. Added candidate profile
`profile.claude-agent-dev-proof` version 1.1.0 (`ext.bwrap`, localhost-only,
`default: deny` with declared `api.anthropic.com:443`) for GLAS-WP-0012 review.
The committed profile grants no egress by itself: `ext.bwrap` refuses it unless
the owner extension config allowlists the destination, and a regression test now
asserts that fail-closed default. `make check`: lint clean, 172 tests passed.
Resolved the differing project examples in favour of the acceptance runner's
`actor: agt` / `project: glas-local-proof`, and bound the documented credential
route to that single project rather than broadening it. The full return contract
— profile id/revision, host scope, consumer tuple, runtime digest and mount
paths, declared egress, and the value-free denial/cleanup receipts — is in
`docs/bwrap-runtime.md`.
T04 stays `wait`: the concrete Claude credential lane and delivery/revocation
contract, owner machine authentication, the pinned Claude executable with its
own proxy startup proof, and the real-model acceptance are still operator and
upstream-owner gated. Provider workspace scope and spend limit remain operator
inputs. `SAND-WP-0014-T05` remains open on the same gates.
## Package and prove the pinned Claude executable independently of custody
```task
id: SAND-WP-0015-T05
status: done
priority: high
state_hub_task_id: "f0163cda-b581-56a0-a0dd-9c8a436f5bbe"
```
Split the independent executable packaging/startup portion from T04 so credential
custody does not hide completed local runtime work. Implement explicit binary
pinning, reject mismatches/host wrappers, and prove actual startup with the
broker proxy environment and complete cleanup. T04 retains protected deployment,
credential adoption and real-provider acceptance.
## Pinned Claude candidate and isolated startup — 2026-09-06
The builder now accepts `--claude-binary`, `--claude-sha256` and
`--claude-version` together. It requires a regular native ELF file, verifies the
exact copied bytes, refuses symlinks/wrappers/mismatches and existing destination
files, and copies only the executable as bin/claude mode 0755. The expected
version is metadata until the sandbox smoke checks the actual output. No
interactive HOME, provider key or login state is copied.
Candidate provenance: the explicitly selected installed native release
`/home/worsch/.local/share/claude/versions/2.1.263`, SHA-256
`26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba`.
This is a digest pin of the selected local artifact, not an upstream signature
verification claim. Complete runtime candidate:
`/tmp/sandboxer-claude-runtime-20260906`, SHA-256
`5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`.
Mount remains `/opt/sandboxer/runtime`; Claude is `/opt/sandboxer/runtime/bin/claude`.
Rein sources remain 1429db5 / llm-connect 0056094; resolved dependency versions
are recorded in the evidence. Rebuilding may resolve different dependencies;
the complete digest pins this artifact, not future builds.
Sandbox f8821ec2 ran real `claude --version` -> `2.1.263 (Claude Code)` plus rein
CLI/import checks, with owner allowlisted provider egress and HTTPS_PROXY set
by the broker. Runtime read-only, private 0700 HOME/state persistence, clean Git,
absent source and loopback-only interfaces passed; workspace and proxy removed.
`make check`: lint passed, 175 tests passed.
Evidence: docs/evidence/SAND-WP-0015-claude-startup-2026-09-06.json.
Reproduce with the builder options above and `scripts/smoke-bwrap-runtime.py
--runtime-path <candidate> --runtime-sha256 <digest> --claude-version 2.1.263`.
Limits: `--version` proves startup with the proxy environment, not that Claude
has sent a provider request through it. No credential acquisition or model run.
The /tmp candidate is not production placement; protected artifact installation,
owner configuration, credential adoption and real-model proof remain T04 gates.
## Install the pinned runtime in the local owner's protected store
```task
id: SAND-WP-0015-T06
status: done
priority: high
assignee: the-custodian
state_hub_task_id: "f85f54c9-1081-5d0c-95ad-aca20ede8d66"
```
HFACT-WP-0001-T04 consumes this independent installation return. Reuse the
already verified combined candidate with digest
`5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`;
do not rebuild resolved dependencies. Publish it under the local owner's
private artifact store, verify exact copied content/modes and refusal of unsafe
or corrupted destinations, and prove startup/read-only access/teardown through
the installed path. The owner remains trusted; this is not root-owned storage.
T04 retains real credential adoption, owner configuration, model acceptance
and production placement. No credential route or profile is activated here.
`scripts/install-bwrap-runtime.py` installs below the owner's home into a
mode-0700 store, verifies before and after copying, preserves artifact modes
and internal symlinks, serializes publishers and never overwrites a published
artifact. Existing modified artifacts are refused rather than repaired silently.
Group/other-writable ancestors, unsafe permission bits and store aliases refuse.
Unit tests cover integrity, idempotence, copy-time corruption and access modes.
T06 completed 2026-09-08 on bnt-lap001, local owner UID 1000. Installed the
unchanged 358-entry / 245176062-byte candidate at
`/home/worsch/.local/share/sandboxer/runtimes/5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`.
The private store is mode 0700; the installing owner remains trusted. A root-owned
system installation was not claimed or required for this local owner contract.
The installed-path sandbox smoke `51b59587` passed real Claude 2.1.263 and rein
startup, read-only mount, private state persistence, clean worktree, absent source,
loopback-only interfaces and complete workspace/proxy teardown. No model call
or credential acquisition occurred. `make check`: lint clean, 188 tests passed.
Two existing tests require the checkout directory name `sand-boxer`; the final
full check used that canonical basename. Evidence:
`docs/evidence/SAND-WP-0015-protected-local-install-2026-09-08.json`.
T04 retains owner execution configuration, native credential/egress and real-model
acceptance; Railiance installation needs its own target-specific return.
## 2026-09-09 factory metered route source return
Implemented the trusted, ephemeral Messages route described in
[docs/bwrap-messages-route.md](../docs/bwrap-messages-route.md). Actual local
bwrap owner transport proves provider key/ledger separation, direct-route denial,
revocation and teardown; rein also proves metered request plus commit import and
close replay. `make check`: lint clean, 199 passed. T04 remains waiting for the
admitted provider-to-owner bootstrap, updated protected runtime/profile, Railiance
placement, live compatibility and G0. Existing child-provider-key/direct-CONNECT
proofs do not admit this different credential holder or metered profile. No CCR,
secret read, deployment or paid request was performed.
## 2026-09-09 standalone owner packaging and runtime selection
Added frozen-lock owner build mode for the matched rein/llm/Glas/sandboxer set,
with non-editable installation and recorded lock/source/package pins. Sand-boxer
wheels now carry their profile and extension definitions, fixing a bwrap owner
failure that source-checkout tests could hide. The trusted Messages binding can
select the digest-pinned runtime without adding an API or profile override.
`make check`: lint clean, 205 passed. Existing standalone workload builds remain
supported. See docs/bwrap-runtime.md and rein's docs/owner-bootstrap.md.
The project's `evidence/2026-09-09-owner-bootstrap.json` records the actual candidate
build and isolated installed-interpreter/CLI proof. T04 remains wait for accepted
credential-to-owner delivery, current protected installation and Railiance placement,
provider compatibility and G0. Source packaging does not reopen completed T01-T03,
T05-T06, broaden the old CCRs or activate the previously installed 2.1.263 artifact.
The first standalone candidate exposed a stale cached local wheel despite current
Git metadata. Owner builds now refresh all local distributions and compare every
installed package source/definition file with its tracked source; stale, missing or
extra files refuse the build. Four content-conformance regressions cover that gap.
## 2026-09-27 installed launcher defect and guarded correction
REINAH-WP-0003's single admitted attempt completed native provider/companion
apply, verify and exec, including scoped-token revocation. The claimed job
`6efa9436-6a91-47c0-94e5-b31b5a0e7e3a` failed before any provider request
reservation. The sandbox was destroyed and the source repository is unchanged.
The parent EUR 10 liability remains held for explicit reconciliation; no retry
or replacement authority is inferred.
Installed artifact `b6e4e8a4` contains uv-generated three-line shell launchers
that still reference a vanished temporary build interpreter. The builder had
only rewritten direct Python shebangs. Fixed both generated forms and added
actual relocation/execution tests for direct, long and space-containing build
paths, plus refusal of unknown governed launchers. Builder tests: 12 passed;
focused lint passes. Rein's real installed bwrap probe reproduces both broken
entrypoints at exit 127 with zero provider forwards; its new bootstrap checks
refuse the malformed artifact before claiming.
T04 remains `wait` and this plan stays `blocked`: replacement artifact build/
admission and a separately authorized successful model/tool/commit proof remain.
The existing SAND-WP-0015 / REINAH-WP-0003 / SECRETS-WP-0009 records retain the
work. No new task or workplan. Detailed non-secret receipts are in rein-aharness
`docs/evidence/2026-09-27-metered-*.json`.
## 2026-10-04 offline owner startup proof preparation
The existing no-credential smoke command now supports
`--require-owner-entrypoints`: the admitted digest-pinned runtime must start both
`rein-aharness --help` and `glas-harness --help`, and imported rein, llm-connect,
Glas and sandboxer modules must originate inside the mounted artifact. Broken or
missing launchers, failed help, timeout and external package origins cannot be
recorded as startup success. The default standalone mode remains supported.
Launcher diagnostics are not copied into the receipt. Focused tests exercise
real subprocesses, including the vanished-build-interpreter failure.
This is preparation for a corrected immutable candidate's independent native
startup proof; it is not that candidate's receipt or owner acceptance. T04 stays
`wait`, the plan stays `blocked`, and the Sept27 artifact/liability evidence
remains intact. Replacement artifact admission and pins, explicit retained
EUR 10 reconciliation, and a separately authorized successful model/tool/commit
attempt remain with the existing owners. No credentials, provider request,
queue claim, policy activation or retry authorization is supplied here.
2026-10-04 current blocker reconciliation: September 27 native provider/companion
apply/verify/exec and successful revocation supersede the original missing
platform route. The remaining owner return is rein-aharness corrected artifact/
profile admission, retained EUR10 reconciliation and fresh retry authority.
T04 stays wait; no renewed trial is authorized. Full owner check: lint clean,
219 tests passed (one existing Starlette deprecation warning).
## 2026-10-13 owner returns: reconciliation and corrected build
rein-aharness `d897a83` records owner reconciliation of the Sept 27 held
liability: run `6efa9436` failed at launcher exit 127 before any provider
reservation; liability stays at the full hold, exhausting that one-attempt
envelope, and the owner authorized a new retry envelope on 2026-10-13.
rein-aharness `9a4469d` records corrected runtime `ee3d0a53…` (replaces
`b6e4e8a4…`; only rein moved, adding pre-claim launcher rejection), built with
the sand-boxer builder fix; both launchers exit 0 inside bwrap. Receipts:
rein-aharness `docs/evidence/2026-10-13-*.json`.
Remaining before T04 closes: install `ee3d0a53` on railiance01, pin its digest
in owner config, pass `--require-owner-entrypoints` offline startup proof, then
run the single authorized attempt to a successful model/tool/commit receipt.
T04 stays `wait`; the plan stays `blocked`.
2026-10-14 check: rein-aharness pushed `9a4469d` (corrected build record) and
opened REINAH-WP-0008 (simpler metered retry, active). No railiance01 install/
admission of `ee3d0a53` or successful acceptance receipt yet; request sent on
hub message `82faf25c`. T04 stays `wait`; plan stays `blocked`.
2026-10-14 rein-aharness reply (thread `82faf25c`): step 1 done (`9a4469d` on
origin/main). Steps 2-4 are REINAH-WP-0008-T07. Budget is now up to three
attempts within EUR 10 under memo `infd-20261014-b01` (observed-usage
settlement); order: memo acceptance in the attended informed-decision sitting,
rebuild and install `ee3d0a53` on railiance01, `metered-once --rehearse`,
`owner renew`, first attempt. Railiance01 still pins `b6e4e8a4`. T04 stays `wait`.
2026-10-15 owner-approved rebuild: the pinned inputs of the `ee3d0a53` receipt
(same four commits, uv.lock `8775904b…`, Claude 2.1.266 `19842705…`) rebuilt
to `818b1b7c8292…f359`. The digest differs only because the builder embeds
absolute source paths (`direct_url.json`, `build-info.json`). Owner-entrypoint
startup smoke passed in sandbox `28df7a1d`; no credentials or model calls.
Sent to rein-aharness (message `8a7962f7`, thread `82faf25c`) to record as the
then-current corrected runtime for REINAH-WP-0008-T07 and to load the unsigned
memo sitting. Nothing installed on railiance01. T04 stays `wait`.

View file

@ -0,0 +1,375 @@
---
id: SAND-WP-0015
type: workplan
title: "Provide a pinned bwrap rein runtime and private state"
domain: infotech
repo: sand-boxer
status: blocked
flavor: implementation
owner: codex
topic_slug: bwrap-runtime-and-private-state
created: "2026-09-05"
updated: "2026-10-04"
state_hub_workstream_id: "d3f12387-fd23-58f0-b979-9c811507614d"
---
# Provide a pinned bwrap rein runtime and private state
Implement the runtime prerequisites returned by Glas in `GLAS-WP-0012` and
`docs/local-profile-acceptance.md`. Continue owner work `SAND-WP-0014-T05` and
live residual `GLAS-IN-0002`. Runtime startup is a separate acceptance gate
from credential delivery, enforced provider egress, and a real model task.
## Mount an owner-selected, digest-pinned Python runtime
```task
id: SAND-WP-0015-T01
status: done
priority: high
state_hub_task_id: "93e4aad8-ba9b-5850-93d4-b5bf78ac8978"
```
Add exact artifact verification to trusted extension configuration and mount
the standalone runtime read-only at a fixed namespace path. Reject altered
content/modes, unexpected files, escaping symlinks, special files, and source
or workspace overlap. A caller cannot choose runtime paths through exec input.
Provide a non-editable rein-aharness/llm-connect bundle builder with recorded
source revisions and resolved dependency versions.
Completed 2026-09-05. `extensions/runtime.py` verifies complete artifact
contents/modes before a read-only mount; the builder installed committed
rein-aharness `1429db5` and llm-connect `0056094` without editable source paths.
Candidate digest and source/package evidence are in `docs/bwrap-runtime.md`.
## Keep writable runtime state outside the repository
```task
id: SAND-WP-0015-T02
status: done
priority: high
state_hub_task_id: "c8a689f8-bdf2-57a2-8521-a1c0ca44e750"
```
Create private mode-0700 HOME/config/cache/state/tmp directories within the
namespace, preserve them across exec requests, and remove them with teardown.
Keep the command environment sanitized and disable Python user-site/bytecode
writes. Fail closed on profile network/setup-credential declarations that the
current bwrap implementation cannot honor.
Completed 2026-09-05. Mode-0700 namespace HOME and XDG/TMP directories sit
outside the copied Git tree. Explicit runtime PATH selection remains owner
controlled. Unsupported egress/default-allow and setup credentials now refuse
before workspace creation; regression tests cover these boundaries.
## Verify the real rein runtime and owner regression paths
```task
id: SAND-WP-0015-T03
status: done
priority: high
state_hub_task_id: "1a3002f6-c6a8-59d0-9d38-942028419de5"
```
Run `make check`, the existing authenticated cross-request owner smoke, and a
real runtime startup smoke. Verify the actual rein CLI and adapter imports,
read-only runtime, private HOME, clean worktree, absent source, loopback-only
network, and teardown. Keep the production Glas profile blocked.
Completed 2026-09-05. `make check`: lint clean, 132 tests passed. Authenticated
owner API smoke `223db65b` returned HTTP 200 and proved exact consumer identity,
stdin delivery, absent source, loopback-only network and complete teardown.
Pinned runtime smoke `d4de9531` ran the actual rein CLI and imported its Claude
adapter, proved read-only runtime/private HOME/clean worktree, retained private
state across a second exec, and removed the workspace. No model call or
credential acquisition occurred; T04 remains waiting.
## Resolve Claude credentials, enforced egress, and production acceptance
```task
id: SAND-WP-0015-T04
status: wait
blocked_on: message-from:rein-aharness
blocking_reason: "Tracked as HARNESS-WP-0008-T07 (owner, attended): accept spend memo infd-20261014-b01 (up to three attempts within EUR 10, observed-usage settlement), rebuild/install ee3d0a53 on railiance01, rehearse, renew, then a successful attempt."
priority: high
state_hub_task_id: "58817ef6-76d9-5e34-908f-c024e6c99f93"
```
The 2026-09-05 routing lookup found no concrete Anthropic/Claude workload lane.
The generic OpenBao template is not a delegable lane; the OpenRouter AppRole
belongs to another rein. Credential owner railiance-platform/OpenBao plus
rein-aharness must establish a concrete Claude-compatible route and delivery/
revocation contract before values can be requested or supplied to the runtime.
Sand-boxer must then implement the matching enforced provider egress contract
(including DNS/TLS and denied-destination proof) and pin/deploy the actual
Claude executable. No unrestricted-network or interactive-login substitute is
permitted. Review the deployed runtime/profile with Glas, run its real-rein
acceptance fixture, and update only the proven profile's readiness. No
production schedule is enabled by the runtime-startup smoke.
This task and SAND-WP-0014-T05 remain open until those gates pass. The detailed
return contract and runtime proof are in `docs/bwrap-runtime.md`; the live
cross-repo residual remains `GLAS-IN-0002`.
## 2026-09-05 transport implementation evidence
T04 remains waiting on owner machine authentication, protected credential
delivery, pinned Claude startup and the real model proof. CCR-2026-0016 custody
is confirmed by a metadata-only check of live version 2; no value was read.
HTTPS egress is now implemented as an exact owner/profile allowlisted CONNECT
proxy while retaining an isolated network namespace. Non-secret live smoke
sandbox e290e788 verified provider TLS response, undeclared destination and
direct-IP denial, and proxy/workspace teardown. See docs/bwrap-egress.md.
## 2026-09-05 credential transport owner return
Owner-bound exec-env transport is implemented and proved with a synthetic
provider in real sandbox 0e5fb35a. Exact route/profile/project/actor/run binding,
child-only key injection, pre-truncation redaction, next-exec absence and teardown
pass. No real key read or production profile activation. See docs/bwrap-credentials.md.
SECRETS-WP-0009 now owns native AppRole adoption for CCR-2026-0016, including
a data-only consumer read policy. Its production exec refuses before OpenBao
until SECRETS-WP-0007-T04 and SECRETS-WP-0008-T02/T06 deliver canonical
authorization/consume and service authority. T04 remains waiting on that live
owner path, the pinned Claude executable and real model acceptance.
## 2026-09-06 combined-runtime candidate returned to Glas
Answered the GLAS-WP-0015 handoff. Added candidate profile
`profile.claude-agent-dev-proof` version 1.1.0 (`ext.bwrap`, localhost-only,
`default: deny` with declared `api.anthropic.com:443`) for GLAS-WP-0012 review.
The committed profile grants no egress by itself: `ext.bwrap` refuses it unless
the owner extension config allowlists the destination, and a regression test now
asserts that fail-closed default. `make check`: lint clean, 172 tests passed.
Resolved the differing project examples in favour of the acceptance runner's
`actor: agt` / `project: glas-local-proof`, and bound the documented credential
route to that single project rather than broadening it. The full return contract
— profile id/revision, host scope, consumer tuple, runtime digest and mount
paths, declared egress, and the value-free denial/cleanup receipts — is in
`docs/bwrap-runtime.md`.
T04 stays `wait`: the concrete Claude credential lane and delivery/revocation
contract, owner machine authentication, the pinned Claude executable with its
own proxy startup proof, and the real-model acceptance are still operator and
upstream-owner gated. Provider workspace scope and spend limit remain operator
inputs. `SAND-WP-0014-T05` remains open on the same gates.
## Package and prove the pinned Claude executable independently of custody
```task
id: SAND-WP-0015-T05
status: done
priority: high
state_hub_task_id: "f0163cda-b581-56a0-a0dd-9c8a436f5bbe"
```
Split the independent executable packaging/startup portion from T04 so credential
custody does not hide completed local runtime work. Implement explicit binary
pinning, reject mismatches/host wrappers, and prove actual startup with the
broker proxy environment and complete cleanup. T04 retains protected deployment,
credential adoption and real-provider acceptance.
## Pinned Claude candidate and isolated startup — 2026-09-06
The builder now accepts `--claude-binary`, `--claude-sha256` and
`--claude-version` together. It requires a regular native ELF file, verifies the
exact copied bytes, refuses symlinks/wrappers/mismatches and existing destination
files, and copies only the executable as bin/claude mode 0755. The expected
version is metadata until the sandbox smoke checks the actual output. No
interactive HOME, provider key or login state is copied.
Candidate provenance: the explicitly selected installed native release
`/home/worsch/.local/share/claude/versions/2.1.263`, SHA-256
`26d020351e8112f4006790f3cfce43b4c9df0c1bb1d0e542364d64151b81d5ba`.
This is a digest pin of the selected local artifact, not an upstream signature
verification claim. Complete runtime candidate:
`/tmp/sandboxer-claude-runtime-20260906`, SHA-256
`5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`.
Mount remains `/opt/sandboxer/runtime`; Claude is `/opt/sandboxer/runtime/bin/claude`.
Rein sources remain 1429db5 / llm-connect 0056094; resolved dependency versions
are recorded in the evidence. Rebuilding may resolve different dependencies;
the complete digest pins this artifact, not future builds.
Sandbox f8821ec2 ran real `claude --version` -> `2.1.263 (Claude Code)` plus rein
CLI/import checks, with owner allowlisted provider egress and HTTPS_PROXY set
by the broker. Runtime read-only, private 0700 HOME/state persistence, clean Git,
absent source and loopback-only interfaces passed; workspace and proxy removed.
`make check`: lint passed, 175 tests passed.
Evidence: docs/evidence/SAND-WP-0015-claude-startup-2026-09-06.json.
Reproduce with the builder options above and `scripts/smoke-bwrap-runtime.py
--runtime-path <candidate> --runtime-sha256 <digest> --claude-version 2.1.263`.
Limits: `--version` proves startup with the proxy environment, not that Claude
has sent a provider request through it. No credential acquisition or model run.
The /tmp candidate is not production placement; protected artifact installation,
owner configuration, credential adoption and real-model proof remain T04 gates.
## Install the pinned runtime in the local owner's protected store
```task
id: SAND-WP-0015-T06
status: done
priority: high
assignee: the-custodian
state_hub_task_id: "f85f54c9-1081-5d0c-95ad-aca20ede8d66"
```
HFACT-WP-0001-T04 consumes this independent installation return. Reuse the
already verified combined candidate with digest
`5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`;
do not rebuild resolved dependencies. Publish it under the local owner's
private artifact store, verify exact copied content/modes and refusal of unsafe
or corrupted destinations, and prove startup/read-only access/teardown through
the installed path. The owner remains trusted; this is not root-owned storage.
T04 retains real credential adoption, owner configuration, model acceptance
and production placement. No credential route or profile is activated here.
`scripts/install-bwrap-runtime.py` installs below the owner's home into a
mode-0700 store, verifies before and after copying, preserves artifact modes
and internal symlinks, serializes publishers and never overwrites a published
artifact. Existing modified artifacts are refused rather than repaired silently.
Group/other-writable ancestors, unsafe permission bits and store aliases refuse.
Unit tests cover integrity, idempotence, copy-time corruption and access modes.
T06 completed 2026-09-08 on bnt-lap001, local owner UID 1000. Installed the
unchanged 358-entry / 245176062-byte candidate at
`/home/worsch/.local/share/sandboxer/runtimes/5cf9a16c5d77a16bdb2cb5b3df06ea655356bc2d44741791e3fedfee20d7e922`.
The private store is mode 0700; the installing owner remains trusted. A root-owned
system installation was not claimed or required for this local owner contract.
The installed-path sandbox smoke `51b59587` passed real Claude 2.1.263 and rein
startup, read-only mount, private state persistence, clean worktree, absent source,
loopback-only interfaces and complete workspace/proxy teardown. No model call
or credential acquisition occurred. `make check`: lint clean, 188 tests passed.
Two existing tests require the checkout directory name `sand-boxer`; the final
full check used that canonical basename. Evidence:
`docs/evidence/SAND-WP-0015-protected-local-install-2026-09-08.json`.
T04 retains owner execution configuration, native credential/egress and real-model
acceptance; Railiance installation needs its own target-specific return.
## 2026-09-09 factory metered route source return
Implemented the trusted, ephemeral Messages route described in
[docs/bwrap-messages-route.md](../docs/bwrap-messages-route.md). Actual local
bwrap owner transport proves provider key/ledger separation, direct-route denial,
revocation and teardown; rein also proves metered request plus commit import and
close replay. `make check`: lint clean, 199 passed. T04 remains waiting for the
admitted provider-to-owner bootstrap, updated protected runtime/profile, Railiance
placement, live compatibility and G0. Existing child-provider-key/direct-CONNECT
proofs do not admit this different credential holder or metered profile. No CCR,
secret read, deployment or paid request was performed.
## 2026-09-09 standalone owner packaging and runtime selection
Added frozen-lock owner build mode for the matched rein/llm/Glas/sandboxer set,
with non-editable installation and recorded lock/source/package pins. Sand-boxer
wheels now carry their profile and extension definitions, fixing a bwrap owner
failure that source-checkout tests could hide. The trusted Messages binding can
select the digest-pinned runtime without adding an API or profile override.
`make check`: lint clean, 205 passed. Existing standalone workload builds remain
supported. See docs/bwrap-runtime.md and rein's docs/owner-bootstrap.md.
The project's `evidence/2026-09-09-owner-bootstrap.json` records the actual candidate
build and isolated installed-interpreter/CLI proof. T04 remains wait for accepted
credential-to-owner delivery, current protected installation and Railiance placement,
provider compatibility and G0. Source packaging does not reopen completed T01-T03,
T05-T06, broaden the old CCRs or activate the previously installed 2.1.263 artifact.
The first standalone candidate exposed a stale cached local wheel despite current
Git metadata. Owner builds now refresh all local distributions and compare every
installed package source/definition file with its tracked source; stale, missing or
extra files refuse the build. Four content-conformance regressions cover that gap.
## 2026-09-27 installed launcher defect and guarded correction
REINAH-WP-0003's single admitted attempt completed native provider/companion
apply, verify and exec, including scoped-token revocation. The claimed job
`6efa9436-6a91-47c0-94e5-b31b5a0e7e3a` failed before any provider request
reservation. The sandbox was destroyed and the source repository is unchanged.
The parent EUR 10 liability remains held for explicit reconciliation; no retry
or replacement authority is inferred.
Installed artifact `b6e4e8a4` contains uv-generated three-line shell launchers
that still reference a vanished temporary build interpreter. The builder had
only rewritten direct Python shebangs. Fixed both generated forms and added
actual relocation/execution tests for direct, long and space-containing build
paths, plus refusal of unknown governed launchers. Builder tests: 12 passed;
focused lint passes. Rein's real installed bwrap probe reproduces both broken
entrypoints at exit 127 with zero provider forwards; its new bootstrap checks
refuse the malformed artifact before claiming.
T04 remains `wait` and this plan stays `blocked`: replacement artifact build/
admission and a separately authorized successful model/tool/commit proof remain.
The existing SAND-WP-0015 / REINAH-WP-0003 / SECRETS-WP-0009 records retain the
work. No new task or workplan. Detailed non-secret receipts are in rein-aharness
`docs/evidence/2026-09-27-metered-*.json`.
## 2026-10-04 offline owner startup proof preparation
The existing no-credential smoke command now supports
`--require-owner-entrypoints`: the admitted digest-pinned runtime must start both
`rein-aharness --help` and `glas-harness --help`, and imported rein, llm-connect,
Glas and sandboxer modules must originate inside the mounted artifact. Broken or
missing launchers, failed help, timeout and external package origins cannot be
recorded as startup success. The default standalone mode remains supported.
Launcher diagnostics are not copied into the receipt. Focused tests exercise
real subprocesses, including the vanished-build-interpreter failure.
This is preparation for a corrected immutable candidate's independent native
startup proof; it is not that candidate's receipt or owner acceptance. T04 stays
`wait`, the plan stays `blocked`, and the Sept27 artifact/liability evidence
remains intact. Replacement artifact admission and pins, explicit retained
EUR 10 reconciliation, and a separately authorized successful model/tool/commit
attempt remain with the existing owners. No credentials, provider request,
queue claim, policy activation or retry authorization is supplied here.
2026-10-04 current blocker reconciliation: September 27 native provider/companion
apply/verify/exec and successful revocation supersede the original missing
platform route. The remaining owner return is rein-aharness corrected artifact/
profile admission, retained EUR10 reconciliation and fresh retry authority.
T04 stays wait; no renewed trial is authorized. Full owner check: lint clean,
219 tests passed (one existing Starlette deprecation warning).
## 2026-10-13 owner returns: reconciliation and corrected build
rein-aharness `d897a83` records owner reconciliation of the Sept 27 held
liability: run `6efa9436` failed at launcher exit 127 before any provider
reservation; liability stays at the full hold, exhausting that one-attempt
envelope, and the owner authorized a new retry envelope on 2026-10-13.
rein-aharness `9a4469d` records corrected runtime `ee3d0a53…` (replaces
`b6e4e8a4…`; only rein moved, adding pre-claim launcher rejection), built with
the sand-boxer builder fix; both launchers exit 0 inside bwrap. Receipts:
rein-aharness `docs/evidence/2026-10-13-*.json`.
Remaining before T04 closes: install `ee3d0a53` on railiance01, pin its digest
in owner config, pass `--require-owner-entrypoints` offline startup proof, then
run the single authorized attempt to a successful model/tool/commit receipt.
T04 stays `wait`; the plan stays `blocked`.
2026-10-14 check: rein-aharness pushed `9a4469d` (corrected build record) and
opened HARNESS-WP-0008 (simpler metered retry, active). No railiance01 install/
admission of `ee3d0a53` or successful acceptance receipt yet; request sent on
hub message `82faf25c`. T04 stays `wait`; plan stays `blocked`.
2026-10-14 rein-aharness reply (thread `82faf25c`): step 1 done (`9a4469d` on
origin/main). Steps 2-4 are HARNESS-WP-0008-T07. Budget is now up to three
attempts within EUR 10 under memo `infd-20261014-b01` (observed-usage
settlement); order: memo acceptance in the attended informed-decision sitting,
rebuild and install `ee3d0a53` on railiance01, `metered-once --rehearse`,
`owner renew`, first attempt. Railiance01 still pins `b6e4e8a4`. T04 stays `wait`.
2026-10-15 owner-approved rebuild: the pinned inputs of the `ee3d0a53` receipt
(same four commits, uv.lock `8775904b…`, Claude 2.1.266 `19842705…`) rebuilt
to `818b1b7c8292…f359`. The digest differs only because the builder embeds
absolute source paths (`direct_url.json`, `build-info.json`). Owner-entrypoint
startup smoke passed in sandbox `28df7a1d`; no credentials or model calls.
Sent to rein-aharness (message `8a7962f7`, thread `82faf25c`) to record as the
then-current corrected runtime for HARNESS-WP-0008-T07 and to load the unsigned
memo sitting. Nothing installed on railiance01. T04 stays `wait`.

View file

@ -0,0 +1,11 @@
--- a/workplans/GLAS-WP-0012-first-local-profile-production-proof.md
+++ b/workplans/GLAS-WP-0012-first-local-profile-production-proof.md
@@ -439,7 +439,7 @@
Latest owner dependency sources supersede the stale missing-lane account:
September 27 native lanes were activated and revoked; corrected immutable
owner runtime is `818b1b7c…` (October 15 startup proof), not yet installed or
-admitted on Railiance. REINAH-WP-0008-T07 retains memo lifecycle acceptance,
+admitted on Railiance. HARNESS-WP-0008-T07 retains memo lifecycle acceptance,
installation, rehearsal, renewal/pin updates, native approvals and bounded
successful model/tool/commit acceptance. Any subsequent build containing this
patch has a different source/artifact pin requiring owner admission.

View file

@ -0,0 +1 @@
sha256:881ab0f5ba3bb43f5404c8f4cd23f9628e014992ba88fc13f541f0fa8b7b1eaa

View file

@ -0,0 +1,262 @@
# rein-aharness — Agent Instructions
## Repo Identity
**Purpose:** Claude-Code-CLI-driven rein: governed, unattended/scheduled agent runtime
**Domain:** infotech
**Repo slug:** rein-aharness
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `REINAH-WP-`
`REIN-A-0001` through `REIN-A-0004` are immutable historical identifiers from
before the fleet `PREFIX-WP-NNNN` convention. `HARNESS-WP-0001` through
`HARNESS-WP-0003` and `HARNESS-WP-ADHOC-2026-09-04` are older ids from the
agent-harness name. Preserve all of these in references and archives, but use
the hub-registered `REINAH-WP-` prefix for every new workplan and task.
---
## Developer Workflow
```bash
# Install (editable, including the governed profile-execution siblings)
python3 -m venv .venv && source .venv/bin/activate
pip install -e . -e ../llm-connect -e ../sand-boxer -e ../glas-harness pytest
# Test
PYTHONPATH=".:$HOME/llm-connect" python3 -m pytest tests/ -q
# Run — bounded agentic session against a real target repo (real Claude Code
# CLI call, real commit if successful)
rein-aharness run --task-file examples/task-hello-sandbox.json --no-hub --no-metrics
# Build the Railiance deploy image
make image
```
## State Hub Integration
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
there is no MCP server for Codex agents.
| Context | URL |
|---------|-----|
| Local workstation | `http://127.0.0.1:8000` |
| Remote (railiance01, in-cluster) | `http://10.43.68.154:8000` |
| Optional local edge relay | http://127.0.0.1:18080 |
When an operator has enabled the edge relay, set API_BASE to the relay URL.
Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
### Orient at session start
```bash
# Offline brief — works without hub connection
cat .custodian-brief.md
# Active workplans for this domain
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
| python3 -m json.tool
# Check inbox
curl -s "http://127.0.0.1:8000/messages/?to_agent=rein-aharness&unread_only=true" \
| python3 -m json.tool
```
Mark a message read:
```bash
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
-H "Content-Type: application/json" -d '{}'
```
### Log progress (required at session close)
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{
"summary": "what was done",
"event_type": "note",
"author": "codex",
"workplan_id": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"status": "progress"}'
# values: wait | todo | progress | done | cancel
```
### Flag a task for human review
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"needs_human": true, "intervention_note": "reason"}'
```
---
## Session Protocol
**Start:**
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
2. Check inbox: `GET /messages/?to_agent=rein-aharness&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
- Record significant decisions via `POST /decisions/`
**Close:**
1. Update workplan file task statuses to reflect progress
2. If finishing a workplan: hand off **residuals** as live work records first
(intake with `origin: residual` + `origin_ref: <WP-id>`, or a next workplan /
decision / engagement). Do not park leftovers only in prose or `SCOPE.md`.
Canon: `the-custodian/canon/standards/work-record-types_v0.1.md` § Residuals.
3. Log: `POST /progress/` with a summary of what changed (name handoff ids)
4. After workplan file changes, run:
```bash
statehub fix-consistency
```
Coding agents should run this directly; ask the operator only if the CLI or
State Hub API is unavailable. This syncs task status from files into the hub DB.
---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=rein-aharness` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
Work items originate as files in this repo — not in the hub. The hub is a
read/cache/index layer that rebuilds from files.
**File location:** `workplans/REINAH-WP-NNNN-<slug>.md`
**Archived location:** finished workplans may move to
`workplans/archived/YYMMDD-REINAH-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
the completion/archive date; the frontmatter `id` does not change.
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
this only for low-risk work completed directly; create a normal workplan for
anything needing analysis, design, approval, dependencies, or multiple phases.
**Frontmatter:**
```yaml
---
id: REINAH-WP-NNNN
type: workplan
title: "..."
domain: infotech
repo: rein-aharness
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
topic_slug: ...
created: "YYYY-MM-DD"
updated: "YYYY-MM-DD"
state_hub_workstream_id: "<uuid>" # fix-consistency — do not edit (legacy field name; workplan UUID)
---
```
Use `proposed` for a new draft, `ready` after review against current repo
state, and `finished` after implementation. `stalled` and `needs_review` are
derived health labels, not frontmatter statuses.
**Terminology:** workplan is the fleet term; `workstream` appears only in legacy
API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
**Task block format** (one per `##` section):
```
## Task Title
` ` `task
id: REINAH-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` `
Task description text.
```
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
**Residuals when finishing:** actionable leftovers become live work records
before `status: finished` — usually an intake (`origin: residual`,
`origin_ref: REINAH-WP-NNNN`) or a spawned workplan. Residual is a *role*,
not a kind. Fleet list lives on State Hub, not in `SCOPE.md`.
To create a new workplan:
1. Write the file following the format above
2. Run `statehub fix-consistency` locally; ask the operator only if the CLI or
State Hub API is unavailable.

View file

@ -0,0 +1,354 @@
---
id: REINAH-WP-0008
type: workplan
title: "Simpler metered retry: free rehearsal, zero-cost hold release, one-command renewal"
domain: infotech
repo: rein-aharness
status: blocked
owner: claude-code
topic_slug: rein-aharness
priority: high
created: "2026-10-13"
updated: "2026-10-16"
related:
- REINAH-WP-0003
state_hub_workstream_id: "187c2599-84a6-5764-a14f-7e9121975bf2"
---
# REINAH-WP-0008 — Simpler metered retry
## Origin
On 2026-10-13 the owner reconciled the 2026-09-27 hold for run `6efa9436`
(`docs/evidence/2026-10-13-held-liability-reconciliation.json`). A corrected
runtime `ee3d0a53…` was built
(`docs/evidence/2026-10-13-corrected-owner-runtime-build.json`). The owner
authorized a new envelope and one retry.
Preparing that retry took too many owner ceremonies:
1. The runtime is built on the workstation and copied to railiance01 by hand.
2. `owner.json` and the spend policy hold five digests that the owner edits
by hand.
3. A failed attempt keeps its full hold even when no provider request was
made, so every failure needs a new envelope, ledger and pins.
4. Every attempt needs six native approvals.
5. The launcher bug that failed run `6efa9436` was found only by spending
money. A free check on the host would have caught it.
The owner agreed (2026-10-13) to fix items 5, 3 and 2 here, and to record
the rest as decisions instead of blocking on them.
**Goal:** a retry becomes: install the artifact, rehearse for free, approve
once, run.
## A. Free rehearsal on the host
```task
id: REINAH-WP-0008-T01
status: done
priority: high
state_hub_task_id: "c6536c8d-7862-5de6-ad2b-401077b60c04"
```
Add a free rehearsal to the owner check. It runs everything
`metered-once --check` does, then executes the admitted runtime's
`rein-aharness` and `glas-harness` launchers inside bwrap from the mount
path, with no network, credentials, provider route or ledger write. The
sandbox profile is already resolved by `--check`. It prints one receipt with
the runtime digest and each launcher's exit code.
Today's launcher check reads only the first line of each launcher. This
rehearsal runs them, the same way the failing run did.
**Done when:** tests prove the rehearsal fails on a runtime whose launchers
point at a missing build directory (the `6efa9436` failure) and passes on a
relocated runtime. It must make no network calls and write nothing. The
owner procedure in `docs/owner-bootstrap.md` makes the rehearsal a required
step before any paid attempt.
### Implemented — 2026-10-13
`rein-aharness metered-once --owner-config <owner.json> --rehearse` (the flag
implies `--check`, so it never reads a provider key or claims). The code is in
`rein_aharness/owner_rehearsal.py`. It uses sand-boxer's isolation flags and
read-only system binds, with the runtime read-only at `/opt/sandboxer/runtime`,
`--unshare-net` and `--clearenv`. It runs each Python launcher with `--help`,
plus `claude --version` when `build-info.json` declares Claude. A missing
bwrap fails closed.
Tests (`tests/test_owner_rehearsal.py`, `tests/test_owner_bootstrap.py`):
- With real bwrap, a relocated runtime passes. The uv trampoline that
re-executes a deleted build interpreter fails with 127, as run `6efa9436`
did.
- The rehearsal never claims and never reads the key.
- The command line has no writable bind and no credential.
The corrected runtime `ee3d0a53…` rehearses clean: rein-aharness 0,
glas-harness 0, claude 0.
## B. Release holds that provably cost nothing
```task
id: REINAH-WP-0008-T02
status: done
priority: high
state_hub_task_id: "6a413f5a-4ac2-5d5e-9b19-71b33445c0a9"
```
`SpendLedger.reconcile` keeps the full hold (`max(held, observed)`). Add an
evidence-backed release. If the run has zero provider-request reservations
and its request route is revoked, reconciliation may set the liability to
the observed amount. Otherwise the current rule stands. The release records
which evidence allowed it, and the CLI flag says so, for example
`--release-unused-hold`.
This changes a deliberate safety rule, so it needs owner sign-off on the
exact rule before merge. That sign-off is recorded in T04's decision note.
**Done when:** tests cover all four cases. A run with no request
reservations and a revoked route releases. A run with any request
reservation, or with a live route, refuses the release. A second, identical
reconcile changes nothing. The envelope is never marked breached by a
release.
### Widened to observed-usage settlement — 2026-10-14
On 2026-10-13 the owner asked for three attempts within EUR 10 and chose
option B: settle to actual usage instead of releasing only unused holds.
Under full-hold accounting each request holds USD 4.64 (1M context plus
64k output), so EUR 10 cannot fit three attempts.
Implemented as an opt-in envelope term (`settlement: observed-usage`) plus
`max_runs`; see `docs/owner-bootstrap.md`. Under it:
- a completed request is charged its gateway-metered cost;
- runs and reconciliations are charged
`max(metered requests, reported or attested cost)`;
- unfinished requests keep their full hold, and overruns still freeze the
envelope.
The run 6efa9436 shape (failure before any request) now reconciles to its
attested cost. Defaults and existing digests are unchanged. Tests:
`tests/test_spend_settlement.py`, plus renewal cases in
`tests/test_owner_renew.py`.
Merge-ready, but the rule still needs the owner's acceptance in decision
`a93619a9`.
## D. One owner command for renewal
```task
id: REINAH-WP-0008-T03
status: done
priority: high
state_hub_task_id: "87457216-2371-51ca-b3d4-3917e6f180a2"
```
Add `rein-aharness owner renew --owner <owner.json> --runtime <digest>
--envelope <envelope.json>`. It does three things:
1. It refuses unless every reservation in the current ledger is settled.
2. It writes the new spend policy, initializes a fresh ledger, and updates
the runtime and spend-policy pins in `owner.json` together. Old files are
kept as backups. Existing ledgers are never reset.
3. It prints one summary for the owner to review: old to new runtime, old
to new policy digest, envelope cap and attempt limit, and ledger path.
Add a dry-run mode that only prints the summary. Afterwards, run the T01
rehearsal.
**Done when:** a test renews from the 2026-09-27 layout to a new envelope.
The owner check passes on the result. A second renew with an unsettled
reservation refuses. No digest has to be typed by hand.
### Implemented — 2026-10-13
The command is `rein-aharness owner renew --owner-config --policy --ledger
--runtime-path --envelope [--apply]`, in `rein_aharness/owner_renew.py`.
Without `--apply` it is a dry run. Choices:
- **Envelope file:** only the envelope fields may change. Worker, target,
profile and grant pins carry over, and `SpendPolicy` validation runs on
the result.
- **Paths stay the same:** the old ledger is renamed to
`.retired-<old envelope>`, never reset, and the old policy and owner.json
are kept as `.bak`. Secrets Engine pins the `exec_owner` files by sha256
and the ledger path through its fixed environment, so keeping the paths
means the catalog needs only new file hashes. The command prints them.
- **Refusals:** a held reservation, an open request route, an open SQLite
transaction, an owner.json that doesn't match the policy, a reused
envelope id, a disallowed field, or leftovers from an earlier renewal.
Tests (`tests/test_owner_renew.py`):
- A dry run changes nothing.
- An applied renewal passes the real owner check (`prepare`) at the
unchanged paths.
- A charged reservation, the railiance01 state after the 2026-10-13
reconciliation, renews, and the row survives in the retired ledger.
- Bad envelopes and a second apply both refuse.
Finding for T06: renewal still changes the pinned file hashes, so each
renewal needs a Secrets Engine catalog update and fresh approvals.
## C. Envelopes that cover several attempts (decision)
```task
id: REINAH-WP-0008-T04
status: done
decision_id: "a93619a9-8a70-40d8-aaf9-c29357d2a043"
priority: medium
state_hub_task_id: "898e02af-51bb-5e8d-93d0-ae908466c612"
```
Owner decision: should future envelopes be admitted as "up to N attempts
within EUR X" instead of one attempt? The EUR 10 cap already fits two
USD 4.64 request holds. Record the choice, together with T02's sign-off,
as a decision note in `docs/`. T03's envelope format then reflects it.
### Decided — 2026-10-14
The owner chose up to three attempts within EUR 10 (EUR 5 per attempt) and
observed-usage settlement, and resolved hub decision `a93619a9` with that
choice. Record: `docs/decisions/2026-10-14-spend-envelope-settlement.md`.
The terms are in spend memo `infd-20261014-b01`.
## E. Ship the owner runtime as a container image (decision)
```task
id: REINAH-WP-0008-T05
status: wait
needs_human: true
decision_id: "e6ca2f07-2708-4013-bfba-bf89fa0f1cb5"
priority: low
state_hub_task_id: "b486e86b-9d0f-501d-a394-771b40f771ee"
```
Decision for later, with Platform (railiance) and Sand-boxer: should the
owner runtime ship through the registry and GitOps, using the existing
`make image` path, instead of rsync and a manual digest check? Out of scope
here. If accepted, hand it off as a residual intake to the owning repos.
## F. Approvals per envelope (decision)
```task
id: REINAH-WP-0008-T06
status: wait
needs_human: true
decision_id: "e8986e40-f6f8-4c87-b704-bff50cc4958b"
priority: low
state_hub_task_id: "4cf721e6-724b-5c64-91bb-67b098bb97af"
```
Decision for later, with Secrets Engine: should one approval set cover an
envelope's attempts instead of six native approvals per attempt? The work
lives outside this repo. If accepted, hand it off as a residual intake.
## Retry on the corrected runtime
```task
id: REINAH-WP-0008-T07
status: wait
needs_human: true
blocking_reason: "Owner: accept spend memo infd-20261014-b01, then attended install, rehearsal, renew and approvals on railiance01"
priority: high
state_hub_task_id: "80318842-ccaa-5484-a54a-3d9981eaec8b"
```
Waits on T01 and T03. It does not need T02.
Steps:
1. Rebuild `ee3d0a53…`, or the then-current corrected runtime, from the
pinned commits.
2. Install it on railiance01.
3. Run the T01 rehearsal.
4. Renew with T03 to the owner-authorized new envelope (EUR 10, authorized
2026-10-13).
5. Run exactly one attended attempt, which consumes fresh native approvals.
Record the result in REINAH-WP-0003-T05/T06.
### Waiting on the owner — 2026-10-14
Everything this repo can do is done: the rehearsal (T01), the settlement rule
(T02, T04) and renewal (T03) are in place. Draft memo `infd-20261014-b01` is
in informed-decision `ee6a65b`. What remains needs the owner, in order:
1. Accept the memo in the attended sitting.
2. ~~Rebuild `ee3d0a53` from the pinned commits~~ (done 2026-10-15 as
`818b1b7c…`, see below) and install it on railiance01.
3. Run `metered-once --rehearse`.
4. Run `owner renew` with the memo's terms, then update the Secrets Engine
file pins.
5. Approve the native actions and run the first attempt.
Sand-boxer's steps 2–4 in message `82faf25c` are this same sequence.
### Runtime rebuilt — 2026-10-15
Step 1 is done. The owner approved a rebuild because `ee3d0a53…` was not kept
on the workstation. Sand-boxer built `818b1b7c…` from the same pinned inputs
and builder. The digest differs only because the build-source paths are
embedded in the artifact. The bwrap startup smoke passed (sandbox `28df7a1d`)
with no credentials and no model run. `818b1b7c…` is now the install
candidate. It is not installed on railiance01 (still `b6e4e8a4…`) or admitted.
Receipt: `docs/evidence/2026-10-15-rebuilt-owner-runtime.json`. The memo still
still has to be created, admitted, loaded and accepted first. The owner steps
are in informed-decision `docs/batches/2026-10-14-three-attempts/OPERATOR.md`.
### Memo queued — 2026-10-15
Owner step 1 is in progress via the lifecycle intake path (owner's choice; the
review-record approval counts as spend authority). Workspace draft
`2ccb4bda-e318-51d2-a8dd-709db768494f`, review
`review-e2d2e98f-4701-45b0-abc1-4fe9f00bd1a8`, queued by the owner in the State
Hub memo dispatcher; approval intent `accept` by `helixforge-factory`. Record:
informed-decision `d5f1021` (sitting.json, OPERATOR.md). The accepted approval id
becomes `authority_ref` for `owner renew`. Install candidate remains `818b1b7c`.
### Workload-only Glas source integration — 2026-10-16
Reviewed and integrated the one-line runtime-contract lock advance from Glas
`30be81808140a3a3d137e731d1fe9aed59ad8581` to published workload-only credential
delivery `7357115c8311e55ee3d495f5266f501a17ec3c89`. llm-connect `37436bb5` and
sandboxer `221126cb` remain pinned. This is source integration under T07, not
native artifact installation or admission. T07 remains waiting with its existing
owner gates.
Prior preparation used rein base `be5ef400` plus isolated lock-only commit
`c811fc43` to build candidate
`634e65fee5517ebee0d1080805fd84c81bd46af26a2b38ec1ab14bd2be17bf8f`. The
candidate's installed packages passed actual SandboxManager/Glas gateway proof
with unchanged `harness.agent-dev-local@1.1.1` (still unverified): real rein/Glas
CLI and Claude 2.1.266 startup, read-only runtime, synthetic host-file and
inherited environment/child-process denial, exact task identity/private state,
deterministic workspace Git effect, task removal and destruction. A separately
labelled synthetic route fixture passed workload-only delivery, output redaction
and clean subsequent housekeeping through the same actual manager. Wrong
actor/project/run, expired execution, expiry cleanup, cancellation and
post-cancellation refusal passed. All 141 Glas tests and seven rein lock/rehearsal
tests passed; whole artifact digest remained unchanged after proof.
The first proof runner correctly encountered the manager's PermissionError for
wrong consumer but expected ValueError/RuntimeError. Its finally cleanup ran;
the runner catch was refined and the failure retained. This does not earn an
unchanged execution success. No model/provider request, real credential read,
queue access or spend occurred.
The preparation artifact is not the artifact of this integrated owner revision.
Rebuild from the exact committed owner source and verify its new digest and
actual-manager proof before considering installation. Source inputs, receipts,
reproducible proof and attempt history are retained in the
[Custodian evidence directory](../../the-custodian/docs/changes/netkingdom-target-2026-10-16/agent-isolation-next/README.md).
The same directory records the integrated-source rebuild separately.
Retain `818b1b7c`, `ee3d0a53`, installed `b6e4e8a4` and their outcomes as historical
evidence. Native acceptance still requires the queued spend memo lifecycle,
owner artifact installation/rehearsal, renewal and Secrets Engine file-pin
updates, required native approvals and bounded real-model/tool proof. Actual
interactive assignment migration and external tool-path denial remain Custodian
acceptance obligations; no profile readiness or supervision authority changed.

View file

@ -0,0 +1,27 @@
{
"schema": "custodian.source-identity-correction.v1",
"status": "source_applied_publication_pending",
"files": {
"HEAD-source/AGENTS.md": "sha256:5d01af0ea255eab568ae72f326378219d1be4c77a9bc001ae12f4a1be2660034",
"HEAD-source/workplans/REINAH-WP-0008-simpler-metered-retry.md": "sha256:9aac375bdb2e93c57cd513ab8cd5bf0c0979a0699ea0181e6d405899d906bf68",
"current-driver-patch-selection.json": "sha256:02f3e03ebbd59f307bc9c1651f824beec2f0f3240a894f7f34e243ec060c3e6c",
"driver-proposals/glas-harness/workplans/GLAS-WP-0012-first-local-profile-production-proof.md": "sha256:23f97bc183bef2b0f38eb40c64bc0bb010df1c1eb4596ff3b5e0f73da7c2fcfc",
"driver-proposals/sand-boxer/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md": "sha256:10ff0a59b155bccd15bc2432619142062f6d04052deabb7cd8a796d908c894ca",
"driver-proposals/sand-boxer-current/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md": "sha256:56022703fd4d973f40ddad8eb99cb02783b086549b24ef89f89157de33eafe31",
"glas-harness-driver-reference.patch": "sha256:fb4518d2c1ed022230bbb2955970c20fcd288141dcd88718526defaca0895b2a",
"glas-harness-driver-reference.preimage.sha256": "sha256:de5afac619d1863d9340961c321effbda3677cdfcb765e24d4e5c4bfe673095c",
"index-source/AGENTS.md": "sha256:5d01af0ea255eab568ae72f326378219d1be4c77a9bc001ae12f4a1be2660034",
"index-source/workplans/REINAH-WP-0008-simpler-metered-retry.md": "sha256:9aac375bdb2e93c57cd513ab8cd5bf0c0979a0699ea0181e6d405899d906bf68",
"index.preimage": "sha256:08c186d73338c3eb03a8216c7025fdb6cb386a647802f4df7f096c2f177ff98c",
"postimages/AGENTS.md": "sha256:da71c6e3d76d7c867b2db6e67cd919fba594acc3804d3784dd744438103560c2",
"postimages/workplans/REINAH-WP-0008-simpler-metered-retry.md": "sha256:1118924bb159f8a52a37ace8b8663c75274b89928f7a8b957638428af224b054",
"preimages/AGENTS.md": "sha256:5d01af0ea255eab568ae72f326378219d1be4c77a9bc001ae12f4a1be2660034",
"preimages/workplans/REINAH-WP-0008-simpler-metered-retry.md": "sha256:9aac375bdb2e93c57cd513ab8cd5bf0c0979a0699ea0181e6d405899d906bf68",
"preparation.json": "sha256:758faed4c436d70106bd9d109a440595bea4e744d3066e1bab1d2080edcd165b",
"rein-aharness-source-correction.patch": "sha256:16c8223d22621aaccf3f430b88d1b3ec0178b60a4d4f210a34948d677f4f2441",
"sand-boxer-current-driver-reference.patch": "sha256:0c4d631655b7657cadcf8be51861a4d95edb84ccc8a0c8b8cc39f7e66549da5f",
"sand-boxer-driver-reference.patch": "sha256:63f234472f1728cdb10bdd24e27f90e05b5f3f17c91bc87eb9f973bcfd2ff8ff",
"sand-boxer-driver-reference.preimage.sha256": "sha256:5bbe7e322db7c9279240de942ebb9ebc5eca11e6945540e8d0bb59de3e0e33b8",
"verification.json": "sha256:a32b481c98e10fe10678c49b71b06fa62b2b4b8c4dc040c4d2f5a6732463ce09"
}
}

View file

@ -0,0 +1,264 @@
# rein-aharness — Agent Instructions
## Repo Identity
**Purpose:** Claude-Code-CLI-driven rein: governed, unattended/scheduled agent runtime
**Domain:** infotech
**Repo slug:** rein-aharness
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `REINAH-WP-`
`REIN-A-0001` through `REIN-A-0004` are immutable historical identifiers from
before the fleet `PREFIX-WP-NNNN` convention. `HARNESS-WP-0001` through
`HARNESS-WP-0003`, `HARNESS-WP-0008` and
`HARNESS-WP-ADHOC-2026-09-04` are preserved historical ids. Workplan 0008
keeps its originally issued HARNESS identity after the 2026-10-17 correction
of an erroneous REINAH relabel; its existing REINAH filename is only a path. Preserve all of these in references and archives, but use
the hub-registered `REINAH-WP-` prefix for every new workplan and task.
---
## Developer Workflow
```bash
# Install (editable, including the governed profile-execution siblings)
python3 -m venv .venv && source .venv/bin/activate
pip install -e . -e ../llm-connect -e ../sand-boxer -e ../glas-harness pytest
# Test
PYTHONPATH=".:$HOME/llm-connect" python3 -m pytest tests/ -q
# Run — bounded agentic session against a real target repo (real Claude Code
# CLI call, real commit if successful)
rein-aharness run --task-file examples/task-hello-sandbox.json --no-hub --no-metrics
# Build the Railiance deploy image
make image
```
## State Hub Integration
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
there is no MCP server for Codex agents.
| Context | URL |
|---------|-----|
| Local workstation | `http://127.0.0.1:8000` |
| Remote (railiance01, in-cluster) | `http://10.43.68.154:8000` |
| Optional local edge relay | http://127.0.0.1:18080 |
When an operator has enabled the edge relay, set API_BASE to the relay URL.
Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
### Orient at session start
```bash
# Offline brief — works without hub connection
cat .custodian-brief.md
# Active workplans for this domain
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
| python3 -m json.tool
# Check inbox
curl -s "http://127.0.0.1:8000/messages/?to_agent=rein-aharness&unread_only=true" \
| python3 -m json.tool
```
Mark a message read:
```bash
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
-H "Content-Type: application/json" -d '{}'
```
### Log progress (required at session close)
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{
"summary": "what was done",
"event_type": "note",
"author": "codex",
"workplan_id": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"status": "progress"}'
# values: wait | todo | progress | done | cancel
```
### Flag a task for human review
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"needs_human": true, "intervention_note": "reason"}'
```
---
## Session Protocol
**Start:**
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
2. Check inbox: `GET /messages/?to_agent=rein-aharness&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
- Record significant decisions via `POST /decisions/`
**Close:**
1. Update workplan file task statuses to reflect progress
2. If finishing a workplan: hand off **residuals** as live work records first
(intake with `origin: residual` + `origin_ref: <WP-id>`, or a next workplan /
decision / engagement). Do not park leftovers only in prose or `SCOPE.md`.
Canon: `the-custodian/canon/standards/work-record-types_v0.1.md` § Residuals.
3. Log: `POST /progress/` with a summary of what changed (name handoff ids)
4. After workplan file changes, run:
```bash
statehub fix-consistency
```
Coding agents should run this directly; ask the operator only if the CLI or
State Hub API is unavailable. This syncs task status from files into the hub DB.
---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=rein-aharness` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
Work items originate as files in this repo — not in the hub. The hub is a
read/cache/index layer that rebuilds from files.
**File location:** `workplans/REINAH-WP-NNNN-<slug>.md`
**Archived location:** finished workplans may move to
`workplans/archived/YYMMDD-REINAH-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
the completion/archive date; the frontmatter `id` does not change.
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
this only for low-risk work completed directly; create a normal workplan for
anything needing analysis, design, approval, dependencies, or multiple phases.
**Frontmatter:**
```yaml
---
id: REINAH-WP-NNNN
type: workplan
title: "..."
domain: infotech
repo: rein-aharness
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
topic_slug: ...
created: "YYYY-MM-DD"
updated: "YYYY-MM-DD"
state_hub_workstream_id: "<uuid>" # fix-consistency — do not edit (legacy field name; workplan UUID)
---
```
Use `proposed` for a new draft, `ready` after review against current repo
state, and `finished` after implementation. `stalled` and `needs_review` are
derived health labels, not frontmatter statuses.
**Terminology:** workplan is the fleet term; `workstream` appears only in legacy
API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
**Task block format** (one per `##` section):
```
## Task Title
` ` `task
id: REINAH-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` `
Task description text.
```
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
**Residuals when finishing:** actionable leftovers become live work records
before `status: finished` — usually an intake (`origin: residual`,
`origin_ref: REINAH-WP-NNNN`) or a spawned workplan. Residual is a *role*,
not a kind. Fleet list lives on State Hub, not in `SCOPE.md`.
To create a new workplan:
1. Write the file following the format above
2. Run `statehub fix-consistency` locally; ask the operator only if the CLI or
State Hub API is unavailable.

View file

@ -0,0 +1,365 @@
---
id: HARNESS-WP-0008
type: workplan
title: "Simpler metered retry: free rehearsal, zero-cost hold release, one-command renewal"
domain: infotech
repo: rein-aharness
status: blocked
owner: claude-code
topic_slug: rein-aharness
priority: high
created: "2026-10-13"
updated: "2026-10-17"
related:
- REINAH-WP-0003
state_hub_workstream_id: "187c2599-84a6-5764-a14f-7e9121975bf2"
---
# HARNESS-WP-0008 — Simpler metered retry
## Origin
Identity correction, 2026-10-17: restore the originally issued
`HARNESS-WP-0008` and T01–T07 canonical IDs, retaining every existing Hub UUID,
status, decision and human/native gate. Commit `a85de661` briefly relabelled
this workplan `REINAH-WP-0008` while preserving HARNESS-derived UUIDs in response
to a C-26 warning. Canon grandfathering preserves existing IDs; C-26 applies
to new plans and supplies no record-rekey authority. The REINAH filename remains
as an existing backing path, and the erroneous label remains historical
provenance rather than another canonical record. Future new workplans still
use REINAH-WP. No UUID migration, retirement, human disposition, installation,
spend authority or native acceptance is granted by this source correction.
On 2026-10-13 the owner reconciled the 2026-09-27 hold for run `6efa9436`
(`docs/evidence/2026-10-13-held-liability-reconciliation.json`). A corrected
runtime `ee3d0a53…` was built
(`docs/evidence/2026-10-13-corrected-owner-runtime-build.json`). The owner
authorized a new envelope and one retry.
Preparing that retry took too many owner ceremonies:
1. The runtime is built on the workstation and copied to railiance01 by hand.
2. `owner.json` and the spend policy hold five digests that the owner edits
by hand.
3. A failed attempt keeps its full hold even when no provider request was
made, so every failure needs a new envelope, ledger and pins.
4. Every attempt needs six native approvals.
5. The launcher bug that failed run `6efa9436` was found only by spending
money. A free check on the host would have caught it.
The owner agreed (2026-10-13) to fix items 5, 3 and 2 here, and to record
the rest as decisions instead of blocking on them.
**Goal:** a retry becomes: install the artifact, rehearse for free, approve
once, run.
## A. Free rehearsal on the host
```task
id: HARNESS-WP-0008-T01
status: done
priority: high
state_hub_task_id: "c6536c8d-7862-5de6-ad2b-401077b60c04"
```
Add a free rehearsal to the owner check. It runs everything
`metered-once --check` does, then executes the admitted runtime's
`rein-aharness` and `glas-harness` launchers inside bwrap from the mount
path, with no network, credentials, provider route or ledger write. The
sandbox profile is already resolved by `--check`. It prints one receipt with
the runtime digest and each launcher's exit code.
Today's launcher check reads only the first line of each launcher. This
rehearsal runs them, the same way the failing run did.
**Done when:** tests prove the rehearsal fails on a runtime whose launchers
point at a missing build directory (the `6efa9436` failure) and passes on a
relocated runtime. It must make no network calls and write nothing. The
owner procedure in `docs/owner-bootstrap.md` makes the rehearsal a required
step before any paid attempt.
### Implemented — 2026-10-13
`rein-aharness metered-once --owner-config <owner.json> --rehearse` (the flag
implies `--check`, so it never reads a provider key or claims). The code is in
`rein_aharness/owner_rehearsal.py`. It uses sand-boxer's isolation flags and
read-only system binds, with the runtime read-only at `/opt/sandboxer/runtime`,
`--unshare-net` and `--clearenv`. It runs each Python launcher with `--help`,
plus `claude --version` when `build-info.json` declares Claude. A missing
bwrap fails closed.
Tests (`tests/test_owner_rehearsal.py`, `tests/test_owner_bootstrap.py`):
- With real bwrap, a relocated runtime passes. The uv trampoline that
re-executes a deleted build interpreter fails with 127, as run `6efa9436`
did.
- The rehearsal never claims and never reads the key.
- The command line has no writable bind and no credential.
The corrected runtime `ee3d0a53…` rehearses clean: rein-aharness 0,
glas-harness 0, claude 0.
## B. Release holds that provably cost nothing
```task
id: HARNESS-WP-0008-T02
status: done
priority: high
state_hub_task_id: "6a413f5a-4ac2-5d5e-9b19-71b33445c0a9"
```
`SpendLedger.reconcile` keeps the full hold (`max(held, observed)`). Add an
evidence-backed release. If the run has zero provider-request reservations
and its request route is revoked, reconciliation may set the liability to
the observed amount. Otherwise the current rule stands. The release records
which evidence allowed it, and the CLI flag says so, for example
`--release-unused-hold`.
This changes a deliberate safety rule, so it needs owner sign-off on the
exact rule before merge. That sign-off is recorded in T04's decision note.
**Done when:** tests cover all four cases. A run with no request
reservations and a revoked route releases. A run with any request
reservation, or with a live route, refuses the release. A second, identical
reconcile changes nothing. The envelope is never marked breached by a
release.
### Widened to observed-usage settlement — 2026-10-14
On 2026-10-13 the owner asked for three attempts within EUR 10 and chose
option B: settle to actual usage instead of releasing only unused holds.
Under full-hold accounting each request holds USD 4.64 (1M context plus
64k output), so EUR 10 cannot fit three attempts.
Implemented as an opt-in envelope term (`settlement: observed-usage`) plus
`max_runs`; see `docs/owner-bootstrap.md`. Under it:
- a completed request is charged its gateway-metered cost;
- runs and reconciliations are charged
`max(metered requests, reported or attested cost)`;
- unfinished requests keep their full hold, and overruns still freeze the
envelope.
The run 6efa9436 shape (failure before any request) now reconciles to its
attested cost. Defaults and existing digests are unchanged. Tests:
`tests/test_spend_settlement.py`, plus renewal cases in
`tests/test_owner_renew.py`.
Merge-ready, but the rule still needs the owner's acceptance in decision
`a93619a9`.
## D. One owner command for renewal
```task
id: HARNESS-WP-0008-T03
status: done
priority: high
state_hub_task_id: "87457216-2371-51ca-b3d4-3917e6f180a2"
```
Add `rein-aharness owner renew --owner <owner.json> --runtime <digest>
--envelope <envelope.json>`. It does three things:
1. It refuses unless every reservation in the current ledger is settled.
2. It writes the new spend policy, initializes a fresh ledger, and updates
the runtime and spend-policy pins in `owner.json` together. Old files are
kept as backups. Existing ledgers are never reset.
3. It prints one summary for the owner to review: old to new runtime, old
to new policy digest, envelope cap and attempt limit, and ledger path.
Add a dry-run mode that only prints the summary. Afterwards, run the T01
rehearsal.
**Done when:** a test renews from the 2026-09-27 layout to a new envelope.
The owner check passes on the result. A second renew with an unsettled
reservation refuses. No digest has to be typed by hand.
### Implemented — 2026-10-13
The command is `rein-aharness owner renew --owner-config --policy --ledger
--runtime-path --envelope [--apply]`, in `rein_aharness/owner_renew.py`.
Without `--apply` it is a dry run. Choices:
- **Envelope file:** only the envelope fields may change. Worker, target,
profile and grant pins carry over, and `SpendPolicy` validation runs on
the result.
- **Paths stay the same:** the old ledger is renamed to
`.retired-<old envelope>`, never reset, and the old policy and owner.json
are kept as `.bak`. Secrets Engine pins the `exec_owner` files by sha256
and the ledger path through its fixed environment, so keeping the paths
means the catalog needs only new file hashes. The command prints them.
- **Refusals:** a held reservation, an open request route, an open SQLite
transaction, an owner.json that doesn't match the policy, a reused
envelope id, a disallowed field, or leftovers from an earlier renewal.
Tests (`tests/test_owner_renew.py`):
- A dry run changes nothing.
- An applied renewal passes the real owner check (`prepare`) at the
unchanged paths.
- A charged reservation, the railiance01 state after the 2026-10-13
reconciliation, renews, and the row survives in the retired ledger.
- Bad envelopes and a second apply both refuse.
Finding for T06: renewal still changes the pinned file hashes, so each
renewal needs a Secrets Engine catalog update and fresh approvals.
## C. Envelopes that cover several attempts (decision)
```task
id: HARNESS-WP-0008-T04
status: done
decision_id: "a93619a9-8a70-40d8-aaf9-c29357d2a043"
priority: medium
state_hub_task_id: "898e02af-51bb-5e8d-93d0-ae908466c612"
```
Owner decision: should future envelopes be admitted as "up to N attempts
within EUR X" instead of one attempt? The EUR 10 cap already fits two
USD 4.64 request holds. Record the choice, together with T02's sign-off,
as a decision note in `docs/`. T03's envelope format then reflects it.
### Decided — 2026-10-14
The owner chose up to three attempts within EUR 10 (EUR 5 per attempt) and
observed-usage settlement, and resolved hub decision `a93619a9` with that
choice. Record: `docs/decisions/2026-10-14-spend-envelope-settlement.md`.
The terms are in spend memo `infd-20261014-b01`.
## E. Ship the owner runtime as a container image (decision)
```task
id: HARNESS-WP-0008-T05
status: wait
needs_human: true
decision_id: "e6ca2f07-2708-4013-bfba-bf89fa0f1cb5"
priority: low
state_hub_task_id: "b486e86b-9d0f-501d-a394-771b40f771ee"
```
Decision for later, with Platform (railiance) and Sand-boxer: should the
owner runtime ship through the registry and GitOps, using the existing
`make image` path, instead of rsync and a manual digest check? Out of scope
here. If accepted, hand it off as a residual intake to the owning repos.
## F. Approvals per envelope (decision)
```task
id: HARNESS-WP-0008-T06
status: wait
needs_human: true
decision_id: "e8986e40-f6f8-4c87-b704-bff50cc4958b"
priority: low
state_hub_task_id: "4cf721e6-724b-5c64-91bb-67b098bb97af"
```
Decision for later, with Secrets Engine: should one approval set cover an
envelope's attempts instead of six native approvals per attempt? The work
lives outside this repo. If accepted, hand it off as a residual intake.
## Retry on the corrected runtime
```task
id: HARNESS-WP-0008-T07
status: wait
needs_human: true
blocking_reason: "Owner: accept spend memo infd-20261014-b01, then attended install, rehearsal, renew and approvals on railiance01"
priority: high
state_hub_task_id: "80318842-ccaa-5484-a54a-3d9981eaec8b"
```
Waits on T01 and T03. It does not need T02.
Steps:
1. Rebuild `ee3d0a53…`, or the then-current corrected runtime, from the
pinned commits.
2. Install it on railiance01.
3. Run the T01 rehearsal.
4. Renew with T03 to the owner-authorized new envelope (EUR 10, authorized
2026-10-13).
5. Run exactly one attended attempt, which consumes fresh native approvals.
Record the result in REINAH-WP-0003-T05/T06.
### Waiting on the owner — 2026-10-14
Everything this repo can do is done: the rehearsal (T01), the settlement rule
(T02, T04) and renewal (T03) are in place. Draft memo `infd-20261014-b01` is
in informed-decision `ee6a65b`. What remains needs the owner, in order:
1. Accept the memo in the attended sitting.
2. ~~Rebuild `ee3d0a53` from the pinned commits~~ (done 2026-10-15 as
`818b1b7c…`, see below) and install it on railiance01.
3. Run `metered-once --rehearse`.
4. Run `owner renew` with the memo's terms, then update the Secrets Engine
file pins.
5. Approve the native actions and run the first attempt.
Sand-boxer's steps 2–4 in message `82faf25c` are this same sequence.
### Runtime rebuilt — 2026-10-15
Step 1 is done. The owner approved a rebuild because `ee3d0a53…` was not kept
on the workstation. Sand-boxer built `818b1b7c…` from the same pinned inputs
and builder. The digest differs only because the build-source paths are
embedded in the artifact. The bwrap startup smoke passed (sandbox `28df7a1d`)
with no credentials and no model run. `818b1b7c…` is now the install
candidate. It is not installed on railiance01 (still `b6e4e8a4…`) or admitted.
Receipt: `docs/evidence/2026-10-15-rebuilt-owner-runtime.json`. The memo still
still has to be created, admitted, loaded and accepted first. The owner steps
are in informed-decision `docs/batches/2026-10-14-three-attempts/OPERATOR.md`.
### Memo queued — 2026-10-15
Owner step 1 is in progress via the lifecycle intake path (owner's choice; the
review-record approval counts as spend authority). Workspace draft
`2ccb4bda-e318-51d2-a8dd-709db768494f`, review
`review-e2d2e98f-4701-45b0-abc1-4fe9f00bd1a8`, queued by the owner in the State
Hub memo dispatcher; approval intent `accept` by `helixforge-factory`. Record:
informed-decision `d5f1021` (sitting.json, OPERATOR.md). The accepted approval id
becomes `authority_ref` for `owner renew`. Install candidate remains `818b1b7c`.
### Workload-only Glas source integration — 2026-10-16
Reviewed and integrated the one-line runtime-contract lock advance from Glas
`30be81808140a3a3d137e731d1fe9aed59ad8581` to published workload-only credential
delivery `7357115c8311e55ee3d495f5266f501a17ec3c89`. llm-connect `37436bb5` and
sandboxer `221126cb` remain pinned. This is source integration under T07, not
native artifact installation or admission. T07 remains waiting with its existing
owner gates.
Prior preparation used rein base `be5ef400` plus isolated lock-only commit
`c811fc43` to build candidate
`634e65fee5517ebee0d1080805fd84c81bd46af26a2b38ec1ab14bd2be17bf8f`. The
candidate's installed packages passed actual SandboxManager/Glas gateway proof
with unchanged `harness.agent-dev-local@1.1.1` (still unverified): real rein/Glas
CLI and Claude 2.1.266 startup, read-only runtime, synthetic host-file and
inherited environment/child-process denial, exact task identity/private state,
deterministic workspace Git effect, task removal and destruction. A separately
labelled synthetic route fixture passed workload-only delivery, output redaction
and clean subsequent housekeeping through the same actual manager. Wrong
actor/project/run, expired execution, expiry cleanup, cancellation and
post-cancellation refusal passed. All 141 Glas tests and seven rein lock/rehearsal
tests passed; whole artifact digest remained unchanged after proof.
The first proof runner correctly encountered the manager's PermissionError for
wrong consumer but expected ValueError/RuntimeError. Its finally cleanup ran;
the runner catch was refined and the failure retained. This does not earn an
unchanged execution success. No model/provider request, real credential read,
queue access or spend occurred.
The preparation artifact is not the artifact of this integrated owner revision.
Rebuild from the exact committed owner source and verify its new digest and
actual-manager proof before considering installation. Source inputs, receipts,
reproducible proof and attempt history are retained in the
[Custodian evidence directory](../../the-custodian/docs/changes/netkingdom-target-2026-10-16/agent-isolation-next/README.md).
The same directory records the integrated-source rebuild separately.
Retain `818b1b7c`, `ee3d0a53`, installed `b6e4e8a4` and their outcomes as historical
evidence. Native acceptance still requires the queued spend memo lifecycle,
owner artifact installation/rehearsal, renewal and Secrets Engine file-pin
updates, required native approvals and bounded real-model/tool proof. Actual
interactive assignment migration and external tool-path denial remain Custodian
acceptance obligations; no profile readiness or supervision authority changed.

View file

@ -0,0 +1,262 @@
# rein-aharness — Agent Instructions
## Repo Identity
**Purpose:** Claude-Code-CLI-driven rein: governed, unattended/scheduled agent runtime
**Domain:** infotech
**Repo slug:** rein-aharness
**Topic ID:** `cee7bedf-2b48-46ef-8601-006474f2ad7a`
**Workplan prefix:** `REINAH-WP-`
`REIN-A-0001` through `REIN-A-0004` are immutable historical identifiers from
before the fleet `PREFIX-WP-NNNN` convention. `HARNESS-WP-0001` through
`HARNESS-WP-0003` and `HARNESS-WP-ADHOC-2026-09-04` are older ids from the
agent-harness name. Preserve all of these in references and archives, but use
the hub-registered `REINAH-WP-` prefix for every new workplan and task.
---
## Developer Workflow
```bash
# Install (editable, including the governed profile-execution siblings)
python3 -m venv .venv && source .venv/bin/activate
pip install -e . -e ../llm-connect -e ../sand-boxer -e ../glas-harness pytest
# Test
PYTHONPATH=".:$HOME/llm-connect" python3 -m pytest tests/ -q
# Run — bounded agentic session against a real target repo (real Claude Code
# CLI call, real commit if successful)
rein-aharness run --task-file examples/task-hello-sandbox.json --no-hub --no-metrics
# Build the Railiance deploy image
make image
```
## State Hub Integration
The Custodian State Hub tracks work across all domains. Interact via HTTP REST —
there is no MCP server for Codex agents.
| Context | URL |
|---------|-----|
| Local workstation | `http://127.0.0.1:8000` |
| Remote (railiance01, in-cluster) | `http://10.43.68.154:8000` |
| Optional local edge relay | http://127.0.0.1:18080 |
When an operator has enabled the edge relay, set API_BASE to the relay URL.
Queueable writes return an explicit queued receipt if the central hub is
unreachable. Treat that as pending local evidence, then ask the operator to run
statehub outbox status/replay after connectivity returns.
### Orient at session start
```bash
# Offline brief — works without hub connection
cat .custodian-brief.md
# Active workplans for this domain
curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-006474f2ad7a&status=active" \
| python3 -m json.tool
# Check inbox
curl -s "http://127.0.0.1:8000/messages/?to_agent=rein-aharness&unread_only=true" \
| python3 -m json.tool
```
Mark a message read:
```bash
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
-H "Content-Type: application/json" -d '{}'
```
### Log progress (required at session close)
```bash
curl -s -X POST http://127.0.0.1:8000/progress/ \
-H "Content-Type: application/json" \
-d '{
"summary": "what was done",
"event_type": "note",
"author": "codex",
"workplan_id": "<uuid>",
"task_id": "<uuid>"
}'
```
Omit `workplan_id` / `task_id` when not applicable.
### Update task status
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"status": "progress"}'
# values: wait | todo | progress | done | cancel
```
### Flag a task for human review
```bash
curl -s -X PATCH "http://127.0.0.1:8000/tasks/<task_id>" \
-H "Content-Type: application/json" \
-d '{"needs_human": true, "intervention_note": "reason"}'
```
---
## Session Protocol
**Start:**
1. `cat .custodian-brief.md` — domain goal and open workplans (offline-safe)
2. Check inbox: `GET /messages/?to_agent=rein-aharness&unread_only=true`; mark read
3. Scan workplans: `ls workplans/` — note `status: ready`, `active`, or `blocked` files and open tasks
4. Check human-needed tasks: `GET /tasks/?needs_human=true`
**During work:**
- Update task statuses in workplan files as tasks progress
- Record significant decisions via `POST /decisions/`
**Close:**
1. Update workplan file task statuses to reflect progress
2. If finishing a workplan: hand off **residuals** as live work records first
(intake with `origin: residual` + `origin_ref: <WP-id>`, or a next workplan /
decision / engagement). Do not park leftovers only in prose or `SCOPE.md`.
Canon: `the-custodian/canon/standards/work-record-types_v0.1.md` § Residuals.
3. Log: `POST /progress/` with a summary of what changed (name handoff ids)
4. After workplan file changes, run:
```bash
statehub fix-consistency
```
Coding agents should run this directly; ask the operator only if the CLI or
State Hub API is unavailable. This syncs task status from files into the hub DB.
---
## Credential and access routing
**Audience:** Codex, Claude Code, Grok, and custodian agents that call **llm-connect**
for inference. Run this check **before** requesting secrets, API keys, SSH access,
login tokens, or database passwords — in any repo, not only `ops-warden`.
ops-warden **issues SSH certificates only** (`warden sign`, `cert_command`). Every
other credential need belongs to another subsystem. **Do not** message
`ops-warden` on State Hub expecting a secret value; the reply is a pointer, not a key.
### Lookup (do this first)
```bash
warden route find "<describe your need>" --json
warden route show <catalog-id> --json
```
Requires the `warden` CLI from `~/ops-warden` (`uv tool install .` or `uv run warden`).
| Agent runtime | How to orient |
| --- | --- |
| **Codex / Grok** (shell, HTTP State Hub) | `warden route` commands above; inbox `to_agent=rein-aharness` is for coordination, not secret vending |
| **Claude Code** (MCP when available) | `get_domain_summary("custodian")` for workplans; **still** use `warden route` for credential ownership |
| **llm-connect** (inference service) | Never put secret retrieval in prompts; route custody to OpenBao/operator paths surfaced by `warden route` |
### Quick routing table
| I need… | Owner | ops-warden executes? |
| --- | --- | --- |
| SSH cert (`adm`/`agt`/`atm`) | ops-warden | **Yes** — `warden sign` |
| API key, DB password, provider token | OpenBao (`railiance-platform`) | No — route only |
| Login / OIDC / MFA | key-cape / Keycloak | No — route only |
| Authorization decision | flex-auth | No — route only |
| activity-core → issue-core emission | activity-core + issue-core | No — `warden route show activity-core-issue-sink` |
| SSH tunnel | ops-bridge (+ `cert_command` from warden) | No — route only |
### Anti-patterns (do not do these)
- `POST /messages/` to `ops-warden` asking for `ISSUE_CORE_API_KEY`, `OPENROUTER_API_KEY`, etc.
- Inventing `warden secret`, `warden login`, `warden bao`, `warden tunnel` — they do not exist
- Pasting secrets into Git, State Hub, workplans, logs, or chat
### Other capabilities (reuse-surface)
Non-credential capabilities are usually discovered through **reuse-surface** federation
(`reuse-surface` registry / `capability.*` indexes). Credential routing is inlined in
every repo's agent instructions because it is high-frequency, high-risk, and easy to
get wrong.
**Canon:** `~/ops-warden/wiki/CredentialRouting.md` · catalog `~/ops-warden/registry/routing/catalog.yaml`
<!-- REPO-AGENTS-EXTENSIONS -->
<!-- Append repo-specific agent instructions below this marker.
The state-hub template sync preserves content after this line. -->
---
## Workplan Convention (ADR-001)
Work items originate as files in this repo — not in the hub. The hub is a
read/cache/index layer that rebuilds from files.
**File location:** `workplans/REINAH-WP-NNNN-<slug>.md`
**Archived location:** finished workplans may move to
`workplans/archived/YYMMDD-REINAH-WP-NNNN-<slug>.md`. The `YYMMDD` prefix is
the completion/archive date; the frontmatter `id` does not change.
**Ad Hoc Tasks:** small opportunistic fixes discovered during a session use
`workplans/ADHOC-YYYY-MM-DD.md` with task ids `ADHOC-YYYY-MM-DD-T01`, etc. Use
this only for low-risk work completed directly; create a normal workplan for
anything needing analysis, design, approval, dependencies, or multiple phases.
**Frontmatter:**
```yaml
---
id: REINAH-WP-NNNN
type: workplan
title: "..."
domain: infotech
repo: rein-aharness
status: proposed | ready | active | blocked | backlog | finished | archived
owner: codex
topic_slug: ...
created: "YYYY-MM-DD"
updated: "YYYY-MM-DD"
state_hub_workstream_id: "<uuid>" # fix-consistency — do not edit (legacy field name; workplan UUID)
---
```
Use `proposed` for a new draft, `ready` after review against current repo
state, and `finished` after implementation. `stalled` and `needs_review` are
derived health labels, not frontmatter statuses.
**Terminology:** workplan is the fleet term; `workstream` appears only in legacy
API/MCP/frontmatter bridges until `STATE-WP-0069` retires them — see
`the-custodian/canon/standards/workplan-terminology-fleet_v0.1.md`.
**Task block format** (one per `##` section):
```
## Task Title
` ` `task
id: REINAH-WP-NNNN-T01
status: wait | todo | progress | done | cancel
priority: high | medium | low
state_hub_task_id: "<uuid>" # written by fix-consistency — do not edit
` ` `
Task description text.
```
Status progression: `todo` → `progress` → `done`; use `wait` for waiting/blocked work and `cancel` for stopped work.
**Residuals when finishing:** actionable leftovers become live work records
before `status: finished` — usually an intake (`origin: residual`,
`origin_ref: REINAH-WP-NNNN`) or a spawned workplan. Residual is a *role*,
not a kind. Fleet list lives on State Hub, not in `SCOPE.md`.
To create a new workplan:
1. Write the file following the format above
2. Run `statehub fix-consistency` locally; ask the operator only if the CLI or
State Hub API is unavailable.

View file

@ -0,0 +1,354 @@
---
id: REINAH-WP-0008
type: workplan
title: "Simpler metered retry: free rehearsal, zero-cost hold release, one-command renewal"
domain: infotech
repo: rein-aharness
status: blocked
owner: claude-code
topic_slug: rein-aharness
priority: high
created: "2026-10-13"
updated: "2026-10-16"
related:
- REINAH-WP-0003
state_hub_workstream_id: "187c2599-84a6-5764-a14f-7e9121975bf2"
---
# REINAH-WP-0008 — Simpler metered retry
## Origin
On 2026-10-13 the owner reconciled the 2026-09-27 hold for run `6efa9436`
(`docs/evidence/2026-10-13-held-liability-reconciliation.json`). A corrected
runtime `ee3d0a53…` was built
(`docs/evidence/2026-10-13-corrected-owner-runtime-build.json`). The owner
authorized a new envelope and one retry.
Preparing that retry took too many owner ceremonies:
1. The runtime is built on the workstation and copied to railiance01 by hand.
2. `owner.json` and the spend policy hold five digests that the owner edits
by hand.
3. A failed attempt keeps its full hold even when no provider request was
made, so every failure needs a new envelope, ledger and pins.
4. Every attempt needs six native approvals.
5. The launcher bug that failed run `6efa9436` was found only by spending
money. A free check on the host would have caught it.
The owner agreed (2026-10-13) to fix items 5, 3 and 2 here, and to record
the rest as decisions instead of blocking on them.
**Goal:** a retry becomes: install the artifact, rehearse for free, approve
once, run.
## A. Free rehearsal on the host
```task
id: REINAH-WP-0008-T01
status: done
priority: high
state_hub_task_id: "c6536c8d-7862-5de6-ad2b-401077b60c04"
```
Add a free rehearsal to the owner check. It runs everything
`metered-once --check` does, then executes the admitted runtime's
`rein-aharness` and `glas-harness` launchers inside bwrap from the mount
path, with no network, credentials, provider route or ledger write. The
sandbox profile is already resolved by `--check`. It prints one receipt with
the runtime digest and each launcher's exit code.
Today's launcher check reads only the first line of each launcher. This
rehearsal runs them, the same way the failing run did.
**Done when:** tests prove the rehearsal fails on a runtime whose launchers
point at a missing build directory (the `6efa9436` failure) and passes on a
relocated runtime. It must make no network calls and write nothing. The
owner procedure in `docs/owner-bootstrap.md` makes the rehearsal a required
step before any paid attempt.
### Implemented — 2026-10-13
`rein-aharness metered-once --owner-config <owner.json> --rehearse` (the flag
implies `--check`, so it never reads a provider key or claims). The code is in
`rein_aharness/owner_rehearsal.py`. It uses sand-boxer's isolation flags and
read-only system binds, with the runtime read-only at `/opt/sandboxer/runtime`,
`--unshare-net` and `--clearenv`. It runs each Python launcher with `--help`,
plus `claude --version` when `build-info.json` declares Claude. A missing
bwrap fails closed.
Tests (`tests/test_owner_rehearsal.py`, `tests/test_owner_bootstrap.py`):
- With real bwrap, a relocated runtime passes. The uv trampoline that
re-executes a deleted build interpreter fails with 127, as run `6efa9436`
did.
- The rehearsal never claims and never reads the key.
- The command line has no writable bind and no credential.
The corrected runtime `ee3d0a53…` rehearses clean: rein-aharness 0,
glas-harness 0, claude 0.
## B. Release holds that provably cost nothing
```task
id: REINAH-WP-0008-T02
status: done
priority: high
state_hub_task_id: "6a413f5a-4ac2-5d5e-9b19-71b33445c0a9"
```
`SpendLedger.reconcile` keeps the full hold (`max(held, observed)`). Add an
evidence-backed release. If the run has zero provider-request reservations
and its request route is revoked, reconciliation may set the liability to
the observed amount. Otherwise the current rule stands. The release records
which evidence allowed it, and the CLI flag says so, for example
`--release-unused-hold`.
This changes a deliberate safety rule, so it needs owner sign-off on the
exact rule before merge. That sign-off is recorded in T04's decision note.
**Done when:** tests cover all four cases. A run with no request
reservations and a revoked route releases. A run with any request
reservation, or with a live route, refuses the release. A second, identical
reconcile changes nothing. The envelope is never marked breached by a
release.
### Widened to observed-usage settlement — 2026-10-14
On 2026-10-13 the owner asked for three attempts within EUR 10 and chose
option B: settle to actual usage instead of releasing only unused holds.
Under full-hold accounting each request holds USD 4.64 (1M context plus
64k output), so EUR 10 cannot fit three attempts.
Implemented as an opt-in envelope term (`settlement: observed-usage`) plus
`max_runs`; see `docs/owner-bootstrap.md`. Under it:
- a completed request is charged its gateway-metered cost;
- runs and reconciliations are charged
`max(metered requests, reported or attested cost)`;
- unfinished requests keep their full hold, and overruns still freeze the
envelope.
The run 6efa9436 shape (failure before any request) now reconciles to its
attested cost. Defaults and existing digests are unchanged. Tests:
`tests/test_spend_settlement.py`, plus renewal cases in
`tests/test_owner_renew.py`.
Merge-ready, but the rule still needs the owner's acceptance in decision
`a93619a9`.
## D. One owner command for renewal
```task
id: REINAH-WP-0008-T03
status: done
priority: high
state_hub_task_id: "87457216-2371-51ca-b3d4-3917e6f180a2"
```
Add `rein-aharness owner renew --owner <owner.json> --runtime <digest>
--envelope <envelope.json>`. It does three things:
1. It refuses unless every reservation in the current ledger is settled.
2. It writes the new spend policy, initializes a fresh ledger, and updates
the runtime and spend-policy pins in `owner.json` together. Old files are
kept as backups. Existing ledgers are never reset.
3. It prints one summary for the owner to review: old to new runtime, old
to new policy digest, envelope cap and attempt limit, and ledger path.
Add a dry-run mode that only prints the summary. Afterwards, run the T01
rehearsal.
**Done when:** a test renews from the 2026-09-27 layout to a new envelope.
The owner check passes on the result. A second renew with an unsettled
reservation refuses. No digest has to be typed by hand.
### Implemented — 2026-10-13
The command is `rein-aharness owner renew --owner-config --policy --ledger
--runtime-path --envelope [--apply]`, in `rein_aharness/owner_renew.py`.
Without `--apply` it is a dry run. Choices:
- **Envelope file:** only the envelope fields may change. Worker, target,
profile and grant pins carry over, and `SpendPolicy` validation runs on
the result.
- **Paths stay the same:** the old ledger is renamed to
`.retired-<old envelope>`, never reset, and the old policy and owner.json
are kept as `.bak`. Secrets Engine pins the `exec_owner` files by sha256
and the ledger path through its fixed environment, so keeping the paths
means the catalog needs only new file hashes. The command prints them.
- **Refusals:** a held reservation, an open request route, an open SQLite
transaction, an owner.json that doesn't match the policy, a reused
envelope id, a disallowed field, or leftovers from an earlier renewal.
Tests (`tests/test_owner_renew.py`):
- A dry run changes nothing.
- An applied renewal passes the real owner check (`prepare`) at the
unchanged paths.
- A charged reservation, the railiance01 state after the 2026-10-13
reconciliation, renews, and the row survives in the retired ledger.
- Bad envelopes and a second apply both refuse.
Finding for T06: renewal still changes the pinned file hashes, so each
renewal needs a Secrets Engine catalog update and fresh approvals.
## C. Envelopes that cover several attempts (decision)
```task
id: REINAH-WP-0008-T04
status: done
decision_id: "a93619a9-8a70-40d8-aaf9-c29357d2a043"
priority: medium
state_hub_task_id: "898e02af-51bb-5e8d-93d0-ae908466c612"
```
Owner decision: should future envelopes be admitted as "up to N attempts
within EUR X" instead of one attempt? The EUR 10 cap already fits two
USD 4.64 request holds. Record the choice, together with T02's sign-off,
as a decision note in `docs/`. T03's envelope format then reflects it.
### Decided — 2026-10-14
The owner chose up to three attempts within EUR 10 (EUR 5 per attempt) and
observed-usage settlement, and resolved hub decision `a93619a9` with that
choice. Record: `docs/decisions/2026-10-14-spend-envelope-settlement.md`.
The terms are in spend memo `infd-20261014-b01`.
## E. Ship the owner runtime as a container image (decision)
```task
id: REINAH-WP-0008-T05
status: wait
needs_human: true
decision_id: "e6ca2f07-2708-4013-bfba-bf89fa0f1cb5"
priority: low
state_hub_task_id: "b486e86b-9d0f-501d-a394-771b40f771ee"
```
Decision for later, with Platform (railiance) and Sand-boxer: should the
owner runtime ship through the registry and GitOps, using the existing
`make image` path, instead of rsync and a manual digest check? Out of scope
here. If accepted, hand it off as a residual intake to the owning repos.
## F. Approvals per envelope (decision)
```task
id: REINAH-WP-0008-T06
status: wait
needs_human: true
decision_id: "e8986e40-f6f8-4c87-b704-bff50cc4958b"
priority: low
state_hub_task_id: "4cf721e6-724b-5c64-91bb-67b098bb97af"
```
Decision for later, with Secrets Engine: should one approval set cover an
envelope's attempts instead of six native approvals per attempt? The work
lives outside this repo. If accepted, hand it off as a residual intake.
## Retry on the corrected runtime
```task
id: REINAH-WP-0008-T07
status: wait
needs_human: true
blocking_reason: "Owner: accept spend memo infd-20261014-b01, then attended install, rehearsal, renew and approvals on railiance01"
priority: high
state_hub_task_id: "80318842-ccaa-5484-a54a-3d9981eaec8b"
```
Waits on T01 and T03. It does not need T02.
Steps:
1. Rebuild `ee3d0a53…`, or the then-current corrected runtime, from the
pinned commits.
2. Install it on railiance01.
3. Run the T01 rehearsal.
4. Renew with T03 to the owner-authorized new envelope (EUR 10, authorized
2026-10-13).
5. Run exactly one attended attempt, which consumes fresh native approvals.
Record the result in REINAH-WP-0003-T05/T06.
### Waiting on the owner — 2026-10-14
Everything this repo can do is done: the rehearsal (T01), the settlement rule
(T02, T04) and renewal (T03) are in place. Draft memo `infd-20261014-b01` is
in informed-decision `ee6a65b`. What remains needs the owner, in order:
1. Accept the memo in the attended sitting.
2. ~~Rebuild `ee3d0a53` from the pinned commits~~ (done 2026-10-15 as
`818b1b7c…`, see below) and install it on railiance01.
3. Run `metered-once --rehearse`.
4. Run `owner renew` with the memo's terms, then update the Secrets Engine
file pins.
5. Approve the native actions and run the first attempt.
Sand-boxer's steps 2–4 in message `82faf25c` are this same sequence.
### Runtime rebuilt — 2026-10-15
Step 1 is done. The owner approved a rebuild because `ee3d0a53…` was not kept
on the workstation. Sand-boxer built `818b1b7c…` from the same pinned inputs
and builder. The digest differs only because the build-source paths are
embedded in the artifact. The bwrap startup smoke passed (sandbox `28df7a1d`)
with no credentials and no model run. `818b1b7c…` is now the install
candidate. It is not installed on railiance01 (still `b6e4e8a4…`) or admitted.
Receipt: `docs/evidence/2026-10-15-rebuilt-owner-runtime.json`. The memo still
still has to be created, admitted, loaded and accepted first. The owner steps
are in informed-decision `docs/batches/2026-10-14-three-attempts/OPERATOR.md`.
### Memo queued — 2026-10-15
Owner step 1 is in progress via the lifecycle intake path (owner's choice; the
review-record approval counts as spend authority). Workspace draft
`2ccb4bda-e318-51d2-a8dd-709db768494f`, review
`review-e2d2e98f-4701-45b0-abc1-4fe9f00bd1a8`, queued by the owner in the State
Hub memo dispatcher; approval intent `accept` by `helixforge-factory`. Record:
informed-decision `d5f1021` (sitting.json, OPERATOR.md). The accepted approval id
becomes `authority_ref` for `owner renew`. Install candidate remains `818b1b7c`.
### Workload-only Glas source integration — 2026-10-16
Reviewed and integrated the one-line runtime-contract lock advance from Glas
`30be81808140a3a3d137e731d1fe9aed59ad8581` to published workload-only credential
delivery `7357115c8311e55ee3d495f5266f501a17ec3c89`. llm-connect `37436bb5` and
sandboxer `221126cb` remain pinned. This is source integration under T07, not
native artifact installation or admission. T07 remains waiting with its existing
owner gates.
Prior preparation used rein base `be5ef400` plus isolated lock-only commit
`c811fc43` to build candidate
`634e65fee5517ebee0d1080805fd84c81bd46af26a2b38ec1ab14bd2be17bf8f`. The
candidate's installed packages passed actual SandboxManager/Glas gateway proof
with unchanged `harness.agent-dev-local@1.1.1` (still unverified): real rein/Glas
CLI and Claude 2.1.266 startup, read-only runtime, synthetic host-file and
inherited environment/child-process denial, exact task identity/private state,
deterministic workspace Git effect, task removal and destruction. A separately
labelled synthetic route fixture passed workload-only delivery, output redaction
and clean subsequent housekeeping through the same actual manager. Wrong
actor/project/run, expired execution, expiry cleanup, cancellation and
post-cancellation refusal passed. All 141 Glas tests and seven rein lock/rehearsal
tests passed; whole artifact digest remained unchanged after proof.
The first proof runner correctly encountered the manager's PermissionError for
wrong consumer but expected ValueError/RuntimeError. Its finally cleanup ran;
the runner catch was refined and the failure retained. This does not earn an
unchanged execution success. No model/provider request, real credential read,
queue access or spend occurred.
The preparation artifact is not the artifact of this integrated owner revision.
Rebuild from the exact committed owner source and verify its new digest and
actual-manager proof before considering installation. Source inputs, receipts,
reproducible proof and attempt history are retained in the
[Custodian evidence directory](../../the-custodian/docs/changes/netkingdom-target-2026-10-16/agent-isolation-next/README.md).
The same directory records the integrated-source rebuild separately.
Retain `818b1b7c`, `ee3d0a53`, installed `b6e4e8a4` and their outcomes as historical
evidence. Native acceptance still requires the queued spend memo lifecycle,
owner artifact installation/rehearsal, renewal and Secrets Engine file-pin
updates, required native approvals and bounded real-model/tool proof. Actual
interactive assignment migration and external tool-path denial remain Custodian
acceptance obligations; no profile readiness or supervision authority changed.

View file

@ -0,0 +1,11 @@
{
"observed_at": "2026-10-17T06:55:02.225819+00:00",
"head": "9b88cadaa817e5abea7950b4a264d4e2739a5ddb",
"status_before": "",
"initial_review_manifest_sha256": "sha256:cae64cd2c52623dadc1905f0f418a91c99ab1eb981e02accc28855fe22c43f28",
"index_sha256": "sha256:08c186d73338c3eb03a8216c7025fdb6cb386a647802f4df7f096c2f177ff98c",
"all_8_uuids_preserved": true,
"all_7_status_human_decision_and_other_task_fields_preserved": true,
"canonical_write_pending": true,
"native_proposal_compatibility": "Original native-install R1/R2 and command helpers remain immutable. Stable REINAH-prefixed proposal ID is an opaque trial ID and is not renamed; actual canonical owner task is clarified as HARNESS-WP-0008-T07 with identical UUID80318842-ccaa-5484-a54a-3d9981eaec8b. This is no action revision, scope change or authority."
}

View file

@ -0,0 +1,116 @@
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,8 +11,10 @@
`REIN-A-0001` through `REIN-A-0004` are immutable historical identifiers from
before the fleet `PREFIX-WP-NNNN` convention. `HARNESS-WP-0001` through
-`HARNESS-WP-0003` and `HARNESS-WP-ADHOC-2026-09-04` are older ids from the
-agent-harness name. Preserve all of these in references and archives, but use
+`HARNESS-WP-0003`, `HARNESS-WP-0008` and
+`HARNESS-WP-ADHOC-2026-09-04` are preserved historical ids. Workplan 0008
+keeps its originally issued HARNESS identity after the 2026-10-17 correction
+of an erroneous REINAH relabel; its existing REINAH filename is only a path. Preserve all of these in references and archives, but use
the hub-registered `REINAH-WP-` prefix for every new workplan and task.
---
--- a/workplans/REINAH-WP-0008-simpler-metered-retry.md
+++ b/workplans/REINAH-WP-0008-simpler-metered-retry.md
@@ -1,5 +1,5 @@
---
-id: REINAH-WP-0008
+id: HARNESS-WP-0008
type: workplan
title: "Simpler metered retry: free rehearsal, zero-cost hold release, one-command renewal"
domain: infotech
@@ -9,15 +9,26 @@
topic_slug: rein-aharness
priority: high
created: "2026-10-13"
-updated: "2026-10-16"
+updated: "2026-10-17"
related:
- REINAH-WP-0003
state_hub_workstream_id: "187c2599-84a6-5764-a14f-7e9121975bf2"
---
-# REINAH-WP-0008 — Simpler metered retry
+# HARNESS-WP-0008 — Simpler metered retry
## Origin
+
+Identity correction, 2026-10-17: restore the originally issued
+`HARNESS-WP-0008` and T01–T07 canonical IDs, retaining every existing Hub UUID,
+status, decision and human/native gate. Commit `a85de661` briefly relabelled
+this workplan `REINAH-WP-0008` while preserving HARNESS-derived UUIDs in response
+to a C-26 warning. Canon grandfathering preserves existing IDs; C-26 applies
+to new plans and supplies no record-rekey authority. The REINAH filename remains
+as an existing backing path, and the erroneous label remains historical
+provenance rather than another canonical record. Future new workplans still
+use REINAH-WP. No UUID migration, retirement, human disposition, installation,
+spend authority or native acceptance is granted by this source correction.
On 2026-10-13 the owner reconciled the 2026-09-27 hold for run `6efa9436`
(`docs/evidence/2026-10-13-held-liability-reconciliation.json`). A corrected
@@ -45,7 +56,7 @@
## A. Free rehearsal on the host
```task
-id: REINAH-WP-0008-T01
+id: HARNESS-WP-0008-T01
status: done
priority: high
state_hub_task_id: "c6536c8d-7862-5de6-ad2b-401077b60c04"
@@ -91,7 +102,7 @@
## B. Release holds that provably cost nothing
```task
-id: REINAH-WP-0008-T02
+id: HARNESS-WP-0008-T02
status: done
priority: high
state_hub_task_id: "6a413f5a-4ac2-5d5e-9b19-71b33445c0a9"
@@ -140,7 +151,7 @@
## D. One owner command for renewal
```task
-id: REINAH-WP-0008-T03
+id: HARNESS-WP-0008-T03
status: done
priority: high
state_hub_task_id: "87457216-2371-51ca-b3d4-3917e6f180a2"
@@ -196,7 +207,7 @@
## C. Envelopes that cover several attempts (decision)
```task
-id: REINAH-WP-0008-T04
+id: HARNESS-WP-0008-T04
status: done
decision_id: "a93619a9-8a70-40d8-aaf9-c29357d2a043"
priority: medium
@@ -218,7 +229,7 @@
## E. Ship the owner runtime as a container image (decision)
```task
-id: REINAH-WP-0008-T05
+id: HARNESS-WP-0008-T05
status: wait
needs_human: true
decision_id: "e6ca2f07-2708-4013-bfba-bf89fa0f1cb5"
@@ -234,7 +245,7 @@
## F. Approvals per envelope (decision)
```task
-id: REINAH-WP-0008-T06
+id: HARNESS-WP-0008-T06
status: wait
needs_human: true
decision_id: "e8986e40-f6f8-4c87-b704-bff50cc4958b"
@@ -249,7 +260,7 @@
## Retry on the corrected runtime
```task
-id: REINAH-WP-0008-T07
+id: HARNESS-WP-0008-T07
status: wait
needs_human: true
blocking_reason: "Owner: accept spend memo infd-20261014-b01, then attended install, rehearsal, renew and approvals on railiance01"

View file

@ -0,0 +1,11 @@
--- a/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md
+++ b/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md
@@ -89,7 +89,7 @@
id: SAND-WP-0015-T04
status: wait
blocked_on: message-from:rein-aharness
-blocking_reason: "Tracked as REINAH-WP-0008-T07 (owner, attended): accept spend memo infd-20261014-b01 (up to three attempts within EUR 10, observed-usage settlement), rebuild/install ee3d0a53 on railiance01, rehearse, renew, then a successful attempt."
+blocking_reason: "Tracked as HARNESS-WP-0008-T07 (owner, attended): accept spend memo infd-20261014-b01 (up to three attempts within EUR 10, observed-usage settlement), rebuild/install ee3d0a53 on railiance01, rehearse, renew, then a successful attempt."
priority: high
state_hub_task_id: "58817ef6-76d9-5e34-908f-c024e6c99f93"
```

View file

@ -0,0 +1,33 @@
--- a/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md
+++ b/workplans/SAND-WP-0015-bwrap-runtime-and-private-state.md
@@ -89,7 +89,7 @@
id: SAND-WP-0015-T04
status: wait
blocked_on: message-from:rein-aharness
-blocking_reason: "Tracked as REINAH-WP-0008-T07 (owner, attended): accept spend memo infd-20261014-b01 (up to three attempts within EUR 10, observed-usage settlement), rebuild/install ee3d0a53 on railiance01, rehearse, renew, then a successful attempt."
+blocking_reason: "Tracked as HARNESS-WP-0008-T07 (owner, attended): accept spend memo infd-20261014-b01 (up to three attempts within EUR 10, observed-usage settlement), rebuild/install ee3d0a53 on railiance01, rehearse, renew, then a successful attempt."
priority: high
state_hub_task_id: "58817ef6-76d9-5e34-908f-c024e6c99f93"
```
@@ -354,12 +354,12 @@
T04 stays `wait`; the plan stays `blocked`.
2026-10-14 check: rein-aharness pushed `9a4469d` (corrected build record) and
-opened REINAH-WP-0008 (simpler metered retry, active). No railiance01 install/
+opened HARNESS-WP-0008 (simpler metered retry, active). No railiance01 install/
admission of `ee3d0a53` or successful acceptance receipt yet; request sent on
hub message `82faf25c`. T04 stays `wait`; plan stays `blocked`.
2026-10-14 rein-aharness reply (thread `82faf25c`): step 1 done (`9a4469d` on
-origin/main). Steps 2-4 are REINAH-WP-0008-T07. Budget is now up to three
+origin/main). Steps 2-4 are HARNESS-WP-0008-T07. Budget is now up to three
attempts within EUR 10 under memo `infd-20261014-b01` (observed-usage
settlement); order: memo acceptance in the attended informed-decision sitting,
rebuild and install `ee3d0a53` on railiance01, `metered-once --rehearse`,
@@ -371,5 +371,5 @@
absolute source paths (`direct_url.json`, `build-info.json`). Owner-entrypoint
startup smoke passed in sandbox `28df7a1d`; no credentials or model calls.
Sent to rein-aharness (message `8a7962f7`, thread `82faf25c`) to record as the
-then-current corrected runtime for REINAH-WP-0008-T07 and to load the unsigned
+then-current corrected runtime for HARNESS-WP-0008-T07 and to load the unsigned
memo sitting. Nothing installed on railiance01. T04 stays `wait`.

View file

@ -0,0 +1 @@
sha256:230d1ef37aa9c2023335a617492a72678c4671a8bc82d5577e9d7a7cea6e8fe4

View file

@ -0,0 +1,99 @@
{
"observed_at": "2026-10-17T06:55:41.814838+00:00",
"status": "authorized_source_identity_restored_not_published",
"source_changed_paths": [
{
"path": "AGENTS.md",
"before_sha256": "sha256:5d01af0ea255eab568ae72f326378219d1be4c77a9bc001ae12f4a1be2660034",
"after_sha256": "sha256:da71c6e3d76d7c867b2db6e67cd919fba594acc3804d3784dd744438103560c2"
},
{
"path": "workplans/REINAH-WP-0008-simpler-metered-retry.md",
"before_sha256": "sha256:9aac375bdb2e93c57cd513ab8cd5bf0c0979a0699ea0181e6d405899d906bf68",
"after_sha256": "sha256:1118924bb159f8a52a37ace8b8663c75274b89928f7a8b957638428af224b054"
}
],
"head_unchanged": true,
"index_unchanged": true,
"source_uuid_identity_consistent": true,
"all_hub_rows_and_two_dependency_rows_unchanged": true,
"task_bindings": [
{
"canonical_source_id": "HARNESS-WP-0008-T01",
"uuid": "c6536c8d-7862-5de6-ad2b-401077b60c04",
"source_derived_uuid_matches_existing": true,
"hub_unchanged": true,
"status": "done",
"needs_human": false,
"record_id": null
},
{
"canonical_source_id": "HARNESS-WP-0008-T02",
"uuid": "6a413f5a-4ac2-5d5e-9b19-71b33445c0a9",
"source_derived_uuid_matches_existing": true,
"hub_unchanged": true,
"status": "done",
"needs_human": false,
"record_id": null
},
{
"canonical_source_id": "HARNESS-WP-0008-T03",
"uuid": "87457216-2371-51ca-b3d4-3917e6f180a2",
"source_derived_uuid_matches_existing": true,
"hub_unchanged": true,
"status": "done",
"needs_human": false,
"record_id": null
},
{
"canonical_source_id": "HARNESS-WP-0008-T04",
"uuid": "898e02af-51bb-5e8d-93d0-ae908466c612",
"source_derived_uuid_matches_existing": true,
"hub_unchanged": true,
"status": "done",
"needs_human": false,
"record_id": null
},
{
"canonical_source_id": "HARNESS-WP-0008-T05",
"uuid": "b486e86b-9d0f-501d-a394-771b40f771ee",
"source_derived_uuid_matches_existing": true,
"hub_unchanged": true,
"status": "wait",
"needs_human": true,
"record_id": null
},
{
"canonical_source_id": "HARNESS-WP-0008-T06",
"uuid": "4cf721e6-724b-5c64-91bb-67b098bb97af",
"source_derived_uuid_matches_existing": true,
"hub_unchanged": true,
"status": "wait",
"needs_human": true,
"record_id": null
},
{
"canonical_source_id": "HARNESS-WP-0008-T07",
"uuid": "80318842-ccaa-5484-a54a-3d9981eaec8b",
"source_derived_uuid_matches_existing": true,
"hub_unchanged": true,
"status": "wait",
"needs_human": true,
"record_id": null
}
],
"no_hub_write": true,
"no_retirement_acknowledged": true,
"no_uuid_migration": true,
"no_sync_or_publish": true,
"driver_patches_only": true,
"native_proposals_and_helpers_unchanged": true,
"initial_review_history": "Initial review recommended separately revising native-install references; root clarified opaque stable proposal ID and pending R2 action stay immutable. The current companion mapping supersedes that recommendation without changing R1 review bytes.",
"owner_task_mapping": {
"old_erroneous_source_label": "REINAH-WP-0008-T07",
"actual_canonical_owner_task": "HARNESS-WP-0008-T07",
"unchanged_uuid": "80318842-ccaa-5484-a54a-3d9981eaec8b",
"native_action_scope_or_revision_changed": false
},
"next_step": "Root reviews two current driver-reference patches, then normal owner preparation/source publication and receiving without retirement acknowledgement; confirm 7 null record_ids converge on HARNESS IDs with no UUID/status/human-gate change."
}

View file

@ -0,0 +1,63 @@
{
"status": "source_metadata_only",
"references": [
{
"repo": "rein-aharness",
"path": "AGENTS.md",
"head_observed": "9b88cadaa817e5abea7950b4a264d4e2739a5ddb",
"sha256": "sha256:5d01af0ea255eab568ae72f326378219d1be4c77a9bc001ae12f4a1be2660034"
},
{
"repo": "rein-aharness",
"path": "workplans/REINAH-WP-0008-simpler-metered-retry.md",
"head_observed": "9b88cadaa817e5abea7950b4a264d4e2739a5ddb",
"sha256": "sha256:9aac375bdb2e93c57cd513ab8cd5bf0c0979a0699ea0181e6d405899d906bf68"
},
{
"repo": "repo-manager",
"path": "src/repo_manager/identifiers.py",
"head_observed": "3bcb1e1cf3e58b263f690534f7f28024d75cdbf6",
"sha256": "sha256:4e35fcd009c20822574fd33171176bd14a1ff2dd5af8b0bb2820954974593a2a"
},
{
"repo": "repo-manager",
"path": "docs/work-record-uuid-derivation_v1.md",
"head_observed": "3bcb1e1cf3e58b263f690534f7f28024d75cdbf6",
"sha256": "sha256:600f5c16a0f9325df56dd698311d0151571742782664a2d2bc791b8f5d964157"
},
{
"repo": "state-hub",
"path": "api/services/forge_projection.py",
"head_observed": "200cbd0a61a805d0fe29f9d6d1c49a6cf1f1703a",
"sha256": "sha256:b980e62cbd85c3e7df42b01abf14354e04133374db94af6531dcd91629c3487d"
},
{
"repo": "state-hub",
"path": "api/services/work_record_identifier_migration.py",
"head_observed": "200cbd0a61a805d0fe29f9d6d1c49a6cf1f1703a",
"sha256": "sha256:f02fc379d03b2ae78e2b2ed781430d45e025221fd55558be3df60105046ecf53"
},
{
"repo": "state-hub",
"path": "api/models/work_record_identifier_alias.py",
"head_observed": "200cbd0a61a805d0fe29f9d6d1c49a6cf1f1703a",
"sha256": "sha256:101e6013d52e5aeb66a44c8b666ff40cdbcd9e52028149c7b891d801039c71e5"
},
{
"repo": "state-hub",
"path": "tests/test_forge_projection.py",
"head_observed": "200cbd0a61a805d0fe29f9d6d1c49a6cf1f1703a",
"sha256": "sha256:3e41a2ba7b68dd86ec6d31984063d0d7846b41ec40a4545f9fcf9582c20cf9d2"
},
{
"repo": "the-custodian",
"path": "canon/standards/work-record-types_v0.1.md",
"sha256": "sha256:ab5d9de07fd899fc5334c9912571ed3d6748f2ee4bd0c9f81c5606c4727024d1"
},
{
"repo": "the-custodian",
"path": "docs/changes/netkingdom-target-2026-10-17/native-gates-r2/rein-aharness-receiving.json",
"sha256": "sha256:583561db0386f95650125045f786b96370d7743e132d24cf50342a56208333fd"
}
]
}

View file

@ -0,0 +1,103 @@
"""Hash selected public image files without pulling, installing or running it."""
from pathlib import Path, PurePosixPath
import hashlib
import io
import json
import re
import tarfile
import time
import urllib.parse
import urllib.request
BASE = Path(__file__).resolve().parent
HOST = 'https://forgejo.coulomb.social'
IMAGE = '/v2/coulomb/state-hub/'
DIGEST = 'sha256:c802690ee1811b3d77df4077936f9cdced1385514e8673fcbc9232b84d645015'
DEADLINE = time.monotonic() + 180
class NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, *args, **kwargs):
raise RuntimeError('redirect_refused')
opener = urllib.request.build_opener(urllib.request.ProxyHandler({}), NoRedirect)
def get(path, limit, token=None, accept=None):
assert time.monotonic() < DEADLINE
headers = {}
if token:
headers['Authorization'] = 'Bearer ' + token
if accept:
headers['Accept'] = accept
with opener.open(urllib.request.Request(HOST + path, headers=headers), timeout=30) as response:
raw = response.read(limit + 1)
assert len(raw) <= limit
return raw
query = urllib.parse.urlencode({'service': 'container_registry', 'scope': 'repository:coulomb/state-hub:pull'})
auth = json.loads(get('/v2/token?' + query, 65536))
token = auth.get('token', auth.get('access_token'))
assert isinstance(token, str) and 0 < len(token) < 60000
raw = get(IMAGE + 'manifests/' + DIGEST, 1048576, token,
'application/vnd.docker.distribution.manifest.v2+json')
assert 'sha256:' + hashlib.sha256(raw).hexdigest() == DIGEST
manifest = json.loads(raw)
assert manifest['schemaVersion'] == 2 and len(manifest['layers']) <= 80
assert sum(x['size'] for x in manifest['layers']) <= 500 * 1024**2
files, packages, layers = {}, {}, []
for layer in manifest['layers']:
pin = layer['digest']
assert re.fullmatch('sha256:[0-9a-f]{64}', pin) and 0 <= layer['size'] <= 160 * 1024**2
blob = get(IMAGE + 'blobs/' + pin, layer['size'], token)
assert len(blob) == layer['size'] and 'sha256:' + hashlib.sha256(blob).hexdigest() == pin
with tarfile.open(fileobj=io.BytesIO(blob), mode='r|*') as archive:
count = 0
for entry in archive:
count += 1
assert count <= 100000 and time.monotonic() < DEADLINE
path = entry.name.removeprefix('./')
assert not path.startswith('/') and '..' not in PurePosixPath(path).parts
parent, _, basename = path.rpartition('/')
# Apply OCI deletions to the selected metadata view.
if basename.startswith('.wh.'):
erased = parent + '/' + basename[4:]
for key in list(files):
if key == erased or key.startswith(erased + '/'):
del files[key]
for key in list(packages):
if key == erased or key.startswith(erased + '/'):
del packages[key]
continue
code = (path == 'app/api/services/historical_projection.py'
or path.startswith('app/migrations/versions/') and path.endswith('.py')
or '/site-packages/hub_core/' in path and path.endswith('.py')
or path.startswith('tmp/hub-core/hub_core/') and path.endswith('.py')
or path == 'tmp/.ci-build-id')
metadata = '/site-packages/' in path and path.endswith('.dist-info/METADATA')
if not code and not metadata:
continue
assert entry.isfile() and entry.size <= 2 * 1024**2
stream = archive.extractfile(entry)
content = stream.read(2 * 1024**2 + 1)
assert len(content) == entry.size
if code:
files[path] = 'sha256:' + hashlib.sha256(content).hexdigest()
if metadata:
text = content.decode('utf-8')
name = re.findall(r'^Name: ([A-Za-z0-9_.-]+)$', text, re.M)
version = re.findall(r'^Version: ([A-Za-z0-9_.+!-]+)$', text, re.M)
assert len(name) == len(version) == 1
packages[path] = {'name': name[0], 'version': version[0],
'metadata_sha256': 'sha256:' + hashlib.sha256(content).hexdigest()}
layers.append({'digest': pin, 'compressed_bytes': len(blob), 'archive_entries': count})
del blob
expected = 'sha256:' + hashlib.sha256(Path('/home/worsch/state-hub/api/services/historical_projection.py').read_bytes()).hexdigest()
assert files.get('app/api/services/historical_projection.py') == expected
assert files.get('tmp/.ci-build-id') == 'sha256:' + hashlib.sha256(b'51bc70d\n').hexdigest()
migration = 'app/migrations/versions/c4d5e6f7a8b9_inbox_feed_revision.py'
assert files.get(migration) == 'sha256:48f1730037be99b5623e878d7f20a06bec243ce27317fde81c82e16006d7b3ce'
del auth, token
result = {'schema': 'statehub.public-image-inspection.v1', 'status': 'verified_selected_image_files',
'source_revision': '51bc70d1ff8f25450a8650d8e50caf053093a02e', 'image_digest': DIGEST,
'anonymous_protocol_only': True, 'token_retained': False, 'image_executed': False,
'image_installed_or_daemon_pulled': False, 'layers': layers, 'files': files,
'python_package_metadata': packages,
'limitations': ['Selected code and package metadata inventory; no full application acceptance or hermetic build provenance.',
'Floating dependency origins remain unknown; observed bytes and versions are bound to this immutable image digest.']}
(BASE / 'statehub-public-image-inspection.json').write_text(json.dumps(result, indent=2) + '\n')
print(json.dumps({'status': result['status'], 'layers': len(layers), 'selected_files': len(files), 'packages': len(packages)}))

View file

@ -0,0 +1,368 @@
---
id: KEY-WP-0039
type: workplan
title: "Negative-check tokens for the secrets-engine OpenBao JWT role"
domain: infotech
repo: key-cape
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-10"
updated: "2026-10-17"
depends_on: []
related_repos:
- railiance-platform
- secrets-engine
state_hub_workstream_id: "09c3bddf-6058-5603-8488-cccdb7be4f86"
---
# KEY-WP-0039 - Negative-check tokens for the secrets-engine OpenBao JWT role
## Why
CCR-2026-0038 (railiance-platform RPF-WP-0035-T02) requires the OpenBao role
`secrets-engine-openbao` on `auth/keycape-services` to refuse JWTs with a wrong
`aud`, `sub`, `tenant`, `principal_type`, `roles` or `scope`, a coding-agent JWT
and an expired JWT. The positive login proof passed on 2026-10-10 (secrets-engine
`docs/evidence/2026-10-10-keycape-openbao-probe.json`). railiance-platform asked
whether KeyCape can issue the negative tokens without touching the
`secrets-engine` registration.
KeyCape signs only what a registration declares. It has no way to mint a token
with arbitrary claims, and adding one would create a forging path in the issuer.
So the negative tokens must come from real, separate registrations that differ
from `secrets-engine` in exactly one property each.
## Boundaries
- The `secrets-engine` registration is not changed.
- Probe clients are confidential `client_credentials` clients with the minimum
60-second lifetime, scopes that grant nothing, and secrets minted into custody
like any other client. They are removed after the proof.
- The probe script prints only accept or refuse booleans per case, never tokens or
secrets. It runs attended.
## T01 — Define the probe registrations
```task
id: KEY-WP-0039-T01
status: done
priority: medium
state_hub_task_id: "4d06b059-3768-53bb-83a4-a9a71c0850b8"
```
Write a non-secret request packet under `docs/` with one probe client per claim
mismatch, each identical to `secrets-engine` except for:
- `aud`: a different access-token resource audience
- `sub`: a different service subject
- `tenant`: a tenant other than `platform`
- `roles`: a role other than `secrets-engine`
- `scope`: a scope outside the role's bound scope
Two cases need no probe client. A service token always carries
`principal_type: service` (`src/internal/server/oidc/token.go`), so the wrong
`principal_type` case uses a human access token from an attended login. The
coding-agent case uses the existing `codex-railiance-platform` registration (role
`coding-agent`, `docs/openbao-service-auth-contract.md`) if it is registered live;
T01 confirms that.
The expired-JWT case reuses any probe client: request a token, wait past its
60-second lifetime, then present it.
Acceptance: railiance-platform agrees the packet covers every refusal that
CCR-2026-0038 names.
2026-10-10: railiance-platform agreed that the case list covers every
CCR-2026-0038 claim negative. The remaining CCR checks (capabilities-self deny, no
provider fallback) are theirs and secrets-engine's. Packet written at
`docs/openbao-negative-probe-clients.yaml`. It departs from the agreed list in two
ways, both sent back for re-agreement:
- **Expired case.** A token from another probe already fails a claim, so its
refusal does not show that expiry was checked. It needs a sixth client,
`openbao-probe-expiry`, identical to `secrets-engine-openbao` in every claim. That
makes it a second credential able to log in as secrets-engine for the session.
OpenBao's `expiration_leeway` (150s by default) also extends the wait.
- **Audience pin.** A service token's `aud` is its `clientId` unless `audience` is
set. Every probe except the aud probe therefore pins
`audience: secrets-engine-openbao`, so it fails only on its own claim.
Condition (a), no OpenBao custody, holds. The values still have to reach KeyCape's
environment, because KeyCape resolves `secretRef` at startup. The packet uses a
transient Kubernetes Secret that is deleted in the same session.
## T02 — Register the probe clients, run the attended probe, then remove them
```task
id: KEY-WP-0039-T02
status: wait
depends_on: [KEY-WP-0039-T01, KEY-WP-0038-T02, KEY-WP-0038-T03]
blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved."
priority: medium
state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
```
2026-10-11: requalified — live registration go caafe4be was already resolved; owner authorization present. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.
Write the attended script, which prints booleans only.
2026-10-10: script written at `scripts/openbao_negative_probe.py`, with tests in
`scripts/test_openbao_negative_probe.py`. It meets condition (b): one refused
boolean per case plus the refusal class (`claim_mismatch`, `expired`,
`not_service_principal`), and no token, secret, claim value or OpenBao error text.
A transport failure or an unexpected response is reported as `inconclusive`, never
as a refusal. A token that OpenBao accepts is revoked at once and fails the run.
Still waiting: railiance-platform's re-agreement on the two departures above, and
an attended registry session with net-kingdom. Register the clients
through the normal registration path, hand the run to railiance-platform, record
a value-free receipt in `docs/evidence/`, and deregister the clients afterwards.
Acceptance: every case is refused, the receipt is committed, and the probe clients
are gone.
2026-10-11 source preparation: T01 remains progress because the exact expiry
client and audience-pin amendments require platform-owner re-agreement; the
prior agreement covered the earlier case list, not this revised packet. The
prepared runner revision adds a valid expiry positive control and immediate
revoke, exact decoded claim-isolation preflight (not signature evidence),
semantic refusal classes, bounded expiry TTL and redirect refusal. Seventeen
local tests pass; original-source semantic regressions demonstrate two false
successes. See `docs/openbao-negative-probe-session-proposal.md` for exact owner
returns, live authority, effective leeway, custody boundaries and cleanup. No
live token, registry or OpenBao action occurred. T02 continues to wait for owner
re-agreement plus separate attended live authorization. MASON-WP-0009-T05
operator/ESO capability and nonmember checks remain independent acceptance.
2026-10-11: Platform independently re-agreed the final amended design (session proposal SHA25623f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09). Original conditional review and narrow confirmation are retained in railiance-platform/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review*. Source acceptance T01 is complete; actual registry/leeway/readiness, founder live go, negative/cleanup and separate capability/no-fallback obligations remain T02 and owner plans.
2026-10-17 Custodian coordinator finalization review (not a KeyCape formal owner
acknowledgment): the [infrastructure assessment](../../the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-infra-finalization-r1/ASSESSMENT.md)
confirms all 14 executable/test/producer-decision pins and three Platform source
pins still match. Source reviews remain complete: T01/T03 done, T02 wait. Existing
source acceptance remains valid. Resolved registration go caafe4be covers only
the original secrets-engine-openbao client (KEY-WP-0037-T03, deployed 6f79a2af,
NK-WP-0046 exact-match helper); it does not authorize new probe clients or
coding-client custody, and no renewed original-client approval is requested.
The standing attended rule remains separate, for reviewed commands within
existing scope; no new protocol or blanket approval request is introduced. The
remaining sequence is KEY-WP-0038-T02/T03 first-case registration/claims proof,
admission of the existing coding-client proposal with sanitized owner readiness
returns, then one attended negative/revoke/cleanup receipt committed with probe
clients removed. No native acceptance or credential/registration action is
inferred from this coordinator review.
## KeyCape issuer-source compatibility inspection for lifecycle v2
```task
id: KEY-WP-0039-T03
status: done
priority: medium
state_hub_task_id: "54d37ad8-2352-51c2-bb2e-af6070dbbd73"
```
2026-10-11: Completed KeyCape's own issuer-source inspection for
KEY-WP-0039-T02/attended-lifecycle-v2, exact manifest SHA256
6fc0fe47f99a97c11c6e7538f5ea02054335df7d46055d3f84bed4e194aeaaed.
Read AGENTS.md, governing INTENT.md, current client-credentials claim source,
registration fragments, native caller-authentication commands and frozen v2
issuer transport/probe wiring. Seventeen unchanged runner tests pass locally.
This task completes only issuer-source inspection; the resulting narrow
source acknowledgment is in docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json (retained frozen prior acknowledgment).
T01 stays done and T02 stays open for actual native registration, token input,
refusal and cleanup proof. No Secret, token, credential or native environment was accessed; no owner
checkout was changed during this source inspection. Owner source files were
read for the bounded issuer compatibility check.
Current token.go:306-309 returns HTTP 401 invalid_profile_usage with
feature=client_id for a removed confidential client; wrong-secret
feature=Authorization cannot count as removed-registration evidence. The
proposal and packet comments are narrowly corrected against that current
issuer contract. Prior T01/source re-agreement history is preserved; this source
acknowledgment supplies no NetKingdom runtime-owner review or founder live go.
The private token-producer handoff remains a real execution gap under T02.
Existing keycape login/service-token commands require a new private output
file; login also needs a concrete admitted public PKCE client/callback and
attended browser/MFA. The lifecycle expects an in-memory private stdin object
containing human_token and coding_agent_token, but no reviewed file-to-process
or process-to-process producer, admitted human client/callback, custody-injected
coding-agent source, native placement or producer cleanup receipt has been
provided. No unattended token producer or admitted secret route is invented.
Resolve those exact owner contracts before founder native approval; existing
static registration is not live token custody or producer admission.
2026-10-11 same T03 source-inspection refinement for lifecycle V4: completed
KeyCape issuer-facing inspection before the new formal acknowledgment in
docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json. Exact V4
manifest SHA256 d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8,
executor b4687c2d07819c43b45d0cddcad0488b62eb49175b2a06a43f7806a0838d3a4d.
NetKingdom's independent V4 source review is approved, receipt SHA256
4f4498203a31bd46dbc3715c0df1cbb9eee63bed5b2c1baf430ff3c4a37c13cb.
The V2/V3 unsuccessful reviews and earlier KeyCape acknowledgment remain
retained as one correction trial; they are not extra scored successes.
V4 retains the unchanged issuer runner and six-client claim design, and fixes
finite/nonboolean windows, true total request deadlines, late-known OpenBao
session private revoke/refusal and guarded bounded final Secret deletion.
The issuer-facing acknowledgement covers only stopping late token exchanges,
claim source/shape and preserving token cleanup failure/unknown outcomes; it
does not replace NetKingdom runtime or Platform backend acceptance. Tests: 41
supplied lifecycle, 17 unchanged runner and 17 independent owner checks pass
locally. No native tokens, credentials, issuer mutations or live cleanup were
performed. T01 stays done; T02 stays open and the genuine private token-producer
handoff remains its next source prerequisite before actual founder native go.
2026-10-11 same T03 source-inspection refinement for lifecycle V5: completed
KeyCape's own current issuer-source/claim/cleanup-contract inspection before
any new formal acknowledgment. Current token.go:306-309 still distinguishes
removed-client invalid_profile_usage feature=client_id from wrong-secret
feature=Authorization. The six-client design, audience source and expiry
positive control remain unchanged, and the original runner's 17 tests pass.
Exact V5 manifest SHA256
d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285;
executor 3bcceebc845b17492040c76223d77088257c547a2b890198e7b5c2b04a0cd360.
V5 has 46 passing supplied lifecycle checks. It adds a single exceptional
response-close abort under a fixed20ms cap clipped to nonresettable aggregate
cleanup, and to positive original request time remaining, with expired aggregate
zero calls, fixed private errors and truthful known-session revoke/unknown
outcomes. This inspection covers issuer-facing token lifetime and claim
interfaces only; NetKingdom and Platform independently own their runtime and
backend review decisions. Previous V2/V3 Net source refusals and Platform V4
exceptional-close refusal remain retained; KeyCape's V2/V4 acknowledgments are
historical narrow source inspections, never aggregate native approval.
T01 remains done, T02 open. No native actions or credentials occurred. The real
private token-producer/client/callback/coding-agent custody/cleanup handoff gap
stays the next prerequisite; no unattended admission is inferred.
Exact independent V5 source decisions were then verified: NetKingdom approved
receipt SHA256 32a62bd1ca1bd7311965d016d4d6a6e4d9177f3423c63add436d9d309865eacc
(46 supplied +17 runner +21 independent checks); Platform approved receipt
SHA256 ba872dc53061b1dcca9dea1e8897597c394c1f9365e0fc49ee6f2095f57f11e0
(46 supplied +5 independent +17 previously verified runner checks). Those are
foreign owners' own source decisions, not KeyCape runtime/backend reviews or
founder native authority. Formal KeyCape bounded issuer acknowledgment follows
in docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json.
2026-10-11 T02 continuing source preparation: private producer decision packet
`KEY-WP-0039-T02/private-producer-handoff-v1` stages supported existing CLI
flows, selected existing netkingdom-bootstrap-console literal-loopback human
client subject to owner purpose/MFA/listener admission, and the existing
codex-railiance-platform source identity subject to actual live/custody/consumer
admission. No existing coding-agent delivery receipt was found; AppRole tokens
do not substitute for issuer JWTs. A bounded pipe-only adapter for the current
CLI's two caller-owned private files has 18 passing synthetic checks, with
identity-guarded explicit file consumption and fixed value-free failure output.
Decoded claim shape does not prove signatures or authenticated admission.
See docs/openbao-negative-token-producer-decision.md and .json for exact owner
choices and missing receipts. The exact trusted workstation-to-node launcher,
private-file allowance and railiance01 Bao route/public-health guard still need
source review and founder disposition before any token/temporary credential/
mutation/restart. No registration, custody, login, native action or credential
request occurred. V5 and failed review histories retained; T01 stays done and
T02 open. No new work record or native acceptance is inferred.
2026-10-11 same T02 source-preparation correction for
KEY-WP-0039-T02/private-producer-handoff-v2: NetKingdom's exact V1 consumer
refusal remains recorded (no child-ready gate, stdio alias, non-atomic unlink).
V2 has source-pinned private launch/ready handshake compatible with exact
Net V9 manifest84542ddc: fresh nonce/source/envelope/health/window marker before
ANY caller-token file load/consume/write; separate local data/control pipes
reject kernel identity aliases of stdin/stdout/stderr. Adapter read/write budget
only clips downward after readiness. 31 distinct producer/launcher synthetic
checks and 74 compatible consumer supplied checks pass. No SSH/native/login or
credential access was performed. Stat/unlink remains non-atomic and requires
continuously exclusive sameUID custody; the retained race test is a custody
violation counterexample, not a claim of repaired filesystem atomicity. Receipt
keeps atomic delete/copy cleanup unverified and possible copies true. Exact
trusted launcher/directSSH profile/private tmpfs allowance, selected human
client/MFA purpose reuse and coding-agent registration/custody/delivery remain
unadmitted owner choices. Native node route, role/leeway/window/founder decision
and actual negative/cleanup acceptance remain pending. T01done and T02open; no
new task or plan, and V1/V5-V8/current failure histories are not overwritten.
2026-10-11 same T02 producer-preparation correction V3: Platform V2 pair
source refusal RPF-KEY39-PAIR-V2-F01 is retained. Private write uncertainty
now propagates into sticky remote consumer uncertainty before launcher early
return; local SSH termination never proves remote no-effects/recovery. Actual
full-payload then interrupted confirmation and4096-byte partialpipe failures
are retained regressions. 33 distinct source checks pass. Exact Net V9 source
bytes unchanged; new source reviews required, not implicit repin. Native
client/MFA/coding-agent custody/transport/tmpfsfile and actualroute/session
authority remain unadmitted. T01done/T02open; no new task/plan or native action.
2026-10-11 same T02 producer-preparation V4: exact V3 Net consumer refusal
NET-PRODUCER-V3-F01 retained. Actual CLI contract acquisition now bounded
nofollow/nonblocking regular stable reads and one ORIGINAL5-second preparation
budget across contract/source/envelope/SSHspawn/ready/file/delivery phases, not
a renewed launcher allowance. 40 own source tests plus9 retained paired
wrapper/child handshake regressions pass with synthetic/no native transport.
V3stickyremoteunknown/full+partialdelivery regressions retained; Net V9 exact
bytes unchanged. Prior issuer/custody/transport/file/route/readiness-Argo/role/
maintenanceexception/founder choices remain unadmitted. No SSH/network/Secrets/
credentials/native/owner checkout changes; no task/plan duplication. T01done
and T02open; source correction is not native acceptance.
2026-10-11 T02 source-design disposition checkpoint: founder accepted exact
source memo `infd-20261011-key39-source-design-r1`, review
`review-62626f54-0212-4684-9b45-0a7222d61957` version1 at `2026-10-11T17:19:01Z`.
Disposition `disp-870f9c1b-0444-4216-ae7f-f5463d590e36`, approval
`ca118afc-d746-4454-a5b9-9b387b120cd9`; authenticated person subject
`uid=platform-root,ou=people,dc=netkingdom,dc=local`. Exact canonical public review URL:
`https://decisions.coulomb.social/review?memo_id=review-62626f54-0212-4684-9b45-0a7222d61957`.
Root's authenticated confirmed readback is
`the-custodian/docs/evidence/2026-10-10-security-consolidation/key39-source-design-publication/founder-source-disposition-readback-r1.json`,
SHA256 `453213194d605336e9c424976f1ac27691def39e225026f0a99628d1cfc97d1d`; observed
`2026-10-11T17:30:47.080081+00:00`. Source commands SHA256
`36e20813596f3ddbb86e01fd0b838867ece2f4916c5880305cf12cd61b567f89` and source inventory SHA256
`aea196db9e513900d5e7165455d6e9c3dc68749a606dc219ec340ac595642cc0` bind the accepted source direction.
Acceptance covers recommended source preparation for existing bootstrap-console
human-client purpose reuse, returning existing coding-agent custody admission or
preparing a narrow owner proposal, proposed founder directSSH source profile,
and conditional exclusive-owner tmpfs files. It does not establish actual
registration/MFA/listener, coding-agent custody/delivery, transport/file admission,
node-local Bao route, issuer/OpenBao role/leeway/maintenance readiness or fresh
attended native go. The published choice documents remain historical exact
pending proposals; this appended receipt records their later source disposition.
T02 remains wait on external owner custody/admission and separate attended
execution authorization. Source acceptance permits further source preparation;
actual registry, token exchange, negative probe, revoke and cleanup remain pending. T01 and T03
remain done. No native or credential action is authorized by this source receipt.
See docs/proposals/2026-10-11-key39-next-execution-prerequisites.json for exact
owner returns and safe metadata evidence needed before preparing a runnable
attended execution proposal.
2026-10-11 ordered-goal qualification (T02 only): the final source runner already
exists and its 17 local semantic tests pass. No new helper or source protocol is
needed to repeat accepted preparation. Native execution follows the first
Forgejo SecurityCase registration and claims proof (KEY-WP-0038-T02/T03);
only those task metadata were checked, not wait-task bodies. NetKingdom owns the
registry/env/restart session, KeyCape owns issuance/isolation, and Platform owns
leeway/refusal/capability/no-fallback/cleanup proof. The live registration go is
already recorded resolved; do not ask again. Attended human authentication and
owner-held existing coding-agent custody go on the single attended checklist.
No credentials, live registry/API changes or KEY-WP-0040 recovery work occurred.
2026-10-17 Platform owner returns (source-prepared, not admitted):
railiance-platform `docs/proposals/2026-10-17-key39-t02-owner-returns.md`
(commit `0a9617a`). The coding client `codex-railiance-platform` has no
admission, custody or delivery record. The existing narrow proposal
`docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json` (`9460f6f`)
remains the one to admit, so the coding-agent case stays unrunnable until owners
admit it. The role source (`openbao/auth/secrets-engine-jwt-role.json`) matches
the packet's claim design. It declares no `expiration_leeway`, so the expiry
probe must assume OpenBao's 150s default until an attended sanitized read
confirms the live value. The node-local Bao origin is not declared in source and
also needs that attended read. T02 stays wait.

View file

@ -0,0 +1,368 @@
---
id: KEY-WP-0039
type: workplan
title: "Negative-check tokens for the secrets-engine OpenBao JWT role"
domain: infotech
repo: key-cape
status: active
flavor: implementation
owner: claude-code
topic_slug: netkingdom
created: "2026-10-10"
updated: "2026-10-17"
depends_on: []
related_repos:
- railiance-platform
- secrets-engine
state_hub_workstream_id: "09c3bddf-6058-5603-8488-cccdb7be4f86"
---
# KEY-WP-0039 - Negative-check tokens for the secrets-engine OpenBao JWT role
## Why
CCR-2026-0038 (railiance-platform RPF-WP-0035-T02) requires the OpenBao role
`secrets-engine-openbao` on `auth/keycape-services` to refuse JWTs with a wrong
`aud`, `sub`, `tenant`, `principal_type`, `roles` or `scope`, a coding-agent JWT
and an expired JWT. The positive login proof passed on 2026-10-10 (secrets-engine
`docs/evidence/2026-10-10-keycape-openbao-probe.json`). railiance-platform asked
whether KeyCape can issue the negative tokens without touching the
`secrets-engine` registration.
KeyCape signs only what a registration declares. It has no way to mint a token
with arbitrary claims, and adding one would create a forging path in the issuer.
So the negative tokens must come from real, separate registrations that differ
from `secrets-engine` in exactly one property each.
## Boundaries
- The `secrets-engine` registration is not changed.
- Probe clients are confidential `client_credentials` clients with the minimum
60-second lifetime, scopes that grant nothing, and secrets minted into custody
like any other client. They are removed after the proof.
- The probe script prints only accept or refuse booleans per case, never tokens or
secrets. It runs attended.
## T01 — Define the probe registrations
```task
id: KEY-WP-0039-T01
status: done
priority: medium
state_hub_task_id: "4d06b059-3768-53bb-83a4-a9a71c0850b8"
```
Write a non-secret request packet under `docs/` with one probe client per claim
mismatch, each identical to `secrets-engine` except for:
- `aud`: a different access-token resource audience
- `sub`: a different service subject
- `tenant`: a tenant other than `platform`
- `roles`: a role other than `secrets-engine`
- `scope`: a scope outside the role's bound scope
Two cases need no probe client. A service token always carries
`principal_type: service` (`src/internal/server/oidc/token.go`), so the wrong
`principal_type` case uses a human access token from an attended login. The
coding-agent case uses the existing `codex-railiance-platform` registration (role
`coding-agent`, `docs/openbao-service-auth-contract.md`) if it is registered live;
T01 confirms that.
The expired-JWT case reuses any probe client: request a token, wait past its
60-second lifetime, then present it.
Acceptance: railiance-platform agrees the packet covers every refusal that
CCR-2026-0038 names.
2026-10-10: railiance-platform agreed that the case list covers every
CCR-2026-0038 claim negative. The remaining CCR checks (capabilities-self deny, no
provider fallback) are theirs and secrets-engine's. Packet written at
`docs/openbao-negative-probe-clients.yaml`. It departs from the agreed list in two
ways, both sent back for re-agreement:
- **Expired case.** A token from another probe already fails a claim, so its
refusal does not show that expiry was checked. It needs a sixth client,
`openbao-probe-expiry`, identical to `secrets-engine-openbao` in every claim. That
makes it a second credential able to log in as secrets-engine for the session.
OpenBao's `expiration_leeway` (150s by default) also extends the wait.
- **Audience pin.** A service token's `aud` is its `clientId` unless `audience` is
set. Every probe except the aud probe therefore pins
`audience: secrets-engine-openbao`, so it fails only on its own claim.
Condition (a), no OpenBao custody, holds. The values still have to reach KeyCape's
environment, because KeyCape resolves `secretRef` at startup. The packet uses a
transient Kubernetes Secret that is deleted in the same session.
## T02 — Register the probe clients, run the attended probe, then remove them
```task
id: KEY-WP-0039-T02
status: wait
depends_on: [KEY-WP-0039-T01, KEY-WP-0038-T02, KEY-WP-0038-T03]
blocking_reason: "Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established."
priority: medium
state_hub_task_id: "03da4c38-1dac-53b1-8b0b-e697df48e9d3"
```
2026-10-11: requalified — live registration go caafe4be was already resolved; owner authorization present. See the-custodian docs/decisions/2026-10-11-security-unblock-decisions.md.
Write the attended script, which prints booleans only.
2026-10-10: script written at `scripts/openbao_negative_probe.py`, with tests in
`scripts/test_openbao_negative_probe.py`. It meets condition (b): one refused
boolean per case plus the refusal class (`claim_mismatch`, `expired`,
`not_service_principal`), and no token, secret, claim value or OpenBao error text.
A transport failure or an unexpected response is reported as `inconclusive`, never
as a refusal. A token that OpenBao accepts is revoked at once and fails the run.
Still waiting: railiance-platform's re-agreement on the two departures above, and
an attended registry session with net-kingdom. Register the clients
through the normal registration path, hand the run to railiance-platform, record
a value-free receipt in `docs/evidence/`, and deregister the clients afterwards.
Acceptance: every case is refused, the receipt is committed, and the probe clients
are gone.
2026-10-11 source preparation: T01 remains progress because the exact expiry
client and audience-pin amendments require platform-owner re-agreement; the
prior agreement covered the earlier case list, not this revised packet. The
prepared runner revision adds a valid expiry positive control and immediate
revoke, exact decoded claim-isolation preflight (not signature evidence),
semantic refusal classes, bounded expiry TTL and redirect refusal. Seventeen
local tests pass; original-source semantic regressions demonstrate two false
successes. See `docs/openbao-negative-probe-session-proposal.md` for exact owner
returns, live authority, effective leeway, custody boundaries and cleanup. No
live token, registry or OpenBao action occurred. T02 continues to wait for owner
re-agreement plus separate attended live authorization. MASON-WP-0009-T05
operator/ESO capability and nonmember checks remain independent acceptance.
2026-10-11: Platform independently re-agreed the final amended design (session proposal SHA25623f13d2a5a4204f749f35c5d44ef39d213838cd2fce1c36fb4775254ef1c7f09). Original conditional review and narrow confirmation are retained in railiance-platform/docs/reviews/2026-10-11-keycape-openbao-negative-session-owner-review*. Source acceptance T01 is complete; actual registry/leeway/readiness, founder live go, negative/cleanup and separate capability/no-fallback obligations remain T02 and owner plans.
2026-10-17 Custodian coordinator finalization review (not a KeyCape formal owner
acknowledgment): the [infrastructure assessment](../../the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-infra-finalization-r1/ASSESSMENT.md)
confirms all 14 executable/test/producer-decision pins and three Platform source
pins still match. Source reviews remain complete: T01/T03 done, T02 wait. Existing
source acceptance remains valid. Resolved registration go caafe4be covers only
the original secrets-engine-openbao client (KEY-WP-0037-T03, deployed 6f79a2af,
NK-WP-0046 exact-match helper); it does not authorize new probe clients or
coding-client custody, and no renewed original-client approval is requested.
The standing attended rule remains separate, for reviewed commands within
existing scope; no new protocol or blanket approval request is introduced. The
remaining sequence is KEY-WP-0038-T02/T03 first-case registration/claims proof,
admission of the existing coding-client proposal with sanitized owner readiness
returns, then one attended negative/revoke/cleanup receipt committed with probe
clients removed. No native acceptance or credential/registration action is
inferred from this coordinator review.
## KeyCape issuer-source compatibility inspection for lifecycle v2
```task
id: KEY-WP-0039-T03
status: done
priority: medium
state_hub_task_id: "54d37ad8-2352-51c2-bb2e-af6070dbbd73"
```
2026-10-11: Completed KeyCape's own issuer-source inspection for
KEY-WP-0039-T02/attended-lifecycle-v2, exact manifest SHA256
6fc0fe47f99a97c11c6e7538f5ea02054335df7d46055d3f84bed4e194aeaaed.
Read AGENTS.md, governing INTENT.md, current client-credentials claim source,
registration fragments, native caller-authentication commands and frozen v2
issuer transport/probe wiring. Seventeen unchanged runner tests pass locally.
This task completes only issuer-source inspection; the resulting narrow
source acknowledgment is in docs/reviews/2026-10-11-key39-lifecycle-v2-keycape-source-ack.json (retained frozen prior acknowledgment).
T01 stays done and T02 stays open for actual native registration, token input,
refusal and cleanup proof. No Secret, token, credential or native environment was accessed; no owner
checkout was changed during this source inspection. Owner source files were
read for the bounded issuer compatibility check.
Current token.go:306-309 returns HTTP 401 invalid_profile_usage with
feature=client_id for a removed confidential client; wrong-secret
feature=Authorization cannot count as removed-registration evidence. The
proposal and packet comments are narrowly corrected against that current
issuer contract. Prior T01/source re-agreement history is preserved; this source
acknowledgment supplies no NetKingdom runtime-owner review or founder live go.
The private token-producer handoff remains a real execution gap under T02.
Existing keycape login/service-token commands require a new private output
file; login also needs a concrete admitted public PKCE client/callback and
attended browser/MFA. The lifecycle expects an in-memory private stdin object
containing human_token and coding_agent_token, but no reviewed file-to-process
or process-to-process producer, admitted human client/callback, custody-injected
coding-agent source, native placement or producer cleanup receipt has been
provided. No unattended token producer or admitted secret route is invented.
Resolve those exact owner contracts before founder native approval; existing
static registration is not live token custody or producer admission.
2026-10-11 same T03 source-inspection refinement for lifecycle V4: completed
KeyCape issuer-facing inspection before the new formal acknowledgment in
docs/reviews/2026-10-11-key39-lifecycle-v4-keycape-source-ack.json. Exact V4
manifest SHA256 d079f85c47d5c2635e1ee633ea4d1346b1737ce2be9ca273f22a38e620f418d8,
executor b4687c2d07819c43b45d0cddcad0488b62eb49175b2a06a43f7806a0838d3a4d.
NetKingdom's independent V4 source review is approved, receipt SHA256
4f4498203a31bd46dbc3715c0df1cbb9eee63bed5b2c1baf430ff3c4a37c13cb.
The V2/V3 unsuccessful reviews and earlier KeyCape acknowledgment remain
retained as one correction trial; they are not extra scored successes.
V4 retains the unchanged issuer runner and six-client claim design, and fixes
finite/nonboolean windows, true total request deadlines, late-known OpenBao
session private revoke/refusal and guarded bounded final Secret deletion.
The issuer-facing acknowledgement covers only stopping late token exchanges,
claim source/shape and preserving token cleanup failure/unknown outcomes; it
does not replace NetKingdom runtime or Platform backend acceptance. Tests: 41
supplied lifecycle, 17 unchanged runner and 17 independent owner checks pass
locally. No native tokens, credentials, issuer mutations or live cleanup were
performed. T01 stays done; T02 stays open and the genuine private token-producer
handoff remains its next source prerequisite before actual founder native go.
2026-10-11 same T03 source-inspection refinement for lifecycle V5: completed
KeyCape's own current issuer-source/claim/cleanup-contract inspection before
any new formal acknowledgment. Current token.go:306-309 still distinguishes
removed-client invalid_profile_usage feature=client_id from wrong-secret
feature=Authorization. The six-client design, audience source and expiry
positive control remain unchanged, and the original runner's 17 tests pass.
Exact V5 manifest SHA256
d1f997624db46693a7126a6eb6435f6b3a353d5bd67dcc8a539683e4b7147285;
executor 3bcceebc845b17492040c76223d77088257c547a2b890198e7b5c2b04a0cd360.
V5 has 46 passing supplied lifecycle checks. It adds a single exceptional
response-close abort under a fixed20ms cap clipped to nonresettable aggregate
cleanup, and to positive original request time remaining, with expired aggregate
zero calls, fixed private errors and truthful known-session revoke/unknown
outcomes. This inspection covers issuer-facing token lifetime and claim
interfaces only; NetKingdom and Platform independently own their runtime and
backend review decisions. Previous V2/V3 Net source refusals and Platform V4
exceptional-close refusal remain retained; KeyCape's V2/V4 acknowledgments are
historical narrow source inspections, never aggregate native approval.
T01 remains done, T02 open. No native actions or credentials occurred. The real
private token-producer/client/callback/coding-agent custody/cleanup handoff gap
stays the next prerequisite; no unattended admission is inferred.
Exact independent V5 source decisions were then verified: NetKingdom approved
receipt SHA256 32a62bd1ca1bd7311965d016d4d6a6e4d9177f3423c63add436d9d309865eacc
(46 supplied +17 runner +21 independent checks); Platform approved receipt
SHA256 ba872dc53061b1dcca9dea1e8897597c394c1f9365e0fc49ee6f2095f57f11e0
(46 supplied +5 independent +17 previously verified runner checks). Those are
foreign owners' own source decisions, not KeyCape runtime/backend reviews or
founder native authority. Formal KeyCape bounded issuer acknowledgment follows
in docs/reviews/2026-10-11-key39-lifecycle-v5-keycape-source-ack.json.
2026-10-11 T02 continuing source preparation: private producer decision packet
`KEY-WP-0039-T02/private-producer-handoff-v1` stages supported existing CLI
flows, selected existing netkingdom-bootstrap-console literal-loopback human
client subject to owner purpose/MFA/listener admission, and the existing
codex-railiance-platform source identity subject to actual live/custody/consumer
admission. No existing coding-agent delivery receipt was found; AppRole tokens
do not substitute for issuer JWTs. A bounded pipe-only adapter for the current
CLI's two caller-owned private files has 18 passing synthetic checks, with
identity-guarded explicit file consumption and fixed value-free failure output.
Decoded claim shape does not prove signatures or authenticated admission.
See docs/openbao-negative-token-producer-decision.md and .json for exact owner
choices and missing receipts. The exact trusted workstation-to-node launcher,
private-file allowance and railiance01 Bao route/public-health guard still need
source review and founder disposition before any token/temporary credential/
mutation/restart. No registration, custody, login, native action or credential
request occurred. V5 and failed review histories retained; T01 stays done and
T02 open. No new work record or native acceptance is inferred.
2026-10-11 same T02 source-preparation correction for
KEY-WP-0039-T02/private-producer-handoff-v2: NetKingdom's exact V1 consumer
refusal remains recorded (no child-ready gate, stdio alias, non-atomic unlink).
V2 has source-pinned private launch/ready handshake compatible with exact
Net V9 manifest84542ddc: fresh nonce/source/envelope/health/window marker before
ANY caller-token file load/consume/write; separate local data/control pipes
reject kernel identity aliases of stdin/stdout/stderr. Adapter read/write budget
only clips downward after readiness. 31 distinct producer/launcher synthetic
checks and 74 compatible consumer supplied checks pass. No SSH/native/login or
credential access was performed. Stat/unlink remains non-atomic and requires
continuously exclusive sameUID custody; the retained race test is a custody
violation counterexample, not a claim of repaired filesystem atomicity. Receipt
keeps atomic delete/copy cleanup unverified and possible copies true. Exact
trusted launcher/directSSH profile/private tmpfs allowance, selected human
client/MFA purpose reuse and coding-agent registration/custody/delivery remain
unadmitted owner choices. Native node route, role/leeway/window/founder decision
and actual negative/cleanup acceptance remain pending. T01done and T02open; no
new task or plan, and V1/V5-V8/current failure histories are not overwritten.
2026-10-11 same T02 producer-preparation correction V3: Platform V2 pair
source refusal RPF-KEY39-PAIR-V2-F01 is retained. Private write uncertainty
now propagates into sticky remote consumer uncertainty before launcher early
return; local SSH termination never proves remote no-effects/recovery. Actual
full-payload then interrupted confirmation and4096-byte partialpipe failures
are retained regressions. 33 distinct source checks pass. Exact Net V9 source
bytes unchanged; new source reviews required, not implicit repin. Native
client/MFA/coding-agent custody/transport/tmpfsfile and actualroute/session
authority remain unadmitted. T01done/T02open; no new task/plan or native action.
2026-10-11 same T02 producer-preparation V4: exact V3 Net consumer refusal
NET-PRODUCER-V3-F01 retained. Actual CLI contract acquisition now bounded
nofollow/nonblocking regular stable reads and one ORIGINAL5-second preparation
budget across contract/source/envelope/SSHspawn/ready/file/delivery phases, not
a renewed launcher allowance. 40 own source tests plus9 retained paired
wrapper/child handshake regressions pass with synthetic/no native transport.
V3stickyremoteunknown/full+partialdelivery regressions retained; Net V9 exact
bytes unchanged. Prior issuer/custody/transport/file/route/readiness-Argo/role/
maintenanceexception/founder choices remain unadmitted. No SSH/network/Secrets/
credentials/native/owner checkout changes; no task/plan duplication. T01done
and T02open; source correction is not native acceptance.
2026-10-11 T02 source-design disposition checkpoint: founder accepted exact
source memo `infd-20261011-key39-source-design-r1`, review
`review-62626f54-0212-4684-9b45-0a7222d61957` version1 at `2026-10-11T17:19:01Z`.
Disposition `disp-870f9c1b-0444-4216-ae7f-f5463d590e36`, approval
`ca118afc-d746-4454-a5b9-9b387b120cd9`; authenticated person subject
`uid=platform-root,ou=people,dc=netkingdom,dc=local`. Exact canonical public review URL:
`https://decisions.coulomb.social/review?memo_id=review-62626f54-0212-4684-9b45-0a7222d61957`.
Root's authenticated confirmed readback is
`the-custodian/docs/evidence/2026-10-10-security-consolidation/key39-source-design-publication/founder-source-disposition-readback-r1.json`,
SHA256 `453213194d605336e9c424976f1ac27691def39e225026f0a99628d1cfc97d1d`; observed
`2026-10-11T17:30:47.080081+00:00`. Source commands SHA256
`36e20813596f3ddbb86e01fd0b838867ece2f4916c5880305cf12cd61b567f89` and source inventory SHA256
`aea196db9e513900d5e7165455d6e9c3dc68749a606dc219ec340ac595642cc0` bind the accepted source direction.
Acceptance covers recommended source preparation for existing bootstrap-console
human-client purpose reuse, returning existing coding-agent custody admission or
preparing a narrow owner proposal, proposed founder directSSH source profile,
and conditional exclusive-owner tmpfs files. It does not establish actual
registration/MFA/listener, coding-agent custody/delivery, transport/file admission,
node-local Bao route, issuer/OpenBao role/leeway/maintenance readiness or fresh
attended native go. The published choice documents remain historical exact
pending proposals; this appended receipt records their later source disposition.
T02 remains wait on external owner custody/admission and separate attended
execution authorization. Source acceptance permits further source preparation;
actual registry, token exchange, negative probe, revoke and cleanup remain pending. T01 and T03
remain done. No native or credential action is authorized by this source receipt.
See docs/proposals/2026-10-11-key39-next-execution-prerequisites.json for exact
owner returns and safe metadata evidence needed before preparing a runnable
attended execution proposal.
2026-10-11 ordered-goal qualification (T02 only): the final source runner already
exists and its 17 local semantic tests pass. No new helper or source protocol is
needed to repeat accepted preparation. Native execution follows the first
Forgejo SecurityCase registration and claims proof (KEY-WP-0038-T02/T03);
only those task metadata were checked, not wait-task bodies. NetKingdom owns the
registry/env/restart session, KeyCape owns issuance/isolation, and Platform owns
leeway/refusal/capability/no-fallback/cleanup proof. The live registration go is
already recorded resolved; do not ask again. Attended human authentication and
owner-held existing coding-agent custody go on the single attended checklist.
No credentials, live registry/API changes or KEY-WP-0040 recovery work occurred.
2026-10-17 Platform owner returns (source-prepared, not admitted):
railiance-platform `docs/proposals/2026-10-17-key39-t02-owner-returns.md`
(commit `0a9617a`). The coding client `codex-railiance-platform` has no
admission, custody or delivery record. The existing narrow proposal
`docs/proposals/2026-10-11-key39-coding-client-custody-proposal.json` (`9460f6f`)
remains the one to admit, so the coding-agent case stays unrunnable until owners
admit it. The role source (`openbao/auth/secrets-engine-jwt-role.json`) matches
the packet's claim design. It declares no `expiration_leeway`, so the expiry
probe must assume OpenBao's 150s default until an attended sanitized read
confirms the live value. The node-local Bao origin is not declared in source and
also needs that attended read. T02 stays wait.

View file

@ -0,0 +1,21 @@
from pathlib import Path
import subprocess,json,hashlib,re,yaml,urllib.request
root=Path('/home/worsch/key-cape');out=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-finalization-blocking-reason-r1');out.mkdir()
rel='workplans/KEY-WP-0039-openbao-negative-probe-tokens.md';p=root/rel
def git(*args):return subprocess.check_output(['git','-C',str(root),*args]).decode()
base=git('rev-parse','HEAD').strip();assert base=='26ec9bb9b195ad377448570d6b161fd682311a0b';assert not git('status','--porcelain=v1');assert git('ls-remote','origin','refs/heads/main').split()[0]==base
with urllib.request.urlopen('http://127.0.0.1:8000/tasks/03da4c38-1dac-53b1-8b0b-e697df48e9d3',timeout=30) as response:task_before=json.load(response)
assert task_before.get('needs_human') is False
before=p.read_text();old='registration authorization already resolved.';new='original secrets-engine-openbao registration go remains resolved; probe/coding-client custody and attended readiness are not established.';assert before.count(old)==1;after=before.replace(old,new)
blocks=lambda text:[yaml.safe_load(t) for t in re.findall(r'^```task\n(.*?)^```',text,re.M|re.S)]
b=blocks(before);a=blocks(after);assert len(a)==len(b)==3
for oldblock,newblock in zip(b,a):
if oldblock['id']=='KEY-WP-0039-T02':
expected={**oldblock,'blocking_reason':oldblock['blocking_reason'].replace(old,new)};assert newblock==expected
else:assert oldblock==newblock
assert before.split('---',2)[1]==after.split('---',2)[1]
(out/'KEY39.preimage.md').write_text(before);(out/'KEY39.proposed.md').write_text(after)
baseline={'base_commit':base,'branch':git('branch','--show-current').strip(),'status':git('status','--porcelain=v1'),'index':git('ls-files','--stage'),'path':rel,'before_sha256':hashlib.sha256(before.encode()).hexdigest(),'after_sha256':hashlib.sha256(after.encode()).hexdigest(),'frontmatter':yaml.safe_load(before.split('---',2)[1]),'task_blocks':b,'task_blocks_after':a,'only_task_field_delta':{'task':'KEY-WP-0039-T02','field':'blocking_reason','old':b[1]['blocking_reason'],'new':a[1]['blocking_reason']},'needs_human_before':False,'task_before_public_metadata':{k:task_before.get(k) for k in ('id','status','needs_human','workplan_id','blocking_reason')}};(out/'baseline.json').write_text(json.dumps(baseline,indent=2)+'\n')
p.write_text(after);(out/'one-path.patch').write_text(git('diff','--',rel));assert git('diff','--name-only').splitlines()==[rel]
print(json.dumps({'status':'only authorized blocking_reason suffix changed','needs_human':False,'all_other_task_and_frontmatter_fields_unchanged':True,'base':base}))

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,173 @@
{
"schema": "state-hub.repository-projection-reconcile.v1",
"instance_role": "primary",
"instance_label": "railiance01",
"expected_commit": "d803e9282a47b49daf930fb905312fa5104c0ace",
"derived_commit": "d803e9282a47b49daf930fb905312fa5104c0ace",
"outcome": {
"schema": "state-hub.projection-reset.v1",
"repo_slug": "key-cape",
"commit": "d803e9282a47b49daf930fb905312fa5104c0ace",
"status": "applied",
"counts": {
"created": 0,
"updated": 44,
"retired": 0,
"refused": 0,
"released": 0,
"created_tasks": 0,
"updated_tasks": 0,
"cancelled_tasks": 0,
"created_edges": 0,
"removed_edges": 0,
"unresolved_edges": 1,
"external_edges": 8
},
"created": [],
"updated": [
"ADHOC-2026-09-05",
"ADHOC-2026-09-07",
"ADHOC-2026-09-27",
"KEY-WP-0001",
"KEY-WP-0002",
"KEY-WP-0003",
"KEY-WP-0004",
"KEY-WP-0005",
"KEY-WP-0006",
"KEY-WP-0007",
"KEY-WP-0008",
"KEY-WP-0009",
"KEY-WP-0010",
"KEY-WP-0011",
"KEY-WP-0012",
"KEY-WP-0013",
"KEY-WP-0014",
"KEY-WP-0015",
"KEY-WP-0016",
"KEY-WP-0017",
"KEY-WP-0018",
"KEY-WP-0019",
"KEY-WP-0020",
"KEY-WP-0021",
"KEY-WP-0022",
"KEY-WP-0023",
"KEY-WP-0024",
"KEY-WP-0025",
"KEY-WP-0026",
"KEY-WP-0027",
"KEY-WP-0028",
"KEY-WP-0029",
"KEY-WP-0030",
"KEY-WP-0031",
"KEY-WP-0032",
"KEY-WP-0033",
"KEY-WP-0034",
"KEY-WP-0035",
"KEY-WP-0036",
"KEY-WP-0037",
"KEY-WP-0038",
"KEY-WP-0039",
"KEY-WP-0040",
"KEY-WP-0041"
],
"retired": [],
"released": [],
"created_tasks": [],
"updated_tasks": [],
"cancelled_tasks": [],
"created_edges": [],
"removed_edges": [],
"unresolved_edges": [
{
"workplan": "KEY-WP-0010",
"from": "KEY-WP-0010",
"target": "RAILIANCE-WP-0027",
"reason": "missing"
}
],
"external_edges": [
{
"workplan": "KEY-WP-0038",
"from": "KEY-WP-0038-T03",
"target": "KEY-WP-0038-T02",
"relationship_type": "starts_after",
"from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9",
"from_task_id": "40ddaa29-7245-508c-ad48-ddc063e25115",
"to_workplan_id": null,
"to_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
},
{
"workplan": "KEY-WP-0038",
"from": "KEY-WP-0038-T02",
"target": "KEY-WP-0038-T01",
"relationship_type": "starts_after",
"from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9",
"from_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30",
"to_workplan_id": null,
"to_task_id": "5f019d13-1fa3-5b88-a27a-75a3339ca96e"
},
{
"workplan": "KEY-WP-0038",
"from": "KEY-WP-0038-T02",
"target": "NK-WP-0048-T06",
"relationship_type": "starts_after",
"from_workplan_id": "098e3e2c-a138-560e-b2d3-3a15eb49d8a9",
"from_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30",
"to_workplan_id": null,
"to_task_id": "d9b26bcf-a7c6-59a2-92a0-f913d76a203c"
},
{
"workplan": "KEY-WP-0039",
"from": "KEY-WP-0039-T02",
"target": "KEY-WP-0038-T03",
"relationship_type": "starts_after",
"from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
"from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
"to_workplan_id": null,
"to_task_id": "40ddaa29-7245-508c-ad48-ddc063e25115"
},
{
"workplan": "KEY-WP-0039",
"from": "KEY-WP-0039-T02",
"target": "KEY-WP-0039-T01",
"relationship_type": "starts_after",
"from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
"from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
"to_workplan_id": null,
"to_task_id": "4d06b059-3768-53bb-83a4-a9a71c0850b8"
},
{
"workplan": "KEY-WP-0039",
"from": "KEY-WP-0039-T02",
"target": "KEY-WP-0038-T02",
"relationship_type": "starts_after",
"from_workplan_id": "09c3bddf-6058-5603-8488-cccdb7be4f86",
"from_task_id": "03da4c38-1dac-53b1-8b0b-e697df48e9d3",
"to_workplan_id": null,
"to_task_id": "ddcfb482-8f33-5c81-9ce8-84be8fa5dc30"
},
{
"workplan": "KEY-WP-0007",
"from": "KEY-WP-0007",
"target": "KEY-WP-0005",
"relationship_type": "blocks",
"from_workplan_id": "24db4178-705c-5b09-b7db-ee7a345d71a0",
"from_task_id": null,
"to_workplan_id": "711a8303-69aa-5db6-9043-92530c6dc81b",
"to_task_id": null
},
{
"workplan": "KEY-WP-0009",
"from": "KEY-WP-0009",
"target": "KEY-WP-0006",
"relationship_type": "blocks",
"from_workplan_id": "c1a9b1cc-2ff0-566a-b544-1a2ef967fc0d",
"from_task_id": null,
"to_workplan_id": "d8d20d8f-61e0-54e2-9a88-a3958f0b6f2d",
"to_task_id": null
}
],
"refused": [],
"notes": []
}
}

View file

@ -0,0 +1,40 @@
from pathlib import Path
import hashlib,json,subprocess,urllib.request,urllib.error,yaml,re
root=Path('/home/worsch/key-cape');out=Path('/home/worsch/the-custodian/docs/changes/netkingdom-target-2026-10-17/key39-finalization-blocking-reason-r1');base='http://127.0.0.1:8000';head='d803e9282a47b49daf930fb905312fa5104c0ace'
def git(*args):return subprocess.check_output(['git','-C',str(root),*args]).decode()
assert git('rev-parse','HEAD').strip()==head
assert git('ls-remote','origin','refs/heads/main').split()[0]==head
assert not git('status','--porcelain=v1')
def get(path):
with urllib.request.urlopen(base+path,timeout=30) as response:return json.load(response)
health=get('/state/health');assert (health['instance_role'],health['instance_label'])==('primary','railiance01')
request_path='/repos/key-cape/work-record-projection/reconcile';body={'expected_commit':head,'acknowledge_retirements':False}
digest=hashlib.sha256(json.dumps({'method':'POST','path':request_path,'body':body},sort_keys=True,separators=(',',':')).encode()).hexdigest()
request=urllib.request.Request(base+request_path,data=json.dumps(body).encode(),method='POST',headers={'Content-Type':'application/json','Idempotency-Key':'rmgr-projection:v2:'+digest,'X-StateHub-Source-Agent':'custodian-codex'})
try:
with urllib.request.urlopen(request,timeout=120) as response:receipt=json.load(response)
except urllib.error.HTTPError as error:
raw=error.read();(out/'canonical-receiver-refusal.json').write_bytes(raw);raise
(out/'canonical-receiver.json').write_text(json.dumps(receipt,indent=2)+'\n')
assert receipt['expected_commit']==receipt['derived_commit']==head
assert (receipt['instance_role'],receipt['instance_label'])==('primary','railiance01')
assert receipt['outcome']['status'] in ('applied','noop'),receipt['outcome']['status']
snapshot=get('/repos/key-cape/work-record-projection/snapshot');baseline=json.loads((out/'baseline.json').read_text())
planid=baseline['frontmatter']['state_hub_workstream_id'];plan=next(w for w in snapshot['workplans'] if w['id']==planid)
rows=[]
for block in baseline['task_blocks']:
task=next(t for t in snapshot['tasks'] if t['id']==block['state_hub_task_id'])
assert task['status']==block['status'];rows.append({'record_id':block['id'],'state_hub_task_id':task['id'],'status':task['status'],'workplan_id':task.get('workplan_id'),'depends_on_source':block.get('depends_on',[])})
current=(root/baseline['path']).read_text();assert current==(out/'KEY39.proposed.md').read_text()
actual_blocks=[yaml.safe_load(t) for t in re.findall(r'^```task\n(.*?)^```',current,re.M|re.S)]
assert actual_blocks==baseline['task_blocks_after']
for before,after in zip(baseline['task_blocks'],actual_blocks):
expected=dict(before)
if before['id']=='KEY-WP-0039-T02':expected['blocking_reason']=baseline['only_task_field_delta']['new']
assert after==expected
task_after=get('/tasks/03da4c38-1dac-53b1-8b0b-e697df48e9d3')
assert task_after.get('needs_human') is False
assert task_after.get('blocking_reason')==baseline['only_task_field_delta']['new']
verification={'commit':head,'receiver_status':receipt['outcome']['status'],'acknowledge_retirements':False,'instance_role':receipt['instance_role'],'instance_label':receipt['instance_label'],'workplan':{'id':planid,'status':plan['status'],'record_id':'KEY-WP-0039'},'task_bindings':rows,'all_other_task_fields_unchanged':True,'only_task_field_delta':baseline['only_task_field_delta'],'needs_human':False,'primary_task_blocking_reason':task_after['blocking_reason'],'frontmatter_unchanged':True,'only_committed_path':baseline['path'],'remote_forge_matches':True,'owner_worktree_and_index_clean':not git('status','--porcelain=v1'),'normal_sync_refusal':'unrelated grandfathered ADHOC-2026-09-27-T02 UUID; not modified','native_acceptance_inferred':False}
assert plan['status']==baseline['frontmatter']['status']
(out/'final-verification.json').write_text(json.dumps(verification,indent=2)+'\n');print(json.dumps(verification,indent=2))

View file

@ -0,0 +1,12 @@
{
"commit": "d803e9282a47b49daf930fb905312fa5104c0ace",
"parent": "26ec9bb9b195ad377448570d6b161fd682311a0b",
"paths": [
"workplans/KEY-WP-0039-openbao-negative-probe-tokens.md"
],
"normal_hook": "scanner8.30.1 clean",
"only_task_field_delta": "KEY-WP-0039-T02 blocking_reason suffix; all other fields unchanged",
"native_or_credential_action": false,
"needs_human": false,
"needs_human_preserved": true
}

View file

@ -0,0 +1,37 @@
<!-- custodian-brief: generated by fix-consistency — do not edit manually -->
# Custodian Brief — key-cape
**Domain:** infotech
**Last synced:** 2026-10-17 02:16 UTC
**State Hub:** http://127.0.0.1:8000 *(adjust if running on a remote machine)*
## Active Workstreams
### Negative-check tokens for the secrets-engine OpenBao JWT role
Progress: 2/3 done | workplan_id: `09c3bddf-6058-5603-8488-cccdb7be4f86`
**Open tasks:**
- ! T01 — Define the probe registrations `03da4c38`
*(wait: Ordered identity gate: Forgejo first SecurityCase registration/claims proof, then NetKingdom registry session and attended human/coding-agent custody; registration authorization already resolved.)*
### Forgejo as a NetKingdom OIDC client, so the forge can close
Progress: 2/4 done | workplan_id: `098e3e2c-a138-560e-b2d3-3a15eb49d8a9`
**Open tasks:**
- ! T01 — Define what Forgejo receives `ddcfb482`
*(wait: Registration runs as the first KnownSecurityCase of type oidc-browser-relying-party (NK-WP-0048-T06), not as a hand-built rollout.)*
- ! T02 — Register the client and hand over its secret `40ddaa29`
*(wait: Claims are verified against the registered client.)*
## Inbox Hygiene
**Stale unread:** 2 message(s) older than 3 day(s) — triage at session start.
**Missing thread_id:** 2 unread message(s) lack supersession chains.
---
## MCP Orientation (when available)
If the state-hub MCP server is reachable, call:
`get_domain_summary("infotech")`
This provides richer cross-domain context.
If the MCP call fails, use this file as your orientation source.

View file

@ -0,0 +1,224 @@
# Work Records — key-cape
> Generated by `statehub fix-consistency` (CUST-WP-0061-T04, work-record
> stage 3). Do not edit by hand — edit the source file/block listed for
> each record and re-run fix-consistency to refresh this index. Archived
> workplans are omitted; closed decisions/intakes/engagements stay listed
> so recently-resolved work is still visible. [auto]
| Kind | ID | Status | Lane | Source |
| --- | --- | --- | --- | --- |
| workplan | ADHOC-2026-09-05 | finished | — | workplans/ADHOC-2026-09-05.md |
| workplan | ADHOC-2026-09-07 | finished | — | workplans/ADHOC-2026-09-07.md |
| workplan | ADHOC-2026-09-27 | finished | — | workplans/ADHOC-2026-09-27.md |
| workplan | KEY-WP-0001 | finished | — | workplans/KEY-WP-0001-keycape-implementation.md |
| workplan | KEY-WP-0002 | finished | — | workplans/KEY-WP-0002-container-image-gitea.md |
| workplan | KEY-WP-0003 | finished | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
| workplan | KEY-WP-0004 | finished | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| workplan | KEY-WP-0005 | finished | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
| workplan | KEY-WP-0006 | finished | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
| workplan | KEY-WP-0007 | finished | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
| workplan | KEY-WP-0008 | finished | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
| workplan | KEY-WP-0009 | finished | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
| workplan | KEY-WP-0010 | finished | — | workplans/KEY-WP-0010-openbao-operator-loopback-callback.md |
| workplan | KEY-WP-0011 | finished | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
| workplan | KEY-WP-0012 | finished | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
| workplan | KEY-WP-0013 | finished | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| workplan | KEY-WP-0014 | finished | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
| workplan | KEY-WP-0015 | finished | — | workplans/KEY-WP-0015-scope-intent-assessment.md |
| workplan | KEY-WP-0016 | finished | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
| workplan | KEY-WP-0017 | finished | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
| workplan | KEY-WP-0018 | finished | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
| workplan | KEY-WP-0019 | finished | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
| workplan | KEY-WP-0020 | finished | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
| workplan | KEY-WP-0021 | finished | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
| workplan | KEY-WP-0022 | finished | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
| workplan | KEY-WP-0023 | finished | — | workplans/KEY-WP-0023-live-migration-proof.md |
| workplan | KEY-WP-0024 | finished | — | workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md |
| workplan | KEY-WP-0025 | finished | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
| workplan | KEY-WP-0026 | finished | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
| workplan | KEY-WP-0027 | finished | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
| workplan | KEY-WP-0028 | finished | — | workplans/KEY-WP-0028-browser-client-field-validation.md |
| workplan | KEY-WP-0029 | finished | — | workplans/KEY-WP-0029-scope-reassessment.md |
| workplan | KEY-WP-0030 | finished | — | workplans/KEY-WP-0030-tenant-precondition-guard.md |
| workplan | KEY-WP-0031 | finished | — | workplans/KEY-WP-0031-gate-house-tenant-ruling.md |
| workplan | KEY-WP-0032 | finished | — | workplans/KEY-WP-0032-principal-type-provenance-guard.md |
| workplan | KEY-WP-0033 | finished | — | workplans/KEY-WP-0033-vergabe-fresh-login.md |
| workplan | KEY-WP-0034 | finished | — | workplans/KEY-WP-0034-account-recovery.md |
| workplan | KEY-WP-0035 | finished | — | workplans/KEY-WP-0035-optional-mfa.md |
| workplan | KEY-WP-0036 | finished | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
| workplan | KEY-WP-0037 | finished | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
| workplan | KEY-WP-0038 | active | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| workplan | KEY-WP-0039 | active | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
| workplan | KEY-WP-0040 | finished | — | workplans/KEY-WP-0040-mfa-attended-run-findings.md |
| workplan | KEY-WP-0041 | finished | — | workplans/KEY-WP-0041-sign-in-diagnosis-page.md |
| task | ADHOC-2026-09-05-T01 | done | — | workplans/ADHOC-2026-09-05.md |
| task | ADHOC-2026-09-07-T01 | done | — | workplans/ADHOC-2026-09-07.md |
| task | ADHOC-2026-09-27-T01 | done | — | workplans/ADHOC-2026-09-27.md |
| task | ADHOC-2026-09-27-T02 | done | — | workplans/ADHOC-2026-09-27.md |
| task | KEY-WP-0001-T01 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T02 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T03 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T04 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T05 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T06 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T07 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T08 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T09 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T10 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T11 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T12 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T13 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T14 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T15 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T16 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T17 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T18 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T19 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T20 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T21 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T22 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0001-T23 | done | — | workplans/KEY-WP-0001-keycape-implementation.md |
| task | KEY-WP-0002-T01 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
| task | KEY-WP-0002-T02 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
| task | KEY-WP-0002-T03 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
| task | KEY-WP-0002-T04 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
| task | KEY-WP-0002-T05 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
| task | KEY-WP-0002-T06 | done | — | workplans/KEY-WP-0002-container-image-gitea.md |
| task | KEY-WP-0003-T01 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
| task | KEY-WP-0003-T02 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
| task | KEY-WP-0003-T03 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
| task | KEY-WP-0003-T04 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
| task | KEY-WP-0003-T05 | done | — | workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md |
| task | KEY-WP-0004-T01 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| task | KEY-WP-0004-T02 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| task | KEY-WP-0004-T03 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| task | KEY-WP-0004-T04 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| task | KEY-WP-0004-T05 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| task | KEY-WP-0004-T06 | cancel | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| task | KEY-WP-0004-T07 | done | — | workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md |
| task | KEY-WP-0005-T01 | done | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
| task | KEY-WP-0005-T02 | done | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
| task | KEY-WP-0005-T03 | done | — | workplans/KEY-WP-0005-iam-profile-core-claims.md |
| task | KEY-WP-0006-T01 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
| task | KEY-WP-0006-T02 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
| task | KEY-WP-0006-T03 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
| task | KEY-WP-0006-T04 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
| task | KEY-WP-0006-T05 | done | — | workplans/KEY-WP-0006-client-credentials-service-tokens.md |
| task | KEY-WP-0007-T01 | done | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
| task | KEY-WP-0007-T02 | done | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
| task | KEY-WP-0007-T03 | done | — | workplans/KEY-WP-0007-user-engine-portal-oidc-client.md |
| task | KEY-WP-0008-T01 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
| task | KEY-WP-0008-T02 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
| task | KEY-WP-0008-T03 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
| task | KEY-WP-0008-T04 | done | — | workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md |
| task | KEY-WP-0009-T01 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
| task | KEY-WP-0009-T02 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
| task | KEY-WP-0009-T03 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
| task | KEY-WP-0009-T04 | done | — | workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md |
| task | KEY-WP-0010-T01 | done | — | workplans/KEY-WP-0010-openbao-operator-loopback-callback.md |
| task | KEY-WP-0011-T01 | done | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
| task | KEY-WP-0011-T02 | done | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
| task | KEY-WP-0011-T03 | done | — | workplans/KEY-WP-0011-live-secret-exposure-recovery.md |
| task | KEY-WP-0012-T01 | done | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
| task | KEY-WP-0012-T02 | done | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
| task | KEY-WP-0012-T03 | done | — | workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md |
| task | KEY-WP-0013-T01 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| task | KEY-WP-0013-T02 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| task | KEY-WP-0013-T03 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| task | KEY-WP-0013-T04 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| task | KEY-WP-0013-T05 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| task | KEY-WP-0013-T06 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| task | KEY-WP-0013-T07 | done | — | workplans/KEY-WP-0013-approval-engine-resource-audience.md |
| task | KEY-WP-0014-T01 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
| task | KEY-WP-0014-T02 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
| task | KEY-WP-0014-T03 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
| task | KEY-WP-0014-T04 | done | — | workplans/KEY-WP-0014-native-credential-lane-handoff.md |
| task | KEY-WP-0015-T01 | done | — | workplans/KEY-WP-0015-scope-intent-assessment.md |
| task | KEY-WP-0016-T01 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
| task | KEY-WP-0016-T02 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
| task | KEY-WP-0016-T03 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
| task | KEY-WP-0016-T04 | done | — | workplans/KEY-WP-0016-authorization-code-protocol-hardening.md |
| task | KEY-WP-0017-T01 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
| task | KEY-WP-0017-T02 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
| task | KEY-WP-0017-T03 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
| task | KEY-WP-0017-T04 | done | — | workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md |
| task | KEY-WP-0018-T01 | done | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
| task | KEY-WP-0018-T02 | done | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
| task | KEY-WP-0018-T03 | done | — | workplans/KEY-WP-0018-export-completeness-evidence.md |
| task | KEY-WP-0019-T01 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
| task | KEY-WP-0019-T02 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
| task | KEY-WP-0019-T03 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
| task | KEY-WP-0019-T04 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
| task | KEY-WP-0019-T05 | done | — | workplans/KEY-WP-0019-upstream-provider-token-verification.md |
| task | KEY-WP-0020-T01 | done | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
| task | KEY-WP-0020-T02 | done | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
| task | KEY-WP-0020-T03 | done | — | workplans/KEY-WP-0020-migration-contract-preservation.md |
| task | KEY-WP-0021-T01 | done | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
| task | KEY-WP-0021-T02 | done | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
| task | KEY-WP-0021-T03 | done | — | workplans/KEY-WP-0021-snapshot-attribute-validation.md |
| task | KEY-WP-0022-T01 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
| task | KEY-WP-0022-T02 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
| task | KEY-WP-0022-T03 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
| task | KEY-WP-0022-T04 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
| task | KEY-WP-0022-T05 | done | — | workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md |
| task | KEY-WP-0023-T01 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
| task | KEY-WP-0023-T02 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
| task | KEY-WP-0023-T03 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
| task | KEY-WP-0023-T04 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
| task | KEY-WP-0023-T05 | done | — | workplans/KEY-WP-0023-live-migration-proof.md |
| task | KEY-WP-0024-T01 | done | — | workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md |
| task | KEY-WP-0024-T02 | done | — | workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md |
| task | KEY-WP-0025-T01 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
| task | KEY-WP-0025-T02 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
| task | KEY-WP-0025-T03 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
| task | KEY-WP-0025-T04 | done | — | workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md |
| task | KEY-WP-0026-T01 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
| task | KEY-WP-0026-T02 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
| task | KEY-WP-0026-T03 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
| task | KEY-WP-0026-T04 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
| task | KEY-WP-0026-T05 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
| task | KEY-WP-0026-T06 | done | — | workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md |
| task | KEY-WP-0027-T01 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
| task | KEY-WP-0027-T02 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
| task | KEY-WP-0027-T03 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
| task | KEY-WP-0027-T04 | done | — | workplans/KEY-WP-0027-rollout-readiness-and-live-state.md |
| task | KEY-WP-0028-T01 | done | — | workplans/KEY-WP-0028-browser-client-field-validation.md |
| task | KEY-WP-0028-T02 | done | — | workplans/KEY-WP-0028-browser-client-field-validation.md |
| task | KEY-WP-0029-T01 | done | — | workplans/KEY-WP-0029-scope-reassessment.md |
| task | KEY-WP-0029-T02 | done | — | workplans/KEY-WP-0029-scope-reassessment.md |
| task | KEY-WP-0029-T03 | done | — | workplans/KEY-WP-0029-scope-reassessment.md |
| task | KEY-WP-0030-T01 | done | — | workplans/KEY-WP-0030-tenant-precondition-guard.md |
| task | KEY-WP-0030-T02 | done | — | workplans/KEY-WP-0030-tenant-precondition-guard.md |
| task | KEY-WP-0031-T01 | done | — | workplans/KEY-WP-0031-gate-house-tenant-ruling.md |
| task | KEY-WP-0031-T02 | done | — | workplans/KEY-WP-0031-gate-house-tenant-ruling.md |
| task | KEY-WP-0032-T01 | done | — | workplans/KEY-WP-0032-principal-type-provenance-guard.md |
| task | KEY-WP-0032-T02 | done | — | workplans/KEY-WP-0032-principal-type-provenance-guard.md |
| task | KEY-WP-0033-T01 | done | — | workplans/KEY-WP-0033-vergabe-fresh-login.md |
| task | KEY-WP-0033-T02 | done | — | workplans/KEY-WP-0033-vergabe-fresh-login.md |
| task | KEY-WP-0034-T01 | done | — | workplans/KEY-WP-0034-account-recovery.md |
| task | KEY-WP-0034-T02 | done | — | workplans/KEY-WP-0034-account-recovery.md |
| task | KEY-WP-0035-T01 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
| task | KEY-WP-0035-T02 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
| task | KEY-WP-0035-T03 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
| task | KEY-WP-0035-T04 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
| task | KEY-WP-0035-T05 | done | — | workplans/KEY-WP-0035-optional-mfa.md |
| task | KEY-WP-0036-T01 | done | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
| task | KEY-WP-0036-T02 | cancel | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
| task | KEY-WP-0036-T03 | done | — | workplans/KEY-WP-0036-platform-factor-recovery.md |
| task | KEY-WP-0037-T01 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
| task | KEY-WP-0037-T02 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
| task | KEY-WP-0037-T03 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
| task | KEY-WP-0037-T04 | done | — | workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md |
| task | KEY-WP-0038-T01 | done | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| task | KEY-WP-0038-T02 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| task | KEY-WP-0038-T03 | wait | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| task | KEY-WP-0038-T04 | cancel | — | workplans/KEY-WP-0038-forgejo-oidc-client.md |
| task | KEY-WP-0039-T01 | done | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
| task | KEY-WP-0039-T02 | wait | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
| task | KEY-WP-0039-T03 | done | — | workplans/KEY-WP-0039-openbao-negative-probe-tokens.md |
| task | KEY-WP-0040-T01 | done | — | workplans/KEY-WP-0040-mfa-attended-run-findings.md |
| task | KEY-WP-0040-T02 | done | — | workplans/KEY-WP-0040-mfa-attended-run-findings.md |
| task | KEY-WP-0041-T01 | done | — | workplans/KEY-WP-0041-sign-in-diagnosis-page.md |
| task | KEY-WP-0041-T02 | done | — | workplans/KEY-WP-0041-sign-in-diagnosis-page.md |
| intake | KEY-IN-0001 | closed | — | intakes/intakes.md |

View file

@ -0,0 +1,232 @@
{
".custodian-brief.md": {
"sha256": "96d84539b3a92786bff37fcc70161adec4648b9547287e52e6b817bc080a080a",
"mode": 436,
"index": "100644 d3b36c3bffbeb922769a2a788e35b4113fccaa3f 0\t.custodian-brief.md\n"
},
"WORK-RECORDS.md": {
"sha256": "431842547bd82750f284f8c1c9f519892f1bb1c1a684b9b1e38adc6fdfdc299d",
"mode": 420,
"index": "100644 d86b29bbc480acfa6d12d5052039578d7d4e380c 0\tWORK-RECORDS.md\n"
},
"workplans/ADHOC-2026-09-05.md": {
"sha256": "76123d22d0c1efc51212f8914e386f078b3c6d908959af6978ae73874e492b04",
"mode": 420,
"index": "100644 f7e78675df62c9b48e29f882fa99298d6d8ce7f1 0\tworkplans/ADHOC-2026-09-05.md\n"
},
"workplans/ADHOC-2026-09-07.md": {
"sha256": "c335ebfe91dad291277e8f17034ac8f785b3bc767f281f205c446c08f03d7978",
"mode": 420,
"index": "100644 17401073c053c9710e6e680243bbd313b08d1fc1 0\tworkplans/ADHOC-2026-09-07.md\n"
},
"workplans/ADHOC-2026-09-27.md": {
"sha256": "2794d1940fc3c5e4a2df57de9d4b742fa6aa8602b67f1d3177c0d96bc5aaf314",
"mode": 420,
"index": "100644 0b755547aa7d5589f0898df2f102a45b6aa24838 0\tworkplans/ADHOC-2026-09-27.md\n"
},
"workplans/KEY-WP-0001-keycape-implementation.md": {
"sha256": "29a2bf4892b7185e28e9126a3d8311d0fed34a876f416a3e75c605915fde0969",
"mode": 420,
"index": "100644 74334411cd056e7c7253e84f1c4b40c42eb13de3 0\tworkplans/KEY-WP-0001-keycape-implementation.md\n"
},
"workplans/KEY-WP-0002-container-image-gitea.md": {
"sha256": "76fee187185b30219bc257ccc9b587661df4489574a2bb57703a365a5d803cec",
"mode": 420,
"index": "100644 418f477c0ede13d7c003d744abf234347f5d5e31 0\tworkplans/KEY-WP-0002-container-image-gitea.md\n"
},
"workplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md": {
"sha256": "5e949c696e587540d305b682eb1f8390ec544bbec6a566be8d04e73fb1b254ca",
"mode": 420,
"index": "100644 9896c67d78f0efdd03325535eb41295250be3a19 0\tworkplans/KEY-WP-0003-bootstrap-console-oidc-mfa-login.md\n"
},
"workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md": {
"sha256": "260ba7d3116db3bc5a72e5cd8b9984137e7e372c8128d21073614dbd948c10c2",
"mode": 420,
"index": "100644 a50e910558ee43073c92cb27865a1aceac990dd5 0\tworkplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md\n"
},
"workplans/KEY-WP-0005-iam-profile-core-claims.md": {
"sha256": "c6702b644cdd046c0d3bba8ba8837050826e4dc3d855bd73327ae94218ba5b57",
"mode": 420,
"index": "100644 648d2405699dcf25b3121df6c46d871597100d7b 0\tworkplans/KEY-WP-0005-iam-profile-core-claims.md\n"
},
"workplans/KEY-WP-0006-client-credentials-service-tokens.md": {
"sha256": "b3227849978c95cad89e02afb74bf2217b6188ae65c5b0af6a21703d65477701",
"mode": 420,
"index": "100644 632c4c2cad39cbde55c2ae1f2ba5ecf2f445b1a0 0\tworkplans/KEY-WP-0006-client-credentials-service-tokens.md\n"
},
"workplans/KEY-WP-0007-user-engine-portal-oidc-client.md": {
"sha256": "5408ff6dfeb58a8a7756b48fe4f54f7c7b98d7940cb9aaa5c7864f1247e58150",
"mode": 420,
"index": "100644 f1b04e1a61b398426c23d8d9ca655b3937d7bd10 0\tworkplans/KEY-WP-0007-user-engine-portal-oidc-client.md\n"
},
"workplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md": {
"sha256": "bfc9b2118f0712e92a86bbb0f14a2857fafc3a42a3c14c0e2535002aaa57a2f5",
"mode": 420,
"index": "100644 438ab5480de3bd66d75b6ec7e09639d1b93e3e5f 0\tworkplans/KEY-WP-0008-registration-handoff-and-client-mfa-policy.md\n"
},
"workplans/KEY-WP-0009-provider-capabilities-and-service-identities.md": {
"sha256": "d1daac9815930104a392ea96c14922e34f947720a31d1b0ac45a770997e0f476",
"mode": 420,
"index": "100644 c9e585db65424c7c4e3b5adf37a68de2bdfbdc1c 0\tworkplans/KEY-WP-0009-provider-capabilities-and-service-identities.md\n"
},
"workplans/KEY-WP-0010-openbao-operator-loopback-callback.md": {
"sha256": "41d28a16bef9313b776c5c6305316a9e7d4f5528b7fb7427b8384a0ed0ad35c4",
"mode": 420,
"index": "100644 81614eeb8c98205d6e5eb3201da10e36b76a21e9 0\tworkplans/KEY-WP-0010-openbao-operator-loopback-callback.md\n"
},
"workplans/KEY-WP-0011-live-secret-exposure-recovery.md": {
"sha256": "3c5458180fe81a04e357f8db737e4287f79640b5ab02682bcadc595b73f846cd",
"mode": 420,
"index": "100644 f7b906f552f6e3279895a90afbe216b6e78ee5ff 0\tworkplans/KEY-WP-0011-live-secret-exposure-recovery.md\n"
},
"workplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md": {
"sha256": "68d34d75c914e7d2d8177000e800d6372cccda2b850c03ab85da42b90a66b91c",
"mode": 420,
"index": "100644 0f3deb626e5de9a9f898754ef81d3f2ee5282792 0\tworkplans/KEY-WP-0012-userinfo-canonical-subject-resolution.md\n"
},
"workplans/KEY-WP-0013-approval-engine-resource-audience.md": {
"sha256": "55c88bf4ed9a456db59b319bbaf960dfe6f4147437e339f950e0583d7582057a",
"mode": 420,
"index": "100644 7d77aee3caf3d79fe6d6c1369249c6d94e0ae123 0\tworkplans/KEY-WP-0013-approval-engine-resource-audience.md\n"
},
"workplans/KEY-WP-0014-native-credential-lane-handoff.md": {
"sha256": "22aac43ae5039aebd11f9f0aa57a882ad009ad6ed119ec08eb85b32a5af48cd7",
"mode": 420,
"index": "100644 bacf04c07f59125a90c519d3c306a7414cb9a1a3 0\tworkplans/KEY-WP-0014-native-credential-lane-handoff.md\n"
},
"workplans/KEY-WP-0015-scope-intent-assessment.md": {
"sha256": "60b9f2265eff880d916c3ee8dba36aec9984ec310804de776cebf89cc98fdb95",
"mode": 420,
"index": "100644 82b952a216346ffb7f5cab413623fe4a88f7700e 0\tworkplans/KEY-WP-0015-scope-intent-assessment.md\n"
},
"workplans/KEY-WP-0016-authorization-code-protocol-hardening.md": {
"sha256": "ed48128a1cb51cbe3d154727bafd5a5ffb3b5b618c20649b2a0e7db154cf3a0c",
"mode": 420,
"index": "100644 52839bd0f83d97656c8ad97a8ffc338d9019239a 0\tworkplans/KEY-WP-0016-authorization-code-protocol-hardening.md\n"
},
"workplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md": {
"sha256": "8700e0e5aa07dcd7f651e792cca12a0330b7ee9d2f6064b4cc3fcf4c7d509340",
"mode": 420,
"index": "100644 00eb03971aae75724afbf7b8a4946ce6b7eda3af 0\tworkplans/KEY-WP-0017-canonical-model-and-discovery-conformance.md\n"
},
"workplans/KEY-WP-0018-export-completeness-evidence.md": {
"sha256": "15026b708d116f803b0b2fa43f30a4dbced76e4d8137b5fcb5b4275e593d7c7b",
"mode": 420,
"index": "100644 325fc483d62dbad53b92b986de56112168a92b4a 0\tworkplans/KEY-WP-0018-export-completeness-evidence.md\n"
},
"workplans/KEY-WP-0019-upstream-provider-token-verification.md": {
"sha256": "0d2d8b5bda1e0d333935812d8d3749cb7b0c5523a3987d1b26185f9f6d0f0fa2",
"mode": 420,
"index": "100644 dfeb92296f46d4ce2ac7df1fedcc61e9ccc5d6b3 0\tworkplans/KEY-WP-0019-upstream-provider-token-verification.md\n"
},
"workplans/KEY-WP-0020-migration-contract-preservation.md": {
"sha256": "cc842d15bf42ddc4d0f4056bc19b3176cab2c5f3c1a2fe6d648e5dbd077be8b3",
"mode": 420,
"index": "100644 7b6f3be976293469dee2180c10e16f313876d640 0\tworkplans/KEY-WP-0020-migration-contract-preservation.md\n"
},
"workplans/KEY-WP-0021-snapshot-attribute-validation.md": {
"sha256": "b603248693073a9b9b25b4d0a4147d8d88c16a07dc9936e48fa1f3c76fe8bab6",
"mode": 420,
"index": "100644 1f6efeb32a6eb8fcbc52f4d3b591e2fc6b9e006d 0\tworkplans/KEY-WP-0021-snapshot-attribute-validation.md\n"
},
"workplans/KEY-WP-0022-replacement-harness-and-external-conformance.md": {
"sha256": "8c135e84be81de31b4949f52ac28dccd404f2e4c3eaa576d733bd50160427a53",
"mode": 420,
"index": "100644 7c69629ecd20d0158aa1332d9d1442b796f6a7af 0\tworkplans/KEY-WP-0022-replacement-harness-and-external-conformance.md\n"
},
"workplans/KEY-WP-0023-live-migration-proof.md": {
"sha256": "e581cd11f3905805b5e8ba8e2ede85092577c7d086e1761f2a2d033fdead4e47",
"mode": 420,
"index": "100644 3e7c83cb12e7fc31e682cf3c27f4c4767ded2905 0\tworkplans/KEY-WP-0023-live-migration-proof.md\n"
},
"workplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md": {
"sha256": "af7842fa97263bfd46a4b5ae84c5c1ecddb06ab9623586e0292dcc93099fea35",
"mode": 420,
"index": "100644 baa0bbd195a92708f2befd027b938fcd1812ced7 0\tworkplans/KEY-WP-0024-tenant-roles-opt-in-wiring.md\n"
},
"workplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md": {
"sha256": "0addb34e6d85e376c6a5db7afba597b6f268afa98b86c8c60add9be174dd1b26",
"mode": 420,
"index": "100644 a53e646761178748e8c4b00dcde888698447a948 0\tworkplans/KEY-WP-0025-runtime-lifecycle-and-readiness.md\n"
},
"workplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md": {
"sha256": "c1bc0de47511ae49102009a172c84d9d5d64e829a18c8d40564fe817935563b3",
"mode": 420,
"index": "100644 1f96a2ac3ac939c4e6e0f66172477cf73db04e51 0\tworkplans/KEY-WP-0026-packaging-bootstrap-and-credential-handling.md\n"
},
"workplans/KEY-WP-0027-rollout-readiness-and-live-state.md": {
"sha256": "f559eae61d433d451f80ea575254bb14a47f2485267b412e42d524b434bb1b59",
"mode": 420,
"index": "100644 57e01b2db033847cc786504b8795e37112d988a7 0\tworkplans/KEY-WP-0027-rollout-readiness-and-live-state.md\n"
},
"workplans/KEY-WP-0028-browser-client-field-validation.md": {
"sha256": "fbd51422fa5e6449000e4520e6639f6135c7393165a5ae02b1e92892edba0480",
"mode": 420,
"index": "100644 46b0672fea90cd7cb672f7d119706c240474f8ca 0\tworkplans/KEY-WP-0028-browser-client-field-validation.md\n"
},
"workplans/KEY-WP-0029-scope-reassessment.md": {
"sha256": "3241f3b94db9cad5e3770cf1fedc5c211288357e4f69b5ba9da57065094bdf0e",
"mode": 420,
"index": "100644 392f97750f6cb8b12868a6fd016c7d396b58477e 0\tworkplans/KEY-WP-0029-scope-reassessment.md\n"
},
"workplans/KEY-WP-0030-tenant-precondition-guard.md": {
"sha256": "c94c3afc7fb4270dfc18f3fe8a32946c524e4a6ba2d02a3d8fda69c9b8441af0",
"mode": 420,
"index": "100644 0f74e0d5a037eec64b11b57c67b02057a3b0bfef 0\tworkplans/KEY-WP-0030-tenant-precondition-guard.md\n"
},
"workplans/KEY-WP-0031-gate-house-tenant-ruling.md": {
"sha256": "0abb135367f0366aa071769c4bd76fdeb321d810ec75ad3b58d718734f78382b",
"mode": 420,
"index": "100644 d2ede9d494927976ccb9ba6a5aeaf1ed21c30837 0\tworkplans/KEY-WP-0031-gate-house-tenant-ruling.md\n"
},
"workplans/KEY-WP-0032-principal-type-provenance-guard.md": {
"sha256": "6a5cd483a40b9477227b02156c42b3469094dac145b33727b336118898aa782a",
"mode": 420,
"index": "100644 a04f52f817a51efc1d0d0ecb99efacea8dc53a17 0\tworkplans/KEY-WP-0032-principal-type-provenance-guard.md\n"
},
"workplans/KEY-WP-0033-vergabe-fresh-login.md": {
"sha256": "c01bb51cf2dcad06ea846040dddef09845041fa7271cd7015e5a34a49e60bb8f",
"mode": 420,
"index": "100644 5b478f953890339aec796c11a536af0ec3238789 0\tworkplans/KEY-WP-0033-vergabe-fresh-login.md\n"
},
"workplans/KEY-WP-0034-account-recovery.md": {
"sha256": "7db9adcd418ccabfbce672aec283249a6e374212492f0349e8c47a568f130eda",
"mode": 420,
"index": "100644 eb58f99bc26e3c3ac33230dfe432c8b1fabe47ce 0\tworkplans/KEY-WP-0034-account-recovery.md\n"
},
"workplans/KEY-WP-0035-optional-mfa.md": {
"sha256": "c60ea8d0c195686c0d2317f657aad2de27c9b9432d311627c0e1d06124cabb49",
"mode": 420,
"index": "100644 29eb711f656c91808e91f3981a9168c3a0ebc253 0\tworkplans/KEY-WP-0035-optional-mfa.md\n"
},
"workplans/KEY-WP-0036-platform-factor-recovery.md": {
"sha256": "5ceb08fe5789b4f02550bb04fcaf8eeb25a43ac3f506dfc62d60a513bcd0013f",
"mode": 420,
"index": "100644 fd296bc05a6e565b0ef6e88a9ae3daa7c9f81786 0\tworkplans/KEY-WP-0036-platform-factor-recovery.md\n"
},
"workplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md": {
"sha256": "3a90a0c460fc439570820b96d1ddc3b7f8646c557ca3a8aa27c6d546bb1a845a",
"mode": 420,
"index": "100644 ad7fed77a27aa8487693ad6fcf342502f418427a 0\tworkplans/KEY-WP-0037-secrets-engine-openbao-registration-custody.md\n"
},
"workplans/KEY-WP-0038-forgejo-oidc-client.md": {
"sha256": "dc8d88d462799e7e6e44da759a973d52e44102a271969a5daaa9d30ec015b064",
"mode": 420,
"index": "100644 8cd5bc66f7b0115eec7813d6eec994a1bb618668 0\tworkplans/KEY-WP-0038-forgejo-oidc-client.md\n"
},
"workplans/KEY-WP-0039-openbao-negative-probe-tokens.md": {
"sha256": "517f9526d574ce3faafad18837e0039db8f3f7990eac3e34a1678cdd7307a9e1",
"mode": 420,
"index": "100644 4398517afe04e3b0723722af37b434fa8d2209ab 0\tworkplans/KEY-WP-0039-openbao-negative-probe-tokens.md\n"
},
"workplans/KEY-WP-0040-mfa-attended-run-findings.md": {
"sha256": "98b23d8810bff047067169232213cb313ecc14ff79751224842723afe808fbd2",
"mode": 420,
"index": "100644 8e8730c8e7733b13d6a9f42689a9d8e407769e81 0\tworkplans/KEY-WP-0040-mfa-attended-run-findings.md\n"
},
"workplans/KEY-WP-0041-sign-in-diagnosis-page.md": {
"sha256": "7e44f46acd6b3942c1852b49e0d64b205dfd7d9bb378aed5241b1bf22d7ce88a",
"mode": 420,
"index": "100644 56fd05819e3acd37b8e6cb468ec3d62307f1ae7a 0\tworkplans/KEY-WP-0041-sign-in-diagnosis-page.md\n"
}
}

View file

@ -0,0 +1,27 @@
---
id: ADHOC-2026-09-05
type: workplan
title: "Repository review and layer declaration"
domain: infotech
repo: key-cape
status: finished
owner: codex
created: "2026-09-05"
updated: "2026-09-05"
state_hub_workstream_id: "6f5a12f0-5dac-5e02-bbd9-9ab2263264ba"
---
## Resolve declaration intake and normalize repository records
```task
id: ADHOC-2026-09-05-T01
status: done
priority: low
state_hub_task_id: "54c89abe-2539-550d-bc3a-649af8d4580a"
```
Reviewed KEY-IN-0001 against local security-layer-model_v0.4. Declared Tooling
and the authentication/authorization boundary in INTENT.md and closed the intake.
Normalized two legacy completed workplan frontmatter statuses to finished.
Ignored the generated .repo-manager workstation cache. Existing task statuses
already conform to the State Hub task lifecycle canon.

View file

@ -0,0 +1,36 @@
---
id: ADHOC-2026-09-07
type: workplan
title: "Document the KEY-WP-0016 authorization-code bindings for consumers"
domain: infotech
repo: key-cape
status: finished
owner: claude
created: "2026-09-07"
updated: "2026-09-07"
state_hub_workstream_id: "c2fdc5cc-89be-561d-b832-f0b3019ea1c7"
---
## Publish the consumer-facing rollout note
```task
id: ADHOC-2026-09-07-T01
status: done
priority: medium
state_hub_task_id: "2187cef2-07c0-5ac1-89ca-3bb25a160fa4"
```
KEY-WP-0016 changed `/token` and `/userinfo` behaviour without a consumer-facing
note; no document in `docs/` mentioned `redirect_uri` at all, so the change would
have reached a deployment silently.
Published `docs/authorization-code-bindings.md` stating what an exchange must
send, who is affected and how to roll out. Verified the blast radius rather than
assuming it: all five browser registrations in `config/dev-config.yaml`
(`openbao-admin`, `demo-app`, `netkingdom-bootstrap-console`,
`user-engine-portal`, `coulomb-social`) are `clientType: public` with
`grantTypes: ["authorization_code"]`, so the grant-type and confidential-client
bindings cannot affect them and only the `redirect_uri` requirement can. Named
the realistic failure — a client that sends `redirect_uri` to `/authorize` but
omits it at `/token` — and said plainly that it has not been observed against a
live consumer. Referenced the note from SCOPE.md.

View file

@ -0,0 +1,100 @@
---
id: ADHOC-2026-09-27
type: workplan
title: "Fresh MFA no longer backdated to a stale kc_login cookie (INFD-IN-0005)"
domain: infotech
repo: key-cape
status: finished
owner: claude
created: "2026-09-27"
updated: "2026-09-27"
state_hub_workstream_id: "5217dd06-73cb-50f8-ba64-528e421b29de"
---
## Stop completeAuthorization from reusing a stale prior session's IssuedAt after real MFA
```task
id: ADHOC-2026-09-27-T01
status: done
priority: high
state_hub_task_id: "ce98e4af-cfaa-5620-8f21-15ade18ee9e8"
```
`informed-decision` intake `INFD-IN-0005` (open, `origin_ref: SECRETS-WP-0010`):
platform-root's approval of `infd-20260927-b01` was refused by flex-auth's
`compact_sitting` policy with `policy_denied`, despite platform-root having just
completed a fresh key-cape MFA sign-in. flex-auth's 900-second review-freshness
check reads `assurance.at`, which key-cape derives from the PKCE session's
`AuthTime`.
Root cause in `src/internal/server/oidc/authorize.go`,
`AuthorizeHandler.completeAuthorization`: whenever a `kc_login` cookie already
existed for the same username, its `IssuedAt` was unconditionally reused as
`authTime` — even on the two call sites that had just called
`MFA.ValidateMFAToken` successfully in this exact request (the callback's
`mfaToken != ""` branch and `serveMFASubmission`). A user who had an older
browser session and then stepped up with a fresh MFA challenge got the *old*
session's timestamp stamped onto the *new*, just-proven authentication —
understating freshness for every downstream binding-grade check, exactly as
`INFD-IN-0005` describes ("completeAuthorization currently copies a prior
timestamp... only after actual reauthentication").
The reuse itself is correct when MFA was **not** just performed in this
request (`decision.MFAVerified` carried forward from an existing AAL2 session,
via `domain.DecideAssurance`) — reporting mint time there would overstate
freshness (`TestAssuranceReportsAuthenticationTimeNotMintTime`). The two cases
were conflated under a single `mfaVerified` bool.
Fix: added a `freshMFA bool` parameter to `completeAuthorization`, `true` only
at the two call sites that just validated an MFA token in this request, `false`
at the carried-forward-assurance call site. `authTime` now falls back to the
prior session's `IssuedAt` only when `!freshMFA`.
Added `TestAuthorizeCallback_MFASubmission_FreshEvenWithStalePriorLoginCookie`,
which seeds a 4-hour-old `kc_login` cookie for the same user, then submits a
valid MFA token and asserts the resulting `AuthTime` is within a minute of now.
Confirmed it fails against the pre-fix code (`AuthTime` ~4h old) and passes
after. Full suite (`go test ./...`) green, no other test needed adjustment —
the carried-forward-assurance case (`TestPolicy_...`, `TestLogout_...`,
`assurance_time_test.go`) is unaffected since none of it exercises a fresh MFA
submission with a pre-existing cookie for the same user.
Does not touch flex-auth (its 900s window is correct; it was consuming a bad
timestamp) or informed-decision (correctly refuses to interpret the PDP's
denial). `INFD-IN-0005` can close once informed-decision confirms platform-root
can approve after a fresh sign-in against this build.
## Discovery/JWKS fetch was missing the split-horizon origin headers
```task
id: ADHOC-2026-09-27-T02
status: done
priority: high
```
Rolling out T01 (image digest `sha256:248e449a...`) broke every KeyCape login,
including unrelated clients (`user-engine-portal`), with `auth_failure` /
`provider_metadata_unavailable` at `/api/oidc/token` on the KeyCape side and
Authelia logging `invalid X-Forwarded-Proto header value 'http'` on
`GET /.well-known/openid-configuration` from KeyCape's pod IP.
Root cause, unrelated to T01's change: `AutheliaAdapter.setPublicOriginHeaders`
(the NK-WP-0041-T02/path-B fix, key-cape@3b0446e) annotates only the token
exchange (`newTokenRequest`) with `X-Forwarded-Proto`/`X-Forwarded-Host` so
Authelia can derive the public issuer for a split-horizon (in-cluster HTTP,
public HTTPS) deployment. `idTokenVerifier.get` in `verify.go` — used for the
provider-metadata and JWKS fetches that verify every upstream ID token — never
got the same treatment; it built a bare request with only an `Accept` header.
Authelia 4.38 fails closed on the resulting unmarked in-cluster request. This
gap predates today's session (present since 3b0446e); it surfaced now because
this was the first login attempted since that image went live.
Fix: extracted `setPublicOriginHeaders` from an `AutheliaAdapter` method to a
package-level function taking the target public base URL explicitly, and gave
`idTokenVerifier` its own resolved `publicBaseURL` (same
`BrowserBaseURL`-else-`BaseURL` fallback as the adapter) so `get()` applies
the same headers. Added `TestDiscoveryFetch_CarriesPublicOriginHeaders`
(confirmed it fails without the fix, passes with it). Full suite green.
New image `key-cape@<pending>` still needs a rollout to `sso-mfa/k8s/keycape/deployment.yaml`
before platform-root can sign in again.

View file

@ -0,0 +1,499 @@
---
id: KEY-WP-0001
type: workplan
title: "KeyCape Implementation — Lightweight IAM Profile"
domain: infotech
repo: key-cape
status: finished
owner: Bernd
topic_slug: netkingdom
topic_id: a6c6e745-bf54-4465-9340-1534a2be493e
repo_id: 8a99bb74-1ec0-4478-ac70-35a7cddb0e3c
created: 2026-03-13
updated: 2026-03-13
spec_refs:
- wiki/KeyCapeSpecification_v0.1.md
- wiki/KeyCapeSpecificationPack_v0.1.md
decisions:
- id: ADR-0001
title: "Implementation language: Go"
hub_decision_id: 620beb04-fa3f-4a9d-9806-02890a7a2b0d
status: accepted
ref: docs/adr/ADR-0001-choose-go-for-keycape.md
state_hub_workstream_id: "0d34dfc1-7ccb-4bd5-b872-5c7379b9adce"
---
# KEY-WP-0001 — KeyCape Implementation
Implements the **NetKingdom IAM Profile** via KeyCape: a stateless, profile-constrained OIDC
server orchestrating Authelia, LLDAP, and privacyIDEA. Replaceable by Keycloak without
application changes.
## Language Decision
**Go** — decided 2026-03-13 by Bernd. See `docs/adr/ADR-0001-choose-go-for-keycape.md`.
KeyCape is an orchestrating boundary service (HTTP, adapters, JWT via library, CLI tooling) —
Go's strongest domain. Rust revisitable if a subcomponent needs stronger guarantees later.
Mandatory guardrails: typed domain models, narrow adapter interfaces, layered architecture,
fuzz tests on validator/redirect/claim-mapping, no cleverness.
## Repo Structure (from spec §12)
```
src/
server/
oidc/ # Profile endpoints
telemetry/ # Structured event emission
errors/ # Error taxonomy + enforcement middleware
adapters/
authelia/ # Auth flow delegation
lldap/ # Identity directory reads
privacyidea/ # MFA enforcement
validator/ # Canonical LDAP schema validator binary
migration/
lldap-export/ # LLDAP → canonical
keycape-to-keycloak/ # Canonical → Keycloak realm import
lldap-to-ldap/ # LLDAP → OpenLDAP/389DS/AD LDIF
spec/
canonical-model.yaml # Source of truth for all identity data
ldap-schema.yaml # Canonical LDAP schema rules
tests/
profile/ # Scenario A — lightweight baseline
negative/ # Scenario D — unsupported feature rejection
migration/ # Scenarios B & C — replacement
```
## Dependency Order
```
T01 (project setup)
└─ T02 (canonical model) T04 (error taxonomy)
└─ T03 (LDAP validator) └─ T13 (telemetry)
└─ T10 (LLDAP adapter) └─ T14 (enforcement layer)
└─ T11 (Authelia) │
└─ T12 (privacyIDEA) │
│ │
T05 ─ T06 ─ T07 ─ T08 ─┴─ T09 (OIDC server)
│
T18 (profile tests / Scenario A)
T21 (negative tests / Scenario D)
│
T15 → T16 → T19 (Scenario B)
T15 → T17 → T20 (Scenario C)
│
T22 (dev stack)
T23 (production packaging)
```
---
## Phase 1 — Foundations
## T01 — Project setup: Go module, repo layout, CI skeleton
```task
id: KEY-WP-0001-T01
hub_task_id: 25613e3f-2a65-409e-afaa-d23ded0bc256
priority: high
status: done
state_hub_task_id: "38822bc0-4189-4909-874e-ea40e5771250"
```
Initialise language module in `src/`. Create directory skeleton per spec §12. Add Makefile
targets: `build`, `test`, `lint`. Set up CI (build + test on every push). Scaffolding only —
no application code. **Agent must call `record_decision()` with chosen language (Go or Rust).**
## T02 — Canonical identity model: machine-readable schema
```task
id: KEY-WP-0001-T02
hub_task_id: deee2929-9386-41db-bf91-fbd9ad646c28
priority: high
status: done
depends_on: [T01]
state_hub_task_id: "940c118b-c1e6-4dda-bd4c-4fac105822be"
```
Write `spec/canonical-model.yaml`. Six entities: User, Group, Role, Client, Membership,
MFAEnrollment (fields per spec §2). Include JSON Schema or CUE schema for programmatic
validation. This file is the **source of truth** — all other code derives from it.
## T03 — Canonical LDAP schema + validator
```task
id: KEY-WP-0001-T03
hub_task_id: 02592c65-db23-474b-b06b-019e95df8146
priority: high
status: done
depends_on: [T01, T02]
state_hub_task_id: "c1715d70-f10f-45e9-b73a-b54a3d360342"
```
Write `spec/ldap-schema.yaml`: tree layout (`ou=users`, `ou=groups`, `ou=clients` under
`dc=netkingdom,dc=local`), object classes (`inetOrgPerson`, `groupOfNames`), required/optional
attributes. Implement `validator/` binary. Structural rules: valid DN, required attrs, no unknown
attrs, valid group memberships. Semantic rules: referenced users exist, no cycles, usernames
unique, email format valid. Validator runs in `--mode=ci|provisioning|migration`. Emits
machine-readable report.
## T04 — Error taxonomy: types, JSON format, middleware
```task
id: KEY-WP-0001-T04
hub_task_id: 46870fd6-0672-432b-8824-6bc2e24811b3
priority: high
status: done
depends_on: [T01]
state_hub_task_id: "6e3b6b97-ac77-44c5-959e-be12751f1b63"
```
Implement four error types (spec §5):
- `feature_not_supported_by_profile`
- `available_in_keycloak_mode_only`
- `rejected_for_profile_safety`
- `invalid_profile_usage`
JSON format: `{"error": "...", "description": "...", "feature": "..."}`. HTTP middleware wraps
all handler errors. Error type strings are stable and test-assertable.
---
## Phase 2 — OIDC Server
## T05 — OIDC discovery endpoint (/.well-known/openid-configuration)
```task
id: KEY-WP-0001-T05
hub_task_id: 92eb8916-cb22-4786-9f16-a8a07272f818
priority: high
status: done
depends_on: [T04]
state_hub_task_id: "0dbc08e3-c465-4c37-a219-832a580bedfd"
```
`GET /.well-known/openid-configuration`. Advertise **only** profile-supported features:
`authorization_code`, S256 PKCE, RS256, static scopes. Must NOT advertise: dynamic registration,
implicit flow. Issuer configurable. Cacheable response.
## T06 — Authorization endpoint with PKCE and redirect URI validation
```task
id: KEY-WP-0001-T06
hub_task_id: c3df620b-9864-4ff1-ba6d-26057c6f4d59
priority: high
status: done
depends_on: [T04, T11, T12, T13, T14]
state_hub_task_id: "cdb4b06d-3d54-49dd-ac05-ca9ed6d7322f"
```
`GET/POST /authorize`. Validate: `client_id` (static config), `redirect_uri` (exact match —
wildcard → `rejected_for_profile_safety`), `response_type=code`, `scope` contains `openid`,
`code_challenge` present (missing → `invalid_profile_usage`), `code_challenge_method=S256`.
Delegate to Authelia adapter. Store PKCE state server-side. No implicit or hybrid flow.
## T07 — Token endpoint: JWT/RS256, canonical claim mapping
```task
id: KEY-WP-0001-T07
hub_task_id: d3248f4d-e0e9-4144-9844-c9768dc896d6
priority: high
status: done
depends_on: [T06, T08, T10]
state_hub_task_id: "534d8616-90de-4d32-961c-c2ef719642e4"
```
`POST /token`. Validate PKCE `code_verifier`. Issue RS256 JWT via standard library (no custom
crypto). Mandatory claims: `iss`, `sub` (canonical user ID), `aud`, `exp`, `iat`. Optional
claims: `preferred_username` (LDAP `uid`), `email` (LDAP `mail`), `groups` (groupOfNames),
`roles`. Short, explicitly configured token lifetime. Any other grant type →
`feature_not_supported_by_profile`.
## T08 — JWKS endpoint (/jwks)
```task
id: KEY-WP-0001-T08
hub_task_id: 58a1d705-b788-4d66-8c0a-33edff63a885
priority: high
status: done
depends_on: [T01]
state_hub_task_id: "7e2167be-bcc7-49c2-8681-e518abd5bc0c"
```
`GET /jwks`. RS256 public key in JWK Set format. Key loaded from config. Key rotation: serve
multiple keys during rotation window, keyed by `kid`. Standard library key generation only.
## T09 — Userinfo endpoint (/userinfo)
```task
id: KEY-WP-0001-T09
hub_task_id: 742d3924-21e9-4304-86e9-0400af0e81ee
priority: medium
status: done
depends_on: [T07, T10]
state_hub_task_id: "78094ca5-a831-4443-9ccf-fc476ff87b91"
```
`GET /userinfo`. Optional per spec — implement if any registered client requires it. Validate
Bearer token (RS256 + expiry). Return claim subset scoped to granted scopes. Claims must be
identical to ID token for same scopes. If no client needs it: stub returning
`available_in_keycloak_mode_only`.
---
## Phase 3 — Backend Adapters
## T10 — LLDAP adapter: user and group reads
```task
id: KEY-WP-0001-T10
hub_task_id: 2043b10a-6822-45f8-abcc-4e233d918fb0
priority: high
status: done
depends_on: [T02, T03]
state_hub_task_id: "97d19662-f482-4ea5-84fd-9fccb84ff317"
```
`adapters/lldap`. LDAP protocol connection to LLDAP. Interface: `LookupUser(username) → canonical
User`, `LookupGroups(userDN) → []Group`, `ValidatePassword(username, password) → bool`. Attribute
map: `uid→username`, `cn→displayName`, `mail→email`, `memberOf→groups`. Run canonical LDAP schema
validator on every read. No LDAP internals exposed to `server/`.
## T11 — Authelia adapter: session and auth flow delegation
```task
id: KEY-WP-0001-T11
hub_task_id: ad129a14-1552-4717-b1dd-b529d18ce681
priority: high
status: done
depends_on: [T04, T13]
state_hub_task_id: "6461865b-f57c-4591-9cf3-68c79af22723"
```
`adapters/authelia`. Initiate auth redirect to Authelia, receive callback, extract authenticated
user identity, hand off to MFA check (T12). Must not leak Authelia session tokens/cookies into
profile layer. Unavailable Authelia → fail closed (`auth_failure` event).
## T12 — privacyIDEA adapter: MFA enforcement
```task
id: KEY-WP-0001-T12
hub_task_id: 1ef196e6-2304-4cf6-b205-47ac1da879ec
priority: high
status: done
depends_on: [T02, T13]
state_hub_task_id: "e403a783-c856-4d6d-b859-a9cad7545fe1"
```
`adapters/privacyidea`. **KeyCape must NOT implement MFA logic.** Interface:
`CheckMFARequired(user) → bool`, `ValidateMFAToken(user, token) → bool`. MFA failure → no token
issued + `auth_failure` telemetry. MFA enrollment state from canonical `MFAEnrollment` entity.
privacyIDEA remains stable across lightweight → expanded migration.
---
## Phase 4 — Telemetry & Enforcement
## T13 — Telemetry pipeline: structured event emission
```task
id: KEY-WP-0001-T13
hub_task_id: 704146bf-cd60-4922-b18b-3d209cff3ac3
priority: high
status: done
depends_on: [T01]
state_hub_task_id: "4df7bda1-5b84-4b4c-9b16-bcb1d3cca096"
```
`server/telemetry`. Event types (spec §6.1): `auth_start`, `auth_success`, `auth_failure`,
`token_issued`, `unsupported_feature`, `invalid_request`, `migration_event`. Required fields
(spec §6.2): `timestamp`, `client_id`, `endpoint`, `feature`, `result`, `error_type`, `scopes`,
`grant_type`, `environment`, `trace_id`. Pluggable outputs: structured log (default), Prometheus
metrics endpoint. Every auth and error path emits an event — **no silent paths**.
## T14 — Unsupported feature enforcement layer
```task
id: KEY-WP-0001-T14
hub_task_id: 71f44886-ab61-4160-a435-72b35af472a0
priority: high
status: done
depends_on: [T04, T13]
state_hub_task_id: "ae16fba9-5bb4-4780-ac77-558e3ed7e1dd"
```
`server/errors` enforcement middleware. Intercept any parameter, grant type, scope, or client
config exceeding the profile. Return correct error type + emit `unsupported_feature` telemetry.
Maintain a **registry** of unsupported features (adding new ones requires no handler changes):
`dynamic_client_registration`, `identity_broker`, `wildcard_redirect_uri`, `implicit_flow`, etc.
Every registry entry must have a corresponding test in T21.
---
## Phase 5 — Migration Tooling
## T15 — Migration: LLDAP → canonical export
```task
id: KEY-WP-0001-T15
hub_task_id: 1bd13f76-2d62-429d-b230-d785ef6a3f2f
priority: medium
status: done
depends_on: [T02, T03, T10]
state_hub_task_id: "f7549cd7-33f0-4407-a656-ab8f5a184e64"
```
`migration/lldap-export` tool. Read all users, groups, memberships, attributes from LLDAP. Map
to canonical model. Validate against LDAP schema validator before writing. Output:
`canonical-export.yaml`. Emit `migration_event` telemetry. Idempotent. Include incompatibility
report for unmappable LLDAP data.
## T16 — Migration: canonical → Keycloak import
```task
id: KEY-WP-0001-T16
hub_task_id: f3d50e80-f6b5-4c0c-a5f3-08308ea1a95e
priority: medium
status: done
depends_on: [T15]
state_hub_task_id: "96486c41-9f33-42a5-b7b6-ad0a9eb2bdee"
```
`migration/keycape-to-keycloak` tool. Read canonical export (T15). Transform to Keycloak realm
import format: users, groups, clients, roles, scope mappings. Preserve: same issuer, same claims,
same scopes, same client behavior. Output: `keycloak-realm-import.json`. Emit `migration_event`.
Include round-trip validation report.
## T17 — Migration: LLDAP → full LDAP (OpenLDAP / 389DS / AD)
```task
id: KEY-WP-0001-T17
hub_task_id: 044d99c8-39cb-4f35-9308-912ae829bd22
priority: medium
status: done
depends_on: [T15]
state_hub_task_id: "1ec335a2-80ca-4c34-b08e-211f537e4214"
```
`migration/lldap-to-ldap` tool. Export via T15 canonical export. Generate LDIF for target
(`--target=openldap|389ds|ad`). Migrate: users, groups, memberships, attributes. Run validator
on output LDIF before import. Produce validation report. **Orthogonal to T16** — the two
migration dimensions are independent (spec §14.1).
---
## Phase 6 — Replacement Tests
## T18 — Profile test suite: Scenario A (lightweight baseline)
```task
id: KEY-WP-0001-T18
hub_task_id: 76abc3f6-9c4e-4aca-9995-72b728925812
priority: high
status: done
depends_on: [T05, T06, T07, T08, T09, T22]
state_hub_task_id: "1b0e9f26-d441-42b8-b532-1eb713fb355d"
```
`tests/profile`. Provision canonical fixtures into LLDAP + Authelia + KeyCape. Test categories
(spec §15.3): discovery, login flow (PKCE), token claim assertions (all mandatory + optional),
redirect validation, client config, MFA policy, logout (if implemented). Tests are
**backend-agnostic** — same suite runs in T19 and T20. Must pass for Scenario A conformance.
## T19 — Replacement test suite: Scenario B (IAM swap, same directory)
```task
id: KEY-WP-0001-T19
hub_task_id: 56d03e89-934b-4992-bfe4-b32f275882e3
priority: medium
status: done
depends_on: [T18, T16]
state_hub_task_id: "a02d24e7-32de-4be6-935c-896c10dde020"
```
Run T18 suite against Keycloak + LLDAP (configured from T16 canonical export). **No test code
changes allowed.** Migration successful only if all T18 tests pass. Proves IAM replaceability
without directory migration.
## T20 — Replacement test suite: Scenario C (full expansion)
```task
id: KEY-WP-0001-T20
hub_task_id: ec3cae5c-9942-4be7-acc0-1eb9f02aba45
priority: medium
status: done
depends_on: [T19, T17]
state_hub_task_id: "545f319f-053d-48bd-8d94-c8c05cd56736"
```
Apply T17 LLDAP→OpenLDAP migration, then T16 Keycloak import. Run T18 suite. Migration successful
only if all tests pass. privacyIDEA must remain stable (no MFA re-enrollment required).
## T21 — Negative profile tests: Scenario D (unsupported feature rejection)
```task
id: KEY-WP-0001-T21
hub_task_id: a5112b63-121f-4d17-ac1a-fb46d160413e
priority: high
status: done
depends_on: [T14]
state_hub_task_id: "5856afe0-2a9e-4489-b057-35e59f86c359"
```
`tests/negative`. For every entry in T14 unsupported-feature registry: attempt usage, assert
correct `error.error` string, assert `unsupported_feature` telemetry event emitted. Covered
cases: `dynamic_client_registration`, `implicit_flow`, wildcard redirect URIs, identity brokering,
missing PKCE, unknown scopes, unknown grant types. **Must run in CI.** Proves enforcement layer
is complete.
---
## Phase 7 — Operations
## T22 — Dev mode stack: docker-compose with LLDAP + Authelia
```task
id: KEY-WP-0001-T22
hub_task_id: e840963f-cd19-4b38-857a-7c40df165d3d
priority: medium
status: done
depends_on: [T01]
state_hub_task_id: "b98f2671-a20a-4438-99c9-fbe0e5324534"
```
`docker-compose.dev.yml`: KeyCape, LLDAP, Authelia, privacyIDEA (or stub). Pre-seeded with
canonical fixtures from T02. Makefile targets: `make dev` (start + verify basic auth flow),
`make seed` (re-apply fixtures without full restart). Deterministic: same seed → same state.
Test environment for T18 and T21.
## T23 — Slim production packaging: binary + config + health
```task
id: KEY-WP-0001-T23
hub_task_id: d5723683-f739-4362-b62e-71213dc5a89e
priority: low
status: done
depends_on: [T18, T21]
state_hub_task_id: "8c1752c2-7fb3-4da5-aab3-6b7acf12ea64"
```
Single stateless binary. Declarative YAML config: profile version, client definitions, backend
connections, LDAP schema rules, privacyIDEA settings, telemetry destination, token lifetime,
issuer URL. Static config validation on startup. `/healthz` endpoint. Minimal container image
(distroless or Alpine). Config environment-promotable via env var overrides only.
---
## Acceptance Criteria (from spec §15.4 and §20)
A release is conformant when:
- [x] Scenario A tests pass (T18) — `src/tests/profile/profile_test.go` (8 tests)
- [x] Scenario D tests pass (T21) — `src/tests/negative/negative_test.go` (8 tests)
- [x] Scenario B tests pass (T19) — `src/tests/migration/scenario_b_test.go` (7 tests)
- [x] Scenario C tests pass (T20) — `src/tests/migration/scenario_c_test.go` (6 tests)
- [x] All error responses use taxonomy types from spec §5 — `internal/errors/taxonomy.go`
- [x] All auth/error paths emit structured telemetry (T13) — `internal/server/telemetry/`
- [x] Canonical LDAP schema validator passes on all fixtures (T03) — `internal/validator/`
- [x] No handwritten cryptography anywhere in the codebase — stdlib `crypto/rsa` only
- [x] Config is statically validated at startup (T23) — `internal/config/validate.go`

View file

@ -0,0 +1,245 @@
---
id: KEY-WP-0002
type: workplan
title: "KeyCape Container Image — Build & Publish to Gitea OCI Registry"
domain: infotech
repo: key-cape
status: finished
owner: netkingdom
topic_slug: netkingdom
created: "2026-03-22"
updated: "2026-03-21"
capability_request_id: ""
state_hub_workstream_id: "c8843c7a-460a-47a2-b45a-b8d3940f9aa2"
---
# KEY-WP-0002 — KeyCape Container Image — Build & Publish to Gitea OCI Registry
## Problem
KeyCape has a `Dockerfile` but no automated build pipeline and no published
image. Other services (k3s deployments, local dev) that need to run KeyCape
must build locally from source. There is no versioned artefact to reference
in Helm charts or manifests.
The capability request for this work was originally misrouted to railiance.
It belongs here: KeyCape owns its own image.
## Goal
Produce a versioned OCI image for KeyCape, published to the Gitea container
registry on CoulombCore (`92.205.130.254:32166`), triggered automatically on
every merge to `main` and on semver tags (`v*`).
**Gitea OCI registry endpoint:** `92.205.130.254:32166`
**Image name:** `92.205.130.254:32166/coulomb/key-cape`
> **Why Gitea, not GHCR?**
> The net-kingdom cluster is self-hosted. Keeping images in Gitea (also
> self-hosted on CoulombCore) avoids any external registry dependency and
> keeps image pulls within the cluster network. GHCR is a future option
> once public distribution is needed.
## Design
### Image naming & tagging
| Trigger | Tags applied |
|---------|-------------|
| push to `main` | `latest`, `main-<short-sha>` |
| tag `v1.2.3` | `1.2.3`, `1.2`, `1`, `latest` |
### Build
Multi-stage Dockerfile already present — no changes needed to the build
itself. The image builds to a distroless static binary (~10 MB).
### Registry auth
Gitea issues a personal access token (or machine account token) with
`write:packages` scope. Stored as Gitea Actions secret `REGISTRY_TOKEN`;
username stored as `REGISTRY_USER`.
For local `make push`, credentials are passed via `docker login` before
the push target runs.
### Makefile targets
```makefile
IMAGE_REGISTRY ?= 92.205.130.254:32166
IMAGE_REPO ?= coulomb/key-cape
IMAGE_TAG ?= latest
IMAGE := $(IMAGE_REGISTRY)/$(IMAGE_REPO):$(IMAGE_TAG)
image:
docker build -t $(IMAGE) .
push: image
docker push $(IMAGE)
image-tag:
docker tag $(IMAGE) $(IMAGE_REGISTRY)/$(IMAGE_REPO):$(IMAGE_TAG)
```
### Gitea Actions workflow
`.gitea/workflows/image.yaml` — triggers on push to `main` and on `v*` tags:
- Checkout
- Set up Docker Buildx
- Login to `92.205.130.254:32166` using secrets
- Build and push with metadata-action tags
- (Optional) sign with cosign if available
### k3s insecure registry
Gitea runs over plain HTTP on port 32166 (NodePort). k3s must be configured
to treat this endpoint as an insecure registry so image pulls work from
within the cluster:
```yaml
# /etc/rancher/k3s/registries.yaml (on CoulombCore)
mirrors:
"92.205.130.254:32166":
endpoint:
- "http://92.205.130.254:32166"
```
k3s picks this up on restart (or SIGHUP). Worker nodes (if any) need the
same file.
---
## Tasks
### T01 — Makefile: image, push, image-tag targets
```task
id: KEY-WP-0002-T01
status: done
priority: high
state_hub_task_id: "749472fc-edb9-4948-9ebc-58d5f38327ee"
```
Add `image`, `push`, and `image-tag` targets to `Makefile` with
`IMAGE_REGISTRY`, `IMAGE_REPO`, `IMAGE_TAG` variables defaulting to the
Gitea endpoint and `coulomb/key-cape:latest`.
Gate: `make image` builds successfully locally; `IMAGE_TAG=dev make image`
produces a differently-tagged image.
---
### T02 — Gitea Actions workflow
```task
id: KEY-WP-0002-T02
status: done
priority: high
state_hub_task_id: "8ecf18cc-a3bb-4ede-a09c-fcd0d26d7f9d"
```
Create `.gitea/workflows/image.yaml`:
- Trigger: `push` to `main`, `push` tags matching `v*`
- Runner: `act_runner` label (or `ubuntu-latest` if configured)
- Steps: checkout → docker buildx → login → build+push
- Tags via `docker/metadata-action`: `latest` on main, semver on tags
Secrets required (document in README.md under "CI"):
- `REGISTRY_USER` — Gitea username or machine account
- `REGISTRY_TOKEN` — Gitea personal access token with `write:packages`
Gate: workflow file is syntactically valid; documented in README.
---
### T03 — k3s insecure registry config on CoulombCore
```task
id: KEY-WP-0002-T03
status: done
priority: high
state_hub_task_id: "2dde67f9-944f-418d-a2e9-7367bc556425"
```
On CoulombCore, create/update `/etc/rancher/k3s/registries.yaml` to add
the Gitea NodePort as an HTTP mirror. Restart k3s (or send SIGHUP) and
verify `crictl pull 92.205.130.254:32166/coulomb/key-cape:latest` works.
Gate: image pull from within the cluster succeeds without TLS errors.
---
### T04 — Create Gitea machine account & token
```task
id: KEY-WP-0002-T04
status: done
priority: medium
state_hub_task_id: "25775e10-3164-4adb-9c41-835c86fde5f8"
```
In Gitea (http://92.205.130.254:32166), create a machine account
`ci-netkingdom` (or reuse an existing service account) with access to
the `netkingdom` organisation. Generate a token with `write:packages`
scope and store it in:
- Gitea Actions secrets on the `key-cape` repo: `REGISTRY_USER`, `REGISTRY_TOKEN`
- The net-kingdom credential store (SOPS-encrypted) under
`credentials/gitea-ci-token.enc.yaml`
Gate: `docker login 92.205.130.254:32166` succeeds with the token;
secret is in the credential store.
---
### T05 — Smoke test: push and pull a dev image
```task
id: KEY-WP-0002-T05
status: done
priority: medium
state_hub_task_id: "0f6ab38f-6d34-41af-9180-f19c687947b5"
```
Manually trigger a build-and-push:
```bash
docker login 92.205.130.254:32166
IMAGE_TAG=dev make push
```
Then verify the image is pullable from CoulombCore:
```bash
# on CoulombCore
crictl pull 92.205.130.254:32166/coulomb/key-cape:dev
```
Gate: pull succeeds; image is listed in Gitea -> Packages -> coulomb/key-cape.
---
### T06 — Update README with registry & CI docs
```task
id: KEY-WP-0002-T06
status: done
priority: low
state_hub_task_id: "946cd34d-94da-4fa9-a781-ed36f6c827a3"
```
Add a "Container Image" section to `README.md` documenting:
- Registry URL and image name
- How to pull (`docker pull 92.205.130.254:32166/coulomb/key-cape:latest`)
- How to build and push locally (Makefile targets)
- CI secrets required for the Actions workflow
Gate: README section present and accurate.
---
## Done Criteria
- [ ] `make image` and `make push` work locally
- [ ] `.gitea/workflows/image.yaml` present and documented
- [ ] k3s can pull the image from Gitea without TLS errors
- [ ] Machine account token stored in credential store
- [ ] Smoke test: `dev` image pushed and pulled successfully
- [ ] README updated

View file

@ -0,0 +1,200 @@
---
id: KEY-WP-0003
type: workplan
title: "Bootstrap Console OIDC Login and MFA Verification"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-05-24"
updated: "2026-05-24"
state_hub_workstream_id: "02990009-a2b3-44f6-a579-487fbacae41a"
---
# KEY-WP-0003 - Bootstrap Console OIDC Login and MFA Verification
## Problem
The NetKingdom security bootstrap console now acts as a local OIDC client
callback so the operator can verify the dedicated platform-root login before
approving custody mode. The current live KeyCape deployment rejects that flow
with:
```json
{
"error": "invalid_profile_usage",
"description": "redirect_uri does not match any registered URI",
"feature": "redirect_uri"
}
```
That error is correct profile enforcement: KeyCape only accepts exact
registered redirect URIs. The live `demo-app` registration has not yet been
updated to allow the local bootstrap console callback:
- `http://127.0.0.1:8876/oidc/callback`
- `http://localhost:8876/oidc/callback`
After that is fixed, there is a second usability/security gap. KeyCape checks
privacyIDEA MFA after the Authelia callback, but the browser flow currently
expects an `mfa_token` query parameter instead of presenting a proper OTP
challenge page to the human operator.
## Goal
Make the bootstrap console's "Start demo OIDC login" button a real
end-to-end verification path for the current lightweight IAM stack:
1. KeyCape accepts the bootstrap console callback URI by exact registration.
2. The browser leaves KeyCape for the public Authelia login URL.
3. After password login, KeyCape presents a minimal MFA challenge when
privacyIDEA requires one.
4. KeyCape issues an OIDC authorization code to the bootstrap console callback.
5. The console can exchange the code and let the operator mark
`OIDC login verified` without exposing tokens or secrets.
This keeps KeyCape's security posture intact: no wildcard redirect URIs, no
dynamic client registration, no token display, and no storage of OTP material.
## Design Notes
- Prefer a dedicated public client named `netkingdom-bootstrap-console` for
long-lived clarity. Reusing `demo-app` is acceptable for the immediate
unblock only if the deployment/runbook clearly labels it as a bootstrap test
client.
- The bootstrap callback is local-only and operator-attended. It must be an
exact URI in config, not a wildcard or dynamic registration exception.
- Browser-facing Authelia redirects must use the public Authelia base URL
(`https://auth.coulomb.social`) so the human login page opens correctly.
- KeyCape may still need an internal service URL for back-channel token
exchange. If so, split the current single Authelia URL into browser-facing
authorize URL and internal token URL instead of making the browser use an
in-cluster hostname.
- The MFA prompt should collect only a one-time code, post it back to KeyCape,
validate with privacyIDEA, and then continue the normal OIDC code flow.
- This work unblocks the NetKingdom custody gate in
`NET-WP-0015-platform-root-custody-and-openbao-identity-bootstrap`.
## Implementation Notes
**2026-05-24:** Implemented in source:
- added `netkingdom-bootstrap-console` as a public OIDC client in the sample
KeyCape config, while keeping the local callback registered on `demo-app`
for compatibility,
- split Authelia browser redirects from server-side token exchange via
`browserBaseURL` and `tokenBaseURL`,
- added a browser MFA challenge page at `POST /authorize/callback` that
validates the one-time code with privacyIDEA before issuing the downstream
OIDC authorization code,
- updated NetKingdom's `keycape-config` generation template and bootstrap
console to use the dedicated client,
- added regression tests for callback registration, split Authelia URLs, MFA
challenge rendering, valid OTP continuation, and invalid OTP failure.
Live use still requires deployment: build/publish the updated KeyCape image,
refresh the live `keycape-config` Secret through the custodian age-key unlock
ceremony, and restart the KeyCape deployment.
---
## T01 - Register the bootstrap console callback client
```task
id: KEY-WP-0003-T01
status: done
priority: high
state_hub_task_id: "b396c99f-d711-475a-9cba-4f03a1db561d"
```
Add a KeyCape client registration for the bootstrap console. Either create a
dedicated `netkingdom-bootstrap-console` public client or update `demo-app`
temporarily with these exact redirect URIs:
- `http://127.0.0.1:8876/oidc/callback`
- `http://localhost:8876/oidc/callback`
Update the sample config, tests, and deployment/runbook references so the
registered client is reproducible and not just a live-cluster patch.
Gate: an authorize request using the local callback no longer returns
`invalid_profile_usage` for `redirect_uri`.
## T02 - Separate browser-facing and internal Authelia URLs if needed
```task
id: KEY-WP-0003-T02
status: done
priority: high
state_hub_task_id: "46172e6d-3e11-493c-b223-79c2fc321aec"
```
Confirm whether the current `authelia.baseURL` is safe to use for both browser
redirects and server-side token exchange. If not, add explicit configuration
for the browser authorize base URL and internal token/userinfo base URL.
Gate: the first browser redirect leaves `https://kc.coulomb.social` for
`https://auth.coulomb.social/...`; server-side token exchange still works from
inside the deployment.
## T03 - Add a browser MFA challenge step
```task
id: KEY-WP-0003-T03
status: done
priority: high
state_hub_task_id: "92fca4d0-6215-4ea6-9f80-9178ae183acb"
```
When `CheckMFARequired` returns true after the Authelia callback, render a
minimal KeyCape MFA challenge page instead of requiring `mfa_token` in the
callback query string. The page should:
- show the authenticated username and client display name,
- collect only the OTP code,
- preserve the pending OIDC state server-side,
- validate with privacyIDEA,
- continue to issue the normal authorization code on success,
- fail closed with the existing telemetry on invalid MFA.
Gate: a user enrolled in privacyIDEA can complete password + OTP in the
browser and is returned to the registered downstream callback.
## T04 - Add end-to-end profile tests for the bootstrap login path
```task
id: KEY-WP-0003-T04
status: done
priority: medium
state_hub_task_id: "079a5929-1864-4461-a64c-746cebca469d"
```
Add tests that cover:
- local bootstrap callback registration,
- rejection of unregistered callbacks remains intact,
- Authelia browser redirect uses the expected public URL,
- MFA-required login presents a challenge instead of immediate failure,
- invalid OTP fails closed,
- valid OTP produces an authorization code bound to the original PKCE session.
Gate: `make test` passes and the negative redirect URI tests remain green.
## T05 - Document the live rollout ceremony
```task
id: KEY-WP-0003-T05
status: done
priority: medium
state_hub_task_id: "1d67225d-a20b-4e36-9b2e-20836be2f439"
```
Document the deployment path for updating live KeyCape config without
regenerating unrelated secrets. The runbook must fit the NetKingdom custodian
age-key model: decrypt or unlock only during an attended ceremony, apply the
updated client registration/config, restart KeyCape, and remove plaintext
secret material afterward.
Gate: an operator can update the live `keycape-config` Secret and verify the
bootstrap console OIDC login without printing or committing secrets.

View file

@ -0,0 +1,332 @@
---
id: KEY-WP-0004
type: workplan
title: "Binky Hedgehog GmbH as first NetKingdom tenant + qonto runtime workload identity"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
updated: "2026-07-29"
state_hub_workstream_id: "817709be-5756-5ef7-9ccd-dcb84b6b9c64"
---
# Binky Hedgehog GmbH as first NetKingdom tenant + qonto runtime workload identity
Binky Hedgehog GmbH ("binky") becomes the **first real tenant** provisioned in
the NetKingdom IAM stack, per the NetKingdom IAM Profile v0.2's tenant claim
model (`net-kingdom/canon/standards/iam-profile_v0.2.md` §"Tenant Claim") —
distinct from `tenant:platform` (control plane) and `tenant:coulomb` (the
ecosystem-development tenant this workstation already operates under).
This closes **Gap #1** flagged in `qonto-assistant`'s
`workplans/QONTO-WP-0003-mcp-surface.md` closure: no OIDC issuer existed
anywhere in the fleet, so `qonto-assistant`'s MCP surface shipped with an
interim shared-secret bearer token instead of real workload identity.
`key-cape` is "Implementation complete (v0.1)" and already implements the
profile's `client_credentials`/workload-identity grant for service accounts
— it's the component that should close this gap, not `user-engine`
(downstream user/profile data layer, not an identity provider) or
`identity-canon` (terminology research only).
**Depends on:** `key-cape` v0.1 (done, `KEY-WP-0001`), NetKingdom IAM Profile
v0.2 tenant + service-account claim rules (already ratified canon — no new
profile work needed, this is provisioning against an existing contract), and
the cross-repo runtime direction now tracked in
`railiance-master/workplans/RMASTER-WP-0019-knative-qonto-runtime-on-reef-railiance.md`.
This workplan no longer targets a permanent direct `qonto-assistant`
deployment shape. Its workload-identity lane is intended for the future
`rapp-qonto` runtime on `rail-knative` hosted by `reef-railiance`, with
current `qonto-assistant` consumption allowed only as a migration bridge until
that package/rail path exists.
## Task: Confirm tenant identifier and provision the tenant plane
```task
id: KEY-WP-0004-T01
status: done
priority: high
state_hub_task_id: "c3bdbdb6-6b3f-51b5-8521-d67f113f1c3d"
```
**Decided 2026-07-23 (Bernd):** the IAM profile's `tenant:customer:<name>` /
`tenant:sandbox:<name>` suggested identifiers name a tenant by **capability
role** (customer, vendor, ...) — but tenants already known to hold multiple
roles at once (see `net-kingdom/docs/princedom-isolation-exploration.md`'s
unratified `PLTF`/`IAM`/`VEN`/`CUS` role model; Binky itself is both `CUS`
and, later, `VEN`). Using a role word as the tenant *grouping* would collide
with that separate role dimension. Decision: classify tenants by an
onboarding-risk / entity-shape **grouping**, orthogonal to capability role:
```text
trial — test/trial/showcase tenants only
friendly — known, easily reached, tolerant of experimentation/instability
single — one-person business entities (freelance consultants)
small — up to 10 employees at time of onboarding (attoo)
medium — up to 100 employees (attoo)
large — up to 1000 employees (attoo)
enterprise — 1001+ employees (attoo)
consumer — private individuals
family — a legal family
community — a non-legal group of people
association — a legal association of people
agentic — financially enabled AI entities
```
`tenant:<grouping>:<name>` replaces `tenant:customer:<name>` /
`tenant:sandbox:<name>` as the identifier shape. **Binky maps to `friendly`**
— known, reachable, tolerant of early instability — giving
**`tenant:friendly:binky`** (slug matches the OpenBao path already in
production, `tenants/binky/qonto-api`).
**Open question, not yet resolved by this decision:** this grouping list
classifies tenants as external-shaped entities: it has no obvious slot for
`tenant:coulomb` itself (the platform's own internal/reference tenant,
ADR-0006) — none of `trial`/`friendly`/`single`/.../`agentic` fit "the
platform building itself." Confirm with Bernd whether `tenant:coulomb` stays
an ungrouped special case (alongside `tenant:platform`) or gets folded into
this scheme somehow, before treating the grouping list as complete.
**Also open:** this is a fleet-wide vocabulary decision — the ratified
`net-kingdom/canon/standards/iam-profile_v0.2.md` "Tenant Claim" section
still documents the old `customer`/`sandbox` suggested identifiers, and
NetKingdom owns that canon per ADR-0011. Recording this grouping list only
in this key-cape workplan lets it drift from canon. Recommend a follow-up:
either a decision record or an ADR in `net-kingdom` adopting this grouping
list as the tenant-identifier standard, superseding the old suggested
identifiers. Not drafted here — flagging so it isn't lost.
Provision whatever tenant/org-unit boundary key-cape's current backend
(LLDAP/Authelia) needs so tokens issued for subjects under this tenant
correctly carry `tenant:friendly:binky` and nothing implies platform-root
authority for it (profile rule: tenant administration for a tenant must
never imply platform-root authority).
Done when: `tenant:coulomb`'s place in the grouping scheme is resolved; the
`net-kingdom` canon-update question above is answered (drafted or explicitly
deferred); a test-subject token carries the `tenant:friendly:binky` claim.
2026-07-27: IAM Profile v0.2 now preserves `tenant:platform` and
`tenant:coulomb` as reserved ungrouped tenants and ratifies the external
grouping vocabulary. The live service-token proof carries
`tenant:friendly:binky` without platform authority.
## Task: Provision bernd.worsch@binky-hedgehog.com as tenant-admin
```task
id: KEY-WP-0004-T02
status: done
priority: high
state_hub_task_id: "202c419e-838f-5c2f-8a52-9b1c36a6d6b9"
```
Create the user in key-cape's current backend, enroll MFA per profile
requirements (`assurance` claim, `net-kingdom/canon/standards/iam-profile_v0.2.md`),
and assign a tenant-admin role/group scoped to `tenant:friendly:binky` only.
2026-07-27 direction update: do not complete this through a one-shot operator
script. `USER-WP-0020` and `NK-WP-0023` now own a reusable self-service and
administration portal plus NetKingdom provisioning adapters. This task is
their first production acceptance case.
The `binky-hedgehog.com` mailbox must exist and be reachable for account
verification, but mailbox availability is now an input to the reusable
registration flow rather than a manual provisioning procedure.
Done when: `bernd.worsch@binky-hedgehog.com` completes OIDC/PKCE + MFA login
and receives a token carrying `tenant:friendly:binky` and a tenant-admin role
claim; the same token does **not** grant access to `tenant:platform` or any
other tenant.
2026-07-28: user-engine's deployed portal API created the tenant-admin
membership and provisioned/linked the LLDAP identity through the reusable
NetKingdom adapter. The identity is intentionally in
`password_setup_required`; LLDAP has no SMTP configuration for its native
reset delivery. First-password handoff, MFA enrollment, and final human-token
claim/denial evidence remain. No operator-set password or raw credential was
used as a shortcut.
2026-07-28 update: the reusable password handoff is now live at the canonical
KeyCape host and is issued/renewed through the user-engine administration UI.
Links are opaque, expire after 15 minutes, are single use, and older links are
revoked on renewal. A disposable-user live test proved password registration,
directory login, replay denial, and cleanup. This task now awaits only the
Binky user's own password choice, MFA enrollment, and final scoped-token
acceptance.
2026-07-29: the first portal attempt correctly denied cross-tenant access
because KeyCape had not projected the established `net-kingdom-admins` group
to the new platform-operator role. Commit `90a2078` adds and tests the explicit
`tenant:platform` / `platform-operator` compatibility mapping and is deployed
Ready. The operator must start a fresh OIDC session so the corrected claims
are minted.
2026-07-29 human acceptance: the Binky administrator completed the reusable
password setup, enrolled a privacyIDEA TOTP factor, and successfully signed in
through KeyCape with password plus OTP. Enrollment revealed that privacyIDEA's
default QR label exposed only the token serial. The live
`coulomb-friendly-token-labels` enrollment policy now emits issuer `Coulomb`
and label `{user}@{realm}` for future tokens; existing authenticator entries
must be renamed locally because wallets do not accept remote label updates.
The live token then carried `tenant:friendly:binky`, roles `user` and
`tenant-admin`, only the Binky users/admins groups, and AAL2 assurance with
`pwd` + `otp` and `mfa: true`. The same session was denied access to both
`tenant:platform` and `tenant:coulomb` administration. Acceptance complete.
## Task: Register a workload-identity OIDC client for the qonto runtime
```task
id: KEY-WP-0004-T03
status: done
priority: high
state_hub_task_id: "0d3ccd35-bad5-5c30-95a3-61fce584425d"
```
Register an OIDC client per the profile's service-account/workload-identity
flow (`client_credentials` or the documented workload-token-exchange
equivalent — §"Service Account Flow"), scoped to `tenant:friendly:binky`,
with the minimum role/scope the qonto runtime surface actually needs. The
intended steady-state consumer is the future `rapp-qonto` runtime on
`rail-knative`; current `qonto-assistant` callers may use the same lane only
as a documented migration bridge until that package split exists. No
interactive login for this client — it's for agent-harness sessions and other
automated callers, not humans.
The resulting `client_secret` must never be pasted into chat, Git, this
workplan, or a State Hub message or progress event — hand-off happens only
through T04's secrets-engine/OpenBao lane.
Done when: a `client_credentials` token exchange against key-cape succeeds
for this client and produces a short-lived service token carrying
`tenant:friendly:binky`; the `client_id`, granted scopes, and intended future
binding to `rapp-qonto` are documented here; no secret material appears
anywhere in this repo's history.
2026-07-26: Added `docs/qonto-runtime-identity-contract.md` with stable
principal/client names, least-privilege claims, non-secret evidence, and the
automation/custody boundary. Actual client issuance remains open.
2026-07-27: `KEY-WP-0006` delivered `rapp-qonto-client`. Live exchange
produced only the documented tenant, role, and `qonto:read` scope; wrong
secret and excessive scope were denied.
## Task: Route client_secret custody through secrets-engine + OpenBao
```task
id: KEY-WP-0004-T04
status: done
priority: high
state_hub_task_id: "9cb09dbf-1fd0-55c5-9c45-b3b7296ecd91"
```
Coordinate with `secrets-engine` to create a scoped OpenBao lane for this
`client_secret`, mirroring the existing `tenants/binky/qonto-api` pattern
already in production use for the Qonto bank credential itself. The steady
target is a dedicated future runtime role for `rapp-qonto`; if a temporary
bridge via the current `qonto-assistant` runtime role is needed before the
package split lands, it must be recorded as temporary rather than treated as
the final contract.
Done when: the intended qonto runtime role can fetch the `client_secret` via
`secrets-engine`/OpenBao; any temporary `qonto-assistant` bridge is explicitly
marked transitional; Bernd never handles the raw value at any point in this
flow.
2026-07-26: `warden plan` classified first-time provisioning as one explicit
founder authority act through `warden desk`. No secret was requested or read.
All repeatable verification and revocation work is designated for sanctioned
agent execution and later functional automation.
2026-07-27: Custody is established at
`platform/workloads/rapp-qonto/keycape-client`, with runtime injection through
`sso/keycape-rapp-qonto-client`. No raw value entered evidence or human
handling.
## Task: Register the credential lane with ops-warden
```task
id: KEY-WP-0004-T05
status: done
priority: high
state_hub_task_id: "39f1f803-732e-5905-97d9-c7493c66c8ee"
```
This is the task that directly minimizes Bernd's ongoing engagement: add an
entry to ops-warden's routing catalog (`registry/routing/catalog.yaml`)
pointing at this new OIDC client / login lane, following the pattern already
documented in `ops-warden/SCOPE.md`'s "Issue vs route" table ("Login / OIDC /
MFA → key-cape / Keycloak → Assist — route; proxy `login` lane when
`exec_capable`"). ops-warden does not mint tokens or take custody here — it
routes/assists, same as every other credential lane it fronts. The lane should
describe the future `rapp-qonto` runtime path on `rail-knative`, not only the
current source repo.
Once the OpenBao lane from T04 exists, run ops-warden's workload security
posture conformance checker
(`ops-warden/scripts/check_secret_posture_conformance.py`) against the qonto
runtime workload that will later become `rapp-qonto`; until the split lands,
`qonto-assistant` may be used as the migration subject if that temporary state
is recorded. Confirm it meets the expected maturity tier (dev/test/prod
posture, M0–M3 workload maturity) before this goes live with real
credentials, and record the result (pass or documented exception).
Done when: `warden route show` (or equivalent) resolves this lane correctly;
posture conformance check passes or exceptions are explicitly recorded; from
this point on, discovering and using this credential lane needs `warden
access`, not direct key-cape/OpenBao admin knowledge.
2026-07-26: Routing requirements and required conformance evidence are now
documented. Catalog publication waits for the real client and OpenBao role so
the route does not advertise an unusable lane.
2026-07-27: Published route `rapp-qonto-keycape-client`; route resolution and
the metadata-only M3/prod posture check both pass.
## Task: automation@binky-hedgehog.com service identity (deferred)
```task
id: KEY-WP-0004-T06
status: cancel
priority: low
state_hub_task_id: "fbee11fc-7a60-5dd7-8522-f80ddb4aea55"
```
Forward-looking per Bernd's framing ("I guess it will later be helpful") —
**do not provision until a concrete automation workflow actually needs its
own identity** distinct from the qonto runtime workload client from T03.
Dormant service-account credentials are themselves a posture risk (unused
attack surface, nothing to rotate against), so this stays parked rather than
front-loaded.
When a real consuming workflow exists: same pattern as T02 but scoped to an
automation-appropriate role (not tenant-admin), likely paired with its own
`client_credentials` registration for whichever agent/service authenticates
as it, following T03/T04/T05's pattern rather than inventing a new one.
Done when: explicitly triggered by a named consuming workflow — until then
this task stays `todo` and does not block closing the rest of this workplan.
## Task: Closure review
```task
id: KEY-WP-0004-T07
status: done
priority: low
state_hub_task_id: "77b0556f-62fd-5046-9ba1-718246151a6f"
```
Mark workplan finished when T01–T05 are done (T06 may legitimately remain
`todo`/parked by design — see its own task note). Confirm end to end: a
human token (bernd.worsch@binky-hedgehog.com, tenant-admin) and a service
token (future qonto runtime client, with any temporary `qonto-assistant`
bridge explicitly recorded) both resolve to `tenant:friendly:binky` and
nothing else; the credential lane is discoverable via `warden access` without
Bernd touching key-cape/OpenBao admin surfaces directly. The runtime follow-up
then continues in `QONTO-WP-0004` and `RMASTER-WP-0019`. Run
`statehub fix-consistency`.
2026-07-29 closure: T01–T05 are complete, T06 is intentionally cancelled,
the human Binky flow passed AAL2 and tenant-isolation acceptance, and the qonto
workload client passed its positive-scope and negative credential/scope checks.

View file

@ -0,0 +1,245 @@
---
id: KEY-WP-0005
type: workplan
title: "IAM Profile core claims: tenant, principal_type, groups, roles, assurance, tenant_roles"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-23"
updated: "2026-07-23"
state_hub_workstream_id: "711a8303-69aa-5db6-9043-92530c6dc81b"
---
# IAM Profile core claims: tenant, principal_type, groups, roles, assurance, tenant_roles
Discovered while planning `tenant-engine/workplans/TEN-WP-0003-flex-auth-write-authorizer.md`:
`key-cape`'s token issuance (`src/internal/server/oidc/token.go`) does not
currently emit `tenant`, `principal_type`, `groups`, `roles`, or
`assurance` — none of `net-kingdom/canon/standards/iam-profile_v0.3.md`'s
Core Claims are implemented yet, only `preferred_username`/`email`/`groups`
(LDAP-sourced, not the profile's normalized `groups` claim) and OIDC's own
base claims (`nonce`, etc.).
This is bigger than "add the `tenant_roles` claim on top of existing tenant
support" — `key-cape` isn't yet profile-conformant at all on the tenant/
principal/assurance axis. `tenant_roles` (the reason this workplan exists)
is the last, optional claim in that same set (v0.3's addition on top of
v0.2's baseline) — implementing it in isolation without the claims it
depends on for meaning (`tenant`, `principal_type`) would be incoherent.
**This workplan is registered, not implemented, by the same session that
found the gap.** Token issuance is security-sensitive code in a Go codebase
with its own adapter conventions (`internal/adapters/{lldap,privacyidea,authelia}`)
that deserves dedicated context before changing, not a rushed edit from a
first read of `token.go`. Scoped and sequenced here so the work isn't lost.
**Depends on:** `net-kingdom/canon/standards/iam-profile_v0.3.md` (ratified).
**Related:** `tenant-engine/workplans/TEN-WP-0003-flex-auth-write-authorizer.md`
(the consumer waiting on `tenant_roles`); `key-cape/workplans/KEY-WP-0004-binky-hedgehog-tenant-onboarding.md`
(Binky's onboarding, which needs `tenant` claim support to produce a
correct `tenant:friendly:binky` token at all).
## Task: Emit `tenant`, `principal_type`, `groups`, `roles`, `assurance`
```task
id: KEY-WP-0005-T01
status: done
priority: high
state_hub_task_id: "32d5fbdc-cd2b-5bc8-9d2b-1a410b55e806"
```
Bring `token.go`'s claim assembly up to `iam-profile_v0.3.md`'s Core Claims
table baseline (the v0.2-originated set):
- `tenant`: resolve from whatever user/service/agent-to-tenant mapping
`key-cape`'s LLDAP/Authelia backend can provide today; if no such mapping
exists yet, that's itself a sub-task — this claim cannot be stamped from
nothing.
- `principal_type`: `human` for the existing Authorization Code + PKCE flow;
`service` for client-credentials flows (check whether `key-cape` already
issues client-credentials tokens at all — `grant_types_supported` in the
discovery contract claims it does, but that needs verifying against real
code, the same way this workplan verified `tenant` claim absence).
- `groups`, `roles`: normalize from LDAP group/role data into the profile's
flat string-array shape.
- `assurance`: `{level, methods, mfa, source, at}` per the profile —
`privacyIDEA`-backed MFA evidence maps to `aal2`; single-factor maps to
`aal1`.
Done when: `tools/iam-profile-conformance/` (already exists) passes against
these claims for both human and service token issuance paths.
**Done 2026-07-24, human flow only — scope split made explicit, not
silently narrowed:** verified first (as flagged): `grant_types_supported`
advertises `client_credentials` in `discovery.go`, but `token.go`'s
`ServeHTTP` only ever accepted `grant_type == "authorization_code"` — there
is no service-token issuance path at all, so "verifying against service
token issuance" isn't possible until that path exists. Building a
`client_credentials` grant handler from scratch is a distinct, materially
larger piece of work than extending the existing human flow (new endpoint
logic, new client-authentication semantics, no existing tests to extend)
— **not attempted in this pass**, left open below rather than declared done.
What shipped for the human Authorization Code + PKCE flow:
- `domain.User.Tenant` (new field, `omitempty`, empty-safe for existing
YAML configs) + `token.go`'s `effectiveTenant()`: falls back to
`tenant:coulomb` (this workstation's actual current tenant, ADR-0006)
when unset — never emits an empty `tenant` claim, never silently assigns
a user to a tenant they weren't given.
- `principal_type: "human"`, unconditional.
- `groups`/`roles`: promoted from scope-gated (only `groups`, only with the
`groups` scope) to unconditional core claims, always present as `[]`
when empty, never `null` (`nonNilStrings()`). One pre-existing test
asserted the old scope-gated behavior for `groups`
(`TestTokenHandler_ScopeFiltering_ProfileScope`) — updated to assert the
new, intentional behavior, not silently left failing or reverted.
- `assurance`: built from whether MFA was *actually verified in this
session* (`PKCESession.MFAVerified`, new field, threaded through
`completeAuthorization`'s two call sites in `authorize.go`), not from
static enrollment state — `aal2` only when MFA was required and passed
this time, `aal1` otherwise.
Verified for real: `go build ./...` and `go vet ./...` clean; `go test
./...` green across the whole repo (one pre-existing test updated to match
the new intentional behavior, described above, not silently broken). Two
new `authorize_test.go` cases directly assert `PKCESession.MFAVerified` is
set correctly on both the MFA-required and MFA-not-required paths.
`tests/profile/profile_test.go`'s `TestCompleteTokenFlow` — the repo's own
full HTTP integration test (real server, real PKCE flow, real JWT decode)
— extended with real assertions for all five new/changed claims, not just
presence checks: `tenant == "tenant:coulomb"` (the fallback path, since
the test's `testuser` has no explicit tenant), `principal_type == "human"`,
`groups == ["developers"]`, `roles == []`, and
`assurance == {level: "aal1", mfa: false, source: "key-cape", ...}` (this
test's `mockMFA` never requires MFA, so `aal1` is the correct expected
value, not `aal2`).
Not run: `net-kingdom/tools/iam-profile-conformance`'s Python conformance
tool against a live minted token — it needs a reachable issuer, which would
mean standing up the full `docker-compose.dev.yml` stack
(Authelia+LLDAP+privacyIDEA) beyond what's practical in this pass. The
equivalent claim-shape and value checks it would perform are covered by
`TestCompleteTokenFlow`'s real HTTP round trip instead — not a like-for-like
substitute, but real coverage, not an assumption.
**Explicitly still open** (not this task, not silently dropped):
`client_credentials` grant handling — service-token issuance for `key-cape`
callers with no human in the loop (`tenant-engine`'s own eventual key-cape
integration, and any other service caller). Needs its own scoped follow-up:
a new `principal_type: "service"` code path in `token.go` (or a sibling
handler), client secret/authentication semantics, and its own test suite —
this workplan's `T02` (`tenant_roles`) doesn't strictly require it, since
`tenant_roles` attaches to whatever principal_type a token already carries,
human included.
## Task: Emit `tenant_roles` (optional, cached)
```task
id: KEY-WP-0005-T02
status: done
priority: high
state_hub_task_id: "c3ee6021-b28d-50a5-be60-ecb5631f56e9"
```
At token-issuance time, call `tenant-engine`'s cache-read endpoint
(`GET /tenants/{tenant_id}/roles`) using the `tenant` claim resolved in T01,
and stamp the result as the optional `tenant_roles` claim
(`iam-profile_v0.3.md`, "Tenant Roles" section). Follows the same adapter
pattern as `internal/adapters/{lldap,privacyidea,authelia}` — a new
`internal/adapters/tenantengine` package, not inline HTTP calls in
`token.go`.
Fail open on this claim specifically, not closed: `tenant_roles` is
documented as a cache callers must not trust for privileged decisions
anyway (`flex-auth` re-validates live). If `tenant-engine` is unreachable at
issuance time, omit the claim rather than fail the entire token issuance —
losing a performance optimization is acceptable; blocking login because a
cache source is down is not. This is the one place in the whole `tenant_roles`
design where fail-open is correct, precisely because the live-lookup path
exists as the real gate.
Done when: token issuance still succeeds with `tenant_roles` omitted when
`tenant-engine` is unreachable (test simulates the outage); present and
correct when reachable, for a tenant with known role grants.
**Done 2026-07-24:** New `internal/adapters/tenantengine` package
(`Client.Roles(ctx, tenantID) ([]string, bool)`), mirroring
`internal/adapters/{lldap,privacyidea,authelia}`'s shape exactly. Fails
open by construction — unreachable, non-200, or malformed body all return
`(nil, false)`, never an error the caller has to specially handle; a `nil
*Client` also fails open safely rather than panicking, so `TokenHandler`
doesn't need a separate "is this configured" branch. Wired into
`TokenHandler.TenantEngine` (nil by default — existing tests and call
sites are unaffected); `token.go` stamps `tenant_roles` only when `ok`.
7 adapter-level tests (success, non-200, malformed body, connection
failure, timeout, nil client, empty base URL). 3 new `token_test.go` cases
prove the actual required behavior end-to-end through `TokenHandler`, not
just at the adapter layer: present and correct when reachable; **token
issuance still returns 200 with every other required core claim intact**
when unreachable, `tenant_roles` simply absent (the literal done-criteria);
absent when `TenantEngine` isn't configured at all. `go build`/`vet`
clean, `go test ./...` green repo-wide.
**A real bug found and fixed along the way, not worked around:** the first
live cross-process check (real `flex-auth`, real `tenant-engine`, this
adapter) returned `tenant_not_found` for a tenant that genuinely existed.
`tenant-engine`'s `GET /tenants/{tenant_id}/roles` was keyed by its
internal `tenant_id`, but `key-cape` (like any external caller) only ever
has the tenant's profile *identifier* (the `tenant` claim value) — it has
no way to know tenant-engine's internal id. Fixed at the source:
`tenant-engine/workplans/ADHOC-2026-07-24.md` (identifier-or-id resolution
added to `InMemoryTenantStore`), re-verified with the exact same live
three-process chain afterward — `roles=[IAM] ok=true`, resolved by
identifier. This is exactly why T02 insisted on a live-server-reachable
test rather than only an `httptest` fake: the fake would have used
whatever id shape the test author happened to pick and never caught this.
## Task: Closure review
```task
id: KEY-WP-0005-T03
status: done
priority: low
state_hub_task_id: "c88a3457-2964-5c44-8220-76f82df3a753"
```
Confirm T01–T02 done; conformance suite green for both tasks together.
Note in closure: this unblocks `KEY-WP-0004`'s Binky onboarding actually
producing conformant tokens, and unblocks `flex-auth`'s side of the
`tenant_roles` picture receiving a real claim to reason about (though
`flex-auth`'s live-lookup path doesn't depend on this claim existing — only
the cache-read/performance path does). Run `statehub fix-consistency`.
**Closed 2026-07-24.** T01–T02 done. `go build ./...`, `go vet ./...` clean;
`go test ./...` green across the whole repo. Python
`net-kingdom/tools/iam-profile-conformance` not run against a live instance
(needs the full Authelia+LLDAP+privacyIDEA stack, impractical in this
pass) — real coverage instead came from `tests/profile`'s own full HTTP
integration test (`TestCompleteTokenFlow`) with genuine claim-value
assertions, and from three separate live cross-process checks against a
real `tenant-engine` (one of which found and fixed a real bug in
`tenant-engine` itself, `ADHOC-2026-07-24`).
**What this closes:** `key-cape`'s human Authorization Code + PKCE flow now
emits every IAM Profile v0.3 core claim (`tenant`, `principal_type`,
`groups`, `roles`, `assurance`) plus the optional `tenant_roles` cache,
correctly sourced from a real `assurance` signal (MFA actually verified
this session, not static enrollment) and a real `tenant-engine` call
(fail-open, never blocking login). `KEY-WP-0004`'s Binky onboarding can now
produce conformant tokens for human logins.
**What stays explicitly open, not silently dropped:**
- `client_credentials` / service-token issuance — no such flow exists in
`token.go` at all yet (confirmed in T01); needed before any *service*
caller (not a human) can get a token with `principal_type: "service"` and
its own `tenant`/`tenant_roles`. A materially larger, separate piece of
work than either task in this workplan.
- `flex-auth`'s side of `tenant_roles` (`FLEX-WP-0008`, already closed
separately) doesn't depend on this claim existing at all — its
live-lookup path calls `tenant-engine` directly, so this workplan doesn't
block it, but also doesn't complete the full `tenant_roles` picture on
its own until service tokens exist too.

View file

@ -0,0 +1,116 @@
---
id: KEY-WP-0006
type: workplan
title: "Client credentials and service-token issuance"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-27"
updated: "2026-07-27"
state_hub_workstream_id: "d8d20d8f-61e0-54e2-9a88-a3958f0b6f2d"
---
# KEY-WP-0006 - Client credentials and service tokens
Implement the missing service-principal half of the IAM Profile so
`KEY-WP-0004` can issue a least-privilege identity to `rapp-qonto`.
## T01 - Model confidential service clients
```task
id: KEY-WP-0006-T01
status: done
priority: high
state_hub_task_id: "993913a7-f951-50ee-9b10-d5b9e67b523c"
```
Extend static client registration with an explicit service subject, tenant,
roles, and an environment-backed secret reference. Refuse startup when a
`client_credentials` client is public, lacks its identity fields, or cannot
resolve its secret. Never serialize or log the resolved secret.
2026-07-27: Added explicit service subject, tenant, roles, and in-memory-only
resolved secret fields. Configuration validation rejects public or incomplete
service clients, and startup resolves only `env:` secret references while
reporting the variable name—not its value—on failure.
## T02 - Implement secure client_credentials exchange
```task
id: KEY-WP-0006-T02
status: done
priority: high
state_hub_task_id: "801a6d55-2f91-51b1-88e4-cc7f0867238d"
```
Accept confidential client authentication through HTTP Basic, compare secrets
in constant time, restrict requested scopes to the registered allowlist, and
issue a short-lived access token with `principal_type=service`,
tenant-specific subject/roles, empty groups, `aal1` client-secret assurance,
and optional cached `tenant_roles`. Do not issue an ID token for this
non-human grant.
2026-07-27: Implemented HTTP Basic confidential-client authentication with
fixed-length SHA-256 constant-time comparison, allowlisted scopes, short-lived
service claims, optional tenant-role cache, token-issued telemetry, and no ID
token.
## T03 - Prove positive and negative conformance
```task
id: KEY-WP-0006-T03
status: done
priority: high
state_hub_task_id: "e5c2a8b1-0879-57fa-a197-250a34c75bdb"
```
Cover valid exchange, unknown client, public client, missing/wrong secret,
unsupported grant, excess scope, tenant isolation, telemetry redaction, and
discovery metadata. Run the full Go build, vet, and test suite.
2026-07-27: Added positive service-claim and negative wrong-secret/excess-scope
tests, updated discovery to advertise only implemented grants/auth methods,
and verified `go test ./...`, `go vet ./...`, and `go build ./...` across the
module.
## T04 - Provision and verify rapp-qonto
```task
id: KEY-WP-0006-T04
status: done
priority: high
state_hub_task_id: "31af6b8c-96ff-5060-87db-3803b9a9dd12"
```
Generate the client secret without disclosure, store it through the approved
OpenBao lane, register the static `rapp-qonto` client, deploy KeyCape, and
verify a real exchange yields only the documented Binky service claims.
Publish non-secret evidence to `KEY-WP-0004-T03` through T05.
2026-07-27: Provisioned the confidential client secret at
`platform/workloads/rapp-qonto/keycape-client`, injected it into
`sso/keycape-rapp-qonto-client`, registered `rapp-qonto-client`, and deployed
image `main-e877d27-2` on railiance01. A real exchange produced only
`sub=rapp-qonto`, `tenant=tenant:friendly:binky`, `principal_type=service`,
`roles=[qonto-reader]`, empty groups, and `scope=qonto:read`; excessive scope
and a wrong secret were denied. Public DNS still resolves
`kc.coulomb.social` to the older CoulombCore endpoint, so the deployment proof
used TLS-preserving direct resolution to railiance01 (`92.205.62.239`).
## T05 - Closure review
```task
id: KEY-WP-0006-T05
status: done
priority: low
state_hub_task_id: "8c72d7d9-4086-5e64-b939-a12bff076124"
```
Close after T01-T04 pass and the repeatable verification path is documented.
2026-07-27: Closure review passed. Build, vet, unit/profile tests, live
positive claims, negative authentication/scope checks, and discovery metadata
all passed. The stale public DNS target is handed off as routing work rather
than weakening the service-token contract.

View file

@ -0,0 +1,81 @@
---
id: KEY-WP-0007
type: workplan
title: "User-engine portal OIDC relying-party integration"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-07-27"
updated: "2026-07-29"
depends_on:
- KEY-WP-0005
- USER-WP-0020
state_hub_workstream_id: "24db4178-705c-5b09-b7db-ee7a345d71a0"
---
# KEY-WP-0007 - User-engine portal OIDC relying-party integration
Register and prove the reusable user-engine portal as a strict KeyCape public
client. This is the authentication edge required by `NK-WP-0023-T03`; it does
not move user-domain or authorization ownership into KeyCape.
## Register the static portal client
```task
id: KEY-WP-0007-T01
status: done
priority: high
state_hub_task_id: "7d722cae-bf6d-52e0-80f4-cc51e9fb42f0"
```
Register `user-engine-portal` for authorization code with mandatory S256 PKCE,
exact callback URIs and only `openid profile email groups`. No wildcard,
implicit flow, client secret, or dynamic registration is allowed.
## Deploy and verify the live client
```task
id: KEY-WP-0007-T02
status: done
priority: high
state_hub_task_id: "0b222062-4352-5ae6-9e85-95787af08409"
```
Update the railiance01 KeyCape configuration without exposing its signing key
or backend credentials. Verify discovery, authorize redirect validation,
unregistered callback denial and successful token exchange through the portal.
## Prove claims and MFA isolation
```task
id: KEY-WP-0007-T03
status: done
priority: high
state_hub_task_id: "859e7322-71dc-58cc-9f44-6f869c2d0b59"
```
Prove issuer, audience, tenant, groups, roles and assurance claims are verified
by the portal and that tenant administration does not imply platform-root.
Complete the Binky user/MFA acceptance through the reusable browser path.
2026-07-27: The live client accepts only the exact portal callback and rejects
an unregistered callback with `invalid_profile_usage`. The portal begins an
S256 PKCE flow and hands authentication to Authelia. LLDAP tenant envelope
mapping now recognizes unambiguous `tenant:<kind>:<slug>:users|admins` groups;
ambiguous multi-tenant directory envelopes fail closed to no explicit tenant.
2026-07-28: suspension now fails closed at both token exchange and
`/userinfo`; the deployed `key-cape:909bb32` image refuses subjects carrying
the `netkingdom-suspended` directory group. The portal admin surface uses this
through the provider-neutral lifecycle API. The human Binky MFA/claims
acceptance remains the final part of this task.
2026-07-29 complete: the reusable browser path issued the Binky password
handoff, completed privacyIDEA TOTP enrollment, and returned through the
portal authorization-code flow. `/api/v1/me` verified the canonical issuer,
`user-engine-portal` audience, `tenant:friendly:binky`, Binky-only groups,
roles `user` + `tenant-admin`, and AAL2 assurance with `pwd` + `otp` and
`mfa: true`. The same session was denied administration of
`tenant:platform` and `tenant:coulomb`. All workplan tasks are complete.

View file

@ -0,0 +1,118 @@
---
id: KEY-WP-0008
type: workplan
title: "Registration handoff and client-aware MFA policy"
domain: infotech
repo: key-cape
status: finished
owner: grok
topic_slug: netkingdom
created: "2026-08-09"
updated: "2026-08-16"
depends_on:
- NK-WP-0025
state_hub_workstream_id: "23eb67d4-cf10-57a1-92f6-90146a294bda"
---
# KEY-WP-0008 - registration handoff and client-aware MFA
Let a registered OIDC client offer NetKingdom signup and request step-up
without making KeyCape an account store or weakening high-assurance clients.
## T01 - Add a safe registration handoff
```task
id: KEY-WP-0008-T01
status: done
priority: high
state_hub_task_id: "e5fb4d73-1e19-565c-80d5-e0eee274ce63"
```
Define an allow-listed registration URL for eligible clients and preserve
client ID, redirect URI, PKCE intent, tenant hint, and return context in a
signed, expiring state envelope. Registration completion must restart the
normal authorization flow and must not mint a token directly.
Done when unknown users can choose signup from an eligible authorization flow
without open redirect, client substitution, or state replay.
Implemented `prompt=create` and `/authorize/register` against the client's
static `registrationUrl`, plus HMAC-signed `kc_handoff` envelopes consumed
once at `/authorize/return`. Return restarts `/authorize` and never mints a
code. Ineligible clients get no signup link. Live registration entry remains
user-engine-owned per NK-WP-0025; KeyCape only issues the return envelope.
## T02 - Replace global MFA with client-aware minimum assurance
```task
id: KEY-WP-0008-T02
status: done
priority: high
state_hub_task_id: "1ba50454-b6f5-578e-a526-da74b8082a89"
```
Replace the single require-for-all switch with policy that combines client
minimum assurance, requested ACR/step-up, tenant policy, protected action, and
current session assurance. Preserve mandatory MFA for platform/admin clients.
Allow coulomb-social ordinary login at password assurance when no stronger
rule applies.
Done when one low-assurance client cannot suppress MFA for another client or
reuse an under-assured session for a high-assurance request.
Implemented with nullable per-client `mfaRequired`: an explicit client value
overrides the provider default only for that client. Absent values preserve
the existing provider-driven policy.
2026-08-16: `DecideAssurance` now combines client minimum, requested ACR,
provider default, `max_age`, `prompt=login`, and current KeyCape login-session
level. An AAL1 session cannot satisfy an AAL2 client or `acr_values=aal2`.
`coulomb-social` in `config/dev-config.yaml` is `mfaRequired: false`; other
clients keep the provider default. Users without an enrolled factor are sent
to the client's `enrollmentUrl` instead of completing authorization.
## T03 - Support explicit step-up and fresh authentication
```task
id: KEY-WP-0008-T03
status: done
priority: high
state_hub_task_id: "f7acbc12-e597-5d1e-8b29-bfa02237730f"
```
Implement supported ACR/max-age or equivalent IAM Profile parameters, invoke
privacyIDEA only when policy requires it, and return verifiable assurance
claims. Handle users without an enrolled factor through a safe enrollment
handoff rather than an authorization bypass.
Done when coulomb.social can request MFA for a profile/action and verify the
result from token claims.
Implemented `acr_values` preservation and AAL2/MFA forcing through the
privacyIDEA challenge. The existing token `assurance` claim reports `aal2`
and `mfa: true` only after successful verification.
## T04 - Prove policy isolation and compatibility
```task
id: KEY-WP-0008-T04
status: done
priority: high
state_hub_task_id: "bc660767-387e-59a5-bfce-caccf4082c08"
```
Test known and unknown users, registration link eligibility, state expiry and
replay, password-only coulomb-social login, profile/action step-up, no-factor
enrollment, OpenBao mandatory MFA, cross-client session reuse, and logout.
Keep static client registration and exact redirect rules unchanged.
Done when existing high-assurance clients pass unchanged and the new
coulomb-social journey passes live.
2026-08-16: isolation tests cover known/unknown users, registration
eligibility, state expiry/replay, password-only coulomb-social, ACR step-up,
no-factor enrollment handoff, OpenBao mandatory MFA, cross-client AAL1
session reuse, logout, and exact redirect enforcement. Full Go suite passes.
Live coulomb-social AAL1/AAL2 isolation was already proven on railiance01
under NK-WP-0025-T05 (2026-08-14); this closeout adds the KeyCape-side
regression suite and `/logout`.

View file

@ -0,0 +1,138 @@
---
id: KEY-WP-0009
type: workplan
title: "Provider capability declarations and bounded service identities"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-08-23"
updated: "2026-09-27"
depends_on:
- NK-WP-0030
- KEY-WP-0006
state_hub_workstream_id: "c1a9b1cc-2ff0-566a-b544-1a2ef967fc0d"
---
# KEY-WP-0009 — Provider capabilities and bounded service identities
Publish KeyCape-owned security-scenario interfaces and make the existing
service-token issuer precise enough for OpenBao machine-login consumers. This
work accepts identity issuance ownership without taking over OpenBao roles,
policies, secret custody, or privacyIDEA token lifecycle.
## Publish C1 and C2b provider declarations
```task
id: KEY-WP-0009-T01
status: done
priority: high
state_hub_task_id: "eaf7af48-b3d6-5f85-b0ce-ff2b640f9cc3"
```
Publish Playbook Capability Contract v0.1 declarations for the KeyCape C1
runtime and its privacyIDEA-backed C2b integration. Name exact entry points,
parameter authority, resource ownership, trust requirements, and readiness
evidence. Do not claim C2a or privacyIDEA-owned token lifecycle.
Published `capabilities/playbooks/key-cape.lightweight-sso.yaml` and
`capabilities/playbooks/key-cape.privacyidea-token-authority.yaml`. The latter
claims the KeyCape integration for C2b while leaving factor enrollment, token
state, custody keys, validation decisions, and lifecycle with privacyIDEA.
## Define bounded service-auth contracts
```task
id: KEY-WP-0009-T02
status: done
priority: high
state_hub_task_id: "212eb75c-a36c-5cb8-bf67-7d524f00f9ef"
```
Define the KeyCape service-auth claims, renewal/expiry, failure, custody, and
owner boundaries required by secrets-engine and OpenBao JWT roles. Add
per-client access-token lifetimes so a bounded client contract does not depend
on an unrelated global default.
Added a validated 1m-1h per-client `tokenLifetime` override, applied it to JWT
`exp` and `expires_in`, and documented claims, renewal, expiry, residual JWT
validity, explicit OpenBao cleanup, and no-fallback failure semantics in
`docs/openbao-service-auth-contract.md`.
## Accept coding-agent issuance ownership
```task
id: KEY-WP-0009-T03
status: done
priority: high
state_hub_task_id: "c41e0144-8169-57cf-8c34-eb52e65d6d76"
```
Publish the non-secret static registration for `codex-railiance-platform` with
the exact audience, subject, tenant, role, scope, and 15-minute lifetime already
accepted by railiance-platform. KeyCape owns JWT issuance and client disablement;
railiance-platform owns the exact-bound OpenBao role and policy; OpenBao owns
resulting token enforcement; secret values remain outside this repository.
Accepted ownership through the exact non-secret registration in
`config/service-clients.example.yaml`. Added the parallel reviewed
`secrets-engine-openbao` contract. Live value generation/materialization,
deployment merge, and OpenBao role/policy application remain with their named
custody and platform owners and are not implied by this source registration.
## Validate and hand off
```task
id: KEY-WP-0009-T04
status: done
priority: high
state_hub_task_id: "6945c0c0-bbb6-552b-8d62-d806d3fd0018"
```
Run the canonical declaration validator, Go formatting/build/vet/tests, and
repository checks. Reply to NetKingdom, secrets-engine, ops-warden, and
railiance-platform with revision-independent paths and exact ownership status.
Both declarations pass NetKingdom's canonical validator. The full Go suite,
vet, build, `gofmt`, YAML parsing, and `git diff --check` pass using an explicit
Go 1.23 toolchain and writable local cache; the workstation's default Go cache
is read-only and was not used.
2026-09-05 correction: the earlier statement that State Hub handoffs were
delivered was not supported by receipt IDs. The audit found no matching handoff
receipts in the Hub response. KEY-WP-0014-T02 records the correction.
2026-09-08 closure. Verification re-run on `main` with `GOTOOLCHAIN=go1.23.0` and
a writable `GOCACHE`: `make contract-test` PASS, `go build ./...`, `go vet ./...`
and `go test ./...` all pass, `git diff --check` clean. `gofmt` had drifted on
`src/internal/server/telemetry/events.go` and `src/tests/migration/scenario_c_test.go`
(comment alignment only, no semantic change); both were reformatted so the
formatting claim now holds.
The four handoffs were then actually sent, and each receipt is readable back via
`GET /messages/?from_agent=key-cape`:
| Recipient | Receipt id |
| --- | --- |
| netkingdom | `692ed0d7-e21b-48e7-8600-5b32b551c627` |
| secrets-engine | `479a1698-87dc-4ee2-a732-c2dc18bc4e84` |
| ops-warden | `5bc58847-eed5-4164-bb0f-a0d8455e75b8` |
| railiance-platform | `9c6a4e70-6bc0-4ad6-8293-35a56e507dfc` |
Each names revision-independent paths, the 2026-09-08 verification result, and
the same ownership split: KeyCape owns issuance, static registrations, client
disablement and the claim set; secret custody, OpenBao roles/policies and token
enforcement, privacyIDEA factor and token lifecycle, resource authorization and
deployment remain with their named owners. C2a is not claimed. This closes the
notification gap; it is still not evidence of live deployment or provider cutover.
### 2026-09-27 operator tenant-zero correction
OpenBao infrastructure identities belong to `tenant:platform`, not Coulomb.
Corrected both static infrastructure registrations, matching source tests and
the tenant/OpenBao contracts. Consumer preflight and platform role sources are
updated together; no live registration or token migration is claimed. The
provider contract remains complete; RPF-WP-0035-T02 and SECRETS-WP-0008-T06
retain coordinated custody/deployment and native rejection/revocation proof.
Human fallback and workload tenant grants are not elevated to platform.

View file

@ -0,0 +1,33 @@
---
id: KEY-WP-0010
type: workplan
title: "Admit the operator-tunneled OpenBao callback"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-08-23"
updated: "2026-08-23"
depends_on:
- RAILIANCE-WP-0027
state_hub_workstream_id: "8bdf9a24-9dc1-53f9-9448-3b29c36d9950"
---
# KEY-WP-0010 — OpenBao operator loopback callback
## Admit and validate the exact callback
```task
id: KEY-WP-0010-T01
status: done
priority: high
state_hub_task_id: "46d78907-bda4-5cc9-853d-98c0026d9fe1"
```
Added the exact operator-only callback
`http://127.0.0.1:18200/ui/vault/auth/netkingdom/oidc/callback` to the
source-owned `openbao-admin` static client in `config/dev-config.yaml`. The
registration retains exact-match redirects and does not authorize a login,
change the OpenBao role, broaden a listener, or handle a credential. A config
test pins the exact URI and proves the complete source config validates.

View file

@ -0,0 +1,68 @@
---
id: KEY-WP-0011
type: workplan
title: "Recover from live KeyCape Secret output exposure"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: netkingdom
created: "2026-08-23"
updated: "2026-08-23"
state_hub_workstream_id: "158aa957-8818-52ea-b7a5-3962cf30ba5e"
---
# KEY-WP-0011 — Live KeyCape Secret exposure recovery
Recover safely after the base64 data map of Kubernetes Secret
`sso/keycape-config` was mistakenly rendered in agent tool output during a
metadata check. Never reproduce, decode, or store the exposed payload.
## Contain and notify owners
```task
id: KEY-WP-0011-T01
status: done
priority: high
state_hub_task_id: "a8f5e33d-c725-5181-849e-a42bba34d3d8"
```
Stopped live rollout work, treated the payload as exposed, and notified
railiance-platform and NetKingdom without values. The affected classes are the
KeyCape RS256 signing private key and credentials embedded in `config.yaml`
(LLDAP bind credential, Authelia client credential, and privacyIDEA admin
token). No value was decoded, reused, or intentionally repeated and no live
mutation was attempted.
## Approve and execute coordinated rotation
```task
id: KEY-WP-0011-T02
status: done
priority: high
state_hub_task_id: "9d60569c-a0f1-53a9-b4dc-01687ab92bcc"
```
Approved by the operator on 2026-08-23, accepting deliberate invalidation of
active sessions and a brief SSO interruption. The RS256 key, LLDAP bind/admin
credential, Authelia-KeyCape client credential, and privacyIDEA application
signing secret were replaced without rendering values. The exposed
privacyIDEA admin JWT was removed from live use. Replacement material is
recoverable only through the NetKingdom age recipient in the encrypted
recovery overlay under `history/`.
## Verify recovery without values
```task
id: KEY-WP-0011-T03
status: done
priority: high
state_hub_task_id: "a079573d-97fd-5c6c-b1f9-c0af65e2933a"
```
Verified changed resource versions and public JWKS fingerprints, four Ready
deployments, successful replacement LLDAP and Authelia authentication,
wrong-secret rejection, explicit privacyIDEA fail-closed denial, issuer
discovery, and the OpenBao callback handoff to Authelia. The temporary LLDAP
force-reset flag was removed. No Kubernetes Secret payload or authentication
value was rendered during recovery.

View file

@ -0,0 +1,57 @@
---
id: KEY-WP-0012
type: workplan
title: "Repair UserInfo canonical subject resolution"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: userinfo-canonical-subject-resolution
created: "2026-08-31"
updated: "2026-09-01"
state_hub_workstream_id: "2aca83d8-4e80-52c6-8929-f1e957576b47"
---
## Repair subject lookup
```task
id: KEY-WP-0012-T01
status: done
priority: high
state_hub_task_id: "bc1ee2c7-a321-5914-96d1-9cb871f0ee1b"
```
Resolve the canonical LDAP-DN `sub` emitted by the token endpoint without
passing it to the username-only repository lookup. Preserve stable subject
semantics and verify any `preferred_username` lookup against the canonical ID.
## Regression verification
```task
id: KEY-WP-0012-T02
status: done
priority: high
state_hub_task_id: "7073cc30-a518-5c27-acaf-1a323fd981c6"
```
Cover canonical-ID, legacy username-sub, missing subject, and suspended-user
behavior. Run the KeyCape test suite and image build checks.
## Deploy and verify OpenBao OIDC
```task
id: KEY-WP-0012-T03
status: done
priority: high
state_hub_task_id: "571c5237-93fa-5c61-87af-974e02fd8929"
```
Publish and deploy the corrected KeyCape image, prove `/userinfo` accepts a
fresh human access token, then resume the governed Policy Nexus bootstrap.
Completed 2026-09-01. Forgejo Actions run 14 built commit
`153258b9d39273d53ca7ab5d1bdc09961ea9026e`; image digest
`sha256:4c7193efc3e50bd72a01812a583e823cc04f41ca96ef387b31ba9ae2533b80d0`
rolled out successfully. A fresh OpenBao OIDC flow passed `/userinfo`, and the
governed Policy Nexus bootstrap subsequently completed with deterministic
session revocation.

View file

@ -0,0 +1,573 @@
---
id: KEY-WP-0013
type: workplan
title: "Approval-engine resource audience and client registrations"
domain: infotech
repo: key-cape
status: finished
flavor: implementation
owner: codex
topic_slug: approval-engine-resource-audience
created: "2026-09-05"
updated: "2026-09-30"
state_hub_workstream_id: "6e815d88-b0e3-5ce0-be5d-13ab15917f7f"
---
Source: approval-engine inbox request 5583e896-52f2-45bd-895f-02f227b7e836,
reviewed against its local registration and caller-authentication contracts.
## Implement static resource audiences
```task
id: KEY-WP-0013-T01
status: done
priority: high
state_hub_task_id: "a92432a2-4e92-5b90-be2b-d82b784bf8f0"
```
Added optional static audience configuration for access tokens in both grants;
ID tokens retain the relying-party audience. Added human access-token scope.
Published bounded approval service fragments and the human registration contract.
Regression tests cover the default audience, request override resistance, JWKS
signature validation, ID-token separation and service registration scope isolation.
Browser requests and token exchanges now enforce the client scope allow-list,
including grants removed after authorization.
## Provision and prove live registrations
```task
id: KEY-WP-0013-T02
status: done
priority: high
state_hub_task_id: "607897c5-bad9-55e5-86df-7802f592d6e8"
```
Needs deployment-owned custody for both new service secret references and the
compatible image/configuration rollout. The upstream issuer is now verified and
pinned by NetKingdom (2026-09-09). Deploy the implementation and service registrations together,
then prove live JWKS verification and denied excess scopes without logging values.
Local signature proof is not live rollout evidence. See docs/approval-engine-auth-contract.md.
The separate human UI callback gate is retained in T05; a bearer-only resource
server does not own a redirect and its absence does not block these service clients.
2026-09-05 follow-up: read-only deployment metadata shows the current image is
forgejo.coulomb.social/coulomb/key-cape:main-153258b and only the Qonto service
secret environment reference is present. The two approval clients are not
materialized through deployment environment references. Published a concrete
non-secret admission packet at docs/approval-engine-provisioning-request.yaml.
Custody routing has no exact admitted lane for these two clients. `warden plan`
returned founder_required but matched an unrelated generic database lane; that
mismatch is not authority to provision. Human callback clarification is pending.
No secrets were read or production resources changed.
2026-09-08: the two blocking requests were sent and their receipts are readable
back via `GET /messages/?from_agent=key-cape`.
- `278a3ebe-b529-49f6-bd1a-e3ebcf318260` -> railiance-platform. Custody admission
for both clients, quoting `docs/approval-engine-provisioning-request.yaml`
verbatim: exact OpenBao paths and `client_secret` field, the two Kubernetes
delivery references, the two `KEYCAPE_*_CLIENT_SECRET` environment names,
`tenant:platform` per decision `5ed3fb35-eca9-413a-82b9-95171ba85bf6`, and the
15m lifetime. It asks for four things explicitly: confirm/correct the paths and
fields, confirm the delivery references, name the authority for the attended
first provision, and agree a rollout window in which registrations and the
KeyCape version that reads them deploy together. It restates that the
`warden plan` `founder_required` result matched an unrelated generic database
lane and is not being treated as authorization.
- `43fbd997-6e22-4859-8433-58b784c2df6c` -> approval-engine. Requests the two
missing human-registration inputs — the exact `client_id` and the full callback
URI — noting redirects are matched exactly and a near-miss fails closed at
`/authorize`. Restates the human client shape (public, S256 PKCE,
`audience: approval-engine`, `allowedScopes: [openid, approval:approve]`,
`mfaRequired: true`, no consume grant) and asks approval-engine to confirm it
validates the access token, not the ID token, against the deployed `/jwks`.
Deployment state is unchanged from the 2026-09-05 observation: neither approval
client is materialized. Task remains `wait` — the two admissions are owned by
railiance-platform and approval-engine respectively, and live provisioning proof
cannot begin until both land. Still no secret read and no production resource
changed.
2026-09-08, second pass. Custody and the human callback are still owed, but one
part of this task was ours all along and was not built: the task requires proving
"live JWKS verification and denied excess scopes without logging values", and
there was no way to do that except by hand, against production, at the moment
custody lands — which is the worst time to be improvising a check.
Shipped `keycape verify-client` (`src/internal/authclient/verify.go`, documented
in `docs/native-authentication.md`). Per registration it verifies discovery
origin, the `client_credentials` exchange and its RS256 signature against the
deployed JWKS, exact `sub`/`tenant`/`roles`/`principal_type`, that every
`-deny-scope` is refused, and that the token carries no scope that was not
requested — the last being a gap the caller commands do not cover, since they
prove requested scopes were granted and not that nothing extra came back.
Failures name the claim, never the observed value, and nothing is written to
disk, so it is safe to run against production. The exact invocation for each of
the two clients is now recorded in the `verification:` block of
`docs/approval-engine-provisioning-request.yaml`, so admission hands back a
command rather than a description.
Tests: `src/internal/authclient/verify_test.go` covers the passing case, an
over-broad registration, a live predecessor secret, an identical "rotation" and
output non-disclosure. Verified with teeth — neutering `mustFail` makes the suite
fail rather than pass silently.
Task stays `wait`. What is owed from elsewhere is unchanged and unreduced: the
two secret values through an admitted custody path, and the exact human
`client_id` and callback URI. Nothing here provisions anything.
### 2026-09-08 attended upstream issuer proof — T02 return
The user explicitly admitted the prepared temporary probe. At 21:44:44 UTC its
actual Authelia authorization-code exchange verified the signed issuer
**`https://auth.coulomb.social`**, including signature, keycape audience, validity
window and browser-bound nonce. The pinned Job exited 0. No token or user claim
was retained, and no downstream credential was issued.
All five temporary resources and the Pod were removed with scoped cleanup;
KeyCape Deployment and config Secret metadata remained unchanged. Receipt:
`docs/evidence/2026-09-08-upstream-issuer-live-proof.json`.
This closes the actual-token issuer observation gate. T02 remains wait for the
configuration owner to ensure `authelia.issuer` is pinned to the verified HTTPS
value, named CCR-2026-0017/0018 reviews, custody delivery and compatible rollout.
The diagnostic did not prove downstream KeyCape MFA/application login and did
not activate custody. Do not repeat discovery or request another observation
unless the provider/configuration or proof context changes.
2026-09-09 configuration-owner return: NetKingdom's exercised, revision-guarded
issuer-only helper inserted `authelia.issuer: https://auth.coulomb.social` into
`sso/keycape-config`; resourceVersion `51346058` -> `58713343`. Independent
readback matches. Every other configuration byte and Secret data entry, including
the private key, was preserved. Thirteen tests passed. Receipt:
`docs/evidence/2026-09-09-upstream-issuer-pin.json`.
The stored issuer configuration prerequisite is closed. No process restart,
image rollout, custody activation or downstream login proof occurred. T02 stays
wait for the named CCR reviews, attended custody and compatible rollout. The
single-instance replacement and existing-human-login checks remain mandatory.
2026-09-09 completion: both named CCR reviews are recorded. Verifier-side custody,
ESO delivery and the compatible pinned image/config are now live. Both service
clients passed the pinned verifier plus independent live signature and exact
claim/lifetime checks; wrong-secret and excess-scope requests were rejected.
The existing OpenBao human login passed before and after replacement; each
attended session self-revoked. Receipt: `docs/evidence/2026-09-09-keycape-verifier-admission.json`.
The deployment owner exercised compatible rollback and version-preserving resume.
The final helper checks containerd imageID against the pinned manifest and uses
the existing 30-second future-iat bound without extending token expiry. The
pinned native verifier runs in the contained owner process because workload
network policy excludes public issuer HTTPS. No network grant was broadened.
Live residual ownership remains KEY-WP-0013-T05 for the approval UI callback/MFA;
RPF-WP-0035-T05 for separate client-side delivery; AUDIT-WP-0009-T09 for audit
custody; APPROVAL-WP-0002 and SECRETS-WP-0009-T03 for end-to-end claim/consume,
expiry/revocation and native delivery. Natural JWT expiry and real predecessor
rotation were not exercised by initial provisioning.
## Reconcile tenant vocabularies across approval layers
```task
id: KEY-WP-0013-T03
status: done
priority: high
state_hub_task_id: "e98da2eb-1f05-5c7f-bc84-711fa38b8c4a"
```
Source: glas-harness inbox message 356f6977-d361-4e3b-83ab-b2c7f4759286
(GLAS-WP-0015), which asks for the exact store tenant, JWT tenant, CheckRequest
tenant, any permitted mapping and wrong-tenant denial evidence.
Published `docs/tenant-claim-contract.md`. The KeyCape-owned JWT tenant for all
four reviewed service registrations is `tenant:coulomb`, bound at registration
and required by config validation. Approval store `platform` and policy
`tenant:platform` are owned by approval-engine and flex-auth; KeyCape performs no
normalization or aliasing, so exact comparison does not match today. No mapping
was invented and no live registration or policy subject was changed — the two
admissible resolutions are recorded for the owning parties to decide.
Added `src/internal/server/oidc/tenant_test.go`: request-supplied `tenant` and
`tenant_hint` cannot alter the claim; two registrations never carry each other's
tenant (the wrong-tenant denial basis); human tokens default to `tenant:coulomb`
rather than an empty claim. Local issuance proof only, not live-rollout evidence.
## Align approval registrations to tenant:platform
```task
id: KEY-WP-0013-T04
status: done
priority: high
state_hub_task_id: "d93e21cc-a422-5e78-981f-9c00f3161375"
```
Source: glas-harness inbox message f487c63a-dd7e-4ad8-9188-eec9fbea59c6,
operator decision 5ed3fb35-eca9-413a-82b9-95171ba85bf6 ("Use tenant:platform for
the Glas approval dependency chain", verified resolved in the hub). This closed
resolution 2 of the two options recorded in KEY-WP-0013-T03.
Changed the `tenant` field on exactly `secrets-engine-approval` and
`approval-engine-operator` in `config/service-clients.example.yaml` and the two
matching entries in `docs/approval-engine-provisioning-request.yaml` to
`tenant:platform`, each annotated with the decision id. `codex-railiance-platform`,
`secrets-engine-openbao` and the human directory default keep `tenant:coulomb`.
No audience, scope, subject, role, lifetime or MFA change; no cross-tenant grant.
Evidence: `TestApprovalClientIssuesExactPlatformTenantAndRejectsAliases` (exact
`tenant:platform` with `aud=approval-engine` even when the caller requests
`platform`, `tenant:coulomb` or `TENANT:PLATFORM`),
`TestUnrelatedServiceClientKeepsCoulombTenant`, and
`TestServiceRegistrationTenantsAreExactPerDecision`, which pins every reviewed
client's tenant against the real fixture so a reintroduced alias fails the build.
Full `go test ./...` and `go vet ./...` pass. Choice only — live provisioning and
the other admission gates remain with KEY-WP-0013-T02.
2026-09-09 observation, read-only. Custody has been admitted since the
2026-09-08 record and the deployment now carries the pieces T02 was waiting for:
`sso/keycape-config` registers `secrets-engine-approval` and
`approval-engine-operator` alongside the existing clients; the secrets
`keycape-secrets-engine-approval-client` and
`keycape-approval-engine-operator-client` exist in the namespace; both
`KEYCAPE_SECRETS_ENGINE_APPROVAL_CLIENT_SECRET` and
`KEYCAPE_APPROVAL_ENGINE_OPERATOR_CLIENT_SECRET` are wired into the pod; and the
image is pinned by digest to
`sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611`, the
candidate T06 published.
What that does and does not establish. The live issuer passes every conformance
check that needs no credential — it advertises itself, offers the profile
authorization surface, publishes usable RS256 keys and advertises no excluded
grant (`src/tests/conformance`, run against `https://kc.coulomb.social`). It does
**not** establish the claim contract: subject, tenant, roles, scopes, lifetime
and excess-scope denial per client are exactly what `keycape verify-client`
exists to prove, and that needs the client secret in the environment.
Deliberately not done here. Reading either secret to run the verifier is
client-side retrieval, which the provisioning packet records as **not admitted**
(`custody_return.scope`: verifier-side copies only). So the remaining proof is an
attended operator action, not something to take by reading the secret. T02 stays
`wait` on that run, not on custody.
**Correction, same day.** The refusal above was right; the conclusion drawn from
it was not. The attended run had already happened, on 2026-09-09T00:10Z, and its
receipt is committed in this repository at
`docs/evidence/2026-09-09-keycape-verifier-admission.json`. Per client — both
`secrets-engine-approval` and `approval-engine-operator` —
`live_jwks_signature_verified: true`, `exact_claims_verified: true`,
`excess_scope_denied: true`, `wrong_secret_denied: true`,
`lifetime_seconds: 900`, `maximum_future_iat_seconds: 30`,
`pinned_artifact_verifier_passed: true`, `verifier_location: "attended owner
process"`. `credential_values_emitted: false` and
`client_side_read_admitted: false` throughout, so it was run the admitted way.
The claim contract is established and T02's `status: done` is correct; it does
not revert to `wait`.
The reasoning error is worth naming because it is the third of its kind here in
two days: "not admitted **for me**" was read as "not done **by anyone**", without
checking `docs/evidence/`. The preceding paragraph's read-only observations are
sound and its restraint about not reading the secret is exactly right — only the
final sentence was wrong, and it is retained above rather than deleted so the
correction has something to point at.
What the receipt itself declines to claim, which is the honest residual and
matches this task's earlier record: `real_predecessor_rotation_tested: false`,
`observed_wall_clock_expiry: false`, and under `limits`,
`actual_predecessor_rotation_observed: false` and
`wall_clock_jwt_expiry_observed: false`. Natural expiry and a real predecessor
rotation were not exercised by initial provisioning and are not claimed by
anyone. Predecessor rejection is implemented and unit-tested in
`keycape verify-client`; it has not been run against a real rotation, which is
KEY-WP-0014-T04's territory and still has no admitted execution authority.
**Independently confirmed by the custody owner, 2026-09-09** (railiance-platform
`ae6ff4bc`, `570e6746`). They reached the same reading from their own side and
add one datum this record did not carry: `human_client_consume_denied: true` for
both clients, alongside the signature, exact-claim, lifetime, excess-scope and
wrong-secret results. They confirm the verifier ran twice — once during
activation and again after rollout — at deployment generation 38, single ready
replica, signing key unchanged, no credential values emitted, and state that T02
can be closed against that receipt rather than against a run KeyCape cannot make.
They also correct one thing in our framing, and they are right: the packet says
*client-side retrieval* is unadmitted, which remains true, but the attended
operator path is not a client-side read and never required one. The two are
distinct and this task had been treating them as the same constraint.
**Do not cite this receipt as rotation evidence.** Both owners now state the same
two gaps independently: `real_predecessor_rotation_tested: false` and
`observed_wall_clock_expiry: false`. Neither is a T02 acceptance criterion, and
neither is closed by anything recorded here — see KEY-WP-0014-T04.
**Client-side retrieval is prepared but still unadmitted.** CCR-2026-0019
(secrets-engine reader) and CCR-2026-0020 (approval-engine-operator reader) are
`in_flight` under RPF-WP-0035-T06. 0020 has no named presenting actor in any
owner source, and railiance-platform left it undetermined rather than guessing —
correctly. KeyCape's view, asked for as the issuer of the identity and recorded
in `docs/approval-engine-provisioning-request.yaml`, is that the registration as
issued carries both `approval:create` and `approval:approve`, so a single
presenter can create and then approve the same entry. That is a
separation-of-duties question about who may hold the credential, not a defect in
the token: KeyCape issues the grants approval-engine requested. The decision is
approval-engine's and the doctrine question is gate-house's.
**Answered 2026-09-09, and the answer withdrew the client.** approval-engine
checked and the presenter is nobody (`056aef0d`). Nothing in their repository
obtains an OAuth token — the engine only verifies them, there is no
client-credentials grant in the package, and the one outbound credential is a
mounted audit-token, not an OAuth client. Scope by scope the bundle never
described a single actor: `approval:approve` now belongs to the informed-decision
human client, `approval:emit` is redundant because the running server emits its
own heartbeat with no token, and no requester identity was ever settled for
`approval:create`. It was a convenience bundle written when a lifecycle operator
was assumed to exist.
They asked railiance-platform to cancel the client-side reader CCR-2026-0020 as
an owner decision. Verifier custody CCR-2026-0018 stands, so the client remains
authenticable if a presenter appears, and they asked for it to be kept on that
basis rather than removed. Annotated as withdrawn in
`config/service-clients.example.yaml` and the provisioning packet; not deleted.
The separation-of-duties observation above is retained because it constrains the
re-request: when a presenter is named the registration comes back **narrowed**,
and `approval:approve` must not travel with the operational scopes.
**One thing "do not provision" cannot undo, raised rather than filed.** This
client is already live — provisioned in the 2026-09-09 attended rollout and
verified in `docs/evidence/2026-09-09-keycape-verifier-admission.json`. So the
estate now holds a confidential credential that can create *and* approve
approvals, that nobody presents, and that no consumer is waiting on. That is
standing capability with no counterparty, which is a worse resting state than
either provisioning it for a named presenter or disabling it. KeyCape owns client
disablement, so the action is ours to take and not ours to decide alone; raised
with approval-engine and railiance-platform rather than exercised, since removing
a live registration is a deployment change. Recorded here so it is not lost:
**an unpresented create+approve credential should not be left standing
indefinitely.**
## Assign and register the human approver browser client
```task
id: KEY-WP-0013-T05
status: done
priority: high
assignee: the-custodian
state_hub_task_id: "9a782909-91db-59fa-aae7-83766f4fbb0d"
```
Own the residual human registration separately from service-client T02. Reuse
an existing accepted UI if available; do not invent a callback on approval-engine.
Require public S256 PKCE, exact redirect, MFA, approval:approve without consume,
and a real human access-token proof. No service credential substitutes for this.
HFACT-WP-0001-T03 consumes the acceptance where a human approval is required.
2026-09-09. The owner exists: `informed-decision` (hub repo cf4c7da8) claims the
approver surface and owns the two strings. approval-engine was right that a
bearer-only resource server never owned them. Both wrote in asking to be told
early about problems with the registration shape rather than at handover, so
their requested shape was checked against the source rather than agreed on
paper. Three results.
**Scope gap — accepted, theirs was right.** `[openid, approval:approve]` cannot
render a decision: `GET /v1/approvals/{id}` and `/claim` both need
`approval:read`, so the surface could submit an entry it was never able to
display. Published `[openid, approval:read, approval:approve]` in
`docs/approval-engine-auth-contract.md` and the provisioning packet. Reading
through a service identity was the alternative and is worse: it weakens the
evidence-of-what-this-person-saw claim the component exists to make.
`approval:consume` stays excluded.
**Assurance shape — published, and a defect fixed.** Both asked for a documented
shape; KeyCape already emitted one, so it is written down rather than
renegotiated: `level` (aal1|aal2, ~acr), `methods` (~amr), `mfa`, `source`, `at`.
Writing it down surfaced that `at` was the token **mint** time, not the
authentication time. Those differ by hours whenever a browser session is reused,
and approval-engine persists this object verbatim as the only downstream record
that MFA happened — so a stored approval could have evidenced MFA at a moment the
person proved nothing. `PKCESession.AuthTime` now carries the original login
instant through session reuse, with mint time as the fallback for sessions
predating the field. Covered by `src/internal/server/oidc/assurance_time_test.go`.
**Blocker found before anyone built on it — a human token cannot carry
`tenant:platform`.** `effectiveTenant` resolves the human tenant from the
directory user, not the client registration, and no adapter populates
`domain.User.Tenant`, so every human token defaults to `tenant:coulomb`. The
per-client `tenant` field that carries `tenant:platform` on the two service
clients is read only on the `client_credentials` path. approval-engine compares
tenant by exact string equality and pins near-miss spellings as refused, so an
approver token issued today would be rejected — and it would present as a failed
approval, not as a registration defect.
Two resolutions, sent to both owners (`67132ddc`, `a6a070a1`, pointer
`42d435e8`) rather than chosen here, because the choice decides what a human's
tenant *means*: (1) directory-sourced, approvers carry `tenant:platform` on their
LLDAP record — keeps tenant a property of the person, needs a directory attribute
and an owner, and changes those users everywhere; (2) registration-bound and
fail-closed, symmetric with the service clients and with decision
`5ed3fb35-eca9-413a-82b9-95171ba85bf6` — defensible only because registrations
are static and deployment-owned, since dynamic registration is excluded by
design. KeyCape leans to (2) and deliberately implemented neither: (1) is a
directory-ownership question and (2) writes a cross-tenant capability into the
issuer.
**Tenant blocker resolved in code, 2026-09-09.** On review the choice did not
have to be made to unblock: the two resolutions differ in where a human's tenant
*comes from*, and an implementation exists that is correct under either. A client
registration may now declare a `tenant`, and `humanTenant()` resolves it by four
rules — no declaration keeps the directory answer unchanged; a declared zone
applies when the directory has placed the user nowhere; agreement passes; and a
declared zone that **conflicts** with a directory assignment refuses issuance
(`403`, `error_type: tenant_binding`) rather than relabelling the user.
That refusal is the whole design. A registration can bind a zone for unplaced
users and can never move a placed one, so option (2) is available now without
writing a general cross-tenant override into the issuer, and if the owners prefer
option (1) the same code stops supplying the zone and starts enforcing agreement
with it — no second migration and no window where a stale registration silently
wins. It is safe only because registrations are static and deployment-owned;
`docs/tenant-claim-contract.md` records that this rule must be revisited if
dynamic client registration is ever admitted.
Evidence: `src/internal/server/oidc/human_tenant_test.go`, four cases including
the relabel refusal. Verified with teeth — neutering the conflict check makes
`TestHumanTenantRefusesToRelabelAPlacedUser` fail rather than pass silently.
`docs/tenant-claim-contract.md` carries the resolution table,
`docs/approval-engine-auth-contract.md` states the requirement on the approver
client, and the provisioning packet now declares `tenant: tenant:platform`.
**Cleared and registered 2026-09-10.** informed-decision submitted the two
strings with the origin already live:
client_id informed-decision-approver
redirect_uri https://decisions.coulomb.social/auth/callback
The host is `decisions.coulomb.social`, not the `decide.coulomb.social` of an
earlier draft. They verified the origin rather than reporting it: `/` and
`/auth/callback` both return 200 from 92.205.62.239 on a Let's Encrypt
certificate CN=decisions.coulomb.social valid 2026-09-10 to 2026-12-09. The path
serves a placeholder while their surface is gated on APPROVAL-WP-0002-T01, which
does not affect the registration — the redirect is matched as an exact string at
`/authorize` and never fetched.
Published in `config/service-clients.example.yaml`: public `authorization_code`
with S256 PKCE, `audience: approval-engine`, scopes
`[openid, approval:read, approval:approve]`, `mfaRequired: true`, declared
`tenant: tenant:platform`. No `secretRef` — PKCE is the whole proof — and no
`serviceSubject` or `roles`, which validation rejects on a browser client.
`TestApproverRegistrationShapeIsExact` pins every field, so widening a scope or
relaxing MFA fails the build rather than reading as an edit, and asserts the
registration passes startup validation — which proves the KEY-WP-0028 tenant
exemption holds for the registration that depends on it, not only in a synthetic
case.
Two existing guards fired on the way in, both correctly, and neither was loosened:
- `TestServiceRegistrationTenantsAreExactPerDecision` refused an unreviewed client
carrying a tenant, which is exactly its purpose. The approver was added to its
reviewed set as a deliberate act.
- `TestServiceRegistrationAudienceAndScopeIsolation` panicked on
`secretRef[4:]`, an assumption that was safe while the fixture held only
confidential clients. The approver is the first public one; the loop now guards
on the `env:` prefix.
The tenant is declared, and the token says so: `tenant_source` is `registration`,
never `directory`. Per gate-house's correction that is adequate for
approval-engine's store-isolation gate and **never** for doctrine turning on this
person's membership of the zone.
Not live: this is a published registration fragment. Deployment is owner-side and
nothing has been issued to a real approver.
## Make negative rollout evidence discriminate actual issuer refusal
```task
id: KEY-WP-0013-T06
status: done
priority: high
assignee: the-custodian
state_hub_task_id: "65132e1d-10db-5915-a664-83beaf30365a"
```
Critical-path review found `mustFail` accepted any Exchange error: timeout,
HTTP 5xx, invalid token or JWKS failure could falsely prove excess-scope denial
or predecessor rejection after a successful positive exchange. Preserve a bounded
typed provider refusal, require the exact token endpoint/status/error/feature,
and prove the checker rejects unrelated failures without exposing provider bodies.
Publish the verified image candidate and return its immutable digest to T02.
Platform's 2026-09-08 return is RPF-WP-0035-T05 and proposed CCR-2026-0017/0018.
The custody field is `CLIENT_SECRET`; Kubernetes key `client-secret` and both env
names remain as requested. These records cover verifier-side delivery only.
Owner approval, client-side retrieval and the actual upstream ID-token issuer
proof remain distinct gates. Public discovery currently advertises
`https://auth.coulomb.social`; that alone is not the signed-token observation.
T06 completion: full Go suite and vet passed; published code `dcebd46` and pulled the image by digest `sha256:7ff54c54e63ee172ae9e6e7fd2da96e427352f712343d74626ee6fe0f6f82611`. Container `keycape verify-client --help` confirms the command is present. `docs/approval-clients-rollout.md` and its proposed deployment patch record configuration/issuer, first-provision, single-instance rollout, acceptance and rollback. This closes verifier preparation only; T02 and T05 retain the explicit live dependencies.
## Prepare a bounded upstream issuer observation before client rollout
```task
id: KEY-WP-0013-T07
status: done
priority: high
assignee: the-custodian
state_hub_task_id: "62bc6d8e-dbb2-57b6-a30b-472ffd06b497"
```
2026-09-08: T02's actual upstream signed-token issuer gate had no executable
observation path. A downstream OpenBao/KeyCape login cannot return the upstream
Authelia token; it remains inside the adapter. Prepare `probe-upstream-issuer`
and an exact-state temporary route so one attended flow can be verified before
changing the normal KeyCape Deployment or its configuration.
Acceptance: signed issuer/audience/time/nonce proof without token or user-claim
output; browser/state binding and one-shot refusal; finite issuer allowlist;
ten-minute bound; no downstream credential; current Traefik route isolation;
immutable image; reviewed temporary Job/config projection, network policies and
owner-reference cleanup. Normal callbacks and the production issuer stay on
the existing Deployment. The probe mounts only `config.yaml`, not `key.pem`,
and has no Kubernetes API token. That file contains existing credential data;
its use by the temporary diagnostic needs deployment-owner admission.
Source tests, image and deployment packet close this preparation task. The
attended live receipt and issuer pin remain in T02, alongside the named CCR
reviews and custody/rollout acceptance. Preparing the diagnostic does not
approve CCR-2026-0017/0018 or close any live factory gate.
T07 preparation complete: source `6f33abd`, immutable image
`sha256:0c85ed377cae7ae6ca5b5c56b1a52930e706b3cb78009747e42f551e869a22e4`.
Complete Go race suite and vet passed; five renderer/collector checks, seven
real Traefik 3.6.7 route cases and Railiance server dry-run passed. The packaged
command ran as UID 65534 with no network and a read-only filesystem and produced
the expected bounded idle-timeout receipt. See `docs/upstream-issuer-proof.md`
and `docs/evidence/upstream-issuer-probe.json`. T02 retains the live proof and
configuration-owner admission; no production resource or custody value changed.
## Status 2026-09-30 (CUST-WP-0074)
All tasks T01-T07 are `done`; nothing in this plan is waiting, so it is
`finished` rather than `blocked`. Residual items live in other plans
(RPF-WP-0035-T05/T06, AUDIT-WP-0009-T09, KEY-WP-0014-T04). The one open
question recorded above, disabling the unpresented create+approve client, is a
KeyCape deployment decision to be tracked in a new plan if taken up.
**2026-10-10:** KeyCape proposed the joint disablement sequence that
railiance-platform asked for on 2026-09-15, to railiance-platform, approval-engine
and net-kingdom: remove the registration (attended), verify `invalid_client`,
unbind the env, retire the ESO delivery, then retire custody. The proposal executes
nothing. Each owner confirms their step and the founder schedules the attended window.
**2026-10-10, amended by the owners.** net-kingdom: step 1 (`keycape-client.py
apply --operation remove`, case acceptance CCR-2026-0018) only patches
`sso/keycape-config` and does not restart. The step-3 env removal triggers the one
rollout, and the new pod starts with neither the client nor the env. Step 2's
`invalid_client` check follows that rollout. Rollback runs in reverse: env first,
then the client. railiance-platform (RPF-WP-0035-T10): step 4 also retires the
ClusterSecretStore, the Kubernetes-auth role and the reader policies, through an
attended prune sync run strictly after step 3. Step 5 is a metadata delete, not an
archive, run only after the step-2 receipt. Steps 1-5 can share one attended
window. Still open: approval-engine's reconfirmation that there is no presenter,
and the founder scheduling the window. Before that rollout, run
`keycape check-config` on the patched configuration.

View file

@ -0,0 +1,362 @@
---
id: KEY-WP-0014
type: workplan
title: "Review native login and client credential lane handoffs"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: native-credential-lane-handoff
created: "2026-09-05"
updated: "2026-09-10"
state_hub_workstream_id: "0d003df3-f7d3-5063-8ca0-e1e33f7df74a"
---
Source: ops-warden inbox message 0dd9c7bd-0ecd-42d1-806f-7cc4ba9730ed.
The native service exchange and public PKCE login commands are now implemented.
Cross-owner rotation and consumer-specific route cutover remain outstanding.
## Design owner command and custody boundaries
```task
id: KEY-WP-0014-T01
status: done
priority: medium
state_hub_task_id: "0c0a0b61-c19e-5631-9cda-8b2dc0f47d8f"
```
Review ops-warden's existing key-cape-oidc-login proxy and
rapp-qonto-keycape-client route contracts. Specify the native interactive login
and bounded exchange commands, token delivery, renewal and custody-mediated
rotation before implementation. Keep secret custody with OpenBao and avoid
retiring the proxy until replacement commands have equivalent verification.
## Verify handoff delivery evidence
```task
id: KEY-WP-0014-T02
status: done
priority: low
state_hub_task_id: "d9a5de97-b7d5-5599-98c2-eaab32f51495"
```
Ops-warden reports KEY-WP-0009-T04's claimed reply did not arrive. Verify prior
receipts for all four named recipients before claiming successful notification.
No outbound coordination messages were sent during the 2026-09-05 repo review.
## Implement and verify native caller commands
```task
id: KEY-WP-0014-T03
status: done
priority: high
state_hub_task_id: "4e46f474-a15e-594f-ae07-ad37a9667d89"
```
Implemented keycape service-token and login with HTTPS discovery, RS256/JWKS
verification, exact audience bindings, PKCE/state/nonce, private out-of-Git file
delivery and redacted errors. Service Basic authentication now form-decodes
credentials. Integration tests exercise the real token/JWKS handlers and browser
callback, including forged state, tampering, excess scope and unsafe output.
See docs/native-authentication.md for invocation and ownership boundaries.
2026-09-10. The authority question is answered and the blocker is narrowed to a
schedulable act. railiance-platform named the executor (platform operator,
attended founder session; explicitly not ops-warden, not secrets-engine
autonomously, and no unattended agent), the transport (the governed
`openbao-platform-admin-login` lane via `warden access ... --exec`), and the
authority (founder_required attended OIDC, role=platform-admin). What is missing
is a rotation CCR, which they will prepare *if KeyCape asks* — a two-custodian
CAS rotation with a service restart being a distinct version-guarded operation
rather than an implementation detail of an existing lane.
**Not asked, deliberately.** Requesting a production credential rotation with a
restart window is the operator's decision, not this repository's, so it was put
to him rather than answered here (ops-warden msg 10fcc8af).
Established before recommending anything either way: **this rotation has no
incident driver.** KEY-WP-0011 recovered a real exposure on 2026-08-23, and the
coordinated rotation that followed replaced the RS256 signing key, the LLDAP bind
credential, the Authelia client credential and the privacyIDEA admin token. The
`rapp-qonto` client secret was not in that payload — it is an `env:` secretRef
resolved from a separate Kubernetes Secret, never inline in `config.yaml`, which
config validation enforces. So this is lifecycle hygiene, not remediation, and
nobody should be carrying it as leftover incident work. Hygiene can wait for a
chosen window; remediation could not.
Also recorded: `warden route`/`warden plan` previously returned a generic match
that read as authorization, and offered read transports against a write need.
This repository declined to act on it; railiance-platform independently called it
a defect; fixed under WARDEN-WP-0038.
## Admit rotation and verify consumer handoff
```task
id: KEY-WP-0014-T04
status: done
priority: high
state_hub_task_id: "e7632c71-9676-5837-a925-7f905e3015c5"
```
Qonto rotation requires admitted custody/provider transaction and rollback
semantics; see the exact review sequence in docs/native-authentication.md.
The current login proxy yields an OpenBao token, so it cannot be replaced by a
JWT-writing command without adapting its consumer contract. Verify that split
with the platform owner and ops-warden before route changes. No route was changed.
2026-09-05 receipt audit: GET /messages/?from_agent=key-cape returned one
2026-03 image-publication message and no KEY-WP-0009 handoff receipts for
NetKingdom, secrets-engine, ops-warden or railiance-platform. Corrected the
unsupported statement in KEY-WP-0009-T04. This proves only that no receipts
are present in the current Hub response, not that no message ever existed.
The 2026-09-05 audit's finding stands as recorded; the replacement outbound
messages were sent on 2026-09-08 (below).
2026-09-08: `0a6c9d88-cf50-45d8-acdc-99a40747683e` -> ops-warden, readable back
via `GET /messages/?from_agent=key-cape`. It puts both open questions to the
route and custody owner in one place:
1. The `key-cape-oidc-login` proxy yields an OpenBao token and `keycape login`
yields an issuer JWT — different credential types, different verifiers,
different enforcement owners, so not interchangeable. Two admissible options
are offered: keep the proxy and run `keycape login` alongside it for JWT
consumers, or adapt the route's consumer contract to a JWT and then cut over.
Option (a) is recommended until every consumer of the route is enumerated.
2. Qonto rotation for `rapp-qonto-keycape-client`, quoting the five-step reviewed
sequence from `docs/native-authentication.md` and asking who executes the
successor generation and CAS update, in which transport, under what authority.
It records that `warden route`/`warden plan` returned no exact admitted lane
and that a generic match is not being treated as authorization.
The KEY-WP-0009 handoff ops-warden reported missing has also now been delivered
(`5bc58847-eed5-4164-bb0f-a0d8455e75b8`), closing the KEY-WP-0014-T02 correction.
Task remains `wait` — no route was changed and no rotation command ships until
ops-warden answers (1) and an execution/rollback authority is admitted for (2).
2026-09-08, second pass — read ops-warden's own catalog rather than waiting for a
reply, and it changes the shape of both questions.
`registry/routing/catalog.yaml`, lane `key-cape-oidc-login`: `intended_owner` was
already corrected to key-cape, and `verified: asked-and-waiting` since
2026-08-28. **ops-warden is waiting on us, not the other way round.** The lane is
also `warden_executes: false` with a `wiki_ref` into `CredentialRouting.md` — a
pointer, per their ADR-0001, not a code path with programmatic consumers. So the
consumer-contract risk behind question (1) is smaller than assumed: adding
`keycape login` alongside retires nothing, and the acceptance we owe them is a
statement of ownership, not a cutover.
Lane `rapp-qonto-keycape-client`: `rotation.owner: key-cape`,
`automatable: true`, and `blocked_on` reads "still no key-cape-native
exchange/rotation command", `verified: source-read` 2026-08-28. **That blocker is
stale.** `keycape service-token` (T03) shipped 2026-09-05 and is exactly the
native `client_secret_basic` exchange they record as absent. It does not clear
the lane, and should be narrowed rather than closed.
Shipped here: `keycape verify-client`, which is rotation step 4 and the denial
checks step 5 rests on — new exchange verified against deployed JWKS, predecessor
refused, excess scope refused, no value printed or written. See
`docs/native-authentication.md`, "Verifying a live registration". The identical
predecessor case is treated as a failure, because an unchanged secret is a
rotation that did not happen.
Steps 1-3 remain custody's and are deliberately not automated. Task stays `wait`
on exactly two answers: which option ops-warden takes for the login proxy, and
who executes the successor generation and CAS update under what authority. No
route changed.
Both catalog corrections were sent, receipts readable via
`GET /messages/?from_agent=key-cape`:
- `12f1bdfa-b240-414b-aa49-128a4d2acf20` — accepts ownership of lane
`key-cape-oidc-login`, answering the WARDEN-WP-0033-T04 follow-up they have
carried as `asked-and-waiting` since 2026-08-28. States the boundary the lane's
`fetch_command` crosses: key-cape owns browser authentication and the identity
token; the `netkingdom` auth mount, `role=<domain>` mapping, token store and
enforcement of the resulting OpenBao token are not ours. Supplies the source
paths they need to move the lane to `verified: source-read`, and records our
revised, lower estimate of the consumer risk — a pointer lane under ADR-0001
retires nothing when `keycape login` runs alongside it — while asking them to
correct us if the catalog understates its consumers.
- `08d42f47-8e19-4976-98f5-dd259e7b1225` — reports lane
`rapp-qonto-keycape-client`'s blocker as stale against its 2026-08-28
source-read, since `keycape service-token` is the native `client_secret_basic`
exchange it records as absent and `keycape verify-client` is its rotation step 3
verbatim. Asks them to **narrow** it to steps 1-2 rather than clear it, with
proposed replacement text, and notes `rotation.automatable: true` is now
half-true and should say so precisely rather than be flipped either way.
Neither message asks for a route change and neither was one.
2026-09-08/09 — ops-warden answered both, and the second answer matters more than
the question did.
**Item 1 answered: option (a).** Keep the proxy as-is and run `keycape login`
alongside it, "for your reason rather than out of caution -- they are different
credential types with different verifiers, so this is not a cutover deferred, it
is two things that were never one." They verified our pointer-layer reading
rather than accepting it, and added a fact we did not have: `warden access`
already excludes `is_login` lanes from raw-value streaming
(WARDEN-WP-0032-T05). Lane `key-cape-oidc-login` moved to `owner-confirmed` with
our boundary written in verbatim (commit c133004). It stays `interim` on purpose:
per their ADR-0003 a lane retires when a front door exists, not when ownership is
agreed, and by our own analysis `keycape login` is not that front door. The entry
now names the retirement condition instead of implying we owe one.
**Item 2 answered: nobody owns it, admittedly.** Steps 1-2 mutate custody on
platform workload paths, which is railiance-platform's, with `secrets-engine exec`
as the admitted transport. ops-warden declined to take the act on the grounds
that fronting the read lane is not authority to mutate — their ADR-0002 and
ADR-0005 — and routed it themselves rather than describing the route and leaving
it to us. Lane `rapp-qonto-keycape-client` narrowed exactly as asked and moved
`source-read` -> `owner-confirmed`, crediting the correction to us rather than to
a re-read they did not perform. Rotation step 3 now points at
`keycape verify-client` instead of restating its assertions.
**They found a defect in their own front door because of our caution, and told
us.** Running our need through `warden plan` returns `verdict: autonomous`,
`lane_id: rapp-qonto-keycape-client`, answered with three read transports — a
need containing *generate* and *CAS-write* inherits the verdict of the lane that
reads the same path, because `warden plan` has no read-versus-mutate intent at
all. `autonomous` is documented as the signal to proceed without the founder.
Raised as WARDEN-WP-0038. Their stated conclusion, which we should not soften: "A
counterparty declining to trust our output is not a control, and we are not going
to record it as one."
Standing instruction from them until WP-0038 lands, recorded here so it is not
lost in an inbox: **treat a `warden plan` verdict on any need containing a write,
rotate or provision act as unreliable.** That applies to this repository whenever
it plans a rotation.
**On `automatable`, they did not do what we suggested and said so.** We asked
them not to flip the boolean and to state the half-truth precisely; the schema has
one boolean whose only consumer is a future executable driver, and a driver told
`true` would attempt the custody steps. So it is `false`, failing safe, with the
precision moved into per-step notes. They offered to add a per-step field if we
would rather. We would not: a lane-level flag that fails safe plus per-step notes
is the right shape, and asking them to change a schema for our convenience would
be worse than the imprecision. No reply needed on that point.
T04 stays `wait`: the rotation authority now has a named owner
(railiance-platform, transport `secrets-engine exec`) but does not yet exist, and
nothing here changed a route.
2026-09-09, guarding against a misreading that is now easy to make. The approval
clients went live and `docs/evidence/2026-09-09-keycape-verifier-admission.json`
records a passing verifier run, so there is now a committed receipt in this repo
that looks like rotation evidence and is not. It records
`real_predecessor_rotation_tested: false` and `observed_wall_clock_expiry: false`,
and under `limits`, `actual_predecessor_rotation_observed: false` and
`wall_clock_jwt_expiry_observed: false`. railiance-platform stated the same two
gaps unprompted from their side (`ae6ff4bc`): "do not cite this receipt as
rotation evidence."
So `keycape verify-client`'s predecessor rejection has been exercised only
against unit tests and a first provisioning where predecessor and successor were
never distinct. Rotation step 4 is implemented and unproven, which is a different
state from either "missing" or "done", and T04 is where that distinction lives.
**2026-09-10: the authority is answered. What remains is an artifact and a
decision.** railiance-platform answered the routed question (`7c7228ac`, relayed
by ops-warden `b14655b3`):
- **Who:** the platform operator, attended, as a founder-attended session. Not
ops-warden, not secrets-engine autonomously, and no unattended agent —
railiance-platform named themselves in that exclusion too.
- **Transport:** the governed `openbao-platform-admin-login` lane, invoked only
through `warden access openbao-platform-admin-login --exec -- <command>` with a
unique metadata receipt path. The owner command is never run directly.
- **Authority:** `founder_required` attended OIDC via netkingdom
`role=platform-admin`. Nothing else in that repo carries write authority
against `platform/workloads/`.
**Missing: a rotation CCR.** They will not raise one speculatively, and their
reason is sound — a two-custodian CAS rotation with a service restart is a
distinct version-guarded operation, not an implementation detail of an existing
lane. It must name the CAS precondition and expected version on *both*
custodians, sibling-field preservation on the KV write, the restart window,
bounded predecessor retention for rollback and denial checks, and the
reconcile-to-same-version step on failure. That last is step 5 of the sequence in
`docs/native-authentication.md`, so the reviewed sequence is what the CCR encodes.
**Blocked on a decision that is not KeyCape's to take unilaterally.** Their offer:
"if key-cape wants the rotation scheduled, say so and I will prepare it under
RPF-WP-0035 with both custodians in one window." ops-warden deliberately did not
ask on our behalf — correctly, per their ADR-0003, since implying an owner request
from a routing errand is quiet ownership. So T04 now waits on the repository owner
deciding whether the Qonto rotation should happen, and when. Nothing indicates
compromise; this is hygiene plus the first real exercise of rotation step 4, which
is why it is a judgement call rather than an obvious yes.
**Precedent offered, and it is the right one.** The identical two-custodian shape
was exercised 2026-09-09 for the approval clients: attended founder session,
OpenBao KV custody plus a Kubernetes-delivered consumer copy, compatible pinned
rollout, protected recovery retained, and rollback that restored configuration and
image and detached delivery before a clean resume. Their
`scripts/keycape_approval_custody.py` (activate / resume-activate / verify) is the
shape a rapp-qonto rotation should follow rather than a bare `bao kv patch` — for
exactly the provider/consumer consistency reason this task gave when it declined
to ship a wrapper around a raw KV write. They endorsed that refusal unprompted.
**And the `warden plan` defect is fixed** (WARDEN-WP-0038, 2026-09-10). The Qonto
need now returns `founder_required` naming the write owner, and read transports
are no longer offered against a write need. ops-warden's closing line is worth
keeping: "Your caution was load-bearing and it should not have had to be."
If the rotation is scheduled, it is also the first opportunity to close the
`real_predecessor_rotation_tested: false` gap recorded above — `verify-client`
with `-previous-secret-env` is exactly step 4, and the predecessor would finally
be genuinely distinct.
**Decision, 2026-09-10: not yet.** The repository owner declined to schedule the
rotation now. Recorded with the reasoning so it is revisited on evidence rather
than re-litigated from scratch: nothing indicates compromise, the offer stands
open, and the estate already has one founder-attended window pending for the two
fail-closed startup changes. Rotating for hygiene alone, in a second attended
session, against a credential with no incident attached, is work the owner chose
not to spend now.
This is a deferral, not a refusal, and the thing that should reopen it is a
change in evidence rather than the passage of time: an actual exposure or
suspected one (see KEY-WP-0011), the Qonto secret's age becoming a stated concern,
a consumer requiring proof of rotation, or a decision to prove rotation step 4
before relying on it elsewhere. Any of those makes the answer different; a
calendar date does not.
**Closed 2026-09-10.** The paragraph above left T04 `wait` with nothing
outstanding, which is a contradiction: `wait` means waiting or blocked, and this
was neither. Re-reading the task against its own title settles it — *admit
rotation and verify consumer handoff* — and both halves are delivered:
- **Rotation admitted.** The semantics were reviewed and published
(`docs/native-authentication.md`), the executor and authority are named
(platform operator, attended founder session, governed
`openbao-platform-admin-login` lane, `founder_required` attended OIDC via
`role=platform-admin`), the transport is admitted, and the CCR will be prepared
on request. Step 4 shipped here as `keycape verify-client`, treating an
identical predecessor as a failure because an unchanged secret is a rotation
that did not happen. Steps 1–3 are custody's and are deliberately not
automated by this repository.
- **Consumer handoff verified.** ops-warden took option (a) for the reason we
gave rather than out of caution — the proxy yields an OpenBao token and
`keycape login` an issuer JWT, so they were never one thing to cut over
between. Lane `key-cape-oidc-login` is `owner-confirmed` with our boundary
written in verbatim, and `rapp-qonto-keycape-client` was narrowed to steps 1–2
rather than cleared. No route was changed.
*Executing* a rotation was never this task's deliverable, and treating the
owner's deferral as an open item would have kept a task open against work it was
never scoped to do. The deferral stands on its own above, revisited on evidence.
Two residuals are recorded rather than carried here, because they belong to
whoever next opens a window: `real_predecessor_rotation_tested: false` and
`observed_wall_clock_expiry: false`. `verify-client -previous-secret-env` is
ready to close the first the moment a genuinely distinct predecessor exists.
Also standing, from ops-warden and not to be lost: until WARDEN-WP-0038 lands,
**treat a `warden plan` verdict on any need containing a write, rotate or
provision act as unreliable** — it inherits the verdict of the lane that reads
the same path, and `autonomous` is documented as the signal to proceed without
the founder. That applies to this repository whenever it plans a rotation.

View file

@ -0,0 +1,29 @@
---
id: KEY-WP-0015
type: workplan
title: "Reconcile repository scope with implementation and intent"
domain: infotech
repo: key-cape
status: finished
owner: codex
topic_slug: scope-intent-assessment
created: "2026-09-05"
updated: "2026-09-05"
state_hub_workstream_id: "14a7312b-ec92-5640-8b26-cbaa4753c18e"
---
## Inventory implementation and document scope gaps
```task
id: KEY-WP-0015-T01
status: done
priority: medium
state_hub_task_id: "99be1489-d9e5-53b1-9c82-75dc961131f7"
```
Reviewed source revision b989de4, INTENT, SCOPE, runtime composition, authentication
commands, adapters, migration/validator code, tests, packaging and open workplans.
Updated SCOPE.md to describe implemented surfaces and their actual limits.
Recorded evidence, ten prioritized gaps and closure criteria in
history/2026-09-05-011726-scope-intent-assessment.md. INTENT and implementation are
unchanged. Findings are assessment results, not implementation completion claims.