Skip to content

fix(lint): planned liveness rows get their own verdict and rule id (were reported dead) - #11527

Merged
os-steve merged 1 commit into
mainfrom
claude/issue-11384-planned-liveness-verdict
Aug 24, 2026
Merged

fix(lint): planned liveness rows get their own verdict and rule id (were reported dead)#11527
os-steve merged 1 commit into
mainfrom
claude/issue-11384-planned-liveness-verdict

Conversation

@os-steve

@os-steveos-steve commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

The contradiction

describe() in packages/lint/src/lint-liveness-properties.ts only knew two
verdicts — experimental, and everything else (including planned) fell
through into dead. A real finding, captured from 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 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 — all status: 'planned', all warned via authorWarn.

The fix: a third, distinct verdict

describe() now returns one of three verdicts, each with its own rule id and
default hint:

statusrule idmessagedefault hint (used only when the ledger row has neither authorHint nor note)
experimentalliveness-experimental-property"is experimental — declared but NOT enforced at runtime"(unchanged)
planned (new)liveness-planned-property"is planned — declared, and a consumer is being built against it (not read YET)""Keep it — a consumer is being built against this property; it has no runtime effect yet."
deadliveness-dead-property"has no runtime effect (liveness: dead)""Remove it — it is declared in the spec but not consumed at runtime." (unchanged)

Advisory-only posture: lintLivenessProperties is registered once in
authoring-rules.ts (tier: 'advisory', every finding mapped to
severity: 'warning' regardless of f.rule), so the new rule id inherits the
same 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-property
today (index.ts, the source file, and its test are the only 3 hits), so
liveness-planned-property needed no other wiring.

Unknown status: now fails loud

LedgerEntry.status is a plain string — the ledger's status vocabulary
(live | experimental | planned | dead) is documented in a comment at the top
of packages/spec/scripts/liveness/check-liveness.mts, not enforced by a
Zod 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 same
defect class the card reports, just with a different trigger. describe() now
throws, naming the offending status, when it sees anything other than
experimental / planned / dead. This is scoped narrower than the file's
general "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: true and no status at
all, relying on the old silent-dead fallthrough. Updated to declare
status: 'dead' explicitly — that block asserts nothing about verdicts, only
about the getNested array walk, so the choice of status is arbitrary but
must now be one of the three.

Verification

  • pnpm --filter '@objectstack/lint^...' build then
    pnpm --filter @objectstack/lint build — clean.
  • pnpm --filter @objectstack/lint test2266/2266 passed (80 files),
    including 8 new cases pinning all three verdicts' message+rule-id pairs, the
    planned default hint (never "Remove it"), the unknown-status throw (named
    status in the message), and a live-mistakenly-authorWarn'd row also
    throwing.
  • pnpm --filter @objectstack/lint typecheck — clean.
  • Reverse verification: committed the fix, then reverted just the two
    source files (lint-liveness-properties.ts, index.ts) to their
    origin/main content via git checkout, to stand up the pre-patch source
    under the new tests. 7 of the 8 new tests failed (rule id
    liveness-dead-property instead of liveness-planned-property; "Remove
    it" leaking into the experimental default hint; the two throw-expectation
    tests found nothing thrown). The 8th (dead branch, 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 HEAD clean) before re-running the full suite
    green.
  • Live capture — running the linter over a fixture with all three
    shipped planned rows 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.relatedListFilter and object.externalSharingModel produce the
    matching shape.)
  • Local gate list derived via node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack from this worktree — every path-derived and
    convention-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, and check:type-check-coverage (both the plain run
    and the --re-measure ratchet, the latter after building the full
    ./packages/* + ./packages/*/* closure it requires — 70/70 tasks) — no
    new debt, no ratchet moved (32 ledger entries re-measured, none above their
    recorded ceiling). pnpm lint (repo-wide ESLint) is CI's to run, not
    re-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 added: .changeset/planned-liveness-verdict-not-dead.md
    (@objectstack/lint, patch — @objectstack/lint sits in the repo's fixed
    publish group).

Scope

Touches only packages/lint/src/lint-liveness-properties.ts, its test file,
and the index.ts export barrel. No packages/spec/src/** diff — the ledger
JSON files and their status vocabulary are unchanged; this is purely the
author-facing lint's own verdict mapping.

Fixes#11384


Generated by Claude Code

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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ✅

What this run could not see
  • 1 changed file(s) yielded no anchor (packages/lint/src/index.ts) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json daacc1071c69604976796f92d39448117b3b4990packageMentionDocs.

Which tree this was computed on

This run read content/docs from 066db27cac5a39e3c75980a949fa192214fd4d34 — the merge of head 25e144edff6c55e70c1f259ae43093568bda89d0 into base daacc1071c69604976796f92d39448117b3b4990, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 24, 2026
@os-steve
os-steve marked this pull request as ready for review August 24, 2026 02:38
@os-steve
os-steve added this pull request to the merge queueAug 24, 2026
Merged via the queue into main with commit ba8420bAug 24, 2026
35 checks passed
@os-steve
os-steve deleted the claude/issue-11384-planned-liveness-verdict branch August 24, 2026 03:31
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lintLivenessProperties tells the author a planned property is dead — the message and the hint on the same finding contradict each other

2 participants

@os-steve@claude