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 <noreply@anthropic.com>
This commit is contained in:
parent
c357ce5908
commit
6e1d5201aa
4 changed files with 308 additions and 1 deletions
100
registry/generated/high-risk-data-paths.yaml
Normal file
100
registry/generated/high-risk-data-paths.yaml
Normal file
|
|
@ -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
|
||||||
155
scripts/emit_high_risk_paths.py
Executable file
155
scripts/emit_high_risk_paths.py
Executable file
|
|
@ -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())
|
||||||
|
|
@ -98,3 +98,34 @@ class TestAgainstTheRealCatalog:
|
||||||
continue
|
continue
|
||||||
resolved = mod.to_data_path(template)
|
resolved = mod.to_data_path(template)
|
||||||
assert resolved is None or resolved.count("/data/") == 1, entry["id"]
|
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"
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ new binding rule, so it is a record rather than a wiki note.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: WARDEN-WP-0033-T03
|
id: WARDEN-WP-0033-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
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
|
set before fixing the grades would ship the wrong list to a consumer who will
|
||||||
apply it.
|
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
|
```task
|
||||||
id: WARDEN-WP-0033-T04
|
id: WARDEN-WP-0033-T04
|
||||||
status: todo
|
status: todo
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue