- canon/schemas/execution-attribution-receipt_v0.1.schema.json: the bounded-executor profile of the PCC v0.2 receipt, both audit-core events (started, receipt), every object closed. - tools/execution-attribution-receipt: EAR-1..EAR-10 validator, fixtures generated from the real executor (completed, reverted, completed-on-retry) with a drift check. - executor: started carries receipt_version, digests, steps_planned and data.window.not_after (audit-core AUDIT-WP-0018-T03 request); receipt carries receipt_version and initiator.iss (configured approver_issuer, else unknown). - bounded-executor/exercise_audit_sender.py: real executor and client against audit-core's real ingestion and the Git scope overlay: 202, duplicate 200, conflict 409 as a defect, wrong source/tenant/token refused and not stored, a receipt-less start flagged by its own window. 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
289 lines
14 KiB
Python
289 lines
14 KiB
Python
#!/usr/bin/env python3
|
|
"""Executor audit sender against audit-core's real ingestion (NK-WP-0056-T15).
|
|
|
|
audit-core's source (IngestionApplication, SQLite backend) is served on loopback with its sender registry built the
|
|
way production builds it: a Secret-shaped AUDIT_CORE_SENDERS entry plus the Git-declared scope overlay
|
|
deploy/senders-scope.json. The executor side is real: executor.Run.execute() on in-memory operations, emitting
|
|
through clients.AuditCore over HTTP with a token file.
|
|
|
|
Checks:
|
|
- a completed run and a reverted run: started and receipt accepted (202), and both pairs pass the receipt validator
|
|
(tools/execution-attribution-receipt, schema v0.1)
|
|
- a retry of the same event is a duplicate (200); the same id with another body is a conflict (409), which the
|
|
client reports as a run-id-reuse defect
|
|
- wrong source, wrong tenant (`tenant:platform`), no token and a wrong token are refused; nothing is stored for them
|
|
- a started event whose admission window has passed, with no receipt, becomes an opening_without_closing finding
|
|
held to that window; the closed runs do not
|
|
Synthetic tokens only. Writes a value-free receipt. Exit 0 when every check passes.
|
|
|
|
python3 bounded-executor/exercise_audit_sender.py --receipt FILE [--audit-core-source ~/audit-core]
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import copy
|
|
import datetime as dt
|
|
import hashlib
|
|
import importlib.util
|
|
import io
|
|
import json
|
|
import os
|
|
import pathlib
|
|
import secrets
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import threading
|
|
import urllib.error
|
|
import urllib.request
|
|
import uuid
|
|
from wsgiref.simple_server import WSGIRequestHandler, make_server
|
|
|
|
HERE = pathlib.Path(__file__).resolve().parent
|
|
REPO = HERE.parent
|
|
TOOL = REPO / "tools/execution-attribution-receipt"
|
|
|
|
|
|
def _load(name, path):
|
|
spec = importlib.util.spec_from_file_location(name, path)
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = module
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
G = _load("ear_generate", TOOL / "generate_fixtures.py") # loads executor.py as `executor`
|
|
V = _load("ear_validate", TOOL / "validate.py")
|
|
X = G.X
|
|
C = _load("clients", HERE / "clients.py")
|
|
|
|
|
|
def commit(path: pathlib.Path) -> str:
|
|
return subprocess.run(["git", "-C", str(path), "rev-parse", "HEAD"], capture_output=True, text=True,
|
|
check=True).stdout.strip()
|
|
|
|
|
|
def private_write(path: pathlib.Path, value: str) -> None:
|
|
with path.open("x") as out:
|
|
os.chmod(path, 0o600)
|
|
out.write(value)
|
|
|
|
|
|
def run_executor(name: str, audit, fail_checks=()) -> list[dict]:
|
|
"""One real executor run on the example admission with a window around now; returns the events it emitted."""
|
|
doc = G.X.bea.load(REPO / "examples/bounded-execution-admission/bind-rotation-svc-authelia.yaml")
|
|
now = dt.datetime.now(dt.UTC).replace(microsecond=0)
|
|
doc["spec"]["id"] = name
|
|
doc["spec"]["window"] = {"not_before": (now - dt.timedelta(minutes=5)).isoformat().replace("+00:00", "Z"),
|
|
"not_after": (now + dt.timedelta(hours=2)).isoformat().replace("+00:00", "Z")}
|
|
doc["spec"]["budget"]["attempts"] = 1 # a failed check then ends the run as reverted, not retried
|
|
doc["spec"]["plan_digest"] = X.bea.plan_digest(doc)
|
|
fake = G.Fake(doc, fail_checks)
|
|
seen: list[dict] = []
|
|
|
|
class Tee:
|
|
def emit(self, event):
|
|
audit.emit(event) # raises unless audit-core stored it
|
|
seen.append(event)
|
|
|
|
plan = doc["spec"]["plan"]
|
|
deps = X.Deps(carrier=fake, operations={s["operation"]: fake.op for s in plan["steps"] + plan["revert_steps"]},
|
|
checks={c["id"]: fake.check for c in doc["spec"]["checks"]}, trust=lambda _s: True, audit=Tee(),
|
|
alert=lambda *_a: None, sleep=lambda _s: None)
|
|
cfg = X.Config(executor_identity=doc["spec"]["executor"]["identity"],
|
|
runtime_principal="system:serviceaccount:sso:nk-bind-executor", tenant="platform",
|
|
environment="TEST", approver_subjects={"admin": "kc-sub-0f3a"},
|
|
approver_issuer="https://kc.coulomb.social")
|
|
run = X.Run(doc, G.APPROVAL, cfg, deps)
|
|
run.preflight = lambda: run._check_approval(fake.get(G.APPROVAL), consumed_ok=False) # pins are the example's
|
|
result = run.execute()
|
|
assert result["outcome"] in ("completed", "reverted") and "reason" not in result, result.get("reason")
|
|
if G.MARKER in json.dumps(seen):
|
|
raise RuntimeError("an operation value reached an event")
|
|
return seen
|
|
|
|
|
|
def exercise(args) -> dict:
|
|
sys.path.insert(0, str(args.audit_core_source))
|
|
from audit_core.ingestion import IngestionApplication
|
|
from audit_core.senders import SenderRegistry
|
|
from audit_core.sqlite_backend import SQLiteAuditBackend
|
|
|
|
scope = args.audit_core_source / "deploy/senders-scope.json"
|
|
receipt = {
|
|
"schema": "netkingdom.bounded-executor.audit-sender-exercise/v1",
|
|
"task": "NK-WP-0056-T15",
|
|
"target": "disposable local processes; synthetic tokens",
|
|
"started_at": dt.datetime.now(dt.UTC).isoformat(timespec="seconds"),
|
|
"audit_core_commit": commit(args.audit_core_source),
|
|
"senders_scope_sha256": hashlib.sha256(scope.read_bytes()).hexdigest(),
|
|
"net_kingdom_commit": commit(REPO),
|
|
"executor_source_sha256": {n: hashlib.sha256((HERE / n).read_bytes()).hexdigest()
|
|
for n in ("executor.py", "clients.py")},
|
|
"receipt_schema_sha256": hashlib.sha256(V.SCHEMA.read_bytes()).hexdigest(),
|
|
"limitations": [
|
|
"audit-core runs from source in-process (SQLite backend), not the released receiver image",
|
|
"the executor's operations are in-memory; the bind rotation itself is T12's synthetic pilot",
|
|
"plain HTTP on loopback; the live path is the ESO-delivered bearer over the sso ingress (T16)",
|
|
],
|
|
"checks": {},
|
|
}
|
|
checks = receipt["checks"]
|
|
tokens = {"executor": secrets.token_urlsafe(32), "operator": secrets.token_urlsafe(32),
|
|
"other": secrets.token_urlsafe(32)}
|
|
with tempfile.TemporaryDirectory(prefix="nk56-t15-") as temporary:
|
|
root = pathlib.Path(temporary)
|
|
registry = SenderRegistry.from_env({
|
|
# Minted with the defaults on purpose, as audit-core's own test does: the Git overlay must narrow it.
|
|
"AUDIT_CORE_SENDERS": json.dumps([
|
|
{"name": "nk-bounded-executor", "tokens": [tokens["executor"]], "sources": ["*"], "tenants": ["*"]},
|
|
{"name": "user-engine", "tokens": [tokens["other"]], "sources": ["user-engine"]},
|
|
{"name": "operator", "tokens": [tokens["operator"]], "sources": ["*"], "tenants": ["*"],
|
|
"may_write": False, "may_read": True}]),
|
|
"AUDIT_CORE_SENDERS_SCOPE_PATH": str(scope)})
|
|
backend = SQLiteAuditBackend(str(root / "audit.db"))
|
|
app = IngestionApplication(backend, registry)
|
|
|
|
class Quiet(WSGIRequestHandler):
|
|
def log_message(self, *_args):
|
|
pass
|
|
|
|
server = make_server("127.0.0.1", 0, app, handler_class=Quiet)
|
|
threading.Thread(target=server.serve_forever, daemon=True).start()
|
|
base = f"http://127.0.0.1:{server.server_port}"
|
|
try:
|
|
for name, value in tokens.items():
|
|
private_write(root / f"{name}.token", value + "\n")
|
|
audit = C.AuditCore(base, str(root / "executor.token"))
|
|
|
|
def raw(event, token: str | None = "executor", path="/v1/events", method="POST"):
|
|
headers = {"Content-Type": "application/json"}
|
|
if token:
|
|
headers["Authorization"] = "Bearer " + (root / f"{token}.token").read_text().strip() \
|
|
if token in tokens else "Bearer " + token
|
|
if event is not None:
|
|
headers["Idempotency-Key"] = event["id"]
|
|
req = urllib.request.Request(base + path, method=method, headers=headers,
|
|
data=json.dumps(event).encode() if event is not None else None)
|
|
try:
|
|
with urllib.request.urlopen(req, timeout=5) as response:
|
|
return response.status, json.loads(response.read() or b"{}")
|
|
except urllib.error.HTTPError as error:
|
|
with error:
|
|
return error.code, json.loads(error.read() or b"{}")
|
|
|
|
# ---- real runs through the real client
|
|
first_status = []
|
|
original = audit.http.call
|
|
|
|
def recording(method, path, body=None, headers=None, scope=None):
|
|
status, data = original(method, path, body, headers, scope)
|
|
first_status.append(status)
|
|
return status, data
|
|
|
|
audit.http.call = recording
|
|
completed = run_executor("BEA-2026-9201", audit)
|
|
reverted = run_executor("BEA-2026-9202", audit, fail_checks=["synthetic-journey"])
|
|
audit.http.call = original
|
|
assert first_status == [202, 202, 202, 202], first_status
|
|
assert completed[1]["data"]["outcome"] == "completed" and reverted[1]["data"]["outcome"] == "reverted"
|
|
checks["completed_run_started_and_receipt_accepted_202"] = True
|
|
checks["reverted_run_started_and_receipt_accepted_202"] = True
|
|
for started, final in (completed, reverted):
|
|
assert V.evaluate_pair(started, final) == {"valid": True, "findings": []}
|
|
checks["emitted_pairs_pass_receipt_schema_v0_1"] = True
|
|
|
|
assert raw(completed[0])[0] == 200 and raw(completed[1])[0] == 200
|
|
audit.emit(completed[1]) # the client's own retry path counts 200 as stored
|
|
checks["retry_is_duplicate_200"] = True
|
|
|
|
changed = copy.deepcopy(completed[1])
|
|
changed["data"]["outcome"] = "stopped"
|
|
assert raw(changed)[0] == 409
|
|
try:
|
|
audit.emit(changed)
|
|
except C.ClientError as error:
|
|
assert "409" in str(error) and "run id reused" in str(error)
|
|
else:
|
|
raise AssertionError("conflict accepted")
|
|
checks["same_id_other_body_conflict_409_reported_as_defect"] = True
|
|
|
|
def fresh(event, **over):
|
|
e = copy.deepcopy(event)
|
|
rid = str(uuid.uuid4())
|
|
e.update(id=f"run:{rid}:started", correlation_id=rid, **over)
|
|
e["data"]["run_id"] = rid
|
|
return e
|
|
|
|
refused = {
|
|
"wrong_source": (fresh(completed[0], source="user-engine"), "executor"),
|
|
"wrong_tenant": (fresh(completed[0], tenant="tenant:platform"), "executor"),
|
|
"other_sender_token": (fresh(completed[0]), "other"),
|
|
"no_token": (fresh(completed[0]), None),
|
|
"unknown_token": (fresh(completed[0]), "synthetic-unknown-token"),
|
|
}
|
|
for label, (event, token) in refused.items():
|
|
status, _ = raw(event, token)
|
|
assert status in (400, 401, 403), (label, status)
|
|
for event, _ in refused.values(): # nothing stored under a refused event's run id
|
|
status, body = raw(None, "operator", "/v1/events?correlation_id=" + event["correlation_id"], "GET")
|
|
assert status == 200 and body["events"] == [], status
|
|
status, body = raw(None, "operator", "/v1/events?correlation_id=" + completed[0]["correlation_id"], "GET")
|
|
assert status == 200 and len(body["events"]) == 2, status # while the accepted run's events are held
|
|
checks["wrong_source_tenant_or_token_refused_and_not_stored"] = True
|
|
|
|
# ---- a started event without a receipt, whose admission window closed two hours ago
|
|
orphan = fresh(completed[0])
|
|
orphan["data"]["window"]["not_after"] = (dt.datetime.now(dt.UTC) - dt.timedelta(hours=2)) \
|
|
.isoformat(timespec="seconds").replace("+00:00", "Z")
|
|
audit.emit(orphan)
|
|
status, body = raw(None, "operator", "/v1/stream-findings", "GET")
|
|
assert status == 200
|
|
opened = [f for f in body.get("stream_findings", []) if f.get("kind") == "opening_without_closing"
|
|
and f.get("class") == "execution.run.started"]
|
|
flagged = json.dumps(opened)
|
|
mine = [f for f in opened if f.get("correlation_id") == orphan["correlation_id"]]
|
|
assert len(mine) == 1 and mine[0]["bound"] == "window.not_after", "orphan not flagged by its window"
|
|
assert completed[0]["correlation_id"] not in flagged and reverted[0]["correlation_id"] not in flagged
|
|
checks["started_without_receipt_flagged_by_its_own_window"] = True
|
|
checks["closed_runs_not_flagged"] = True
|
|
finally:
|
|
server.shutdown()
|
|
server.server_close()
|
|
|
|
receipt.update(status="passed", cleanup_complete=True,
|
|
finished_at=dt.datetime.now(dt.UTC).isoformat(timespec="seconds"))
|
|
if any(v in json.dumps(receipt) for v in tokens.values()):
|
|
raise RuntimeError("receipt would carry a generated value")
|
|
return receipt
|
|
|
|
|
|
def main(argv=None) -> int:
|
|
parser = argparse.ArgumentParser(description=__doc__.split("\n", 1)[0])
|
|
parser.add_argument("--audit-core-source", type=pathlib.Path, default=pathlib.Path.home() / "audit-core")
|
|
parser.add_argument("--receipt", required=True, type=pathlib.Path)
|
|
parser.add_argument("--trace", action="store_true", help="print the failing line (no values)")
|
|
args = parser.parse_args(argv)
|
|
if args.receipt.exists():
|
|
raise SystemExit("receipt path must be new")
|
|
try:
|
|
receipt = exercise(args)
|
|
except Exception as error:
|
|
where = ""
|
|
if args.trace:
|
|
tb, line = error.__traceback__, None
|
|
while tb:
|
|
if tb.tb_frame.f_code.co_filename == __file__:
|
|
line = tb.tb_lineno
|
|
tb = tb.tb_next
|
|
detail = str(error) if isinstance(error, AssertionError) else "" # own messages: statuses, labels
|
|
where = f" at line {line} {detail}".rstrip()
|
|
print(f"Audit sender exercise failed: {type(error).__name__}{where}", file=sys.stderr)
|
|
return 1
|
|
private_write(args.receipt, json.dumps(receipt, indent=2) + "\n")
|
|
print(f"Audit sender exercise passed: {len(receipt['checks'])} checks; temporary resources removed.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|