Repository navigation
自增号格式带「序号槽之后的后缀」时,播种解析读错数字段:引擎读成年份(2026),driver-sql 读成拼接串(12026),两侧还互不一致 #6468
Description
Activity
Triage:
pm:queue·domain:engine-core·target:v17— cross-domain, deliberately NOT splitClassification —
pm:queue, notneeds-user-decision. The card offers two routes and asks triage to pick; the pick is available without a maintainer, because one route restores an invariant the contract already declares and the other removes a shipped capability.renderAutonumberreturnssuffixas a declared return value — verified onorigin/main(eb7613c) atpackages/spec/src/data/autonumber-format.ts:165(suffix: string;),:172("Final value: prefix + zero-padded(seq) + suffix"),:200,:213,:214. So this is thedeclared ≠ enforcedshape: the format language promises a post-slot suffix, and both seeding parsers assume the digits at the end of the string are the counter. Default route = make both parsers honour the declaredsuffix(restore-invariant ⇒ auto-queue). Narrowing the format language instead would remove a shipped, documented capability — that route, and only that route, would need a maintainer ruling, so a dev choosing it must stop and escalate rather than proceed.Landing sites re-read on
origin/main, not in the shared worktree:side verified anchor engine packages/objectql/src/engine.ts:2178private async seedAutonumber(, called at:2129driver-sql packages/drivers/driver-sql/src/sql-driver.ts:3033protected async scanMaxNumericTail(, called at:3127contract packages/spec/src/data/autonumber-format.ts:165-214(read-only reference — the spec side is correct as written; no spec change is implied by the default route)Incidental confirmation that the shape is already recognised in-tree:
engine.ts:212carries the words "plus the engine's ownseedAutonumber. Declared ≠ enforced" in its own header comment.Domain —
engine-core, and the card is exempt from the cross-domain split. The fix genuinely spans two lanes (engine-coreforengine.ts,driversforsql-driver.ts), but the body's own constraint forbids splitting it: change one parser alone and the two sides go from two different wrong answers to one right and one wrong, which is still cross-driver inconsistency and is arguably worse to debug. That is the "真拆不动的跨域单" exception path, so this card carries one label and rides as a single PR. Declared file face for the claiming lane:packages/objectql/src/engine.ts(seedAutonumber) +packages/drivers/driver-sql/src/sql-driver.ts(scanMaxNumericTail).⚠️ Thedriverslane should not open a parallel card for thesql-driver.tshalf — it is inside this one.Dedup — searched open issues and open PRs across the three repos for
autonumber,seedAutonumber,scanMaxNumericTail. No duplicate; two same-region neighbours:- 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 (
pm:queue/domain:engine-core/target:v17) — counter does not sync toMAX(existing)per tenant. Same file, same region, different defect (which row is scanned vs. how the scanned value is parsed). The body already flags the collision; confirmed. Serialize. - seedAutonumber 的播种扫描是「取前 5000 行、不排序、不按前缀过滤」的 MAX —— 超过 5000 行的对象会播种出低于真实 MAX 的号 #6249 / PR fix(objectql): seedAutonumber 播种扫描覆盖 scope 内每一行,不再取 5000 行窗口 (#6249) #6467 — the scan-window fix that surfaced this. Explicitly disjoint: that PR left the per-value parsing untouched, and this card is exactly those lines.
target:v17— blocking, on two independent criteria. ② public contractdeclared ≠ enforced: the format language advertises a post-slot suffix and the write path silently mis-seeds on it. ① stored-data damage a user hits today:{000}-{YYYY}is an ordinary invoice-number shape, needs no unusual authoring, and seeds the counter to2026(engine) or12026(driver-sql) against a true counter of1— the next issued number jumps to2027-2026, and the numbers already burned cannot be reclaimed after the fact.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
- 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 (
认领(engine-core 席 #6019,会话
session_019Q7oc7ASjh8yxyS3Yz78We,第 20 轮):- 分支:
claude/issue-6468-autonumber-suffix-parse;工作树:../objectstack-6468 - 文件面(按分诊跨域豁免,单 PR 双侧):
packages/objectql/src/engine.ts(seedAutonumber逐值解析)+packages/drivers/driver-sql/src/sql-driver.ts(scanMaxNumericTail)+ 两侧测试 + changeset(双包 patch)。packages/spec/src/data/autonumber-format.ts只读引用,⛔ 不改 spec。 - Serial constraints(分诊要求):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 同文件同区域 —— 复核其评论区无在飞认领(重锚 drivers 请求仍未处理,证伪后修法预期在 driver-sql 的
getNextSequenceValue,与两处播种解析零函数交集);若重锚结论相反本单让位。seedAutonumber 的播种扫描是「取前 5000 行、不排序、不按前缀过滤」的 MAX —— 超过 5000 行的对象会播种出低于真实 MAX 的号 #6249/PR fix(objectql): seedAutonumber 播种扫描覆盖 scope 内每一行,不再取 5000 行窗口 (#6249) #6467(扫描窗口)已落地且明确不相交(该 PR 刻意未动逐值解析)。engine.ts 串行链当前空。 - 执行口径:默认路线 = 两侧解析器尊重已声明的
suffix(restore-invariant,分诊已裁);若实施中出现「必须收窄格式语言」的证据 —— 停,回报,那条路归维护者。⛔ drivers 席不平行开卡(分诊已注)。 - 标签对:
pm:queue→pm:dispatched(target:v17保留,双判据上板)。
Generated by Claude Code
- 分支:
- added 3 commits that reference this issue
on Aug 17, 2026 - added a commit that references this issue
on Sep 1, 2026 - added a commit that references this issue
on Oct 7, 2026
发现于 #6249 的实现(PR #6467)途中,未在该单 PR 内顺手修 —— #6249 修的是播种扫描的行窗口,本条是同一函数里数字段解析的独立缺陷。按 Prime Directive #10 单独开单。
缺陷
autonumberFormat允许序号槽{0..0}后面还有 token(renderAutonumber专门有suffix这个返回值,见packages/spec/src/data/autonumber-format.ts:199-213:width === null之前的片段进prefix,之后的进suffix)。这类格式渲染出的值,序号不在字符串末尾。而两侧的播种解析都假定「末尾的数字就是计数器」:
packages/objectql/src/engine.tsseedAutonumber():prefix为空时取整串的最后一个数字段;packages/drivers/driver-sql/src/sql-driver.ts:3033scanMaxNumericTail():parseInt(tail.replace(/[^0-9]/g, ''), 10)—— 把 tail 里所有数字拼起来。以
{000}-{YYYY}为例(序号在前、年份在后,是一种常见的单号写法),实测:(探针直接调用
packages/spec/dist的parseAutonumberFormat/renderAutonumber,再分别复刻两侧的解析。后两行是对照:无后缀的格式两侧都正确。)真实计数器是 1,而:
2027-2026;001和2026拼成一个数;为什么这不是 #6249 的一部分
#6249 / PR #6467 修的是「只看任意 5000 行窗口」,修法是把扫描做完整;逐值解析的那几行原封未动(PR 里明确写了「该逻辑一字未改」)。扫描做完整之后,这条解析缺陷照旧:读全了,还是读错。
可达性
需要作者写出「序号槽之后还有 token」的格式。
suffix是renderAutonumber的声明返回值、不是意外产物,格式语言明确支持;{000}-{YYYY}这种「序号-年份」写法在单号里很常见。没有任何校验或 lint 拦截这类格式。供分诊参考(不预设结论)
修法至少要先定一件事:播种解析应当按
suffix反向定位序号段(即 tail 去掉已知 suffix 之后再取数字段),还是收窄格式语言(禁止序号槽后出现 token,在 compile lint 里拒绝)。前者两侧各改一处解析;后者是 authorable surface 的收窄,要走既有格式的兼容判断。两侧解析必须同时改,否则会把「两个不同的错误答案」变成「一个正确一个错误」,跨驱动仍不一致。getNextSequenceValue序列重同步)同文件同区域,串行安排时需一并考虑。