Uh oh!
There was an error while loading. Please reload this page.
fix(lint): teach describe() the live-elsewhere verdict instead of throwing - #14632
Conversation
…owing `describe()` in `lint-liveness-properties.ts` mapped three ledger verdicts to author-facing findings and threw, by design, on anything else. #13483 shipped the ledger's fifth status — `live-elsewhere` (dead here by measurement, genuinely enforced in a sibling repo; first row `manifest.runtime`, enforced at the cloud marketplace publish gate) — without teaching this function about it, so the day such a row opts into `authorWarn: true` the author gets a crash instead of an advisory warning. `live-elsewhere` now has its own branch and its own rule id (`liveness-live-elsewhere-property`), a message that says the property is enforced in a sibling repo rather than here, and a default hint that keeps the property and points at the ledger row's evidence. It must never fall into the `dead` branch: per #11384 the verdicts imply opposite author actions, and "Remove it" is exactly the wrong sentence about a key whose enforcement is real and remote — deleting it tears out a live gate's input. The sentinel throw stays for genuinely unknown statuses, its enumeration updated. The suite gains a coverage pin derived from the shipped ledger rows — not a hand-list — so a sixth status fails by name instead of repeating this card. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
…nt-liveness-live-elsewhere
`check:doc-authoring` (sibling-package prose-id leg) fails a NEW `#NNNN` inside a runtime string: an author, an operator or a generated surface reads that message and cannot resolve a tracker id. The reference moves to the adjacent `//` comment, where the reader who can resolve it is already looking. Maintainer ruling 2026-08-12, quoted by the gate: 处理 issue 时犯的错应该总结成经验,保留 issue id没有意义 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
📓 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 — 5 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 913f571e75fbda67f34777d7051aeaa695dca7df && git checkout 913f571e75fbda67f34777d7051aeaa695dca7df
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 937ec142d5bb6e3f112dae2530842b1292be1cd4 6d75c646275cbb88c980bbd3c4d06799fe68b17f && git checkout -B drift-repro 937ec142d5bb6e3f112dae2530842b1292be1cd4 && git merge --no-ff 6d75c646275cbb88c980bbd3c4d06799fe68b17f
node scripts/docs-audit/affected-docs.mjs --json 937ec142d5bb6e3f112dae2530842b1292be1cd4 |
…wn at minor Two defects in the previous commit's changeset, neither in the implementation. RESTORED. `.changeset/liveness-live-elsewhere-verdict.md` already existed on `origin/main` — it is #13483's, declaring `@objectstack/spec: patch` and describing the fifth verdict, its gate-executable criteria, the 180-day re-attestation discipline and the `manifest.runtime` migration. Writing this package's note to that path truncated it, which would have dropped the pending `@objectstack/spec` bump and taken all of that out of the next release notes. `packages/spec/CHANGELOG.md` has zero hits for `live-elsewhere`, so it is unconsumed and still owed, not a stale leftover. The file is restored byte for byte from `origin/main` (blob 5321f1b) and this package's note moves to `.changeset/lint-liveness-live-elsewhere-rule-id.md`. LEVEL. `minor`, not `patch`: `LIVENESS_LIVE_ELSEWHERE_PROPERTY` is a new export on `packages/lint/src/index.ts`, the public entry, and a new public-entry export is `minor` under the precedent this lane applied today (#14606 took `@objectstack/types: minor` for three new exports). Nothing narrows, so no BREAKING banner and no ADR-0087 marker are owed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68
os-musk
commented
Sep 2, 2026
Landing provenance (engine execution seat, session
Generated by Claude Code |
Fixes#14057
describe()inpackages/lint/src/lint-liveness-properties.tsmaps ledger verdicts to author-facing findings, and throws — deliberately, per its own boundary comment — on a status it has not been taught. #13483 then shipped the ledger's fifth status,live-elsewhere(dead HERE by measurement, genuinely enforced in a sibling repo), and migratedmanifest.runtimeonto it.describe()was never taught it. The day anylive-elsewhererow opts intoauthorWarn: true,os lintraises a shipped-ledger-integrity error instead of the advisory warning the author should have received.It must never be the
deadbranchThis is the load-bearing constraint on the repair, and it is why the fix is a new branch rather than a widened one. Per the #11384 lesson, the verdicts imply opposite author actions, so telling an author to "Remove it" about a key whose enforcement is real and remote is exactly the wrong sentence: for
manifest.runtimethe enforcer is the cloud marketplace publish gate, where an unverified publisher requesting the node tier is hard-rejected, so deleting the key would tear out a live gate's input.live-elsewheretherefore gets its own rule id, its own message, and a default hint that keeps the property and points at the ledger row'sevidencefor the enforcer.What changed
describe(), placed against thedeadbranch it must never be, with a comment saying why the adjacency is deliberate: the two verdicts are one measurement apart and opposite in what they ask of the author.LIVENESS_LIVE_ELSEWHERE_PROPERTY = 'liveness-live-elsewhere-property', exported beside its three siblings and re-exported fromsrc/index.ts. Advisory-only, like them; nothing downstream keys off these ids today (measured: the only non-CHANGELOG consumers of any liveness rule id aresrc/index.ts, this package's tests, and one CLI test; there is no rule catalogue and no docs page listing them).check-liveness.mts's header asStatuses: live | experimental | planned | dead. That header now readslive | experimental | planned | dead | live-elsewhere.Reachability, measured
Still a fuse, not a fire. An entry reaches
describe()only aftershouldWarn()says yes (authorWarn: true, orstatus === 'experimental'), and the shippedmanifest.runtimerow carries noauthorWarn. A negative control on the real ledger pins that, with an anti-vacuity guard: the coverage walk must SEE alive-elsewhererow (the one row carrying that status lives inmanifest.json, so seeing the status proves the file was read) whileauthorWarnedProperties('manifest')must not containruntime. If that row ever opts in, the pin goes red and the reviewer updates the pin — the new branch is what makes that flip safe.Coverage, derived rather than hand-listed
The triage asked for a check that
describe()covers every verdict, because patching one status leaves the next to repeat this card. Hand-listing the vocabulary in a test would go stale for exactly the reasondescribe()did — nobody edits the list they did not know existed — so the pin walks the shipped ledger rows (shippedLedgerStatuses(), a package-internal seam in the same posture as the existing #10262 seam: exported from the module only, re-exported by neithersrc/index.tsnor the package'sexportsmap) and requires every distinct status it finds to be answered bydescribe()with a rule id of its own, plus asserts no two verdicts share a rule id. The vocabulary is prose in a comment, not an importable enum, so rows are the only machine-readable statement of it in this package's reach; a status with zero rows cannot reachdescribe()anyway, and the moment one appears, the pin names it.liveis the one member that deliberately gets no branch, and the source header already ruled it out in a sentence this PR keeps and now pins: an entry markedauthorWarn: trueon aliverow "would be a ledger authoring mistake, not a user error". A mistake in our own shipped data is what the sentinel is for, so the pin assertslivestays loud rather than handled — matching the case the suite already carried.Measured vocabulary: five members at
packages/spec/scripts/liveness/check-liveness.mts:36and inpackages/spec/liveness/README.md. Theinconclusivementioned in triage is not a ledger status — zero hits acrosspackages/spec/; it belongs to the skill-side vocabulary of the sibling card, which is out of scope here.Changeset
'@objectstack/lint': minor, in.changeset/lint-liveness-live-elsewhere-rule-id.md.LIVENESS_LIVE_ELSEWHERE_PROPERTYis a new export onpackages/lint/src/index.ts, the public entry, and a new public-entry export isminorunder the precedent this lane applied today — #14606 took@objectstack/types: minorfor three new exports. Nothing narrows, so no BREAKING banner and no ADR-0087 marker is owed; the behaviour change is an internal integrity error becoming the advisory finding it should always have been.An earlier revision of this PR got two things wrong here and both are fixed. It declared
patch, arguing from theplannedverdict's own changeset; and it wrote its note to.changeset/liveness-live-elsewhere-verdict.md, a filename derived from the verdict — which is #13483's existing file onorigin/main, declaring the pending@objectstack/spec: patchbump and describing the fifth verdict's gate-executable criteria and the 180-day re-attestation discipline. Writing there truncated it. That file is restored byte for byte (blob5321f1b09,git diff origin/mainon the path empty, and the re-derived three-dot change set no longer mentions it at all), and this package's note lives under its own non-colliding name.Verification, at
168c1d7fafor the code, re-confirmed at6d75c6462The head moved only by the two changeset files above; the re-derived gate family is byte-identical between the two heads, so the union below carries over.
pnpm --filter @objectstack/lint typecheckand the full package suite: 93 files, 2821 passed, 5 skipped. The new tests are genuinely in the typecheck population —tsconfig.test.json --listFileslistslint-liveness-properties.test.ts.live-elsewhere— the card's defect, reproduced:3 failed | 65 passed. Restored: blob equality with HEAD,git diff HEADempty,git status --porcelainempty,68 passed. The mutation was confirmed on disk before the run (removed text 0 hits, injected marker 1 hit, blob hash changed), and the subject is a package-relative source import, so nodistindirection is in the path.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(34 commands): 30 green.pnpm lint(whole repo),pnpm check:nul-bytesandpnpm check:error-status-conformancealso green. On the new head,check-empty-changeset("1 declaring changeset(s) added"),check-changeset-no-majorandcheck-adr-0087-registrationwere re-run: all exit 0.check-test-completeness,pm/check-half-states,check:dual-build-cjs-loads("PREREQUISITE NOT MET ... this is NOT a pass: nothing was measured") andcheck:type-check-debt. The latter two read built output for every package; CI builds that closure before running them.check:doc-authoringcaught a real defect in the first draft: the new tracker id sat inside the sentinel's runtime string, which reaches authors and generated surfaces that cannot resolve it. Moved to the adjacent//comment; the gate is green.Scope
packages/spec/liveness/**and its scripts were read, never changed. The skills-lane sibling card is untouched.🤖 Generated with Claude Code
https://claude.ai/code/session_0112hMx9hjJ9BgB28X97DS68