Typed, dated, defaulted waits — and cut the four-hop chain

The register had nine waits in four days, one four hops deep: F-0003's
embargo waited on F-0009, which waited on railiance-platform, which
waited on live OpenBao verification, which waited on a credential nobody
has. No single link was wrong, which is why it needed a rule.

docs/method/dependencies.md: the register never waits to decide, it
decides and revises. Every wait carries who, what, since, what it would
change, what happens if nobody answers, and the date that default
applies. Depth one — a record never waits on a record that is itself
waiting. Defaults are dates and are pessimistic: silence costs the grade
the evidence supports rather than buying a softer one, and owners are
told the default in advance because a default nobody was warned about is
an ambush.

Applied: F-0009's embargo now lifts on railiance-platform reporting
coverage, with live verification as a refinement rather than a condition,
cutting the F-0003 chain from four hops to two. All eight open waits are
typed with defaults. make check reports them with age, owner and default
date, flags defaults come due, and catches depth-two violations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
tegwick 2026-08-20 22:34:56 +02:00
parent 56b8d61583
commit d5147bfaea
11 changed files with 220 additions and 7 deletions

View file

@ -8,13 +8,13 @@ Generated by `tools/register_index.py` from `findings/`. Do not edit by hand. La
| ID | Finding | System | Severity | Disclosure | Escalation | Fix owner | Status | Cadence | Next check |
| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |
| [RISK-F-0009](findings/RISK-F-0009-openbao-deny-set-covers-a-third-of-high-risk-lanes.md) | agent-high-risk-boundary denies 6 of 17 high-risk lanes; the direct bao path is unprotected for the rest | railiance-platform | **high** | embargoed | none | railiance-platform | open | 1h (1) | 2026-08-20 11:02Z |
| [RISK-F-0009](findings/RISK-F-0009-openbao-deny-set-covers-a-third-of-high-risk-lanes.md) | agent-high-risk-boundary denies 6 of 17 high-risk lanes; the direct bao path is unprotected for the rest | railiance-platform | **high** | embargoed | none | railiance-platform | open | 1h (1) | **due** |
| [RISK-F-0008](findings/RISK-F-0008-audit-retention-legal-basis-assumed.md) | The legal basis for retaining audit facts against an erasure request has been assumed, never established | audit-core | medium | public | **required** (t2, partially-answered) | risk-nexus | open | instant (0) | **due** |
| [RISK-F-0007](findings/RISK-F-0007-unverified-tenant-boundary.md) | No consumer's tenant boundary is verified anywhere | estate | **high** | embargoed | **answered** (t4, assigned) | per-consumer, on request | accepted | instant (0) | **due** |
| [RISK-F-0006](findings/RISK-F-0006-apps-pg-no-backup-configured.md) | apps-pg has no backup configured at all: R0 means no recovery | railiance-platform | **high** | embargoed | **answered** (t3, approved) | railiance-platform | open | 1h (1) | 2026-08-20 11:02Z |
| [RISK-F-0005](findings/RISK-F-0005-audit-core-unfiltered-read-path.md) | audit-core read path applies no tenant filter; the bound is deployment, not code | audit-core | medium | embargoed | none | audit-core | open | 1h (1) | 2026-08-20 11:02Z |
| [RISK-F-0004](findings/RISK-F-0004-tenant-engine-unfiltered-event-read.md) | tenant-engine events() returns the entire event log unfiltered | tenant-engine | **high** | embargoed | none | tenant-engine | open | 1h (1) | 2026-08-20 11:02Z |
| [RISK-F-0003](findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md) | ops-warden agent read-boundary does not fire on ungraded catalog lanes | ops-warden | medium | embargoed | none | ops-warden | mitigated | 1h (1) | 2026-08-20 11:02Z |
| [RISK-F-0006](findings/RISK-F-0006-apps-pg-no-backup-configured.md) | apps-pg has no backup configured at all: R0 means no recovery | railiance-platform | **high** | embargoed | **answered** (t3, approved) | railiance-platform | open | 1h (1) | **due** |
| [RISK-F-0005](findings/RISK-F-0005-audit-core-unfiltered-read-path.md) | audit-core read path applies no tenant filter; the bound is deployment, not code | audit-core | medium | embargoed | none | audit-core | open | 1h (1) | **due** |
| [RISK-F-0004](findings/RISK-F-0004-tenant-engine-unfiltered-event-read.md) | tenant-engine events() returns the entire event log unfiltered | tenant-engine | **high** | embargoed | none | tenant-engine | open | 1h (1) | **due** |
| [RISK-F-0003](findings/RISK-F-0003-ops-warden-read-boundary-ungraded-lanes.md) | ops-warden agent read-boundary does not fire on ungraded catalog lanes | ops-warden | medium | embargoed | none | ops-warden | mitigated | 1h (1) | **due** |
| [RISK-F-0002](findings/RISK-F-0002-ops-warden-sign-ungated.md) | ops-warden signs SSH certificates with no authorization decision, and its unblock is now unsafe | ops-warden | medium | embargoed | **withdrawn** (t6, withdrawn-hazard-window-closed) | ops-warden | open | instant (0) | **due** |
| [RISK-F-0001](findings/RISK-F-0001-flex-auth-unauthenticated-check.md) | flex-auth /v1/check authenticates no caller | flex-auth | **high** | public | **withdrawn** (t1, withdrawn-before-sending) | flex-auth | fixed | instant (0) | **due** |
@ -26,13 +26,29 @@ Hazards created by acting in the wrong order. Each binds another finding's remed
| --- | --- | --- | --- |
| RISK-F-0002 | RISK-F-0001 | **lifted** | LIFTED 2026-08-19 — flex-auth /v1/check now authenticates callers (RISK-F-0001 fixed). Enabling policy.enabled is now an availability question for ops-warden, no longer an attestation hazard. |
## Waiting on someone
Every wait resolves on its default date whether or not anyone answers.
Silence never buys a softer grade — see `docs/method/dependencies.md`.
| Finding | Who | What would change | Default if silent | On |
| --- | --- | --- | --- | --- |
| RISK-F-0009 | railiance-platform | embargo lifts on coverage; live verification would refine the grade but is not required for it | the eight uncovered paths stand as recorded and the finding is re-raised | 2026-09-03 |
| RISK-F-0008 | audit-core | a working keyed commitment narrows RISK-REG-0001 to retained-by-obligation categories only | encrypt-then-hash recorded as the only known route, and the retention period recorded as unstateable | 2026-11-17 |
| RISK-F-0008 | the-custodian | fixes when the estate stops running on an assumption | the assumption is recorded in the register as an assumption | 2026-11-17 |
| RISK-F-0007 | user-engine | likelihood falls for user-engine if a verification exists; a defect becomes its own finding if not | the on-request path is recorded as having produced no answer, which makes the acceptance itself unsupported and is escalated | 2026-09-03 |
| RISK-F-0006 | railiance-platform | embargo lifts on a demonstrated restore; the approved spend becomes a real figure | recorded as stalled with approval already granted, which is the worst kind of stall | 2026-09-18 |
| RISK-F-0005 | audit-core | likelihood rises to L3 if any other production credential carries may_read | graded on the sender alone, as stated; the wider question is recorded as unanswered | 2026-09-19 |
| RISK-F-0004 | tenant-engine | grade rises if the log carries tenant payload rather than metadata | grade stands as recorded; absent fix tracking is recorded as a stall | 2026-09-03 |
| RISK-F-0002 | ops-warden | if it admits no ingress, enabling the gate stops all signing — an availability blocker, not a risk one | the register records the ordering as unverified and re-raises it; the finding stands at medium | 2026-08-27 |
## Embargoes
Held from publication with a stated condition. A hold with no moving condition is a stall.
| Finding | Since | Lifts when | Re-decided |
| --- | --- | --- | --- |
| RISK-F-0009 | 2026-08-20 | the deny set covers every high-risk lane with a KV path, verified against live OpenBao | — |
| RISK-F-0009 | 2026-08-20 | railiance-platform reports the deny set covers every high-risk lane with a KV path (live verification refines the grade, it is not the condition) | — |
| RISK-F-0007 | 2026-08-19 | a verification exists for at least one consumer boundary | 2026-09-18 |
| RISK-F-0006 | 2026-08-19 | a backup exists and a restore has been demonstrated once | 2026-09-18 |
| RISK-F-0005 | 2026-08-19 | AUDIT-WP-0008-T04 lands a tenant filter in the read path | 2026-11-17 |

