Repository navigation
Commit 70e1a82
feat(spec): the liveness gate reads the retiredKey() tombstone marker — a tombstoned key may not be graded live (#19092)
Fixes #19062
Clause-②: no
## What this closes
The liveness gate graded a schema property from two sources: the ledger
row's `status`, or — with no row — a `[experimental` / `[planned` marker
read by `markerStatus()`. It had **no reading of the `retiredKey()`
tombstone marker at all**, so `tombstone + status: live` was a
*structurally permitted* combination rather than an oversight:
- the forward pass is satisfied — a tombstone keeps the key in the
walked shape, so the row exists and is classified;
- the orphan pass is satisfied for the same reason — the property has
not left the schema;
- nothing constrained what that row was allowed to **say**.
This adds the join both directions lacked, as a fourth direction beside
the orphan scan: walk the tombstones the gate already reaches, and ask
each one's ledger row what it claims.
## Ordering — discharged
This section used to read "this PR must not merge before #19059", and
that constraint is now **spent**: #19059 landed as `a362e0e425`, `main`
is merged into this branch, and the single offending row it named —
`agent/tools`, #18304's — is `dead` on the real ledgers. Re-measured by
the seat on this PR's head `3d49d7a199` (2026-09-18T22:08Z):
- `check:liveness` against the shipped ledgers: **exit 0**, printing `79
graded with a status the tombstone allows`.
- The 16 control cases that were red for that one cause are green. The
whole `scripts/liveness/` suite: **333 passed (333)** over 12 files,
exit 0.
⛔ No landing order remains on this PR. Read the population table below
as the measurement that *motivated* the rule, not as a description of
today's tree.
## The population, measured — the card's first deliverable
The card states its own evidence limit: "the population is unmeasured,
and measuring it is the first piece of the work — it needs exactly the
join the gate lacks". Measured over all 39 governed types by the gate's
own walk, at two commits — the tree the rule was written against, and
this PR's head `3d49d7a199` (the right-hand column re-measured by the
seat, 2026-09-18T22:08Z):
| reading | tree the rule was written against | this PR's head |
|---|---|---|
| `[REMOVED]` tombstones the gate's walk reaches | **79** | **79** |
| …graded `dead` | **78** | **79** |
| …graded `live` (the defective state) | **1** — `agent/tools` | **0** —
#19059 repaired that row |
| …whose status came from an explicit ledger row | **79 / 79** | **79 /
79** (none from a marker, none from `childrenDefault`, none
UNCLASSIFIED) |
The full list of 79 is in the report on the card. **Drift control on the
instrument**: the census walk reproduces the gate's own per-type verdict
counts for all 39 types and 1092 verdict points exactly (compared
against `check:liveness --json`'s `classified + unclassified`), so it is
the gate's walk and not a copy of it.
**Reachable radius of that census** — it is the gate's walk, so it is
the ledger-driven drill, nothing wider. **Known targets necessarily
outside it**: a full-depth walk of the same governed roots finds **59
further `[REMOVED]` tombstones the gate never reaches** —
`object/fields.conditionalRequired`, `object/listViews.performance`,
`job/retryPolicy.retryDelayMs` among them — because they sit under
containers carrying a blanket verdict (the 601 child keys the gate
already reports as classified nowhere). No rule placed in this gate can
grade a key the gate does not walk; closing that is the
undrilled-container worklist, not this card.
## Why remedy (2), measured rather than argued
Triage named two shapes and refused to pick. Their side-effect surfaces
were measured, each applied alone to the base gate with the mutation
proven on disk before any verdict was read:
- **(1) a third `[REMOVED]` arm in `markerStatus()`** — on the
acceptance sample this leaves the gate at **exit 0**. `markerStatus()`
is consulted only when `led?.status` is falsy, and all 79 tombstones the
walk reaches take their status from an explicit row, so this arm changes
the verdict of **0 of the 79** today. What it does change is the no-row
case, which the gate currently reports as UNCLASSIFIED and fails on, and
which `ORPHAN_GUIDANCE` tells authors never to create — so (1) alone
would *silence* an existing loud failure. It cannot satisfy the
acceptance.
- **(2) a rule beside the tombstone-row rule** — implemented here. The
forbidden set is a named constant holding exactly `live`, which is what
was ruled. `planned` / `experimental` / `live-elsewhere` on a tombstone
are equally false, and their population today is zero, so widening the
set is a separate measurement rather than a rider on this one.
## Acceptance
Every leg runs the real gate the way CI invokes it, via `--ledger-root`
against a copied ledger.
The carrier the legs flip is **derived per run from the gate's own
`report.tombstones` enumeration**, not named: top-level, not a
`BOUND_PROOF_PATHS` coordinate, and currently `dead`. A fixture pinned
to one row's *current verdict* is pinned to a moving fact, which is how
this block first broke — it named `agent/tools`, that row went `dead`
upstream, and the "this would be a no-op" guard took four cases red. The
seat re-derived the same filter chain independently (2026-09-18T22:08Z):
**79 enumerated → 42 top-level → 42 unbound → 42 `dead`**, carrier
`action/bulkEnabled`. 1 parent aadea24 commit 70e1a82
4 files changed
Lines changed: 558 additions & 6 deletions
File tree
- packages/spec/scripts/liveness
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
217 | 217 | | |
218 | 218 | | |
219 | 219 | | |
220 | | - | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
221 | 228 | | |
222 | 229 | | |
223 | 230 | | |
| |||
678 | 685 | | |
679 | 686 | | |
680 | 687 | | |
| 688 | + | |
| 689 | + | |
681 | 690 | | |
682 | 691 | | |
683 | 692 | | |
| |||
971 | 980 | | |
972 | 981 | | |
973 | 982 | | |
| 983 | + | |
| 984 | + | |
| 985 | + | |
| 986 | + | |
| 987 | + | |
| 988 | + | |
| 989 | + | |
974 | 990 | | |
975 | 991 | | |
976 | 992 | | |
| |||
1029 | 1045 | | |
1030 | 1046 | | |
1031 | 1047 | | |
1032 | | - | |
| 1048 | + | |
| 1049 | + | |
1033 | 1050 | | |
| 1051 | + | |
1034 | 1052 | | |
1035 | 1053 | | |
1036 | 1054 | | |
| |||
1070 | 1088 | | |
1071 | 1089 | | |
1072 | 1090 | | |
1073 | | - | |
| 1091 | + | |
| 1092 | + | |
| 1093 | + | |
| 1094 | + | |
| 1095 | + | |
1074 | 1096 | | |
1075 | 1097 | | |
1076 | 1098 | | |
1077 | 1099 | | |
1078 | 1100 | | |
1079 | 1101 | | |
| 1102 | + | |
1080 | 1103 | | |
1081 | 1104 | | |
1082 | 1105 | | |
| |||
1089 | 1112 | | |
1090 | 1113 | | |
1091 | 1114 | | |
| 1115 | + | |
| 1116 | + | |
| 1117 | + | |
| 1118 | + | |
| 1119 | + | |
| 1120 | + | |
| 1121 | + | |
| 1122 | + | |
| 1123 | + | |
1092 | 1124 | | |
1093 | 1125 | | |
1094 | 1126 | | |
| |||
1353 | 1385 | | |
1354 | 1386 | | |
1355 | 1387 | | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
1356 | 1397 | | |
1357 | 1398 | | |
1358 | 1399 | | |
| |||
1640 | 1681 | | |
1641 | 1682 | | |
1642 | 1683 | | |
| 1684 | + | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
| 1688 | + | |
| 1689 | + | |
| 1690 | + | |
| 1691 | + | |
1643 | 1692 | | |
1644 | 1693 | | |
1645 | 1694 | | |
| |||
1797 | 1846 | | |
1798 | 1847 | | |
1799 | 1848 | | |
| 1849 | + | |
| 1850 | + | |
| 1851 | + | |
| 1852 | + | |
| 1853 | + | |
| 1854 | + | |
| 1855 | + | |
| 1856 | + | |
| 1857 | + | |
| 1858 | + | |
1800 | 1859 | | |
1801 | 1860 | | |
1802 | 1861 | | |
| |||
1853 | 1912 | | |
1854 | 1913 | | |
1855 | 1914 | | |
1856 | | - | |
| 1915 | + | |
| 1916 | + | |
1857 | 1917 | | |
1858 | 1918 | | |
1859 | 1919 | | |
| |||
0 commit comments