the-custodian/history/20261003-security-gate-packets.md
codex 0aee8dc727
Some checks are pending
CI Smoke / host-smoke (push) Waiting to run
CI Smoke / container-smoke (push) Waiting to run
CUST-WP-0075: ranked decision packets, sweep results, qualifier sync gap
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 03:46:36 +02:00

170 lines
8.5 KiB
Markdown

---
id: 20261003-security-gate-packets
type: assessment
title: "What the dependency graph says about how security work gets blocked, and what to change"
date: "2026-10-03"
assessor: the-custodian (Claude)
audience: founder
status: recorded
related_workplans:
- CUST-WP-0075
- CUST-WP-0074
- CUST-WP-0072
- RPF-WP-0035
- WARDEN-WP-0027
origin: "Founder request 2026-10-03: use the dependency facility to shrink the blocked set and learn how to make the security infrastructure easier to maintain and extend."
---
# What the dependency graph says about security work
Snapshot 2026-10-03 from the workplan files of 145 registered repos
(`docs/evidence/2026-10-03-dependency-graph-triage/`). This is an
assessment with proposals. It changes no canon and authorizes nothing.
## 1. The shape of the blockage
| Measure | Value |
| --- | ---: |
| Open workplans | 108 (100 blocked) |
| Wait tasks inside them | 232 |
| Human gates (`needs_human: true`) | 69 |
| Human gates that named a hub decision | 0 |
| Owner-return waits (`blocked_on: message-from:`) | 20 |
| Wait tasks whose blockers were already terminal | 8 |
| Cycles | 0 |
Three facts matter more than the counts.
**The graph has no cycles and few true roots.** Removing the already-
satisfied and mis-statused cases, the open set hangs off roughly a dozen
root nodes. The two largest, `SAND-WP-0015-T04` (7 workplans behind it)
and `SECRETS-WP-0009-T03` (5), are themselves owner-return waits on
railiance-platform and glas-harness, which in turn wait on one attended
OpenBao session. So the single largest lever is not a repo and not a
workplan; it is one kind of act by one person.
**Human gates were invisible to the hub.** The wait-qualifier standard
(`canon/standards/task-wait-qualifiers_v0.1.md`) and C-40 rule 4 already
let a resolved decision clear a task. Nobody used it: all 69 gates were
prose. Bernd therefore had 69 places to read and no list to resolve.
`CUST-WP-0075-T03` routed 68 of them to eleven hub decisions (one has no
stated gate at all, `WARDEN-WP-0037-T03`, and goes back to its owner).
**Security work is where this concentrates.** Of the 69 gates, 19 are
attended OpenBao provisioning acts (packet D1), 14 are the ArgoCD
production-ownership sequence (D2), 3 are trust-root and custody design
(D3), 3 are destructive cleanups behind retention windows (D4), 7 are
production risk acceptances (D5). That is 46 of 69 in the credential,
identity, delivery and custody space. Product and research gates are the
minority.
## 2. Why credential work blocks the way it does
Reading the lane documents in railiance-platform
(`docs/credential-lane-designs/`, `docs/openbao-approved-automation-delegation.md`,
`docs/credential-lane-lifecycle-runbook.md`) and the ops-warden catalog
(38 routing entries) gives a consistent picture:
1. **Metadata is delegated, values are not.** The delegated applier may
write policies and auth roles from an approved CCR; secret *values*
stay an attended operator act "unless a later workplan approves a
stronger dual-control flow". Almost every D1 task is blocked at
exactly that value step: the policy is designed, reviewed, often
dry-run, and then waits for a session.
2. **Each lane negotiates its own session.** Nineteen tasks in twelve
repos describe the same act in their own words, with their own
evidence expectations. Nothing aggregates them, so no session is ever
"worth" scheduling and each lane waits for its own.
3. **Owner-return waits chain across repos.** sand-boxer waits on
railiance-platform, which waits on secrets-engine, which waits on the
session. Each hop is a hub message that may or may not be answered;
the C-25 unread-reply check exists but nothing sweeps it.
4. **The read model lags the files.** The hub carried 201 dependency
edges where the files carry more; task-level edges from the September
backfill were "parked" behind a deploy. Anyone querying the hub for
the graph got a thinner picture than the files hold.
None of this is a design flaw in OpenBao, KeyCape or ops-warden. It is
the absence of a *batching* and *routing* layer between well-designed
lanes and one scarce person.
## 3a. Two failure modes found while acting on the graph
**Prose-only blockers defeat C-40.** Three of the eight "satisfied" waits
were not satisfied. Their `depends_on` named an old in-repo predecessor;
the real gate (`GLAS-WP-0012`, an unread canon registration, an
independent human author) lived only in the dated notes below the task.
C-40 can only clear what was encoded, so it reported them as clearable.
The fix was to encode the real edge, not to clear the task. Rule worth
adding to the standard: when a dated note re-blocks a task, the same
edit updates `depends_on` / `needs_human` / `blocked_on`.
**Owner returns arrive and are not read.** Seventeen owner-return waits
had an unread reply from exactly the agent they wait on. One of them
(`COORDINATION-WP-0004-T02`) was fully satisfied for a week: info-tech-canon
finished `INFO-WP-0030` on 2026-09-27 and sent the artifact id; nobody
read it. Closing it took ten minutes. This is the strongest argument for
P5 below, and it is not specific to security work, although most of the
seventeen sit in security repos (ops-mason, gate-house, user-engine,
railiance-platform).
## 3. Proposals (ease of maintenance and extension)
**P1 — Gates are born routed.** Extend C-39: a `needs_human: true` wait
in an open, non-residual workplan without `decision_id` is a WARN. Owner
repos then attach a decision at the moment they create the gate, and the
hub's open-decision list *is* Bernd's queue. (state-hub, small.)
**P2 — The attended provisioning window is a standing event.** Instead
of per-lane sessions, one dated window (fortnightly is enough at current
volume). Its checklist is generated, not written: every task carrying
the window's `decision_id`, joined to its CCR and lane doc, rendered by
`credential-change.py` as a numbered attended sequence with the receipt
each owner needs. Owners prepare to that checklist; the session runs it
top to bottom; resolving the decision raises C-40 on every task.
(railiance-platform owns the renderer; the-custodian owns the cadence.)
**P3 — Decide the dual-control value path once.** The delegation doc
leaves "stronger dual-control" open. The honest options are (a) keep
values attended but batched (P2 alone), (b) response-wrapped delivery
where the issuing system writes the value into OpenBao under a
one-time wrapping token the operator only *unwraps*, or (c) a scoped
operator-write role under MFA used by a wrapped command that never
prints. Pick one in packet D3 alongside the trust-root ruling; it removes
the value step from most future lanes. (railiance-platform workplan.)
**P4 — Catalog lane as the unit of extension.** A new consumer today
costs a design workplan, a CCR, an attended apply and bespoke evidence.
Make the ops-warden catalog entry the template: entry + CCR skeleton +
policy/role rendering + ExternalSecret manifest + evidence script
scaffolded from one command. Then a new lane is a reviewed PR, not a
workplan, and only its value step needs the window. (ops-warden +
railiance-platform.)
**P5 — Owner-return sweep.** A daily check lists every
`blocked_on: message-from:<agent>` task whose awaited agent has sent the
waiting repo a message newer than the task's last note, read or not, and
wakes that repo's agent to act on it. C-25 already watches unread
replies; promote its finding from INFO to a work item. The 2026-10-03
sweep script is in the triage evidence folder.
**P6a — Fix the qualifier sync gap (found 2026-10-03).** After the
routing commits, `statehub fix-consistency --repo <slug>` reported PASS in
every repo, yet the hub held the `decision_id` for 1 of 58 routed tasks.
Running `scripts/consistency_check.py --repo <slug> --fix` directly from the
state-hub checkout raised C-41 and synced them. Same interpreter, same
checker file, different working directory; the cause is not isolated. Until
it is, a routing change is not visible to the hub, so P1 and P2 would act on
stale data. (state-hub, high.)
**P6 — Keep the hub graph current.** Run `statehub fix-consistency --all
--remote` on railiance01 nightly so C-20 keeps the edge table equal to
the files, and finish the deploy that carries task-level edges. The
graph scripts in this triage can then run against the hub alone.
## 4. What this does not change
The hub stays a read model. Decisions were recorded through the
sanctioned route and every `decision_id` lives in the owner file, synced
by fix-consistency. No workplan or task was registered by hand. No
credential was requested, printed or moved.