107
docs/method/dependencies.md Normal file
View file

@ -0,0 +1,107 @@
---
id: RISK-METHOD-DEPENDENCIES
type: method
title: "Waiting: how this register depends on other people without becoming a queue"
status: adopted
owner: risk-nexus
adopted: "2026-08-20"
workplan: RISK-WP-0001
review_interval: 180d
---
# Waiting
By 2026-08-20 the register had accumulated nine waits in four days, one of them
four hops deep: `RISK-F-0003`'s embargo waited on `RISK-F-0009`, which waited
on `railiance-platform` fixing a deny set, which waited on someone verifying it
against live OpenBao, which waited on a credential nobody has.
Nothing in that chain is anyone's fault, and every link was individually
reasonable. That is exactly why it needs a rule: deep dependencies are not
built deliberately, they accrete one sensible step at a time.
## The principle
**The register never waits to decide. It decides, and revises when told.**
A wait is a *refinement* pending, not a decision pending. If the register
cannot act until someone answers, the register has made that person's silence
into its own paralysis — and `INTENT.md` is explicit that a register nobody
acts on is worse than none.
## The four rules
### 1. Every wait is typed
No record may say "waiting on X". A wait carries six things:
```yaml
waiting_on:
- who: tenant-engine
what: "confirm or correct the unfiltered events() read; open fix tracking"
since: "2026-08-19"
would_change: "grade rises if the log carries payload rather than metadata"
default: "grade stands as recorded; absent fix tracking noted as a stall"
default_at: "2026-09-03"
```
`would_change` is the discipline. If nobody can say what the answer would
change, there is nothing being waited for, and the wait should be deleted
rather than carried.
### 2. Depth one
**A record may wait on at most one other record, and never on a record that is
itself waiting.**
When the chain would go deeper, the far end is cut: the record takes its own
default and says which unresolved thing it declined to wait for. Two hops is
the point at which nobody can see the whole line any more, and a wait nobody
can see is indistinguishable from a thing that was dropped.
Applied 2026-08-20: `RISK-F-0009`'s embargo condition was
"verified against live OpenBao", which no one in the estate can currently do.
It now lifts on **`railiance-platform` reporting the coverage**, with live
verification recorded as a refinement rather than a condition. That cut the
`RISK-F-0003` chain from four hops to two.
### 3. Defaults are dates, and defaults are pessimistic
Every wait resolves on a date whether or not anyone answers. The default is
**the reading the stated facts already support** — never a hold, never a
downgrade earned by silence.
This is what removes the incentive to wait. Silence does not buy an owner a
softer grade or a quieter register; it costs them the grade the evidence
supports, which is usually the one they would want corrected. Answering is how
a grade improves, and that is the right shape for the incentive.
The register says so in advance, to the owner, in writing. A default nobody was
warned about is an ambush, not a rule.
### 4. A condition naming somebody else's action carries a date beside it
"Embargo lifts when X ships" is a dependency with no end. "Lifts when X ships,
or is re-decided on 2026-09-20" terminates.
Re-decided is not the same as lifted — the re-decision may extend the hold with
a fresh reason. What it may not do is extend by default, which is how holds
quietly become permanent.
## What this does not solve
Some dependencies are real and cannot be defaulted away. Nobody can verify an
OpenBao policy without a token, and no rule here conjures one.
What the rules do is stop that from propagating: the register grades on what is
stated, records what it could not verify, and keeps its own position
independent of the blockage. `docs/method/verification.md` bounds what this repo
can establish itself, and every grade resting on a document rather than a probe
says so on its face.
## Where the waits are visible
`make check` reports every open wait with its age, its owner and its default
date, flags any default that has come due, and flags any wait that points at a
record which is itself waiting — a depth-two violation, caught by tooling
rather than by someone noticing.

