NK-WP-0058-T10 done: contract table part 3 (Argo vs manual applies, warden lanes, founder steps, agent-readable status); C3 corrected
All checks were successful
CI Smoke / host-smoke (push) Successful in 1s
CI Smoke / container-smoke (push) Successful in 3s

Only informed-decision lifecycle, the Secret admission guard and shared SecretStores are Argo-managed;
two platform-admin session patterns; ~45 founder commands with five hand-carried values; the agent
reads only NetworkPolicies. C3: the canon defines materialization as structure, so the order is right.

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
This commit is contained in:
tegwick 2026-11-05 23:10:56 +01:00
parent add152fcdb
commit e1561c09ee
3 changed files with 153 additions and 15 deletions

View file

@ -379,7 +379,7 @@
| task | NK-WP-0058-T07 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T08 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T09 | done | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T10 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T10 | done | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T11 | done | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| intake | NK-IN-0001 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |
| intake | NK-IN-0002 | closed | blue | docs/intakes/activity-core-ops-sso-operators.md |

View file

@ -1,7 +1,6 @@
# Bounded execution: cross-repo contract table
NK-WP-0058-T01 (part 1) and T09 (part 2), as of 2026-11-05. Part 3 (T10: operations and the founder) adds rows to
this file. NK-WP-0057-T01 takes the rows marked **owner decision** as inputs and does
NK-WP-0058-T01 (part 1), T09 (part 2) and T10 (part 3), as of 2026-11-05. NK-WP-0057-T01 takes the rows marked **owner decision** as inputs and does
not inventory them again.
Each row is one interface on the bounded-execution path. Every claim cites the file that defines it, at these
@ -235,7 +234,7 @@ NK-WP-0058-T09, as of 2026-11-05. It uses the same revisions as part 1, plus acc
| --- | --- | --- | --- | --- | --- | --- |
| C1 | CCR lifecycle ↔ net-kingdom lane tooling | railiance-platform: `credential-change-requests/CCR-2026-0058`, `-0059`, `scripts/credential-change.py` | `tools/service-binds/executor-lanes.py`, which imports three platform helper modules; `pilot-run.sh` | Local: `tests/test_executor_lanes.py` copies `railiance-platform/scripts` | `window-close` failed as bare `LaneError` (identity phase) | **merge**; **owner decision** on a supported interface |
| C2 | Case-type bytes ↔ broker admission | net-kingdom `security-case-types/*/type.yaml` (canon). Pin: railiance-platform `scripts/keycape_client_secret_generate.py ACKNOWLEDGED_TYPES` (sha256) | railiance-platform credential broker | none in net-kingdom. The README errata warns against edits | none (both hashes match today) | **keep**; add a net-kingdom guard test |
| C3 | Case status ↔ broker gate | net-kingdom `security-cases/*.yaml` (`status`, `acceptance`); canon `canon/standards/security-case-types_v0.1.md` | broker (`case_not_materialized`), founder runbooks | Local: the broker case check on a synthetic copy | net-kingdom commits `materialized` **before** the mint, because the broker refuses anything else | **owner decision** |
| C3 | Case status ↔ broker gate | net-kingdom `security-cases/*.yaml` (`status`, `acceptance`); canon `canon/standards/security-case-types_v0.1.md` | broker (`case_not_materialized`), founder runbooks | Local: the broker case check on a synthetic copy | none. The status is committed by hand from a founder message | **keep**; small **owner decision** on who materializes |
| C4 | KeyCape client secret delivery | railiance-platform: CCR-2026-0060 and -0061, `manifests/nk-bind-executor-eso.yaml`, template `keycape-issuer-eso.yaml` | net-kingdom `sso-mfa/k8s/keycape/deployment.yaml` env `KEYCAPE_NK_BIND_EXECUTOR_APPROVAL_CLIENT_SECRET`; `keycape-client.py env_secret_name()`; executor E1 lane | railiance-platform `tests/test_nk_bind_executor_custody.py`, `test_keycape_client_secret_generate.py`; net-kingdom `sso-mfa/k8s/keycape/test_keycape_client.py`; no cross test | none (custody verify passed 2026-11-04) | **merge** |
| C5 | audit-core sender bearer custody | railiance-platform: CCR-2026-0063 (path `platform/workloads/net-kingdom/bounded-executor/audit-sender`, field `AUDIT_TOKEN`), ESO to `sso/bounded-executor-tokens` key `audit-core`. Seed: audit-core `scripts/seed-nk-bounded-executor-sender.py` (single seeder) | rapp `templates/job.yaml` mount; executor `audit_core.token_file`; railiance-platform `nk_bind_executor_custody.py verify` | audit-core `tests/test_seed_nk_bounded_executor_sender.py`; railiance-platform custody tests; no cross test | While being prepared: competing seeders in two repos and two Secret names. Settled in RPF-WP-0069 and AUDIT-WP-0018-T04; one stale copy remains | **merge**; **delete** the stale design text |
| C6 | Executor's submitter profile | **Two owners, one fact.** informed-decision `deploy/lifecycle/intake-submitters.json` and access-engine `examples/informed-decision-present-existing-review/policy.md present_existing_profiles` (v14) | informed-decision intake; access-engine decisions; executor `executor_identity` (rapp config) | none across the three | none | **merge**; **owner decision** on which is the source |
@ -269,12 +268,14 @@ NK-WP-0058-T09, as of 2026-11-05. It uses the same revisions as part 1, plus acc
- **Contract:** the broker refuses unless the case is `status: materialized` with
`acceptance: {at, record, by: [{role: owner}, {role: founder}]}`.
- **Gap:** the go-live runbook (`docs/credential-lane-designs/nk-bind-executor-go-live.md`, gate G1) has
net-kingdom commit `materialized` before the mint that materializes the case. The status claims a fact before it
is true.
- **Owner decision:** net-kingdom owns the case canon. Either the broker gates on `accepted` and the status moves to
`materialized` after verify, or the canon defines `materialized` as "admitted for materialization". This needs
railiance-platform's agreement, because the broker check changes.
- **Consistent with the canon:** materialization is the OpenBao structure only, never values
(`security-case-types_v0.1.md`, line 61). Committing `materialized` after go-live steps 0–3 and before the mint is
therefore correct; the mint and verification lead to `live`. An earlier draft of this row said otherwise.
- **Gap:**
- the status is committed by hand by claude-code after the founder reports steps 0–3. No receipt links them;
the T04 state file could record that link.
- the canon names ops-mason as the materializer, but railiance-platform's go-live steps did it here. Small
**owner decision:** amend the canon, or route materialization through ops-mason.
### C4: KeyCape client secret delivery
@ -333,7 +334,9 @@ access-engine's policy is the source, with the other checked against it. Until t
## Summary for part 2
- **keep:** C2, with a net-kingdom guard test for the acknowledged `type.yaml` bytes.
- **keep:**
- C2, with a net-kingdom guard test for the acknowledged `type.yaml` bytes;
- C3.
- **merge:**
- C1: lane tooling on platform internals;
- C4: the KeyCape secret naming convention and the KV path;
@ -343,7 +346,123 @@ access-engine's policy is the source, with the other checked against it. Until t
(an owner request).
- **owner decision:**
- C1: a supported platform interface for window roles (railiance-platform);
- C3: what `materialized` means and when the broker admits (net-kingdom canon with railiance-platform);
- C3, small: who materializes. The canon names ops-mason; railiance-platform did it here;
- C6: the source of the submitter profile (informed-decision and access-engine);
- C7: routing and visibility (NK-WP-0057, together with E5).
- **For T07:** the hard-coded path and cluster UID in audit-core's seeder.
## Part 3: operations and the founder
NK-WP-0058-T10, as of 2026-11-05. It uses the same revisions as parts 1 and 2. Sources for the founder steps:
`sso-mfa/k8s/bounded-executor/T06-PILOT-RUNBOOK.md`, railiance-platform
`docs/credential-lane-designs/nk-bind-executor-go-live.md`, audit-core `docs/bounded-executor-receipts.md`, and the
evidence `docs/evidence/2026-11-04-nk56-t06-go-live/`, `2026-11-04-nk56-t06-pilot/` and `2026-11-05-nk56-t06-pilot/`.
| ID | Interface | Owner and source of truth | Consumer | Tested today | 2026-11-04/05 failure | Mark |
| --- | --- | --- | --- | --- | --- | --- |
| O1 | Argo CD ↔ manual applies | railiance-platform `argocd/railiance01/applications/` (pins, no automated sync). Every other object is applied by hand with `kubectl` (table below) | founder runbooks; agents writing apply commands | none: no drift check between Argo pin, repo file and live | Argo pin for informed-decision older than the live release, so a sync would have reverted it; Argo-managed admission guard refused client-side ESO apply; a live approval-engine policy missing from its file | **owner decision** (railiance-platform); **merge** (drift check in T03, convention in T07) |
| O2 | warden lanes ↔ child scripts | ops-warden: `warden access <lane> --exec` (silent, one command, fails on output, revokes on every path). Children: railiance-platform and audit-core silent scripts | the founder; seven lane invocations in the go-live runbook | none across repos | Seed r1 refused `token_env_must_be_unset`: warden strips `BAO_TOKEN` and `VAULT_TOKEN`, not `OPENBAO_TOKEN` | **keep**; **owner request** to ops-warden |
| O3 | Platform-admin session: lane vs shell | Two patterns: ops-warden lanes (railiance-platform, audit-core) and an interactive `bao login -method=oidc -path=netkingdom role=platform-admin` in the shell (net-kingdom `executor-lanes.py`, `pilot-run.sh`, T06 runbook 6c) | `executor-lanes.py attended_platform_admin()` | none | 2026-11-05: `window-close` failed `phase: identity, LaneError` in a shell without a login | **merge** into one pattern |
| O4 | Cluster access routes | Three in one runbook: `kubectl --kubeconfig ~/.kube/config-railiance01` (admin); `keycape-client.py` via `portal-client-rollout.py` hard-coding `/home/worsch/.kube/config-hosteurope` with `--server https://127.0.0.1:16444` (cluster UID pinned); the agent's default context (NetworkPolicy reads only) | founder, agents | none | none directly; costs founder setup | **merge** (T07: no hard-coded kubeconfigs) |
| F1 | Founder ↔ runbook steps | net-kingdom T06 runbook, railiance-platform go-live, audit-core founder runbook | the founder | live only | about 50 copy-paste rounds; values carried by hand between steps (table below) | **merge** into T04's commands |
| F2 | Founder identity in the browser | informed-decision UI at aal2; the identity model (`admin` for production, `platform-root` kept out) | the founder; executor `_check_approval` | none | The first approval came as `uid=platform-root`, the identity signed in at that moment, and was refused | **keep** the refusal; add identity hygiene to T04 |
### O1: which controller manages each object the path writes
| Object | Written by | Managed by | Note |
| --- | --- | --- | --- |
| informed-decision `deploy/lifecycle` (incl. `review-groups.json`, `intake-submitters.json` ConfigMaps) | founder release (2026-11-01), routing change (2026-11-05) | **Argo CD**, pinned revision, manual sync | The pin lagged live until railiance-platform `31cf74d`; a sync before that would have reverted the present-existing release |
| `reject-secret-last-applied` admission policy | — | **Argo CD** (`secret-annotation-guard`) | Refuses Secrets carrying `last-applied`; ESO propagates the annotation from a client-side apply |
| OpenBao ClusterSecretStores (shared) | — | **Argo CD** (`openbao-secretstore`) | |
| `sso` ESO objects (`manifests/nk-bind-executor-eso.yaml`) | founder, `kubectl apply --server-side` | manual | First apply was client-side and failed (runbook fixed, railiance-platform `9a2495d`) |
| approval-engine and audit-core ingress policies | founder, one object each | manual | Live approval-engine also has `informed-decision-review-ingress`, not in its file |
| KeyCape Deployment env and client registry | founder (rollout pause, patch, `keycape-client.py apply`) | manual | net-kingdom `sso-mfa/k8s/keycape/deployment.yaml` updated afterwards to match live |
| rapp standing objects (ServiceAccount, Role, RoleBinding, NetworkPolicy) | founder, `make deploy` | manual | |
| Per-run Job and ConfigMap | founder, `make request`, `make run` | manual (`kubectl create`) | |
| OpenBao write role | founder, `executor-lanes.py window-open` / `window-close` | manual | Left open overnight; T11 confirmed no use |
**Owner decision (railiance-platform, with railiance-cluster):** which objects on this path Argo should own. The
alternative is for each runbook to state the managing controller before it writes. Until that's decided, T07's
convention ("check the managing controller before writing apply commands") and T03's readiness check (compare each
Argo pin with live) cover the gap.
### O2 and O3: platform-admin sessions
- **The ops-warden lane contract:**
- one reviewed command per invocation;
- the child must be silent and write its own receipt;
- output fails the lane, and the token is revoked on every path;
- the caller passes `BAO_ADDR`, `VAULT_ADDR` and, where needed, `KUBECONFIG`.
- **Owner request to ops-warden:** strip `OPENBAO_TOKEN` too, or every token variable by pattern (T07).
- **The second pattern:** net-kingdom's window commands assume a token in the shell instead. They run several
commands, and `pilot-run.sh` keeps that session open across a whole run.
- **Proposal:** run `executor-lanes.py window-open`, `window-close` and the T03 readiness check as warden lanes. They
already write their own receipts and print one line. `pilot-run.sh` then opens one lane per command and needs no
shell login.
### F1: the founder's steps, 2026-11-04/05
| Step | Authority it needs | Value carried by hand | Value-free result | Safe to repeat |
| --- | --- | --- | --- | --- |
| 1 owner ingress, two objects | admin kubeconfig | none | `kubectl` output; `make netcheck` columns | yes |
| 2 accept the case | the founder in State Hub | decision id | the decision | yes |
| 3.0–3.7 custody go-live | platform-admin through 7 warden lanes; admin kubeconfig for ESO | none, but the case is committed `materialized` by claude-code between 3.3 and 3.5, after a pull | one receipt per step | mostly; the mint is create-only |
| 4 KeyCape bind and register | admin kubeconfig; the `keycape-client.py` route (O4); docker, to extract `check-config` | `before.uid` and `before.resource_version` from `inspect` into `apply` | registry count, check-config, rollout status | no: one restart, in a quiet minute |
| 5 admission | none (claude-code prepares) | window start | the file | yes, as a new file |
| 6 setup | none | `trust_satisfied` edited into a temporary config by an inline Python line | none | yes |
| 6a request | admin kubeconfig | **the approval id**, into 6d | `outcome: presented` | yes (idempotent memo) |
| 6b approve | **`admin` at aal2 in the browser** | none | none in the shell | — |
| 6c window open | platform-admin shell login (O3); CCR sha256s | the two CCR sha256s, receipt numbers | `window_opened` | refuses if already open |
| 6d run | admin kubeconfig | approval id; Job name | the result line in the log | no: one consume |
| 6e window close | platform-admin shell login | receipt number | `window_closed` | yes |
| 6f evidence | the agent reads audit-core by correlation id | — | — | yes |
There are about 45 commands across three runbooks, with five kinds of values carried by hand: UID and resource
version, approval id, CCR digests, receipt numbers and `trust_satisfied`. That fits the reported ~50 rounds. T04's
state file removes the carried values: `ready`, `request`, `run` and `status` read and write it, and the receipt
numbers come from the tool.
### F2: the browser identity
The approval was refused correctly, because the admission names `admin` and the founder's identity model keeps
`platform-root` out of production. But the founder learned which identity was signed in only from the refusal,
after the window had opened. T04 records the hygiene: a browser profile per identity, and a check of the signed-in
subject and OTP freshness before approving. The T03 check confirms that the memo's recipient is the admission's
approver.
### Statuses the agent had to ask the founder for (input to T05)
The agent's default context can read NetworkPolicies in `sso`, `approval-engine`, `audit-core`,
`informed-decision` and `openbao`. It can't read Jobs, pods, pod logs, Deployments or ConfigMaps (`kubectl auth can-i`,
2026-11-05). ExternalSecret read is not checked: the repository's secret-read guard blocks the question.
| Status | Where | Readable by the agent today | Proposal for T05 |
| --- | --- | --- | --- |
| Run and request Job status and result line | `sso` Jobs, pod logs (value-free by contract) | no | grant `get` on Jobs and pods, `get` on pod logs of `app.kubernetes.io/name=bounded-executor` pods |
| Diagnostic pod output | `sso` pods, logs | no | covered by the same grant; T03's `--preflight-only` should make diagnostic pods unnecessary |
| ExternalSecret `Ready` (KeyCape client, `bounded-executor-tokens`) | `sso` ExternalSecrets | unknown | `get` on ExternalSecrets in `sso` (status only, no Secret access) |
| KeyCape and Authelia rollout status | `sso` Deployments | no | `get` on Deployments in `sso` |
| Live ingress policies | NetworkPolicies | **yes** | none needed |
| informed-decision registry ConfigMaps vs Argo pin | `informed-decision` ConfigMaps, Argo Application | no | `get` on those ConfigMaps (non-secret), or read the Argo Application status |
| Approval entries | approval-engine `GET /v1/approvals/{id}` | no (needed diagnostic pod 4) | the executor's `approval_entry` names the failed field (T03); otherwise an operator read scope |
| OpenBao write role present | OpenBao `auth/kubernetes/role/...` | no | **founder-only**: OpenBao read under platform-admin; T03 and T06 report it value-free |
| OpenBao audit log | `openbao-0` file | no | **founder-only** (exec); audit forwarding (NK-WP-0055-T11) is the durable path |
| audit-core receipt in custody | audit-core operator read | through the run result's `receipt_not_in_custody` flag | keep: the result line already carries it |
## Summary for part 3
- **keep:**
- O2: the warden lane contract;
- F2: the executor's refusal of the wrong identity.
- **merge:**
- O1: drift check and controller convention;
- O3: one platform-admin pattern, warden lanes for the window commands;
- O4: one cluster access route, no hard-coded kubeconfigs;
- F1: carried values move to T04's state file.
- **owner decision:** O1, which objects Argo owns on this path (railiance-platform with railiance-cluster).
- **owner request:** O2, ops-warden strips every token variable.
- **For T05:** the status table above. The minimal grant is read-only `get` on Jobs, pods, executor pod logs,
Deployments and ExternalSecrets in `sso`, plus the informed-decision registry ConfigMaps. It excludes Secrets,
exec and kubeconfigs. The founder-only rows stay founder-only.
- **For T07:** `portal-client-rollout.py` and three other tools hard-code kubeconfigs (`config-hosteurope`,
`/etc/rancher/k3s/k3s.yaml`).

View file

@ -189,7 +189,9 @@ Mark the duplicated pieces, for example the two seeders and the two Secret names
- **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.
- **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;
@ -197,7 +199,8 @@ Mark the duplicated pieces, for example the two seeders and the two Secret names
- C6: the executor's submitter profile is in informed-decision, access-engine and the rapp.
- **owner decisions:**
- C1: a supported platform interface;
- C3: `materialized` is committed before the mint;
- 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.
@ -207,7 +210,7 @@ Mark the duplicated pieces, for example the two seeders and the two Secret names
```task
id: NK-WP-0058-T10
status: todo
status: done
priority: high
assignee: claude-code
depends_on: [NK-WP-0058-T01]
@ -223,6 +226,22 @@ Add rows to the T01 table, in the same format, for:
**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