Repository navigation
test(lint): derive the list-view position-coverage criterion from the rule - #18876
Conversation
… rule The test table's docblock claimed two directions and the assertion under it carried one. `expect(cases.length).toBeGreaterThanOrEqual(51)` counts the rows of the test table, which a position added to the rule's own POSITIONS table never moves, so "a position added to the rule without a row here leaves the count assertion below short" reported nothing at all. That is the direction #18565 came in through. Replace the floor with set equality against a set derived from the rule's own POSITIONS and COLUMN_ENTRY_POSITIONS tables, exposed as a same-package `listViewWalkedPositions()` seam that reaches no published entry (measured: every dist artifact byte-identical to the build that predates it). Both directions now hold, and the offender is named either way. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. 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 35c40cd6419eb0655e2b1f6191af5ecb27a8b140 && git checkout 35c40cd6419eb0655e2b1f6191af5ecb27a8b140
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 0b31d90fb37d490d7da12c7acd4b14c4feb22501 9897eaae2913a6e7e6581b84de95a82eb07369df && git checkout -B drift-repro 0b31d90fb37d490d7da12c7acd4b14c4feb22501 && git merge --no-ff 9897eaae2913a6e7e6581b84de95a82eb07369df
node scripts/docs-audit/affected-docs.mjs --json 0b31d90fb37d490d7da12c7acd4b14c4feb22501 |
Contract reviewServed-tier: 110/110 ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: FAIL Generated by Claude Code |
席位采信记录 —— 达档契约复核 FAIL,⛔ 载体不剥,本 PR 回工⏱️ 2026-09-18T03:55Z。
⭐ 本席独立复验了那条致命读数 —— ⛔ 不是采信转述FAIL 压在一条判断上:新判据在地板本来就守着的那个方向上丢了覆盖。本席从复核者自己的 ablation 日志逐条读出: 而代码自己给出了机制,⏱️ 2026-09-18T03:55Z 本席现读分支上
⇒ 那三条 filter-walk 的表行按构造就在覆盖集之外。 ⭐⭐ 而这正是本卡自己的论点,反噬在它自己的修法上本卡的论点是:一条声称了自己没有的保护的注释,比没有注释更坏 —— 因为它让下一个读者不再去找别的保护。 ⇒ 而新加的行内注释写着「a row deleted from either table takes its position off the covered set, so the deletion is red」,对那三行为假。⇒ ⭐ 修法交付了一条声称了它没有的保护的新注释。
回工清单 —— 三项,归属分明
⛔ 本席不做的
⏱️ 2026-09-18T03:55Z · Generated by Claude Code |
…hangeset Contract review measured the new criterion losing coverage the row-counting floor had: deleting the two filter-walk rows was red at base (the count fell below 51) and silent at head, because those three walks are open code rather than table rows and nothing derives them. The inline comment nevertheless claimed "a row deleted from either table takes its position off the covered set, so the deletion is red" -- a comment claiming protection it lacks, which is the defect this card is about, delivered by the card's own fix. Declare the three walks as an explicit list and assert it exactly, so a deleted filter row is red and an undeclared fourth walk is red. Restate the residue in the comment per kind of row, including the one seam left open and disclosed: the two positions asserted twice on purpose. Drop the changeset. The review measured 0 changed bytes in every code and type artifact, so a minor would move about 70 lockstep packages for nothing; skip-changeset is the grade. Claude-Session: https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3 Co-authored-by: Claude <noreply@anthropic.com>
席位:回工三项已收,正文与标签由本席办完 —— ⭐ 新 head 上重起复核(⛔ 不是重摇)⏱️ 2026-09-18T04:25Z。head 本席逐条复验(⛔ 不按报告叙述)
⭐ dev 走了第二条路(把三条硬编码 filter walk 声明成显式列表并精确断言),⛔ 不是第一条(只写明它们在判据外)。⇒ 那恢复了旧地板本来守着、而派生判据结构上够不到的那半覆盖 —— 复核测出「base 红 / head 静默」的那三个删除,现在 head 上是红的。 ⭐ 而 dev 还报告了一件溢出的加强:新的姊妹断言同时抓住「规则里删掉一个位置之后留下的陈旧表行」(该行两边都不匹配,冒出来成为 extra)⇒ 方向一现在被守了两次。 (b) 正文:本席改了两处,⛔ 两处都留了可见的更正块
|
Contract reviewServed-tier: 95/95 ① Derived judgments
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位采信记录 —— 回工后的达档复核 PASS,双载体已清⏱️ 2026-09-18T04:46Z。
⭐ 这是一次回工后的新复核,⛔ 不是重摇⏱️ 2026-09-18T04:46Z 复读:上一轮在 head 上一轮 FAIL 点名的四项,复核逐项判 CLOSED,本席复核其中两项⏱️ 2026-09-18T04:46Z 取数,下列每条读数皆然。
|
Fixes #18836
Clause-②: no
The test table's docblock claimed two directions and the assertion under it carried one. This replaces the floor with a criterion derived from the rule's own tables, and makes the docblock true.
The premise, re-measured
Every reading below was taken on this branch's base,
origin/main36583e989b— the dispatch quoteda43b9d0654, this worktree branched later, so the numbers were re-taken rather than inherited. They land in the same place:packages/lint/src/validate-list-view-field-refs.test.ts:205-210, still word for worda position added to the rule without a row here leaves the count assertion below short:315,expect(cases.length).toBeGreaterThanOrEqual(51)cases.lengthat that commit: 52Both directions were measured by mutating the rule's
POSITIONStable on disk and running the rule's own test file. Each mutation proved itself on disk (anchor count 1 before, 0 after; injected line count 1; blob hash moved) and was restored under a trap, withgit diff HEADproving the restore byte for byte.tree.labelFieldtree.ablationProbeFieldcovers every position the rule walks, diff names- "tree.ablationProbeField"Direction two reported nothing at all:
cases.lengthcounts the rows of the test table, which a position added to the rule never moves, so the floor stayed true. That is the direction #18565 came in through. After this PR the failing assertion is the completeness one, and its diff names the offender (- "tree.ablationProbeField").Direction one is unchanged and still carried by the per-case assertions, exactly as the docblock's first half says — the failing test both before and after is
views[0].list.tree.labelField reports at warning.A third reading the card did not claim. The floor did not reliably do even its own advertised job. Deleting one row from the table (
tree.labelField) on the base tree left 129 passed, 0 failed — 52 rows against a floor of 51 is exactly one row of slack. The same deletion after this PR is red, and the failing assertion is the completeness one.Step 1 — does the new export reach a published entry?
@objectstack/lintis not private,filesisdist/README.md/CHANGELOG.md, andexportshas.and./runtime— sosrc/does not ship butdist/does. Re-measured on this base and it holds. Two facts about the barrels:src/index.tsre-exports this module by NAME (there is noexport *in either barrel, count 0), andsrc/runtime.tsdoes not reference the module at all.Readings after
pnpm --filter @objectstack/lint build, greppingdist/. Bright control throughout:validateListViewFieldRefs, a symbol known to ship.Shape measured first —
export const X = Object.freeze([...]):dist/fileindex.jsindex.cjsruntime.jsruntime.cjsindex.d.tsindex.d.ctsruntime.d.ts/runtime.d.ctsruntime-DAPAkqcs.d.ts/.d.ctsA raw count in a bundle body is not a published entry, so the export layer was measured separately. In
dist/index.js'sexport {}clause: new symbol 0, against 1 each forvalidateListViewFieldRefs,LIST_VIEW_FIELD_DOTTEDandLIST_VIEW_FIELD_UNKNOWN. Indist/index.cjs's__exportmap: new symbol 0, controls 1 each. Dark control for that layer:COLUMN_ENTRY_POSITIONS, a module const the rule has never exported, reads 2 inindex.jsand 2 inindex.cjs— a name in the bundle body is genuinely no evidence of a published entry.Under this shape four
distJS files moved bytes (esbuild keeps a frozen initializer as dead weight); all four.d.ts/.d.ctsfiles were byte-identical.Shape shipped —
export function listViewWalkedPositions(): the symbol is tree-shaken. 0 occurrences in all eight published files, while the bright control still reads 3 / 4 / 2 with the same instrument on the same files.Reading: the new export reaches no published entry and moves zero bytes of the published artifact. Per the dispatch's step-2 table, that is route A, and route A is what this PR implements.
On the declaration line above
Clause-②: yes (widening)was the dispatch's mechanical floor, declared before this measurement existed and routed for the seat's contract review. The at-tier review (record5724912914) overturned it tono, with no direction arm — the diff widens no accept set and expands no public surface. Perreferences/lanes/spec.md:20a conservativeyesis never an error, so the overturn is by design and is ⛔ not a fault of the author.What changed
validate-list-view-field-refs.tsgains one module-level export,listViewWalkedPositions(), which derives theblock.keyid of every field-naming position from the rule's ownPOSITIONSandCOLUMN_ENTRY_POSITIONStables. 52 positions today. NoPOSITIONSrow is touched — not a position, not a tier, not a severity — and the barrel is untouched.validate-list-view-field-refs.test.ts: the floor becomes set equality between the positions the rule walks and the positions this file's two case tables assert. The five-row measured table moves to module scope because three positions (grouping.fields,kanban.groupByField,gantt.startDateField) are asserted there and nowhere else; a criterion reading only the sibling table would need a hand-written exemption for them, which is the kind of constant this card is about. The per-case assertion bodies are unchanged, byte for byte.Why set equality and not the count equality route A suggested
Route A said: derive the count from
POSITIONSand compare it tocases.length. That equality cannot be written without magic constants, because rows are not positions:columnsandgantt.tooltipFields: once as a bare name, once as a record with afieldkey), so the second table holds 1 duplicate row.The arithmetic a count equality would have had to hard-code is
52 = 52 - 3 + 2 + 1. Set equality states the property directly instead, names the offending position in the failure diff, and needs no constant at all. Per the dispatch's own zoning this is the suggested-route zone, so it is reported rather than silently taken: the ruling (measurement decides A vs B) is executed as written.What the new assertion does NOT carry, stated in the code so it cannot become the next overclaim: it is one-directional.
positionAssertedanswers only with positions the rule currently walks, so a row for a position the rule has dropped contributes to neither side and the assertion stays green — that direction belongs to the per-case assertions, and both halves are measured above. A first draft of the comment claimed both directions; the direction-one probe showed only the per-case assertion firing, and the comment was corrected before the first push rather than shipped.docblock, sentence by sentence
Every remaining position, with the severity tier it earns.The table is the readable half of the rule's own POSITIONS tablea position dropped from the rule fails herea position added to the rule without a row here leaves the count assertion below shorta position added to the rule without a row in either table fails the completeness assertion below — which derives the set of positions from the rule's own tables instead of counting this one's rowsThe inline comment above the assertion
The dispatch fenced it as accurate and not to be rewritten as if it were the defect. It is not rewritten as a finding — it is replaced because the assertion it annotates no longer exists, and the dispatch also forbids leaving a floor and an equality side by side. Its semantics were right; the one-row slack measured above is reported as a reading, not fixed as a second thing.
Verification
Run through the shared verify lock. Package:
@objectstack/lint.pnpm --filter @objectstack/lint testit)pnpm --filter @objectstack/lint typechecktsc --noEmitpluscheck:test-typecheck: 2 files / 6 errors / 2 pinned signatures held)pnpm lint(repo-wide,eslint . --no-inline-config)Gates:
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 51 families from this tree at9897eaae29(58 at the previous head; the 7 that left are the changeset families, since the changeset was deleted); all 51 were run and each exit code recorded before any pipe, then reconciled with--ran. 48 green, 3 NOT MEASURED —check:dual-build-cjs-loads,check:lean-entry-closureandcheck:type-check-debteach exited 3, PREREQUISITE NOT MET: all three read built output across the whole repo, which this worktree does not have. They are neither a pass nor a failure here, and CI builds fresh and runs them. Thedistbyte-identity measured above is why this diff cannot move their verdict.Dependency-closure build:
pnpm --filter '@objectstack/lint^...' build, exit 0.Acceptance notes
POSITIONStable, the package barrel and the per-case assertion bodies are untouched; the only positions-table edits in this branch were the two ablation mutations, each restored and proven restored.dist/runtime.jsbundlesvalidateListViewFieldRefsalthoughdist/runtime.d.tsexports it nowhere. That is the documented consequence ofsplitting: falseinpackages/lint/tsup.config.ts, whose own header carries the measurement, not a defect. Carrier: none.Generated by Claude Code