audit-core/scripts/t02_synthetic_load_driver.py
tegwick 8c8bcf49ae
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
feat(AUDIT-WP-0008): add T02 synthetic load driver
Assistant: codex
Assistant-Model: gpt-5.6-sol
Assistant-Session: 01a02991-be07-7bb3-8b6d-e9701b5621de
2026-08-22 16:46:31 +02:00

252 lines
8.8 KiB
Python
Executable file

#!/usr/bin/env python3
"""Value-safe synthetic sender for the attended T02 lease-recovery exercise.
The driver implements the revision-pinned ``railiance.synthetic-load-driver``
interface. It reads a short-lived bearer only from a mounted file, sends one
immutable synthetic event, and emits only the exact evidence shape required by
the selected phase. It never prints request or response bodies.
"""
from __future__ import annotations
import argparse
import json
import os
import stat
import sys
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass
from pathlib import Path
from typing import Callable, Mapping
CONTRACT_ID = "RAILIANCE-WP-0024-T02-SYNTHETIC-01"
PHASES = ("baseline", "expect-unavailable", "expect-recovered", "cleanup")
TERMINAL_HTTP = {400, 401, 403, 409}
RETRYABLE_HTTP = {0, 500, 503}
MAX_RESPONSE_BYTES = 64 * 1024
class DriverError(RuntimeError):
"""A value-safe, operator-facing driver failure."""
@dataclass(frozen=True)
class Config:
base_url: str
token_file: Path
tenant: str
source: str
fixture_id: str
occurred_at: str
max_attempts: int
retry_interval: float
def _required(env: Mapping[str, str], name: str) -> str:
value = env.get(name, "").strip()
if not value:
raise DriverError(f"required configuration is missing: {name}")
return value
def _bounded_int(env: Mapping[str, str], name: str, default: int) -> int:
try:
value = int(env.get(name, str(default)))
except ValueError as exc:
raise DriverError(f"invalid numeric configuration: {name}") from exc
if not 1 <= value <= 30:
raise DriverError(f"configuration is outside its safe bound: {name}")
return value
def _bounded_float(env: Mapping[str, str], name: str, default: float) -> float:
try:
value = float(env.get(name, str(default)))
except ValueError as exc:
raise DriverError(f"invalid numeric configuration: {name}") from exc
if not 0 <= value <= 10:
raise DriverError(f"configuration is outside its safe bound: {name}")
return value
def load_config(env: Mapping[str, str]) -> Config:
base_url = _required(env, "AUDIT_T02_BASE_URL").rstrip("/")
parsed = urllib.parse.urlsplit(base_url)
if (
parsed.scheme not in {"http", "https"}
or not parsed.netloc
or parsed.username is not None
or parsed.password is not None
or parsed.query
or parsed.fragment
):
raise DriverError("AUDIT_T02_BASE_URL must be a credential-free HTTP(S) URL")
token_file = Path(_required(env, "AUDIT_T02_TOKEN_FILE"))
return Config(
base_url=base_url,
token_file=token_file,
tenant=_required(env, "AUDIT_T02_TENANT"),
source=_required(env, "AUDIT_T02_SOURCE"),
fixture_id=_required(env, "AUDIT_T02_FIXTURE_ID"),
occurred_at=_required(env, "AUDIT_T02_OCCURRED_AT"),
max_attempts=_bounded_int(env, "AUDIT_T02_MAX_ATTEMPTS", 6),
retry_interval=_bounded_float(env, "AUDIT_T02_RETRY_INTERVAL_SECONDS", 1.0),
)
def load_cleanup_fixture(env: Mapping[str, str]) -> str:
"""Cleanup owns no credential or server-side object; only the id is needed."""
return _required(env, "AUDIT_T02_FIXTURE_ID")
def read_bearer(path: Path) -> str:
try:
details = path.stat()
except OSError as exc:
raise DriverError("approved mounted bearer file is unavailable") from exc
mode = stat.S_IMODE(details.st_mode)
if not stat.S_ISREG(details.st_mode) or mode & 0o077:
raise DriverError("approved mounted bearer file must be private and regular")
try:
raw = path.read_text(encoding="utf-8")
except OSError as exc:
raise DriverError("approved mounted bearer file cannot be read") from exc
lines = raw.splitlines()
bearer = lines[0].strip() if len(lines) == 1 else ""
if not bearer or len(bearer) > 4096:
raise DriverError("approved mounted bearer file has an invalid shape")
return bearer
def event_payload(config: Config) -> dict[str, object]:
return {
"id": config.fixture_id,
"type": "audit.recovery.synthetic",
"source": config.source,
"subject": config.fixture_id,
"tenant": config.tenant,
"correlation_id": f"{config.fixture_id}:t02",
"occurred_at": config.occurred_at,
"data": {"exercise": "RAILIANCE-WP-0024-T02", "synthetic": True},
}
def request_event(config: Config) -> tuple[int, str | None]:
bearer = read_bearer(config.token_file)
payload = json.dumps(event_payload(config), separators=(",", ":")).encode("utf-8")
request = urllib.request.Request(
f"{config.base_url}/v1/events", data=payload, method="POST"
)
request.add_header("Authorization", f"Bearer {bearer}")
request.add_header("Content-Type", "application/json")
request.add_header("Idempotency-Key", config.fixture_id)
try:
with urllib.request.urlopen(request, timeout=10) as response:
status = response.status
body = response.read(MAX_RESPONSE_BYTES + 1)
except urllib.error.HTTPError as exc:
exc.close()
return exc.code, None
except (OSError, TimeoutError, urllib.error.URLError):
return 0, None
if len(body) > MAX_RESPONSE_BYTES:
raise DriverError("target response exceeded the safe size bound")
try:
value = json.loads(body or b"{}")
except json.JSONDecodeError as exc:
raise DriverError("target returned invalid JSON") from exc
application_status = value.get("status") if isinstance(value, dict) else None
return status, application_status if isinstance(application_status, str) else None
def _attempt(
config: Config,
phase: str,
requester: Callable[[Config], tuple[int, str | None]],
sleeper: Callable[[float], None],
) -> dict[str, object]:
last_status = 0
for attempt in range(1, config.max_attempts + 1):
http_status, application_status = requester(config)
last_status = http_status
if http_status in TERMINAL_HTTP:
raise DriverError(f"{phase} received terminal HTTP status {http_status}")
if phase == "expect-unavailable" and http_status == 503:
return {
"contract_id": CONTRACT_ID,
"fixture_id": config.fixture_id,
"status": "retryable_unavailable",
"http_status": 503,
"attempts": attempt,
"secret_values_observed": False,
}
if http_status in {200, 202}:
if application_status not in {"accepted", "duplicate"}:
raise DriverError(f"{phase} returned an unexpected application status")
if phase == "expect-unavailable":
raise DriverError("expect-unavailable observed a successful accept")
if phase == "baseline":
return {
"contract_id": CONTRACT_ID,
"fixture_id": config.fixture_id,
"status": "ready",
"secret_values_observed": False,
}
return {
"contract_id": CONTRACT_ID,
"fixture_id": config.fixture_id,
"status": application_status,
"http_status": http_status,
"attempts": attempt,
"secret_values_observed": False,
}
if http_status not in RETRYABLE_HTTP:
raise DriverError(f"{phase} received unexpected HTTP status {http_status}")
if attempt < config.max_attempts:
sleeper(config.retry_interval)
raise DriverError(f"{phase} exhausted its bounded attempts at HTTP status {last_status}")
def run_phase(
phase: str,
contract_id: str,
*,
env: Mapping[str, str] = os.environ,
requester: Callable[[Config], tuple[int, str | None]] = request_event,
sleeper: Callable[[float], None] = time.sleep,
) -> dict[str, object]:
if contract_id != CONTRACT_ID:
raise DriverError("requested contract id is not approved")
if phase == "cleanup":
return {
"contract_id": CONTRACT_ID,
"fixture_id": load_cleanup_fixture(env),
"status": "clean",
"secret_values_observed": False,
}
return _attempt(load_config(env), phase, requester, sleeper)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("phase", choices=PHASES)
parser.add_argument("--contract-id", required=True)
args = parser.parse_args(argv)
try:
result = run_phase(args.phase, args.contract_id)
except DriverError as exc:
print(f"synthetic-load driver refused: {exc}", file=sys.stderr)
return 2
print(json.dumps(result, sort_keys=True, separators=(",", ":")))
return 0
if __name__ == "__main__":
raise SystemExit(main())