Skip to content

fix(service-analytics): runtime strings state each decision in words instead of a tracker number (stage 8) - #21569

Merged
objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20751-services-strings-stage8
Oct 3, 2026
Merged

objectstack-fleet[bot] merged 2 commits into
mainfrom
claude/issue-20751-services-strings-stage8

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

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 [PM seat] domain:spec — 🟢 os-project-manager · session_01T9u38rswFp5Rw8DswRUReJ #6017 (5967607594).

Generated by Claude Code

…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>
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation tooling labels Oct 3, 2026
@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-analytics, touching 5 documentable anchor(s).

⛔ 2 release-owned page(s) name something this change touched. These are read-only:

  • content/docs/releases/v14.mdx (via generateSql (symbol, a method of class ObjectQLStrategy))
  • content/docs/releases/v17/17-5.mdx (via generateSql (symbol, a method of class ObjectQLStrategy))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 6dd99b82c38cd68b51c86241d53c5b7dda670a08 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e927884a1d4343c3f58f04eb9dc1f82eae00ad9d — the merge of head 3628b4e8cb7fe7b8f04232e0fe5f3097455aa8cb into base 6dd99b82c38cd68b51c86241d53c5b7dda670a08, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 6dd99b82c38cd68b51c86241d53c5b7dda670a08 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…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>
@github-actions github-actions Bot added size/m and removed size/s labels Oct 3, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 3, 2026 10:12
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 3, 2026 10:12
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 3, 2026
Merged via the queue into main with commit 44072fc Oct 3, 2026
43 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20751-services-strings-stage8 branch October 3, 2026 10:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants