Repository navigation
fix(spec,lint): one-line functional-completeness verdicts; os explain RULE_ID carries their reasoning - #22383
Conversation
…ning moves to `os explain RULE_ID` The nine rule ids the ADR-0078 completeness predicate emits printed a 239-793 character message on every `os validate` / `os build` / `os dev` run. Each finding now carries one verdict sentence that still names the runtime site making it true (ADR-0078 §6) and its unchanged one-line fix; the long reasoning is each id's entry in RULE_EXPLANATIONS, which `os explain RULE_ID` prints and the `rule:` line points at. Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
… verdicts Claude-Session: https://claude.ai/code/session_01RPo7FUd6bSnAfkWMAKi848 Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check15 anchor(s) derived from 2 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
Coarse fallback — 139 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 cd6e325092cad41aa635d1c06a1d302857424470 && git checkout cd6e325092cad41aa635d1c06a1d302857424470
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bb4f5cc0058d1b787120ebd5f505ada79ec5a865 b3598b011036c699ee3cf10034e90f09d6619761 && git checkout -B drift-repro bb4f5cc0058d1b787120ebd5f505ada79ec5a865 && git merge --no-ff b3598b011036c699ee3cf10034e90f09d6619761
node scripts/docs-audit/affected-docs.mjs --json bb4f5cc0058d1b787120ebd5f505ada79ec5a865 |
Part of #22161
Clause-②: no
Stage 2 of the card, first slice: the rule ids whose message lives in
packages/spec/src/kernel/functional-completeness.ts. The card stays open for the later slices listed at the end.What changes
messageof one verdict sentence, at most 200 characters. The longest was 793. The verdict still names the runtime site that makes it true, because ADR-0078 §6 (the calibration section) keeps that citation in the finding's own message. Thefixis unchanged. It is already one pastable line, and the registry's boot log prints it, so leaving it alone keeps that door's text as it was.RULE_EXPLANATIONS(packages/lint/src/rule-explanations.ts): nine new entries, one per rule id.os explain RULE_IDprints them, and the CLI'srule:line now ends with— `os explain RULE_ID` for …for these ids. No CLI file changes.explainPointer()andos explainresolve any id the table holds. That was measured through the real printer andpackages/cli/test/explain-rule-id.test.ts, which iterates every key (below).fix:line or the explanation. Forview/layout-without-binding, the keys the old per-type body said to declare are on thefix:line, which prints directly under the verdict. Their required/optional status and each type's measured renderer path are in the explanation.pathorfixchanges either.functional-completeness.test.ts(spec): every firing variant (18 fixtures, all nine ids) prints one line of at most 200 characters, with no newline inmessageorfix. The calendar/gantt/timeline/map key pins now read thefixline. Every other existing message pin still holds: the runtime file or symbol, the refusal screen names, the offendingkey= "value" entries, the cross-reference toview/row-color-without-colors,no manual fire path exists,isActive.validate-functional-completeness.test.ts(lint): every id inFUNCTIONAL_COMPLETENESS_RULEShas an explanation, and each explanation names what its verdict stopped saying.rule-explanations.test.tsaccepts the spec predicate's ids as rule id constants (they live in@objectstack/spec/kernel, not the lint barrel).Census (re-taken at
6a53564b9, before the change)Every firing variant of every rule in the file, run through the predicate. Lengths are
messagealone; the printed line addswhereand:. All nine ids exceed 200 characters, and all nine are changed here. Stage 1's list counted 8. It measured over thepackages/lintsuite only, whereview/row-color-unresolvable-value(726) never fires. That id fires only in the spec suite.field/summary-without-operationsfield/formula-without-expressionfield/relationship-without-referencefield/relationship-without-referencefield/choice-without-optionsfield/choice-without-optionsfield/choice-without-optionsview/layout-without-bindingview/layout-without-bindingview/layout-without-bindingview/layout-without-bindingview/layout-without-bindingview/layout-without-bindingview/layout-without-bindingview/tree-without-parent-fieldview/row-color-without-colorsview/row-color-unresolvable-valuewebhook/without-triggersDoors that print the new text
Measured in-process at
b3598b011:runAuthoringRules/runRuntimeAuthoringRulesfrom the rebuilt@objectstack/lintdist, printed throughpackages/cli/src/utils/format.ts's real printers. The protocol ordering was read from source.os validate,os build(os compile, whichos devruns per compile),os lintcommands: ALL)rule:line gains theos explainpointer;os validate --jsonwarningsandos build --jsonauthor-timeissuescarry the newmessageos verify,os initscaffold checkbuild)objectwrites (Studio, REST/meta, MCP)field/*only (runtimeTypes: ['object']; the gate's context holds objects, permissions and books, no views or webhooks)summaryandformulaerrors: the 422 issuemessage, and the hatch's refusal log line.checkboxeswarning: the 2xxadvisoriesmessage, and the deduped[Protocol] authoring advisorylog line.hintunchangedlookup/master_detailwithoutreference,select/radiowithoutoptionssaveMetaItemruns the schemasafeParse(protocol.ts:20704) beforerunAuthoringGate(:20890), andfield.zod.tsrefuses both shapes with its own message, unchanged herewarnFunctionalCompleteness,packages/objectql/src/registry.ts)field/*fix, nevermessagewebhook/without-triggersMeasured printed shape (
os buildtext face, rebuilt dist):(The
fix:placeholders are printed in angle brackets; they are spelled in capitals here because GitHub strips angle-bracket fragments from bodies.)Before and after, per rule
field/summary-without-operationssummary — before 319, after 159:
field/formula-without-expressionformula — before 259, after 176:
field/relationship-without-referencelookup — before 239, after 166:
master_detail — before 246, after 173:
field/choice-without-optionsselect — before 250, after 170:
radio — before 249, after 169:
checkboxes — before 291, after 175:
view/layout-without-bindingkanban (no block) — before 248, after 142:
calendar (no block) — before 636, after 170:
gantt (no block) — before 673, after 155:
timeline (no block) — before 568, after 151:
map (no block) — before 638, after 152:
tree (no block) — before 244, after 138:
map (block, no coords) — before 570, after 189:
view/tree-without-parent-fieldtree (no parent) — before 592, after 190:
view/row-color-without-colorsgrid rowColor field=status, no colors — before 793, after 172:
view/row-color-unresolvable-valuegrid rowColor 1 hex value — before 726, after 194:
webhook/without-triggerswebhook — before 594, after 182:
Tests and gates (all local; this branch)
pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2:Test Files 680 passed (680),Tests 19655 passed | 1 todo (19656), run at7386db03f. The only later commit,b3598b011, adds the changeset.pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2:Test Files 128 passed (128),Tests 5863 passed (5863), run at7386db03f.pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 test/explain-rule-id.test.ts:12 passed, against the rebuilt lint dist.every rule the pointer can name resolves through the command to its own explanationiterates all 11 keys, including the nine new ones.pnpm --filter @objectstack/lint run typecheckandpnpm --filter @objectstack/spec run typecheck: exit 0.check:test-typecheckheld at 2/6/2 (lint) and 52/246/135 (spec).turbo run build --filter=@objectstack/lint...(spec, formula, sdui-parser, lint), then--filter=@objectstack/core...and--filter=@objectstack/objectql...for the dist-reading gates.git statusstayed clean after each, with no generated artifact moved. The moved strings feed no generated file or docs table: no.describe()and no export changed, andgit grepfinds no copy of the old message text outside the predicate and its own tests.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatb3598b011derived 86 commands. The printed artifact-roster block adds 51 more (one duplicate), so 136 ran, each with its exit code captured before any pipe.check:dual-build-cjs-loadsandcheck:lean-entry-closurefirst exited 3 (PREREQUISITE NOT MET, missing dists). After the builds above, both exited 0 on rerun (107 entries / 66 packages; 15 packages forobjectql/core).check-partof-closing-keywordexited 2 (NOT WIRED, no PR context). It was rerun withPR_BODYset to this body: exit 0, "this PR carries no Part-of/closing-keyword contradiction".check-closing-target-claimandcheck-single-claim-pathsexited 2 (NOT WIRED: they needPR_NUMBERand a token). NOT MEASURED locally; their guard workflows run them on this PR.dispatch-gates --ran: exit 0, "86 derived famil(ies) accounted for — 86 run, 0 NOT-MEASURED". The two NOT-WIRED roster guards are recorded as NOT-MEASURED rows outside the derived 86npx eslint --no-inline-config --format jsonover the 5 changed.tsfiles. The JSON report lists 5 files, 0 errors, 0 warnings, and none is ignored.eslint.config.mjsnever enables type-aware linting (its comment at:327), so this diff cannot change the verdict on any untouched file.Next slices left on #22161
The card stays open. These are named here, not built:
packages/lintwhose fired message exceeds about 200 characters (stage 1's "Second stage" list, with measured lengths).validate-expressions.ts,lint-flow-patterns.tsandvalidate-flow-template-paths.ts, serial behind open PRs.packages/objectql/src/action-governance.ts(:568/:584).Acceptance notes
lookup/selectrefusal comes from the object schema's parse before the authoring gate. That conclusion is a source reading ofprotocol.tsandfield.zod.ts. The lint-level gate itself still produces those findings, as measured withrunRuntimeAuthoringRules.rule:line is still long. With the pointer it runs about 150 to 190 characters for these ids, for examplerule: view/layout-without-binding at views[0].list.calendar — .... That is stage 1's printer shape, unchanged here. Noted, not filed.Generated by Claude Code