Skip to content

fix(lint): one-line verdicts for the widget, dataset, security-posture and visibility rules; os explain RULE_ID carries their reasoning - #22448

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

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

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Part of #22161
Clause-②: no

Stage 2 of the card, slice 2: 26 packages/lint rule ids, four whole source files. The card stays open for the later slices listed under "Remaining" below.

What changes

  • One verdict line per finding. Each finding of the 26 ids prints a message of one verdict sentence. Every finding the rules' own suites fire is now 197 characters or fewer (the longest of each id was 205 to 697 before). The values the author wrote still close the verdict: field paths, the candidate roster, the selection list, and the predicate excerpt, now capped at 72 characters. visibility-predicate-over-budget no longer echoes the predicate at all; its path locates it.
  • The reasoning moves to RULE_EXPLANATIONS (packages/lint/src/rule-explanations.ts), 26 new entries, so os explain RULE_ID prints it and the CLI's rule: line ends with the pointer for these ids. No CLI file changes: explainPointer() and os explain resolve any key the table holds (packages/cli/test/explain-rule-id.test.ts iterates every key; run below).
  • Nothing else moves. Rule ids, severities, path, hint (the fix: line) and what each rule accepts or refuses are unchanged. Every fired message of every other rule id is byte-identical, measured below.
  • One shared clause added: unprovisionedAnchorVerdict() in system-fields.ts is the one-clause form of unprovisionedAnchorCause(), for a rule whose finding is one sentence (here dashboard-filter-field-unprovisioned). The eight rule files still on the long clause converge on it when their slices shorten them.
  • .changeset/22161-lint-slice-2-one-line.md: @objectstack/lint patch, naming every door that prints the new text (below). Clause-②: no follows the contract review on slice 1 (its section ②): RULE_EXPLANATIONS entries are data in an existing export.

The shape, through the CLI's real printers against the rebuilt @objectstack/lint dist (a scratch stack, runAuthoringRules('build', …)):

  • dataset "deal_metrics" › dimension "region": dimension field "account.region" resolves, but its relationship prefix "account" is not declared in this dataset's `include`, so no join reaches the column
      fix: Add "account" to include (declaring "a.b" implicitly includes "a"), or bind this position to a field on "crm_deal" itself. Declared include paths: (none).
      rule: dataset-field-not-included  at datasets[0].dimensions[1].field — `os explain dataset-field-not-included` for why a relationship prefix must be in include
  ⚠ dashboard "sales_board" › widget "deals_by_stage": 'bar' widget selects no dimensions, so the renderer draws a single KPI number instead of a 'bar' chart
    fix: Plot the chart against a dataset dimension — dimensions: ['NAME'] (declared dimensions: stage, region) — or, if a single value IS what this tile should show, declare it as a 'metric' or 'kpi' widget so the type matches what renders. Suppress with: suppressWarnings: ['chart-dimensions-missing']
    rule: chart-dimensions-missing  at dashboards[0].widgets[0] — `os explain chart-dimensions-missing` for why a chart with no dimensions draws one number

(The fix: line prints the rule's unchanged hint; its placeholder is spelled NAME here only because this page's sanitizer eats angle-bracket tokens.)

Census (taken first, before any edit)

Method: a scratch preload (NODE_OPTIONS=--import, never committed) recorded every finding-shaped object (rule + message) pushed into an array or produced by map/flatMap, deduped by (rule, message), with its push site, in every vitest worker and every process a test spawns.

  • packages/lint suite at 05c7c3fa3b (the base): 128 files, 5,894 tests passed; 241 rule ids fired, 147 over 200 characters.
  • packages/cli suite, unit and integration, against the base's lint dist: 372 files, 4,961 passed / 2 skipped. It fires 75 ids. Rows from os lint's own printer (commands/lint.ts), whose message is the printed where: message line, are excluded. It adds one over-200 lint id the lint suite never fires that long (relationship/delete-behavior 201), longer variants of five others (marked below), and nine over-200 ids owned by packages/cli.
  • Not measured: the metadata-protocol, spec and example suites (the functional-completeness ids were measured over the spec suite by slice 1). The census counts what some test fires; a variant no test fires is not in it, and chart-config-missing, react-prop-deprecated, relationship/association-inline-edit and rollup/missing-summary fired in neither suite.
  • Lengths are message alone; the printed line adds where and : .

