NK-WP-0056-T06 prep: admission helper, write-role window-open/close, service-client case; T15 evidence

- bounded-executor/prepare_admission.py: the live admission from the pilot example, pins read
  from the image's commit, window <= 2 h (audit-core's declared bound), validated.
- executor-lanes.py window-open/window-close: CCR-2026-0058's window-scoped write role, created
  for one admitted open window under attended platform-admin and deleted afterwards (idempotent).
- security-cases/nk-bind-executor-approval.yaml: the case of record, proposed (founder acceptance).
- docs/evidence/2026-11-04-nk56-t15-audit-sender: 8 checks against audit-core 876c3f6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Assistant: claude-code
Assistant-Model: opus
Assistant-Process: 523928@bnt-lap001
Assistant-Session: 6c737afb-9c14-4152-9f02-477214b9c350
This commit is contained in:
tegwick 2026-11-04 01:49:40 +01:00
parent 9bd7bf0bdc
commit c43afd46a7
7 changed files with 368 additions and 6 deletions

View file

@ -0,0 +1,92 @@
#!/usr/bin/env python3
"""Prepare the live pilot admission (NK-WP-0056-T06) from the pilot example.
The executor checks every pinned file against the copy inside its image (source_root /app), so the pins are taken
from the commit the pinned image was built from (`--commit`, read with `git show`), not from the working tree.
The window opens at `--not-before` and lasts `--hours`. The budget, plan, checks and scope ceiling are the
example's, so the admission stays inside the executor's declared grants (make bounded-executor-test).
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
Prints the admission and plan digests. Writes nothing else and contacts nothing.
"""
from __future__ import annotations
import argparse
import datetime as dt
import hashlib
import importlib.util
import json
import pathlib
import subprocess
import sys
import yaml
HERE = pathlib.Path(__file__).resolve().parent
REPO = HERE.parent
EXAMPLE = REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml"
_spec = importlib.util.spec_from_file_location("bea_validate_prepare", REPO / "tools/bounded-execution-admission/validate.py")
bea = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(bea)
def _git(*args: str) -> bytes:
return subprocess.run(["git", "-C", str(REPO), *args], capture_output=True, check=True).stdout
def _z(t: dt.datetime) -> str:
return t.astimezone(dt.UTC).isoformat(timespec="seconds").replace("+00:00", "Z")
def prepare(admission_id: str, commit: str, not_before: dt.datetime, hours: float) -> dict:
full = _git("rev-parse", "--verify", commit + "^{commit}").decode().strip()
doc = bea.load(EXAMPLE)
spec = doc["spec"]
spec.update(id=admission_id, work_ref="NK-WP-0056-T06",
title="Rotate the svc-authelia directory bind (live pilot, one consumer)")
spec["window"] = {"not_before": _z(not_before), "not_after": _z(not_before + dt.timedelta(hours=hours))}
files = [{"path": f["path"], "sha256": hashlib.sha256(_git("show", f"{full}:{f['path']}")).hexdigest()}
for f in spec["plan"]["source"]["files"]]
spec["plan"]["source"].update(commit=full[:12], files=files)
pins = {f["path"]: f["sha256"] for f in files}
for check in spec["checks"]:
check["sha256"] = pins[check["ref"]]
spec["plan_digest"] = bea.plan_digest(doc)
report = bea.evaluate(doc, json.loads(bea.SCHEMA.read_text()), as_of=not_before)
if not report["valid"]:
raise ValueError("admission invalid: " + ",".join(sorted({f["rule"] for f in report["findings"]})))
return doc
def main(argv=None) -> int:
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
parser.add_argument("--id", required=True)
parser.add_argument("--commit", required=True, help="the commit the pinned executor image was built from")
parser.add_argument("--not-before", required=True, help="window start, RFC 3339 with offset")
parser.add_argument("--hours", type=float, default=2.0)
parser.add_argument("--out", required=True, type=pathlib.Path)
args = parser.parse_args(argv)
if args.out.exists():
raise SystemExit("output exists; admissions are immutable, choose a new id")
if not 0 < args.hours <= 2:
raise SystemExit("--hours must be in (0, 2]: audit-core's declared bound for a run without receipt is 2 h")
try:
doc = prepare(args.id, args.commit, dt.datetime.fromisoformat(args.not_before.replace("Z", "+00:00")),
args.hours)
except (ValueError, subprocess.CalledProcessError) as error:
print(f"refused: {error}" if isinstance(error, ValueError) else "refused: unknown commit or path",
file=sys.stderr)
return 4
args.out.parent.mkdir(parents=True, exist_ok=True)
args.out.write_text("# NK-WP-0056-T06 live pilot admission. Immutable once requested: a change is a new id.\n"
+ yaml.safe_dump(doc, sort_keys=False, width=120))
print(json.dumps({"admission": args.id, "admission_digest": bea.admission_digest(doc),
"plan_digest": doc["spec"]["plan_digest"], "window": doc["spec"]["window"],
"pinned_commit": doc["spec"]["plan"]["source"]["commit"]}, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())

View file

@ -0,0 +1,55 @@
"""Live pilot admission preparation (NK-WP-0056-T06)."""
from __future__ import annotations
import datetime as dt
import hashlib
import importlib.util
import json
import pathlib
import subprocess
import sys
import tempfile
import unittest
HERE = pathlib.Path(__file__).resolve().parent
spec = importlib.util.spec_from_file_location("prepare_admission", HERE.parent / "prepare_admission.py")
P = importlib.util.module_from_spec(spec)
sys.modules["prepare_admission"] = P
spec.loader.exec_module(P)
START = dt.datetime(2026, 11, 5, 9, 0, tzinfo=dt.UTC)
@unittest.skipUnless((P.REPO / ".git").exists(), "needs a git checkout (CI tests a source tarball)")
class Prepare(unittest.TestCase):
def setUp(self):
self.commit = subprocess.run(["git", "-C", str(P.REPO), "rev-parse", "HEAD"], capture_output=True,
text=True, check=True).stdout.strip()
def test_pins_the_commits_content_and_is_valid(self):
doc = P.prepare("BEA-2026-0099", self.commit, START, 2)
spec = doc["spec"]
self.assertEqual(spec["window"], {"not_before": "2026-11-05T09:00:00Z", "not_after": "2026-11-05T11:00:00Z"})
self.assertEqual(spec["plan"]["source"]["commit"], self.commit[:12])
for f in spec["plan"]["source"]["files"]:
blob = subprocess.run(["git", "-C", str(P.REPO), "show", f"{self.commit}:{f['path']}"],
capture_output=True, check=True).stdout
self.assertEqual(f["sha256"], hashlib.sha256(blob).hexdigest())
report = P.bea.evaluate(doc, json.loads(P.bea.SCHEMA.read_text()), as_of=START)
self.assertTrue(report["valid"], report["findings"])
def test_refusals(self):
with tempfile.TemporaryDirectory() as tmp:
out = pathlib.Path(tmp) / "a.yaml"
out.write_text("exists")
with self.assertRaises(SystemExit):
P.main(["--id", "BEA-2026-0099", "--commit", self.commit, "--not-before", "2026-11-05T09:00:00Z",
"--out", str(out)])
with self.assertRaises(SystemExit):
P.main(["--id", "BEA-2026-0099", "--commit", self.commit, "--not-before", "2026-11-05T09:00:00Z",
"--hours", "3", "--out", str(pathlib.Path(tmp) / "b.yaml")])
self.assertEqual(P.main(["--id", "BEA-2026-0099", "--commit", "0" * 40, "--not-before",
"2026-11-05T09:00:00Z", "--out", str(pathlib.Path(tmp) / "c.yaml")]), 4)
if __name__ == "__main__":
unittest.main()