Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 5502@bnt-lap001 Assistant-Session: 68b3b02f-5d0f-4e71-8421-796f62e50196
39 KiB
| id | type | title | domain | repo | status | flavor | owner | topic_slug | created | updated | related | state_hub_workstream_id | ||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| NK-WP-0056 | workplan | Bounded executor path: founder admits, executors run | infotech | net-kingdom | proposed | design | claude | netkingdom | 2026-10-30 | 2026-10-31 |
|
5bc74ef8-136e-5157-9a7f-da765dbfc977 |
NK-WP-0056 — Bounded executor path
Intake from the founder's direction on 2026-10-30, before the live LLDAP bind split: top-level decisions and guardrails stay with the founder; execution below that is delegated to automation with limited expense budgets and risk control, and the security architecture is the means. Founder-attended runbooks are a transitional state.
SI-15 (canon/standards/service-identity-privilege_v0.1.md) already names the golden path prepare →
admit → execute → verify → retain and allows "a bounded executor under the admission" at step 3.
Today preparation and verification are strong, admission is prose (a hub decision) and execution falls
to the founder, because no executor exists that has its own identity, its own secret delivery and an
authority ceiling the founder can admit and bound. SI-14 adds that an interactive agent session holds
everything its tools reach, so it cannot be that executor until a session contract exists
(glas-harness GLAS-WP-0018, proposed).
Why the bind split needed the founder (2026-10-30 run, step by step)
| Runbook step | Needed the founder because | Class | Delegable once |
|---|---|---|---|
| Setup: decrypt custody copy | custody is a personal SOPS/age copy | delegable | service credentials live in OpenBao (T04) |
| 1 prepare custody (generate passwords) | writes the personal custody copy | delegable | executor generates into OpenBao (T04) |
| 2 plan, 4 verify, 10 verify | sign in as the human admin |
delegable | read-only executor directory identity (T03) |
3 create svc-* accounts |
needs lldap_admin (LLDAP has no narrower grant that can create users) |
root-class | admitted per run; executor runs it (T03) |
| 5-8 switch one consumer | cluster-admin kubeconfig; secret values; founder sign-in check | delegable | scoped service account (T03), OpenBao/ESO (T04), synthetic MFA journey (T05) |
| 9 change LLDAP's configured super-admin | changes who administers the directory | root-class | admitted per run; executor runs it |
10 change admin's own password; seal ld-admin in the password safe |
the founder's own credential and crisis custody | human-intrinsic | never: stays with the founder |
| Afterwards: restore drill on the next generation | admin operates drills |
delegable | executor identity with the drill's read scope |
Root-class means the founder still admits each run, but no longer types it. The run also showed why
personal custody does not scale: the custody copy had drifted from live (AUTHELIA_KEYCAPE_CLIENT_SECRET)
and step 6 had to be done by a one-off path.
Admission contract
id: NK-WP-0056-T01
status: done
priority: high
assignee: claude-code
state_hub_task_id: "99df0ee2-2c17-55f2-9f3e-4277da52e01c"
Draft a NetKingdom standard for a bounded-execution admission: the founder approves, at aal2, an
exact plan by digest with a validity window, a scope ceiling (MODE @ SCOPE grants, SI terms), a
blast-radius rule (for example one consumer at a time), a stop-and-revert policy tied to named machine
checks, a risk class, and an expense budget (wall time, attempts, compute/egress cost). The executor
consumes the admission once. Map it to SI-15, the Playbook Capability Contract's trust-state
requirements and the execution-attribution receipt (NK-WP-0040). Include a validator and synthetic
fixtures.
Done when: the standard is proposed with a schema, a validator, and the bind-split steps expressed as admissions in its examples.
Done 2026-10-31. Proposed canon/standards/bounded-execution-admission_v0.1.md with
canon/schemas/bounded-execution-admission_v0.1.schema.json, tools/bounded-execution-admission/validate.py
(rules BEA-1..14 on the request, BEA-C1..C7 on a carrier record; 22 tests) and four examples in
examples/bounded-execution-admission/: the bind split's read-only verify (low), create svc-* accounts and switch
LLDAP's super-admin (root-class, high), and one-consumer bind rotation (delegable, medium, the T06 shape). Step 10 is
refused as human-intrinsic. Design choices: the request is immutable and bound by digest; the founder's approval
lives in a carrier (Approval Engine preferred, founder SSH signature transitional), so no second approval system;
trust_requires uses the Playbook Capability Contract's trust states; settle_seconds carries the T08
privacyIDEA-cache finding; recurring admissions are excluded from v0.1. The Approval Engine mapping is a proposal for T02.
Owner agreement on the components
id: NK-WP-0056-T02
status: done
priority: high
assignee: claude-code
needs_human: true
decision_id: 6d1adce9-40e2-472a-92bb-0ae942218ae8
intervention_note: "Founder chooses the executor runtime owner; owner answers are agreements, not agent decisions."
depends_on: [NK-WP-0056-T01]
state_hub_task_id: "7263417b-b227-54f7-9edc-3e7f97cf132f"
Ask the owners which part each carries: approval-engine (the admission as its consumable approval object, plan digest and budget as bound fields, single consume), access-engine (reads the admission as an input claim), secrets-engine and railiance-platform (executor credential delivery through OpenBao), glas-harness (GLAS-WP-0018 session contract, so a harness-launched agent can be an executor), audit-core (receipt holder, as in NK-WP-0040). The founder decides who builds the executor runtime itself.
Done when: each component has a named owner and an answer, and the founder has chosen the executor runtime owner.
2026-10-31, founder decision (hub 6d1adce9): net-kingdom writes the executor runtime (source and tests next
to the admission validator, bind lanes and synthetic journey); a new rapp-* package (working name
rapp-bounded-executor, ADR-0015) builds and deploys it in sso; glas-harness is the later path for agent
executors. Owner requests sent the same day through the hub to approval-engine, access-engine, secrets-engine,
railiance-platform, audit-core, glas-harness and railiance-apps
(requests and answer table). Waiting on their answers.
Done 2026-10-31. All seven owners answered (worker agents in each repository, founder instruction); answers and
the collected founder decisions are in the answer table.
Key results: approval-engine already carries binding, single consume and revoke (aal2 recorded, not yet enforced);
access-engine accepts the pilot without the PDP gate if the receipt says not-evaluated; railiance-platform added
request type workload-kv-write-next-version and drafted CCR-2026-0058; secrets-engine prefers self-rotation, which
NetKingdom proved on the pinned LLDAP; audit-core holds receipts plus a run.started event; railiance-apps: repo-manager
creates the rapp, the image is built in net-kingdom, one Job per admission; glas-harness: agent executors after
GLAS-WP-0018-T04.
2026-10-31, founder decisions after the answers (hub c29c6eda, 3d0c8f86, 19afd15e):
- The executor's OpenBao write role is window-scoped. CCR-2026-0058 was amended, approved and pushed.
- The bind rotates its own password, so
svc-executor-pwis dropped and RPF-WP-0069-T06 is cancelled. - The pilot runs without the policy gate (
not-evaluated); later admissions need the gate or a recorded exception. - Packaging is
rapp-bounded-executor, one Job per admission insso, with the image built in net-kingdom. - Receipts are a started event plus one receipt per run.
- These owner workplans are approved: APPROVAL-WP-0007, FLEX-WP-0040, SECRETS-WP-0017, RPF-WP-0069, AUDIT-WP-0018 and GLAS-WP-0018. glas-harness owns agent executors later.
Runtime work is split into T11 (core runner) and T12 (pilot operations and packaging).
Executor identity and authority ceiling
id: NK-WP-0056-T03
status: done
priority: high
assignee: claude-code
depends_on: [NK-WP-0056-T02]
state_hub_task_id: "592231dd-1981-57a6-a0e8-5d6fc474c6e7"
Declare the executor as a WORKLOAD (or harness-launched AGENT) principal in declarations/ with
grants no wider than the pilot: a Kubernetes service account whose Role names the sso Secrets,
ConfigMaps and Deployments it may patch or restart; a read-only directory bind for plan and verify;
lldap_admin only for root-class steps under an admission. Extend tools/service-identity to check
it.
Done when: the declaration validates and the Role is prepared in source with tests, not applied.
Done 2026-10-31. tools/service-identity/declarations/bounded-executor.yaml declares the executor's credentials
(all planned, all sharing executor_subject: service:nk-bind-executor): the Kubernetes ServiceAccount, its OpenBao
role, svc-executor-pw (lldap_password_manager: LLDAP cannot narrow to one account, the admission narrows it),
the read-only bind svc-executor-read, and svc-executor-admin (lldap_admin, root-class only, per-run activation
open). tools/service-identity gained convention executor with rule SI-15 (no person, not interactive, every grant
APPROVED, no SOVEREIGN/GOVERN/CONSTRUCT) and --admissions, which checks that an admission's ceiling lies inside
the executor's declared grants: the pilot (BEA-2026-0001) is covered; the root-class and read-only examples are
correctly reported as needing grants the pilot does not hold. sso-mfa/k8s/bounded-executor/rbac.yaml (not applied):
ServiceAccount without automount, a Role with get/watch/patch on the authelia Deployment and get/patch on the
authelia ExternalSecret only (no read of credential objects: the pilot's delivery check became "ExternalSecret Ready
and refreshed"), and its RoleBinding; test_rbac.py ties Role = declaration = pilot ceiling. 44 identity tests,
5 RBAC tests.
Service credentials into OpenBao
id: NK-WP-0056-T04
status: done
priority: high
assignee: claude-code
needs_human: true
intervention_note: "Founder runs the custody reconciliation and approves moving service binds out of personal custody."
depends_on: [NK-WP-0056-T02]
state_hub_task_id: "fd439c0c-9b5e-562c-9bfb-48dd0fb2c570"
With railiance-platform, move the LLDAP service binds (LLDAP_SVC_*_PASS) and the Authelia secrets
from the personal custody copy into OpenBao paths delivered by ESO, so that neither a person nor an
agent handles the values in a rotation. First reconcile the stale custody
AUTHELIA_KEYCAPE_CLIENT_SECRET with the live value (founder-run, value-free comparison as in the
2026-10-30 evidence). Root-class material (ld-admin, unseal shares) stays in founder custody.
Done when: the four service binds are ESO-delivered from OpenBao, and the custody copy no longer needs to be decrypted for a bind rotation.
2026-10-30: reconciliation tool prepared: sso-mfa/bootstrap/reconcile-keycape-client-secret.py
(value-free; accepts the live KeyCape value only if it matches Authelia's live hash, writes custody through
rotation-env.py, no cluster writes; tests/test_reconcile_keycape_client_secret.py). Founder ran it
2026-10-30: live KeyCape value matches Authelia's hash; custody did not match before, was updated and matches
after (value-free output); custody re-sealed and shredded. The reconciliation step is done; the ESO move remains.
2026-10-30, pilot svc-provisioner prepared (source only, nothing applied): railiance-platform
CCR-2026-0052 (proposed) and policy net-kingdom-svc-provisioner-bind-eso; sso-mfa/k8s/identity-provisioner/bind-eso.yaml
(ServiceAccount, SecretStore, ExternalSecret with creationPolicy: Merge into the existing Secret, password only);
tools/service-binds/bind-lane.py setup|verify (approved sha256-bound CCR, attended platform-admin, custody must equal
live, coding-agent fence, KV CAS=0, value-free receipts); tests/test_service_bind_lane.py (7). Next: owner approval of
the CCR, then the founder-run setup, ESO apply and verify. Authelia, KeyCape and privacyIDEA follow one at a time.
2026-10-30, pilot live and verified: CCR-2026-0052 approved; founder-run setup, ESO apply and verify passed;
svc-provisioner now binds with the ESO-delivered value (evidence).
Finding: ESO targets need template.metadata: {} or the admission guard refuses the Secret update. Remaining:
svc-authelia, svc-keycape, svc-privacyidea.
2026-10-31, svc-authelia lane prepared (source only, nothing applied): railiance-platform CCR-2026-0053
(proposed) and policy net-kingdom-svc-authelia-bind-eso; sso-mfa/k8s/authelia/bind-eso.yaml (Merge of
ldap_password only into authelia-secrets, which also holds the session, storage and OIDC keys). bind-lane.py
gains per-lane Secret keys: Authelia keeps the bind user in authelia-config, so setup refuses unless the live
configuration binds uid=svc-authelia, and it decodes only the bind key. Authelia reads the file at start-up
only, so the identical projection needs no restart; a later rotation does. 12 lane tests. Next: owner approval
and binding confirmation, then the founder-run setup, ESO apply, verify and an Authelia restart with a login.
2026-10-31, svc-authelia live and verified: CCR-2026-0053 approved and binding confirmed; founder-run setup,
ESO apply (Ready first time), verify and Authelia restart passed, and an admin portal sign-in with OTP worked
(evidence). Two of four binds are ESO-delivered.
Remaining: svc-keycape (bind inside sso/keycape-config), svc-privacyidea (resolver password stored in the
privacyIDEA database, not a Secret key).
2026-10-31, svc-keycape design proposal (for founder decision): KeyCape's server reads lldap.bindPW only
from config.yaml inside sso/keycape-config (internal/config/config.go); unlike client secrets (secretRef: env:... via secretKeyRef) it has no environment indirection. ESO Merge cannot own one field inside a YAML
document, and rendering all of config.yaml from OpenBao would move every KeyCape value at once. Proposed:
- key-cape (owner change, new KEY-WP):
config.LoadtakesKEYCAPE_LLDAP_BIND_PW(the namelldap-exportalready uses). Env set andbindPWempty: use env. Both set and equal: use it and log thatbindPWis redundant. Both set and different: refuse to start. This allows a staged switch with a clean rollback. Image through the owner pipeline;check-configreports which source is used, without the value. - NetKingdom: CCR-2026-0054 and a
svc-keycapelane; ESO creates a new dedicated Secretkeycape-lldap-bind(creationPolicy: Owner, keypassword), so no Secret has two writers. Setup compares custody with the livebindPWinkeycape-config. - Deployment: add the env from
secretKeyRef keycape-lldap-bind/passwordwith the new image; values are equal, so behaviour is unchanged. Verify, restart, admin login. - Remove
bindPWfromkeycape-config(directory-bind-rollout.py --operation clear-password, guarded on uid/resourceVersion);create-secrets.pystops writing it. Rollback at any step: re-add the value or drop
2026-10-31, founder decision: environment override, implemented by claude-code in key-cape. Done in source:
key-cape e2b5fa1 (KEY-WP-0046-T01; image main-e2b5fa1 = sha256:9e082dca…6f8d, built by the Forgejo workflow;
its acceptance job also fails on the previous commit a1ab8ff). NetKingdom: CCR-2026-0054 (proposed),
sso-mfa/k8s/keycape/bind-eso.yaml (dedicated Secret keycape-lldap-bind, creationPolicy: Owner),
bind-lane.py svc-keycape (setup reads bindPW from keycape-config and requires bindDN = svc-keycape;
verify reads the dedicated Secret and requires it to equal bindPW while present), directory-bind-rollout.py --operation clear-password (only with the service bind and the delivered env; switch restores), and
deployment.yaml pinned to the new image with the KEYCAPE_LLDAP_BIND_PW env. The new image also carries
key-cape 67a0869..e2c5a94 (online token status, inactive while tokenStatus is unset) and identity-facts.
Founder order: approve, setup, ESO apply, verify, deployment apply (image + env), login, clear-password, restart, login.
2026-10-31, svc-keycape live and verified: CCR-2026-0054 approved and binding confirmed; founder-run setup, ESO
apply, verify, deployment rollout (image main-e2b5fa1 + env; both sources equal), admin login, clear-password,
restart, env-only verify and a second admin login all passed
(evidence). Three of four binds are ESO-delivered;
keycape-config no longer holds the bind password. Remaining: svc-privacyidea (resolver password stored in the
privacyIDEA database, not a Secret key: needs its own design).
2026-10-31, svc-privacyidea design proposal (for founder decision): privacyIDEA keeps the resolver BINDPW
encrypted in its own database, and only the pi-admin API can change it (realm_bootstrap.py), so neither ESO nor
an unattended job can deliver it without pi-admin's password standing in the cluster (rejected: SI rules).
Proposed: OpenBao custody with attended write-through, following CCR-2026-0028 (attended-host-key-delivery):
- bind-lane
svc-privacyidea setup: the custody value must sign in to LLDAP assvc-privacyidea(LLDAP is the authority for the current password; the working resolver proves privacyIDEA holds the same one), then KVdirectory-binds/svc-privacyideaCAS=0, coding agents fenced. No Kubernetes role, no ESO,runtime_access: false. realm_bootstrap.py --switch-bind --from-openbao: reads the KV value in memory under the attendedplatform-adminsession and writes onlyBINDPWwith the attendedpi-admin; the existing preservation checks apply, and the write must be proven by an MFA login through the resolver (--verify-user).- Rotation becomes: change the password in LLDAP, write the next KV version, run step 2, verify with an OTP login. No custody decryption; the founder runs it and proves it with an MFA login.
- CCR-2026-0055 needs a request type for attended delivery into a service; railiance-platform's
credential-change.pygains it, modelled onattended-host-key-delivery. The done criterion of T04 then reads: three binds ESO-delivered,svc-privacyideaheld in OpenBao with attended write-through. Alternative: keep it in founder custody as a recorded exception until privacyIDEA can read the resolver password from a file or environment.
2026-10-31, founder decision: OpenBao custody with attended write-through. Prepared in source:
railiance-platform 2d4a383 adds request type attended-service-credential-delivery (scripts/attended_service_delivery.py,
4 tests; no policy, auth role, ESO or runtime frontdoor allowed) and CCR-2026-0055 (proposed). bind-lane.py svc-privacyidea: setup proves the custody value current by an LLDAP sign-in as svc-privacyidea, fences coding
agents and writes KV v1 (CAS=0); verify proves the KV value current the same way, the fence present and no workload
role. realm_bootstrap.py --rotate-bind --from-openbao rewrites only BINDPW on the service bind from OpenBao
(attended platform-admin) and requires --verify-user. Correction: pi-admin signs in with a password; the MFA
proof is the --verify-user login through the resolver.
Done 2026-10-31. svc-privacyidea live: CCR-2026-0055 approved; founder-run setup and verify passed (KV v1, value
proven current by LLDAP sign-in, no workload role) and realm_bootstrap.py --rotate-bind --from-openbao wrote the
resolver from OpenBao with an admin MFA proof (evidence;
a first apply wrote the same value but its check got a code that was not admin's; both MFA prompts now name the user).
All four service binds are held in OpenBao: svc-provisioner, svc-authelia and svc-keycape ESO-delivered,
svc-privacyidea by attended write-through (founder decision 2026-10-31 amends the done criterion accordingly).
No bind rotation needs the custody copy decrypted. The custody copy keeps the current values as a fallback.
the env.
Synthetic verification instead of a founder sign-in
id: NK-WP-0056-T05
status: done
priority: medium
assignee: claude-code
depends_on: [NK-WP-0056-T04]
state_hub_task_id: "bb511d1e-6d5d-56d1-9030-2e0d94006a16"
A synthetic test user with an OTP seed held in OpenBao, and a journey that signs in through Authelia and KeyCape with MFA and resolves through privacyIDEA. Reuse the identity-provider journey acceptance. The executor runs it as the check between consumers and reverts on failure.
2026-10-31, founder decision: split into T05 (runner), T08 (synthetic stack on real pinned images),
T09 (synthetic user and seed in OpenBao) and T10 (unattended live run). T05 is now the runner only:
a stdlib journey that drives KeyCape /authorize (PKCE, acr_values=urn:netkingdom:aal2), Authelia
/api/firstfactor and its OIDC authorization, the KeyCape callback and OTP form (KeyCape validates the
code through privacyIDEA), and the KeyCape token exchange, then verifies the ID token's signature and
assurance (aal2, otp). Secrets come from files or descriptors, never argv; the receipt is value-free
and names the first failed step. All three consumer binds are on the path (svc-authelia at first
factor, svc-keycape at the callback, svc-privacyidea at the OTP check); svc-provisioner is not.
Done when: the runner and its tests (in-process fakes per bind, fail-closed at the named step, no secret in the receipt) pass in the identity-provider journey acceptance workflow.
2026-10-31, implemented: sso-mfa/k8s/synthetic-journey/journey.py (stdlib; RS256 verified against
KeyCape's JWKS without third-party packages; host_map for an in-cluster executor) with 26 tests in
test_journey.py. Each broken bind fails closed at its step (svc-authelia → authelia_first_factor,
svc-keycape → keycape_callback, svc-privacyidea → keycape_otp), and a sign-in without an OTP
challenge, an aal1 token or a tampered signature fails. Green locally and in the workflow's pinned
python:3.12-slim image with the network off; the job is added to identity-journeys.yaml.
Done: the workflow's provider job passed on 0b0c6c9 (run 719). Next: T08.
Synthetic identity stack on pinned images
id: NK-WP-0056-T08
status: done
priority: medium
assignee: claude-code
depends_on: [NK-WP-0056-T05]
state_hub_task_id: "02611880-9ea4-5c9d-a5ab-b50847608f92"
A throwaway stack on the pinned production images (LLDAP, Authelia, privacyIDEA, KeyCape), published on
127.0.0.1 only, with generated values and production-shaped binds (svc-authelia, svc-keycape,
svc-privacyidea), a synthetic user with a TOTP token, and the T05 runner against it. Extends the
bind-split-rehearsal.py pattern.
Done when: the journey passes on the healthy stack and fails closed, at the expected step, for a deliberately broken bind of each of the three consumers, with value-free receipts.
Done 2026-10-31 (evidence):
sso-mfa/k8s/synthetic-journey/stack.py + pi_fixture.py on the four pinned images. Healthy passes
(aal2, otp, signature verified); svc-authelia fails closed at authelia_first_factor, svc-keycape
at keycape_token (KeyCape binds at token time, not at the callback: mapping corrected), and
svc-privacyidea at keycape_otp, but only after privacyIDEA's resolver cache expired (live
CACHE_TIMEOUT 120 s; the first journey after the break passed). Each scenario passes again after the
password is restored. Also: privacyIDEA refuses a reused TOTP time step, so the runner gained
min_time_step. Consequence for T06/T10: the between-consumers check for svc-privacyidea must wait out
or clear the resolver cache, and the attended --rotate-bind MFA proof has the same blind spot.
Synthetic user and seed in OpenBao
id: NK-WP-0056-T09
status: done
priority: medium
assignee: claude-code
needs_human: true
decision_id: 91323969-43fa-460f-9e0a-2b8cd44bdc9a
intervention_note: "Founder runs the live setup (LLDAP user, privacyIDEA token, OpenBao KV) under a CCR."
depends_on: [NK-WP-0056-T08]
state_hub_task_id: "1f788455-f79e-5630-bede-ec3218f6969d"
An unprivileged live user (for example syn-journey, no groups), its own privacyIDEA TOTP token, and
password and seed held in OpenBao with coding agents fenced, a dedicated KeyCape public client for the
journey, prepared as bind-lane.py-style setup/verify helpers under a CCR. Founder-run.
Done when: setup and verify pass live and one founder-started journey run passes with a receipt.
2026-10-31, prepared for founder decision (sso-mfa/k8s/synthetic-journey/T09-RUNBOOK.md):
CCR-2026-0057 (railiance-platform, proposed; attended-service-credential-delivery, KV
platform/workloads/net-kingdom/synthetic-journey/syn-journey fields PASSWORD, TOTP_SEED, no workload
reader), SecurityCase security-cases/nk-synthetic-journey.yaml (public PKCE client, MFA pinned), and
lane.py setup/verify/teardown with 8 fake-backed tests. Identity route: attended owner trial by admin
(the route the NK-WP-0049 retirement names for test identities), not User Engine onboarding; name
syn-journey under a proposed syn-<check> convention (SI §5), declared SERVICE, status planned.
Open, recorded: KeyCape marks every token principal_type: human; KeyCape has no per-client group gate.
Waiting on: CCR approval, case acceptance, founder run of the runbook.
Done 2026-10-31 (evidence): CCR-2026-0057
approved; case accepted; client registered; founder-run lane.py setup and verify passed. The live journey
reached aal2 (pwd+otp, signature verified) against kc.coulomb.social. Two lane fixes on the way: privacyIDEA
tokens are looked up by serial, and enrollment uses a privacyIDEA-generated key, because pi-admin's action=*
enables totp_force_server_generate, which silently replaced the supplied key (reproduced on 3.12.2). One
partial run was removed with lane.py teardown. Next: T10 needs the executor identity (T03) and a
workload-kv-read request for it.
Unattended live journey
id: NK-WP-0056-T10
status: todo
priority: medium
assignee: claude-code
depends_on: [NK-WP-0056-T03, NK-WP-0056-T09]
state_hub_task_id: "a68252da-46f7-52eb-81d3-942ddd9ecb9b"
The executor identity (T03) reads the journey secrets from OpenBao itself and runs the journey as the check between consumers; failure triggers the revert path.
Done when: the journey runs unattended against live with no person involved, and fails closed against a deliberately broken bind on a synthetic directory (T08).
2026-10-31, prerequisites:
- Done: the
ssoingress rules for executor pods were applied by the founder and read back live. - Done: railiance-platform CCR-2026-0059 drafted (
c9b68ea,proposed). It is the journey read with its own role,net-kingdom-bind-executor-syn-journey-read. It widens CCR-2026-0057; standing reader (founder decision). - Two executor gaps found while drafting it:
- Two roles. The executor logs in to OpenBao once (
openbao.k8s_role) and reads the bind and the journey with that token. railiance-platform keeps one role per lane and never combines policies, so the executor needs a second login for the journey read: ajourney.k8s_roleand its ownOpenBaoclient, revoked like the first. - Audience. CCR-2026-0058 and CCR-2026-0059 bind audience
openbao. The rapp Job projects only the default API-audience token, so it also needs anopenbao-audience projected token, andopenbao.jwt_filemust point to it.
- Two roles. The executor logs in to OpenBao once (
- Both need an executor change, a new image and a new pin.
2026-10-31, gaps fixed (evidence):
1cdc18f: one OpenBao client per lane, and one role for both lanes is refused.- Every login is revoked at exit, also when setup fails.
- A third gap was found and fixed: the 15m token is shorter than the 20m budget, so clients now log in again before the lease ends, re-reading the rotating JWT.
- Synthetic pilot with per-lane tokens: completed and reverted,
all_met. Examples re-pinned. - rapp
368fee4:- a second projected token with audience
openbao; - the config names both CCR roles;
- the renderer refuses other roles and the API token;
- tests tie the roles to the platform CCRs.
- a second projected token with audience
- The founder chose a standing journey reader. CCR-2026-0059 records it (railiance-platform
98a8b08).
Left:
- the image for the new commit, and its pin in the rapp;
- founder approval of CCR-2026-0059;
make deployof the standing objects, thenconfirm-binding;- applying the policy and roles (platform-admin);
- the approval-engine and audit-core replies and the token Secret.
Executor runtime: core runner
id: NK-WP-0056-T11
status: done
priority: high
assignee: claude-code
depends_on: [NK-WP-0056-T01, NK-WP-0056-T03]
state_hub_task_id: "24121a14-92d8-577c-a651-992afcbdb4cc"
The executor itself, in net-kingdom (founder decision 6d1adce9):
- consume one admission through the approval-engine carrier (independent GET, digest check, aal2 check, consume once);
- refuse outside the window or ceiling;
- run the pinned steps one consumer at a time, with checks after each mutating step, settle times, revert on
failure, the budget (wall time, attempts) and an
on_unknown: stop; - emit
execution.run.startedand the receipt (PDPnot-evaluatedfor the pilot).
Tested against in-process fakes for the carrier, operations and audit sink.
Done when: the runner passes its tests, including revert on a failed check, budget exhaustion, a stale or reused approval, and a step outside the ceiling. Every refusal must happen before any effect.
Done 2026-10-31. bounded-executor/executor.py with 23 tests (bounded-executor/tests/test_executor.py; fakes
for carrier, operations, checks, trust, audit, clocks), modelled on approval-engine's real object and consume
response. Refusals before any effect cover:
- an invalid admission;
- another executor;
- source drift;
- unsatisfied trust;
- an unknown operation;
- outside the window, or a window shorter than the budget;
- a binding digest or actor mismatch;
- a non-human, aal1 or unlisted approver;
- an already-consumed approval.
Consume:
- an unclear consume is read back and never repeated;
- an idempotent response is refused;
- with no audit custody, no run.
Failure handling:
- a failed check reverts in reverse order, each revert proving the restored state with its own checks;
- a clean revert retries within
attempts; - a failed revert, or a revert check that fails, stops and alerts;
- an unknown outcome stops without reverting;
- budget exhaustion and the kill switch revert;
- settle times are waited and counted;
- a receipt-custody failure is reported.
Receipts carry no operation values.
Standard and validator change: BEA-9 now applies to revert steps, which name their own checks; examples regenerated.
Executor runtime: pilot operations and packaging
id: NK-WP-0056-T12
status: done
priority: high
assignee: claude-code
depends_on: [NK-WP-0056-T11, NK-WP-0056-T08]
state_hub_task_id: "815cc63a-a2a0-5b8f-83ae-2b80e37ea8f5"
The pilot's operations:
- OpenBao read and CAS write of the next version;
- LLDAP self-rotation of the bind;
- ExternalSecret force-sync and its status check;
- Deployment restart and rollout check;
- the synthetic journey.
Run them on the T08 synthetic stack, including a revert. Add the Containerfile and image workflow in
net-kingdom. The rapp-bounded-executor package is created with rmgr rapp wrap, and the founder creates the
Forgejo remote. It runs one Job per admission in sso with an egress-only NetworkPolicy.
Done when: a synthetic rotation completes and a deliberately failing one reverts, with receipts.
Done 2026-10-31 (evidence).
bounded-executor/operations.py:
- the OpenBao KV v2 CAS client;
- LLDAP self-rotation, the same operation forward and in revert;
- the Kubernetes ExternalSecret force-sync and status check, and the rollout restart that raises
RolloutFailed; - the pilot checks.
bounded-executor/synthetic_pilot.py: rotation completed, and a one-off wrong delivery reverted in declared order
with the earlier value restored. 29 tests.
Findings:
- CCR-2026-0058's
required_parametersrefused reads. Corrected in railiance-platform; needs re-approval. - Revert steps now run in declared order (BEA-10).
- Definite failures revert; unknown outcomes stop.
- Receipts carry error types.
The image part moved to T13 (split agreed 2026-10-31).
Executor runtime: packaging
id: NK-WP-0056-T13
status: done
priority: high
assignee: claude-code
depends_on: [NK-WP-0056-T12]
state_hub_task_id: "7355e4a2-95cc-5860-a0b0-52978b8501e0"
The CLI entry point for one admission:
- reads the admission, the approval id and the configuration;
- uses Kubernetes auth to OpenBao;
- uses the in-cluster Kubernetes and audit sender;
- ships the pinned
lldap_set_passwordin the image.
Also: the Containerfile and the Forgejo image workflow in net-kingdom, and the rapp-bounded-executor package
(rmgr rapp wrap, founder creates the Forgejo remote). The package holds one Job per admission in sso with the
T03 ServiceAccount, Role and an egress-only NetworkPolicy.
Done when: the image builds in the workflow, the rapp renders and passes server-dry-run against a synthetic
manifest check, and nothing is applied.
2026-10-31, in progress. Done in net-kingdom:
bounded-executor/cli.py(exit codes 0/3/4/5/6),clients.py(approval-engine GET/consume, audit-corePOST /v1/eventswithIdempotency-Key);- events now carry
occurred_at, which audit-core requires; Containerfile: pinned Python and LLDAP stages, hash-locked requirements, UID 10001; builds locally;.forgejo/workflows/bounded-executor-image.yaml, and the executor tests are inidentity-journeys.yaml;- the pilot example pins the executed files (
executor.py,operations.py,journey.py). 35 tests.
2026-10-31, rapp reshaped. ~/rapp-bounded-executor is at forgejo-remote:coulomb/rapp-bounded-executor (4517aaf; rebased onto the Forgejo stub, so local hashes changed):
- Standing objects in
sso:manifests/rbac.yaml, a byte-identical copy ofsso-mfa/k8s/bounded-executor/rbac.yaml. A test fails on drift;make ceiling-synccopies it.manifests/networkpolicy.yaml: no ingress. Egress only to DNS, the API server (node/32, 6443), LLDAP 17170, KeyCape 8080, Authelia 9091, OpenBao 8200, approval-engine 8080 and audit-core 8080.- No Namespace, Deployment, Service, probes or database.
templates/job.yamlandtools/render_job.pyrender one ConfigMap (admission + config) and one Job per admission, namedbounded-executor-<id>:make runuseskubectl create, so a second run of the same admission fails;backoffLimit: 0;activeDeadlineSeconds= min(budget + 300 s, time left in the window);- projected SA token, no automount;
- approval-engine and audit-core bearers come from an ESO Secret
bounded-executor-tokens; - the renderer refuses a placeholder digest, a window that is not open, another executor, secret-like config keys and token paths that differ from the mounts.
declarations/rapp.yamlnames no database.rmgr rapp validatepasses. 12 tests.docs/runtime-contract.mdanddocs/platform-handoffs.mdlist what must exist before T06.
Server dry run with the limited ops-mason-builder identity:
- ServiceAccount and NetworkPolicy: admitted.
- Role, RoleBinding and the per-admission ConfigMap and Job:
Forbiddenfor that identity, so the founder runs them. - The SSH path (
kubectlon railiance01) can't read the k3s kubeconfig. The Makefile therefore takesKUBECTL=....
Left:
- DONE 2026-10-31, founder: full server dry run with the admin kubeconfig. The ServiceAccount, Role, RoleBinding and NetworkPolicy were applied server-side as a dry run, and the synthetic BEA-2026-0001 ConfigMap and Job were created as a dry run. Nothing was persisted.
Done 2026-10-31.
- The image workflow for
ba9d266succeeded, and the digestsha256:8f40f998738789aec21898fe2a36fda56f33c6ae2bb5749f88583d4d70f070c2is pinned in the rapp (e9a943b). check-oci-image.shfound the manifest, andrmgr rapp validatepasses. 13 rapp tests.rmgr rapp pin-imageonly scansmanifests/, so the pin went in by hand. A test keeps the Makefile, Job template and declaration pins equal.- Nothing was applied.
Follow-up outside the done criteria:
- DONE 2026-10-31: the founder created the Forgejo repo with a README stub. The local commits were rebased onto it
(the local README kept) and pushed; HEAD is
e779a22.
Follow-up 2026-10-31 for T10/T06 reachability:
netpol-sso.yamlgainedallow-bounded-executor-to-{lldap-api,keycape,authelia}, which admit only executor pods, on 17170, 8080 and 9091; never LDAP 3890. Tests:test_netpol.py, plus a rapp test that its in-namespace egress equals these admissions. Prepared in source, not applied.- Live reads (limited identity):
- OpenBao's chart policy admits every namespace on 8200, so nothing is needed there.
- approval-engine is deployed but admits only secrets-engine and namespaces labelled
railiance.io/approval-engine-client=true, and none are. - audit-core has no ingress from
sso. - Both owners were asked for pod-scoped rules; see the rapp's
docs/platform-handoffs.md.
Pilot: unattended bind rotation under one admission
id: NK-WP-0056-T06
status: todo
priority: high
assignee: claude-code
needs_human: true
intervention_note: "Founder signs the admission for the single live pilot rotation."
depends_on: [NK-WP-0056-T01, NK-WP-0056-T03, NK-WP-0056-T04, NK-WP-0056-T05, NK-WP-0056-T10, NK-WP-0056-T13]
state_hub_task_id: "6c3939f8-23a9-5f70-a3b1-94f4b291706b"
Rotate svc-authelia's password with no person in the loop beyond the admission: generate into
OpenBao, set in LLDAP, let ESO deliver, restart, run the synthetic journey, revert on failure, emit a
receipt to audit-core. Rehearse on a synthetic directory first, then one live run under a
founder-signed admission.
Done when: the live rotation completes or reverts on its own, within its budget, with a verified receipt, and the founder's only action was the admission.
Budget and risk enforcement
id: NK-WP-0056-T07
status: todo
priority: medium
assignee: claude-code
depends_on: [NK-WP-0056-T06]
state_hub_task_id: "a87c5edb-7604-5eba-9261-b1ed2ed16655"
Enforce the admission's budget and blast radius at run time (attempt and time limits, a kill switch the founder holds, refusal when a check is missing), and feed outcomes and drift into posture feedback (NK-WP-0031) so that recurring admissions can be reviewed in aggregate rather than one by one.
Done when: a run that exceeds its budget or scope stops and reverts in a synthetic test, and the pilot's receipts appear in a posture-feedback report.