NK-WP-0056-T07: founder kill switch and execution findings in posture feedback
All checks were successful
Build and Publish bounded-executor / build-and-push (push) Successful in 8s
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 2s
Identity provider journey acceptance / provider (push) Successful in 37s

- cli: SIGTERM/SIGINT (kubectl delete job) sets the stop flag; the runner stops at the next
  step boundary and reverts. A stop before consume refuses and spends nothing.
- posture feedback v0.1 section 4a: --receipts reads bounded-executor events through the
  receipt validator: execution-receipt-missing, -not-completed, -reverted, -budget-pressure,
  and the aggregate execution-recurring-revert. Owners come from the authoritative
  tools/posture-feedback/execution-owners.yaml, never inferred.

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:44:12 +01:00
parent d7133e3a04
commit 9bd7bf0bdc
9 changed files with 309 additions and 3 deletions

View file

@ -17,6 +17,11 @@ The config (JSON, non-secret) names endpoints, token files and the pilot's targe
Exit codes: 0 completed, 3 reverted, 4 refused before any effect, 5 stopped / unknown / revert failed / budget,
6 consumed but not in custody. The result printed is value-free.
Kill switch (NK-WP-0056-T07): SIGTERM or SIGINT, e.g. the founder's `kubectl -n sso delete job <job>`, sets the stop
flag. Before consume the run refuses and spends nothing; after it, the runner finishes the step in flight, then
stops and reverts what it changed (BEA stop_and_revert). The Job's terminationGracePeriodSeconds must cover a
revert.
python3 bounded-executor/cli.py --journey-only --config FILE [--result FILE]
python3 bounded-executor/cli.py --request --admission FILE --config FILE [--result FILE]
@ -46,7 +51,9 @@ import argparse
import importlib.util
import json
import pathlib
import signal
import sys
import threading
HERE = pathlib.Path(__file__).resolve().parent
@ -63,6 +70,14 @@ X = _load("executor")
OPS = _load("operations")
C = _load("clients")
EXIT = {"completed": 0, "reverted": 3, "refused": 4}
STOP = threading.Event() # the kill switch; set by SIGTERM/SIGINT, read between steps
def install_kill_switch() -> None:
def stop(_signum, _frame):
STOP.set() # never raises into the step in flight: the runner stops at the next boundary and reverts
signal.signal(signal.SIGTERM, stop)
signal.signal(signal.SIGINT, stop)
def approval_carrier(cfg: dict, logins: list):
@ -125,7 +140,7 @@ def build(cfg: dict, logins: list):
operations=operations, checks=checks, trust=lambda state: state in trusted,
audit=C.AuditCore(cfg["audit_core"]["url"], cfg["audit_core"]["token_file"],
cfg["audit_core"].get("ca_file")),
alert=alert)
alert=alert, should_stop=STOP.is_set)
config = X.Config(executor_identity=cfg["executor_identity"], runtime_principal=cfg["runtime_principal"],
tenant=cfg["tenant"], environment=cfg["environment"],
approver_subjects=dict(cfg["approver_subjects"]),
@ -316,6 +331,7 @@ def main(argv=None) -> int:
result, code = run_journey_only(cfg)
_emit(result, args.result)
return code
install_kill_switch()
admission = X.bea.load(pathlib.Path(args.admission))
logins: list = []
try: