Skip to content

docs(lint): os explain for the two list-view binding verdicts, and the whole-collection docblocks say what is now true - #24858

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24796-lint-closure-docs-explain
Oct 20, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-24796-lint-closure-docs-explain

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Closes #24796

Clause-②: no

Comments and explanation text only. No verdict, severity or accept set moves.

What changed

  • packages/lint/src/authoring-rules.ts: the AuthoringRuleContext.packageScope docblock (it ships in the published .d.ts). It used to say an object missing from a whole snapshot "may come from another package". It now says that what such a miss means depends on who built the snapshot. On the CLI the stack is one app, so "exists nowhere" cannot be decided. At the runtime door with no closure in force the snapshot is the whole served collection, so the object exists nowhere, and a list view bound to it is refused list-view-object-unknown.
  • packages/lint/src/runtime-gate.ts: the comment at the packageScope spread in runRuntimeAuthoringRules gets the same fix. It used to say "from one a whole-collection snapshot merely does not carry". It now says that without a closure the snapshots carry the whole collection, and only the CLI still cannot decide.
  • packages/lint/src/rule-explanations.ts:
    • New os explain list-view-object-outside-closure entry. It covers the package closure's limbs (as narrowObjectsToPackageClosure keeps them), why the binding is not resolvable by declaration, the matching dataset-door verdict, why field positions stay unjudged, and the remedy (declare the dependency, or bind inside the closure).
    • New os explain list-view-object-unknown entry. It covers the whole collection a write with no package is judged against, why that lets the door say "exists nowhere", why the nearest name rides the fix: line rather than the verdict, why an empty collection is not judged, and why the CLI still skips the binding.
    • The two entries share one paragraph (LIST_VIEW_BINDING_SCOPE): the binding keys, one finding per list view, and that drafts and stored rows are not judged. This is the pattern LIST_VIEW_HEAD_ONLY already uses.
    • Bounded in-place fix, same staleness class and same file: the last paragraph of the existing list-view-field-unknown entry said "An unresolvable data.object skips the whole list view". That is now true only on os validate / os build / os lint. The paragraph now names the two runtime ids that refuse the binding instead. The pinned phrase "one typo yields one finding" is kept.
  • packages/lint/src/validate-list-view-field-refs.test.ts: the existing pin "os explain carries what the verdicts no longer say" now also covers the two new ids. Each must have an entry, and each entry must name its closure/collection facts and its sibling id.
  • .changeset/24796-list-view-binding-explanations.md: @objectstack/lint patch.

Each text was checked against the implementation: validate-list-view-field-refs.ts (the two report functions and their section docblocks), reference-integrity-suite.ts (the wholeCollection / packageScope dispatch), and runtime-gate.ts (narrowObjectsToPackageClosure, packageScopeInForce). Neither entry describes the flow door's flow-trigger-unknown-object verdict, which open PR #24839 is changing.

Parity / pin tests

rule-explanations.test.ts checks only that each key is an exported rule id. It does not require every rule id to have an entry. Both ids are already exported from the root barrel (LIST_VIEW_OBJECT_OUTSIDE_CLOSURE, LIST_VIEW_OBJECT_UNKNOWN), and that test passes unchanged. The parity pattern this family uses is the per-rule facts pin in validate-list-view-field-refs.test.ts, and that is what this PR extends. No generated artifact pins the explanation set.

Verification (head f6a4f21b73)

  • pnpm --filter '@objectstack/lint^...' build: VERDICT command-exit 0.
  • vitest run on rule-explanations.test.ts, validate-list-view-field-refs.test.ts, runtime-gate.whole-collection.test.ts and runtime-gate.package-closure.test.ts: 4 files, 185 tests passed.
  • @objectstack/lint pnpm test: 150 files passed; 6974 tests passed, 5 skipped.
  • @objectstack/lint pnpm typecheck: tsc --noEmit clean, and check:test-typecheck OK.
  • @objectstack/cli test/explain-rule-id.test.ts (unit tier, after building lint and the cli dependency closure): 14 tests passed. That suite holds rule ids disjoint from os explain schema names.
  • dispatch-gates --commands: 66 derived commands (65, plus check-changeset-fixed, whose roster sits under .changeset/) all exit 0. check:dual-build-cjs-loads, check:lean-entry-closure and check:type-check-debt first exited 3 (PREREQUISITE NOT MET: no workspace build, and a lock queue timeout). That is not a measurement. They went green when re-run after check:type-check-debt --re-measure built the workspace: re-measure OK, 26 raw errors, none above the record. check:doc-authoring (no tracker number in author-facing strings) and check:nul-bytes are among the greens.
  • Ablation of the new pin: with the fix committed, scripts/ablation-replace.mjs --delete removed the list-view-object-unknown registry row (anchor 1 to 0, blob 0cd9479e66 to 6ea77f0c56). The facts test went red with the assertion naming the missing os explain list-view-object-unknown entry (expected undefined to be defined). The file was then restored with git checkout HEAD --: the blob equals HEAD (0cd9479e66) and git diff HEAD is empty. The test reads src through a relative import, so no rebuild leg applies.

Acceptance notes


Generated by Claude Code

…e whole-collection docblocks

`os explain` gains entries for list-view-object-unknown and
list-view-object-outside-closure; list-view-field-unknown's skip paragraph
now says the runtime door refuses the binding instead. The packageScope
docblock and the runtime gate's comment no longer say a whole-snapshot miss
may come from another package: that is true only on the CLI. No verdict
change.

Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
Co-authored-by: Claude <noreply@anthropic.com>
Resolves the AuthoringRuleContext.packageScope docblock conflict with the
flow-trigger closure change: keeps that change's "Read by two rules"
sentence and this branch's "exists nowhere" correction.

Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

6 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
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

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 abd5acdaf788df2092064c56583fd73d837fb858 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from cc51177407590fb56167b25527353ba46b3c6635 — the merge of head e846d135088e64fd376a80fd4f4a3c9d0b1b0dde into base abd5acdaf788df2092064c56583fd73d837fb858, 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 cc51177407590fb56167b25527353ba46b3c6635 && git checkout cc51177407590fb56167b25527353ba46b3c6635
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin abd5acdaf788df2092064c56583fd73d837fb858 e846d135088e64fd376a80fd4f4a3c9d0b1b0dde && git checkout -B drift-repro abd5acdaf788df2092064c56583fd73d837fb858 && git merge --no-ff e846d135088e64fd376a80fd4f4a3c9d0b1b0dde

node scripts/docs-audit/affected-docs.mjs --json abd5acdaf788df2092064c56583fd73d837fb858

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants