You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit 44072fc
Browse filesBrowse the repository at this point in the historyBrowse files
fix(service-analytics): runtime strings state each decision in words instead of a tracker number (stage 8) (#21569)
Part of #20751
Clause-②: no
**Stage 8 of the `domain:services` lane 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, error `code`, field, route, export or control flow
moves (the AST skeleton reads SAME for 3 of 3 changed `.ts` files,
below). After this PR the `check:doc-authoring` prose-id ledger is
empty: `{}`.
⚠️ **One question for the seat before landing.** The gate's own header
asks for an explicit seen-floor in the PR that empties the ledger. This
PR does not touch the gate (the dispatch scope). See "For the seat"
below.
## 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 on `origin/main` at `1ca1eb09`
(where the branch was cut): `read-scope-sql.ts` 5 (5 pairs),
`strategies/native-sql-strategy.ts` 2 (2 pairs),
`strategies/objectql-strategy.ts` 6 (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`.
| Where | Cited | The text now says | Decision read from |
|---|---|---|---|
| `read-scope-sql.ts:1676-1688`, `undefinedComparandError`
(`READ_SCOPE_COMPILE_FAILED` / 500, fail-closed) | 6050, 6125 | "An
undefined comparand is refused rather than read as null, on the SQL
drivers and on this door alike." This is stage 7's sentence for the same
decision at the `where` door, 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-closed | 6050's
ruling (2026-08-07, option B): an `undefined` comparand 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 own `READ_SCOPE_COMPILE_FAILED` / 500 envelope, because a read
scope is compiled by the platform and is not the caller's input |
| `read-scope-sql.ts:1864-1875`, `nonBooleanFlagComparandError` (same
envelope, fail-closed) | 5347, 5369, 6387 | "A non-boolean comparand for
any of the three is refused rather than coerced, on every driver and on
this door alike." "The three" are the `$null`, `$exists` and `$empty`
flags the message names two sentences earlier | 5347's PM ruling (option
A): a non-boolean `$null` comparand is refused, not coerced, because the
spec declares `z.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` (and
`driver-sqlite-wasm`, which extends it), `driver-memory`,
`driver-mongodb` and `driver-turso`'s remote transport each refuse a
non-boolean `$null`, `$exists` and `$empty` ("requires a boolean
comparand") |
| `objectql-strategy.ts:461-473`, the `/analytics/sql` echo's refusal of
a `{ $field }` comparison (`INVALID_FILTER` / 400) | 5222, 7598, 3601,
3602, 3650 | "enforces the cross-field rules (declared same-table
columns only, never the tenant-isolation column, one comparison class)
with metadata it owns, so those rules are enforced in one place, next to
the metadata they read" (stage 7's sentence); "an echo that contradicts
execution is worse than no echo, so the echo renders every predicate the
query runs with, or refuses" | 5222's maintainer rulings (2026-08-06,
restated 2026-08-11): same-table columns only, declared-only
enumeration, the tenant-isolation column forbidden on both sides, plus
the comparison class the implementation added. 7598's ruling
(2026-08-12, Q1 = B): native SQL declines a `$field` query 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-scope `WHERE` and
dropped `dateRange`, so it described a different query from the one that
ran; each fix made the echo render what execution applies |
| `objectql-strategy.ts:1479-1484`, the echo's unmapped-operator refusal
(a bare `Error`, deliberately not 400) | 5333 | "an echo without it
describes a WIDER query than the one that ran, and the echo renders
every predicate the query runs with, or refuses" | 5333's landed change:
throw rather than drop an operator the renderer has no arm for, because
the normalizer's vocabulary is closed and an arrival is drift between
two of our own tables. The sentence already said so; it drops its
citation and gains the echo family's one wording |
| `native-sql-strategy.ts:1034-1042`, the cross-field backstop (a bare
`Error`, unreachable by construction) | 7598, 5222 | "answer a wrong row
set silently" drops its citation (the sentence already names the silent
bind 7598 measured); "enforces the cross-field rules (declared
same-table columns only, never the tenant-isolation column, one
comparison class) with metadata it owns, so those rules are enforced in
one place, next to the metadata they read" | as the third row |
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
- `fieldReferenceComparandMessage` and
`fieldReferenceBetweenBoundMessage` reach `read-scope-sql.ts` through a
bare `[read-scope-sql]` prefix (lines 2010 and 2017), so they already
carry stage 7's wording and need nothing here.
- The cross-field rules sentence is now word-for-word the same in
`comparand-shape.ts`, `analytics-service.ts` (both stage 7),
`native-sql-strategy.ts` and `objectql-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 across `packages/` finds no test assertion
and no doc quoting it; the hits are code comments, test comments and
`CHANGELOG.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 every `code` /
`status` assertion 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.
| | before (`1ca1eb09`) | after |
|---|---|---|
| `read-scope-sql.ts` | 5 occurrences, 5 pairs | 0 |
| `strategies/native-sql-strategy.ts` | 2 occurrences, 2 pairs | 0 |
| `strategies/objectql-strategy.ts` | 6 occurrences, 6 pairs | 0 |
| whole ledger | 13 occurrences, 13 pairs, 3 files | 0, 0, 0 |
Nothing else remains in the ledger. `pnpm check:doc-authoring` at 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 by `git checkout HEAD` on the absolute path): putting the
citation back into the unmapped-operator refusal (anchor hit 1 to 0,
blob `eedaeae5` to `9d6ea181`) turned `check:doc-authoring` red with
exactly one growth pair (`objectql-strategy.ts`, id 5333, 0 pinned, 1
measured), as predicted. Restored blob equals `HEAD` (`eedaeae5`), and
`git diff HEAD` is empty.
No gate is added or loosened; `scripts/check-doc-authoring.mjs` is
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:
- With `{}`, 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.
- What already guards it: the leg's `--self-test` asserts 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;
`collectPackageProseFiles` throws 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`:
`patch` for `@objectstack/service-analytics`, `Clause-②: no`. Measured
after the full build: every new sentence is in `dist/index.js` and
`dist/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 `.ts` file, 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. `1ca1eb09` against the head: 3 of 3 SAME. Controls on scratch
copies of `objectql-strategy.ts` from 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
verdict `VERDICT command-exit 0`, at the head `0edca886`:
- Build: `turbo run build --concurrency=2 --filter=./packages/*
--filter=./packages/*/*` (71/71).
- `@objectstack/service-analytics`, `vitest run --maxWorkers=2` in two
shards (`--shard=1/2`, `--shard=2/2`): 88 + 87 = 175 files, 2237 + 1915
= 4152 tests passed, 113 + 140 = 253 skipped.
- `@objectstack/service-analytics` `typecheck` (`tsc --noEmit`): exit 0.
`--listFiles` counts 175 test files in that program, all 175 on disk.
## Gates
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` (no paths) at `0edca886` (5 paths vs merge base `1ca1eb09`,
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)".
- Among them: `check:doc-authoring` (above); `check:issue-citations` and
`check-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-registration` and
`check-empty-changeset` against `origin/main`.
- Outside the derived set, all exit 0 at `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`), plus
`check:durability-log-level` and `check:error-code-casing` (refusal and
diagnostic text moved; no level or code did).
- Not measured locally: the six workflow-valued families
`dispatch-gates` names (`check-issue-citations.mjs --census` with
`GITHUB_TOKEN`, the shard-attestation emits and the test-completeness
reads); they need CI values.
- Lint, narrowed as a measurement: `eslint --no-inline-config --format
json` over the 3 changed `.ts` files at `0edca886`: 3 files linted (none
ignored), 0 errors, 0 warnings. `eslint.config.mjs` enables no
type-aware linting (no `parserOptions.project`, no typed rules; its own
comment at lines 327-328 says so), so this diff cannot move any
untouched file's verdict. Repo-wide `pnpm lint` is CI's.
- `origin/main` moved to `bd707067` (one commit, PR 21563,
`packages/runtime` only) after the branch point. It touches neither
`service-analytics` nor 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:
- Code comments beside the rewritten strings still carry ids (for
example the `[#7598]` docblock on the backstop, the `[#5333]` comment
above the operator refusal, and `comparand-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.
- Test titles and comments that quote card numbers are outside the
ledger too. Carrier: none.
## Round 2: the seen floor (stage 8 claim revision 1, `5967600765`)
The seat answered the open question with A. Commit `3628b4e8c` adds the
explicit seen floor that the header of `scripts/check-doc-authoring.mjs`
prescribes 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_FLOOR` is 600 parsed sources and 40000 strings,
about half of the reading when the floor was pinned (1252 / 86276 at
`0edca886c`). 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.
- A new self-test battery, `cross-package seen floor`, has 8 cases. The
registry floor goes from 16 to 17.
- The header's ratchet note now says the baseline is empty and that the
floor does the stale arm's old blindness-floor job.
- Ablations ran at the committed head, each predicted first. With the
floor raised to 1300 / 90000, the leg showed exactly the two predicted
breach lines. With the predicate disabled, exactly 6 self-test cases
went red. Both restores are byte-identical to `HEAD`.
- There is no changeset change: the script ships in no package (the root
package is private).
- Gates were re-derived at `3628b4e8c`: 77 commands, all exit 0.
- Cross-lane: `scripts/check-doc-authoring.mjs` is `domain:spec`. The
notice is on #6017 (`5967607594`).
---
_Generated by [Claude
Code](https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
The read-scope comparand refusals, the native-SQL cross-field backstop and the two display-SQL echo refusals no longer cite tracker numbers; each one states the decision behind it in words
6
+
7
+
Clause-②: no
8
+
9
+
Some strings the analytics service shows to operators and callers pointed at an issue-tracker number for the reason behind them. The number goes; where the sentence did not already say what was decided, it now does.
10
+
11
+
- The read-scope compiler's undefined-comparand refusal says an undefined comparand is refused rather than read as null, on the SQL drivers and on this door alike. Its refusal of a non-boolean `$null`, `$exists` or `$empty` comparand says a non-boolean comparand for any of the three is refused rather than coerced, on every driver and on this door alike. Both still say they fail closed, and that the producer to fix is whoever built the read scope, never the caller of the query.
12
+
- The native-SQL strategy's cross-field backstop and the `/analytics/sql` echo's refusal of a field-reference comparison say the engine path's driver enforces the cross-field rules (declared same-table columns only, never the tenant-isolation column, one comparison class) with metadata it owns, so those rules are enforced in one place, next to the metadata they read.
13
+
- That echo refusal and the echo's unmapped-operator refusal say the echo renders every predicate the query runs with, or refuses.
14
+
15
+
Text only: no status, error code, field, route or control flow moves. A client or log filter that matches the old text (for example a tracker-number suffix) needs the new spelling.
0 commit comments