From 6e1d5201aa0d9f770666707a85664b27b090cec7 Mon Sep 17 00:00:00 2001 From: tegwick Date: Fri, 21 Aug 2026 08:38:13 +0200 Subject: [PATCH] WARDEN-WP-0033-T03: emit the high-risk data-path artifact railiance-platform asked for a generated list to consume instead of hand- maintaining agent-high-risk-boundary. Hand-maintaining it is what let the two lists drift for four lanes in RISK-F-0009. 19 high-risk lanes, 14 concrete data paths, 5 without a single KV address listed separately so absence does not read as omission. Carries catalog_revision and a dirty flag. fields is null where unestablished, never a one-element guess. The header states plainly that this is an input and not a policy: railiance- platform owns the deny set and may deny more, less, or dispute a grade. ADR-0002 survives the handoff. Two CI tests guard staleness, because a consumer applies this to a live control. Note the immediate consequence of T02: 2 uncovered against a policy they closed to 0 yesterday. Co-Authored-By: Claude Opus 5 --- registry/generated/high-risk-data-paths.yaml | 100 +++++++++++ scripts/emit_high_risk_paths.py | 155 ++++++++++++++++++ tests/test_agent_read_boundary_check.py | 31 ++++ .../WARDEN-WP-0033-native-lane-handoff.md | 23 ++- 4 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 registry/generated/high-risk-data-paths.yaml create mode 100755 scripts/emit_high_risk_paths.py diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml new file mode 100644 index 0000000..26411b4 --- /dev/null +++ b/registry/generated/high-risk-data-paths.yaml @@ -0,0 +1,100 @@ +# GENERATED by scripts/emit_high_risk_paths.py -- do not edit by hand. +# Concrete KV data paths for lanes ops-warden grades `risk: high`. +# +# This is an INPUT, not a policy. ops-warden states which paths it grades +# high; railiance-platform owns what agent-high-risk-boundary denies and may +# deny more, deny less, or dispute a grade (ADR-0002, ADR-0008). +# +# Grades cover every field a read of the path discloses, not the field the +# lane is named after (ADR-0008). `fields` is recorded where an owning CCR +# declares it, and is null where the field set has not been established -- +# null means unknown, never 'one field'. + +generated_at: "2026-08-21T06:37:27Z" +source: ops-warden/registry/routing/catalog.yaml +catalog_revision: "c357ce5908e562755f3c41c24074edc9d231e6ce" +catalog_revision_date: "2026-08-21T08:36:42+02:00" +catalog_dirty: false +high_risk_lane_count: 19 +concrete_path_count: 14 + +# Graded high but not a single KV address -- a routing pattern, a broker +# grant, or a non-KV lane. Nothing here for a policy to deny. +no_concrete_path: + - database-dynamic-credentials + - inter-hub-bootstrap-ssh + - object-storage-sts + - openbao-api-key + - ops-warden-warden-sign-token + +paths: + - id: agent-harness-binky-mail-approle + data_path: tenants/data/binky/company-email/imap + metadata_path: tenants/metadata/binky/company-email/imap + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: agent-harness-forgejo-deploy + data_path: platform/data/workloads/agent-harness/forgejo-deploy-key + metadata_path: platform/metadata/workloads/agent-harness/forgejo-deploy-key + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: audit-core-senders + data_path: platform/data/workloads/audit-core/senders + metadata_path: platform/metadata/workloads/audit-core/senders + owner_repo: ops-mason + fields: null # field set not established -- unknown, not one + - id: binky-company-email-imap + data_path: tenants/data/binky/company-email/imap + metadata_path: tenants/metadata/binky/company-email/imap + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: binky-qonto-api + data_path: tenants/data/binky/qonto-api + metadata_path: tenants/metadata/binky/qonto-api + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: email-connect-transactional + data_path: platform/data/workloads/email-connect/transactional + metadata_path: platform/metadata/workloads/email-connect/transactional + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: forgejo-admin-api-token + data_path: platform/data/workloads/forgejo/forgejo-admin + metadata_path: platform/metadata/workloads/forgejo/forgejo-admin + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: issue-core-ingestion-api-key + data_path: platform/data/workloads/issue-core/issue-core/issue-core-runtime + metadata_path: platform/metadata/workloads/issue-core/issue-core/issue-core-runtime + owner_repo: railiance-platform + fields: [ISSUE_CORE_API_KEY, GITEA_BACKEND_TOKEN] + - id: openrouter-llm-connect + data_path: platform/data/workloads/activity-core/llm-connect/llm-connect-provider-secrets + metadata_path: platform/metadata/workloads/activity-core/llm-connect/llm-connect-provider-secrets + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: railiance-backup-offsite-lane + data_path: platform/data/workloads/railiance/backup/offsite-lane + metadata_path: platform/metadata/workloads/railiance/backup/offsite-lane + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: rapp-qonto-keycape-client + data_path: platform/data/workloads/rapp-qonto/keycape-client + metadata_path: platform/metadata/workloads/rapp-qonto/keycape-client + owner_repo: key-cape + fields: null # field set not established -- unknown, not one + - id: reuse-surface-hub-write-token + data_path: platform/data/workloads/reuse/reuse-surface/runtime-secrets + metadata_path: platform/metadata/workloads/reuse/reuse-surface/runtime-secrets + owner_repo: railiance-platform + fields: [REUSE_SURFACE_TOKEN, REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET] + - id: scaleway-bootstrap + data_path: platform/data/workloads/railiance/scaleway/bootstrap + metadata_path: platform/metadata/workloads/railiance/scaleway/bootstrap + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one + - id: whynot-design-npm-publish + data_path: platform/data/workloads/coulomb/whynot-design/npm-publish + metadata_path: platform/metadata/workloads/coulomb/whynot-design/npm-publish + owner_repo: railiance-platform + fields: null # field set not established -- unknown, not one diff --git a/scripts/emit_high_risk_paths.py b/scripts/emit_high_risk_paths.py new file mode 100755 index 0000000..d8fbe33 --- /dev/null +++ b/scripts/emit_high_risk_paths.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +"""Emit the versioned high-risk data-path artifact (WARDEN-WP-0033-T03). + +`railiance-platform` asked for a generated list of concrete high-risk KV data +paths to consume, instead of hand-maintaining the deny set in +`agent-high-risk-boundary.hcl`. Hand-maintaining it is what let the two lists +drift for four lanes without anyone noticing (`RISK-F-0009`). + +**This artifact is an input, not a policy.** It states which paths ops-warden +grades high. It does not say what to deny -- railiance-platform owns that, and +`ADR-0002` keeps ops-warden a conduit rather than the author of another repo's +control. A consumer is free to deny more, deny less, or disagree with a grade. + +Carries the catalog git revision so a consumer can tell exactly what it was +derived from, and regenerate or diff against it. Read-only: it reads the catalog +and `git`, never OpenBao and never a secret value. +""" + +from __future__ import annotations + +import argparse +import subprocess +import sys +from datetime import datetime, timezone +from pathlib import Path + +REPO = Path(__file__).resolve().parent.parent +CATALOG = REPO / "registry" / "routing" / "catalog.yaml" +DEFAULT_OUT = REPO / "registry" / "generated" / "high-risk-data-paths.yaml" + + +def catalog_revision() -> tuple[str, str]: + """(commit, iso-date) of the last change to the catalog. Never guesses.""" + try: + out = subprocess.run( + ["git", "log", "-1", "--format=%H %cI", "--", str(CATALOG)], + cwd=REPO, capture_output=True, text=True, timeout=15, check=True, + ).stdout.strip() + commit, _, date = out.partition(" ") + return commit or "unknown", date or "unknown" + except (subprocess.SubprocessError, FileNotFoundError): + return "unknown", "unknown" + + +def dirty() -> bool: + """True if the catalog has uncommitted edits -- the revision would be a lie.""" + try: + out = subprocess.run( + ["git", "status", "--porcelain", "--", str(CATALOG)], + cwd=REPO, capture_output=True, text=True, timeout=15, check=True, + ).stdout.strip() + return bool(out) + except (subprocess.SubprocessError, FileNotFoundError): + return False + + +def build() -> tuple[str, int]: + import yaml + + entries = yaml.safe_load(CATALOG.read_text())["entries"] + commit, date = catalog_revision() + + rows, patternish = [], [] + for entry in sorted(entries, key=lambda e: e["id"]): + if entry.get("risk") != "high": + continue + template = entry.get("path_template") + data_path = _to_data_path(template) if template else None + if data_path is None: + patternish.append(entry["id"]) + continue + rows.append({ + "id": entry["id"], + "data_path": data_path, + "metadata_path": data_path.replace("/data/", "/metadata/", 1), + "fields": entry.get("fields"), + "owner_repo": entry.get("owner_repo"), + }) + + lines = [ + "# GENERATED by scripts/emit_high_risk_paths.py -- do not edit by hand.", + "# Concrete KV data paths for lanes ops-warden grades `risk: high`.", + "#", + "# This is an INPUT, not a policy. ops-warden states which paths it grades", + "# high; railiance-platform owns what agent-high-risk-boundary denies and may", + "# deny more, deny less, or dispute a grade (ADR-0002, ADR-0008).", + "#", + "# Grades cover every field a read of the path discloses, not the field the", + "# lane is named after (ADR-0008). `fields` is recorded where an owning CCR", + "# declares it, and is null where the field set has not been established --", + "# null means unknown, never 'one field'.", + "", + f"generated_at: \"{datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ')}\"", + "source: ops-warden/registry/routing/catalog.yaml", + f"catalog_revision: \"{commit}\"", + f"catalog_revision_date: \"{date}\"", + f"catalog_dirty: {str(dirty()).lower()}", + f"high_risk_lane_count: {len([e for e in entries if e.get('risk') == 'high'])}", + f"concrete_path_count: {len(rows)}", + "", + "# Graded high but not a single KV address -- a routing pattern, a broker", + "# grant, or a non-KV lane. Nothing here for a policy to deny.", + "no_concrete_path:", + ] + lines += [f" - {i}" for i in sorted(patternish)] or [" []"] + lines += ["", "paths:"] + for row in rows: + lines.append(f" - id: {row['id']}") + lines.append(f" data_path: {row['data_path']}") + lines.append(f" metadata_path: {row['metadata_path']}") + lines.append(f" owner_repo: {row['owner_repo']}") + if row["fields"]: + lines.append(f" fields: [{', '.join(row['fields'])}]") + else: + lines.append(" fields: null # field set not established -- unknown, not one") + return "\n".join(lines) + "\n", len(rows) + + +def _to_data_path(template: str) -> str | None: + import re + if re.search(r"[<>{}*]", template) or " " in template or template.startswith("k8s:"): + return None + mount, _, rest = template.partition("/") + return f"{mount}/data/{rest}" if rest else None + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--out", type=Path, default=DEFAULT_OUT) + parser.add_argument("--check", action="store_true", + help="exit 1 if the artifact on disk is stale (for CI)") + args = parser.parse_args() + + content, count = build() + + if args.check: + current = args.out.read_text() if args.out.exists() else "" + # generated_at always differs; compare everything else. + strip = lambda t: "\n".join(l for l in t.splitlines() if not l.startswith("generated_at:")) + if strip(current) != strip(content): + print(f"STALE: {args.out} does not match the catalog. Re-run without --check.") + return 1 + print(f"fresh: {args.out} matches the catalog ({count} concrete paths)") + return 0 + + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text(content) + print(f"wrote {args.out} — {count} concrete high-risk data paths") + if dirty(): + print(" ! catalog has uncommitted changes; catalog_revision does not describe it") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/tests/test_agent_read_boundary_check.py b/tests/test_agent_read_boundary_check.py index db33c1f..ec697d1 100644 --- a/tests/test_agent_read_boundary_check.py +++ b/tests/test_agent_read_boundary_check.py @@ -98,3 +98,34 @@ class TestAgainstTheRealCatalog: continue resolved = mod.to_data_path(template) assert resolved is None or resolved.count("/data/") == 1, entry["id"] + + +class TestGeneratedArtifact: + """The artifact railiance-platform consumes (WARDEN-WP-0033-T03). + + A consumer applies this to a live deny set, so staleness is the failure that + matters -- a path graded high after the last emit would silently not reach them. + """ + + def test_artifact_is_current(self): + import subprocess + + result = subprocess.run( + ["python3", str(REPO / "scripts" / "emit_high_risk_paths.py"), "--check"], + capture_output=True, text=True, timeout=60, + ) + assert result.returncode == 0, ( + f"{result.stdout}{result.stderr}\n" + "Re-run scripts/emit_high_risk_paths.py and commit the result." + ) + + def test_every_concrete_high_risk_lane_is_in_the_artifact(self): + import yaml + + catalog = yaml.safe_load((REPO / "registry" / "routing" / "catalog.yaml").read_text()) + artifact = yaml.safe_load( + (REPO / "registry" / "generated" / "high-risk-data-paths.yaml").read_text() + ) + emitted = {row["id"] for row in artifact["paths"]} | set(artifact["no_concrete_path"] or []) + graded_high = {e["id"] for e in catalog["entries"] if e.get("risk") == "high"} + assert graded_high == emitted, "a high-risk lane is missing from the generated artifact" diff --git a/workplans/WARDEN-WP-0033-native-lane-handoff.md b/workplans/WARDEN-WP-0033-native-lane-handoff.md index fc922f6..719205a 100644 --- a/workplans/WARDEN-WP-0033-native-lane-handoff.md +++ b/workplans/WARDEN-WP-0033-native-lane-handoff.md @@ -115,7 +115,7 @@ new binding rule, so it is a record rather than a wiki note. ```task id: WARDEN-WP-0033-T03 -status: todo +status: done priority: high ``` @@ -133,6 +133,27 @@ consumer can tell what it was derived from. Depends on T02 — publishing the de set before fixing the grades would ship the wrong list to a consumer who will apply it. +**Done 2026-08-21.** `scripts/emit_high_risk_paths.py` → +`registry/generated/high-risk-data-paths.yaml`: 19 high-risk lanes, 14 concrete +data paths, 5 with no single KV address listed separately so a consumer does not +read the absence as an omission. Carries `catalog_revision`, its date, and a +`catalog_dirty` flag — a revision that does not describe the file it came from is +worse than none. + +The T02 dependency was not theoretical. The two regrades land in this artifact, +and `check_agent_read_boundary.py` now reports **2 uncovered** against a policy +railiance-platform closed to 0 yesterday. Emitting before regrading would have +handed them a list that confirmed a coverage claim that had just stopped being +true. + +`fields` is emitted as `null` where no CCR declares the set, never as a +single-element guess — `ADR-0008` §3. Two CI tests: the artifact must be current, +and every high-risk lane must appear in it, so a lane graded high after the last +emit cannot silently fail to reach the consumer. + +Boundary held: the header states this is an input and not a policy, and that +railiance-platform may deny more, deny less, or dispute a grade (`ADR-0002`). + ```task id: WARDEN-WP-0033-T04 status: todo