Summary
describe() in packages/lint/src/lint-liveness-properties.ts knows two verdicts, and the ledger ships three:
functiondescribe(entry: LedgerEntry): {kind: string;rule: string}{if(entry.status==='experimental'){return{kind: 'is experimental — declared but NOT enforced at runtime',rule: LIVENESS_EXPERIMENTAL_PROPERTY};}return{kind: 'has no runtime effect (liveness: dead)',rule: LIVENESS_DEAD_PROPERTY};}Anything that is not experimental falls into the dead branch, planned included — so a row the ledger deliberately grades planned ("declared, and a consumer is being built against it") is reported to the author as liveness: dead under the rule id liveness-dead-property.
Measurement (origin/main @ b863f01)
Census of every warned row across all 30 shipped ledgers (authorWarn: true or status: experimental) — 8 rows, and 3 of the 8 are planned:
agent · lifecycle [status=experimental]
agent · memory [status=experimental]
agent · guardrails [status=experimental]
agent · structuredOutput [status=experimental]
field · relatedListFilter [status=planned] <- reported as dead
object · externalSharingModel [status=planned] <- reported as dead
tool · outputSchema [status=experimental]
translation · flows [status=planned] <- reported as dead
A real finding, captured from a test run on translation.flows:
message: "sets `flows` but this translation property has no runtime effect (liveness: dead)."
hint: "No shipped screen-flow runner reads this group yet — until the objectui half of #7646
lands, a `type: 'screen'` flow renders the strings authored on the flow ... in every locale."
The message says the property is dead; the hint attached to the same finding says a runner is coming and names the card. The ledger's own note is explicit that this is not a dead row: "dead means declared with no consumer and no plan, while this group was ruled into the vocabulary by the maintainer specifically so the runner half could be built against it".
Why it matters
The two verdicts imply opposite author actions. dead means remove it — which is what the rule's default hint says when a row has no authorHint ("Remove it — it is declared in the spec but not consumed at runtime"). planned means keep it, it is contract-first and the reader is being built. An author who follows the message deletes metadata the platform asked them to write; an author who reads the hint keeps it and wonders why the linter called it dead. The rule id is also what suppression and reporting key off, so the two states are indistinguishable to anything downstream.
Not urgent: all three rows carry an authorHint today, so the honest sentence is present on every finding — just underneath a contradicting one.
Suggested shape
A third branch in describe() for status === 'planned' with its own wording (declared, not read yet) and its own rule id, plus a decision on whether the new id is advisory-only like the other two. Worth checking at the same time what describe() should do with a status it does not recognise — the current fallthrough grades an unknown status as dead, silently.
Provenance
Found while implementing #11288 (the translation liveness walk, PR #11383) — that fix is what first made a planned translation row reach an author at all, but the defect is older and independent of it: field.relatedListFilter and object.externalSharingModel have been reported this way for as long as they have been planned. Out of scope there, filed here; no fix attempted.
Summary
describe()inpackages/lint/src/lint-liveness-properties.tsknows two verdicts, and the ledger ships three:Anything that is not
experimentalfalls into thedeadbranch,plannedincluded — so a row the ledger deliberately gradesplanned("declared, and a consumer is being built against it") is reported to the author asliveness: deadunder the rule idliveness-dead-property.Measurement (
origin/main@b863f01)Census of every warned row across all 30 shipped ledgers (
authorWarn: trueorstatus: experimental) — 8 rows, and 3 of the 8 areplanned:A real finding, captured from a test run on
translation.flows:The message says the property is dead; the hint attached to the same finding says a runner is coming and names the card. The ledger's own note is explicit that this is not a
deadrow: "deadmeans declared with no consumer and no plan, while this group was ruled into the vocabulary by the maintainer specifically so the runner half could be built against it".Why it matters
The two verdicts imply opposite author actions.
deadmeans remove it — which is what the rule's default hint says when a row has noauthorHint("Remove it — it is declared in the spec but not consumed at runtime").plannedmeans keep it, it is contract-first and the reader is being built. An author who follows the message deletes metadata the platform asked them to write; an author who reads the hint keeps it and wonders why the linter called it dead. The rule id is also what suppression and reporting key off, so the two states are indistinguishable to anything downstream.Not urgent: all three rows carry an
authorHinttoday, so the honest sentence is present on every finding — just underneath a contradicting one.Suggested shape
A third branch in
describe()forstatus === 'planned'with its own wording (declared, not read yet) and its own rule id, plus a decision on whether the new id is advisory-only like the other two. Worth checking at the same time whatdescribe()should do with a status it does not recognise — the current fallthrough grades an unknown status asdead, silently.Provenance
Found while implementing #11288 (the translation liveness walk, PR #11383) — that fix is what first made a
plannedtranslation row reach an author at all, but the defect is older and independent of it:field.relatedListFilterandobject.externalSharingModelhave been reported this way for as long as they have beenplanned. Out of scope there, filed here; no fix attempted.