Repository navigation
fix(objectql, driver-sql, spec): a stored expression that reads an autonumber stores the value computed with the number - #25218
Conversation
…rite for stored expressions that read it Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
…ace table Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
… the search companion; issue numbers per organization; pins Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
… record number carries it Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
…ver SQLite; the inherited member on the Turso remote face Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
…-m25d8w Claude-Session: https://claude.ai/code/session_01LMugqQAMxZFLH5rBUFsFHC Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 5 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 149 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 f013023b84420bebd2f4dc38c20602891d7e71d5 && git checkout f013023b84420bebd2f4dc38c20602891d7e71d5
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 15764cd723e750c01416cfe91b44bbd0a4c28f37 54971471cc12809500c7f7c5c3afa8e2e021892f && git checkout -B drift-repro 15764cd723e750c01416cfe91b44bbd0a4c28f37 && git merge --no-ff 54971471cc12809500c7f7c5c3afa8e2e021892f
node scripts/docs-audit/affected-docs.mjs --json 15764cd723e750c01416cfe91b44bbd0a4c28f37
|
Contract reviewServed-tier: Reviewed 2026-10-22T14:25Z on card #25073, ruling 6335312761 (letter A), the seat's surface call 6340248712, the cross-lane declaration on #6017 (6339724909), the dev report 6342739367, the PR body with its Acceptance notes, the net diff against the merge base ① Derived judgments(a) The ruling's premise and the insert ordering — right, on every path. At this head the insert body's second stored-expression pass ( (b) The (c) The (d) The dev's deviations, each judged:
② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对ACCEPT ·
|
|
Queue note · The merge queue dropped this PR on Generated by Claude Code |
Requested by Bill · project thread
Fixes #25073
Clause-②: yes (widening)
Metadata-protocol: none
Ruling-ref: 6335312761 (letter A). Contract review:
CONTRACT_REVIEW_TIER, run by the engine seat. Cross-lane declaration todomain:specon #6017.What changes
A stored field whose
expressionreads anautonumber(record.case_number + " - " + record.subject, orhas(record.product_code) ? … : record.name) was computed before the number existed, so an insert storednullor thehas()fallback until a later update by id. Now the insert computes it again right after the number is issued, and the row is written once, carrying both.IDataDriver.issueAutonumbers?(object, rows, options?, afterCollision?)(packages/spec/src/contracts/data-driver.ts), the one optional member. Issue mode fills a row's empty autonumber slots in place from the driver's own counter and answers the filled names per row. After-collision mode judges whether acreate()/bulkCreate()error is a unique violation on a number it issued to those row objects; if so it re-seeds each stale counter once, empties the slots drawn from it and answers their names. Otherwise (a value the caller typed, a non-unique error, inside a caller transaction) it answers empty lists and changes nothing.SqlDriver.issueAutonumbersisfillAutoNumberFieldson request (organization stamped first, on a view, ascreate()does) pluscreate()'s own Autonumber counter neither syncs to MAX(existing) per tenant nor re-checks on collision — warm-DB creates 409 in bursts, each failure burning a number (25 retries observed) #5495 / bulkCreate / upsert 仍会逐次烧号:#5495 的重播种只落在 create(),批次语义是独立决策 #6943 re-seed split where the caller must go on (collidingAutoNumberReservations,resyncSequenceToDataMax,autoNumberCounterKey).driver-sqlite-wasmanddriver-tursoinherit it; Turso'sREMOTE_FACE_ANSWERSnames itinherited(the driver-turso remote: inheritedSqlDrivermethods the remote face does not override answer from the placeholder:memory:Knex database —introspectSchema()returns no tables,distinct()a 500,findWithWindowFunctions()a raw SQLite error #20055 compile-time table requires a row for every publicSqlDrivermember).packages/objectql/src/engine.ts). At the pointapplyAutonumbersalready runs (after validation, so a rejected row burns no number):storedExpressionsReading, using formula'sanalyzeRelationshipTraversals, the one member reader);supports.autonumberabsent: driver-memory, driver-mongodb), it computes them again afterapplyAutonumbers;driver.issueAutonumbersper live row, computes them again, and handscreate()/bulkCreate()the filled row: one INSERT, no transaction. Objects with no such field keep the driver's issue-at-write path unchanged;createWithAutonumberResyncis the one single-row retry loop for both issuers: on a driver-issued number the member judges the error and re-seeds, the loop re-issues through the member and computes the readers again, under the sameAUTONUMBER_COLLISION_ATTEMPTS;DUPLICATE_RECORD;create(), and the engine says so once per object aterrorlevel with the remedy;instanceof SqlDriveror driver-name branch anywhere: the member's presence andsupports.autonumberdecide.ObjectQL.registerInsertRecomputeListener(id, fn): called per row after that second computation (and after each re-issue), on the row the driver is handed.plugin-pinyin-searchregisters its existingstampCompanionon it, narrowed to the recomputed fields, so the__searchcompanion is stamped from the value carrying the number.packages/plugins/plugin-pinyin-search/src/companion-projection.tsand this engine method were outside the dispatched surface. The seat decided (A) on the card thread on 2026-10-22, recorded on objectql: a stored field'sexpressionis computed on insert before the autonumber is issued, so an expression that reads the record number stores null (or itshas()fallback) until a later update by id #25073: ruling item 4 binds the same-step recompute, no newHookEventname, an additive plugin-facing registration in theregisterWriteGateProbefamily.Measurements
display_title: null"CASE-00001 - one"has()title"Probe product""PRD-0001 Probe product"null"CASE-00001 - one"__search, concat title asnameField(SQLite and memory)null;$search"zhangwei" 0 hits, "case00001" 0 hits"case00001zhangwei case00001zw"; 1 hit each__search,has()title"chabei cb""prd0001chabei prd0001cb"The
__search"before" column was measured on this branch before the listener existed (the number in the title, the companion still stamped inbeforeInsertfrom the pre-number value).Turso remote face, with the inherited member (
turso-remote-autonumber-issue-ahead.test.ts, a real SQLite behind the@libsql/clientinterface): the member is not redeclared onTursoDriver; it issues from the counter the remotecreate()draws from (last_value1 → 2,create()of the issued row leaves it at 2); after rows 2..30 land by a bypass path, the collision call re-seeds the remote counter to 30 and empties the slot, and the next issue writesCASE-00031; a duplicate on a typed value answers nothing. No fork.Pins and ablations
driver-sql/src/sql-driver-autonumber-issue-ahead.test.tscreate(), per organization from options, typed value untouched, batch alignment); after collision (re-seed + empty, typed duplicate, non-unique error, caller transaction, batch per counter)objectql/src/engine-autonumber-stored-expression.test.tsbulkCreate), partial mode,has(), forced collision, listener per row and per re-issue; controls (expression reading no number, update by id); driver-only: member not asked for an object with no reader, batch collision re-seeds and does not re-issue, exhaustionERR_AUTONUMBER_COLLISION, no-membererroronceruntime/src/autonumber-stored-expression.integration.test.tsSqlDriveron better-sqlite3: single (onecreate, one number drawn), batch (onebulkCreate), partial (dead row burns none),has(), forced single collision, batch collision (nothing lands, re-seed, next past the store),afterInsertsees the value, inside a caller transaction; controls (object with no reader keeps issue-at-write, update by id)plugin-pinyin-search/src/companion-autonumber-recompute.test.tshas()companions carry the number,$searchfinds by pinyin and by number, batch; controls (companion source not recomputed, object with no companion column)driver-turso/src/turso-remote-autonumber-issue-ahead.test.ts(+turso-remote-inherited-members.test.ts)Ablations, each through
scripts/ablation-replace.mjs(mutation proved on disk, restored to HEAD):recomputeAutonumberReadersreturns at entry), objectql suite: 13 red (every autonumber pin on both issuers, plus the batch-collision pin's next title), 7 green (the 4 controls, member-not-asked, exhaustion, no-member).distfor the runtime suite (ablation-dist-preflight.mjssaw the marker in 4 built files): 8 red, 2 green (the two controls).companion-projection.ts, plugin suite: 6 red (the companion pins, both issuers), 4 green (the controls).Acceptance notes
createWithAutonumberResync, re-issuing through the member, for every driver. The batch path keeps the engine's existing "re-seed, never re-issue" policy for driver-issued numbers too, because the engine cannot know whether a driver'sbulkCreateis all-or-nothing (Turso's remotebulkCreatewrites row by row), and re-issuing a partly landed batch duplicates rows. And the engine issues through the member only for objects with a reader: other objects keepSqlDriver's own issue-at-write and its in-driver bulkCreate / upsert 仍会逐次烧号:#5495 的重播种只落在 create(),批次语义是独立决策 #6943 batch heal, which the engine could not take over without re-issuing batches. Reviewer's call if the ruling meant more.ERR_AUTONUMBER_COLLISIONafter the engine's 3 attempts, where the driver's own loop used to retry and the engine then raisedDUPLICATE_RECORD. Named in the changeset.null/ the fallback.engine.upsert→driver.upsertstill issues inside the driver.bulkCreatepartial application is pre-existing (row by row) and unchanged.create()retried nothing there (P4 unchanged): the caller seesDUPLICATE_RECORD.error.patch, notminor: AGENTS.md Post-Task step 3 sets the level from theMetadata-protocolkind alone (none⇒patch), the precedent being IEmailService declares no "does this transport deliver mail" fact, so a consumer cannot tell plugin-email's default log transport from a live one (blocks #25060, ruling 1B's precondition) #25065's optionalIEmailServicemember. The dispatch saidminorwhere the contract widens.Gates
Local, at
800779bd(the last commit before mergingmain):dispatch-gates --commandsderived 98 families; all 98 run, exit codes recorded before any pipe;--ran: 98 run, 0 NOT-MEASURED, 0 UNRUN.check-engine-split-ratio --days 90andcheck-plugin-teardown-shape --self-testrefused on the shallow clone (history deepened, then 0);check:dual-build-cjs-loadsrefused without a whole-workspace build (0 after one);check:query-options-erasurecaught oneas anyon afindquery in the new plugin test (typed in800779bd, then 0).pnpm --filter @objectstack/spec check:generated: 0, all 14 artifacts current.check:driver-conformance: 0 at the base and at the head, 55 covered cells and 0 in the DEBT ledger both times.typecheck: 0 for spec, objectql, driver-sql, driver-turso, plugin-pinyin-search, runtime.The rerun on the merged head and the dogfood battery are reported in the
os-dev-reporton #25073.Generated by Claude Code