- **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) |
| 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`;
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: whether approval-engine should enforce `required_assurance`.
- **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`.
| 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