Author-time ids over about 200 characters, after slice 1: 146 in packages/lint (26 this slice, 26 in fenced files, 94 later), plus 9 in packages/cli and the action-governance boot-log lines in packages/objectql/src/action-governance.ts (named by stage 1, its own later slice). lint-startup-registry-verdict.ts's two ids (779, 731) belong to the repo gate check:startup-registry-verdict, not to an author, and are not counted.

This slice — 26 ids, before → after (every distinct message each id fires in the packages/lint suite)

rule id file severity before: messages · longest after: messages · shortest–longest
dashboard-filter-field-unprovisioned validate-widget-bindings.ts warning 3 · 509 3 · 163–192
chart-field-unknown validate-widget-bindings.ts warning 5 · 407 5 · 134–185
dashboard-filter-field-not-included validate-widget-bindings.ts error 1 · 370 1 · 197–197
widget-filter-field-unknown validate-widget-bindings.ts error 5 · 364 5 · 62–156
dashboard-filter-field-unknown validate-widget-bindings.ts error 11 · 333 11 · 119–189
chart-dimensions-missing validate-widget-bindings.ts warning 13 · 306 13 · 102–124
widget-filter-field-not-included validate-widget-bindings.ts error 1 · 282 1 · 169–169
widget-measures-missing validate-widget-bindings.ts warning 7 · 255 7 · 175–188
widget-sortby-unselected validate-widget-bindings.ts error 2 · 242 2 · 177–191
chart-measures-missing validate-widget-bindings.ts warning 13 · 224 13 · 170–181
widget-legacy-analytics-unrenderable validate-widget-bindings.ts error 3 · 205 3 · 148–176
security-controlled-by-parent-ambiguous-relation validate-security-posture.ts error 8 · 697 6 · 159–194
security-fls-unknown-field validate-security-posture.ts error 7 · 593 7 · 173–197
security-controlled-by-parent-no-relation validate-security-posture.ts error 7 · 526 2 · 165–165
security-master-detail-ungranted validate-security-posture.ts warning 10 · 449 10 · 134–178
security-owd-alias validate-security-posture.ts error 9 · 354 9 · 61–186
security-delegation-missing-reason validate-security-posture.ts error 3 · 210 3 · 100–100
visibility-predicate-unknown-function validate-visibility-predicates.ts error 9 · 655 9 · 156–190
visibility-predicate-over-budget validate-visibility-predicates.ts error 6 · 507 4 · 156–163
visibility-bare-identifier validate-visibility-predicates.ts error 23 · 411 23 · 135–169
visibility-predicate-syntax validate-visibility-predicates.ts error 16 · 341 16 · 140–175
visibility-root-mislayered validate-visibility-predicates.ts warning 4 · 304 4 · 167–169
dataset-field-not-included validate-dataset-references.ts error 1 · 446 1 · 152–152
dataset-field-unknown validate-dataset-references.ts error 13 · 296 13 · 59–100
dataset-filter-field-unknown validate-dataset-references.ts error 5 · 294 5 · 55–98
dataset-include-unknown validate-dataset-references.ts error 6 · 260 6 · 77–152

The message counts fall for three ids because the old messages repeated the object's name (the two controlled_by_parent ids) or echoed the predicate (visibility-predicate-over-budget), which the finding's where and path already carry.

Fenced off by the claim — 26 ids (open PRs and the serial flow-template queue)

  • lint-flow-patterns.ts (15): flow-multi-write-unfiltered 656, flow-decision-mode-invalid 528, flow-loop-body-uncontained 522, flow-try-catch-without-catch 520, flow-approval-revise-target-not-service-owned 366, flow-decision-unconditional-branch 342, flow-error-label-not-fault 315, flow-inert-node-condition 286, flow-runas-unscoped 284, flow-branch-label-unmatched 272, flow-decision-inclusive-overlap 250, flow-default-edge-with-condition 239, flow-multiple-default-edges 211, flow-time-relative-antipattern 208, flow-date-equality-filter 208
  • validate-flow-template-paths.ts (3): flow-template-field-unprovisioned 440, flow-template-lookup-traversal 349, flow-template-unknown-field 258
  • validate-component-props.ts (2): component-props-invalid 994, component-props-unknown-key 844
  • validate-page-field-bindings.ts (2): page-field-unprovisioned 484, page-section-group-unknown 214
  • validate-expressions.ts (1): expression-invalid 2077
  • validate-filter-tokens.ts (1): filter-token-unknown 386
  • validate-form-layout.ts (1): form-section-group-unknown 214
  • validate-print-page-blocks.ts (1): print-page-block-unprintable 368

Remaining for later slices — 94 ids in packages/lint, by file (longest fired message; "cli" marks a length only the cli suite reached)

  • data-model-rules.ts (6): unique/legacy-organization-composite 480 cli, relationship/master-detail-required 462, unique/unscoped-declared-index 427, unique/double-declaration 402 cli, rollup/non-numeric-aggregand 364, relationship/delete-behavior 201 cli
  • validate-flow-trigger-readiness.ts (5): flow-time-relative-descriptor-invalid 796, flow-time-relative-descriptor-unroutable 532, flow-trigger-unroutable 518, flow-api-trigger-secret-missing 336, flow-trigger-unknown-event 222
  • validate-react-page-props.ts (5): react-chart-drilldown-invalid 784, react-chart-aggregate-invalid 507, react-chart-field-unprovisioned 429, react-block-needs-record-context 267, react-page-source-unparseable 211
  • validate-rls-predicate-enforceability.ts (5): rls-predicate-unparseable 2056, rls-predicate-unenforceable 1679, rls-predicate-unknown-user-variable 1544, rls-predicate-unknown-field 1399, rls-predicate-over-budget 1259
  • validate-action-body-writes.ts (4): action-body-write-unprovisioned-anchor 778, action-body-write-unknown-field 433, action-record-write-discarded 293, action-body-source-unparseable 212
  • validate-sharing-rule-enforceability.ts (4): sharing-rule-unlowerable-condition 1093, sharing-rule-object-not-shareable 774, sharing-rule-object-controlled-by-parent 660, sharing-rule-runtime-variable-condition 492
  • validate-ai-agent-authoring.ts (3): default-agent-legacy-alias 466, default-agent-outside-roster 388, agent-authoring-withdrawn 352
  • validate-hook-body-writes.ts (3): hook-body-write-unprovisioned-anchor 792, hook-body-write-unknown-field 527 cli, hook-body-source-unparseable 212
  • validate-predicate-path-refs.ts (3): predicate-rhs-path-shaped 648, predicate-path-unrooted 434, predicate-path-unresolved 371
  • validate-searchable-fields.ts (3): searchable-field-unprovisioned 512, searchable-field-unsearchable 449, searchable-field-unknown 295
  • validate-sortable-fields.ts (3): sort-field-unprovisioned 844, sort-field-unsortable 369, sort-field-unknown 287
  • lint-view-refs.ts (2): view-ref-nav-view-missing 437, view-key-collision 278 cli
  • validate-approval-approvers.ts (2): approval-approvers-may-resolve-empty 451, approval-approver-not-membership-tier 272
  • validate-chart-bindings.ts (2): chart-measure-unknown 442, chart-axis-not-selected 327
  • validate-dashboard-action-refs.ts (2): dashboard-action-route-unresolved 242, dashboard-action-target-undefined 239
  • validate-dataset-measure-aggregates.ts (2): measure-aggregate-field-type-refused 809, dimension-json-stored-field-refused 531
  • validate-empty-combinators.ts (2): filter-empty-combinator 352, filter-empty-node 226
  • validate-flow-node-writes.ts (2): flow-node-write-unprovisioned-anchor 739, flow-node-write-unknown-field 421
  • validate-list-view-field-refs.ts (2): list-view-field-dotted 445, list-view-field-unknown 355
  • validate-readonly-flow-writes.ts (2): flow-update-readonly-field 410, flow-update-readonly-when-field 317
  • validate-readonly-hook-writes.ts (2): hook-api-update-readonly-field 464, hook-api-update-readonly-when-field 323 cli
  • validate-rule-compilability.ts (2): validation-rule-json-schema-uncompilable 517, validation-rule-regex-uncompilable 482
  • validate-translation-references.ts (2): translation-target-unknown 345, translation-option-key-unknown 230
  • lint-flow-credential-literals.ts (1): flow-credential-literal 390
  • validate-action-dispatch-contract.ts (1): action-dispatch-contract-mismatch 927
  • 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-component-types.ts (1): component-type-unknown 807
  • 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-preset-comparands.ts (1): filter-preset-comparand 669
  • validate-readonly-action-writes.ts (1): action-api-update-readonly-when-field 396
  • validate-retired-permission-residue.ts (1): permission-retired-lifecycle-residue 235
  • validate-rule-schema-formats.ts (1): validation-rule-json-schema-unknown-format 1049
  • 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-translatable-sections.ts (1): translation-section-name-missing 553
  • validate-view-containers.ts (1): view-container-shape 290

