11 rows with sources at named revisions; 6 keep, 5 merge, 4 owner decisions routed to NK-WP-0057. CI tests net-kingdom alone and rapp has no CI, so every cross-repo check is local or manual today. 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
16 KiB
Bounded execution: cross-repo contract table
NK-WP-0058-T01 (part 1), as of 2026-11-05. Parts 2 (T09: approval, case and custody chain) and 3 (T10: operations and the founder) add rows to this file. 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.
- approval-engine also has
required_assurance. The executor doesn't send it, and checks aal2 itself ("recorded, not enforced").
- create:
- Proposal:
approval_entrynames the failed field: subject, principal type or assurance. That alone would have replaced diagnostic pod 4.- Whether to send
required_assuranceis a question for approval-engine, not a local change.
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: whether approval-engine should enforce
required_assurance.
- 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.