Skip to content

fix(lint): one-line verdicts for the flow-credential, AI skill, flow-filter-token, managed-API, org-axis, retired-permission, seed and semantic-role rules, and a bounded field-no-consumers clause; os explain RULE_ID carries their reasoning - #22828

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

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #22161
Clause-②: no

Stage 2 of the card, slice 10: the last 10 packages/lint rule ids from PR #22799's "Remaining for later slices" list, one whole source file each, plus stage 1's field-no-consumers (the card's own subject), whose "a consumer of the same name on …" clause listed every object sharing the field name. The card stays open: the packages/cli ids, the fenced ids and the hint call remain (below).

What changes

  • One verdict line per finding. Each finding of the 10 ids prints a message of one verdict sentence, followed by Did you mean "…"? where the rule already offered the nearest name. Every finding the rules' own suites fire is now 194 characters or fewer; the longest of each id was 222 to 441 before. The runtime publish gate's suite (the whole @objectstack/metadata-protocol suite) fires two of the 10, at 181 or fewer (390 and 235 before). The packages/cli unit tier and os validate on the four example apps fire none of the 10.
  • field-no-consumers names the first other object and counts the rest. a consumer of the same name on "showcase_account" (and 9 more) does not count, where it listed all ten. On os validate of examples/app-showcase its longest finding drops from 341 to 199 characters (that 341-character finding now prints 143). Its inert arm is byte-identical, so the CLI pins and stage 1's pending changeset that quote it are untouched. Its explanation is unchanged: the verdict cut names, not reasoning, and the explanation already says "Verdicts are per object".
  • Where a verdict keeps a name, it is the one the author needs. A verdict no longer repeats what where already names: the skill (ai-skill-tool-unresolved now opens on the tool), the managed object, the sharing rule's object and the semantic-role object. seed-value-outside-state-machine names the first three declared states, then (and N more).
  • flow-credential-literal keeps a short route in the verdict (move it to a connector's auth.credentialRef, or drop it for a connector input). Measured at the base: os validate and os build print the hint as their fix: line (authoringFindingDetailLines), but os lint's text face prints it only under --fix (commands/lint.ts, flags.fix && issue.fix). The module comment said both text faces "leave the hint to --json", which is no longer true for os validate; it now says what was measured. The route by auth variant (bearer, a header api-key, api-key with paramName) stays the hint's, unchanged.
  • semantic-role-field-unprovisioned keeps ADR-0015 and the word "storage". validate-expressions.test.ts (the cross-site convergence pin) holds this finding and the expression site's unprovisioned-anchor warning to the same object, column and reason. The first cut dropped both and that pin went red on the whole-suite run; the verdict was fixed, not the pin, which is outside this slice's files.
  • The reasoning moves to RULE_EXPLANATIONS (packages/lint/src/rule-explanations.ts), 10 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 org-axis-cross-org-bu-grant prints the entry. The 121 entries already in the table are byte-equal (the diff of rule-explanations.ts is additions only, +266 / -0).
  • 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 eleven rule files is a message expression, a comment, a verdict helper, or the removal of a value that fed only the old message. The plumbing hunks, each message-only:
    • lint-flow-credential-literals.ts: SERVED becomes one clause; STEER_HEADER and STEER_QUERY become one STEER_CONNECTOR (both arms now give the same short route; the hint keeps routing by shape); STEER_INPUT is shortened; the query arm's position reads url query parameter.
    • validate-managed-api-methods.ts: the import of describeManagedApiMethodConflicts (@objectstack/spec/data) goes; it fed only the message. That spec sentence alone measured about 190 characters at three verbs, so no verdict built on it fits; see Acceptance notes for what that leaves in packages/spec.
    • validate-seed-state-machine.ts: atMostThree() (new, module-private) bounds the known-state list.
    • validate-field-consumers.ts: the sharedClause expression names sharedWith[0] and counts the rest.
    • No helper changed shape (string in, string out); no truth table is involved.
  • .changeset/22161-lint-slice-10-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.
  • The declared rider went unused: the packages/cli and packages/metadata-protocol tests that assert these ids' text pin fragments the new verdicts keep (auth.credentialRef and every member who can read flows for flow-credential-literal; the unchanged inert arm of field-no-consumers), and they ran green unedited (below).

The verdict forms, the longest of each arm the rule suites fired (the last line is os validate on the showcase app):

connector input 'headers.Authorization' holds a credential-shaped literal, readable by every member who can read flows; drop it: the connector authenticates through `auth.credentialRef`
url query parameter 'api_key' holds a credential-shaped literal, readable by every member who can read flows; move it to a connector's `auth.credentialRef`
Agent "helper" (surface 'ask') references skill "metadata_authoring" (surface 'build'), so the runtime refuses the binding and chatting with this agent fails at request time
Tool family "crm_*" matches no tool this stack can resolve, so the subscription contributes zero tools at runtime
Tool "action_send_invoice" names action "send_invoice", which does not become an AI tool (it needs `ai.exposed` and a headless path), so the runtime drops the reference
Tool "knowledge_base" resolves to no stack, platform or materialised action tool, so the runtime drops it and the agent cannot call it. Did you mean "search_knowledge_base"?
Filter value "{DATEADD(day, -45)}" calls 'DATEADD', which neither the flow template dialect nor the filter placeholders define, so the evaluator raises a guard refusal and this node cannot run
`enable.apiMethods` advertises [create, update, delete], which `managedBy: 'better-auth'` affordances refuse (needs userActions create/edit/delete), so the registry strips them
Recipient `unit_and_subordinates` (a business unit AND every descendant unit) on an object that opted out of tenancy spans EVERY organization: a cross-org business-unit grant ADR-0105 D6 forbids
sets `allowRestore: false`, the retired default of a key removed in @objectstack/spec 17, which the parse strips in silence, so this line grants nothing
`mode: 'insert'` re-inserts every record on each replay boot (dev-server restart, package re-publish) with no existing-row check, so every restart duplicates its rows
seeds 'status=complete', which the 'proj' state machine does not declare (known states: active, cancelled, completed (and 2 more)), and seed writes skip that rule, so a typo lands silently
highlightFields points at "organization_id", an injected system column with no storage on this external object (ADR-0015), so every consumer renders it empty on every record
declared, but nothing in this stack displays or reads it (carrier-only: 4 site(s) name it without reading it; a consumer of the same name on "showcase_project_membership" (and 1 more) does not count)

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

shared text ids where it is written once held to its source by
who can read a flow's literal (readable by every member who can read flows) flow-credential-literal, all three positions SERVED in the rule file (verdict); the explanation's first paragraph (reasoning) the rule's test, every position fired

The dispatch expected shared paragraphs across ids, as slices 3 to 9 found. Measured: none of the 10 explanations shares a paragraph with another. The two AI rules judge different references (an agent's skills, a skill's tools) against different universes; the two seed rules answer different questions (replay idempotence, the state-machine exemption); org-axis-cross-org-bu-grant's sibling org-axis-permission-inheritance is not converted (174 at its longest) and has no entry to share with. So the table holds the one in-file shared clause.

The explanation module imports nothing, so each fact it writes out is held to its source by the rule's own test: the credential positions and the three region kinds by firing each; the surface defaults, the both arm and the skipped unresolved skill by the existing cases; the three tool sources, the wildcard and the four headless paths by each one silencing the rule; the flow template functions to FLOW_TEMPLATE_DATE_FUNCTIONS and FLOW_TEMPLATE_VALUE_FUNCTIONS, and the placeholders by each one staying silent; the verb table to MANAGED_WRITE_VERB_AFFORDANCE, verb by verb through the rule; the business-unit recipients to the ShareRecipientType partition the suite pins, each fired; the retired keys and their residue value to the spec's capture; the declared-state union by each of its three sources silencing the rule; and the external-object provenance by the existing cases.

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

Method: slice 4 to 9's scratch preload (NODE_OPTIONS=--import, never committed), which patches Array.prototype.push to record every finding-shaped object (rule + message), deduped by (rule, message) per process, with the push site for the 11 ids and the other ids in their files. Lengths are message alone; the printed line adds where and : . Rows from os lint's own printer (commands/lint.ts:761, which pushes where: message) are named as such. Positive control in every run: field-no-consumers, plus sort-field-unknown and searchable-field-unknown in the runtime-gate suite, recorded in every run.

  • packages/lint suite, base 137 files / 6,416 tests, after the CLI, metadata-protocol and example closure build at the base: all 10 ids fire, every one over 200 at its longest; each longest equals PR fix(lint): one-line verdicts for the nav, object-reference, capability, action-name, mapping-target, view-container and interface-page rules; os explain RULE_ID carries their reasoning #22799's list. field-no-consumers: 14 messages, longest 167.
  • packages/cli unit tier, the whole project (281 files, 4,174 tests): fires none of the 10, before and after; field-no-consumers fires 7 messages, the longest 142 as os lint's printer row, all on the unchanged inert arm.
  • Runtime publish gate: the whole @objectstack/metadata-protocol suite (228 files, 225 run, 28,129 passed), which reads @objectstack/lint from its built dist/, before on the base-built dist and after on the rebuilt one: fires flow-credential-literal (3 messages, runtime-authoring-gate.flow-credential-literal.test.ts) and permission-retired-lifecycle-residue (2); none of the other 9.
  • 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 10 fire; field-no-consumers fires on all four, and every app prints the same number of rule: lines before and after (9 / 7 / 70 / 3).
  • Extra, outside the dispatch's list: the four packages/cli e2e files that name these ids (flow-credential-literal-doors.e2e.test.ts, rule-line-explain-pointer.e2e.test.ts, starter-field-consumers.e2e.test.ts, generate-scaffold-gates.e2e.test.ts; in no per-PR vitest project, run here under OS_TEST_TIERS=nightly) and the one integration file (union-fold-command-parity.test.ts).
  • After: the lint dist/ rebuilt from the slice at dfec085942 (marker holds a credential-shaped literal 1 hit each in dist/index.js, index.cjs, runtime.js, runtime.cjs; the old holds a literal value that reads as a credential 0 in each; why business-unit trees stay inside one organization 1 in dist/rule-explanations.js and .cjs). Every after-run except the final lint suite read that dist; the commits after it add the changeset and the origin/main merge, which touches no packages/lint file.
rule id severity lint suite: before (msgs · range) → after runtime-gate suite examples os validate cli e2e (4 files)
flow-credential-literal warning 10 · 282–390 → 10 · 144–185 3 · 294–390 → 3 · 148–181 — 3 · 294–390 → 3 · 148–181 (printer rows 346–430 → 188–233)
ai-skill-surface-mismatch error 6 · 259–281 → 6 · 151–173 — — —
ai-skill-tool-unresolved warning 24 · 238–441 → 23 · 113–173 — — —
flow-filter-token-unknown error 8 · 267–278 → 8 · 181–192 — — —
object/managed-api-method-unaffordable error 3 · 392–412 → 7 · 146–176 — — —
org-axis-cross-org-bu-grant error 2 · 331–365 → 2 · 160–194 — — —
permission-retired-lifecycle-residue warning 2 · 233–235 → 2 · 150–152 2 · 233–235 → 2 · 150–152 — —
seed-insert-mode-duplicates-on-replay warning 1 · 222 → 1 · 166 — — —
seed-value-outside-state-machine warning 3 · 288–320 → 4 · 161–188 — — —
semantic-role-field-unprovisioned warning 4 · 285–291 → 3 · 163–173 — — —
field-no-consumers warning 14 · 64–167 → 19 · 64–178 — crm 4 · 202 → 4 · 184; todo 2 · 109 → 2 · 109; showcase 21 · 341 → 19 · 199; multi-package 1 · 64 → 1 · 64 4 · 64–108 → 4 · 64–108

The cli unit tier column is not drawn: it fires field-no-consumers' unchanged inert arm alone (7 · 64–142), before and after. The cli integration file fires field-no-consumers only (2 · 64–140, before and after). org-axis-permission-inheritance (not converted): lint suite 4 · 167–174 before and after. A message count that falls after (ai-skill-tool-unresolved, semantic-role-field-unprovisioned, showcase's field-no-consumers) is two findings whose old texts differed only in a name the verdict no longer repeats or a list it now cuts; a count that rises is a case this slice added. Every push site of the 11 ids fires before and after (one each, semantic-role-field-unprovisioned's helper reached from its two roles).

Doors that print the new text

Read from the registry (authoring-rules.ts): lintFlowCredentialLiterals and validateFlowFilterTokens are CLI and runtime on ['flow'], validateManagedApiMethods on ['object'], validateRetiredPermissionResidue on ['permission']; validateOrgAxisRedLines, validateFieldConsumers, validateSemanticRoles and the two seed rules are CLI only; the two AI rules are members of the reference-integrity suite on its default ['flow']. The CLI's callers: commands/validate.ts, compile.ts (os build is its alias), lint.ts, verify.ts through judgeAuthorTimeRules on the validate set, and init.ts through validateScaffold on the build set (one caller, commands/init.ts:1314; commands/generate.ts calls no authoring rule). Measured at the runtime door with runRuntimeAuthoringRules on the rebuilt dist (scratch probe): a flow write answers flow-filter-token-unknown in errors and flow-credential-literal in advisories; an object write answers object/managed-api-method-unaffordable in errors; a permission write answers permission-retired-lifecycle-residue in advisories; an agent, skill or sharing_rule write runs no rule.

door ids what changes
os validate, os build (and os compile, which os dev runs at startup and, under --verbose, per watch recompile), os lint (where: message), the scaffold check os init runs all 11 the text-face verdict line; the rule: line gains the os explain pointer for the 10; os validate --json errors and os build --json author-time issues carry the new message
os verify the four errors: ai-skill-surface-mismatch, flow-filter-token-unknown, object/managed-api-method-unaffordable, org-axis-cross-org-bu-grant its refusal list; it counts advisories without printing them
runtime publish gate, flow write flow-filter-token-unknown (error), flow-credential-literal (warning) the 422 issue's message and the OS_ALLOW_UNLINTED_METADATA_WRITES refusal log line; the 2xx advisories entry and the [Protocol] authoring advisory log line
runtime publish gate, object write object/managed-api-method-unaffordable (error) the 422 issue's message and the refusal log line
runtime publish gate, permission write permission-retired-lifecycle-residue (warning) the 2xx advisories entry and the advisory log line
never at the runtime gate org-axis-cross-org-bu-grant, both seed ids, semantic-role-field-unprovisioned, field-no-consumers (CLI-only rules); both AI ids (run there only for a flow write, whose snapshot carries no agents or skills)
unchanged every hint; every other rule id

Every row is named in the changeset.

field-no-consumers across doors (the dispatch's item 6, measured on examples/app-showcase)

  • os validate: 51 findings, each printed once (verdict, fix:, rule: with the pointer); the longest printed verdict line 391 → 253 characters.
  • os build: 37 of them, each once, inside the 50-entry advisory cap (… and 20 more author-time warning(s) not shown (50 of 70)); longest printed verdict line 257 → 253.
  • os dev --compile --fresh: the startup compile (stdio: 'inherit') prints the same 37, once; a watch recompile prints its output only under --verbose, else only stderr on failure (commands/dev.ts, startWatchRecompile). Nothing is printed again at serve.
  • No second paragraph remains. Give … a consumer and Roots scanned appear 0 times in the os validate, os build and os dev output, before and after: stage 1 moved that text into the explanation. No packages/cli printer was edited.

Tests

The rule suites import the rule source; the CLI, runtime-gate, example and e2e 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 to 9. The eleven touched suites wrap their rule import and record every finding their direct calls fire; the LAST case in each file holds every recorded verdict of its id to one line of at most 200 characters, behind a coverage control (each arm: all three credential positions; both surface directions; all three tool arms; one and several refused verbs; both business-unit recipients; both retired keys; a cut and an uncut state list; both semantic roles; and field-no-consumers' inert, carrier-only, one-name and counted arms). Exact pins hold one verdict per arm; explanation pins hold, per id, that explainRule(id) exists and names what the verdict stopped saying, and the source-held facts above. Every refusal assertion, rule id, severity, path, where and hint pin is unchanged. The pins that read the old text moved to the new verdict: the credential route fragments to move it to a connector's auth.credentialRef (and the hint's type: 'bearer', credentialRef, newly pinned beside the existing headerName one); the AI tool suite's /references tool "…"/ matcher to /^Tool "…"/ and tool family to Tool family.
  • Lint suite at the final head: pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: Test Files 137 passed (137), Tests 6,455 passed (6,455); VERDICT command-exit 0 (base 137 / 6,416; no new file, 39 new cases).
  • Lint build + typecheck: pnpm --filter @objectstack/lint build (VERDICT command-exit 0, check-dts-emitted 6/6), and after the merge turbo run build --filter=@objectstack/lint (4 tasks, VERDICT command-exit 0). pnpm --filter @objectstack/lint run typecheck at the final 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 eleven 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,174 passed (4,174), before and after; VERDICT command-exit 0 both. It includes test/explain-rule-id.test.ts. The integration tier is declared to CI (no file in it asserts these ids' text, and the diff touches no spawn entry); its one file naming these ids ran as the census extra: Test Files 1 passed (1), Tests 9 passed (9), before and after.
  • CLI e2e files naming these ids (OS_TEST_TIERS=nightly): Test Files 4 passed (4), Tests 56 passed (56), before and after; VERDICT command-exit 0 both. flow-credential-literal-doors.e2e.test.ts asserts the message carries auth.credentialRef at every door; it does.
  • Runtime gate: the whole @objectstack/metadata-protocol suite, before and after: Test Files 225 passed | 3 skipped (228), Tests 28,129 passed | 19 skipped (28,148), both runs; VERDICT command-exit 0. runtime-authoring-gate.flow-credential-literal.test.ts asserts every member who can read flows and auth.credentialRef; both are kept.
  • Corpus pins over packages/lint/src: git grep lint/src -- packages/spec/src names two that walk it, src/shared/retired-key-migrate-sentence.test.ts (judges every os migrate meta --from N sentence) and src/identity/position-delegatable-enforcer.pin.test.ts (reads security-* rule-id lines); the other hits name single files outside this slice or are comments. No new text carries the migrate marker (the residue explanation names os migrate meta without --from), and no rule-id line moved. pnpm --filter @objectstack/spec test:repo at the final head: Test Files 55 passed (55), Tests 971 passed (971); VERDICT command-exit 0. Both pins are in its vitest.repo-tests.json list.
  • 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 92a7bebbb4, 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. seed-insert-mode-duplicates-on-replay's pre-slice message tail was restored verbatim through the anchor of its new tail: anchor x1 → x0, blob 2e2c47f4e718 → 9acef22a58d3. Predicted before the run: exactly two cases red, the id's exact-text pin and the 200-character bound pin; the suite's other assertion on the message (toContain('replay')) holds on the old text too. Observed: src/validate-seed-replay-safety.test.ts Test Files 1 failed (1), Tests 2 failed | 7 passed (9); the two red cases are exactly those two (the bound pin read 222 characters). Restored: blob 2e2c47f4e718 == blob at HEAD, git diff HEAD empty, git status --porcelain empty.
  • ESLint, narrowed: npx eslint --no-inline-config --format json over the 23 changed .ts files gave 23 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 plus the packages/**/*.{ts,tsx,mts,cts} blocks, which all 23 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 final head 82555d5ca8 (merge base b7cd1af9df, after one merge of origin/main through scripts/pm/os-regen-merge.sh; the merge touched no packages/lint file, and its step 2 took origin/main's side of the generated artifacts main moved, none of which this branch edits), derived 62 commands. I ran 60 of them sequentially at that head, each with its own log and its exit code captured before any pipe: all 60 exited 0. Two are NOT MEASURED, as the dispatch directs: pnpm check:dual-build-cjs-loads (it needs every package's dist/, and the dispatch excludes a whole-workspace build) and pnpm check:type-check-debt (--re-measure, which builds every package); the non-re-measuring half, check:type-check-coverage, ran and exited 0. --ran over the exit-coded record: Run reconciliation — 62 derived, 60 run, 2 NOT-MEASURED, 0 UNRUN. The artifact-roster block the no-path run prints (51 families) ran too: the 48 that need no pull request all exited 0, check:published-readme-exports only after one PREREQUISITE NOT MET (exit 3: client-react, embedder-openai, knowledge-ragflow and organizations had no dist/; I built exactly those four with their closure under the lock, VERDICT command-exit 0, and it then exited 0). The four rostered under a directory this diff touches (check-changeset-fixed, check:authz-resolver, check:error-code-casing, check:filter-alias-parity) are among the 48. The three PR-context guards (check-closing-target-claim, check-partof-closing-keyword, check-single-claim-paths) run against this PR once it exists, and their verdicts go to the card report (this body is written once). The long ones on the shared box: check:query-options-erasure 249s, check:slot-lookup 135s, check-comment-mask-corpus 112s.

Remaining for this card

Acceptance notes

  • describeManagedApiMethodConflicts keeps no caller outside its own test. Its docblock (packages/spec/src/data/managed-api-affordance.ts) calls it "the one sentence both consumers say", the registry's strip warning and this lint finding. Measured at the base: objectql's reconcileManagedApiMethods already spells its own sentence and never called it, so the lint verdict was its only caller, and this slice's verdict cannot carry it under 200 characters. The export is a published @objectstack/spec symbol and outside this slice's files; not removed and not filed (an observation about a docblock and a now-uncalled export). Carrier: none.
  • A long name can still lengthen a line. The 200 bound holds on every variant the suites and the four example apps fire; the names in a verdict (object, field, skill, tool, recipient, state, input key) are the author's. The closest measured case is the showcase's field-no-consumers on showcase_client_brief.project at 199, whose one named object is 27 characters long.
  • field-group-shadowed (validate-semantic-roles.ts, not one of this slice's ids) lists the group's members unbounded; the suites fire it at 168 at its longest. Observation only.
  • os lint printer rows prefix where: to the message, so flow-credential-literal's connector-input row prints at 233 in the e2e run while its message is 181; slice 9 recorded the same shape (188 against 175).

Generated by Claude Code

…and a bounded field-no-consumers clause

Each finding of flow-credential-literal, ai-skill-surface-mismatch,
ai-skill-tool-unresolved, flow-filter-token-unknown,
object/managed-api-method-unaffordable, org-axis-cross-org-bu-grant,
permission-retired-lifecycle-residue, seed-insert-mode-duplicates-on-replay,
seed-value-outside-state-machine and semantic-role-field-unprovisioned now
prints one verdict sentence of at most 200 characters; the reasoning moves
into ten new RULE_EXPLANATIONS entries behind `os explain RULE_ID`.
field-no-consumers names the first other object declaring the same field
name and counts the rest. Rule ids, severities, paths, hints and what each
rule accepts or refuses are unchanged.

Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF
Co-authored-by: Claude <noreply@anthropic.com>
…emantic-role provenance verdict

validate-expressions.test.ts holds this finding and the expression site's
unprovisioned-anchor warning to the same object, column and reason; the
shortened verdict now keeps both.

Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF
Co-authored-by: Claude <noreply@anthropic.com>
…rdicts

Clause-②: no. Names every door that prints the new text, measured.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

28 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 c4e7fa5a31c627b57dccc410bbbacbf8abff5ea4 → packageMentionDocs.

Which tree this was computed on

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

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

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

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landing pre-checks at 82555d5ca8, by the owning seat: all green, queued

domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-11T12:15Z · holder of claim 6107641302 on #22161.

  • The review: the seat's ACCEPT 6108783500 on this head. Message prose and RULE_EXPLANATIONS data only (Clause-②: no, @objectstack/lint patch), and no pending changeset is edited, so no contract review is owed, as on slices 5–8.
  • CI: 34 check-runs, 31 success and 3 skipped. check-expected-skips --pr 22828 reads all three in the roster: Build Docs, Console Pin Gate and Packed-tarball smoke (opt-in).
  • Governed: check-governed-merges --pr objectstack-ai/objectstack#22828 reads NOT governed: +1,211 / −86, under the human-merge threshold.
  • Closing keywords: the body opens Part of #22161 and carries no closing keyword. No commit message carries one either, so the card stays open for its remaining items.
  • main drift since the merge base b7cd1af9df: main (now efcbac73cc) moved none of the PR's 24 paths and no file under packages/lint/src. Its one adjacent change is a source edit to packages/metadata-protocol/src/protocol.ts whose diff names none of the four ids that gate prints; the merge group re-runs that suite. There is no generated path, so landing rule A owes no sync. GitHub reports mergeable: true, clean.

pr_ready and automerge_enable follow.


Generated by Claude Code

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