ops-warden/docs/adr/ADR-0003-cover-gaps-never-silently-own-them.md
tegwick 35aff380a3
All checks were successful
CI Smoke / host-smoke (push) Successful in 0s
CI Smoke / container-smoke (push) Successful in 1s
Lift ops-warden's binding rules into owned ADRs
Five rules that governed this repo lived in wiki prose, a workplan, and a
comment at the top of catalog.yaml. All were followed; none was addressable.
A reader outside ops-warden could not cite one, could not tell whether it was
current, and — the point of this change — could not tell whether it was ours
to change or someone else's that we merely obey.

  ADR-0001  The routing catalog is a pointer layer, never a second copy
  ADR-0002  ops-warden is a transparent conduit, never a secret broker
  ADR-0003  Cover gaps, but never silently own them
  ADR-0004  High-risk lanes refuse raw value streaming to agent sessions
  ADR-0005  Implement one lane narrowly, route everything else

Each carries owner: ops-warden, which is the load-bearing field. It says we
follow the rule AND we are responsible for changing it — by superseding ADR,
never an in-place edit. The failure this prevents runs both ways: a rule we own
mistaken for inherited canon never gets fixed, because we wait for an owner who
does not exist; inherited canon mistaken for ours gets quietly bent, and the
drift is invisible until it breaks a repo that trusted the canonical version.

Rules we follow but do not own — NetKingdom canon, the IAM profile, the
credential-management standard, the-custodian's ADR-001 workplan convention —
are cited, never copied into docs/adr/. Copying them would recreate exactly the
second-source-of-truth failure ADR-0001 exists to prevent. architecture.md also
now flags the three-digit/four-digit ADR-001 vs ADR-0001 collision, which is
itself an ours-versus-inherited confusion waiting to happen.

Publication verified rather than assumed: all five render through policy-nexus
tools/render.py, and owner reaches the reader in three places — the page eyebrow
(render.py:346), the index Owner column (build_site.py:123,137), and the
publication manifest. build_site.py:179 makes title/status/owner required, so
ownership cannot be dropped on the way out. policy-nexus publishes and never
writes back; docs/adr/ stays the source of truth.

Documentation adapted: SCOPE.md gains a Governing rules section and an orientation
entry; .claude/rules/architecture.md replaced its stub with the ADR index, the
owned-versus-inherited rule, and ADR-over-wiki precedence; finding-routing.md's
ADR-gap section closed; catalog.yaml's no-double-source header now cites ADR-0001
rather than the originating workplan.

uv run pytest -q → 338 passed, 4 deselected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 13:35:13 +02:00

95 lines
3.9 KiB
Markdown

---
id: ops-warden-adr-0003
type: adr
title: "ADR-0003 — Cover gaps, but never silently own them"
domain: infotech
repo: ops-warden
status: accepted
version: "1.0"
revision: "1"
owner: ops-warden
binds: "ops-warden"
created: "2026-07-01"
updated: "2026-08-18"
last_reviewed: "2026-08-18"
review_interval: 6m
enforced_by: "registry/routing/catalog.yaml delegation:; warden route gaps"
supersedes: ""
successor: ""
---
# ADR-0003 — Cover gaps, but never silently own them
## Status
Accepted. Stated as INTENT §9, made structural by WARDEN-WP-0030 (delegation
register).
## Context
ops-warden owns exactly one lane: SSH certificate issuance. It nonetheless fronts
around eleven credential lanes as a caller-identity proxy, because no other
component fronts them yet and a worker blocked on a credential is a worker blocked.
Covering a gap is legitimate and this repo intends to keep doing it. The failure is
subtler: **a cover that is never recorded as a cover becomes ownership by default.**
Nobody decides to permanently own another component's lane. It happens because the
interim arrangement worked, nobody wrote down that it was interim, and the intended
owner never learned they were expected to build a front door.
By August 2026 the primitive to hand a lane back existed and was proven — `exec_owner`
/ `exec_command`, used by secrets-engine for npm publish and by the railiance-platform
credential broker for warden-sign — and was used by 2 of 24 lanes. The other
twenty-two had no record of who *should* own them.
## Decision
**Every catalog entry carries a `delegation:` block**, with a `mode:` of:
| `mode` | Meaning |
| --- | --- |
| `permanent` | Ours forever. SSH certificate issuance, and nothing else |
| `native` | The owner has a front door; we route to it and execute nothing |
| `interim` | We are covering a gap. Requires `intended_owner:` and `blocked_on:` |
**`interim` without an `intended_owner` is not permitted.** If we cannot name who
should own it, we have not understood the lane well enough to be fronting it.
**`blocked_on:` must name a specific condition, not a mood.** "No front door yet" is
not a blocker; "secrets-engine has not confirmed whether `exec --catalog` generalizes
over arbitrary OpenBao lanes (asked 2026-08-11, msg 7d55d332)" is. A blocker with a
question and a date can be chased. A blocker without one is an excuse with a
timestamp.
**The interim set is queryable**: `warden route gaps` lists it with review dates and
staleness. A cover that nobody can enumerate is a cover nobody will retire.
**A blocker is a claim about the world at a date, and expires.** `reviewed:` is
bumped only on a real re-check, never inherited. This was learned the hard way:
`RISK-F-0001` invalidated one of our blockers within a day and nothing would have
re-checked it.
## Consequences
**Retiring a cover is a normal, expected event rather than a renegotiation.** When
an owner ships their front door the lane flips `interim``native`. This has
happened twice and both were routine.
**Other repos can see what we are holding for them.** The register is why key-cape
and user-engine were able to confirm or decline lanes in August 2026 — the question
was answerable because it had been written down. One of those answers was "not ours",
which is a legitimate and useful outcome.
**We accept looking worse than we are.** `warden route gaps` publishes a list of
things this repo is doing that it would rather not be doing. That is the intent: the
alternative is a repo that looks clean because nobody counted.
**This register is not a risk register.** Interim lanes are tracked work with an
owner and a date, not defects. They do not get bulk-filed into `risk-nexus`, which
needs to stay small enough to read. Defects go there; gaps stay here.
## Related
- `INTENT.md` §9 — the principle this formalizes
- `history/2026-08-11-delegation-surface-assessment.md` — the assessment that forced it
- `.claude/rules/finding-routing.md` — the register-versus-findings boundary