Repository navigation
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
Conversation
…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>
…elds arm 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>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 1 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # 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
|
Part of #22161
Clause-②: no
Stage 2 of the card, slice 8: the 9 rule ids of five whole
packages/lintsource 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
messageof one verdict sentence, followed byDid 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-protocolsuite) fires one of the 9,list-view-field-unknown, at 59 (198 before). The wholepackages/cliunit tier andos validateon the four example apps fire none of the 9, before or after.urltarget, the one segment that misses); the translation ids keep the key and the declaration it was looked up under;list-view-field-unknownkeeps 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-missingkeeps the heading and the key that can never exist.reduceFilterVerdict, so the conformance pins invalidate-empty-combinators.test.ts(identity vocabulary agrees with FILTER_LOGIC_CASES,the row-set language is DERIVED from the shared reduction) pass unedited.RULE_EXPLANATIONS(packages/lint/src/rule-explanations.ts), 9 new entries, soos explain RULE_IDprints it and the CLI'srule:line ends with the pointer for these ids. No CLI source changes:explainPointer()andos explainresolve any key the table holds (packages/cli/test/explain-rule-id.test.tsiterates every key; it ran in the unit tier below);node bin/run.js explain filter-empty-nodeprints the entry. The 102 entries already in the table are byte-equal (the diff ofrule-explanations.tsis additions only).path,hint(thefix: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 amessageexpression, 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 localspelling(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-privateSILENT_EMPTYconstant (the consequence sentence everylist-view-field-unknownmessage ended with) goes; its text is the explanation's. IndescribeDottedRefusalthe three head-classbecausestrings are cut to one clause each.validate-translation-references.ts: message strings only, at every push site of both ids (TRANSLATION_TARGET_UNKNOWNthrough theorphan()helper and four action-entry sites,TRANSLATION_OPTION_KEY_UNKNOWNat six sites)..changeset/22161-lint-slice-8-one-line.md:@objectstack/lintpatch, 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.tsis untouched), and slice 1's contract review (② of its record) readsRULE_EXPLANATIONSentries as data in an existing export.packages/cli/testorpackages/metadata-protocolfile asserts these ids' message text (they pinrule,pathand the planted rule id), and nocontent/docspage quotes one of these messages as printed output.content/docs/deployment/validating-metadata.mdxdescribes 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):translation-target-unknownhas 33 message arms (every leg the rule walks) andtranslation-option-key-unknown7; 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)
rule-explanations.ts)DASHBOARD_HEADER_ACTION_SCOPEEMPTY_FILTER_IDENTITIESreduceFilterVerdicton the four shapesEMPTY_FILTER_SCOPEFILTER_KEYS(every key named)LIST_VIEW_HEAD_ONLYlistViewWalkedPositions()translation-target-unknown,translation-option-key-unknownTRANSLATION_LOOKUPFLOW_TERMINAL_MESSAGE_KEYSThe 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 patchesArray.prototype.pushto record every finding-shaped object (rule+message) of the 9 ids, deduped by (rule, message) per process, with its push site. Lengths aremessagealone; the printed line addswhereand:. Positive control in every run:field-no-consumers(and, in the runtime-gate suite,sort-field-unknownandsearchable-field-unknown) added to the recorded set and recorded.packages/lintsuite, 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_IDcarries their reasoning #22742's list carries.packages/cliunit tier, the whole project (281 files, 4,176 tests, all loaded;packages/cli/distbuilt): fires none of the 9, before and after. Control recorded (7 messages).@objectstack/metadata-protocolsuite (227 files, 224 run, 28,116 passed), which reads@objectstack/lintfrom its builtdist/, before on the base-built dist and after on the rebuilt one. It fireslist-view-field-unknownonce (protocol-publish-drafts-object-field-refs.test.ts, which pins the rule id,pathand the field name, never the text); controls recorded both runs.os validate(the built CLI) onapp-crm,app-todo,app-showcaseandapp-multi-package, exit 0 each, before and after: none of the 9 fire; control recorded (22 messages), and every app prints the same number ofrule:lines before and after.packages/cliintegration-tier files that name these ids (authoring-rule-command-parity.test.ts,verify-author-time-stage.test.ts), which fire two of them.dist/rebuilt from the slice (markeris registered in this stack, so the button likely opens a dead route1 hit each indist/index.js,index.cjs,runtime.js,runtime.cjs; the oldthe button likely navigates to a dead route0;how a url header-action route is resolved1 indist/rule-explanations.js), the same runs at0099dc5a9b(lint source byte-identical to the head; what came after is the changeset and theorigin/mainmerge, which touches nopackages/lint,packages/cliorpackages/metadata-protocolfile).dashboard-action-target-undefineddashboard-action-route-unresolvedfilter-empty-combinatorfilter-empty-nodelist-view-field-unknownlist-view-field-dottedtranslation-target-unknowntranslation-option-key-unknowntranslation-section-name-missingAll 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:validateDashboardActionRefsiscommands: ALL, CLI only;validateEmptyCombinatorsiscommands: ALL, CLI and runtime,runtimeTypes: ['flow', 'report']; the reference-integrity suite,commands: ALL, CLI and runtime, dispatchesvalidateListViewFieldRefson['flow', 'view', 'object']andvalidateTranslationReferences/validateTranslatableSectionson the default['flow']), the runtime gate's split (errors → the 422 issue, everything else → 2xxadvisoriesplus the[Protocol] authoring advisorylog line), and the CLI's callers (commands/validate.ts,compile.ts,lint.ts,verify.tsthroughjudgeAuthorTimeRuleson thevalidateset, andinit.tsthroughvalidateScaffoldon thebuildset):os validate,os build(andos compile, whichos devruns per compile),os lint,os verify, the scaffold checkos initrunsrule:line gains theos explainpointer;os validate --jsonerrorsandos build --jsonauthor-timeissuescarry the newmessagefloworreportwritefilter-empty-combinator,filter-empty-node(error)messageand theOS_ALLOW_UNLINTED_METADATA_WRITESrefusal log lineview,objectorflowwritelist-view-field-dottedand the error-tier positions oflist-view-field-unknown; the warning-tier positions oflist-view-field-unknownmessageand the refusal log line; the 2xxadvisoriesentry and the advisory log lineflowwrite's snapshot carries no translation bundles)hint; every other rule idEvery 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/lintdist. Every heavy run went throughscripts/pm/os-verify-lock.sh; its VERDICT lines are quoted.translation-target-unknownarms and 7translation-option-key-unknownarms, by a fragment unique to the arm; the section verdict on a view and on a page'srecord:details). One case was added for the one arm no existing case fired (a result field under a dialog that declares nofields). Exact pins hold one verdict per arm; explanation pins hold, per id, thatexplainRule(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,whereand hint pin is unchanged; the pins that read the old text were moved to the new verdict (ninefirstSentence(...)pins in the translation suite,can only match zero records→so no backend serves the pathin the list-view suite) or to the explanation (walks \sections[].name`` in the section suite).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).pnpm --filter @objectstack/lint build(VERDICT command-exit 0,check-dts-emitted6/6) andpnpm --filter @objectstack/lint run typecheckat the head: VERDICT command-exit 0;check:test-typecheckOK, 2 files / 6 errors / 2 pinned signatures held.tsc -p packages/lint/tsconfig.test.json --listFileslists all six touched test files.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 includestest/explain-rule-id.test.ts(iterates everyRULE_EXPLANATIONSkey, so the 9 new ids resolve throughos explain,explainPointerand 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.@objectstack/metadata-protocolsuite, before and after: Test Files 224 passed | 3 skipped (227), Tests 28,116 passed | 19 skipped (28,135), both runs; VERDICT command-exit 0.#plus digits in any printed message or explanation (rule-explanations.test.tsrefuses one in every paragraph); the citations stay in//comments and test names.77406985d2, throughscripts/ablation-replace.mjswrap mode under the verify lock, plus a shelltraprestoringHEADby 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, blob1da105c527b6→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.npx eslint --no-inline-config --format jsonover the 11 changed.tsfiles atacffa89b16gave 11 files in the report, 0 errors, 0 warnings. The population iseslint.config.mjs'sfiles: ['**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}']block, which all 11 are in (the changeset is not a linted kind).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, no typed@typescript-eslintrules; 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.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackwith no paths, at the headacffa89b16(merge basee86530088a), 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.--rangaveRun 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-erasure258s,check:slot-lookup128s,check-comment-mask-corpus113s.Remaining for later slices — 20 ids in
packages/lint, by filePR #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-literal390validate-action-name-refs.ts(1):action-name-undefined409validate-ai-surface-affinity.ts(1):ai-skill-surface-mismatch281validate-ai-tool-references.ts(1):ai-skill-tool-unresolved441validate-capability-references.ts(1):capability-reference-unknown219validate-flow-filter-tokens.ts(1):flow-filter-token-unknown278validate-managed-api-methods.ts(1):object/managed-api-method-unaffordable412validate-mapping-target-fields.ts(1):mapping-target-field-unknown466validate-nav-access.ts(1):nav-object-ungranted353validate-nav-object-servability.ts(1):nav-object-unservable528validate-nav-target-refs.ts(1):nav-target-unresolved426validate-object-field-refs.ts(1):object-field-ref-unknown320validate-object-references.ts(1):object-reference-unregistered-platform324validate-org-axis-red-lines.ts(1):org-axis-cross-org-bu-grant365validate-page-visualization-bindings.ts(1):page/visualization-without-binding629validate-retired-permission-residue.ts(1):permission-retired-lifecycle-residue235validate-seed-replay-safety.ts(1):seed-insert-mode-duplicates-on-replay222validate-seed-state-machine.ts(1):seed-value-outside-state-machine320validate-semantic-roles.ts(1):semantic-role-field-unprovisioned291validate-view-containers.ts(1):view-container-shape290The 26 ids slice 2 fenced, the 9 ids owned by
packages/cli, and theaction-governance.tsboot-log lines stay as PR #22448's body lists them, andreact-prop-deprecatedas PR #22700's notes it. This slice touched none of them.Acceptance notes
$or-branch hint's "(A compiler that DROPPED the empty branch …)", the section hint's "the name above is a suggestion" (it means thename:the hint itself suggests), the tab hint'sinterfaceConfig.userFilters.tabsnote and the list-view hints are each self-contained. Unchanged here, as every slice leaves hints..changeset/22161-lint-slice-5-one-line.md,-6-,-7-) name "the scaffold checkos initandos generaterun" as a door. Measured at the head:validateScaffold(packages/cli/src/utils/scaffold-validate.ts) has one caller,commands/init.ts, andcommands/generate.tscalls no authoring rule; this slice's changeset namesos initalone. 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 onos validateofexamples/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_IDcarries their reasoning #22742 recorded; untouched, a later slice's.successMessageis the one toast key no case fires; it shares theerrorMessagearm's template (FLOW_TERMINAL_MESSAGE_KEYSinterpolated), which is fired and pinned.list-view-field-unknownis 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.Generated by Claude Code