Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 943911@bnt-lap001 Assistant-Session: fc322940-1c8e-4bed-ab39-86cec99f23a0
591 lines
29 KiB
Markdown
591 lines
29 KiB
Markdown
---
|
||
id: NK-WP-0058
|
||
type: workplan
|
||
title: "Bounded execution operability: clear boundaries, lean interfaces, transparent diagnostics"
|
||
domain: infotech
|
||
repo: net-kingdom
|
||
status: active
|
||
flavor: implementation
|
||
owner: claude
|
||
topic_slug: netkingdom
|
||
created: "2026-11-05"
|
||
updated: "2026-11-05"
|
||
related:
|
||
- NK-WP-0056
|
||
- NK-WP-0057
|
||
state_hub_workstream_id: "30b26339-60cd-510b-97cb-93eaf304a2c3"
|
||
---
|
||
|
||
# NK-WP-0058 — Bounded execution operability
|
||
|
||
Intake from the founder after NK-WP-0056 finished (2026-11-05). The pilot worked: one admission, one approval, then
|
||
an unattended rotation in 30 s. Getting there was the problem:
|
||
|
||
- about 50 founder copy-paste rounds;
|
||
- one wasted window, because the approver was configured differently in three repos;
|
||
- an opaque executor error that took four founder-run diagnostic pods to locate;
|
||
- runbook assumptions that failed live (admission policy on Secrets, Argo-managed ConfigMap, warden environment,
|
||
`kubectl wait`);
|
||
- a write role left open overnight;
|
||
- workers in two repos building competing scripts and using different names.
|
||
|
||
The review behind this workplan is the session of 2026-11-05. Its findings are in the NK-WP-0056 evidence
|
||
(`docs/evidence/2026-11-04-nk56-t06-pilot/`, `docs/evidence/2026-11-05-nk56-t06-pilot/`).
|
||
|
||
## Boundary conditions (apply to every task)
|
||
|
||
Founder direction, 2026-11-05. A task is not done unless its result meets all four:
|
||
|
||
1. **Clear responsibility boundaries between repos.** Every interface has one owner. Every artifact has one source of
|
||
truth, which is named, and no repo silently mirrors another's configuration. Where a responsibility is unclear, the
|
||
task records the question for NK-WP-0057 (decision routing) or the owning repo, instead of deciding it locally.
|
||
2. **Better interfaces between repos and with the human operator.** Cross-repo contracts are written down, versioned
|
||
and checked: names, paths, roles, payloads and exit codes. Founder steps are few, short and stateful. Each one
|
||
prints a single value-free result and is safe to repeat.
|
||
3. **Transparency first, so diagnosis is easy.**
|
||
- every failure names where it happened and why, by phase, endpoint or check, never a bare exception type;
|
||
- status is readable by the agent identity without secrets;
|
||
- every live step leaves a value-free receipt.
|
||
4. **Minimal code, carried by fully automated interface tests.**
|
||
- Prefer deleting, merging or reusing an owner's tool over adding one.
|
||
- Every cross-repo interface used by the bounded-execution path gets automated contract tests, against stubs or
|
||
the owner's real source where the service can't run in CI.
|
||
- Contract tests replace manual rehearsal wherever they can.
|
||
- Tests run in CI; a change to the code an interface depends on, in either repo, fails a test.
|
||
|
||
## Review, 2026-11-05
|
||
|
||
Reviewed against the repository state on 2026-11-05 and set to `ready`. Changes from the proposal:
|
||
|
||
- The overnight write-role check was split out of T06 into T11 and made urgent: it depends on OpenBao audit
|
||
retention.
|
||
- The inventory (T01) was split into three parts: T01, T09 and T10. They fill one table, which is also the input to
|
||
NK-WP-0057-T01, so that no second inventory is started.
|
||
- T02 now starts with an owner agreement on how a contract change reaches net-kingdom CI.
|
||
- T03 names the owners of the executor mode: net-kingdom owns the source and the image, and `rapp-bounded-executor`
|
||
packages and deploys it (NK-WP-0056, founder decision `6d1adce9`).
|
||
- T05's decision is created after T01, once the list of statuses the agent could not read is known.
|
||
- T07 no longer waits for the inventory.
|
||
|
||
Suggested order: T11 first, then T01, T09 and T10, then T03. T02 and T04 come before the next live window. T05, T06
|
||
and T07 can run in parallel with these, and T08 comes last.
|
||
|
||
## Tasks
|
||
|
||
### Confirm the overnight write-role window left no trace
|
||
|
||
```task
|
||
id: NK-WP-0058-T11
|
||
status: done
|
||
priority: high
|
||
assignee: claude-code
|
||
state_hub_task_id: "e2d02456-b072-5d42-a25b-964dc14de5ef"
|
||
```
|
||
|
||
This is the open check from 2026-11-05. It must not wait for the rest of the workplan, because the evidence it needs
|
||
ages out with OpenBao audit retention.
|
||
|
||
- Confirm that CCR-2026-0058's write role is absent now.
|
||
- Confirm from OpenBao's audit that no login used the role while it was open overnight on 2026-11-04/05.
|
||
|
||
The agent prepares read-only, value-free queries for both checks. Where the agent identity lacks audit access, the
|
||
founder runs them and reports one result line for each.
|
||
|
||
**Done when:** a dated, value-free receipt records both results under the NK-WP-0056 pilot evidence. Any login that
|
||
was found is routed as a security finding to railiance-platform.
|
||
|
||
**Result, 2026-11-05: clean.** Evidence is in `docs/evidence/2026-11-05-nk58-t11-write-role-check/`, linked from the
|
||
NK-WP-0056 pilot README.
|
||
|
||
- From 23:33:29Z (the last attended login plus the 15-minute token lifetime) to the close at 08:26:56Z, there was no
|
||
login with the write role, no request with its policy, and no failed Kubernetes login.
|
||
- All five logins in the log are the documented attended runs and diagnostics, plus the pilot run.
|
||
- The role is absent at the end of the log (21:33Z). This was derived from the audit log's create and delete
|
||
entries, because the direct `window-close` read failed for lack of a platform-admin login.
|
||
- No finding was routed.
|
||
|
||
Tool: `tools/service-binds/write-role-audit.py`, with tests in `tests/test_write_role_audit.py`.
|
||
|
||
Inputs for other tasks:
|
||
|
||
- **T06:** the audit file is not rotated (2 GiB PVC, history since 2026-08-03), and one read takes about 10 minutes
|
||
through `kubectl exec`. A recurring check should use audit forwarding (NK-WP-0055-T11), not this stream.
|
||
- **T03:** the failed close reported only `LaneError`, without its cause.
|
||
|
||
### Interface and responsibility inventory, part 1: executor and its services
|
||
|
||
```task
|
||
id: NK-WP-0058-T01
|
||
status: done
|
||
priority: high
|
||
assignee: claude-code
|
||
state_hub_task_id: "fe618717-0c28-51c5-8edb-e5d2f4ba2252"
|
||
```
|
||
|
||
Create the single contract table in `docs/`. Each row is one interface the bounded-execution path crosses and records:
|
||
|
||
- **owner and source of truth;**
|
||
- **consumer;**
|
||
- **the exact contract:** names, paths, payloads, exit codes;
|
||
- **how it is tested today** (none, stub, real source, live only);
|
||
- **the 2026-11-04/05 failures it caused.**
|
||
|
||
Part 1 covers the executor's own interfaces:
|
||
|
||
- executor ↔ approval-engine, KeyCape, audit-core, informed-decision, OpenBao and LLDAP;
|
||
- rapp (`rapp-bounded-executor`) ↔ net-kingdom: image, pins, Job template and RBAC.
|
||
|
||
Mark duplicated or unowned pieces, for example the approver configured in three places. A row whose owner is
|
||
unclear is marked "needs an owner decision" and becomes an input to NK-WP-0057-T01, which does not inventory it a
|
||
second time.
|
||
|
||
**Done when:** the table exists with sources for every part 1 row, and each row is marked keep, merge, delete or
|
||
needs an owner decision.
|
||
|
||
**Result, 2026-11-05.** The table is `docs/bounded-execution-contract-table.md`, with 11 rows (E1–E9, R1, R2) and
|
||
details per row.
|
||
|
||
- **keep:** E2, E3, E4, E6, E7 and E9.
|
||
- **merge:**
|
||
- E1: lane names and policy text are copied from the CCRs;
|
||
- E5: the approver is defined four times;
|
||
- E8: the RBAC is copied into the rapp;
|
||
- R1: no record links the commit, the digest and the admission pins;
|
||
- R2: the config and exit codes are copied into the rapp.
|
||
- **owner decisions for NK-WP-0057:**
|
||
- E5: approver and routing;
|
||
- E2: KeyCape client registration, since there is no KeyCape rapp;
|
||
- E7: the runtime owner of LLDAP, Authelia and KeyCape in `sso`;
|
||
- E3: approval-engine enforcing `required_assurance`.
|
||
- **Framing fact for T02:** net-kingdom CI tests net-kingdom alone, so every sibling-dependent test skips there.
|
||
rapp-bounded-executor has no CI, and the T15 and T19 exercises are manual.
|
||
- **Inputs for other tasks:**
|
||
- T03: `executor-lanes.py` lane errors without a cause, and `approval_entry` without the failed field;
|
||
- T07: a hard-coded path in `test_approval_request.py:216`.
|
||
|
||
### Interface and responsibility inventory, part 2: approval, case and custody chain
|
||
|
||
```task
|
||
id: NK-WP-0058-T09
|
||
status: done
|
||
priority: high
|
||
assignee: claude-code
|
||
depends_on: [NK-WP-0058-T01]
|
||
state_hub_task_id: "b10d162b-b66c-5274-ac81-adeae1574389"
|
||
```
|
||
|
||
Add rows to the T01 table, in the same format, for:
|
||
|
||
- railiance-platform CCRs and broker ↔ net-kingdom cases;
|
||
- audit-core seed ↔ railiance-platform custody;
|
||
- informed-decision routing ↔ access-engine policy.
|
||
|
||
Mark the duplicated pieces, for example the two seeders and the two Secret names.
|
||
|
||
**Done when:** every part 2 row has sources and a keep, merge, delete or owner-decision mark.
|
||
|
||
**Result, 2026-11-05.** Part 2 of `docs/bounded-execution-contract-table.md` has seven rows (C1–C7).
|
||
|
||
- **The two seeders and the two Secret names** were already settled on 2026-11-04: audit-core's script is the
|
||
single seeder, and the Secret is `sso/bounded-executor-tokens:audit-core`. One stale copy remains in
|
||
railiance-platform `docs/credential-lane-designs/bounded-executor.md`, which is a **delete** request.
|
||
- **keep:**
|
||
- C2: acknowledged `type.yaml` bytes are hash-pinned by the broker, and net-kingdom has no guard test;
|
||
- C3.
|
||
- **merge:**
|
||
- C1: `executor-lanes.py` imports three railiance-platform internal helpers;
|
||
- C4: the KeyCape secret naming convention is written twice;
|
||
- C5: the audit custody names appear in four repos;
|
||
- C6: the executor's submitter profile is in informed-decision, access-engine and the rapp.
|
||
- **owner decisions:**
|
||
- C1: a supported platform interface;
|
||
- C3, small: who materializes (the canon names ops-mason; railiance-platform did it). An earlier draft called
|
||
the status order wrong; the canon confirms it;
|
||
- C6: which repo is the source of the profile;
|
||
- C7: routing and visibility. access-engine's `list` admits only the routing recipient, so the routing table
|
||
decides who can approve. This goes to NK-WP-0057 with E5.
|
||
- **For T07:** audit-core's seeder hard-codes a home path and the cluster UID.
|
||
|
||
### Interface and responsibility inventory, part 3: operations and the founder
|
||
|
||
```task
|
||
id: NK-WP-0058-T10
|
||
status: done
|
||
priority: high
|
||
assignee: claude-code
|
||
depends_on: [NK-WP-0058-T01]
|
||
state_hub_task_id: "e2515bd5-f4bd-5480-ad86-21e5044b468d"
|
||
```
|
||
|
||
Add rows to the T01 table, in the same format, for:
|
||
|
||
- Argo ↔ manual applies, including which controller manages each object the runbooks write;
|
||
- warden lanes ↔ child scripts, including the environment they pass on;
|
||
- founder ↔ each runbook step, with the authority each step needs and the value-free result it prints.
|
||
|
||
**Done when:** every part 3 row has sources and a mark, and the table lists every status the agent had to ask the
|
||
founder for on 2026-11-04/05 (the input for T05).
|
||
|
||
**Result, 2026-11-05.** Part 3 of `docs/bounded-execution-contract-table.md` has six rows (O1–O4, F1, F2), a
|
||
controller-per-object table, a founder-step table and the status table for T05.
|
||
|
||
- **Argo (O1):** only informed-decision `deploy/lifecycle`, the Secret admission guard and the shared
|
||
SecretStores are Argo-managed, all with pinned revisions and manual sync. Everything else is applied by hand.
|
||
Three surprises came from this split. Which objects Argo owns is an owner decision for railiance-platform.
|
||
- **Sessions (O2, O3):** warden lanes for platform and audit-core steps, but a shell `bao login` for net-kingdom's
|
||
window commands. Merge on warden lanes. Owner request to ops-warden: strip every token variable.
|
||
- **Founder (F1, F2):** about 45 commands across three runbooks, with five kinds of values carried by hand. The
|
||
first approval came from the identity that happened to be signed in. These are inputs for T04.
|
||
- **For T05:** the agent can read only NetworkPolicies today. The proposed grant and the founder-only rows are in
|
||
the status table.
|
||
- **For T07:** four tools hard-code kubeconfigs.
|
||
- **Correction:** C3 in part 2 was corrected the same day. The canon defines materialization as structure only, so
|
||
committing `materialized` before the mint is right.
|
||
|
||
### Contract tests for the cross-repo interfaces
|
||
|
||
```task
|
||
id: NK-WP-0058-T02
|
||
status: done
|
||
priority: high
|
||
assignee: claude-code
|
||
depends_on: [NK-WP-0058-T01, NK-WP-0058-T09, NK-WP-0058-T10]
|
||
state_hub_task_id: "a33eeee6-caef-57d4-b067-26231a19a165"
|
||
```
|
||
|
||
First, agree with each owner how a change to their contract reaches net-kingdom CI. Two ways are possible:
|
||
|
||
- the owner publishes a versioned contract file that net-kingdom pins;
|
||
- net-kingdom CI tests against a pinned checkout of the owner's source and is told about new versions.
|
||
|
||
Record the agreement in the owner's workplan. Where no agreement is reached, the test runs against the pinned
|
||
version, and the table records that drift is caught only when the pin moves.
|
||
|
||
Then, for every interface marked keep or merge, add an automated contract test in CI:
|
||
|
||
- against the owner's real source where it runs locally (approval-engine, audit-core, informed-decision intake,
|
||
the KeyCape image);
|
||
- otherwise against a stub that is generated from, or checked against, the owner's contract.
|
||
|
||
Fold the existing exercises (T19 approval identity, T15 audit sender) into this suite instead of keeping them as
|
||
separate scripts. A change in either repo that breaks a contract fails CI in the consumer, such as a Secret name, a
|
||
role name, a response code or a memo field.
|
||
|
||
**Done when:** the suite runs in CI for every interface in the table, and each interface that failed on 2026-11-04/05
|
||
has a test that would have caught it. Code in net-kingdom that duplicated a contract by hand is removed. For
|
||
duplicates in other repos, a removal request is sent to their owners and recorded.
|
||
|
||
**Result, 2026-11-06.**
|
||
|
||
1. **Owner source in CI.**
|
||
- `contracts/owner-pins.json` pins six public owners.
|
||
- `tools/contract-tests/fetch_owners.py` makes anonymous partial clones of them: pinned on push, `main`
|
||
nightly.
|
||
- Workflow `bounded-execution-contracts.yaml` runs `tools/contract-tests/run.sh`, the one suite list (also
|
||
`make contract-test`). Any skip fails it.
|
||
- pytest and the crypto chain are pinned by hashes checked against PyPI.
|
||
- The private rapp tests itself in its own CI (rapp `15df0e3`), using the run's automatic token.
|
||
2. **Exercises folded in.** T15 runs against audit-core's ingestion. T19 runs against approval-engine's source,
|
||
with the KeyCape binary extracted from the pinned image by verified digests (`oci_extract.py`, no docker).
|
||
3. **Per-row tests.**
|
||
- ingress for E9, with a small NetworkPolicy evaluator and synthetic cases;
|
||
- custody names for C4 and C5;
|
||
- the LLDAP tool and server digest for E7;
|
||
- readiness against real owner source, replaying both 2026-11-04 states from informed-decision history (E5,
|
||
C6, C7).
|
||
|
||
The coverage by row is in the contract table. 95 tests pass in a CI-like layout.
|
||
|
||
- **The 2026-11-04/05 failures and their tests:**
|
||
- the approver routing: the readiness replay;
|
||
- `setup:URLError`: the ingress test, plus the preflight Job before a window;
|
||
- the window left open overnight: `bx` closes in `finally`, tested;
|
||
- the opaque `LaneError`: named causes, tested.
|
||
|
||
The ESO apply and admission-guard and the kubectl-wait failures are T07 conventions. The warden token variable is
|
||
the O2 owner request.
|
||
- **Hand duplicates:** `pilot-run.sh` and the hard-coded informed-decision test path are removed. `LANES` keeps
|
||
copies of the CCR values, but `contract()` checks them against the CCRs on every run and in CI. The stale Secret
|
||
name in railiance-platform was sent as a request (`f6198285`).
|
||
- **Owner agreement:** questions were sent to six owners (`4892658f`, `3a546559`, `aa44a2b3`, `7cf8d61f`,
|
||
`a2105e34`, `ee1e5bb5`). Until they answer, the pinned-plus-nightly mode stands.
|
||
- **Not covered:** OpenBao's ingress (rapp-openbao is private), and live drift from source, which the preflight
|
||
Job and T07 address.
|
||
- **Unverified here:** the first Forgejo runs of both workflows. The Actions API needs a login.
|
||
|
||
### Readiness check before any window opens
|
||
|
||
```task
|
||
id: NK-WP-0058-T03
|
||
status: done
|
||
priority: high
|
||
assignee: claude-code
|
||
depends_on: [NK-WP-0058-T01, NK-WP-0058-T09]
|
||
state_hub_task_id: "0e403b18-cf7b-5899-bd86-661fc7284014"
|
||
```
|
||
|
||
Owners: net-kingdom owns the executor source, its tests and its image, so the `--preflight-only` mode is written
|
||
here. `rapp-bounded-executor` owns the Job packaging, so running the mode as a Job is a change to the rapp. The
|
||
readiness checks call the owners' validators and do not copy them.
|
||
|
||
One command that verifies the chain while nothing is at stake, so a window opens only when everything is green:
|
||
|
||
- the admission is valid and its pins match the pinned image;
|
||
- the admission's approver equals informed-decision's recipient and the approval principal;
|
||
- the CCR approvals and bindings are complete;
|
||
- the case status, custody delivery and endpoint reachability;
|
||
- the write role is absent.
|
||
|
||
Add an executor `--preflight-only` mode that runs setup and preflight without consuming. It replaces hand-written
|
||
diagnostic pods.
|
||
|
||
The check reuses the owners' validators. Where a check needs authority the agent lacks, it names the single
|
||
read-only founder command that supplies it.
|
||
|
||
**Done when:** the readiness check is tested on stubs, runs with the agent identity wherever possible, and
|
||
`--preflight-only` reports every failure it finds by phase and endpoint.
|
||
|
||
**Result, 2026-11-05.** Three steps.
|
||
|
||
1. **Named causes.**
|
||
- `executor-lanes.py`'s identity phase distinguishes `openbao_unreachable`, `openbao_sealed` and
|
||
`platform_admin_login_required`, with a hint. `LaneError` keeps its fixed code.
|
||
- `approval_entry:<approver_not_listed|not_human|not_aal2|self_approval>`.
|
||
2. **`cli.py --preflight-only`.**
|
||
- It checks reachability per endpoint, then sets up without the write role, Kubernetes or directory clients.
|
||
- It runs all seven preflight checks by phase, with no consume, emit or write. Exit 0 ready, 4 not.
|
||
- The real preflight uses the same named checks.
|
||
3. **The readiness command.**
|
||
- `tools/bounded-execution-readiness/ready.py` (`make bounded-execution-ready`) runs with the agent identity
|
||
from the owners' source. It checks:
|
||
- the admission;
|
||
- the approver chain at both the Argo-pinned revision and main of informed-decision;
|
||
- the access-engine profile against the registry;
|
||
- the CCRs, against the owner's `RUNBOOK_ALLOWED_STATUSES`;
|
||
- the case, and the type bytes against the broker's pin.
|
||
- It names three read-only founder commands for the rest:
|
||
- `executor-lanes.py status` (new: write role, fence, `cas_required`);
|
||
- rapp `make preflight` (new: the Job runs `--preflight-only` in the pinned image, with only the openbao
|
||
token);
|
||
- the ExternalSecret `Ready` reads.
|
||
- A replay test shows it catches 2026-11-04: routing to `platform-root` on main while the Argo pin lagged.
|
||
- Against the real sources it passes for BEA-2026-0003.
|
||
|
||
Tests: 125 in `make bounded-executor-test`, 31 in rapp `make test`. R1 needs no pin record: the preflight's `source`
|
||
check compares the admission's pins with the files in the pinned image.
|
||
|
||
**Before first live use,** the executor changed, so a new image build and a rapp re-pin are needed. That is a
|
||
founder step for the next admission. The readiness check is not yet in CI; that is part of T02.
|
||
|
||
### Founder interface: few, short, stateful steps
|
||
|
||
```task
|
||
id: NK-WP-0058-T04
|
||
status: done
|
||
priority: high
|
||
assignee: claude-code
|
||
depends_on: [NK-WP-0058-T03]
|
||
state_hub_task_id: "9b70a844-edb2-50b3-a06c-be57d3823b3c"
|
||
```
|
||
|
||
Replace the copy-paste runbooks for a run with a small command set: `ready`, `request`, `run` (which opens the window
|
||
and always closes it), `status` and `close`. Each command:
|
||
|
||
- keeps its inputs in a state file, not in shell variables;
|
||
- numbers receipts itself;
|
||
- is idempotent;
|
||
- has no line over 120 characters in its instructions;
|
||
- prints one value-free result line.
|
||
|
||
Build on `pilot-run.sh`, and delete the runbook prose these commands replace. Record the identity hygiene a step
|
||
needs: a browser profile per identity, and when a fresh OTP login is due.
|
||
|
||
**Done when:** a full pilot run is at most five short founder commands plus the approval in informed-decision. The
|
||
commands are tested on stubs, and T06's runbook shrinks accordingly.
|
||
|
||
**Result, 2026-11-05.** `tools/bounded-execution-run/bx.py`: `ready`, `request`, (the approval), `run` and `status`
|
||
make four commands plus the approval. `close` is for emergencies.
|
||
|
||
- **State file:** inputs live in `~/.local/state/net-kingdom/bounded-execution/<id>.json`. That covers the
|
||
admission, the approval id, the attested trust states and the evidence folder. The run config is generated from
|
||
the rapp's config, never edited by hand.
|
||
- **Receipts:** numbered by the tool in `docs/evidence/<date>-<id>/`.
|
||
- **Safe to repeat:** every command. `run` never starts a second run for an admission; a run that never started
|
||
may be retried.
|
||
- **One line per command,** value-free.
|
||
- **Two logins in a run:** `ready` and `run` each open one `warden access openbao-platform-admin-login` login.
|
||
The child is silent (`executor-lanes.py --quiet`) and writes the receipts. `run` does preflight, window open,
|
||
run, poll and result, and closes the window in `finally`, also on an error inside the child. This resolves O3.
|
||
- **Tests:** 8 tests on stubs cover the full flow, repeats, a not-ready preflight never opening the window, the
|
||
window always closing, a silent child on error, and instruction lines of at most 120 characters. 134 in
|
||
`make bounded-executor-test`.
|
||
- **Runbook:** sections 5 and 6 of the T06 runbook are replaced by the four commands, a result table and identity
|
||
hygiene: a browser profile per identity, a check of the signed-in subject, and a fresh OTP when a memo doesn't
|
||
list. `pilot-run.sh` is deleted.
|
||
- **Not yet live-proven:** first live use needs the new image, a rapp re-pin and a new admission. Whether warden
|
||
passes `BAO_ADDR` and the `BX_*` variables through to the child is confirmed only from its playbook ("the caller
|
||
passes BAO_ADDR"), so the first `bx ready` is the live check.
|
||
|
||
### Agent status visibility without secrets
|
||
|
||
```task
|
||
id: NK-WP-0058-T05
|
||
status: wait
|
||
priority: medium
|
||
assignee: claude-code
|
||
needs_human: true
|
||
blocked_on: human
|
||
blocking_reason: "Add-on in railiance-platform (4e60f69, 2e1ca67); waits for the founder's Argo sync, agent kubeconfig and first token."
|
||
decision_id: "4af1ec01-e8fb-4352-a09b-ae9cda53d984"
|
||
intervention_note: "Founder runs the steps in docs/proposals/2026-11-06-nk58-t05-agent-status-grant/README.md; then the agent verifies with can-i."
|
||
depends_on: [NK-WP-0058-T10]
|
||
state_hub_task_id: "30f8c62e-5707-5395-9d86-178cd650fb59"
|
||
```
|
||
|
||
The State Hub decision for this gate is created once T10 has listed the statuses the agent could not read. Its id
|
||
then goes into this task block (C-39).
|
||
|
||
**2026-11-06: decision open** (`4af1ec01`). The proposal is in
|
||
`docs/proposals/2026-11-06-nk58-t05-agent-status-grant/`: `rbac.yaml`, not applied, and the options.
|
||
|
||
- **Recommended option A:** a dedicated ServiceAccount `sso/nk-agent-status`, following SI-14, rather than a wider
|
||
`ops-mason-builder`. It reads Jobs, pods, Deployments and ExternalSecrets in `sso`, one named ConfigMap in
|
||
`informed-decision` and one Argo Application. No Secrets, pod logs, exec or writes, and 1 h tokens minted per
|
||
session.
|
||
- **Pod logs stay founder-only,** because RBAC cannot limit them to the executor's pods (option B).
|
||
- `tools/contract-tests/tests/test_agent_status_grant.py` keeps the grant read-only and narrow, and keeps `sso`
|
||
workloads free of literal secret values, since pod specs become readable.
|
||
|
||
After the decision: railiance-cluster reviews the change, the founder applies it, and the agent verifies with
|
||
`can-i`.
|
||
|
||
**2026-11-06: decided, option A with 1 h tokens** (founder; `4af1ec01` resolved).
|
||
|
||
- **Owner corrected to railiance-platform.** Agent-facing cluster grants live there as platform add-ons
|
||
(`argocd/platform-addons/ops-mason-builder/`, a manually synced, pinned Application). railiance-cluster has no
|
||
RBAC manifests. railiance-platform is asked to adopt the grant as `argocd/platform-addons/nk-agent-status/`.
|
||
- **Founder steps** (proposal README):
|
||
- sync the Application once;
|
||
- build `~/.kube/config-agent-status` once. It holds the CA and a token-file pointer, never a token.
|
||
- `make agent-status-token` each working session.
|
||
- **Then the agent verifies:** `auth can-i --list` shows only the grant, and Secrets and pod logs in `sso` answer
|
||
`no`.
|
||
|
||
**2026-11-06: add-on implemented** on the founder's instruction, in railiance-platform:
|
||
- `argocd/platform-addons/nk-agent-status/` (`4e60f69`) with `tests/test_nk_agent_status_rbac.py`;
|
||
- the manual, pinned Application (`2e1ca67`).
|
||
|
||
net-kingdom's copy of the grant is deleted: its contract test reads the platform's files, and the owner pins move to
|
||
`2e1ca67`. The founder steps are in the proposal README:
|
||
- a selective root sync and the child sync, with prune and force off;
|
||
- the agent kubeconfig, once;
|
||
- `make agent-status-token`.
|
||
|
||
Propose a read-only grant for the agent's Kubernetes identity in the namespaces the path uses. The grant covers:
|
||
|
||
- the status and conditions of Jobs, pods, ExternalSecrets and Deployments;
|
||
- pod logs of executor Jobs, which are value-free by contract.
|
||
|
||
It never covers Secrets, kubeconfigs or exec. Route the grant through its owner as a reviewed change. Record what
|
||
stays founder-only and why.
|
||
|
||
**Done when:** the grant is decided, and if approved applied and verified with `can-i`. Every status the agent had
|
||
to ask the founder for on 2026-11-04/05 is then readable, or explicitly founder-only.
|
||
|
||
### Write-role window closed by the platform
|
||
|
||
```task
|
||
id: NK-WP-0058-T06
|
||
status: todo
|
||
priority: medium
|
||
assignee: claude-code
|
||
depends_on: [NK-WP-0058-T09]
|
||
state_hub_task_id: "6a4c7413-0675-5a06-b471-5f849ce0c721"
|
||
```
|
||
|
||
Make the end of CCR-2026-0058's window not depend on a person remembering. Two candidates, to be chosen with
|
||
railiance-platform (the owner):
|
||
|
||
- a scheduled close at `not_after`;
|
||
- an alert or finding whenever the write role exists outside an open admission window. The latter could reuse
|
||
audit-core's stream findings or posture feedback.
|
||
|
||
The open check from 2026-11-05 on the overnight window is T11.
|
||
|
||
**Done when:** an unclosed window is closed or flagged without human memory, and this is tested on a stub.
|
||
|
||
### Runbook and tooling conventions
|
||
|
||
```task
|
||
id: NK-WP-0058-T07
|
||
status: done
|
||
priority: medium
|
||
assignee: claude-code
|
||
state_hub_task_id: "a99424ed-ee8b-5db4-9fa7-2ae5fc32c39c"
|
||
```
|
||
|
||
This task does not wait for the inventory, because the conventions below are already known. Findings from T10 are
|
||
added when they arrive.
|
||
|
||
Turn the 2026-11-04/05 surprises into checked conventions:
|
||
|
||
- server-side apply only;
|
||
- check the managing controller before writing apply commands;
|
||
- poll status instead of `kubectl wait` with several conditions;
|
||
- no hard-coded kubeconfigs in owner tools;
|
||
- warden strips every token variable (ops-warden owner request);
|
||
- receipts are named by the tool, not by the operator.
|
||
|
||
Prefer a lint or a test over prose, and remove the hard-coded and duplicated bits it finds.
|
||
|
||
**Done when:** the conventions are enforced by a test or lint in CI for the net-kingdom and rapp runbooks and tools,
|
||
and the owner requests for other repos are sent and recorded.
|
||
|
||
**Result, 2026-11-06.** `tools/conventions/lint.py` (`make conventions-lint`) has five rules:
|
||
|
||
- `server-side-apply`;
|
||
- `one-wait-condition`;
|
||
- `one-cluster-route`;
|
||
- `tool-numbered-receipts`;
|
||
- `managing-controller`, which reads the paths Argo owns from railiance-platform's `argocd/`.
|
||
|
||
Records (evidence, history, proposals, workplans, wiki) are not checked. Seven exceptions are listed in the tool,
|
||
each with its reason: two quoted or legacy texts, three node-local commands, three completed one-time runbooks, and
|
||
the inventory itself. The lint runs in net-kingdom's contract suite (with 14 tests) and in the rapp's packaging CI
|
||
(rapp `78ed50c`).
|
||
|
||
**Fixed here:**
|
||
- 21 runbook applies are now server-side.
|
||
- `portal-client-rollout.py` (and so `keycape-client.py` and the KeyCape rollout tools) and
|
||
`identity-provisioner-reconcile.py` use `NK_KUBECONFIG`, default `~/.kube/config-railiance01`. They no longer
|
||
use a second kubeconfig with a `--server` override to the same API.
|
||
- The T06 runbook's section 4 note and the bind-split runbook's `K=` line follow the same route.
|
||
- `bx` already numbers receipts and polls instead of using `kubectl wait`.
|
||
|
||
**Owner requests:**
|
||
- the lint findings in six repos: railiance-platform 57 (`c8371e0d`), audit-core 14 (`9e57fd89`),
|
||
informed-decision 4, including one `managing-controller` in its own release runbook (`7da9abe8`), approval-engine
|
||
3 (`41b19d89`), access-engine 2 (`abe7e149`) and key-cape 2 (`573f56a1`);
|
||
- warden stripping every token variable: ops-warden (`4acb653c`, from T10 O2);
|
||
- audit-core's seeder hard-coded path: the lint reports it under `one-cluster-route` in `9e57fd89`.
|
||
|
||
**Observed, not caused here:** `sso-mfa/k8s/keycape` and `sso-mfa/k8s/lldap` have 7 failing tests and 11 that can't
|
||
be collected (`warden` not importable), identical before and after this change. No CI runs them. That's a candidate
|
||
for an ad hoc task.
|
||
|
||
### Multi-repo worker protocol
|
||
|
||
```task
|
||
id: NK-WP-0058-T08
|
||
status: todo
|
||
priority: low
|
||
assignee: claude-code
|
||
depends_on: [NK-WP-0058-T02]
|
||
state_hub_task_id: "dd2b72c8-7225-5b89-8936-d44c99f95872"
|
||
```
|
||
|
||
Before worker agents start on a cross-repo change, they get one contract excerpt: the names, paths and roles they
|
||
touch, and the single owning script per step. Each worker's result is then checked against that excerpt with the
|
||
T02 tests. This prevents the competing seeders and mismatched names seen on 2026-11-04.
|
||
|
||
**Done when:** the protocol is in the agent instructions, and its first use is checked by the contract tests.
|