Outside packages/lint — 9 ids owned by packages/cli (fired by the cli suite)

  • packages/cli/src/lint/hook-body-lowering.ts (4): hook-body/not-lowerable 661, hook-body/unparseable 469, hook-body/bundled-fallback 334, hook-body/extraction-failed 331
  • packages/cli/src/utils/collect-docs.ts (4): docs/uncollected-directory 558, docs/duplicate-name 330, docs/nav-target 299, docs/frontmatter-tags 285
  • packages/cli/src/utils/picklist-references.ts (1): picklist-reference-unverified 245

Picking the slice

Whole files, fenced files excluded, most over-200 ids first, at most 30:

file over-200 ids taken?
validate-widget-bindings.ts 11 yes
validate-security-posture.ts 6 yes
data-model-rules.ts 6 (5 in the lint suite; the cli suite fires relationship/delete-behavior at 201) no: relationship/master-detail-required and rollup/non-numeric-aggregand have no exported rule id constant, and a RULE_EXPLANATIONS key must be one (rule-explanations.test.ts). Exporting two new constants adds public surface, which is not Clause-②: no.
validate-visibility-predicates.ts 5 yes
validate-flow-trigger-readiness.ts 5 no: flow-time-relative-descriptor-invalid (796) ends with the time-relative trigger schema's own refusal prose, which lives in packages/spec, so it needs a spec edit
validate-react-page-props.ts 5 no: react-chart-drilldown-invalid (784) and react-chart-aggregate-invalid (507) are the chart schemas' own refusal prose (packages/spec/src/ui/chart.zod.ts)
validate-rls-predicate-enforceability.ts 5 no: each verdict quotes the whole predicate and composes a per-clause consequence from builders the five ids share (1,259 to 2,056 characters). That security-critical rework is a slice of its own.
validate-dataset-references.ts 4 yes: the dataset half of the widget family (the same object-graph account and the same "compiled into the analytics query" consequence)
validate-action-body-writes.ts, validate-sharing-rule-enforceability.ts 4 each no: the first shares its unprovisioned-anchor and unknown-field wording with validate-hook-body-writes.ts and validate-flow-node-writes.ts, a nine-id family to shorten together. The second shares the RLS consequence prose.

So the slice is 11 + 6 + 5 + 4 = 26 ids.

Doors that print the new text

Read from the registry entries (authoring-rules.ts, reference-integrity-suite.ts) and the runtime gate (metadata-protocol/src/runtime-authoring-gate.ts, lint/src/runtime-gate.ts):

