# T06 runbook: the live pilot rotation under one admission (founder-run) NK-WP-0056-T06. The executor rotates `svc-authelia`'s LLDAP bind. Your only part *in the run* is the admission: you approve it at aal2 in informed-decision. Everything before that (sections 1 to 4) is one-time go-live of the standing paths. Section 6 is the run. Each step prints a value-free result. Paste it back, and claude-code verifies it read-only and records it. **Commands:** - Admin steps use `K="kubectl --kubeconfig $HOME/.kube/config-railiance01"`. - OpenBao steps need `bao login -method=oidc -path=netkingdom role=platform-admin` (MFA) first. - Always run `git pull --ff-only` in a repo before using it. | # | What | Where | Changes | | --- | --- | --- | --- | | 1 | Owner ingress (T16) | approval-engine, audit-core, rapp | two NetworkPolicies; read-only netcheck | | 2 | Accept the case | State Hub decision | `security-cases/nk-bind-executor-approval.yaml` becomes `accepted` | | 3 | Custody and audit sender | railiance-platform, audit-core | approve CCR-2026-0060, -0061, -0063; fence, policies, roles; audit seed; mint; ESO | | 4 | KeyCape client | net-kingdom | bind the env var and register the client in one KeyCape restart | | 5 | Admission | net-kingdom (claude-code) | `admissions/BEA-YYYY-NNNN.yaml` for the window you choose | | 6 | The run | net-kingdom `bx` | `ready`, `request`, approve at aal2, `run`, `status` | ## 1. Owner ingress (T16) Apply each owner's policy object on its own. Live approval-engine also has `informed-decision-review-ingress`, which is not in its file. Applying the whole file would therefore be a broader change than this step. ```bash K="kubectl --kubeconfig $HOME/.kube/config-railiance01" cd ~/approval-engine && git pull --ff-only python3 -c "import yaml,sys; yaml.safe_dump([d for d in yaml.safe_load_all(open('deploy/networkpolicies.yaml')) if d and d['metadata']['name']=='approval-engine-bounded-executor'][0], sys.stdout)" > /tmp/ae-bx.yaml $K apply --dry-run=server -f /tmp/ae-bx.yaml && $K apply -f /tmp/ae-bx.yaml ``` audit-core: run steps 1 and 2 of audit-core `docs/bounded-executor-receipts.md`, "Founder runbook — nk-bounded-executor sender". Step 1 applies `audit-core-nk-bounded-executor-ingress` the same way. Step 2 is a read-only preflight, which also checks that the live scope ConfigMap carries `nk-bounded-executor`. This is railiance-platform's gate G2 before the audit seed. Then prove the path from an executor-labelled pod. It holds no token and makes no login: ```bash cd ~/rapp-bounded-executor && git pull --ff-only make netcheck KUBECTL="$K" ``` Expected, on the last lines: `"approval_engine_health": 200`, `"audit_core_health": 200` and `"informed_decision_tcp": "ok"`, alongside the existing OpenBao, KeyCape and Authelia columns. ## 2. Accept the service-client case Answer State Hub decision `59d570ef` ("Accept SecurityCase nk-bind-executor-approval"). The case type requires owner and founder acceptance; you are both. claude-code then commits the case as `accepted`, with the acceptance record. ## 3. Custody and audit sender (railiance-platform, audit-core) Follow railiance-platform `docs/credential-lane-designs/nk-bind-executor-go-live.md`, steps 0 to 7, in order: - **Steps 0 to 3:** session; approve CCR-2026-0060, -0061 and -0063 and confirm their bindings; the coding-agent fence; policies and roles. - **Then tell claude-code.** It commits the case as `materialized`: its OpenBao structure now exists. The broker mints only for a materialized, accepted case (gate G1). The case's hash changes with that commit, and step 5 derives `REF` from the committed file. So `git pull --ff-only` in net-kingdom before step 5. - **Step 4:** the audit seed, which is step 3 of audit-core's founder runbook. It needs gate G2 from section 1. - **Step 5:** mint the KeyCape client secret through the broker. No value is printed. - **Step 6:** ESO delivery to `sso`: KeyCape's `keycape-nk-bind-executor-approval-client`, and the executor's `bounded-executor-tokens` key `audit-core`. - **Step 7:** `verify`, which checks metadata, capabilities, logins and hashes only. Section 4 needs step 7's `Ready=True` for `keycape-nk-bind-executor-approval-client` immediately before it runs. ## 4. KeyCape: bind and register in one restart KeyCape runs one replica with strategy Recreate. Changing its env is itself a restart, and registering a confidential client whose secret is not bound makes the next start fail closed. So: pause the rollout, bind, register, then resume. That is one restart, with both changes (key-cape R4). Run it in a quiet minute: in-flight sign-ins are dropped. ```bash cd ~/net-kingdom && git pull --ff-only K="kubectl --kubeconfig $HOME/.kube/config-railiance01" $K -n sso get externalsecret keycape-nk-bind-executor-approval-client -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}'; echo # True C=$(mktemp); python3 sso-mfa/k8s/keycape/keycape-client.py client-from-case security-cases/nk-bind-executor-approval.yaml > "$C" B=$HOME/.local/share/key-cape/verified-bin/e2b5fa1 # check-config binary from the deployed image (T09 runbook) test -x "$B/keycape" || { mkdir -p "$B"; cid=$(docker create forgejo.coulomb.social/coulomb/key-cape@sha256:9e082dca78f65a4af3e92d3de3705b99e49ca9b298d9562ad0d37511c4306f8d); docker cp "$cid:/keycape" "$B/keycape"; docker rm "$cid"; } # keycape-client.py uses its built-in kubeconfig through 127.0.0.1:16444, exactly as in the T09 runbook; # it pins the cluster identity and refuses another cluster. $K -n sso rollout pause deployment/keycape $K -n sso patch deployment keycape --type=json -p '[ {"op":"test","path":"/spec/template/spec/containers/0/name","value":"keycape"}, {"op":"add","path":"/spec/template/spec/containers/0/env/-","value":{"name":"KEYCAPE_NK_BIND_EXECUTOR_APPROVAL_CLIENT_SECRET","valueFrom":{"secretKeyRef":{"name":"keycape-nk-bind-executor-approval-client","key":"client-secret"}}}}]' python3 sso-mfa/k8s/keycape/keycape-client.py inspect --client-file "$C" # -> "deployment_env_bound": true, "change_needed": true; note before.uid and before.resource_version ``` Then put the noted values into the apply line, in place of `UID` and `RV`: ```bash python3 sso-mfa/k8s/keycape/keycape-client.py apply --client-file "$C" \ --case-acceptance "security-cases/nk-bind-executor-approval.yaml (accepted, hub decision 59d570ef)" \ --check-config "$B/keycape" --expected-uid UID --expected-resource-version RV $K -n sso rollout resume deployment/keycape $K -n sso rollout status deployment/keycape --timeout=180s ``` If `apply` refuses, nothing in the registry changed. Undo the env patch before resuming: run `$K -n sso rollout undo deployment/keycape`, then `$K -n sso rollout resume deployment/keycape`, and paste the refusal. claude-code then runs the type's verification. The executor's request Job (section 6) performs the token exchange per scope. The excess-scope refusal, the custody denials and the RBAC can-i denial are read-only checks. ## 5. Admission (claude-code prepares, you check) claude-code runs `bounded-executor/prepare_admission.py` for the window you name (at most 2 h), with `--commit` set to the commit the pinned image was built from. It commits `admissions/BEA-YYYY-NNNN.yaml`. Read it: the plan, checks, revert steps, budget and window are what you will approve. ## 6. The run: four commands and the approval (NK-WP-0058-T04) Run from `~/net-kingdom` after `git pull --ff-only` here and in `~/rapp-bounded-executor`: ```bash BX="python3 tools/bounded-execution-run/bx.py" $BX ready admissions/BEA-YYYY-NNNN.yaml # one platform-admin login; expect "ready": true $BX request # expect "outcome": "presented" # approve the memo in informed-decision: browser profile of admin, aal2, fresh OTP $BX run # one platform-admin login; preflight, open, run, close $BX status # expect "outcome": "completed", "window_closed": true ``` Then tell claude-code. It checks the receipts in `docs/evidence/-/` read-only, confirms the run in audit-core, runs posture feedback and records the evidence. - **Each command prints one value-free line and is safe to repeat.** `run` never starts a second run for an admission, and the state file keeps the approval id, so nothing is copied by hand. - **`ready` and `run` each open one `warden access openbao-platform-admin-login` login** (browser, MFA). Its child is silent and writes the receipts. In `run`, the window closes in the same child, also when the run fails. - **Not ready?** The line names each failed check. `run` refuses to open the window unless the preflight Job passes, so fix the cause and repeat. - **Kill switch, at any moment:** `kubectl --kubeconfig ~/.kube/config-railiance01 -n sso delete job bounded-executor-`. The executor finishes the step in flight, then reverts. Before consume it spends nothing. - **Emergency close:** `$BX close` deletes the write role; it is safe to repeat. | Result | Meaning | | --- | --- | | `completed` | rotated, delivered, restarted, journey passed | | `reverted` | a check failed, and the previous value is back, checked | | `refused` | nothing was spent | | `not_started` | the preflight or the window did not pass; no window stayed open | | `stopped`, `unknown`, `revert_failed` | the alert in the result receipt names the step | ### Identity hygiene - Keep one browser profile per identity, and approve only in the `admin` profile. On 2026-11-04 the approval came from whichever identity was signed in (`platform-root`) and was refused. - Before approving, check the signed-in subject in informed-decision. Sign in again with OTP if the memo doesn't list: access-engine lists memos only within a 12-hour MFA window and opens them within 15 minutes. - The warden logins for `ready` and `run` are `platform-admin` in OpenBao, through KeyCape as `admin`.