170 lines
8.5 KiB
Markdown
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.
|