fix(spec): conformance-case notes and names state each decision in words instead of a tracker number (stage 6) - #21593
Conversation
…rds instead of a tracker number (stage 6) The class (c) texts of the spec lane's runtime-string burn-down: 58 case notes and names across the eight shared conformance modules, each now saying what the cited decision was, or dropping a citation its sentence already explained. The lint test that selected the four empty-combinator cases by a tracker substring now selects them by a phrase of the rewritten notes, and the service-analytics test that pinned the infix icontains case name pins the new name verbatim. Text only. Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 138 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 35fc4d91777b2e146bd8e6ccc53de80ed6d704a7 && git checkout 35fc4d91777b2e146bd8e6ccc53de80ed6d704a7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 901e7cf13abd61afb4990ebc1e3b9cd36cf9b33d 1427993cc332321d024701e843f92cae88b05414 && git checkout -B drift-repro 901e7cf13abd61afb4990ebc1e3b9cd36cf9b33d && git merge --no-ff 1427993cc332321d024701e843f92cae88b05414
node scripts/docs-audit/affected-docs.mjs --json 901e7cf13abd61afb4990ebc1e3b9cd36cf9b33d
|
Contract reviewServed-tier: ① Derived judgmentsInputs. Read on GitHub through REST: card #20749 (body and all 21 comments, the triage Text only; the accept set is unchanged. The GitHub net diff is byte-identical to Public surface. The eight tables are exported data values ( The selector seam (A3) is right. Applying the old and the new selector as the test applies them, by text extraction over The name pin (A4) is right. The 58 rewritten notes and names, each against the cited decision. All 58 state the cited decision truly or drop a citation whose sentence already stated it. Three are drop-only (the #7378 rows, whose sentences already carried the ruling in words); 55 gain words naming the decision.
Census and boundary. The stage-5 landing ( ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
ACCEPT — PR #21593 @
|
Part of #20749
Clause-②: no
Stage 6 of the
domain:speclane's share of the runtime-string burn-down (ruling5902360492, form D): class (c), the case notes and names of the eight shared conformance modules inpackages/spec/src, with the two test seams that bind their text. Every rewritten note or name now states in words what the cited decision was, or drops a citation its sentence already explained. Text only.What changed
contracts/metadata-service-roundtrip-conformance.tsand, indata/,aggregation-conformance.ts,filter-comparand-type-conformance.ts,filter-logic-conformance.ts,filter-text-conformance.ts,filter-text-operator-declared-type.ts,temporal-conformance.ts,value-roundtrip-conformance.ts. Fifty-two arenote/whytexts and six are case names.packages/lint/src/validate-empty-combinators.test.ts:275): selects the same four empty-combinator cases by a phrase the rewritten notes carry instead of#5322(A3 below).packages/services/service-analytics/src/__tests__/icontains-dialect-sql.test.ts:369): pins the renamed infixicontainscase verbatim, the same strength (A4 below).@objectstack/specpatch changeset,Clause-②: no: the conformance tables ship in the package'sdist(measured: the new note text is indist/data/index.mjs; the two test files ship nowhere, since@objectstack/lintand@objectstack/service-analyticspublishdistonly and neither dist carries the seam text, with the package's own symbols as the positive control).check:generatedgreen; the tables are values behind annotated types, so the API surface is unchanged).Census at the base (A1)
Stage 3's instrument (
census.cjs, byte-identical copy, md5e5fe562290fcd1ac392bd560cb86d861) at basecc645f2385: class (c) is 58 messages / 68 ids in 8 files, 40 distinct cards, exactly stage 3's count (the[c-SELECTOR]4 / 7 and[c-NAMEPIN]1 / 1 rows included). Under the ~60-card bar, so one stage. Lines are the base's.packages/spec/src)contracts/metadata-service-roundtrip-conformance.tswhyid; the text is a reader's note in the shared tabledata/aggregation-conformance.tsnotedata/filter-comparand-type-conformance.tsname, 2notefilter-save-door-face-parity.test.ts; notes are assertion messagesdata/filter-logic-conformance.tsnotevalidate-empty-combinators.test.ts:275picks the four#5322notesdata/filter-text-conformance.tsname, 5noteicontains-dialect-sql.test.ts:369; notes are assertion messagesdata/filter-text-operator-declared-type.tsnoteengine-text-operator-declared-type-door.test.tsdata/temporal-conformance.tswhy) · 270:#3777 · 393:#3773 · 448/457:#1874 · 549:#3994 (casenote)why, 5notewhyis seeded as DATA into each harness'swhystring column (never filtered on); a casenoteis an assertion messagedata/value-roundtrip-conformance.tsnoteround-trips NAME (NOTE)in five driver suitesNo non-test code reads any of these texts (every
.note/.whyread outside a test is another table's:RETIRED_FILTER_OPERATORS, the driver-conformance ledger).Delivered (A2): each site, the decision as read, the new words
Each cited card was read with its body and every comment; the decision column names the record (comment id, or the landing record where the card carries none). Line = the base line of the edit anchor. "Old → new" shows only the words that changed.
contracts/metadata-service-roundtrip-conformance.ts:194contracts/metadata-service-roundtrip-conformance.ts:202contracts/metadata-service-roundtrip-conformance.ts:280contracts/metadata-service-roundtrip-conformance.ts:296contracts/metadata-service-roundtrip-conformance.ts:304data/aggregation-conformance.ts:342data/aggregation-conformance.ts:398data/aggregation-conformance.ts:407data/aggregation-conformance.ts:417data/aggregation-conformance.ts:535data/aggregation-conformance.ts:550data/aggregation-conformance.ts:561data/aggregation-conformance.ts:577aliasnames the projected group column on every face — the SQL faces that ignored it were made to honour it. Tdata/aggregation-conformance.ts:596aliasrule. Its twin abovedata/filter-comparand-type-conformance.ts:153data/filter-comparand-type-conformance.ts:160data/filter-comparand-type-conformance.ts:178data/filter-comparand-type-conformance.ts:191data/filter-comparand-type-conformance.ts:272data/filter-comparand-type-conformance.ts:282data/filter-comparand-type-conformance.ts:289data/filter-logic-conformance.ts:339data/filter-logic-conformance.ts:362data/filter-logic-conformance.ts:420data/filter-logic-conformance.ts:426data/filter-logic-conformance.ts:432a = x— a silently NARROWED scope (#5297) → Ruled: every face reduces an empty combinator to its boolean identity, so{}is a TRUE disjunct. Collapsing to the surviving branches instead compilesa = x— a silently NARROWED scope, the answer the RLS read-scope compiler gave until it was aligneddata/filter-logic-conformance.ts:438{}is FALSE. Emitting nothing for it runs the query UNSCOPED — on an RLS lowering that is a permission bypass, which the read-scope compiler was until it compiled this to an always-false clausedata/filter-logic-conformance.ts:470data/filter-logic-conformance.ts:476$notitselfdata/filter-logic-conformance.ts:503data/filter-logic-conformance.ts:503data/filter-logic-conformance.ts:509data/filter-logic-conformance.ts:509data/filter-logic-conformance.ts:539$existsmeans HAS A VALUE, never key-presence → Ruled:$existsmeans HAS A VALUE, never key-presence, because SQL cannot tell a missing key from a stored nulldata/filter-logic-conformance.ts:561$emptyby the field's declared type, and null is empty under every type's armdata/filter-logic-conformance.ts:567$empty: falseis the exact complement by rulingdata/filter-logic-conformance.ts:573$emptyspells its NULL case out, so it → The ruled$emptyarms spell their NULL case out, so$emptydata/filter-logic-conformance.ts:579data/filter-logic-conformance.ts:595$empty→ A face that lowers$emptyto the field's declared-type armdata/filter-text-conformance.ts:300data/filter-text-conformance.ts:309icontainsjoined the view and infix vocabulariesdata/filter-text-conformance.ts:349iflag, which #6682 took off → match: the$containsfamily is case-sensitive on every backend, by ruling. SQLite's LIKE folds ASCII — the defect the SQL family replaced with GLOB on the SQLite dialects; a JS backend's equivalent is a RegExp carrying theiflag, since taken off driver-memory and driver-mongodb,data/filter-text-conformance.ts:428$regexwas retired over, by ruling, with a loud refusal naming$icontainsdata/filter-text-conformance.ts:436$regexbefore any backend enrolled this case (re-verified 2026-08 by executing each facedata/filter-text-conformance.ts:452{ field: {} }over → for which a field with zero operators,{ field: {} }, is refused by rulingdata/filter-text-operator-declared-type.ts:392returnType→ unmaterializable-field door refuses EVERY formula filter with INVALID_FIELD 400 whatever thereturnType(no driver stores a formula column)data/filter-text-operator-declared-type.ts:553data/filter-text-operator-declared-type.ts:554data/temporal-conformance.ts:211data/temporal-conformance.ts:215data/temporal-conformance.ts:216data/temporal-conformance.ts:220data/temporal-conformance.ts:270data/temporal-conformance.ts:393data/temporal-conformance.ts:448date == todaysilently matched nothing while dates were stored as instants. E →date == todaysilently matched nothing while dates were stored as instants; ADR-0053 stores a date as its calendar day, so edata/temporal-conformance.ts:457data/temporal-conformance.ts:549Field.timetook one canonicalHH:MM:SS[.fff]text formdata/value-roundtrip-conformance.ts:213data/value-roundtrip-conformance.ts:225data/value-roundtrip-conformance.ts:226lint: validate-empty-combinators.test.ts:275analytics: icontains-dialect-sql.test.ts:369The selector seam (A3)
The filter over
FILTER_LOGIC_CASESnow tests(c.note ?? '').includes('every face reduces an empty combinator to its boolean identity')where it tested.includes('#5322'). The phrase is the #5322 ruling in words, and the four rewritten notes open with it. Measured with an AST extractor over the table (nameandnoteof all 36 cases, then the selector applied exactly as the test applies it):cc645f2385with#5322→ 4 of 36:$not of {} is FALSE — NOT TRUE,a {} branch is a TRUE disjunct and absorbs its $or,empty $and is TRUE — the AND identity,empty $or is FALSE — the OR identity.Ruledselects 8, the four plus$exists true selects exactly the valued rows,$ne …,$nin …and$notContains returns the rows with no value; none of those four is picked by the phrase.$ornote leaves 3, which the test's own guard (toEqualon the four sorted names,toBe(4)) turns red.The test asserts exactly what it asserted before (the names pin, the count, and the row-set wording per case); only the selector moved.
validate-empty-combinators.test.tsran green: 21 of 21, the three identity cases included.The name pin (A4)
'icontains (the infix/view spelling, #8934) lowers to $icontains — % stays a LITERAL through that door too'becomes'icontains (the infix/view spelling, ruled never an alias of ilike) lowers to $icontains — % stays a LITERAL through that door too'inFILTER_TEXT_CASES, and thetoEqualpin inicontains-dialect-sql.test.ts:369moves to the new string verbatim. No other consumer keys on that name (searched the whole tree for the old name and its fragments: the source and the pin only); the other consumers use it as a test title. The file ran green: 16 of 16. The five renamedFILTER_COMPARAND_TYPE_CASESnames have no pin anywhere; they are test titles only.Text only (A5)
Stage 3's
skeleton.cjs(one line changed: the TypeScript load path to this worktree), TypeScript 6.0.3: leg 1 an AST skeleton with every string's text masked (a+chain's adjacent string operands read as one string), leg 2 the text of every string group, each changed group required to carry a tracker id before and none after, every other group byte-identical. Base copies vs the committed files, 10 of 10 SAME on both legs, exit 0:metadata-service-roundtrip-conformance.tsaggregation-conformance.tsfilter-comparand-type-conformance.tsfilter-logic-conformance.tsfilter-text-conformance.tsfilter-text-operator-declared-type.tstemporal-conformance.tsvalue-roundtrip-conformance.tsvalidate-empty-combinators.test.tsicontains-dialect-sql.test.tsParse diagnostics 0 / 0 throughout. 58 changed groups in the eight modules, one per seam. No case's filter, input, expected rows, verdict, code, operator, dialect, order or count moves. Edits were applied by a script whose 62 anchors each had to hit exactly once before any write, and were read back from disk after it (each anchor gone, each replacement present once). Census after the edit: class (c) 0 / 0; non-test 118 → 60 messages, 297 → 229 ids; test strings unchanged (1804 / 1920).
Pins, titles and quotes (A6)
VALUE_ROUNDTRIP_CASESnotes (round-trips NAME (NOTE)). No skip list, ledger or snapshot names any of those titles.content/docs/**page and noskills/**file quotes a changed note or name.TEMPORAL_ROWS[].whyis seeded into astringcolumn (avarcharon Postgres and MySQL), so the four rewrittenwhytexts stay short (164 characters at most).Gates
Head
1427993cc3(the merge oforigin/main901e7cf13a; that merge touched no file underpackages/spec,packages/lintorpackages/services/service-analytics, 0 diff lines there).node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 11 paths vs merge base901e7cf13, 153 changed lines; 88 derived commands, 88 exit 0 on1427993cc3, each exit code written from$?before any pipe;--ran: "✓ dispatch-gates --ran: 88 derived famil(ies) accounted for — 88 run, 0 NOT-MEASURED".pnpm --filter @objectstack/spec buildexit 0 (the dependency closure is empty: no workspace dependencies);check:generated: "✓ All 15 generated artifacts are up to date".pnpm --filter @objectstack/spec test: "Test Files 606 passed (606) / Tests 17952 passed | 1 todo (17953)";typecheckexit 0 ("check:test-typecheck: OK — … 52 file(s) / 246 error(s) / 135 pinned signature(s) held").pnpm --filter @objectstack/lint test: "Test Files 119 passed (119) / Tests 5620 passed (5620)";typecheckexit 0.pnpm --filter @objectstack/service-analytics test: "Test Files 175 passed (175) / Tests 4152 passed | 253 skipped (4405)";typecheckexit 0;icontains-dialect-sql.test.tsalone 16 of 16.pnpm check:doc-authoring(self-test, then the run): "✓ doc authoring guard: 17337 customer-facing string(s) across 1251 spec sources clean" and "sibling-package prose ids hold the baseline — 0 pinned site(s) … no growth, no burn-down unrecorded".pnpm check:nul-bytes: "check-nul-bytes: OK (scanned 9947 text file(s) …; no raw ASCII control bytes)".check-changeset-no-major.mjs,check-empty-changeset.mjs,check-adr-0087-registration.mjs(each--base origin/main) exit 0; with this body as the--eventpayload, see the report.eslint --no-inline-config --format jsonover the 10 changed TS files reads 10 files, 0 errors, 0 warnings; the population is ESLint's own (calculateConfigForFilereturns a config for each,isPathIgnoredfalse); invariance:eslint.config.mjsenables no type-aware linting (parserOptions.project/projectServicenull for all 10), so a string-text edit cannot move an untouched file's verdict. Repo-widepnpm lintis CI's.test:repowas not run locally: none of its 51 files names a changed module or table.Acceptance notes
MetadataFacade.register('object', …)writes where neither of its own object reads look #6725 andavgover a boolean field returnsnullon driver-memory and a number on sqlite — same rows, same dataset, same executor #11065; the same numbers in objectui are unrelated PRs). Their decisions were read from the shipped landing records instead: the objectql and spec CHANGELOG entries1507ba3/bbee302(MetadataFacade.register('object')wrote into a map none of its own object reads consulted) and the driver-memory entry2095040(a boolean aggregand counts as 1 or 0, as on every SQL face). Neither decision read as unclear.AGGREGATION_ROWShas no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152's 2026-08-28 ruling (option A, booleans aggregate as numbers, superseding [Decision]min/maxover a boolean aggregand: pin the cross-driver JSON answer —0/1(SQL) vsfalse/true(both in-memory faces) #11249) is recorded in changesetf6fa22cand matches the source comment above those cases; Field.time repeats the #3912 pattern: writes unnormalised, repaired only on read — window filters and ORDER BY are silently wrong on SQLite #3994's in ADR-0053's D-C addendum.(#5322/#5659),[#16028]) and every other test string; code comments (including the section comments beside these cases); class (f) inapi/error-code-ledger.zod.tsandkernel/public-auth-features.ts;migrations/registry.ts..claude/**,skills/**, ADR, NORTH-STAR or AGENTS.md path.Line budget
153 changed lines (+90 / -63) over 11 files vs merge base
901e7cf13(dispatch-gates), under the 5000 human-merge threshold: the eight modules, the two test files (one line each) and the changeset. No generated file, no governed surface.Generated by Claude Code