Repository navigation
De-fallback driver-sql's autonumber format — drop the two hardcoded || '{0000}' sites for the declared default (#6555 half 3/3) #7263
Description
Activity
Triage:
domain:drivers+pm:blocked.- Ruling executed, not a decision card: the maintainer's 2026-08-08 route-3 ruling on 无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成
{0000}发出0001,引擎兜底路径发出裸1—— 同一份元数据换驱动号形不同 #6555 is quoted verbatim in the body and names the drivers half. Execution sub-cards of a made ruling enter the work queue, not the decision box; this one enters blocked because its contract-first upstream has not landed. - Blocked-by: the spec half rides PR feat(spec): declare
{0000}as the contract default for format-less autonumber fields (#6555) #7265 (branchclaude/issue-6555-autonumber-default-format), open at triage time (04:2xZ). The body'sBlocked-by:line names 无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成{0000}发出0001,引擎兜底路径发出裸1—— 同一份元数据换驱动号形不同 #6555; the unlock artifact is that PR's merge. On unlock, re-verify both sites at the merged ref before dispatch. - Landing verified @
origin/main1da1f32: both hardcoded fallbacks live —packages/drivers/driver-sql/src/sql-driver.ts:5353and:5435(const fmt = rawFmt || '{0000}';) ⇒domain:drivers, per the ruling and the domain table. - Dedup: repo-scoped
autonumbersearch returns only the 无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成{0000}发出0001,引擎兜底路径发出裸1—— 同一份元数据换驱动号形不同 #6555 family (无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成{0000}发出0001,引擎兜底路径发出裸1—— 同一份元数据换驱动号形不同 #6555 = dispatched spec half, 决策:自增号「计数器反解」规则是否作为 renderAutonumber 的逆进 packages/spec(PR #6553 的 open question,维护者裁) #6560 = separate decision card, De-fallback the engine's autonumber render path — read the declared{0000}default instead of parsing the empty string (#6555 half 2/3) #7262 = engine half). The split is the contract-first structure itself; one dispatch entry per half. target:v17not applied: de-fallback/consistency work; the parent 无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成{0000}发出0001,引擎兜底路径发出裸1—— 同一份元数据换驱动号形不同 #6555 carries no target label, and this half adds no independent blocking class (re-ran the four-class test: no shipped-surface hit, no contract lie, no stored-data shape change, no apology line).
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- Ruling executed, not a decision card: the maintainer's 2026-08-08 route-3 ruling on 无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成
Claim: PM loop, drivers lane. Unblocked —
pm:blocked→pm:queue+pm:dispatched.Session:
session_01Hg9Pkg5nDedCRihRsdeCdX
Branch:claude/issue-7263-autonumber-format-defallback
Worktree:objectstack-issue-7263(cloud session — own container, Opus)Unlock verified, not assumed. Triage's instruction here was "On unlock, re-verify both sites at the merged ref before dispatch" — done:
- PR feat(spec): declare
{0000}as the contract default for format-less autonumber fields (#6555) #7265 merged as88154bee1("feat(spec): declare{0000}as the contract default for format-less autonumber (无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成{0000}发出0001,引擎兜底路径发出裸1—— 同一份元数据换驱动号形不同 #6555) (feat(spec): declare{0000}as the contract default for format-less autonumber fields (#6555) #7265)"). resolveAutonumberFormatis live atpackages/spec/src/data/autonumber-format.ts:196and is public surface —packages/spec/api-surface/data.json:665lists it, andexport-origins/data.jsonmaps it tosrc/data/autonumber-format.ts.- Both target sites re-measured at the merged ref:
sql-driver.ts:5359andsql-driver.ts:5441.⚠️ These anchors have now drifted four times (:4322/:4404→:4330/:4412→:5353/:5435→:5359/:5441) — the dispatch tells the dev to locate them by content, not by line number. @objectstack/spec/datais already imported atsql-driver.ts:11(parseAutonumberFormat,renderAutonumber, …) and@objectstack/specis already adependenciesentry. This adds one symbol to an existing import — zero new dependencies.
"Behaviour-neutral by construction" is verified, not taken on faith. The resolver's precedence (canonical
autonumberFormat→formatshorthand →DEFAULT_AUTONUMBER_FORMAT) and its non-empty-string test are character-for-character what the two driver sites do, and its docstring says so deliberately: "A key holding anything other than a NON-EMPTY string counts as undeclared — which is deliberately the SQL driver's long-standing truthiness rule, not the engine's??." That covers theformat: ''and non-string cases the card asks to prove, not just the happy path.One nuance the card raises, bounded explicitly.
sql-driver-autonumber-suffix.test.tspins'0011'and its comment (:167-172) explains it as "this driver substitutes{0000}for a missing format (seeinitObjects)". After this card that sentence is stale and may be re-pointed at the declared default — but⚠️ only the prose. Theexpect(r.rec_no).toBe('0011')assertion, its inputs, and every other case must not move. And note the second half of that comment — the engine emitting bare11— stays true until #7262 lands, so do not delete it as though this card fixed the divergence.⛔ The engine half #7262 is engine-core's. Do not touch
applyAutonumbers. #6555 stays open until both land.
Generated by Claude Code
- PR feat(spec): declare
os-dev report
Landed as draft PR #7334 (branch
claude/issue-7263-autonumber-format-defallback, commit088a1b8, base06be54ec3). The card's premise held: the substitution is exact, and I found no input where old and new disagree in a reachable way.What changed and where
{0000}is read from the contract now instead of being written down in the driver. Located by content (git grep -n "rawFmt"), never by line number.Site Final line import — resolveAutonumberFormatadded to the existing@objectstack/spec/dataimportsql-driver.ts:11initObjectssql-driver.ts:5356external-object registration path sql-driver.ts:5433Both are now
const fmt = resolveAutonumberFormat(field);. No dependency added —@objectstack/specwas already independenciesand the subpath already imported. Site 2's#1603comment was removed as redundant; the resolver's docstring carries it.git grep "rawFmt\|'{0000}'" packages/drivers/driver-sql/src/sql-driver.tsnow returns nothing — no residual fallback.Exactness proof
A differential harness ran the removed expressions and
resolveAutonumberFormatover 484 field documents — both spellings across 22 value shapes: absent key,undefined,null,'', non-empty strings,'0', whitespace, numbers,NaN, booleans, arrays, objects, boxedString,Symbol, function,BigInt, null-prototype object.484/484 returned the identical string.
DEFAULT_AUTONUMBER_FORMAT === '{0000}'exactly. The edge cases the card named, measured rather than assumed:Input old new format: '''{0000}''{0000}'autonumberFormat: '''{0000}''{0000}'both '''{0000}''{0000}'autonumberFormat: ''+format: 'SO-{000}''SO-{000}''SO-{000}'autonumberFormat: 42(non-string)'{0000}''{0000}'format: 42(non-string)'{0000}''{0000}'no keys at all '{0000}''{0000}'One shape does differ, and it is unreachable. A
null/undefinedfield threwTypeErrorunder the old expression and returns the default underresolveAutonumberFormat(which guards withfield?.). It cannot be reached: both sites evaluateconst type = field.type || 'string'several branches earlier, which throws on a null field before the autonumber branch is entered. Not a behaviour change any caller can observe — flagging it only because "no input where they disagree" would otherwise be an overstatement.(Theoretical footnote: the old expression read each key up to three times, the resolver reads it once, so a getter returning different values per read could diverge. Metadata documents are parsed JSON; no getters exist on this path.)
Test results
Gate Result Eight autonumber suites — baseline, pre-edit 8 files / 64 tests passed Eight autonumber suites — post-edit 8 files / 64 tests passed autonumber-seed-cross-side-parity.integration.test.ts— pre & post1 file / 5 tests passed Full driver-sql package suite 81 passed / 4 skipped (85 files), 1156 passed / 48 skipped (1204 tests) typecheck(@objectstack/driver-sql)pass eslinttouched files / full repo (eslint . --no-inline-config)pass / pass (exit 0) check:empty-changeset/check:adr-0087-registration/check-changeset-no-majorpass / pass / pass The eight suites, confirmed present (see "card corrections" below on the glob):
batch-resync,persistence,resync,suffix,tokens,tx,autonumber,upsert-autonumber-immutable.No test weakened
$ git diff --stat HEAD~1 -- '*.test.ts' .../driver-sql/src/sql-driver-autonumber-suffix.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)One test file, the bounded prose exception only. Verified mechanically that every changed line in it is a comment —
git diff -U0 | grep '^[+-]' | grep -v '^[+-]\s*//'returns empty.expect(r.rec_no).toBe('0011'), its inputs, and every other case are byte-identical. Nothing deleted, loosened, retried or skipped anywhere.The comment at
:167-173now points at the declared default, and keeps its second half true as instructed — it still says the engine emits the bare11, now explicitly "until #7262 lands the other half" rather than reading as though this card fixed the divergence.Reverse verification
Predictions stated before measuring; three faults injected at both call sites.
Fault Predicted Measured A — default dropped (undeclared → '')1 red: suffix, '11'vs'0011'✅ exactly that. 1 failed / 63 passed; AssertionError: expected '11' to be '0011'B — formatshorthand branch lostbroad red, ~7 of 8 files ⚠️ 8 of 8 files, 45 failed / 19 passedC — truthiness rule → bare typeof(the engine's??)no red — no suite declares format: ''✅ 8 files / 64 tests still green Fault B came out redder than predicted, and the fixture was at fault, not the code. My survey grep (
grep -c "[^r]format:") reported 0 occurrences insuffix.test.ts, so I predicted it would survive. It misses the ES6 shorthand — the helper writes{ type: 'autonumber', format }, notformat:. All eight suites exercise the shorthand spelling. Corrected before drawing any conclusion from it.Fault C is the load-bearing result. The subtlest wrong substitution — differing from the driver's rule only on the empty string — leaves all 64 tests green. The suites cannot see that case. That is why the exactness claim rests on the 484-document harness and not on the suite, and it is the direct answer to the card's "prove it, don't assert it".
CI — per job
All green on
088a1b8: 23 success, 2 skipped, 0 failed.ADR maintainer approval·Auto Label·Build Core·Check Changeset·Check Documentation Links·Check PR Size·Console Pin Freshness·Dogfood Regression Gate(+ shards 1/3, 2/3, 3/3) ·Dogfood Verify CLI·ESLint·Flag docs affected by code changes·No other open PR may claim the same issue·Temporal Conformance (live PG + MySQL)·Test Core(+ shards 1/3, 2/3, 3/3) ·TypeScript Type Check·Vercel Preview Comments·filter— all success. Skipped:Build Docs,Console Pin Gate(no docs/console changes in the diff).Changeset
.changeset/autonumber-format-defallback-driver-sql.md,"@objectstack/driver-sql": patch, carrying the ruling's compatibility note verbatim:choosing {0000} keeps stored driver-sql data undisturbed; engine-fallback deployments flip from bare 1 to 0001 for newly issued numbers. Counter continuity itself is unaffected (#6468 pinned it).
It also records the 484-document differential result and notes that #6555's divergence persists until #7262 lands.
What the card got wrong / worth knowing
- The glob in the acceptance bar does not match the eight files it names.
packages/drivers/driver-sql/src/sql-driver-autonumber-*.test.tsmatches only six;sql-driver-autonumber.test.tsandsql-driver-upsert-autonumber-immutable.test.tsneed*autonumber*.test.ts. The eight names are all correct and all exist — a copy-paste of the glob alone would have silently run six suites and reported the bar met. - The anchors did not drift a fifth time. The card said to assume they had moved again; at
06be54ec3they were still exactly where the PM measured them (:5359/:5441for theconst fmtline; the four-line blocks start at:5356/:5438). Located by content anyway — the instruction cost nothing and remains the right one. - "Four lines" vs the issue body's "six lines". Both are right about different sites: site 1 is four lines, site 2 is those four plus a two-line
#1603comment. Not a defect, just worth not tripping over. - Coverage finding for the queue, not for this card. Fault A produced exactly one red — a single assertion in all eight suites covers the format-less default path — and no suite declares
format: ''at all. The de-fallback is safe because the resolver is character-identical, not because the suite would have caught a regression. When De-fallback the engine's autonumber render path — read the declared{0000}default instead of parsing the empty string (#6555 half 2/3) #7262 changes the engine side, that thinness is worth knowing: the engine half is the one that actually moves behaviour, and this suite would not see an empty-string mistake there either.
⛔ Out of scope respected:
applyAutonumbersandpackages/objectqluntouched;content/docs/releases/untouched; issue not re-claimed or re-assigned.
Generated by Claude Code
- The glob in the acceptance bar does not match the eight files it names.
PM step-7 gate: ACCEPTED. PR #7334 @
088a1b863— marking ready and enabling auto-merge.Verified independently off the branch and the code, not off the report:
Claim Result No residual fallback ✅ git grep "rawFmt|'{0000}'"onsql-driver.ts@088a1b863returns nothingBoth sites substituted ✅ :5356and:5433, eachconst fmt = resolveAutonumberFormat(field);Zero new dependencies ✅ symbol added to the @objectstack/spec/dataimport already on:11Only one test file touched, comment-only ✅ every changed line in suffix.test.tsis a comment;expect(r.rec_no).toBe('0011'), its inputs and every other case byte-identicalComment kept its still-true half ✅ retains "the engine's fallback still emits the bare 11", now scoped "until #7262 lands the other half"Changeset carries the note verbatim ✅ patch→@objectstack/driver-sqlCI on this head ✅ 25 checks completed — 23 success, 2skipped(Build Docs,Console Pin Gate, both correctly paths-filtered for adriver-sql/src+.changeset/diff). Nofailure, noin_progress.Temporal Conformance (live PG + MySQL)green, so this ran against live Postgres and MySQL, not just SQLiteThe exactness claim I re-derived rather than accepted. Before reading this report I built my own differential matrix from the authoritative sources — 484 cases, 22 value shapes across both spellings — and got 0 mismatches, with a positive control that fires 22 mismatches on a deliberately broken variant so the harness is demonstrably live. Same number, same verdict, two independent harnesses. I had also separately concluded the
field?.divergence is unreachable, by the same route: both loops evaluateconst type = field.type || 'string'several branches earlier. The getter footnote is a fair catch and correctly dismissed — these are parsed JSON documents.The glob defect is mine and the correction is right. I verified it:
sql-driver-autonumber-*.test.tsmatches 6 of the 8 files I named — it missessql-driver-autonumber.test.ts(no hyphen afterautonumber) andsql-driver-upsert-autonumber-immutable.test.ts(different prefix).*autonumber*.test.tsmatches all eight. A dev that ran my glob and reported "all eight green" would have run six and been wrong through no fault of its own. That flaw is in this card's Suggested gates section too, which this seat wrote — I'm correcting it here rather than leaving it to bite the next reader. Fourth time a dev has falsified one of my dispatch facts and been right.Fault C is the result that earns this accept, and it deserves to be read carefully. The subtlest wrong substitution — one differing from the driver's rule only on the empty string — leaves all 64 tests green. I confirmed the mechanism:
git grep "format: ''\|autonumberFormat: ''"across everydriver-sqltest file returns nothing. No suite declares an empty-string format, so no suite can see that case. This is exactly why the card asked for proof rather than assertion, and it means the accept rests on the 484-document harness, not on the green suite. A reviewer who had only seen "64/64 green" would have had no evidence at all for the property that actually matters.Credit where the discipline showed: fault B came out redder than predicted and the report says the fixture was wrong, not the code — the survey grep
grep -c "[^r]format:"missed the ES6 shorthand{ type: 'autonumber', format }. That is the dispatch's "if a predicted red doesn't appear, suspect your fixture" rule applied in the mirror direction, caught and corrected before any conclusion was drawn from it. Reverse verification that only ever confirms predictions isn't doing any work.Two notes carried forward rather than acted on here:
- The coverage thinness above is a live hazard for De-fallback the engine's autonumber render path — read the declared
{0000}default instead of parsing the empty string (#6555 half 2/3) #7262, not for this card — the engine half is the one that actually moves behaviour, and this suite would not catch an empty-string mistake there either. I'm passing that to the engine lane on its own card rather than folding it in. - The anchors did not drift a fifth time; the "assume they moved" instruction cost nothing and stays.
⛔
applyAutonumbers/packages/objectqluntouched,content/docs/releases/untouched, no re-claim. #6555 stays open until #7262 lands.
Generated by Claude Code
- The coverage thinness above is a live hazard for De-fallback the engine's autonumber render path — read the declared
Blocked-by: #6555's spec-half PR (branch
claude/issue-6555-autonumber-default-format) — the declared default must exist before this reads it.Execution half of the maintainer's route-3 ruling on #6555. Filed unassigned and unrouted; triage routes it (the ruling says drivers).
The ruling (verbatim, comment 5225535766)
The change
packages/drivers/driver-sql/src/sql-driver.ts— two sites, re-measured onorigin/main@3566e55(they drifted again from the:4330/:4412triage cited, and from the:4322/:4404in #6555's body)::5350-5353—initObjects:5432-5435— the external-object registration pathBoth spell the same six lines:
Replace each with the contract:
resolveAutonumberFormatis exported from@objectstack/spec/databy the blocking PR, and was written to reproduce exactly this precedence — canonicalautonumberFormat, then theformatshorthand (#1603), thenDEFAULT_AUTONUMBER_FORMATfor anything that is not a non-empty string.Behaviour-neutral by construction — that is the point of this card
Unlike its engine sibling, this half moves nothing:
resolveAutonumberFormat's truthiness rule was deliberately taken from these very lines so already-stored SQL record numbers keep their shape, per the ruling's compatibility note. It should land as a pure de-duplication: the default stops being written down in the driver and is read from the contract instead.That makes the review question a narrow one — prove the substitution is exact, including the
format: ''and non-string cases, not just the happy path.Suggested gates
packages/drivers/driver-sql/src/sql-driver-autonumber-*.test.tssuites — every one must stay green unchanged. A test that needs editing here is a signal the substitution is not exact.sql-driver-autonumber-suffix.test.ts, which pins'0011'and names this divergence in its comment (PR fix(objectql,driver-sql): 播种解析按声明的 suffix 定位计数器,两侧收敛 (#6468) #6553) — with this card landed, that comment can point at the declared default instead of at a local fallback.packages/runtime/src/autonumber-seed-cross-side-parity.integration.test.ts.Not in this card
The engine's
applyAutonumberspath — its own card. #6555 stays open until both land.