View file

@ -33,6 +33,13 @@ last_checked: "2026-08-20T10:02:41Z"
next_check: "2026-08-20T10:02:41Z"
cadence: instant
clean_streak: 0
waiting_on:
- who: ops-warden
what: "probe whether the flex-auth pin you call admits ingress, before enabling policy.enabled"
since: "2026-08-20"
would_change: "if it admits no ingress, enabling the gate stops all signing — an availability blocker, not a risk one"
default: "the register records the ordering as unverified and re-raises it; the finding stands at medium"
default_at: "2026-08-27"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-19
---

View file

@ -29,6 +29,13 @@ last_checked: "2026-08-20T10:02:42Z"
next_check: "2026-08-20T11:02:42Z"
cadence: 1h
clean_streak: 1
waiting_on:
- who: tenant-engine
what: "confirm or correct the unfiltered events() read; open fix tracking"
since: "2026-08-19"
would_change: "grade rises if the log carries tenant payload rather than metadata"
default: "grade stands as recorded; absent fix tracking is recorded as a stall"
default_at: "2026-09-03"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-19-B
---

View file

@ -29,6 +29,13 @@ last_checked: "2026-08-20T10:02:42Z"
next_check: "2026-08-20T11:02:42Z"
cadence: 1h
clean_streak: 1
waiting_on:
- who: audit-core
what: "is may_read false on every production credential, or only on the sender"
since: "2026-08-19"
would_change: "likelihood rises to L3 if any other production credential carries may_read"
default: "graded on the sender alone, as stated; the wider question is recorded as unanswered"
default_at: "2026-09-19"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-19-B
---

View file

@ -35,6 +35,13 @@ last_checked: "2026-08-20T10:02:42Z"
next_check: "2026-08-20T11:02:42Z"
cadence: 1h
clean_streak: 1
waiting_on:
- who: railiance-platform
what: "the backup target chosen, its monthly cost, and a demonstrated restore"
since: "2026-08-19"
would_change: "embargo lifts on a demonstrated restore; the approved spend becomes a real figure"
default: "recorded as stalled with approval already granted, which is the worst kind of stall"
default_at: "2026-09-18"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-19-B
---

View file

@ -38,6 +38,13 @@ last_checked: "2026-08-20T10:02:41Z"
next_check: "2026-08-20T10:02:41Z"
cadence: instant
clean_streak: 0
waiting_on:
- who: user-engine
what: "does anything verify that a caller for tenant A cannot reach tenant B (RISK-V-0002)"
since: "2026-08-20"
would_change: "likelihood falls for user-engine if a verification exists; a defect becomes its own finding if not"
default: "the on-request path is recorded as having produced no answer, which makes the acceptance itself unsupported and is escalated"
default_at: "2026-09-03"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-19-B
---

View file

@ -36,6 +36,19 @@ last_checked: "2026-08-20T10:02:41Z"
next_check: "2026-08-20T10:02:41Z"
cadence: instant
clean_streak: 0
waiting_on:
- who: audit-core
what: "does a keyed commitment restore erasability without breaking chain verification; what is the platform-pg co-residency horizon"
since: "2026-08-20"
would_change: "a working keyed commitment narrows RISK-REG-0001 to retained-by-obligation categories only"
default: "encrypt-then-hash recorded as the only known route, and the retention period recorded as unstateable"
default_at: "2026-11-17"
- who: the-custodian
what: "rule the trigger list for buying an external determination"
since: "2026-08-19"
would_change: "fixes when the estate stops running on an assumption"
default: "the assumption is recorded in the register as an assumption"
default_at: "2026-11-17"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-19-C
---

