NK-WP-0058-T04 done: bx, four founder commands plus the approval
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s

bx ready / request / run / status (close for emergencies): inputs in a state file, receipts numbered
by the tool, every command safe to repeat and one value-free line. OpenBao steps run as silent
children of the warden platform-admin lane (executor-lanes.py --quiet); run closes the window in the
same child, also on error. Runbook sections 5-6 replaced; pilot-run.sh deleted. 134 tests.

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
This commit is contained in:
tegwick 2026-11-05 23:45:02 +01:00
parent a29d92ecda
commit 646ced3f9e
10 changed files with 577 additions and 120 deletions

View file

@ -218,7 +218,7 @@ bounded-execution-ready: ## Readiness before a window, read-only (NK-WP-0058-T03
bounded-executor-test: ## Bounded executor RBAC and declaration tests; the pilot admission inside its declared grants (NK-WP-0056-T03)
python3 -m pytest sso-mfa/k8s/bounded-executor bounded-executor/tests tests/test_executor_lanes.py tests/test_write_role_audit.py \
tools/bounded-execution-readiness/tests
tools/bounded-execution-readiness/tests tools/bounded-execution-run/tests
python3 tools/service-identity/validate.py tools/service-identity/declarations/bounded-executor.yaml \
--admissions examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml

View file

@ -373,7 +373,7 @@
| task | NK-WP-0058-T01 | done | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T02 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T03 | done | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T04 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T04 | done | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T05 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T06 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |
| task | NK-WP-0058-T07 | todo | — | workplans/NK-WP-0058-bounded-execution-operability.md |

View file

