fix(service-analytics): runtime strings state each decision in words instead of a tracker number (stage 8) - #21569
Conversation
…instead of a tracker number (stage 8) The read-scope compiler's undefined-comparand and non-boolean-flag refusals, the native-SQL cross-field backstop and the two /analytics/sql echo refusals no longer cite tracker numbers; each states the decision in words. The doc-authoring prose-id ledger is recomputed with --census-ledger and is now empty. Text only. Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 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 e927884a1d4343c3f58f04eb9dc1f82eae00ad9d && git checkout e927884a1d4343c3f58f04eb9dc1f82eae00ad9d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 6dd99b82c38cd68b51c86241d53c5b7dda670a08 3628b4e8cb7fe7b8f04232e0fe5f3097455aa8cb && git checkout -B drift-repro 6dd99b82c38cd68b51c86241d53c5b7dda670a08 && git merge --no-ff 3628b4e8cb7fe7b8f04232e0fe5f3097455aa8cb
node scripts/docs-audit/affected-docs.mjs --json 6dd99b82c38cd68b51c86241d53c5b7dda670a08
|
…een floor its header prescribes once the baseline is empty
The prose-id baseline is now {}, so the stale arm can no longer catch a
walker or prefilter that goes blind: 0 measured against 0 pinned reads
green. The leg now reds when the real tree's parsed-source or string
count falls below PACKAGES_PROSE_SEEN_FLOOR (600 sources, 40000 strings,
about half of today's 1252 / 86276), each measure judged on its own. A
new self-test battery proves a below-floor reading reds; the header
sentence says the floor exists and why. No other behaviour moves.
Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ
Co-authored-by: Claude <noreply@anthropic.com>
Part of #20751
Clause-②: no
Stage 8 of the
domain:serviceslane under the maintainer's A / A ruling (5902360492):service-analytics, part 2 of 2, the last stage (read-scope-sql.ts,strategies/native-sql-strategy.ts,strategies/objectql-strategy.ts). The card stays open until the seat acts on it after this lands, so this PR carries no closing keyword. Text only: no status, errorcode, field, route, export or control flow moves (the AST skeleton reads SAME for 3 of 3 changed.tsfiles, below). After this PR thecheck:doc-authoringprose-id ledger is empty:{}.What this does
Five messages in these three files (nine string literals) sent the reader to a tracker number for the reason behind them. In form D, as stages 1 to 7 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, in the words stage 7 used wherever the decision is the same one. One decision, one wording.
All 13 ledgered occurrences in this stage's surface (claim
5967239052), re-derived from the ledger onorigin/mainat1ca1eb09(where the branch was cut):read-scope-sql.ts5 (5 pairs),strategies/native-sql-strategy.ts2 (2 pairs),strategies/objectql-strategy.ts6 (6 pairs), in 9 string literals across 5 messages. That matches the seat's reading (5, 2 and 6 ids) and is the whole remaining ledger.Rewritten in words
Refusals an operator or caller reads first; the backstop that is unreachable by construction last. Line numbers are at the head
0edca886.read-scope-sql.ts:1676-1688,undefinedComparandError(READ_SCOPE_COMPILE_FAILED/ 500, fail-closed)wheredoor, word for word. The sentence before it already says the producer to fix is whoever built the read scope, never the caller of the query, and the prefix still says fail-closedundefinedcomparand is refused loudly, never read as null, because the spec declares no such comparand and an undefined key cannot be told from an absent one. 6125's PM ruling (its option 2): that refusal is pushed down to this compiler, in this module's ownREAD_SCOPE_COMPILE_FAILED/ 500 envelope, because a read scope is compiled by the platform and is not the caller's inputread-scope-sql.ts:1864-1875,nonBooleanFlagComparandError(same envelope, fail-closed)$null,$existsand$emptyflags the message names two sentences earlier$nullcomparand is refused, not coerced, because the spec declaresz.boolean()and the backends' two default readings point in opposite directions. 5369: the same for$exists, applied through the 5298 ruling. 6387: the same refusal pushed down to this compiler in this module's envelope (the disposition is inherited, not the 400). "Every driver" is measured on this tree:driver-sql(anddriver-sqlite-wasm, which extends it),driver-memory,driver-mongodbanddriver-turso's remote transport each refuse a non-boolean$null,$existsand$empty("requires a boolean comparand")objectql-strategy.ts:461-473, the/analytics/sqlecho's refusal of a{ $field }comparison (INVALID_FILTER/ 400)$fieldquery so it routes to the engine path, where the driver enforces those rules with metadata it owns, in one place; the echo declines too ("one consistent loud answer, no half-rendering", which the sentence already said). 3601 / 3602 / 3650: the echoed statement carried no read-scopeWHEREand droppeddateRange, so it described a different query from the one that ran; each fix made the echo render what execution appliesobjectql-strategy.ts:1479-1484, the echo's unmapped-operator refusal (a bareError, deliberately not 400)native-sql-strategy.ts:1034-1042, the cross-field backstop (a bareError, unreachable by construction)Every cited card (11: 3601, 3602, 3650, 5222, 5333, 5347, 5369, 6050, 6125, 6387, 7598) was read through REST, body and every comment, before its string was rewritten. All eleven answer 200.
Sibling sentences
fieldReferenceComparandMessageandfieldReferenceBetweenBoundMessagereachread-scope-sql.tsthrough a bare[read-scope-sql]prefix (lines 2010 and 2017), so they already carry stage 7's wording and need nothing here.comparand-shape.ts,analytics-service.ts(both stage 7),native-sql-strategy.tsandobjectql-strategy.ts; the undefined-comparand sentence is the same at both analytics doors.Published contract check
None of these strings is a spec-declared message or an i18n key. They are refusal and diagnostic text built inside
service-analytics. A search for each old fragment acrosspackages/finds no test assertion and no doc quoting it; the hits are code comments, test comments andCHANGELOG.md. No test asserted any of the five messages by an id or by a fragment this PR rewrites. The assertions that do read these messages ("comparand at ... is undefined", "is not a boolean", "never the caller of this query", "sharing rule", "getReadScope",The string "false" is TRUTHY, the one-wording skeleton checks, "$field", "/analytics/query", "read-scope-sql", "cannot render") all still hold, and everycode/statusassertion is untouched.Ledger (
scripts/doc-authoring-prose-id.baseline.json)Regenerated with
node scripts/check-doc-authoring.mjs --census-ledger > scripts/doc-authoring-prose-id.baseline.json(exit 0, no growth refusal). The file is now{}: 21 lines deleted and 1 added, because the empty object collapses its opening and closing braces onto one line. Every pinned pair goes to absent and none is added.1ca1eb09)read-scope-sql.tsstrategies/native-sql-strategy.tsstrategies/objectql-strategy.tsNothing else remains in the ledger.
pnpm check:doc-authoringat the head: "sibling-package prose ids hold the baseline — 0 pinned site(s) across 0 file(s), 86276 string(s) read in 1252 parsed source(s), no growth, no burn-down unrecorded".The empty ledger still bites. Reverse check at the committed head through
scripts/ablation-replace.mjs(WRAP mode, plus an outer trap restoring bygit checkout HEADon the absolute path): putting the citation back into the unmapped-operator refusal (anchor hit 1 to 0, blobeedaeae5to9d6ea181) turnedcheck:doc-authoringred with exactly one growth pair (objectql-strategy.ts, id 5333, 0 pinned, 1 measured), as predicted. Restored blob equalsHEAD(eedaeae5), andgit diff HEADis empty.No gate is added or loosened;
scripts/check-doc-authoring.mjsis untouched.For the seat: the gate header's seen-floor note
scripts/check-doc-authoring.mjs(lines 700-705, the cross-package leg's ratchet notes) says: "While the baseline is non-empty, the ratchet IS this leg's blindness floor: a walker or prefilter that goes blind reads 0 sites against 632 pinned pairs and reds as stale. ... If the baseline is ever burned to empty, add an explicit seen-floor here in the same PR — at that point the stale arm can no longer catch a dormant walker."This PR empties the baseline, and the dispatch says no gate is added or loosened and no other file is touched. So the two disagree, and this PR does not pick a side. What is measured:
{}, the leg has no floor on the real tree: a walker that stopped seeing the real sources would read 0 sites against 0 pinned pairs and print green.--self-testasserts on a fixture tree that strings are seen at all, that the prefilter is a superset of the id regex, and that an empty root is a hard error;collectPackageProseFilesthrows on zero files. The part no check covers is the real tree's population shrinking toward zero while the fixtures still pass.The options and a recommendation are in the dev report on the card. This PR is draft either way.
Changeset
.changeset/20751-services-strings-stage8-state-the-decision.md:patchfor@objectstack/service-analytics,Clause-②: no. Measured after the full build: every new sentence is indist/index.jsanddist/index.cjs, and none of the old citation fragments is (pushed down to this compiler,ruling B,#5347 / #5369,maintainer ruling 2026-08-12,enforces the #5222,silently (#7598),one that ran (#5333): 0 hits in each). A TypeScript scan of every string literal and template text in both built files finds 0 tracker ids (1507 and 1511 strings read). The same scan reads 5, 2 and 6 ids in the three branch-point sources, which is its positive control.Text-only proof
A TypeScript-AST skeleton of each changed
.tsfile, where every string literal and template text is a placeholder, a run of adjacent string operands of a+chain is one string (only its embedded expressions are kept), identifiers and numbers keep their text, and comments are never read.1ca1eb09against the head: 3 of 3 SAME. Controls on scratch copies ofobjectql-strategy.tsfrom the branch point, each mutation's marker counted once on disk first: a one-identifier rename reads DIFF; a text-only change reads SAME; a re-split of one string into two concatenated pieces reads SAME.Tests
All heavy runs went through
scripts/pm/os-verify-lock.sh, every verdictVERDICT command-exit 0, at the head0edca886:turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*(71/71).@objectstack/service-analytics,vitest run --maxWorkers=2in two shards (--shard=1/2,--shard=2/2): 88 + 87 = 175 files, 2237 + 1915 = 4152 tests passed, 113 + 140 = 253 skipped.@objectstack/service-analyticstypecheck(tsc --noEmit): exit 0.--listFilescounts 175 test files in that program, all 175 on disk.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) at0edca886(5 paths vs merge base1ca1eb09, 62 changed lines): 66 commands, run one at a time from the worktree after the full build, each exit code recorded before any pipe; 66 exit 0.--ran: "66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED (a DERIVED zero — all 66 recorded an exit code and none of them is 3)".check:doc-authoring(above);check:issue-citationsandcheck-issue-citations.mjs;check:nul-bytes;check:dts-closure;check:dual-build-cjs-loads;check:published-files;check:sourcemap-no-sources-content;check-adr-0087-registrationandcheck-empty-changesetagainstorigin/main.0edca886: the eleven declared wide-population families (check:init-service-contract,check:live-db-isolation,check:meta-type-normalized,check:optional-error-sink,check:resume-authority-declared,check:route-envelope,check:runner-env-posture,check:settings-bind-window,check:startup-registry-verdict,check:verify-stand-in,check:wildcard-fallthrough), pluscheck:durability-log-levelandcheck:error-code-casing(refusal and diagnostic text moved; no level or code did).dispatch-gatesnames (check-issue-citations.mjs --censuswithGITHUB_TOKEN, the shard-attestation emits and the test-completeness reads); they need CI values.eslint --no-inline-config --format jsonover the 3 changed.tsfiles at0edca886: 3 files linted (none ignored), 0 errors, 0 warnings.eslint.config.mjsenables no type-aware linting (noparserOptions.project, no typed rules; its own comment at lines 327-328 says so), so this diff cannot move any untouched file's verdict. Repo-widepnpm lintis CI's.origin/mainmoved tobd707067(one commit, PR 21563,packages/runtimeonly) after the branch point. It touches neitherservice-analyticsnor the ledger, and adds no id-bearing string line in a non-test package source (its only id-bearing changes are deleted comment lines), so the empty ledger stands on that tree too; the branch is not merged. No open PR touches the ledger or these three files (8 open PRs read by REST at 09:04Z).Acceptance notes
Noted, not filed:
[#7598]docblock on the backstop, the[#5333]comment above the operator refusal, andcomparand-shape.ts:206's table cell naming the read-scope refusal by its two cards). Comments are outside the ledger and outside the rule. Carrier: none.Round 2: the seen floor (stage 8 claim revision 1,
5967600765)The seat answered the open question with A. Commit
3628b4e8cadds the explicit seen floor that the header ofscripts/check-doc-authoring.mjsprescribes for the PR that empties the baseline. It touches the cross-package leg only. This supersedes the line above that says "This PR does not touch the gate".PACKAGES_PROSE_SEEN_FLOORis 600 parsed sources and 40000 strings, about half of the reading when the floor was pinned (1252 / 86276 at0edca886c). The leg reds below either number. Each measure is judged on its own, and a missing measure is a breach. Lowering the floor is marked ⛔ MAINTAINER-ONLY.cross-package seen floor, has 8 cases. The registry floor goes from 16 to 17.HEAD.3628b4e8c: 77 commands, all exit 0.scripts/check-doc-authoring.mjsisdomain:spec. The notice is on [PM seat] domain:spec — 🟢 os-project-manager · session_01T9u38rswFp5Rw8DswRUReJ #6017 (5967607594).Generated by Claude Code