door ids what changes
os validate, os build (and os compile, which os dev runs per compile), os lint, os verify, os init scaffold check all 26 (every entry is commands: ALL) the text-face verdict line; the rule: line gains the os explain pointer; os validate --json warnings and os build --json author-time issues carry the new message
os doctor dashboard widget check the 11 widget ids prints where: message, no rule: line (see Acceptance notes)
runtime publish gate, dashboard writes (Studio, REST /meta, MCP) the 11 widget ids errors: the 422 issue message and the OS_ALLOW_UNLINTED_METADATA_WRITES refusal log line. Warnings (dashboard-filter-field-unprovisioned, chart-field-unknown, chart-measures-missing, widget-measures-missing, chart-dimensions-missing): the 2xx advisories message and the deduped [Protocol] authoring advisory log line
same gate, dataset writes the 4 dataset ids (all errors) the 422 issue message, the hatch log line
same gate, view writes the 5 visibility ids four errors (the 422, the hatch log); visibility-root-mislayered is a warning (the 2xx advisories, the advisory log)
same gate, object / permission / seed writes security-controlled-by-parent-* (object), security-fls-unknown-field (permission), security-master-detail-ungranted (object or permission, a warning), security-delegation-missing-reason (seed) as above, by severity
never at the runtime gate security-owd-alias the object schema's closed OWD enums refuse those values at parse, with their own message, before the gate runs
unchanged every hint; every other rule id

Every row is named in the changeset.

Tests (round 0, head 7564f71b04)

  • Each rule's own suite pins the new shape. The four test files record every finding their cases fire for the shortened ids, through a wrapper around the imported rule. A final case then holds each recorded verdict to one line of at most 200 characters, behind a coverage control that each shortened id fired at least once. So the pin covers every firing variant the suite exercises, not a chosen few, which is the gap slice 1's landing recorded. A second block pins, per id, that explainRule(id) exists and still names what the verdict stopped saying. Existing message pins now read the verdict's substance, the path, names and outcome, never the full prose; every refusal assertion is unchanged.
  • pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2 → Test Files 128 passed (128), Tests 5,928 passed; lock VERDICT command-exit 0.
  • pnpm --filter @objectstack/lint run typecheck → VERDICT command-exit 0. check:test-typecheck OK: 2 files, 6 errors and 2 pinned signatures held.
  • cli, after pnpm --filter @objectstack/lint build: pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 test/explain-rule-id.test.ts test/commands.test.ts → 2 files, 62 passed. explain-rule-id.test.ts iterates every RULE_EXPLANATIONS key, so all 26 new ids resolve through os explain and through explainPointer. The two sets stay disjoint from the schema names. The rebuilt dist carries the new entries (marker grep: 1 hit each in dist/rule-explanations.js and .cjs).
  • Sibling suites that fire these ids, run although the public surface is byte-identical:
    • metadata-protocol, three runtime-gate files (runtime-authoring-gate.dataset-writes, protocol-publish-drafts-closure, protocol.dashboard-dataset-publish-gate) → 3 files, 21 passed;
    • examples/app-showcase, three files (nav-and-detail-grants, dashboard-filter-vocabulary, my-work-visibility) → 3 files, 14 passed, after building the showcase's dependency closure.
  • Ablations, one-shot and on committed state through scripts/ablation-replace.mjs (wrap mode, trap-armed). The tests import the rule source directly, so no dist leg applies.
    • (a) chart-dimensions-missing's verdict lengthened to 240 characters (blob c07dd85c → 449306c6) → validate-widget-bindings.test.ts 1 failed | 161 passed, the one-line case.
    • (b) BY NAME removed from the chart-field-unknown explanation (blob da3574bc → 7288a22f) → 1 failed | 161 passed, that id's explanation case.
    • Both restored: blob == HEAD, git diff HEAD empty, git status --porcelain empty.
  • Nothing else moved: the census preload re-run over the whole lint suite at the head shows every fired message of every id outside the 26 byte-identical to the base.
  • ESLint, narrowed: npx eslint --no-inline-config --format json over the 11 changed .ts files → 11 files in the report, 0 errors, 0 warnings, none ignored. eslint.config.mjs never enables type-aware linting (its comment at :327), so no untouched file's verdict can move.
  • Gates: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 62 commands from the 11 changed paths (merge base 05c7c3fa3). All 62 ran sequentially, each with its own log and an exit code captured before any pipe; 61 exited 0 on the first run. pnpm check:dual-build-cjs-loads exited 3, PREREQUISITE NOT MET, because eight packages outside this tree's builds had no dist/; after building them it exited 0. --ran → ✓ dispatch-gates --ran: 62 derived famil(ies) accounted for — 62 run, 0 NOT-MEASURED. A control-character scan of every changed file: no match.

