fix(lint): data-model, filter, predicate, search, sort, security and registry findings state each decision in words instead of a tracker number (stage 2) - #21491
Conversation
…each decision in words instead of a tracker number (stage 2) Text only: every changed string is a message, hint, thrown error or registry reason that carried a tracker id; no rule id, severity, condition or code path moves. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…he replaced tracker numbers Each assertion keeps its strength: toContain stays toContain on the words that replaced the number, and the exact-message pin stays exact. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…patch changeset The ledger is regenerated with check-doc-authoring --census-ledger: the 18 packages/lint file blocks leave it (71 lines deleted, 0 added), and no other file's row moves. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
…nt-strings-stage2
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also 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 d074b68d2c8c07d06f7d607991f987ca618670f8 && git checkout d074b68d2c8c07d06f7d607991f987ca618670f8
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cba42971763480ff986dcd6954662bf5fed23f7c dfb89505f509e1a2c671da17212628a9090ef396 && git checkout -B drift-repro cba42971763480ff986dcd6954662bf5fed23f7c && git merge --no-ff dfb89505f509e1a2c671da17212628a9090ef396
node scripts/docs-audit/affected-docs.mjs --json cba42971763480ff986dcd6954662bf5fed23f7c
|
Part of #20749
Clause-②: no
Stage 2 of the
domain:speclane's share under the maintainer's A / A ruling (5902360492): the last 51 ledgered tracker-number occurrences inpackages/lint, in 18packages/lint/srcfiles. After this,packages/linthas no row left inscripts/doc-authoring-prose-id.baseline.json. The card stays open for the later stages, so this PR carries no closing keyword. Text only: no rule, condition, code path, rule id, severity or registry field moves (AST skeleton proof below, 18 of 18 SAME).What this does
These 18 files print findings to authors through
os validate,os lintandos build(and, for the rules on the runtime publish gate, through the Studio, REST and MCP publish door).authoring-rules.tsalso carries thesurfaceReasontexts of the exportedAUTHORING_RULESregistry, andlint-liveness-properties.tsone thrown integrity error. 51 tracker numbers in that text sent the reader to a card for the reason behind it. In form D, as stage 1 (PR #21462) and the sibling lane's stages applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words.The surface, re-derived from the ledger and from
check-doc-authoring.mjs --censusonorigin/mainat7e7e64b13d(the claim's numbers hold exactly):authoring-rules.ts9,validate-empty-combinators.ts6,validate-predicate-path-refs.ts6,validate-searchable-fields.ts5,validate-sortable-fields.ts5,validate-null-guards.ts4,validate-visibility-predicates.ts3,data-model-rules.ts2,validate-security-posture.ts2, and one each inlint-liveness-properties.ts,validate-component-props.ts,validate-dashboard-action-refs.ts,validate-nav-object-servability.ts,validate-react-page-props.ts,validate-rule-schema-formats.ts,validate-seed-state-machine.ts,validate-view-containers.tsandvalidate-widget-bindings.ts. They sit in 46 census sites (40 string groups once a+chain is read as one string). By class: 37 occurrences in finding envelopes (amessageorhint), 13 in prose (9 inAUTHORING_RULESsurfaceReasontexts, 4 in the null-guard outcome clauses a finding carries) and 1 thrown. None is logged.Every cited card (32 objectstack cards, plus
objectui#4049,objectui#2348andobjectui#4051, where #5149 was migrated) was read through REST, body and every comment, before its string was rewritten. Where a card closed with no ruling comment, its landing commit was read.Rewritten in words
Lines are the census lines at
7e7e64b13d.authoring-rules.ts:440,:1177,:1974, the full-snapshot, capability-reference and sharing-rulesurfaceReasontextsauthoring-rules.ts:507, the advisory-volume reasonadvisoriesrides the save response; Studio renders it); 4716: adjudication 5328603673 (the five gating object rules cross, the six advisory-only rules do not ride, advisory UX is separate work)authoring-rules.ts:1111, the component-types reasonauthoring-rules.ts:1976, the sharing-rule reasondata-model-rules.ts:344, bare declaredunique: trueunique: 'global', which builds the same physical index."trueto'global', baretruerejected at protocol 18) and the hold records 5225701234 and 5481784294lint-liveness-properties.ts:288, unrecognised ledger status (thrown)dead."dead)validate-component-props.ts:326, props hinttype, warning first then error; B, reshapingproperties, not done)validate-empty-combinators.ts:231,:235,:271, the$and: [],$or: []and empty-node messagesvalidate-empty-combinators.ts:237,$or: []on a read scope$orcompiles to FALSE; the SQL driver had answered the whole table)validate-null-guards.ts:597, the fail-closed outcomehas()shape refused at build/publish)validate-predicate-path-refs.ts:652,:694;validate-visibility-predicates.ts:1001,:1023,:1126, the fall-open consequence:694)objectui#4051, the delegated ruling 5235422835 (fail-open kept); 6254: landing643b7c76bc(a repeater still spells its rootdata, which the:694hint already states)validate-predicate-path-refs.ts:731, dotted right-hand sideobjectui#4049validate-rule-schema-formats.ts:326, format hintrule-validator.tsregisters so that aformatis enforced on every write"ajv-formats) and ACCEPT 5176328955validate-security-posture.ts:464, unset OWDobjectui#2348validate-security-posture.ts:526,controlled_by_parentwith no relationvalidate-seed-state-machine.ts:146, seed outside the state machine0c302a7790(seed writes exempt fromstate_machine: a curated snapshot of established facts)validate-view-containers.ts:133, a ViewItem inviews:views:to one container-only contract."viewItems:home first, then the tighten with lint alignment)validate-react-page-props.ts:458, absentgroupBye0f300ba5d(groupBystays required; the single-value need is served by the object-metric block)Citation only (the sentence already stated the decision)
authoring-rules.ts::1978(8309, "objectsIS carried by the per-write snapshot"; audit 5279528993);:1982(7220, "a rule that has never run at a door should not claim it"; maintainer ruling 5237225765).data-model-rules.ts:521(5030, "SQL UNIQUE is NULL-distinct ... it enforces nothing", the fix naming the NULL-safe organization key part; ADR-0120 and landing record 5178627933).validate-dashboard-action-refs.ts:299(objectstack#6739, "a modal target names a PAGE, only", with the hint namingactionType: 'form'; maintainer ruling 5229995836).validate-empty-combinators.ts:256(5330, the hint already says a programmatic producer keeps the runtime identity and only authored metadata is judged; ACCEPT 5226245518);:295(5297, "which is why the runtime absorbs rather than filters").validate-nav-object-servability.ts:167(7912, "The server prunes this entry from the served/metapayload"; maintainer ruling 5271014830).validate-null-guards.ts:601(4811, the clause already describes the skippedrequiredWhen; ACCEPT 5169421708).validate-predicate-path-refs.ts:737(objectui#4049, "it stops working when this surface moves to the real CEL evaluator");:739(7696, "this one finding carries BOTH readings"; dev report 5255289068).validate-searchable-fields.ts:356,:419,:443,:476(4254, "400 INVALID_FIELD"; landingaf2a0958c8);:414(6674, the hint already says the ingress gate now refuses the entry; triage promotion 5230241805 and landing4ac12ef4cf).validate-sortable-fields.ts:415,:416,:435,:471(6994 and 7095, "the REST ingress and the engine" refuse a formula sort with 400 INVALID_SORT; landings9f7a7c25aband6908830573, maintainer ruling 5236143229 on 7095).validate-widget-bindings.ts:982(2501, "The filter is ANDed into this widget's analytics query", the same treatment this file's two neighbouring messages already had).No occurrence was left in place: no cited decision was unclear, and each fit the string's space.
Ledger (
scripts/doc-authoring-prose-id.baseline.json)Recomputed with
node scripts/check-doc-authoring.mjs --census-ledger(exit 0, no growth refusal) into a scratch file, then copied into place; recomputed again on the merged tree (below), where it is byte-identical to the file in this branch. Againstmainthe diff deletes 71 lines and adds none: exactly the 18 file blocks (36 brace lines and 35 pair lines). A scripted comparison of every other key: 0 moved, 0 added.packages/lint7e7e64b13dmainatcba4297176(merged in)pnpm check:doc-authoringat the merged headdfb89505f5: "sibling-package prose ids hold the baseline — 72 pinned site(s) across 21 file(s), 86353 string(s) read in 1253 parsed source(s), no growth, no burn-down unrecorded". No gate is added or loosened;scripts/check-doc-authoring.mjsis untouched.Ledger serial: while this stage was built, the sibling lane's stage 4 (PR #21472) held the ledger. This PR opened only after it merged (
cc0786223b), withorigin/mainatcba4297176merged in (a merge commit, no rebase) and the ledger recomputed on that tree; git's text merge of the two deletion-only diffs and the recomputation agree byte for byte. At opening, no open PR touches the ledger or any of the 18 files (every open PR's file list read). PR #21480 (#21457) landed in the same queue run; its edits tovalidate-predicate-path-refs.test.tsandlazy-deps.test.tsare kept as they landed (both files equalmainhere), and this PR touches neither.Quoted elsewhere
content/docs/data-modeling/schema-design.mdx:179quoted the stale-entry hint verbatim,(#4254)included; the quote is updated to the new text in this PR.skills/**: no quote of a changed message.Changeset
.changeset/20749-lint-strings-stage2-state-the-decision.md:patchfor@objectstack/lint, carryingClause-②: no. Measured after building: the new sentences are inpackages/lint/dist/index.js,index.cjs,runtime.jsandruntime.cjs, and none of the replaced id-bearing fragments is (the two(#6994)/(#5082)hits left indistare source comments tsup keeps).Text-only proof
The stage-1 tool, unchanged: a TypeScript-AST skeleton of each changed source in which every string literal and template text is a placeholder, a
+chain is flattened and a run of adjacent string operands is one string (only its embedded expressions are kept, in order), identifiers, numbers and regex literals keep their text, every child is visited, and comments are never read. A second leg compares the TEXT of every string group in order: each group that changed must have carried a tracker id before and carry none after, and every other group must be byte-identical.7e7e64b13dagainst the head: 18 of 18 SAME on both legs, token counts identical per file, 40 groups changed, all of them id-bearing before and id-free after, parse diagnostics 0/0.Controls on a scratch copy of
validate-empty-combinators.ts(head version), each mutation counted on disk first (1 anchor hit, replacement present, anchor gone): one function renamed reads DIFF;===flipped to!==reads DIFF; one literal re-split into two+operands reads SAME with no extra group changed; a text change in a string that never carried an id reads SAME on the skeleton and VIOLATION on the text leg. No repo file was mutated for the controls.Pins
Every assertion that found a finding by its tracker number, or by the full old text, now finds it by the words that replaced it, at the same strength (
toContainstaystoContain, the exacttoBestays exact). No rule-id, severity or path assertion was touched.validate-visibility-predicates.test.ts:968,:1418: "failing open is the console's settled behaviour" for'#5149'.validate-react-page-props.test.ts:1202: "groupBy stays required, and a single number belongs in an object-metric block instead" for'5583'.validate-searchable-fields.test.ts:760: the exact outside-the-declared-set message, now ending "returns 400 INVALID_FIELD."packages/cli/test/data-model-rules.test.ts:622: "on every row whose 'organization_id' is NULL it enforces nothing" for'#5030'.Tests
All builds and tests through
scripts/pm/os-verify-lock.sh, every verdictVERDICT command-exit 0on the runs quoted. These ran atd5137b469b, the head beforemainwas merged in; the merge brought ninemaincommits that touch none of this PR's files apart from other rows of the ledger, and the build, the lint suite and the gate union are re-run on the merged head, with their readings in this stage's dev report on #20749.pnpm --workspace-concurrency=2 --filter '@objectstack/lint^...' build(the closure), thenpnpm turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*("Tasks: 71 successful, 71 total") for the dist-reading gates and the CLI test.@objectstack/lint:vitest run --maxWorkers=2: "Test Files 119 passed (119) / Tests 5614 passed | 5 skipped (5619)". The first run, before the re-pins, read 4 failed in 3 files, exactly the four pins listed above.typecheck:tsc --noEmitexit 0 and "check:test-typecheck: OK — @objectstack/lint's test layer compiles ... 2 file(s) / 6 error(s) / 2 pinned signature(s) held".@objectstack/cli, unit layer,test/data-model-rules.test.ts: "Test Files 1 passed (1) / Tests 56 passed (56)". Nopackages/clisource,bin/or integration-layer file is touched, so the integration layer is CI's.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) atd5137b469b: 99 commands, run one at a time from the worktree, each exit code recorded before any pipe.--ran: "99 derived famil(ies) accounted for — 99 run, 0 NOT-MEASURED (a DERIVED zero — all 99 recorded an exit code and none of them is 3)". All 99 exit 0, the dist-reading ones (check:docs-transcript-drift,check:dts-closure,check:dual-build-cjs-loads,check:lean-entry-closure,check:published-files,check:sourcemap-no-sources-content) after the full build. Outside the derived set, also all exit 0: the 11 declared wide-population families and the 11 artifact-roster families whose roster sits under one of this PR's directories (check-changeset-fixed,check-published-list-mirrorsand its self-test,check:authz-resolver,check:console-injection,check:engine-double-contract,check:error-code-casing,check:filter-alias-parity,check:i18n-stale-fill,check:published-readme-exports,check-dts-references --self-test).check:doc-authoring(self-test and run) exit 0;check:nul-bytesexit 0;check-adr-0087-registrationexit 0 ("this PR adds no declared-breaking changeset (1 non-breaking changeset(s) seen)");check:empty-changesetexit 0;check-changeset-fixedexit 0 (""fixed" group is in sync with 69 public workspace packages");check-changeset-no-majorexit 0 on the plain run ("This diff introduces nomajorbump."), and exit 0 when fed this body as apull_requestevent (theClause-②: noline read,patchjudged against it). At the merged headdfb89505f5,check:doc-authoring,check:nul-bytes,check-changeset-no-majorandcheck-adr-0087-registrationwere re-run before opening: all exit 0.check-issue-citations: "no issue citations added against 7e7e64b (18 file(s) read)";check:partof-closing-keywordon this body: exit 0.pnpm lintis CI's.Acceptance notes
Noted, not filed:
lint-liveness-properties.test.ts(35 / 4),validate-component-props.test.ts(19 / 0),validate-dashboard-action-refs.test.ts(8 / 0),validate-empty-combinators.test.ts(3 / 1),validate-null-guards.test.ts(3 / 0),validate-predicate-path-refs.test.ts(11 / 1),validate-react-page-props.test.ts(28 / 0),validate-rule-schema-formats.test.ts(4 / 0),validate-searchable-fields.test.ts(13 / 0),validate-security-posture.test.ts(7 / 1) and its.runtime-surface.test.ts(27 / 2),validate-seed-state-machine.test.ts(1 / 0),validate-sortable-fields.test.ts(12 / 0),validate-view-containers.test.ts(2 / 0),validate-visibility-predicates.test.ts(23 / 0),validate-widget-bindings.test.ts(27 / 0), plusauthoring-rule-wiring.test.ts(1 / 12) forauthoring-rules.tsandreference-integrity-suite.test.ts(4 / 0) forvalidate-nav-object-servability.ts, which have no same-named test file;data-model-rules' two same-named test files carry none, andpackages/cli/test/data-model-rules.test.tscarries 3 in titles. The ledger does not read test files.validate-empty-combinators.test.tsselects its identity cases with(c.note ?? '').includes('#5322')overFILTER_LOGIC_CASESnotes in@objectstack/spec: that test-only id is load-bearing (it is a selector, not prose), so the later stage that sweeps test strings cannot just strip it.authoring-rules.ts:1111's reason holds the component-types rule to a budget measured "over stored tenant page rows"; the object-door crossing it cites was adjudicated on a measured lower bound from authored config-file metadata, with a post-launch replay as the audit, because no tenant rows exist yet. The reason's wording is unchanged in substance here; whether that bar still describes the page crossing is the seat's to judge. Carrier: none..mjsgate scripts stage 1 named (packages/lint/scripts/check-doc-formula-expressions.mjs,scripts/check-startup-registry-verdict.mjs) are untouched, as staged.Generated by Claude Code