View file

@ -22,13 +22,20 @@ likelihood: L2
fidelity_modifier: false
production_rescore: false
disclosure: embargoed
embargo_condition: "the deny set covers every high-risk lane with a KV path, verified against live OpenBao"
embargo_condition: "railiance-platform reports the deny set covers every high-risk lane with a KV path (live verification refines the grade, it is not the condition)"
embargo_since: "2026-08-20"
escalation: none
last_checked: "2026-08-20T10:02:42Z"
next_check: "2026-08-20T11:02:42Z"
cadence: 1h
clean_streak: 1
waiting_on:
- who: railiance-platform
what: "report whether the deny set covers every high-risk lane with a KV path"
since: "2026-08-20"
would_change: "embargo lifts on coverage; live verification would refine the grade but is not required for it"
default: "the eight uncovered paths stand as recorded and the finding is re-raised"
default_at: "2026-09-03"
graded_by: risk-nexus
ruling: RISK-RULING-2026-08-20
---

View file

@ -121,6 +121,29 @@ def main() -> int:
]
section("Stalled — escalation trigger 5", bottom, "none")
# Waits: typed, dated, and defaulted (docs/method/dependencies.md).
waiting, due_defaults, deep = [], [], []
waiting_ids = {f["id"] for f in fs if f.get("waiting_on")}
for f in fs:
for w in (f.get("waiting_on") or []):
since = lib.moment(w.get("since"))
age = f"{(NOW - since).days}d" if since else "?"
when = lib.moment(w.get("default_at"))
waiting.append(f"{f['id']}{w.get('who')}: {str(w.get('what'))[:64]} ({age} old, defaults {w.get('default_at')})")
if when and NOW >= when:
due_defaults.append(
f"{f['id']}{w.get('who')} did not answer by {w.get('default_at')}; apply: {w.get('default')}"
)
# depth-two check: a record waiting on a record that is itself waiting
for ref in ([f.get("constraint_on")] if f.get("constraint_on") else []):
if ref in waiting_ids and f.get("waiting_on"):
deep.append(f"{f['id']} waits, and points at {ref} which also waits — depth two, cut one")
section("Waiting on someone", waiting, "nothing is waiting on anyone")
if due_defaults:
section("Defaults now due — apply them", due_defaults, "none")
if deep:
section("Dependency depth violations", deep, "none")
embargo = [
f"{f['id']} — lifts when: {f.get('embargo_condition')}"
for f in fs

View file

@ -96,6 +96,18 @@ def render() -> str:
id=f["id"], on=f.get("constraint_on", ""),
sev=f.get("constraint_severity", ""), text=f["constraint"]))
waits = [(f, w) for f in fs for w in (f.get("waiting_on") or [])]
if waits:
out += ["", "## Waiting on someone", "",
"Every wait resolves on its default date whether or not anyone answers.",
"Silence never buys a softer grade — see `docs/method/dependencies.md`.",
"", "| Finding | Who | What would change | Default if silent | On |",
"| --- | --- | --- | --- | --- |"]
for f, w in waits:
out.append("| {id} | {who} | {chg} | {dflt} | {at} |".format(
id=f["id"], who=w.get("who", ""), chg=w.get("would_change", ""),
dflt=w.get("default", ""), at=w.get("default_at", "")))
embargoed = [f for f in fs if f.get("disclosure") == "embargoed"]
if embargoed:
out += ["", "## Embargoes", "",