Patch round 1 (head 6258d37185)

Acceptance notes

  • os doctor prints the widget ids without a rule: line. Its dashboard check prints where: message, with the hint only under --verbose. It never named the rule id, and the os explain pointer stage 1 put on the other printers never reached it, so a doctor reader now sees the short verdict and no pointer. os validate on the same project prints the rule: line with the pointer. That is a domain:cli printer change, outside this slice's files, and is recorded rather than built.
  • The rule: line with its pointer runs up to about 190 characters for the longer ids (security-controlled-by-parent-ambiguous-relation plus its covers). This is stage 1's printer shape, carried as before.
  • Author values extend a verdict. The fixed text of every verdict is short; the field path, the candidate roster, the selection list and the predicate excerpt (capped at 72) are what an author needs, so they stay, and a long one lengthens the line. The changeset says so.
  • unprovisionedAnchorCause() keeps its other callers. Only dashboard-filter-field-unprovisioned moves to the new one-clause unprovisionedAnchorVerdict(); the eight other rule files converge when their slices shorten them, which the helper's docblock states.

Generated by Claude Code

claude added 5 commits October 9, 2026 07:55
…d visibility rules

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…verdict shape and explanations

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…dicts in one line

Claude-Session: https://claude.ai/code/session_01VZqqwTj2wsihZEbfT6yyYN
Co-authored-by: Claude <noreply@anthropic.com>
…oor that prints them

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

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/error-catalog.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/data-modeling/objects.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture), read_write (literal, a string literal in owdAliasProvenance))
  • content/docs/getting-started/common-patterns.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/kernel/runtime-services/sharing-service.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/permissions/authorization.mdx (via validateSecurityPosture (symbol, a top-level function), controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/permissions/index.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/permissions/permissions-matrix.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture), read_write (literal, a string literal in owdAliasProvenance))
  • content/docs/permissions/rls.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/permissions/sharing-rules.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture), read_write (literal, a string literal in owdAliasProvenance))
  • content/docs/permissions/system-context.mdx (via publicSharing (literal, a string literal in owdAliasProvenance))
  • content/docs/protocol/kernel/error-handling.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/protocol/objectql/security.mdx (via allowedAudiences (literal, a string literal in owdAliasProvenance), controlled_by_parent (literal, a string literal in validateSecurityPosture), publicSharing (literal, a string literal in owdAliasProvenance), read_write (literal, a string literal in owdAliasProvenance))

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

  • content/docs/releases/implementation-status.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture), read_write (literal, a string literal in owdAliasProvenance))
  • content/docs/releases/v13.mdx (via validateSecurityPosture (symbol, a top-level function), read_write (literal, a string literal in owdAliasProvenance))
  • content/docs/releases/v15.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/releases/v16.mdx (via validateWidgetBindings (symbol, a top-level function))
  • content/docs/releases/v17/17-1.mdx (via validateWidgetBindings (symbol, a top-level function), controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/releases/v17/17-2.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))
  • content/docs/releases/v17/17-3.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture), publicSharing (literal, a string literal in owdAliasProvenance))
  • content/docs/releases/v17/17-4.mdx (via publicSharing (literal, a string literal in owdAliasProvenance))
  • content/docs/releases/v17/17-5.mdx (via publicSharing (literal, a string literal in owdAliasProvenance))
  • content/docs/releases/v17/index.mdx (via controlled_by_parent (literal, a string literal in validateSecurityPosture))

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 2b61f2d9d6f8f7cc7f6b9f5f9772b8bc8fb0ea5d → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 2b61f2d9d6f8f7cc7f6b9f5f9772b8bc8fb0ea5d

⚠️ 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 2b61f2d9d6f8f7cc7f6b9f5f9772b8bc8fb0ea5d → 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