Uh oh!
There was an error while loading. Please reload this page.
fix(lint): planned liveness rows get their own verdict and rule id (were reported dead) - #11527
Conversation
describe() in lint-liveness-properties.ts only knew two verdicts (experimental, everything else -> dead), while the liveness ledger ships a third: status: 'planned' (declared, and a consumer is being built against it - the opposite of dead). Every planned row fell through into the dead branch, so the finding's message told the author to remove metadata the platform had asked them to write, contradicting the same finding's own hint one sentence later. Three shipped rows hit this: field.relatedListFilter, object.externalSharingModel, translation.flows. Adds a third branch (status === 'planned') with its own rule id (liveness-planned-property, mirroring the other two ids' advisory-only posture) and its own message/default hint that never says "Remove it". Also closes the silent fallthrough: an unrecognised ledger status now throws, naming the status, instead of being graded dead. Fixes#11384
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅ What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 066db27cac5a39e3c75980a949fa192214fd4d34 && git checkout 066db27cac5a39e3c75980a949fa192214fd4d34
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin daacc1071c69604976796f92d39448117b3b4990 25e144edff6c55e70c1f259ae43093568bda89d0 && git checkout -B drift-repro daacc1071c69604976796f92d39448117b3b4990 && git merge --no-ff 25e144edff6c55e70c1f259ae43093568bda89d0
node scripts/docs-audit/affected-docs.mjs --json daacc1071c69604976796f92d39448117b3b4990 |
Uh oh!
There was an error while loading. Please reload this page.
The contradiction
describe()inpackages/lint/src/lint-liveness-properties.tsonly knew twoverdicts —
experimental, and everything else (includingplanned) fellthrough into
dead. A real finding, captured fromtranslation.flows:The message says remove it; the hint (and the ledger's own note) says keep
it, a consumer is being built. Three shipped rows hit this:
field.relatedListFilter,object.externalSharingModel,translation.flows— allstatus: 'planned', all warned viaauthorWarn.The fix: a third, distinct verdict
describe()now returns one of three verdicts, each with its own rule id anddefault hint:
authorHintnornote)experimentalliveness-experimental-propertyplanned(new)liveness-planned-propertydeadliveness-dead-propertyAdvisory-only posture:
lintLivenessPropertiesis registered once inauthoring-rules.ts(tier: 'advisory', every finding mapped toseverity: 'warning'regardless off.rule), so the new rule id inherits thesame posture automatically — there is no per-rule-id severity table to update.
Repo-wide search confirms no suppression config, report grouper, or other
consumer keys off
liveness-dead-property/liveness-experimental-propertytoday (
index.ts, the source file, and its test are the only 3 hits), soliveness-planned-propertyneeded no other wiring.Unknown status: now fails loud
LedgerEntry.statusis a plainstring— the ledger's status vocabulary(
live | experimental | planned | dead) is documented in a comment at the topof
packages/spec/scripts/liveness/check-liveness.mts, not enforced by aZod enum. Nothing rejects a ledger JSON file that misspells a status or ships
one this file has never heard of; the CI gate only requires a status be
present. Before this fix, any such entry silently graded
dead— the samedefect class the card reports, just with a different trigger.
describe()nowthrows, naming the offending status, when it sees anything other than
experimental/planned/dead. This is scoped narrower than the file'sgeneral "advisory lint, never throws" contract: that promise covers malformed
stack input from an untrusted app author (the translation-bundle walk
etc.), while a ledger status is our own shipped, framework-controlled data —
nothing an app author writes can trigger this throw.
One existing test (
the array fan-out, against a synthetic warn map (#10262))built its synthetic ledger rows with
authorWarn: trueand nostatusatall, relying on the old silent-
deadfallthrough. Updated to declarestatus: 'dead'explicitly — that block asserts nothing about verdicts, onlyabout the
getNestedarray walk, so the choice of status is arbitrary butmust now be one of the three.
Verification
pnpm --filter '@objectstack/lint^...' buildthenpnpm --filter @objectstack/lint build— clean.pnpm --filter @objectstack/lint test— 2266/2266 passed (80 files),including 8 new cases pinning all three verdicts' message+rule-id pairs, the
planneddefault hint (never "Remove it"), the unknown-status throw (namedstatus in the message), and a
live-mistakenly-authorWarn'd row alsothrowing.
pnpm --filter @objectstack/lint typecheck— clean.source files (
lint-liveness-properties.ts,index.ts) to theirorigin/maincontent viagit checkout, to stand up the pre-patch sourceunder the new tests. 7 of the 8 new tests failed (rule id
liveness-dead-propertyinstead ofliveness-planned-property; "Removeit" leaking into the experimental default hint; the two throw-expectation
tests found nothing thrown). The 8th (
deadbranch, no authorHint/note)passed unchanged both before and after, as expected — that behaviour is
untouched by this fix. Restored both files back to HEAD's committed
content (verified
git diff HEADclean) before re-running the full suitegreen.
shipped
plannedrows authored now produces (rule id + message, no more"dead"):
{ "where": "translation bundle #0 · locale 'zh-CN'", "message": "sets `flows` but this translation property is planned — declared, and a consumer is being built against it (not read YET).", "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 (`config.title`, `fields[].label`, `fields[].placeholder`) in every locale.", "rule": "liveness-planned-property" }field.relatedListFilterandobject.externalSharingModelproduce thematching shape.)
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstackfrom this worktree — every path-derived andconvention-triggered local gate it named ran green:
check:changeset-gate- self-tests,check:cross-package-test-inputs,check:objectui-changeset,check:published-files,check:slot-lookup,check:test-source-alias,check:type-source-resolution,check-adr-0087-registration,check-changeset-no-major,check-ci-filter-parity,check-empty- changeset,check-plugin-teardown-shape,check-affected-docs,check:query-options-erasure,check:engine-double-contract,check:where-matcher, andcheck:type-check-coverage(both the plain runand the
--re-measureratchet, the latter after building the full./packages/*+./packages/*/*closure it requires — 70/70 tasks) — nonew debt, no ratchet moved (32 ledger entries re-measured, none above their
recorded ceiling).
pnpm lint(repo-wide ESLint) is CI's to run, notre-run here; a scoped ESLint pass over just the 3 touched files (0
errors/warnings) was run as an extra courtesy check, not a substitute.
.changeset/planned-liveness-verdict-not-dead.md(
@objectstack/lint, patch —@objectstack/lintsits in the repo's fixedpublish group).
Scope
Touches only
packages/lint/src/lint-liveness-properties.ts, its test file,and the
index.tsexport barrel. Nopackages/spec/src/**diff — the ledgerJSON files and their
statusvocabulary are unchanged; this is purely theauthor-facing lint's own verdict mapping.
Fixes#11384
Generated by Claude Code