@ -400,9 +400,9 @@ Argo pin with live) cover the gap.
- **Owner request to ops-warden:** strip `OPENBAO_TOKEN` too, 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.sh` keeps that session open across a whole run.
- **Proposal:** run `executor-lanes.py window-open`, `window-close` and the T03 readiness check as warden lanes. They
already write their own receipts and print one line. `pilot-run.sh` then opens one lane per command and needs no
shell login.
- **Done (T04, 2026-11-05):** `tools/bounded-execution-run/bx.py` runs the window commands and the OpenBao status
as silent children of the warden lane (`executor-lanes.py --quiet`). `ready` and `run` each need one login,
and no shell login is left open. `pilot-run.sh` is deleted.
### F1: the founder's steps, 2026-11-04/05
@ -424,7 +424,8 @@ Argo pin with live) cover the gap.
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.
numbers come from the tool. **Done (T04):** `bx ready`, `bx request`, the approval, `bx run`, `bx status`. Sections 5
and 6 of the T06 runbook now describe only these.
### F2: the browser identity

View file

@ -18,8 +18,8 @@ Each step prints a value-free result. Paste it back, and claude-code verifies it
| 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-2026-0002.yaml` for the window you choose |
| 6 | The run | rapp, net-kingdom | request, approve at aal2, open the window, run, close the window, verify |
| 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)
@ -115,84 +115,48 @@ per scope. The excess-scope refusal, the custody denials and the RBAC can-i deni
## 5. Admission (claude-code prepares, you check)
claude-code runs, for the window you name (at most 2 h):
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
python3 bounded-executor/prepare_admission.py --id BEA-2026-0002 --commit 9bd7bf0 \
--not-before 2026-11-05T09:00:00Z --hours 2 --out admissions/BEA-2026-0002.yaml
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
```
It commits the file. The pins are the files inside the pinned image (`sha256:2926d7d9…`, built from `9bd7bf0`).
Read it: the plan, the checks, the revert steps, the budget and the window are what you will approve.
Then tell claude-code. It checks the receipts in `docs/evidence/<date>-<id>/` read-only, confirms the run in
audit-core, runs posture feedback and records the evidence.
## 6. The run
- **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-<id>`. 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.
```bash
K="kubectl --kubeconfig $HOME/.kube/config-railiance01"
cd ~/rapp-bounded-executor && git pull --ff-only && cd ~/net-kingdom && git pull --ff-only
A=~/net-kingdom/admissions/BEA-2026-0002.yaml
CFG=$(mktemp --suffix=.json)
python3 -c "import json; c=json.load(open('$HOME/rapp-bounded-executor/examples/config.pilot.json')); c['trust_satisfied']=['runtime_secret_trust','runtime_identity_trust']; json.dump(c, open('$CFG','w'), indent=2)"
# trust_satisfied is your attestation, recorded in the run's result: OpenBao custody and KeyCape identity are healthy
```
| 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 |
**6a. Request.** The executor files the approval as `service:nk-bind-executor` and presents it in informed-decision:
### Identity hygiene
```bash
cd ~/rapp-bounded-executor && make request ADMISSION=$A CONFIG=$CFG KUBECTL="$K"
# -> {"outcome": "presented", "approval_id": "...", ...}; note the approval_id
```
**6b. Approve.** Open the memo in informed-decision and approve it. You must be signed in at aal2 (OTP in this login).
**6c. Open the window.** This creates CCR-2026-0058's write role for this admission only:
```bash
cd ~/net-kingdom && bao login -method=oidc -path=netkingdom role=platform-admin
W=$(sha256sum ~/railiance-platform/credential-change-requests/CCR-2026-0058-*.yaml | cut -d' ' -f1)
J=$(sha256sum ~/railiance-platform/credential-change-requests/CCR-2026-0059-*.yaml | cut -d' ' -f1)
E=docs/evidence/$(date +%F)-nk56-t06-pilot; mkdir -p "$E"
python3 tools/service-binds/executor-lanes.py window-open --admission $A --write-ccr-sha256 "$W" \
--journey-ccr-sha256 "$J" --receipt "$E/01-window-open.json" # {"status": "window_opened", ...}
```
**6d. Run.** Put the approval id from 6a in place of `APPROVAL`:
```bash
cd ~/rapp-bounded-executor && make run ADMISSION=$A APPROVAL_ID=APPROVAL CONFIG=$CFG KUBECTL="$K"
make run-status ADMISSION_ID=BEA-2026-0002 KUBECTL="$K"
$K -n sso logs job/bounded-executor-bea-2026-0002 > ~/net-kingdom/$E/02-result.json
```
The last line of the log is the value-free result:
| Exit | Result | Meaning |
| --- | --- | --- |
| 0 | `completed` | rotated, delivered, restarted, journey passed |
| 3 | `reverted` | a check failed, and the previous value is back, checked |
| 4 | `refused` | nothing was spent |
| 5 | `stopped`, `unknown` or `revert_failed` | the alert names the step |
| 6 | consumed but not in custody | — |
**Kill switch**, at any moment of the run:
```bash
$K -n sso delete job bounded-executor-bea-2026-0002
```
The executor finishes the step in flight, then reverts what it changed (grace period = the budget). Before consume
it refuses and spends nothing.
**6e. Close the window** right after the result, or at window end at the latest:
```bash
cd ~/net-kingdom && python3 tools/service-binds/executor-lanes.py window-close --admission $A \
--receipt "$E/03-window-close.json" # {"status": "window_closed", ...}
```
**6f. Tell claude-code.** It does the rest read-only:
- it confirms the run's started event and receipt in audit-core (operator read by `correlation_id`);
- it validates the pair against receipt schema v0.1;
- it runs the posture-feedback report over the receipts (T07);
- it records the evidence and closes T06.
- 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`.

View file

@ -1,36 +0,0 @@
#!/usr/bin/env bash
# NK-WP-0056-T06 founder-run pilot: open the window, run one admission, ALWAYS close the window.
# sso-mfa/k8s/bounded-executor/pilot-run.sh ADMISSION_FILE APPROVAL_ID EVIDENCE_DIR
# Needs an attended `bao login -method=oidc -path=netkingdom role=platform-admin` in this shell first, and the
# approval already approved in informed-decision by the admission's approver. Prints value-free results only.
# The close runs from an EXIT trap, so a failed or interrupted run never leaves the write role open
# (2026-11-05: a diagnostic window stayed open overnight).
set -euo pipefail
A=${1:?admission file}; APPROVAL=${2:?approval id}; E=${3:?evidence dir}
NK=$HOME/net-kingdom; RAPP=$HOME/rapp-bounded-executor; PLAT=$HOME/railiance-platform
K="kubectl --kubeconfig $HOME/.kube/config-railiance01"
export BAO_ADDR=${BAO_ADDR:-http://127.0.0.1:18200} VAULT_ADDR=${VAULT_ADDR:-http://127.0.0.1:18200}
mkdir -p "$E"
ID=$(python3 -c "import yaml,sys; print(yaml.safe_load(open(sys.argv[1]))['spec']['id'])" "$A")
JOB=bounded-executor-$(echo "$ID" | tr 'A-Z' 'a-z')
W=$(sha256sum "$PLAT"/credential-change-requests/CCR-2026-0058-*.yaml | cut -d' ' -f1)
J=$(sha256sum "$PLAT"/credential-change-requests/CCR-2026-0059-*.yaml | cut -d' ' -f1)
n=$(ls "$E" 2>/dev/null | wc -l)
close() {
python3 "$NK/tools/service-binds/executor-lanes.py" window-close --admission "$A" --receipt "$E/$(printf %02d $((n + 3)))-window-close.json" || true
}
python3 "$NK/tools/service-binds/executor-lanes.py" window-open --admission "$A" --write-ccr-sha256 "$W" \
--journey-ccr-sha256 "$J" --receipt "$E/$(printf %02d $((n + 1)))-window-open.json"
trap close EXIT
CFG=$(mktemp --suffix=.json)
python3 -c "import json,sys; c=json.load(open(sys.argv[1])); c['trust_satisfied']=['runtime_secret_trust','runtime_identity_trust']; json.dump(c, open(sys.argv[2],'w'), indent=2)" "$RAPP/examples/config.pilot.json" "$CFG"
$K -n sso delete job "$JOB" --ignore-not-found >/dev/null; $K -n sso delete configmap "$JOB" --ignore-not-found >/dev/null
(cd "$RAPP" && make run ADMISSION="$A" APPROVAL_ID="$APPROVAL" CONFIG="$CFG" KUBECTL="$K")
# Either condition ends the wait (kubectl wait ANDs repeated --for; seen 2026-11-05, the completed run hung it).
for _ in $(seq 1 300); do
st=$($K -n sso get "job/$JOB" -o jsonpath='{.status.succeeded}{.status.failed}')
[ -n "$st" ] && break
sleep 5
done
$K -n sso logs "job/$JOB" > "$E/$(printf %02d $((n + 2)))-result.json"
tail -40 "$E/$(printf %02d $((n + 2)))-result.json"

View file

@ -279,3 +279,14 @@ def test_status_is_read_only_and_reports_every_check(tmp_path, monkeypatch):
r = {}
m.status(r)
assert (r['write_role_absent'], r['cas_required'], r['ready_for_window']) == (False, False, False)
def test_quiet_prints_nothing_and_still_writes_the_receipt(tmp_path, monkeypatch, capsys):
"""--quiet makes the window commands silent children of warden access --exec (NK-WP-0058-T04)."""
m, fake, d = build(tmp_path, monkeypatch)
run(m, 'setup', d)
receipt = tmp_path / 'status.json'
monkeypatch.setattr(sys, 'argv', ['executor-lanes.py', 'status', '--receipt', str(receipt), '--quiet'])
assert m.main() == 0
assert capsys.readouterr() == ('', '')
assert json.loads(receipt.read_text())['ready_for_window'] is True

View file

@ -0,0 +1,308 @@
#!/usr/bin/env python3
"""Founder commands for one bounded-execution run (NK-WP-0058-T04). Each prints one value-free line.
BX="python3 tools/bounded-execution-run/bx.py"
$BX ready admissions/BEA-YYYY-NNNN.yaml # readiness; one platform-admin login (OpenBao status)
$BX request # file the approval; the id goes into the state file
(approve the memo in informed-decision: browser profile of admin, aal2 with OTP)
$BX run # preflight, then window open, run, close; one platform-admin login
$BX status # the run's state and receipts, no login
$BX close # emergency: delete the write role; safe to repeat
Inputs live in a state file (~/.local/state/net-kingdom/bounded-execution/<id>.json), not in shell variables.
The admission named last is the current one; --admission selects another. Receipts are numbered by the tool in
docs/evidence/<date>-<id>/. Each command is safe to repeat: `run` never starts a second run for an admission.
The OpenBao steps run as silent children of `warden access openbao-platform-admin-login --exec`; the window
is closed in the same child, also when the run fails or is interrupted.
--attest names the trust states you attest for the run (default: runtime_secret_trust,runtime_identity_trust).
They are recorded in the state file and the run config, never edited in by hand.
"""
import argparse
import datetime as dt
import hashlib
import json
import os
import re
import subprocess
import sys
import time
from pathlib import Path
import yaml
REPO = Path(__file__).resolve().parents[2]
HOME = Path.home()
STATE_DIR = Path(os.environ.get('BX_STATE_DIR', HOME / '.local/state/net-kingdom/bounded-execution'))
RAPP = Path(os.environ.get('BX_RAPP', REPO.parent / 'rapp-bounded-executor'))
PLATFORM = Path(os.environ.get('BX_PLATFORM', REPO.parent / 'railiance-platform'))
KUBECONFIG = Path(os.environ.get('BX_KUBECONFIG', HOME / '.kube/config-railiance01'))
BAO_ADDR = os.environ.get('BAO_ADDR', 'http://127.0.0.1:18200')
LANE = ['warden', 'access', 'openbao-platform-admin-login', '--exec', '--']
LANES_TOOL = REPO / 'tools/service-binds/executor-lanes.py'
READY_TOOL = REPO / 'tools/bounded-execution-readiness/ready.py'
CCRS = {'write': 'CCR-2026-0058-*.yaml', 'journey': 'CCR-2026-0059-*.yaml'}
DELIVERIES = ('keycape-nk-bind-executor-approval-client', 'bounded-executor-tokens')
ATTEST = 'runtime_secret_trust,runtime_identity_trust'
WARDEN_EXIT = {10: 'login_failed', 11: 'child_failed', 12: 'child_output', 13: 'revoke_unconfirmed',
14: 'cleanup_failed'}
class Runner:
"""Every outside command goes through here; tests replace it."""
def run(self, argv, env=None, timeout=900):
full = {**os.environ, **(env or {})}
done = subprocess.run([str(a) for a in argv], capture_output=True, env=full, timeout=timeout)
return done.returncode, done.stdout.decode(errors='replace')
def sleep(self, seconds):
time.sleep(seconds)
def kubectl():
return f'kubectl --kubeconfig {KUBECONFIG}'
def json_blocks(text):
"""The value-free JSON results in a log: one-line objects and the executor's indented result blocks."""
out = []
for block in re.findall(r'^\{\n.*?\n\}$', text, re.M | re.S) + re.findall(r'^\{.*\}$', text, re.M):
try:
out.append(json.loads(block))
except ValueError:
pass
return out
class Bx:
def __init__(self, runner=None, now=None):
self.r = runner or Runner()
self.now = now or (lambda: dt.datetime.now(dt.timezone.utc))
# ------------------------------------------------------------------ state and receipts
def state_path(self, admission_id):
return STATE_DIR / f'{admission_id}.json'
def load(self, admission=None):
if admission:
doc = yaml.safe_load(Path(admission).read_text())
sid = doc['spec']['id']
path = self.state_path(sid)
if path.exists():
return json.loads(path.read_text())
evidence = REPO / 'docs/evidence' / f"{self.now():%Y-%m-%d}-{sid.lower()}"
return {'admission_id': sid, 'admission': str(Path(admission).resolve()), 'evidence': str(evidence),
'approval_id': None, 'attest': None, 'run': None}
current = STATE_DIR / 'current'
if not current.exists():
raise SystemExit(self.line({'status': 'refused', 'reason': 'no_admission', 'next': 'bx ready ADMISSION'}))
return json.loads(self.state_path(current.read_text().strip()).read_text())
def save(self, state):
STATE_DIR.mkdir(parents=True, exist_ok=True)
path = self.state_path(state['admission_id'])
path.write_text(json.dumps(state, indent=2, sort_keys=True) + '\n')
(STATE_DIR / 'current').write_text(state['admission_id'] + '\n')
def receipt(self, state, name):
folder = Path(state['evidence'])
folder.mkdir(parents=True, exist_ok=True)
numbers = [int(p.name[:2]) for p in folder.glob('[0-9][0-9]-*')]
return folder / f'{max(numbers, default=0) + 1:02d}-{name}.json'
def write(self, path, data):
path.write_text(json.dumps(data, indent=2, sort_keys=True) + '\n')
return path
def config(self, state):
"""The run config: the rapp's pilot config with the attested trust states. Non-secret."""
cfg = json.loads((RAPP / 'examples/config.pilot.json').read_text())
cfg['trust_satisfied'] = list(state['attest'])
path = STATE_DIR / f"{state['admission_id']}.config.json"
path.write_text(json.dumps(cfg, indent=2) + '\n')
return path
@staticmethod
def line(data):
return json.dumps(data, separators=(', ', ': '))
def lane(self, child):
"""Run a silent child under one platform-admin login. The child gets an isolated HOME from warden, so every
path it needs is passed as an absolute path here."""
env = {'BAO_ADDR': BAO_ADDR, 'VAULT_ADDR': BAO_ADDR, 'PYTHONWARNINGS': 'ignore', 'BX_STATE_DIR': STATE_DIR,
'BX_RAPP': RAPP, 'BX_PLATFORM': PLATFORM, 'BX_KUBECONFIG': KUBECONFIG, 'NK_PLATFORM_ROOT': PLATFORM}
code, _ = self.r.run(LANE + child, env={k: str(v) for k, v in env.items()})
return code
# ------------------------------------------------------------------ commands
def ready(self, admission, attest):
state = self.load(admission)
state['attest'] = state['attest'] or attest.split(',')
self.save(state)
rec = self.receipt(state, 'readiness')
self.r.run(['python3', READY_TOOL, '--admission', state['admission'],
'--approval-id', state['approval_id'] or 'none', '--receipt', rec])
agent = (json.loads(rec.read_text()) if rec.exists()
else {'agent_checks_pass': False, 'failures': ['no_receipt']})
custody = {}
for name in DELIVERIES:
code, out = self.r.run(kubectl().split() + ['-n', 'sso', 'get', 'externalsecret', name, '-o',
'jsonpath={.status.conditions[?(@.type=="Ready")].status}'])
custody[name] = code == 0 and out.strip() == 'True'
self.write(self.receipt(state, 'custody'), {'action': 'custody-delivery', 'ready': custody,
'values_recorded': False})
status = self.receipt(state, 'lanes-status')
warden = self.lane(['python3', LANES_TOOL, 'status', '--receipt', status, '--quiet'])
lanes = json.loads(status.read_text()) if status.exists() else {}
failures = list(agent.get('failures', [])) + [f'custody:{n}' for n, ok in custody.items() if not ok]
if not lanes.get('ready_for_window'):
failures.append('openbao:' + (lanes.get('reason') or WARDEN_EXIT.get(warden, 'not_ready_for_window')))
state['ready'] = {'at': f'{self.now():%Y-%m-%dT%H:%M:%SZ}', 'failures': failures}
self.save(state)
nxt = 'bx run' if state['approval_id'] else 'bx request'
return self.line({'ready': not failures, 'admission': state['admission_id'], 'failures': failures,
'next': nxt if not failures else 'fix the failures, then bx ready again'})
def request(self):
state = self.load()
if state['approval_id']:
return self.line({'approval_id': state['approval_id'], 'status': 'already_requested',
'next': 'approve as admin (aal2), then bx run'})
code, out = self.r.run(['make', '-C', RAPP, 'request', f"ADMISSION={state['admission']}",
f'CONFIG={self.config(state)}', f'KUBECTL={kubectl()}'])
result = next((b for b in reversed(json_blocks(out)) if 'outcome' in b), {})
self.write(self.receipt(state, 'request'), {**result, 'exit': code})
if result.get('approval_id'):
state['approval_id'] = result['approval_id']
self.save(state)
return self.line({'approval_id': result.get('approval_id'), 'outcome': result.get('outcome'),
'next': 'open the memo as admin (aal2, fresh OTP), approve, then bx run'
if result.get('outcome') == 'presented' else 'bx request again (idempotent)'})
def run(self):
state = self.load()
if state.get('run') and state['run'].get('started'):
return self.line({**self.summary(state), 'status': 'already_run'})
if not state['approval_id']:
return self.line({'status': 'refused', 'reason': 'no_approval', 'next': 'bx request'})
self.config(state)
code = self.lane(['python3', Path(__file__).resolve(), '_run-inner', '--state',
self.state_path(state['admission_id'])])
state = json.loads(self.state_path(state['admission_id']).read_text())
if code and not (state.get('run') or {}).get('finished'):
state['run'] = {**(state.get('run') or {}), 'warden': WARDEN_EXIT.get(code, f'exit_{code}')}
self.save(state)
return self.line(self.summary(state))
def run_inner(self, state_file):
"""The silent lane child: preflight, then window open, run, poll, and close in `finally`. Prints nothing."""
state = json.loads(Path(state_file).read_text())
cfg = STATE_DIR / f"{state['admission_id']}.config.json"
k = kubectl()
run = state['run'] = {'started': False, 'receipts': []}
def note(path):
run['receipts'].append(Path(path).name)
self.save(state)
_, out = self.r.run(['make', '-C', RAPP, 'preflight', f"ADMISSION={state['admission']}",
f"APPROVAL_ID={state['approval_id']}", f'CONFIG={cfg}', f'KUBECTL={k}'])
report = next((b for b in reversed(json_blocks(out)) if b.get('mode') == 'preflight-only'),
{'ready': False, 'failures': ['preflight_no_report']})
note(self.write(self.receipt(state, 'preflight'), report))
if not report.get('ready'):
run.update(outcome='not_started', reason='preflight_not_ready', failures=report.get('failures'))
return self.save(state)
digests = {}
for lane, pattern in CCRS.items():
files = sorted((PLATFORM / 'credential-change-requests').glob(pattern))
digests[lane] = hashlib.sha256(files[0].read_bytes()).hexdigest()
opened = self.receipt(state, 'window-open')
try:
self.r.run(['python3', LANES_TOOL, 'window-open', '--admission', state['admission'],
'--write-ccr-sha256', digests['write'], '--journey-ccr-sha256', digests['journey'],
'--receipt', opened, '--quiet'])
note(opened)
if json.loads(opened.read_text()).get('status') != 'window_opened':
run.update(outcome='not_started', reason='window_not_opened')
return
run['started'] = True
self.save(state)
code, _ = self.r.run(['make', '-C', RAPP, 'run', f"ADMISSION={state['admission']}",
f"APPROVAL_ID={state['approval_id']}", f'CONFIG={cfg}', f'KUBECTL={k}'])
if code:
run.update(outcome='not_started', reason='job_not_created')
return
job = 'bounded-executor-' + state['admission_id'].lower()
budget = yaml.safe_load(Path(state['admission']).read_text())['spec']['budget']['wall_time_seconds']
for _ in range(int(budget / 5) + 60):
_, st = self.r.run(k.split() + ['-n', 'sso', 'get', f'job/{job}', '-o',
'jsonpath={.status.succeeded}{.status.failed}'])
if st.strip():
break
self.r.sleep(5)
_, logs = self.r.run(k.split() + ['-n', 'sso', 'logs', f'job/{job}'])
result = next((b for b in reversed(json_blocks(logs)) if 'outcome' in b),
{'outcome': 'unknown', 'reason': 'no_result_in_log'})
note(self.write(self.receipt(state, 'result'), result))
run.update(outcome=result.get('outcome'), reason=result.get('reason'), run_id=result.get('run_id'))
finally:
closed = self.receipt(state, 'window-close')
self.r.run(['python3', LANES_TOOL, 'window-close', '--admission', state['admission'],
'--receipt', closed, '--quiet'])
if closed.exists():
note(closed)
run['window_closed'] = json.loads(closed.read_text()).get('status') == 'window_closed'
run['finished'] = True
self.save(state)
def summary(self, state):
run = state.get('run') or {}
return {'admission': state['admission_id'], 'approval_id': state['approval_id'],
'outcome': run.get('outcome'), 'reason': run.get('reason'), 'window_closed': run.get('window_closed'),
'warden': run.get('warden'), 'error': run.get('error'), 'receipts': len(run.get('receipts', [])),
'evidence': os.path.relpath(state['evidence'], REPO)}
def status(self):
return self.line(self.summary(self.load()))
def close(self):
state = self.load()
rec = self.receipt(state, 'window-close')
code = self.lane(['python3', LANES_TOOL, 'window-close', '--admission', state['admission'], '--receipt', rec,
'--quiet'])
closed = json.loads(rec.read_text()) if rec.exists() else {}
return self.line({'status': closed.get('status', 'failed'), 'reason': closed.get('reason'),
'warden': WARDEN_EXIT.get(code) if code else None})
def main(argv=None, bx=None):
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
p.add_argument('command', choices=['ready', 'request', 'run', 'status', 'close', '_run-inner'])
p.add_argument('admission', nargs='?')
p.add_argument('--attest', default=ATTEST)
p.add_argument('--state', help=argparse.SUPPRESS)
a = p.parse_args(argv)
bx = bx or Bx()
if a.command == '_run-inner': # a silent lane child: any output would fail the lane, so record, never print
try:
bx.run_inner(a.state)
except Exception as error:
state = json.loads(Path(a.state).read_text())
state['run'] = {**(state.get('run') or {}), 'error': type(error).__name__, 'finished': True}
Path(a.state).write_text(json.dumps(state, indent=2, sort_keys=True) + '\n')
return 0
if a.command == 'ready':
if not a.admission:
p.error('ready takes the admission file')
print(bx.ready(a.admission, a.attest))
return 0
if a.admission:
bx.save(bx.load(a.admission))
print({'request': bx.request, 'run': bx.run, 'status': bx.status, 'close': bx.close}[a.command]())
return 0
if __name__ == '__main__':
sys.exit(main())

View file

@ -0,0 +1,183 @@
"""bx.py on stubs (NK-WP-0058-T04): every outside command is faked; no cluster, no OpenBao, no warden."""
import contextlib
import importlib.util
import io
import json
from pathlib import Path
import pytest
REPO = Path(__file__).resolve().parents[3]
spec = importlib.util.spec_from_file_location('bx', REPO / 'tools/bounded-execution-run/bx.py')
bx = importlib.util.module_from_spec(spec)
spec.loader.exec_module(bx)
ADMISSION = REPO / 'admissions/BEA-2026-0003.yaml'
def arg(argv, name):
return argv[argv.index(name) + 1]
class Fake:
def __init__(self, tool, preflight_ready=True, window_opens=True, result='completed', logs_raise=False):
self.tool, self.calls, self.child_output = tool, [], ''
self.preflight_ready, self.window_opens, self.result, self.logs_raise = (
preflight_ready, window_opens, result, logs_raise)
def write(self, argv, data):
Path(arg(argv, '--receipt')).write_text(json.dumps(data))
def run(self, argv, env=None, timeout=900):
argv = [str(a) for a in argv]
joined = ' '.join(argv)
if argv[:3] == ['warden', 'access', 'openbao-platform-admin-login']:
self.calls.append('lane')
assert env['BX_STATE_DIR'] and env['BX_KUBECONFIG'] # absolute paths: the child's HOME is isolated
child = argv[argv.index('--') + 1:]
if '_run-inner' in child:
with contextlib.redirect_stdout(io.StringIO()) as out, contextlib.redirect_stderr(out):
self.tool.main(['_run-inner', '--state', arg(child, '--state')], bx=self.tool_bx)
self.child_output += out.getvalue()
return 0, ''
return self.run(child)
if 'ready.py' in joined:
self.calls.append('readiness')
self.write(argv, {'agent_checks_pass': True, 'failures': []})
return 0, ''
if 'externalsecret' in joined:
self.calls.append('custody')
return 0, 'True'
if 'executor-lanes.py' in joined:
action = argv[2]
self.calls.append(action)
assert '--quiet' in argv
if action == 'status':
self.write(argv, {'status': 'status_read', 'ready_for_window': True})
elif action == 'window-open':
self.write(argv, {'status': 'window_opened' if self.window_opens else 'failed'})
elif action == 'window-close':
self.write(argv, {'status': 'window_closed'})
return 0, ''
if argv[:1] == ['make']:
target = argv[3]
self.calls.append('make ' + target)
if target == 'request':
return 0, 'kubectl ...\n{\n "approval_id": "apr-9",\n "outcome": "presented"\n}\n'
if target == 'preflight':
report = {'mode': 'preflight-only', 'ready': self.preflight_ready, 'failures':
[] if self.preflight_ready else ['preflight:approval']}
return (0 if self.preflight_ready else 2), json.dumps(report, indent=2) + '\n'
if target == 'run':
return 0, 'kubectl create -f - < .rendered/run.yaml\n'
if 'get' in argv and any(a.startswith('job/') for a in argv):
self.calls.append('poll')
return 0, '1'
if 'logs' in argv:
self.calls.append('logs')
if self.logs_raise:
raise TimeoutError
return 0, json.dumps({'outcome': self.result, 'run_id': 'r-1', 'effects': True}, indent=2) + '\n'
raise AssertionError(argv)
def sleep(self, seconds):
pass
@pytest.fixture
def env(tmp_path, monkeypatch):
monkeypatch.setattr(bx, 'STATE_DIR', tmp_path / 'state')
monkeypatch.setattr(bx, 'REPO', tmp_path / 'repo')
rapp = tmp_path / 'rapp'
(rapp / 'examples').mkdir(parents=True)
(rapp / 'examples/config.pilot.json').write_text(json.dumps({'executor_identity': 'service:nk-bind-executor'}))
monkeypatch.setattr(bx, 'RAPP', rapp)
ccrs = tmp_path / 'platform/credential-change-requests'
ccrs.mkdir(parents=True)
for name in ('CCR-2026-0058-w.yaml', 'CCR-2026-0059-j.yaml'):
(ccrs / name).write_text('id: x\n')
monkeypatch.setattr(bx, 'PLATFORM', tmp_path / 'platform')
return tmp_path
def tool(**kw):
fake = Fake(bx, **kw)
fake.tool_bx = bx.Bx(fake)
return fake, fake.tool_bx
def cli(b, *argv):
with contextlib.redirect_stdout(io.StringIO()) as out:
assert bx.main(list(argv), bx=b) == 0
lines = out.getvalue().splitlines()
assert len(lines) == 1 # one value-free line per command
return json.loads(lines[0])
def test_full_run_is_four_commands_plus_the_approval(env):
fake, b = tool()
assert cli(b, 'ready', str(ADMISSION))['next'] == 'bx request'
assert cli(b, 'request')['approval_id'] == 'apr-9'
done = cli(b, 'run')
assert (done['outcome'], done['window_closed'], done['receipts']) == ('completed', True, 4)
assert cli(b, 'status')['outcome'] == 'completed'
assert fake.calls == ['readiness', 'custody', 'custody', 'lane', 'status', 'make request', 'lane',
'make preflight', 'window-open', 'make run', 'poll', 'logs', 'window-close']
assert fake.child_output == '' # the lane child is silent
evidence = sorted(p.name for p in (env / 'repo/docs/evidence').glob('*/*'))
assert [n[:2] for n in evidence] == [f'{i:02d}' for i in range(1, 9)]
assert evidence[4:] == ['05-preflight.json', '06-window-open.json', '07-result.json', '08-window-close.json']
state = json.loads((env / 'state/BEA-2026-0003.json').read_text())
assert state['attest'] == ['runtime_secret_trust', 'runtime_identity_trust']
cfg = json.loads((env / 'state/BEA-2026-0003.config.json').read_text())
assert cfg['trust_satisfied'] == state['attest']
def test_commands_are_safe_to_repeat(env):
fake, b = tool()
cli(b, 'ready', str(ADMISSION))
cli(b, 'request')
assert cli(b, 'request')['status'] == 'already_requested'
cli(b, 'run')
calls = len(fake.calls)
assert cli(b, 'run')['status'] == 'already_run'
assert len(fake.calls) == calls # no second run, no second login
def test_preflight_not_ready_never_opens_the_window(env):
fake, b = tool(preflight_ready=False)
cli(b, 'ready', str(ADMISSION))
cli(b, 'request')
done = cli(b, 'run')
assert (done['outcome'], done['reason']) == ('not_started', 'preflight_not_ready')
assert 'window-open' not in fake.calls and 'make run' not in fake.calls
fake.preflight_ready = True
assert cli(b, 'run')['outcome'] == 'completed' # a run that never started may be retried
@pytest.mark.parametrize('kw, outcome', [({'window_opens': False}, 'not_started'), ({'result': 'reverted'}, 'reverted')])
def test_the_window_is_always_closed(env, kw, outcome):
fake, b = tool(**kw)
cli(b, 'ready', str(ADMISSION))
cli(b, 'request')
done = cli(b, 'run')
assert done['outcome'] == outcome and fake.calls[-1] == 'window-close' and done['window_closed']
def test_an_error_in_the_child_still_closes_and_stays_silent(env):
fake, b = tool(logs_raise=True)
cli(b, 'ready', str(ADMISSION))
cli(b, 'request')
done = cli(b, 'run')
assert done['error'] == 'TimeoutError' and done['window_closed'] is True
assert fake.calls[-1] == 'window-close' and fake.child_output == ''
def test_close_is_one_lane_and_idempotent(env):
fake, b = tool()
cli(b, 'ready', str(ADMISSION))
assert cli(b, 'close')['status'] == 'window_closed'
assert cli(b, 'close')['status'] == 'window_closed'
def test_instructions_fit_120_characters():
assert all(len(line) <= 120 for line in bx.__doc__.splitlines())

View file

@ -331,6 +331,8 @@ def main():
p.add_argument('--kubeconfig', help='admin kubeconfig; required for verify')
p.add_argument('--admission', help='the admitted BEA file; required for window-open and window-close')
p.add_argument('--receipt', required=True)
p.add_argument('--quiet', action='store_true',
help='print nothing; the receipt is the result (a silent child of warden access --exec)')
a = p.parse_args()
if a.action == 'verify' and not a.kubeconfig: p.error('--kubeconfig is required for verify')
if a.action.startswith('window-') and not a.admission: p.error('--admission is required for ' + a.action)
@ -351,8 +353,9 @@ def main():
if r['reason'] in HINTS: r['hint'] = HINTS[r['reason']]
finally:
with os.fdopen(fd, 'w') as out: json.dump(r, out, indent=2, sort_keys=True); out.write('\n')
print(json.dumps({k: r[k] for k in ('status', 'phase', 'reason', 'hint', 'ready_for_window', 'write_role_absent')
if k in r}))
if not a.quiet:
print(json.dumps({k: r[k] for k in ('status', 'phase', 'reason', 'hint', 'ready_for_window',
'write_role_absent') if k in r}))
return int(r['status'] == 'failed')

View file

@ -343,7 +343,7 @@ founder step for the next admission. The readiness check is not yet in CI; that
```task
id: NK-WP-0058-T04
status: todo
status: done
priority: high
assignee: claude-code
depends_on: [NK-WP-0058-T03]
@ -365,6 +365,29 @@ needs: a browser profile per identity, and when a fresh OTP login is due.
**Done when:** a full pilot run is at most five short founder commands plus the approval in informed-decision. The
commands are tested on stubs, and T06's runbook shrinks accordingly.
**Result, 2026-11-05.** `tools/bounded-execution-run/bx.py`: `ready`, `request`, (the approval), `run` and `status`
make four commands plus the approval. `close` is for emergencies.
- **State file:** inputs live in `~/.local/state/net-kingdom/bounded-execution/<id>.json`. That covers the
admission, the approval id, the attested trust states and the evidence folder. The run config is generated from
the rapp's config, never edited by hand.
- **Receipts:** numbered by the tool in `docs/evidence/<date>-<id>/`.
- **Safe to repeat:** every command. `run` never starts a second run for an admission; a run that never started
may be retried.
- **One line per command,** value-free.
- **Two logins in a run:** `ready` and `run` each open one `warden access openbao-platform-admin-login` login.
The child is silent (`executor-lanes.py --quiet`) and writes the receipts. `run` does preflight, window open,
run, poll and result, and closes the window in `finally`, also on an error inside the child. This resolves O3.
- **Tests:** 8 tests on stubs cover the full flow, repeats, a not-ready preflight never opening the window, the
window always closing, a silent child on error, and instruction lines of at most 120 characters. 134 in
`make bounded-executor-test`.
- **Runbook:** sections 5 and 6 of the T06 runbook are replaced by the four commands, a result table and identity
hygiene: a browser profile per identity, a check of the signed-in subject, and a fresh OTP when a memo doesn't
list. `pilot-run.sh` is deleted.
- **Not yet live-proven:** first live use needs the new image, a rapp re-pin and a new admission. Whether warden
passes `BAO_ADDR` and the `BX_*` variables through to the child is confirmed only from its playbook ("the caller
passes BAO_ADDR"), so the first `bx ready` is the live check.
### Agent status visibility without secrets
```task