NK-WP-0058-T08 worker protocol and excerpt tool; T06 window watch (row O5), first use of the protocol

T08: .claude/rules/worker-protocol.md (imported by CLAUDE.md, pointed to from AGENTS.md) and
tools/contract-tests/excerpt.py: name the rows, hand every worker one excerpt, one owner per step, check
with the excerpt's tests. A test requires every table row to have details and a contract test (found O4).
T06: bounded-executor/window_watch.py flags or closes the write role when no run Job is active or recent;
stub tests; in the image. Built from the excerpt for O5, E1, C1, E8, E9, R2.

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-06 01:36:09 +01:00
parent 558bfce848
commit c63aa398d6
9 changed files with 398 additions and 3 deletions

View file

@ -12,7 +12,8 @@ RUN python -m pip install --no-cache-dir --require-hashes -r /app/bounded-execut
COPY canon/schemas/bounded-execution-admission_v0.1.schema.json /app/canon/schemas/
COPY tools/bounded-execution-admission/validate.py /app/tools/bounded-execution-admission/
COPY sso-mfa/k8s/synthetic-journey/journey.py /app/sso-mfa/k8s/synthetic-journey/
COPY bounded-executor/executor.py bounded-executor/operations.py bounded-executor/clients.py bounded-executor/cli.py /app/bounded-executor/
COPY bounded-executor/executor.py bounded-executor/operations.py bounded-executor/clients.py bounded-executor/cli.py \
bounded-executor/window_watch.py /app/bounded-executor/
USER 10001:10001
ENV PYTHONDONTWRITEBYTECODE=1
ENTRYPOINT ["python", "/app/bounded-executor/cli.py"]

View file

@ -0,0 +1,110 @@
"""window_watch.py on stubs (NK-WP-0058-T06, contract row O5): rule, both modes, and errors."""
import datetime as dt
import importlib.util
import pathlib
import sys
import unittest
HERE = pathlib.Path(__file__).resolve().parent
sys.path.insert(0, str(HERE.parent))
spec = importlib.util.spec_from_file_location('window_watch', HERE.parent / 'window_watch.py')
W = importlib.util.module_from_spec(spec)
spec.loader.exec_module(W)
OPS = W.OPS
NOW = dt.datetime(2026, 11, 6, 3, 0, tzinfo=dt.timezone.utc)
def job(minutes_ago, active=0):
created = (NOW - dt.timedelta(minutes=minutes_ago)).isoformat().replace('+00:00', 'Z')
return {'metadata': {'creationTimestamp': created}, 'status': {'active': active} if active else {}}
class FakeBao:
def __init__(self, present=True, delete_works=True):
self.present, self.delete_works, self.calls = present, delete_works, []
def _call(self, method, path, body=None, auth=True):
self.calls.append((method, path))
assert path == W.ROLE_PATH, path # nothing but the write role is ever touched
if method == 'GET':
if not self.present:
raise OPS.OperationError('openbao GET 404')
return {'data': {}}
if method == 'DELETE':
self.present = not self.delete_works
return {}
raise AssertionError(method)
class FakeKube:
def __init__(self, jobs):
self.jobs, self.paths = jobs, []
def get(self, path):
self.paths.append(path)
return {'items': self.jobs}
class Rule(unittest.TestCase):
def test_decide(self):
cases = [
(False, [], 'ok'), # no role: nothing open
(True, [], 'stale'), # role, never a run: left open
(True, [job(3)], 'ok'), # role, run just created: a window in use
(True, [job(30, active=1)], 'ok'), # role, run still active
(True, [job(30)], 'stale'), # role, last run finished 30 min ago: left open
(True, [job(30), job(2)], 'ok'), # the newest run counts
]
for present, jobs, verdict in cases:
with self.subTest(present=present, jobs=len(jobs)):
self.assertEqual(W.decide(present, jobs, NOW)[0], verdict)
def test_run_jobs_selector_excludes_other_components(self):
kube = FakeKube([])
W.run_jobs(kube)
self.assertIn('labelSelector=app.kubernetes.io%2Fname%3Dbounded-executor%2C%21app.kubernetes.io%2Fcomponent',
kube.paths[0])
self.assertTrue(kube.paths[0].startswith('/apis/batch/v1/namespaces/sso/jobs?'))
class Modes(unittest.TestCase):
def test_ok_touches_nothing(self):
bao = FakeBao(present=False)
result, code = W.watch(bao, FakeKube([]), 'close', NOW)
self.assertEqual((result['status'], code), ('ok', 0))
self.assertEqual([m for m, _ in bao.calls], ['GET'])
def test_flag_reads_only_and_fails_the_job(self):
bao = FakeBao()
result, code = W.watch(bao, FakeKube([job(30)]), 'flag', NOW)
self.assertEqual((result['status'], code), ('flagged_stale', 3))
self.assertEqual([m for m, _ in bao.calls], ['GET'])
self.assertEqual(result['newest_age_s'], 1800)
def test_close_deletes_and_proves_absent(self):
bao = FakeBao()
result, code = W.watch(bao, FakeKube([]), 'close', NOW)
self.assertEqual((result['status'], code), ('closed_stale', 0))
self.assertEqual([m for m, _ in bao.calls], ['GET', 'DELETE', 'GET'])
self.assertFalse(bao.present)
def test_close_that_does_not_take_is_an_error(self):
result, code = W.watch(FakeBao(delete_works=False), FakeKube([]), 'close', NOW)
self.assertEqual((result['status'], result['reason'], code), ('error', 'write_role_still_present', 5))
def test_a_window_in_use_is_never_closed(self):
bao = FakeBao()
result, code = W.watch(bao, FakeKube([job(1, active=1)]), 'close', NOW)
self.assertEqual((result['status'], code), ('ok', 0))
self.assertTrue(bao.present)
def test_other_openbao_errors_are_not_read_as_absent(self):
class Denied(FakeBao):
def _call(self, method, path, body=None, auth=True):
raise OPS.OperationError('openbao GET 403')
with self.assertRaises(OPS.OperationError):
W.watch(Denied(), FakeKube([]), 'close', NOW)
if __name__ == '__main__':
unittest.main()

