Skip to content

fix(lint): one-line verdicts for the dashboard-action, empty-filter, list-view-field and translation rules; os explain RULE_ID carries their reasoning - #22765

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22161-s2-lint-slice-8
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-22161-s2-lint-slice-8

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22161
Clause-②: no

Stage 2 of the card, slice 8: the 9 rule ids of five whole packages/lint source files — the dashboard header-action rules, the empty-filter rules, the list-view field-reference rules, the translation-reference rules and the translatable-section rule. The card stays open for the later slices listed under "Remaining for later slices" below.

What changes

  • One verdict line per finding. Each finding of the 9 ids prints a message of one verdict sentence, followed by Did you mean "…"? where the rule offers the nearest declared name. Every finding the rules' own suites fire is now 199 characters or fewer; the longest of each id was 226 to 553 before. The runtime publish gate's suite (the whole @objectstack/metadata-protocol suite) fires one of the 9, list-view-field-unknown, at 59 (198 before). The whole packages/cli unit tier and os validate on the four example apps fire none of the 9, before or after.
  • Where the verdict keeps a name, it is the one the author needs to find the defect. The dashboard ids keep the target (and, for a url target, the one segment that misses); the translation ids keep the key and the declaration it was looked up under; list-view-field-unknown keeps the shared field-path sentence (describeFieldPathVerdict, unchanged, shared with four other rules) and, for a dotted reference, which written name it judged; translation-section-name-missing keeps the heading and the key that can never exist.
  • The empty-filter verdicts still derive their row-set words ("matches EVERY row" / "matches NO row") from reduceFilterVerdict, so the conformance pins in validate-empty-combinators.test.ts (identity vocabulary agrees with FILTER_LOGIC_CASES, the row-set language is DERIVED from the shared reduction) pass unedited.
  • The reasoning moves to RULE_EXPLANATIONS (packages/lint/src/rule-explanations.ts), 9 new entries, so os explain RULE_ID prints it and the CLI's rule: line ends with the pointer for these ids. No CLI source changes: explainPointer() and os explain resolve any key the table holds (packages/cli/test/explain-rule-id.test.ts iterates every key; it ran in the unit tier below); node bin/run.js explain filter-empty-node prints the entry. The 102 entries already in the table are byte-equal (the diff of rule-explanations.ts is additions only).
  • Nothing else moves. Rule ids, severities, path, hint (the fix: line) and what each rule accepts or refuses are unchanged. No condition, branch, skip or dedupe moved, and no export was added, removed or renamed: every hunk in the five rule files is a message expression, a comment, or the removal of a value that fed only the old message. The plumbing hunks, each message-only:
    • validate-empty-combinators.ts: the local spelling (it fed only the old closing sentence "A literal … is not an authoring surface", now the explanation's) goes.
    • validate-list-view-field-refs.ts: the module-private SILENT_EMPTY constant (the consequence sentence every list-view-field-unknown message ended with) goes; its text is the explanation's. In describeDottedRefusal the three head-class because strings are cut to one clause each.
    • validate-translation-references.ts: message strings only, at every push site of both ids (TRANSLATION_TARGET_UNKNOWN through the orphan() helper and four action-entry sites, TRANSLATION_OPTION_KEY_UNKNOWN at six sites).
    • No helper changed shape; no truth table is involved.
  • .changeset/22161-lint-slice-8-one-line.md: @objectstack/lint patch, naming every door that prints the new text (below). Clause-②: no, as line 2 says: every rule id these entries key already had an exported constant on the root barrel (index.ts is untouched), and slice 1's contract review (② of its record) reads RULE_EXPLANATIONS entries as data in an existing export.
  • No rider was needed: no packages/cli/test or packages/metadata-protocol file asserts these ids' message text (they pin rule, path and the planted rule id), and no content/docs page quotes one of these messages as printed output. content/docs/deployment/validating-metadata.mdx describes the dashboard rule in code comments ("script target resolves to no defined action → error"); that describes behaviour, not printed text, and stays true.

The verdict forms, the longest of each arm the rule suites fired (the translation-section verdict prints a bracketed placeholder where this body writes NAME):

script action target "export_dashboard_pdf" names no defined action, so the button renders and does nothing when clicked
modal action target "create_opportunity" names no declared page (a modal target names a page, only), so the button renders and the runtime refuses the dispatch on click
url action target "/apps/no_such_app_nope/dashboard/no_such_dashboard_nope": no dashboard named "no_such_dashboard_nope" is registered in this stack, so the button likely opens a dead route
`$and: []` is a conjunction of ZERO conditions, which every backend reduces to its identity, so it matches EVERY row: this surface reads as filtered and is not
`$or: []` is a disjunction of ZERO branches, which every backend reduces to its identity, so it matches NO row: this surface renders permanently empty
`$not: {}` negates an EMPTY node, which is TRUE, so it matches NO row: the opposite of the "no filter" an empty operand looks like
An EMPTY filter node (`{}`) is TRUE, so it matches EVERY row exactly as if the key were absent: a filter is declared and enforces nothing
An EMPTY branch (`{}`) of a `$or` is TRUE, and one TRUE disjunct ABSORBS the disjunction (it matches EVERY row), so every branch written beside it is dead
An EMPTY branch (`{}`) of a `$and` is TRUE, the AND identity, so it contributes no condition and the conjunction means whatever its other branches mean
quickFilters[0].field "ownr" is not a field on object "duly_task". Did you mean "owner"? (the head of "ownr.name": a list view compiles no joins)
columns[0].field "payload.theme" is a dotted path, so the query projection refuses it (`assertProjectionHasNoDottedPaths`) and the view's first fetch answers 400 INVALID_FIELD
filterableFields[0] "owner.name" is a dotted path whose head "owner" is a `lookup` field on "duly_task" (it stores the related record's id), so no backend serves the path (400 INVALID_FIELD)
filter key "score.x" is a dotted path whose head "score" is a `formula` field on "duly_task" (computed on read, never stored), so no backend serves the path (400 INVALID_FIELD)
filter key "created_at.x" is a dotted path whose head "created_at" is a `datetime` field on "duly_task" (a single scalar value), so no backend serves the path (400 INVALID_FIELD)
Section "Who is this?" declares a label but no `name`, so no `objects.showcase_contact._sections.NAME.label` key can address it and its heading stays in the source locale in every locale
Option translation is keyed by the DISPLAY LABEL "Direct Mail" instead of the stored value "direct_mail", so the resolver never finds it and the option renders untranslated.
Option translation is keyed by "hk", which is not a value declared by screen field "our_entity" of screen "details" in flow "contract_intake", so the option renders untranslated. Did you mean "hq"?
Option translations are keyed under field "name" of object "crm_lead", which declares no `options` at all (field type "text"), so nothing reads this map.
Translations are keyed to "sys_approval_process", which carries a platform namespace prefix but no platform package registers it and this stack does not define it, so nothing resolves these keys.
Translations are keyed to navigation item "nav_deleted" of app "crm_enterprise", which this stack contributes into without declaring and contributes no such item to, so nothing reads this key.
Translations are keyed to validation rule "churn_reason_ghost", which object "demo_account" does not declare, so its refusal `message` stays in the source locale. Did you mean "churn_reason_present"?
Translations are keyed to screen field "owner", but screen "edit_lead" is an OBJECT-FORM screen over "crm_lead", whose input labels come from `objects.crm_lead.fields.*`, so nothing reads this key.
Translations are keyed to refusal "done", which flow "quote_generation" declares as an `end` node that completes, not one declaring `outcome: 'refused'`, so nothing resolves this key.
Translations are keyed to result field "token", but action "show_receipt"'s `resultDialog` declares no `fields` (it renders the whole response as JSON), so nothing reads this label.

translation-target-unknown has 33 message arms (every leg the rule walks) and translation-option-key-unknown 7; the block above shows the longest and the structurally distinct ones, and the rule's test fires and bounds every arm (below).

Shared prose: written once (the dispatch's route)

shared paragraph ids explanation constant (rule-explanations.ts) held to its source by
which dashboard surface is judged, and which targets are not both dashboard ids DASHBOARD_HEADER_ACTION_SCOPE one text under both ids; the route entry's collection list is held by running the rule on each collection, singular and plural
the empty-filter identity table both empty-filter ids EMPTY_FILTER_IDENTITIES reduceFilterVerdict on the four shapes
which filters are judged, and which are not both empty-filter ids EMPTY_FILTER_SCOPE FILTER_KEYS (every key named)
why only the head of a dotted name is judged both list-view ids LIST_VIEW_HEAD_ONLY one text under both ids; the position list is held to listViewWalkedPositions()
how a bundle key is read at all translation-target-unknown, translation-option-key-unknown TRANSLATION_LOOKUP one text under both ids; the toast keys are held to FLOW_TERMINAL_MESSAGE_KEYS

The explanation module imports nothing, so each fact it writes out is held to its source by the rule's own test, as in slices 5–7.

Census (taken first, before any edit, at the base bf515e724d)

Method: slice 4's scratch preload (NODE_OPTIONS=--import, never committed), which patches Array.prototype.push to record every finding-shaped object (rule + message) of the 9 ids, deduped by (rule, message) per process, with its push site. Lengths are message alone; the printed line adds where and : . Positive control in every run: field-no-consumers (and, in the runtime-gate suite, sort-field-unknown and searchable-field-unknown) added to the recorded set and recorded.

  • packages/lint suite, base 136 files / 6,351 tests (after the CLI, metadata-protocol and example closure build): all 9 ids fire, every one over 200 at its longest; the longest of each equals the figure PR fix(lint): one-line verdicts for the approval-approver, data-model, agent-authoring, view-reference and chart-binding rules; os explain RULE_ID carries their reasoning #22742's list carries.
  • packages/cli unit tier, the whole project (281 files, 4,176 tests, all loaded; packages/cli/dist built): fires none of the 9, before and after. Control recorded (7 messages).
  • Runtime publish gate: the whole @objectstack/metadata-protocol suite (227 files, 224 run, 28,116 passed), which reads @objectstack/lint from its built dist/, before on the base-built dist and after on the rebuilt one. It fires list-view-field-unknown once (protocol-publish-drafts-object-field-refs.test.ts, which pins the rule id, path and the field name, never the text); controls recorded both runs.
  • Example apps: os validate (the built CLI) on app-crm, app-todo, app-showcase and app-multi-package, exit 0 each, before and after: none of the 9 fire; control recorded (22 messages), and every app prints the same number of rule: lines before and after.
  • Extra, outside the dispatch's list: the two packages/cli integration-tier files that name these ids (authoring-rule-command-parity.test.ts, verify-author-time-stage.test.ts), which fire two of them.
  • After: the lint dist/ rebuilt from the slice (marker is registered in this stack, so the button likely opens a dead route 1 hit each in dist/index.js, index.cjs, runtime.js, runtime.cjs; the old the button likely navigates to a dead route 0; how a url header-action route is resolved 1 in dist/rule-explanations.js), the same runs at 0099dc5a9b (lint source byte-identical to the head; what came after is the changeset and the origin/main merge, which touches no packages/lint, packages/cli or packages/metadata-protocol file).
rule id severity lint suite: before (msgs · longest) → after (msgs · range) runtime-gate suite cli integration (2 files)
dashboard-action-target-undefined error 5 · 239 → 5 · 112–168 — 1 · 221 → 1 · 120
dashboard-action-route-unresolved warning 5 · 242 → 17 · 125–189 — —
filter-empty-combinator error 3 · 352 → 3 · 130–159 — —
filter-empty-node error 3 · 226 → 3 · 137–154 — —
list-view-field-unknown error / warning 62 · 355 → 62 · 54–145 1 · 198 → 1 · 59 1 · 202 → 1 · 63
list-view-field-dotted error 14 · 445 → 14 · 164–190 — —
translation-target-unknown error 110 · 345 → 114 · 122–199 — —
translation-option-key-unknown warning 12 · 230 → 13 · 135–197 — —
translation-section-name-missing warning 9 · 553 → 9 · 171–188 — —

All push sites fire before and after (dashboard 2, empty-filter 4, list-view 2, translation-target 5 sites with the orphan() helper's 19 callers behind one of them, translation-option 6, section 1). A higher message count after is a case this slice added (the route collection probe, the dialog-without-fields arm, the one-sentence pins).

Doors that print the new text

Read from the registry (authoring-rules.ts: validateDashboardActionRefs is commands: ALL, CLI only; validateEmptyCombinators is commands: ALL, CLI and runtime, runtimeTypes: ['flow', 'report']; the reference-integrity suite, commands: ALL, CLI and runtime, dispatches validateListViewFieldRefs on ['flow', 'view', 'object'] and validateTranslationReferences / validateTranslatableSections on the default ['flow']), the runtime gate's split (errors → the 422 issue, everything else → 2xx advisories plus the [Protocol] authoring advisory log line), and the CLI's callers (commands/validate.ts, compile.ts, lint.ts, verify.ts through judgeAuthorTimeRules on the validate set, and init.ts through validateScaffold on the build set):

door ids what changes
os validate, os build (and os compile, which os dev runs per compile), os lint, os verify, the scaffold check os init runs all 9 the text-face verdict line; the rule: line gains the os explain pointer; os validate --json errors and os build --json author-time issues carry the new message
runtime publish gate, flow or report write filter-empty-combinator, filter-empty-node (error) the 422 issue's message and the OS_ALLOW_UNLINTED_METADATA_WRITES refusal log line
runtime publish gate, view, object or flow write list-view-field-dotted and the error-tier positions of list-view-field-unknown; the warning-tier positions of list-view-field-unknown the 422 issue's message and the refusal log line; the 2xx advisories entry and the advisory log line
never at the runtime gate the dashboard ids (CLI only: a single published item cannot see the actions and pages a target resolves against), the three translation ids (a flow write's snapshot carries no translation bundles)
unchanged every hint; every other rule id

Every row is named in the changeset.

Tests

The rule suites import the rule source; the CLI, runtime-gate and example runs read the rebuilt @objectstack/lint dist. Every heavy run went through scripts/pm/os-verify-lock.sh; its VERDICT lines are quoted.

  • Each rule's own suite pins the new shape, as in slices 2–7. The five touched suites wrap their rule import and record every finding their direct calls fire; a final block in each holds every recorded verdict of its ids to one line of at most 200 characters, behind a coverage control (each id fired, and each arm: both dashboard target arms and a two-segment route; all three combinator shapes and all three node positions; the plain and dotted-head unknown arms, the projection door and the filter door's three refused head classes; every one of the 33 translation-target-unknown arms and 7 translation-option-key-unknown arms, by a fragment unique to the arm; the section verdict on a view and on a page's record:details). One case was added for the one arm no existing case fired (a result field under a dialog that declares no fields). Exact pins hold one verdict per arm; explanation pins hold, per id, that explainRule(id) exists and names what the verdict stopped saying, that each shared paragraph is one text under every id it serves, and the source-held facts in the table above. Every refusal assertion, rule id, severity, path, where and hint pin is unchanged; the pins that read the old text were moved to the new verdict (nine firstSentence(...) pins in the translation suite, can only match zero records → so no backend serves the path in the list-view suite) or to the explanation (walks \sections[].name`` in the section suite).
  • Lint suite at the head acffa89b16: pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: Test Files 136 passed (136), Tests 6,372 passed (6,372); VERDICT command-exit 0 (base 136 / 6,351; 21 new cases, no new file).
  • Lint build + typecheck: pnpm --filter @objectstack/lint build (VERDICT command-exit 0, check-dts-emitted 6/6) and pnpm --filter @objectstack/lint run typecheck at the head: VERDICT command-exit 0; check:test-typecheck OK, 2 files / 6 errors / 2 pinned signatures held. tsc -p packages/lint/tsconfig.test.json --listFiles lists all six touched test files.
  • CLI unit tier, against the rebuilt lint dist: pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2: Test Files 281 passed (281), Tests 4,176 passed (4,176), before and after; VERDICT command-exit 0 both. It includes test/explain-rule-id.test.ts (iterates every RULE_EXPLANATIONS key, so the 9 new ids resolve through os explain, explainPointer and the listing). The integration tier is declared to CI (no file in it asserts these ids' text, and the diff touches no spawn entry); its two files that name these ids ran as the census extra above: Test Files 2 passed (2), Tests 14 passed (14), before and after.
  • Runtime gate: the whole @objectstack/metadata-protocol suite, before and after: Test Files 224 passed | 3 skipped (227), Tests 28,116 passed | 19 skipped (28,135), both runs; VERDICT command-exit 0.
  • Tracker ids: no # plus digits in any printed message or explanation (rule-explanations.test.ts refuses one in every paragraph); the citations stay in // comments and test names.
  • Ablation (one-shot, from the committed state 77406985d2, through scripts/ablation-replace.mjs wrap mode under the verify lock, plus a shell trap restoring HEAD by absolute path). The rule suites import the rule source, so there is no dist leg. translation-section-name-missing's pre-slice message block was restored verbatim (the replacement read from the base file) through the anchor of its new block: anchor x1 → x0, blob 1da105c527b6 → f03c2f8bcf52. Predicted before the run: exactly two cases red — the 200-character bound pin and the id's exact-text pin; every other assertion on the id pins fragments the old text also carries (declares a label but no \name`, the _sectionskey). Observed:src/validate-translatable-sections.test.tsTest Files 1 failed (1), Tests 2 failed | 29 passed (31); the two red cases are exactly those two (the bound pin read 529 characters). Restored: blob1da105c527b6== blob at HEAD,git diff HEADempty,git status --porcelain` empty.
  • ESLint, narrowed: npx eslint --no-inline-config --format json over the 11 changed .ts files at acffa89b16 gave 11 files in the report, 0 errors, 0 warnings. The population is eslint.config.mjs's files: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}'] block, which all 11 are in (the changeset is not a linted kind). eslint.config.mjs never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules; the config's own note says so), so no untouched file's verdict can move. A control-character scan of every changed file found no match.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths, at the head acffa89b16 (merge base e86530088a), derived 62 commands. I ran 61 of them sequentially at that head, each with its own log and its exit code captured before any pipe: all 61 exited 0. One refused on the first pass rather than measured: check:dual-build-cjs-loads, PREREQUISITE NOT MET (six packages' dist/ absent in this worktree: studio, client-react, embedder-openai, knowledge-ragflow, organizations, service-cluster-redis); I built exactly those six under the lock (VERDICT command-exit 0) and it then exited 0. One is NOT MEASURED, as the dispatch directs: pnpm check:type-check-debt (--re-measure, which builds every package); its non-re-measuring half, check:type-check-coverage, ran and exited 0. --ran gave Run reconciliation — 62 derived, 61 run, 0 NOT-MEASURED, 1 UNRUN (that one). The four artifact-roster families rostered under a directory this diff touches (check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity) also ran at the head: all exited 0. The long ones on the shared box: check:query-options-erasure 258s, check:slot-lookup 128s, check-comment-mask-corpus 113s.

Remaining for later slices — 20 ids in packages/lint, by file

PR #22742's list minus this slice's five files and 9 ids. The lengths are slice 2's (PR #22448, census at 05c7c3fa3b), not re-measured here.

  • lint-flow-credential-literals.ts (1): flow-credential-literal 390
  • validate-action-name-refs.ts (1): action-name-undefined 409
  • validate-ai-surface-affinity.ts (1): ai-skill-surface-mismatch 281
  • validate-ai-tool-references.ts (1): ai-skill-tool-unresolved 441
  • validate-capability-references.ts (1): capability-reference-unknown 219
  • validate-flow-filter-tokens.ts (1): flow-filter-token-unknown 278
  • validate-managed-api-methods.ts (1): object/managed-api-method-unaffordable 412
  • validate-mapping-target-fields.ts (1): mapping-target-field-unknown 466
  • validate-nav-access.ts (1): nav-object-ungranted 353
  • validate-nav-object-servability.ts (1): nav-object-unservable 528
  • validate-nav-target-refs.ts (1): nav-target-unresolved 426
  • validate-object-field-refs.ts (1): object-field-ref-unknown 320
  • validate-object-references.ts (1): object-reference-unregistered-platform 324
  • validate-org-axis-red-lines.ts (1): org-axis-cross-org-bu-grant 365
  • validate-page-visualization-bindings.ts (1): page/visualization-without-binding 629
  • validate-retired-permission-residue.ts (1): permission-retired-lifecycle-residue 235
  • validate-seed-replay-safety.ts (1): seed-insert-mode-duplicates-on-replay 222
  • validate-seed-state-machine.ts (1): seed-value-outside-state-machine 320
  • validate-semantic-roles.ts (1): semantic-role-field-unprovisioned 291
  • validate-view-containers.ts (1): view-container-shape 290

The 26 ids slice 2 fenced, the 9 ids owned by packages/cli, and the action-governance.ts boot-log lines stay as PR #22448's body lists them, and react-prop-deprecated as PR #22700's notes it. This slice touched none of them.

Acceptance notes

  • Hints, for the card's open hint call: none of the 9 ids' hints points at text the verdicts cut. Each was read against the cut text: the empty-combinator hints, the $or-branch hint's "(A compiler that DROPPED the empty branch …)", the section hint's "the name above is a suggestion" (it means the name: the hint itself suggests), the tab hint's interfaceConfig.userFilters.tabs note and the list-view hints are each self-contained. Unchanged here, as every slice leaves hints.
  • Pending changeset text, measured in passing: the pending changesets of slices 5, 6 and 7 (.changeset/22161-lint-slice-5-one-line.md, -6-, -7-) name "the scaffold check os init and os generate run" as a door. Measured at the head: validateScaffold (packages/cli/src/utils/scaffold-validate.ts) has one caller, commands/init.ts, and commands/generate.ts calls no authoring rule; this slice's changeset names os init alone. Not edited here (outside this slice's file surface and defect class); reported to the card.
  • field-no-consumers (stage 1's id) still prints its 341-character verdict on os validate of examples/app-showcase (this census's control), as PR fix(lint): one-line verdicts for the approval-approver, data-model, agent-authoring, view-reference and chart-binding rules; os explain RULE_ID carries their reasoning #22742 recorded; untouched, a later slice's.
  • successMessage is the one toast key no case fires; it shares the errorMessage arm's template (FLOW_TERMINAL_MESSAGE_KEYS interpolated), which is fired and pinned.
  • The runtime-gate verdict of list-view-field-unknown is now the shared field-path sentence alone (columns[1] "field_10" is not a field on object "proj_task"., 59 characters, from 198): the consequence sentence every message of the id carried is the explanation's first paragraph, with the error / warning split per position that the old generic sentence did not draw.
  • A long name can still lengthen a line. The 200 bound holds on every variant the suites fire; the object, field, view, flow, screen, action, key and label names in a verdict are the author's.

Generated by Claude Code

…or, list-view-field, translation-reference and translatable-section rules

Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S3aAf11JjbW1mSGL1EhfFj
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/lint, touching 30 documentable anchor(s).

⛔ 1 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via validateDashboardActionRefs (symbol, a top-level function))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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 7098acaef9001961c53b95dd40ac29bfdbb6d9ae → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 7098acaef9001961c53b95dd40ac29bfdbb6d9ae

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 7098acaef9001961c53b95dd40ac29bfdbb6d9ae → pass the list as
args.docs, on the commit named under Which tree this was computed on.

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/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants