diff --git a/.claude/rules/architecture.md b/.claude/rules/architecture.md index 6f6cde3..6e11407 100644 --- a/.claude/rules/architecture.md +++ b/.claude/rules/architecture.md @@ -15,7 +15,6 @@ distinction that matters here. | `ADR-0005` | Implement one lane narrowly, route everything else | | `ADR-0006` | Enforcement is zone-scoped, never a global flag | | `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade | -| `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after | ### Owned versus inherited — check `owner:` before changing anything diff --git a/SCOPE.md b/SCOPE.md index 3581a9e..401afef 100644 --- a/SCOPE.md +++ b/SCOPE.md @@ -155,7 +155,6 @@ superseding ADR, never an in-place edit. | `ADR-0005` | Implement one lane narrowly, route everything else | | `ADR-0006` | Enforcement is zone-scoped, never a global flag (`policy.enabled` stays off until WP-0032) | | `ADR-0007` | Build-stage permissiveness stops at credential disclosure; every lane carries an explicit `risk` grade | -| `ADR-0008` | A lane's risk grade covers every field its path discloses, not just the field it is named after | Rules we follow but do not own — NetKingdom canon, the IAM profile, the credential-management standard, the-custodian's ADR-001 workplan convention — are diff --git a/WORK-RECORDS.md b/WORK-RECORDS.md index 6e1015a..017cfb8 100644 --- a/WORK-RECORDS.md +++ b/WORK-RECORDS.md @@ -29,7 +29,6 @@ | workplan | WARDEN-WP-0030 | finished | — | workplans/WARDEN-WP-0030-delegation-register.md | | workplan | WARDEN-WP-0031 | finished | — | workplans/WARDEN-WP-0031-policy-caller-identity.md | | workplan | WARDEN-WP-0032 | proposed | — | workplans/WARDEN-WP-0032-security-zones.md | -| workplan | WARDEN-WP-0033 | active | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | | task | ADHOC-2026-06-27-T01 | done | — | workplans/ADHOC-2026-06-27.md | | task | ADHOC-2026-06-29-T01 | done | — | workplans/ADHOC-2026-06-29.md | | task | ADHOC-2026-08-11-T01 | done | — | workplans/ADHOC-2026-08-11.md | @@ -122,7 +121,3 @@ | task | WARDEN-WP-0032-T04 | wait | — | workplans/WARDEN-WP-0032-security-zones.md | | task | WARDEN-WP-0032-T05 | done | — | workplans/WARDEN-WP-0032-security-zones.md | | task | WARDEN-WP-0032-T06 | progress | — | workplans/WARDEN-WP-0032-security-zones.md | -| task | WARDEN-WP-0033-T01 | progress | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| task | WARDEN-WP-0033-T02 | todo | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| task | WARDEN-WP-0033-T03 | todo | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | -| task | WARDEN-WP-0033-T04 | todo | — | workplans/WARDEN-WP-0033-native-lane-handoff.md | diff --git a/docs/adr/ADR-0008-grade-the-path-not-the-field.md b/docs/adr/ADR-0008-grade-the-path-not-the-field.md deleted file mode 100644 index 6c2f317..0000000 --- a/docs/adr/ADR-0008-grade-the-path-not-the-field.md +++ /dev/null @@ -1,94 +0,0 @@ ---- -id: ops-warden-adr-0008 -type: adr -title: "ADR-0008 — A lane's risk grade covers every field its path discloses" -domain: infotech -repo: ops-warden -status: accepted -version: "1.0" -revision: "1" -owner: ops-warden -binds: "ops-warden" -created: "2026-08-21" -updated: "2026-08-21" -last_reviewed: "2026-08-21" -review_interval: 6m -enforced_by: "registry/routing/catalog.yaml fields + risk; tests/test_routing.py::test_high_risk_lanes_classified" -supersedes: "" -successor: "" ---- - -# ADR-0008 — A lane's risk grade covers every field its path discloses - -## Status - -Accepted 2026-08-21, after `secrets-engine` found two under-graded lanes while -reviewing ops-warden's own catalog metadata. - -## Context - -`ADR-0007` requires every catalog lane to carry an explicit `risk` grade. It does -not say what the grade is *of*, and the omission turned out to matter. - -The catalog describes a lane by a single `fetch_command` naming a single field — -`bao kv get -field=ISSUE_CORE_API_KEY `. Grading followed that description. -But the unit of disclosure is not the field, it is the **path**: `bao kv get` -without `-field` returns every key stored there, and an agent session that -discloses one field has disclosed all of them. - -On 2026-08-19, grading all 27 lanes, ops-warden graded -`issue-core-ingestion-api-key` and `reuse-surface-hub-write-token` as `standard` -— "ordinary internal workload secrets". Both grades read only the headline field. -`CCR-2026-0002` records a deliberate decision to keep `GITEA_BACKEND_TOKEN` at the -first path; `CCR-2026-0005` declares a dual-consumer webhook HMAC at the second. -Neither is recovered by rotating the credential the lane is named after. - -Three details make this worth a record rather than a fix: - -- **The evidence was already ours.** The field sets were in the CCRs the catalog - already cites as authoritative. This was not missing data; it was unread data. -- **A test held the error still.** `test_high_risk_lanes_classified` asserted - `issue-core-ingestion-api-key` was *not* high. A first grading pass had marked - it high, the test contradicted it, and the test was believed. A test that - encodes a judgement defends that judgement from correction. -- **Another repo found it.** `secrets-engine` graded both `high` independently - while drafting catalog entries whose schema records `fields`. A schema that - names the field set makes the right grade obvious; ours did not have one. - -## Decision - -**A lane's `risk` grade is a property of its path, and must cover the union of -everything a read of that path would disclose.** - -1. Where the field set is known, the catalog records it as `fields`, with the - authority it came from. -2. The grade is argued against the most damaging field, not the named one. -3. Where the field set is unknown, that is stated — never assumed to be one - field. An unverified field set is a reason to grade conservatively, matching - the `inter-hub-bootstrap-ssh` precedent under `ADR-0007`. -4. Establishing a field set must not be done by reading the secret. Use the - owning CCR, the owner's catalog, or `bao kv metadata`. `bao kv get` on a - high-risk path is the 2026-07-16 vector and is forbidden by - `ADR-0004` for agent sessions regardless of intent. - -## Consequences - -`ADR-0007` is unchanged and still governs: every lane carries an explicit grade, -and absence fails safe. This record says what that grade must account for. - -Grading gets more expensive: it now requires knowing what is at a path, not just -what the lane is called. That cost is the point — the cheap version produced two -wrong answers in one pass and is the reason this exists. - -A test that asserts a grade is asserting a judgement. When a grade is disputed, -re-argue it from evidence before trusting the test that encodes it. - -## Related - -- `ADR-0007` — every lane carries an explicit grade; build-stage permissiveness - stops at credential disclosure -- `ADR-0004` — high-risk lanes refuse raw value streaming to agent sessions -- `ADR-0001` — the catalog is a pointer layer; `fields` records the owner's - declared field set with its source, and does not restate their procedure -- `WARDEN-WP-0033-T02`; `secrets-engine` `SECRETS-WP-0006` -- `history/2026-07-16-credential-disclosure-lessons.md` diff --git a/docs/adr/README.md b/docs/adr/README.md index f66f115..8d0bc11 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -69,4 +69,3 @@ and the publication is a defect. | `ADR-0005` | Implement one lane narrowly, route everything else | ops-warden | | `ADR-0006` | Enforcement is zone-scoped, never a global flag | ops-warden | | `ADR-0007` | Build-stage permissiveness stops at credential disclosure | ops-warden | -| `ADR-0008` | A lane's risk grade covers every field its path discloses | ops-warden | diff --git a/registry/generated/high-risk-data-paths.yaml b/registry/generated/high-risk-data-paths.yaml deleted file mode 100644 index 26411b4..0000000 --- a/registry/generated/high-risk-data-paths.yaml +++ /dev/null @@ -1,100 +0,0 @@ -# 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/registry/routing/catalog.yaml b/registry/routing/catalog.yaml index 6658187..893a399 100644 --- a/registry/routing/catalog.yaml +++ b/registry/routing/catalog.yaml @@ -283,16 +283,9 @@ entries: - id: issue-core-ingestion-api-key title: issue-core ingestion API key (OpenBao KV + ESO) - # Regraded standard -> high 2026-08-21 (WARDEN-WP-0033-T02). The T05 grade below - # was wrong, and wrong systematically: it graded the headline field, not the path. - # was: "Ordinary internal workload secret: an ingestion key for a first-party - # service. Rotatable, no spend, no tenant data, no admin scope." - # CCR-2026-0002 records a deliberate field-set decision to keep GITEA_BACKEND_TOKEN - # at this path alongside the ingestion key, and a read discloses every field there. - # A Forgejo backend token is not recovered by rotating an ingestion key. - # Found by secrets-engine reviewing SECRETS-WP-0006 -- not by us. - risk: high - fields: [ISSUE_CORE_API_KEY, GITEA_BACKEND_TOKEN] # CCR-2026-0002 + # Ordinary internal workload secret: an ingestion key for a first-party + # service. Rotatable, no spend, no tenant data, no admin scope (WARDEN-WP-0032-T05). + risk: standard need_keywords: [issue-core, ingestion, api, key, openbao, issue_core_api_key, eso, external-secrets] owner_repo: railiance-platform subsystem: OpenBao + issue-core + activity-core @@ -330,16 +323,9 @@ entries: - id: reuse-surface-hub-write-token title: reuse-surface federation hub write bearer token - # Regraded standard -> high 2026-08-21 (WARDEN-WP-0033-T02), same defective T05 - # pass as issue-core-ingestion-api-key. - # was: "Ordinary internal workload secret. Write access to a first-party - # federation surface; damaging to forge, but rotatable and internal." - # CCR-2026-0005 declares REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET at this path: a - # dual-consumer HMAC that must stay aligned with Forgejo org webhook id=1 on - # rotation. Disclosure lets an attacker forge webhook deliveries into the - # federation hub, which rotating the write token alone does not undo. - risk: high - fields: [REUSE_SURFACE_TOKEN, REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET] # CCR-2026-0005 + # Ordinary internal workload secret. Write access to a first-party federation + # surface; damaging to forge, but rotatable and internal (WARDEN-WP-0032-T05). + risk: standard need_keywords: [reuse-surface, reuse_surface, hub, register, federation, write, token, bearer, REUSE_SURFACE_TOKEN, REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET, reuse.coulomb.social] owner_repo: railiance-platform subsystem: OpenBao + reuse-surface diff --git a/scripts/emit_high_risk_paths.py b/scripts/emit_high_risk_paths.py deleted file mode 100755 index d8fbe33..0000000 --- a/scripts/emit_high_risk_paths.py +++ /dev/null @@ -1,155 +0,0 @@ -#!/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 ec697d1..db33c1f 100644 --- a/tests/test_agent_read_boundary_check.py +++ b/tests/test_agent_read_boundary_check.py @@ -98,34 +98,3 @@ 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/tests/test_routing.py b/tests/test_routing.py index fe28958..e0d5d33 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -543,13 +543,8 @@ def test_high_risk_lanes_classified(): assert "railiance-backup-offsite-lane" in high assert "forgejo-admin-api-token" in high assert "openrouter-llm-connect" in high - # WARDEN-WP-0033-T02: these two were asserted standard here, and the assertion - # held a defective grade still. Both paths carry a second credential the grade - # ignored -- GITEA_BACKEND_TOKEN (CCR-2026-0002) and the dual-consumer webhook - # HMAC (CCR-2026-0005). A read discloses every field at a path, so the grade - # must cover the union, not the headline field. - assert "issue-core-ingestion-api-key" in high - assert "reuse-surface-hub-write-token" in high + # Ordinary workload secrets stay standard unless reclassified. + assert catalog.get("issue-core-ingestion-api-key").is_high_risk is False def test_invalid_risk_rejected(tmp_path): diff --git a/workplans/WARDEN-WP-0033-native-lane-handoff.md b/workplans/WARDEN-WP-0033-native-lane-handoff.md index 52eb47d..c8bb760 100644 --- a/workplans/WARDEN-WP-0033-native-lane-handoff.md +++ b/workplans/WARDEN-WP-0033-native-lane-handoff.md @@ -41,7 +41,7 @@ work completed directly; this is none of those. ```task id: WARDEN-WP-0033-T01 -status: done +status: progress priority: high ``` @@ -62,7 +62,7 @@ applied to ops-warden by someone else, correctly. ```task id: WARDEN-WP-0033-T02 -status: done +status: todo priority: high ``` @@ -89,33 +89,9 @@ disclose, and it should. Fix the grade, then fix the model that produced it: record `fields` per lane and grade on the union. Check the other 25 lanes for the same shape. -**Done 2026-08-21 — and it was two lanes, not one.** While verifying -`reuse-surface-hub-write-token` I told secrets-engine their second field was -unevidenced and asked them to confirm it. It was evidenced: `CCR-2026-0005`'s -`kv_path` block lists both fields and its notes describe the HMAC's alignment -with Forgejo org webhook id=1. A truncated grep, and a retraction sent within -twenty minutes so they would not spend time on it. That lane was under-graded on -exactly the same reasoning, and both had been regraded *down* in the same T05 -pass — which is what makes this a systematic flaw rather than one bad call. - -Blast radius checked and bounded: of the 8 remaining `standard` lanes, 6 have no -KV path at all (SSH, policy check, tunnel, principals, issue-sink, key-cape -login) so nothing is disclosed by a read. Two do have paths and no CCR field -evidence — `rein-openweights-openrouter-approle` and `coulomb-social-runtime-env` -(a whole k8s env Secret, which by shape almost certainly holds several fields). -Deliberately **not** regraded: `ADR-0008` §3 says an unknown field set is stated -rather than assumed, and grading is operator-sanctioned judgement, not backfill. -They are the open item on this task. - -Shipped: both regrades with their evidence and the superseded reasoning kept -inline, `fields` recorded with its CCR source, the test that had been holding the -wrong answer inverted with an explanation of why it was wrong to trust it, and -**`ADR-0008`** — a lane's grade covers every field its path discloses. That is a -new binding rule, so it is a record rather than a wiki note. - ```task id: WARDEN-WP-0033-T03 -status: done +status: todo priority: high ``` @@ -133,30 +109,9 @@ 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: wait +status: todo priority: medium ``` @@ -177,23 +132,6 @@ Note the interaction with `ADR-0004`: the agent read-boundary already depends on side. A real issuance identity is what would make that boundary hold on the OpenBao side too, so ops-warden is an interested consumer, not a bystander. -**Routed 2026-08-21 to `key-cape` (msg 903b2223); waiting on accept or refuse.** -Reasoning given: it is an identity and issuance question about a principal -authenticating to OpenBao, which is key-cape/Keycloak's. The precedent is an hour -old and runs the same direction — `secrets-engine` declined `key-cape-oidc-login` -as ops-warden's to hand them, on the grounds that login and identity-token -issuance stay with key-cape. Absorbing this would contradict agreeing with them. - -Named the adjacent parties explicitly rather than leaving them to inference: -`zone-engine` has an interest (a coding-agent identity is a strong candidate zone -subject) and `user-engine` is **not** involved — this is a machine principal, not -an end-user account. Stated so nobody concludes it by elimination. - -Asked for a refusal-with-pointer as an equally good answer. The failure mode to -avoid is the 2026-08-17 one recorded in `.claude/rules/finding-routing.md`: -ops-warden answered a question well and never routed it, and another repo ended -up filing it. - ## Related - `secrets-engine` `SECRETS-WP-0006` — catalog admission, decision `ae676382`