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
471 lines
38 KiB
Markdown
471 lines
38 KiB
Markdown
# Bounded execution: cross-repo contract table
|
||
|
||
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
|
||
revisions: net-kingdom `23a99c1`, rapp-bounded-executor `3ecd462`, approval-engine `f41a9ca`, informed-decision
|
||
`77161fe`, audit-core `3157454`, key-cape `82b471f`, railiance-platform `31cf74d`.
|
||
|
||
**Marks.**
|
||
|
||
- **keep:** one owner, one source, so it only needs a contract test (T02).
|
||
- **merge:** the contract is written down in more than one place. Keep the named source and derive or check the
|
||
copies.
|
||
- **delete:** the piece is redundant.
|
||
- **owner decision:** the owner or the boundary is unclear. It goes to NK-WP-0057 or to the named owner.
|
||
|
||
**Tested today.** The levels are:
|
||
|
||
- **CI:** runs in a Forgejo workflow on every change;
|
||
- **local:** a test that needs a sibling checkout and skips without one;
|
||
- **manual:** an exercise script someone has to run;
|
||
- **live:** proven only by a founder-run step;
|
||
- **none.**
|
||
|
||
Two facts frame this column:
|
||
|
||
- net-kingdom CI (`.forgejo/workflows/identity-journeys.yaml`) tests a source tarball of net-kingdom alone, so every
|
||
sibling-dependent test skips there.
|
||
- rapp-bounded-executor has no CI workflow.
|
||
|
||
## Part 1: the executor and its services
|
||
|
||
| ID | Interface | Owner and source of truth | Consumer | Tested today | 2026-11-04/05 failure | Mark |
|
||
| --- | --- | --- | --- | --- | --- | --- |
|
||
| E1 | OpenBao: Kubernetes login and KV, three lanes | railiance-platform: CCR-2026-0058, -0059, -0061 and `openbao/policies/` | executor (`operations.py OpenBao`); `executor-lanes.py` creates and deletes the write role | CI: fake OpenBao. Local: `tests/test_executor_lanes.py` against the CCRs | Write role left open overnight; `window-close` failed as bare `LaneError` | **merge** |
|
||
| E2 | KeyCape `client_credentials` token | key-cape: `src/internal/server/oidc/token.go`. Client registration: net-kingdom `sso-mfa/k8s/keycape/` with `keycape-client.py` | executor (`clients.py KeyCapeClientCredentials`) | CI: fakes. Manual: `exercise_approval_identity.py` (T19) against KeyCape source/image | none (the request phase worked) | **keep**; registration ownership is an **owner decision** |
|
||
| E3 | approval-engine: create, get and consume | approval-engine: `approval_engine/api.py`, `store.py` | executor (`clients.py ApprovalEngine`, `executor.py _check_approval`) | CI: fakes. Manual: T19 against approval-engine source | `platform-root` approval refused as `approval_entry`; finding which field failed took diagnostic pod 4 | **keep** |
|
||
| E4 | informed-decision: memo intake (`present_existing`) | informed-decision: `informed_decision/intake_service.py`, `deploy/lifecycle/intake-submitters.json` | executor (`clients.py InformedDecision`, `cli.py admission_memo`) | Local: `test_approval_request.py`, path hard-coded to `/home/worsch/informed-decision` | Memo reached `platform-root` only, so one window was wasted (routing, E5) | **keep** |
|
||
| E5 | The approver's identity across repos | **None.** It is defined four times (below) | executor `_check_approval`, informed-decision routing, the founder | none | One wasted window: the admission named `admin`, routing went to `platform-root` | **merge**; **owner decision** (NK-WP-0057) |
|
||
| E6 | audit-core: event ingest | audit-core: `docs/event-envelope.md`, `audit_core/ingestion.py`, sender registry. Receipt schema: net-kingdom `canon/schemas/execution-attribution-receipt_v0.1.schema.json` | executor (`clients.py AuditCore`) | CI: fakes. Manual: `exercise_audit_sender.py` (T15) against audit-core source. Receipt validator not in a workflow | none (receipt accepted 2026-11-05) | **keep** |
|
||
| E7 | LLDAP: sign-in and self password change | LLDAP upstream. The runtime in `sso` has no owner in `docs/responsibility-map.md`; its reference manifests are in net-kingdom `sso-mfa/k8s/` | executor (`operations.py Directory`, the image's `lldap_set_password`) | CI: fakes. Nothing pins the image's tool version against the server | none | **keep**; runtime ownership is an **owner decision** |
|
||
| E8 | Kubernetes API: ESO force-sync and Deployment restart | API: railiance-cluster. Role content: net-kingdom `sso-mfa/k8s/bounded-executor/rbac.yaml`, copied to rapp `manifests/rbac.yaml` | executor (`operations.py KubeDelivery`, `KubeRestart`) | Make only: `test_rbac.py` needs no sibling, but no workflow runs it. Local: rapp `test_ceiling_is_the_net_kingdom_source` | none in the run (ESO server-side apply was a founder step, T10) | **merge** |
|
||
| E9 | Network path of the executor pod | rapp `manifests/networkpolicy.yaml` (egress). Ingress belongs to each target: net-kingdom `netpol-sso.yaml` (lldap, keycape), approval-engine `e9e9f93`, audit-core `7728fde`, informed-decision `:8081`, OpenBao | executor `wait_all_reachable`, `make netcheck` | Local: rapp `test_every_in_namespace_egress_is_admitted_by_net_kingdom`. Live: `make netcheck` | Two windows refused `setup:URLError`; four diagnostic pods; live approval-engine had a policy its file lacked | **keep**; add a cross-owner ingress test |
|
||
| R1 | Image build and pin chain | net-kingdom builds (`.forgejo/workflows/bounded-executor-image.yaml`); rapp pins (`Makefile IMAGE_DIGEST`); the admission pins source files (`plan.source.commit` and file sha256) | renderer (`tools/render_job.py`), the executor's checks | none: commit to digest is recorded only in the rapp commit message | A re-pin and a new admission after `c2a0f2f`, all by hand | **merge** |
|
||
| R2 | Runtime config, Job template and exit codes | net-kingdom `bounded-executor/cli.py` (config keys in its docstring, `EXIT`). Copies: rapp `examples/config.pilot.json`, `docs/runtime-contract.md`, `render_job.py` allow-lists | rapp renderer, `pilot-run.sh`, the founder | Local: rapp `test_packaging.py` reads `cli.py` (skips without sibling); no rapp CI | `pilot-run.sh` edits `trust_satisfied` into a temporary copy of the config | **merge** |
|
||
|
||
## Row details
|
||
|
||
### E1: OpenBao lanes
|
||
|
||
- **Contract:**
|
||
- address `http://openbao.openbao.svc:8200`;
|
||
- login `POST /v1/auth/kubernetes/login` with the projected token, audience `openbao`, from
|
||
`/var/run/secrets/openbao/token`;
|
||
- one role per lane, with leases of at most 15 minutes, each revoked at exit:
|
||
- write: `net-kingdom-bind-executor-svc-authelia-write`, KV `platform/workloads/net-kingdom/directory-binds/svc-authelia`, field `LDAP_BIND_PASSWORD`, `cas_required`;
|
||
- journey: `net-kingdom-bind-executor-syn-journey-read`, KV `.../synthetic-journey/syn-journey`, fields `PASSWORD`, `TOTP_SEED`;
|
||
- approval client: `keycape-nk-bind-executor-approval-bounded-executor-workload-kv-read`, field `CLIENT_SECRET`, read once and revoked at once.
|
||
- **Window:** the write role exists only between `executor-lanes.py window-open` and `window-close`.
|
||
- **Duplicates:** role names, KV paths and fields appear in four places: the CCR files, `executor-lanes.py LANES`,
|
||
rapp `config.pilot.json` and the rapp renderer's role allow-list. `LANES` also carries the write and journey
|
||
policy text inline.
|
||
- **Proposal:**
|
||
- `LANES` reads names, paths and fields from the CCRs. The policy text is compared against railiance-platform's
|
||
policy file, not kept as a second copy.
|
||
- The renderer's allow-list is checked against the same source.
|
||
- `executor-lanes.py` names the lane error's cause. On 2026-11-05 `phase: identity, LaneError` hid
|
||
"no platform-admin login" (T03).
|
||
|
||
### E2: KeyCape client
|
||
|
||
- **Contract:**
|
||
- `POST http://keycape.sso.svc.cluster.local:8080/token` with Host `kc.coulomb.social`, `client_secret_basic`,
|
||
client `nk-bind-executor-approval`;
|
||
- one scope per token (`approval:create`, `approval:read`, `approval:consume`);
|
||
- the answer must be a bearer token with `expires_in` of at most 900;
|
||
- a scope outside the list must be answered `invalid_profile_usage`.
|
||
- **Owners:** KeyCape owns the token behaviour. The client's registration lives in net-kingdom `sso-mfa/k8s/keycape/`
|
||
(deployment env and `keycape-client.py`), and its case is `security-cases/nk-bind-executor-approval.yaml`
|
||
(oidc-service-client).
|
||
- **Open:**
|
||
- `excess-scope-refused` is not yet proven against live KeyCape.
|
||
- **Owner decision:** ADR-0015 moves runtime packages to `rapp-*`, but there is no KeyCape rapp, so the client
|
||
registration sits in net-kingdom's reference manifests. Who owns registering a KeyCape client?
|
||
|
||
### E3: approval-engine
|
||
|
||
- **Contract:**
|
||
- create: `POST /v1/approvals` answers 201. `binding.actor` must equal the token's `sub` (`api.py`); the executor
|
||
sends `human_control: true`, `pdp_path: false` and `required_count` = the number of approvers.
|
||
- read: `GET /v1/approvals/{id}`.
|
||
- consume: `POST /v1/approvals/{id}/consume` with `request_digest` and `decision_id`.
|
||
- the executor accepts an entry only if:
|
||
- `subject_id` is a listed approver;
|
||
- `principal_type` is human;
|
||
- the assurance is aal2;
|
||
- the subject is not the executor itself.
|
||
- the deployed approval-engine records assurance but doesn't enforce it, so the executor checks aal2 itself.
|
||
- **Proposal:** `approval_entry` names the failed field: subject, principal type or assurance. That alone would
|
||
have replaced diagnostic pod 4.
|
||
- **Already answered by the owner:** approval-engine replied on 2026-10-31 (message `756016f9`,
|
||
`docs/decision-services-alignment-answer.md`, `e9e9f93`). `required_assurance` is in source as schema v7: entries
|
||
below the floor are refused with 403, the claim with `assurance_unsatisfied`, and consume with 409. It waits for a
|
||
v7 image release and a founder-run rollout with a pre-v7 backup. The executor keeps its own aal2 check until then,
|
||
and sends `required_assurance` once v7 is live. This is a dependency, not an open decision.
|
||
|
||
### E4: informed-decision intake
|
||
|
||
- **Contract:**
|
||
- `POST /v1/memos` on `informed-decision-workload.informed-decision.svc:8081`;
|
||
- the bearer is the projected ServiceAccount token, audience `informed-decision-intake`, checked by TokenReview;
|
||
- the registry row is `system:serviceaccount:sso:nk-bind-executor` → submitter `nk-bind-executor`, memo class
|
||
`bounded-execution`, review group `net-kingdom-admins`, `present_existing.approval_actor`
|
||
`service:nk-bind-executor`, daily limit 10;
|
||
- the receipt must echo `memo_id`, `approval_id` and `approval_binding_digest`;
|
||
- `cli.py --request` exits 7 when the memo is not accepted. Resubmitting is idempotent.
|
||
- **Test gap:** `bounded-executor/tests/test_approval_request.py:216` hard-codes `/home/worsch/informed-decision`
|
||
(T07: no hard-coded paths).
|
||
|
||
### E5: approver identity, defined four times
|
||
|
||
| Where | Value on 2026-11-05 | Owner |
|
||
| --- | --- | --- |
|
||
| net-kingdom `admissions/BEA-2026-0003.yaml` `spec.approval.approvers` | `[admin]` (a name) | net-kingdom (`prepare_admission.py`) |
|
||
| rapp `examples/config.pilot.json` `approver_subjects`, `approver_issuer` | `admin` → `uid=admin,ou=people,dc=netkingdom,dc=local`, `https://kc.coulomb.social` | rapp-bounded-executor |
|
||
| informed-decision `deploy/lifecycle/review-groups.json` | `net-kingdom-admins` → `uid=admin,…` (interim, decision `c9bedd9c`; it was `platform-root` until `77161fe`) | informed-decision |
|
||
| key-cape LLDAP adapter | `sub` = the LDAP DN | key-cape |
|
||
|
||
Nothing checks that these four agree, and a mismatch shows only as a refused approval after a window has opened.
|
||
**Until NK-WP-0057 decides who routes:** the T03 readiness check compares them. The admission's approver names
|
||
resolve through the rapp config and must equal the routing recipient for the memo's review group.
|
||
|
||
### E6: audit-core
|
||
|
||
- **Contract:**
|
||
- `POST /v1/events` with `Idempotency-Key` equal to the event's `id`, and all eight envelope fields;
|
||
- answers: 202 accepted, 200 duplicate, 400 dead-lettered with a named reason, and 409 for a reused run id with a
|
||
different body (a defect, never retried);
|
||
- the bearer is the file `/var/run/secrets/bounded-executor/audit-core`, from `sso/bounded-executor-tokens` key
|
||
`audit-core` (custody in part 2);
|
||
- the source is checked against the sender registry;
|
||
- receipt schema v0.1 and its EAR rules (`tools/execution-attribution-receipt`).
|
||
- **Proposal:** fold the T15 exercise into the T02 suite, and run `make execution-attribution-receipt-test` in CI.
|
||
|
||
### E7: LLDAP
|
||
|
||
- **Contract:**
|
||
- `http://lldap.sso.svc.cluster.local:17170`;
|
||
- sign-in checks over its HTTP API;
|
||
- the password change runs the image's `/app/lldap_set_password` with `--base-url`, `--token`, `--username`
|
||
(OPAQUE).
|
||
- **Gap:** nothing checks that the tool in the executor image is compatible with the running LLDAP server.
|
||
- **Owner decision:** `docs/responsibility-map.md` names no owner for the LLDAP, Authelia or KeyCape runtimes in
|
||
`sso`. SCOPE says the `sso-mfa/k8s/` material is "migration inputs, not blanket authority".
|
||
|
||
### E8: Kubernetes API
|
||
|
||
- **Contract:**
|
||
- force-sync: merge-patch `metadata.annotations.force-sync` on `/apis/external-secrets.io/v1/namespaces/sso/externalsecrets/authelia-secrets`, then poll its status;
|
||
- restart: merge-patch `spec.template.metadata.annotations["kubectl.kubernetes.io/restartedAt"]` on Deployment `authelia`, then wait for the rollout;
|
||
- Role: `get/watch/patch` on that Deployment and `get/patch` on that ExternalSecret. No Secret access.
|
||
- **Duplicate:** the Role is written in net-kingdom and copied by hand into rapp (`make ceiling-sync`: "copy, never
|
||
edit"). The compare test skips without the sibling checkout, and rapp has no CI.
|
||
- **Proposal:** keep net-kingdom as the source, and make the compare test a T02 contract test that runs in CI.
|
||
|
||
### E9: network path
|
||
|
||
- **Contract:**
|
||
- the executor pod is labelled `app.kubernetes.io/name=bounded-executor` in namespace `sso`;
|
||
- egress per the rapp policy;
|
||
- each target admits it on its own port: approval-engine and audit-core on 8080, informed-decision on 8081,
|
||
KeyCape and LLDAP through `netpol-sso.yaml`, and OpenBao on 8200;
|
||
- the executor waits up to 30 s for all five endpoints. A setup failure names the unreachable one (since
|
||
`c2a0f2f`).
|
||
- **Gap:** the ingress side lives in five repos, and only the sso part is compared. The live approval-engine
|
||
policy differed from its file.
|
||
- **Proposal:** a T02 test that renders every owner's ingress policy for this label, plus a live comparison in the
|
||
T03 readiness check.
|
||
|
||
### R1: image and pins
|
||
|
||
- **Chain:**
|
||
1. a net-kingdom commit;
|
||
2. the Forgejo image workflow builds a digest;
|
||
3. the digest is pinned by hand in rapp's `Makefile IMAGE_DIGEST`, with the source commit only in the rapp commit
|
||
message;
|
||
4. separately, `prepare_admission.py --commit` pins the admission to source file sha256s
|
||
(`plan.source.files`).
|
||
- **Gap:** no record links the digest to the commit to the admission pins, so "the admission's pins match the
|
||
pinned image" cannot be checked.
|
||
- **Proposal:**
|
||
- the image workflow writes a value-free pin record (commit, file sha256s, digest);
|
||
- rapp pins that record;
|
||
- the renderer refuses when the admission's file hashes are not the image's (T03).
|
||
|
||
### R2: config, Job and exit codes
|
||
|
||
- **Source:** the config keys and exit codes are defined in `cli.py`: 0 completed, 3 reverted, 4 refused, 5
|
||
unknown, 6 not in custody, and 7 for a request whose memo was not accepted.
|
||
- **Copies:**
|
||
- rapp `docs/runtime-contract.md` (prose);
|
||
- `config.pilot.json` (values);
|
||
- `render_job.py` allow-lists (token paths, role names);
|
||
- `pilot-run.sh` sets `trust_satisfied` by editing a temporary copy of the config.
|
||
- **Proposal:**
|
||
- a versioned config schema file in net-kingdom, which the renderer validates against;
|
||
- the exit codes stated once, in that file;
|
||
- `trust_satisfied` becomes a run input recorded in the state file (T04), not a config edit.
|
||
|
||
## Summary for part 1
|
||
|
||
- **keep:** E2, E3, E4, E6, E7, E9. Each needs its T02 contract test. The T15 and T19 exercises become CI tests.
|
||
- **merge:**
|
||
- E1: lane names and policy text, from the CCRs;
|
||
- E5: the approver, compared until it has one source;
|
||
- E8: the RBAC copy;
|
||
- R1: the pin record;
|
||
- R2: the config schema.
|
||
- **delete:** nothing yet. The candidates are the hand copies named under merge, once they are derived or checked.
|
||
- **owner decision** (to NK-WP-0057-T01 or the named owner):
|
||
- E5: who owns the approver and routing;
|
||
- E2: KeyCape client registration with no KeyCape rapp;
|
||
- E7: the LLDAP, Authelia and KeyCape runtime owner in `sso`;
|
||
- E3 is not open: approval-engine enforces `required_assurance` from v7, which waits for its release.
|
||
- **Transparency findings for T03:**
|
||
- `executor-lanes.py` lane errors without a cause;
|
||
- `approval_entry` without the failed field.
|
||
- **Hard-coded path for T07:** `test_approval_request.py:216`.
|
||
|
||
## Part 2: the approval, case and custody chain
|
||
|
||
NK-WP-0058-T09, as of 2026-11-05. It uses the same revisions as part 1, plus access-engine `535bab7`.
|
||
|
||
| ID | Interface | Owner and source of truth | Consumer | Tested today | 2026-11-04/05 failure | Mark |
|
||
| --- | --- | --- | --- | --- | --- | --- |
|
||
| 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 | none. The status is committed by hand from a founder message | **keep** (who materializes: tracked in MASON-WP-0010-T03) |
|
||
| 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 |
|
||
| C7 | Memo routing ↔ who can see it | informed-decision `deploy/lifecycle/review-groups.json` (the recipient). access-engine policy v14 `recipient_ok`: `list` only when `review_recipient == subject.id` | the founder in informed-decision; executor `_check_approval` (E5) | none | The memo was visible only to `platform-root`, so one window was wasted | **owner decision** (NK-WP-0057; this is E5's routing half) |
|
||
|
||
### C1: CCR lifecycle and the lane tooling
|
||
|
||
- **Contract:**
|
||
- `executor-lanes.py` acts only under both CCRs, approved by `platform-owner` and `net-kingdom-owner` with
|
||
`bound_claims_confirmed: true`;
|
||
- each CCR is passed as the sha256 of its file (`--write-ccr-sha256`, `--journey-ccr-sha256`), computed by
|
||
`sha256sum` in `pilot-run.sh`;
|
||
- the identity phase requires an attended `platform-admin` token from `auth/netkingdom/`, never root.
|
||
- **Gap:** `executor-lanes.py` puts `railiance-platform/scripts` on `sys.path` and imports helpers from three
|
||
scripts, `state_hub_preflight_lane`, `repair_eso_kubernetes_auth` and `telemetry_smtp_delivery`. None of them was
|
||
written as an interface for another repo, so a refactor there breaks the window commands without a failing test
|
||
in either CI.
|
||
- **Owner decision (railiance-platform):** offer a supported entry point for role create, delete and read, under a
|
||
CCR digest, or accept these helpers as a versioned interface. Either way T02 adds a test that fails in the
|
||
consumer.
|
||
|
||
### C2: case-type hash pins
|
||
|
||
- **Contract:** the broker admits `oidc-browser-relying-party@1.1` (`f705c64e…`) and `oidc-service-client@0.2`
|
||
(`cd238693…`) only when the net-kingdom `type.yaml` bytes hash to these values. A wording fix in net-kingdom
|
||
silently breaks the mint.
|
||
- **Proposal:** a net-kingdom CI test that compares each acknowledged `type.yaml` with a pin list kept in
|
||
net-kingdom. That list is checked against the broker's `ACKNOWLEDGED_TYPES` once owner source can reach CI (T02).
|
||
|
||
### C3: case status and the broker
|
||
|
||
- **Contract:** the broker refuses unless the case is `status: materialized` with
|
||
`acceptance: {at, record, by: [{role: owner}, {role: founder}]}`.
|
||
- **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 by hand.
|
||
That isn't a new question: ops-mason's case build path (`ops-mason build --case`, MASON-WP-0010-T03) exists and
|
||
waits on railiance-platform's structural evidence. ops-mason reviewed and test-rendered this case on
|
||
2026-10-31 (message `91e2205f`). Tracked there; no new routing.
|
||
|
||
### C4: KeyCape client secret delivery
|
||
|
||
- **Chain:**
|
||
1. the broker mints into `platform/workloads/keycape/nk-bind-executor-approval` (`CLIENT_SECRET`);
|
||
2. ESO (CCR-2026-0060) delivers it to `sso/keycape-nk-bind-executor-approval-client` key `client-secret`;
|
||
3. KeyCape reads it from env `KEYCAPE_NK_BIND_EXECUTOR_APPROVAL_CLIENT_SECRET`;
|
||
4. the executor reads the same KV through CCR-2026-0061.
|
||
- **Duplicates:** the Secret name follows a naming convention written twice: net-kingdom
|
||
`keycape-client.py env_secret_name()` and railiance-platform's ESO template. The KV path appears in the CCRs, the
|
||
rapp config and the custody tool.
|
||
- **Proposal:** one test computes the convention from both sides for every registered client. The executor's KV
|
||
path is checked against CCR-2026-0061 (T02).
|
||
|
||
### C5: audit sender custody
|
||
|
||
- **Contract:**
|
||
- audit-core's seeder mints the bearer CAS 0 into the CCR-2026-0063 path, merges the registry row
|
||
`nk-bounded-executor` with compare-and-set, force-syncs and probes;
|
||
- railiance-platform adds only the coding-agent fence and `verify`;
|
||
- ESO delivers to `sso/bounded-executor-tokens` key `audit-core`;
|
||
- the rapp Job mounts it at `/var/run/secrets/bounded-executor/audit-core`.
|
||
- **Settled 2026-11-04:** one seeder (RPF-WP-0069 "One seeder"; AUDIT-WP-0018-T04, audit-core `3157454`). The
|
||
Secret moved from `sso/nk-bind-executor-audit` key `audit-token` to `sso/bounded-executor-tokens` key
|
||
`audit-core`.
|
||
- **Still duplicated or stale:**
|
||
- railiance-platform `docs/credential-lane-designs/bounded-executor.md:85-90` still names
|
||
`sso/nk-bind-executor-audit` key `audit-token`. That is a removal or correction request to railiance-platform.
|
||
- audit-core's seeder hard-codes `/home/worsch/railiance-platform/...CCR-2026-0063...yaml` and the cluster UID
|
||
(`seed-nk-bounded-executor-sender.py:58-69`), for T07.
|
||
- the names appear in four repos (CCR, seeder, ESO manifest, rapp Job) and no test compares them.
|
||
|
||
### C6: the executor's submitter profile
|
||
|
||
The executor is described as one requester profile in three places:
|
||
|
||
| Where | Fields |
|
||
| --- | --- |
|
||
| informed-decision `intake-submitters.json` | `system:serviceaccount:sso:nk-bind-executor` → `nk-bind-executor`, class `bounded-execution`, group `net-kingdom-admins`, `present_existing.approval_actor` `service:nk-bind-executor`, limit 10 |
|
||
| access-engine policy v14 `present_existing_profiles` | `service:nk-bind-executor` → `intake_submitter` `nk-bind-executor`, `memo_class` `bounded-execution`, `approval_action` `bounded-execution` |
|
||
| rapp `config.pilot.json` | `executor_identity` `service:nk-bind-executor`, `runtime_principal` `system:serviceaccount:sso:nk-bind-executor` |
|
||
|
||
The policy says its profiles are "owner-authored here, not generated". That was a deliberate access-engine choice
|
||
(GH-DEC-2026-026), so it isn't changed locally. **Owner decision:** whether informed-decision's registry or
|
||
access-engine's policy is the source, with the other checked against it. Until then, T02 compares the three.
|
||
|
||
### C7: routing and visibility
|
||
|
||
- informed-decision maps the review group to exactly one recipient (`intake_service.py:189`).
|
||
- access-engine's `list` allows a viewer only if they are that recipient (`policy.md recipient_ok`).
|
||
- The two rules together mean the routing table decides who can approve, without the admission seeing it. On
|
||
2026-11-04 the admission named `admin`, routing named `platform-root`, and the policy then hid the memo from
|
||
`admin`.
|
||
- This is NK-WP-0057's core question. The T03 readiness check compares the admission's approver with the routing
|
||
recipient before a window opens.
|
||
|
||
## Summary for part 2
|
||
|
||
- **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;
|
||
- C5: audit custody names in four repos;
|
||
- C6: the submitter profile in three places.
|
||
- **delete:** the stale Secret name in railiance-platform `docs/credential-lane-designs/bounded-executor.md`
|
||
(an owner request).
|
||
- **owner decision:**
|
||
- C1: a supported platform interface for window roles (railiance-platform);
|
||
- 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`).
|