NK-WP-0058-T04 done: bx, four founder commands plus the approval
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:
parent
a29d92ecda
commit
646ced3f9e
10 changed files with 577 additions and 120 deletions
2
Makefile
2
Makefile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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`.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
@ -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
|
||||
|
|
|
|||
308
tools/bounded-execution-run/bx.py
Normal file
308
tools/bounded-execution-run/bx.py
Normal 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())
|
||||
183
tools/bounded-execution-run/tests/test_bx.py
Normal file
183
tools/bounded-execution-run/tests/test_bx.py
Normal 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())
|
||||
|
|
@ -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')
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue