Uh oh!
There was an error while loading. Please reload this page.
fix(lint): say in the *When skip trailer that a skip is not a to-do item - #11708
Conversation
The skip list prints, per entry, why the PARSER could not read a layer. It never says what layer the fragment documents, and those are different questions: only the second decides whether a site could be re-authored. Read as a worklist, the list produced a measurably wrong triage. Three independent passes partitioned seven skips 4 re-authorable / 3 permanent; judging the sites first gave 1 / 6, and one of the "re-authorable" four was byte-identical to a skip the same ruling protected by name as a false red on correct documentation. Adds the framing sentence to the trailer and pins it, plus a guard that no rendered entry ever NAMES a layer the gate did not derive. Verdicts, counts and the discriminator are untouched: 14 admitted / 6 skipped on both sides. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015ahemw8RcTgqtxrj15PEZx
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. |
os-steve
commented
Aug 24, 2026
ACCEPT — and the ruling on your open question is A. Route B is declined, permanently, on your evidence. This is the outcome I asked for and did not expect to get. I wrote "I would rather be told the second half is unnecessary than get it built dutifully." You measured it and told me. Three separate arguments, and each would have been sufficient: 1. The decisive one — context was already available and did not work
The author had the context in hand, published it in the same artifact, and still misclassified both. So the failure was never an information deficit, and route B reprints information already demonstrated insufficient. That is a fact about a closed card which cannot change, which is what makes the decline permanent rather than provisional. 2. Route B's window is actively misleading at exactly the sites it would serveVerified independently: Both read as object-field definitions. Neither is one — ⭐ Your framing of why that is disqualifying is the part worth keeping:
It does not eliminate the guess. It relocates it to someone with no rule binding them. I did not see that when I wrote the option, and it inverts my assumption — I took for granted that more context helps, and it misleads precisely where the gate cannot help. 3. The comment is 3 of 6, not the patternConfirmed: only the three The fix itselfRoute A only, one file, and pinned twice — which is what makes it more than a comment:
⭐ Ablation B is the detail that shows the pins are real: injecting the forbidden per-entry claim reddens pin 2 while pin 1 stays green. Two pins that always fail together are one pin with extra steps; these are independent. Counts re-measured at your own base (14/6, not the report's 13/7) and identical across the change, since no verdict moved. Discriminator untouched. Your refinement of my framing is accepted, and it is the better statement
I had said the reason texts caused the misclassification. They did not; the layer comments for two of the three misread sites were already quoted in the report that misread them. What the reasons did was make a worklist reading feel safe. The trailer attacks the reading; context would have attacked the symptom. Ruling: A — close #11673 on this PRNot C. Your durability argument decides it:
A card can be closed and forgotten; a PR body is unreachable from the code. A TSDoc on the renderer is read by the exact person who would otherwise reimplement route B. That is the right home. I will close #11673 by hand once this lands — the PR is Flipping ready; arming once every check is green. Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Part of #11673
Route A from the card: the trailer sentence only. The second half — per-entry
source context — is recommended against, on measured grounds set out below. That
recommendation is the PM's to ratify, which is why this says
Part ofrather than aclosing keyword.
What changed
packages/lint/scripts/check-doc-formula-expressions.mjs, one file, +63 lines:renderFieldRuleSkips()now says a skip is not a to-do item, thatevery reason above answers "why could this scan not read a layer here?" and never
"what layer does this fragment document?", and that the layer must be read off the
document before anyone re-authors a listed site.
breaks no other assertion in this file and turns no gate red anywhere:
the fix itself. The cheap version of this card (scrape the nearby comment, print
PageComponent) is easy to write and exactly wrong, and it would be believed.rather than per-entry context.
Zero behaviour change. The discriminator is untouched, no verdict moved, no count moved.
Measurements
Re-measured at base
ffbb7a100, not quoted from the report body (which predates #11672):Identical before and after. Self-test
48 → 50 cases passed(the two new pins).Non-vacuity, both directions, predicted before running
Both ablations ran under
trap … EXIT INT TERM; both restores verified byte-identicalby sha256 against the pristine copy. No rebuild leg applies — the mutated artifact is the
.mjsnode executes directly, not adist/(the gate imports@objectstack/lint/dist,but the script itself is never resolved through it).
✗ REPORT — the trailer says a skip is NOT a to-do item …, self-test1 case(s) failed, exit 1this fragment documents the PageComponent layerper entry✗ REPORT — no rendered skip entry names a layer the gate did not derive; pin 1 stayed✓Mutation proven on disk each time by anchored marker counts and sha256 change — the
edit script
asserts its anchor count, so a zero-hit replace exits non-zero rather thanreporting a healthy no-op. Ablation B's pin-1-stays-green row is what shows the two pins
are independent, i.e. that pin 2 really is the claim guard and not a second copy of pin 1.
Why the second half is recommended against
The card's hypothesis was that printing the two source lines above each site would let a
reader triage the list without opening each file. Checking the other three skips falsifies
it, and the counter-evidence is stronger than "it doesn't always help".
The layer-stating comment does not generalise — it is 3 of 6, all in one file.
layout-dsl.mdx:821// e.g. on a PageComponent — …layout-dsl.mdx:824// e.g. on a FormSection / FormField — …layout-dsl.mdx:863// On a PageComponent, an app/nav entry, or a per-option visibleWhen:flows.mdx:320{ name: 'opportunityName', label: …, type: 'text',/required: true,pages.mdx:165onClick: "navigate_to(…)"/},views.mdx:415required: true, // Override required/span: 'full', // …And for
flows.mdx:320the two-line window is actively misleading, in precisely thiscard's own failure direction. It hands the reader
{ name, label, type, required }— theshape #11651's ruling called "probably a field def", about which it said "probably is
exactly what a layer discriminator must refuse". The site is a flow-screen field whose bare
reference is correct. Route B would move that guess out of the gate, which is forbidden to
make it, and into the reader, who is under no such discipline.
views.mdx:415reads thesame way.
The decisive evidence is that per-entry context was already available and did not work.
#11651's own report body quoted the layer comments above
:821and:824verbatim —— and filed both sites under "re-authorable" in the same document. The author had the
context in hand, published it, and still misclassified. So the failure was never an
information deficit; it was a framing error, and route B reprints information that has
already been demonstrated insufficient. The trailer addresses the thing that actually broke.
Verification
All at final HEAD
4438ef8dc. Gate families derived, not recalled:node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack— provenance lineconfirmed "gate list derived from the tree of 'objectstack-ai/objectstack' at commit
ffbb7a1 … --repo … checked against this checkout's 'origin' remote — it holds", change
set "1 path(s) vs merge base … three-dot semantics". 7 families matched; all run locally,
all green, exit codes captured by redirect-then-capture before any pipe.
check:doc-formula-expressions✓ … 14 predicate(s) … judged clean; 6 skipped as undeterminable.·✓ … self-test: 50 cases passedcheck:published-files✓ … 69 publishable package(s) of 78 workspace member(s) …check:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none new …check:test-source-aliascheck-test-source-alias OK — 72 packages with tests scanned …check:type-source-resolutioncheck-type-source-resolution OK — 77 packages with a tsconfig.json scanned …check-plugin-teardown-shape.mjs✓ … 63 Plugin implementation(s) across 4597 source(s) …check-affected-docs.mjs✓ affected-docs self-test: 395 cases pass.Also
check:nul-bytes(any edit):check-nul-bytes: OK (scanned 6536 text file(s) … no raw ASCII control bytes), plus a control-byte self-scan of the edited file with a positivecontrol proving the scanner fires.
pnpm lint— declared narrowing, not a skip. Raneslint --no-inline-config --format jsonon the changed file: 1 file linted, 0 errors, 0 warnings, count read from the JSONrather than asserted. Positive control: linting
packages/lint/scripts/yields 2 files andthe changed file is one of them, so the clean reading is a measurement and not an empty
population. Invariance for untouched files is read off the repo's own config, which states
it "never enables type-aware linting (no
parserOptions.project, no typed@typescript-eslintrules) for ANY file" — so a one-file diff cannot move any verdict on afile it does not touch. CI runs the repo-wide scan regardless.
NOT MEASURED: CI conclusions — this PR and its report are delivered at draft-PR time
per the standing contract.
No changeset — checked against the rule, not assumed
changeset-checkinpr-automation.ymlhas no path-based exemption: it countsgit diff --diff-filter=A … '.changeset/*.md'for every PR, and the only two escapes arethe
skip-changesetlabel and thechangeset-release/mainPR. So the label is themechanism, and the question is only whether this PR publishes anything. It does not:
@objectstack/lintdeclaresfiles: ["dist","README.md","CHANGELOG.md"], andnpm pack --dry-runlists 17 entries with zero underscripts/. The changed file shipsto no consumer; it is CI-only tooling.
skip-changesetapplied.Generated by Claude Code
Generated by Claude Code