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
40 KiB
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/loginwith the projected token, audienceopenbao, 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, KVplatform/workloads/net-kingdom/directory-binds/svc-authelia, fieldLDAP_BIND_PASSWORD,cas_required; - journey:
net-kingdom-bind-executor-syn-journey-read, KV.../synthetic-journey/syn-journey, fieldsPASSWORD,TOTP_SEED; - approval client:
keycape-nk-bind-executor-approval-bounded-executor-workload-kv-read, fieldCLIENT_SECRET, read once and revoked at once.
- write:
- address
- Window: the write role exists only between
executor-lanes.py window-openandwindow-close. - Duplicates: role names, KV paths and fields appear in four places: the CCR files,
executor-lanes.py LANES, rappconfig.pilot.jsonand the rapp renderer's role allow-list.LANESalso carries the write and journey policy text inline. - Proposal:
LANESreads 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.pynames the lane error's cause. On 2026-11-05phase: identity, LaneErrorhid "no platform-admin login" (T03).
E2: KeyCape client
- Contract:
POST http://keycape.sso.svc.cluster.local:8080/tokenwith Hostkc.coulomb.social,client_secret_basic, clientnk-bind-executor-approval;- one scope per token (
approval:create,approval:read,approval:consume); - the answer must be a bearer token with
expires_inof 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 andkeycape-client.py), and its case issecurity-cases/nk-bind-executor-approval.yaml(oidc-service-client). - Open:
excess-scope-refusedis 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/approvalsanswers 201.binding.actormust equal the token'ssub(api.py); the executor sendshuman_control: true,pdp_path: falseandrequired_count= the number of approvers. - read:
GET /v1/approvals/{id}. - consume:
POST /v1/approvals/{id}/consumewithrequest_digestanddecision_id. - the executor accepts an entry only if:
subject_idis a listed approver;principal_typeis 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.
- create:
- Proposal:
approval_entrynames 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_assuranceis in source as schema v7: entries below the floor are refused with 403, the claim withassurance_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 sendsrequired_assuranceonce v7 is live. This is a dependency, not an open decision.
E4: informed-decision intake
- Contract:
POST /v1/memosoninformed-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→ submitternk-bind-executor, memo classbounded-execution, review groupnet-kingdom-admins,present_existing.approval_actorservice:nk-bind-executor, daily limit 10; - the receipt must echo
memo_id,approval_idandapproval_binding_digest; cli.py --requestexits 7 when the memo is not accepted. Resubmitting is idempotent.
- Test gap:
bounded-executor/tests/test_approval_request.py:216hard-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/eventswithIdempotency-Keyequal to the event'sid, 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, fromsso/bounded-executor-tokenskeyaudit-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-testin 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_passwordwith--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.mdnames no owner for the LLDAP, Authelia or KeyCape runtimes insso. SCOPE says thesso-mfa/k8s/material is "migration inputs, not blanket authority".
E8: Kubernetes API
- Contract:
- force-sync: merge-patch
metadata.annotations.force-syncon/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 Deploymentauthelia, then wait for the rollout; - Role:
get/watch/patchon that Deployment andget/patchon that ExternalSecret. No Secret access.
- force-sync: merge-patch
- 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-executorin namespacesso; - 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).
- the executor pod is labelled
- 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:
- a net-kingdom commit;
- the Forgejo image workflow builds a digest;
- the digest is pinned by hand in rapp's
Makefile IMAGE_DIGEST, with the source commit only in the rapp commit message; - separately,
prepare_admission.py --commitpins 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.pyallow-lists (token paths, role names);pilot-run.shsetstrust_satisfiedby editing a temporary copy of the config.
- rapp
- Proposal:
- a versioned config schema file in net-kingdom, which the renderer validates against;
- the exit codes stated once, in that file;
trust_satisfiedbecomes 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_assurancefrom v7, which waits for its release.
- Transparency findings for T03:
executor-lanes.pylane errors without a cause;approval_entrywithout 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.pyacts only under both CCRs, approved byplatform-ownerandnet-kingdom-ownerwithbound_claims_confirmed: true;- each CCR is passed as the sha256 of its file (
--write-ccr-sha256,--journey-ccr-sha256), computed bysha256suminpilot-run.sh; - the identity phase requires an attended
platform-admintoken fromauth/netkingdom/, never root.
- Gap:
executor-lanes.pyputsrailiance-platform/scriptsonsys.pathand imports helpers from three scripts,state_hub_preflight_lane,repair_eso_kubernetes_authandtelemetry_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…) andoidc-service-client@0.2(cd238693…) only when the net-kingdomtype.yamlbytes 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.yamlwith a pin list kept in net-kingdom. That list is checked against the broker'sACKNOWLEDGED_TYPESonce owner source can reach CI (T02).
C3: case status and the broker
- Contract: the broker refuses unless the case is
status: materializedwithacceptance: {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). Committingmaterializedafter go-live steps 0–3 and before the mint is therefore correct; the mint and verification lead tolive. 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 (message91e2205f). Tracked there; no new routing.
C4: KeyCape client secret delivery
- Chain:
- the broker mints into
platform/workloads/keycape/nk-bind-executor-approval(CLIENT_SECRET); - ESO (CCR-2026-0060) delivers it to
sso/keycape-nk-bind-executor-approval-clientkeyclient-secret; - KeyCape reads it from env
KEYCAPE_NK_BIND_EXECUTOR_APPROVAL_CLIENT_SECRET; - the executor reads the same KV through CCR-2026-0061.
- the broker mints into
- 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-executorwith compare-and-set, force-syncs and probes; - railiance-platform adds only the coding-agent fence and
verify; - ESO delivers to
sso/bounded-executor-tokenskeyaudit-core; - the rapp Job mounts it at
/var/run/secrets/bounded-executor/audit-core.
- audit-core's seeder mints the bearer CAS 0 into the CCR-2026-0063 path, merges the registry row
- Settled 2026-11-04: one seeder (RPF-WP-0069 "One seeder"; AUDIT-WP-0018-T04, audit-core
3157454). The Secret moved fromsso/nk-bind-executor-auditkeyaudit-tokentosso/bounded-executor-tokenskeyaudit-core. - Still duplicated or stale:
- railiance-platform
docs/credential-lane-designs/bounded-executor.md:85-90still namessso/nk-bind-executor-auditkeyaudit-token. That is a removal or correction request to railiance-platform. - audit-core's seeder hard-codes
/home/worsch/railiance-platform/...CCR-2026-0063...yamland 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.
- railiance-platform
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
listallows 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 namedplatform-root, and the policy then hid the memo fromadmin. - 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.yamlbytes; - C3.
- C2, with a net-kingdom guard test for the acknowledged
- 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_ADDRand, where needed,KUBECONFIG.
- Owner request to ops-warden: strip
OPENBAO_TOKENtoo, 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.shkeeps that session open across a whole run. - Proposal: run
executor-lanes.py window-open,window-closeand the T03 readiness check as warden lanes. They already write their own receipts and print one line.pilot-run.shthen 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
geton Jobs, pods, executor pod logs, Deployments and ExternalSecrets insso, plus the informed-decision registry ConfigMaps. It excludes Secrets, exec and kubeconfigs. The founder-only rows stay founder-only. - For T07:
portal-client-rollout.pyand three other tools hard-code kubeconfigs (config-hosteurope,/etc/rancher/k3s/k3s.yaml).
Routing of the owner decisions (2026-11-05)
| Row | Question | Routed to | Reference |
|---|---|---|---|
| E5 + C7 | Approver and routing; the routing table decides who can approve | NK-WP-0057-T01 | workplan inputs |
| E2 | Who registers KeyCape clients, with no KeyCape rapp | NK-WP-0057-T01 | workplan inputs |
| E7 | Runtime owner of LLDAP, Authelia and KeyCape in sso |
NK-WP-0057-T01 | workplan inputs |
| C1 | A supported platform interface for window roles | railiance-platform | message f6198285 |
| O1 | Which objects on the path Argo CD owns | railiance-platform (with railiance-cluster) | message f6198285 |
| C5 | Correct the stale Secret name in bounded-executor.md:85-90 |
railiance-platform | message f6198285 |
| O2 | Strip every token variable in warden access --exec; name the variable in the refusal |
ops-warden | message 4acb653c |
| C6 | Which copy of the executor's present-existing profile is the source | informed-decision and access-engine | messages fa7e25d3, df5aa613 |
| E3 | required_assurance |
not routed: answered 2026-10-31 (756016f9); waits for approval-engine v7 release |
— |
| C3 | Who materializes | not routed: tracked in ops-mason MASON-WP-0010-T03 (91e2205f) |
— |