View file

@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Window watch: the write role must not outlive a run (NK-WP-0058-T06; contract row O5).
python3 bounded-executor/window_watch.py --mode flag|close [--openbao-addr URL] [--jwt-file FILE]
Rule: the write role (CCR-2026-0058) exists, no bounded-executor run Job is active in sso, and none was created in
the last GRACE seconds. A role in that state is a window left open, as on 2026-11-04/05. Run Jobs carry
app.kubernetes.io/name=bounded-executor and no app.kubernetes.io/component label (request, preflight, journey and
netcheck pods carry one).
flag read-only: print `flagged_stale` and exit 3, so the watch Job fails and its status shows it
close delete the role, prove it is absent, print `closed_stale`, exit 0. Closing only reduces privilege: a run
that was about to start is refused before any effect.
The identity is ServiceAccount sso/nk-window-watch. It logs in to OpenBao role
net-kingdom-bounded-executor-window-watch, whose policy allows read (flag), or read and delete (close), on the
write role only, and it may list Jobs in sso. Prints one value-free line; exit 0 ok or closed, 3 flagged, 5 error.
"""
from __future__ import annotations
import argparse
import datetime as dt
import json
import sys
import urllib.parse
from pathlib import Path
HERE = Path(__file__).resolve().parent
sys.path.insert(0, str(HERE))
import operations as OPS # noqa: E402
WRITE_ROLE = 'net-kingdom-bind-executor-svc-authelia-write'
WATCH_ROLE = 'net-kingdom-bounded-executor-window-watch'
ROLE_PATH = '/v1/auth/kubernetes/role/' + WRITE_ROLE
RUN_JOBS = 'app.kubernetes.io/name=bounded-executor,!app.kubernetes.io/component'
GRACE = 600 # seconds: the window opens moments before its run Job is created
def role_present(bao) -> bool:
try:
bao._call('GET', ROLE_PATH)
return True
except OPS.OperationError as error:
if str(error).endswith(' 404'):
return False
raise
def run_jobs(kube, namespace='sso'):
query = urllib.parse.urlencode({'labelSelector': RUN_JOBS})
return kube.get(f'/apis/batch/v1/namespaces/{namespace}/jobs?{query}').get('items', [])
def decide(present: bool, jobs: list, now: dt.datetime, grace: int = GRACE) -> tuple[str, dict]:
"""The rule, without I/O: ('ok' | 'stale', value-free facts)."""
active = sum(1 for j in jobs if (j.get('status') or {}).get('active'))
created = [dt.datetime.fromisoformat(j['metadata']['creationTimestamp'].replace('Z', '+00:00')) for j in jobs]
newest = max(created, default=None)
age = int((now - newest).total_seconds()) if newest else None
facts = {'write_role_present': present, 'run_jobs': len(jobs), 'active': active, 'newest_age_s': age}
stale = present and not active and (age is None or age > grace)
return ('stale' if stale else 'ok'), facts
def watch(bao, kube, mode: str, now: dt.datetime) -> tuple[dict, int]:
verdict, facts = decide(role_present(bao), run_jobs(kube), now)
if verdict == 'ok':
return {'status': 'ok', **facts}, 0
if mode == 'flag':
return {'status': 'flagged_stale', **facts}, 3
bao._call('DELETE', ROLE_PATH)
if role_present(bao):
return {'status': 'error', 'reason': 'write_role_still_present', **facts}, 5
return {'status': 'closed_stale', **facts}, 0
def main(argv=None) -> int:
p = argparse.ArgumentParser(description=__doc__.split('\n', 1)[0])
p.add_argument('--mode', choices=['flag', 'close'], required=True)
p.add_argument('--openbao-addr', default='http://openbao.openbao.svc:8200')
p.add_argument('--jwt-file', default='/var/run/secrets/openbao/token')
a = p.parse_args(argv)
bao = None
try:
bao = OPS.OpenBao(a.openbao_addr, k8s_role=WATCH_ROLE, jwt_file=a.jwt_file)
result, code = watch(bao, OPS.Kube(), a.mode, dt.datetime.now(dt.timezone.utc))
except Exception as error: # value-free: the clients' own errors carry fixed texts
reason = str(error) if isinstance(error, OPS.OperationError) else type(error).__name__
result, code = {'status': 'error', 'reason': reason}, 5
finally:
if bao is not None:
bao.revoke_self()
print(json.dumps({'watch': 'bounded-execution-window', 'mode': a.mode, **result}, sort_keys=True))
return code
if __name__ == '__main__':
sys.exit(main())