Repository navigation
决策:自增号「计数器反解」规则是否作为 renderAutonumber 的逆进 packages/spec(PR #6553 的 open question,维护者裁) #6560
Description
Activity
Maintainer ruling (2026-08-08): Option B — approved. packages/spec gains a pure-function export (readAutonumberCounter-style) for the counter reverse-parse rule, and both consumers (engine and driver-sql) switch to calling it.
Rationale (three-axis review): the defect this card grew out of was caused precisely by two hand-written copies of the same composition rule giving two different wrong answers — a single canonical function pins future edits on both sides to one source (long-term, containment). It is not an authorable-surface change — no Zod, no new vocabulary — so the startup-focus default against capability expansion does not bite (business). S-sized, guarded by the existing cross-side parity tests from PR #6553.
For the triage seat: this card needs its domain routing label (spec export plus two call-site swaps; the shared-contract rule points at the spec seat).
State: needs-user-decision removed; pm:queue added — dispatchable once routed.
Maintainer directive (verbatim, covering all 14 inbox cards): 「你的建议全部接受」. Recorded by PM session session_01JaVVMrSxt7Tgi1uwEuDtH7.
Generated by Claude Code
Routing (maintainer-authorized): domain:spec.
Anchoring: the ruled fix (option B — a pure-function export for the counter reverse-parse rule) lands in packages/spec, and shared contract surfaces have one owner: the spec seat sees the spec queue's in-flight batch and the generated-baseline collisions. The two call-site swaps (engine, driver-sql) ride the same card as consumers of the new export; note this is a non-authorable, non-Zod export — no acceptance-face change, so it stays a spec-lane chore, not a protocol card.
Note for the audit trail: domain labels are normally the triage seat's single-producer territory. Applied here on the maintainer's direct instruction of 2026-08-08 (「留给分诊座位的三处路由欠账 你直接帮我处理吧」— handle the three routing debts directly). Recorded by PM session session_01JaVVMrSxt7Tgi1uwEuDtH7. The triage seat may re-route on better evidence as usual.
Generated by Claude Code
Maintainer ruling (2026-08-08, via the engine-core PM session): option B approved —
packages/specgains the pure-function export (e.g.readAutonumberCounter(value, prefix, suffix)) as the inverse ofrenderAutonumber, and both the engine and driver-sql sides call it, retiring the two hand-written ~4-line readbacks. Non-authorable, no Zod, no new vocabulary; api-surface bookkeeping only. The runtime cross-side parity test stays as the behavioral guard.Rationale accepted as analyzed: the motivating defect (#6468) was precisely two hand-copied readings giving two different wrong answers — the mirror-drift shape ruling D on #4633 retired elsewhere; the forward direction already lives in spec by design ("shared by the ObjectQL engine and the SQL driver"), and no lower shared package exists.
Per this card's own framing ("批 B 则为普通小改动单,spec 席或 engine-core 席执行皆可") and its
domain:speclabel, it is now an ordinary XS implementation card in the spec lane's queue — the spec seat picks it up through its normal sweep; the engine-core seat will not cross-claim it. This card's decision function is discharged.
Generated by Claude Code
Maintainer ruling (2026-08-10): Option B — spec adds the pure-function export; both sides call it.
Maintainer directive (verbatim): 「我接受你的建议。」 — in response to this seat's B recommendation, argued from the established precedent that
packages/specdeliberately carries contract-semantics pure functions when a rule's semantics must be bit-identical across independent implementations:renderAutonumberitself (whose file header states it is shared by the ObjectQL engine and the SQL driver so both paths render identical record numbers), thefoldAsciiCasefamily (#6520 → PR #7123),valueSchemaFor(ADR-0104 D1),isActionParamValuePresent(PR #7126). The forward direction of this exact rule already lives in spec; B places the inverse beside it. Recorded by PM sessionsession_01PiRUoQkTSBBmpyXBY3cVn2; veto window open — a maintainer comment overturns at zero cost.CLAIM — spec-lane PM seat (#6017), same session. Branch:
claude/issue-6560-read-autonumber-counter. Dispatching a cloud dev session now.Scope (the card's own B description, executed as one PR — this is a pure refactor with zero behavior change, so no cross-lane landing window exists and the #7072-style cross-domain exception path applies, anchored at this seat, full file surface declared):
packages/spec/src/data/autonumber-format.ts— add the pure function export (e.g.readAutonumberCounter(value, prefix, suffix)), non-authorable surface, no Zod, no new vocabulary; TSDoc cites this ruling and PR fix(objectql,driver-sql): 播种解析按声明的 suffix 定位计数器,两侧收敛 (#6468) #6553.- The two ~4-line hand-written application copies (engine + driver-sql, located via PR fix(objectql,driver-sql): 播种解析按声明的 suffix 定位计数器,两侧收敛 (#6468) #6553's diff) are replaced by calls to it.
- The
packages/runtimecross-side parity tests stay as the guard and must pass unchanged — they are the proof the refactor moved semantics without altering them. - Dual-snapshot rule applies (new public export).
Closes #6560on the PR.
Generated by Claude Code
OS-DEV-REPORT
{ "issue": 6560, "outcome": "pr_opened", "branch": "claude/issue-6560-read-autonumber-counter", "pr_number": 7247, "premise_check": { "verdict": "confirmed_with_drift_proceeded", "base": "origin/main @ 2e4274d", "card_premise": "two hand-written ~4-line readback copies (engine + driver-sql) as PR #6553 left them", "measured": { "packages/objectql": "MOVED — the four lines were hoisted into a module-local `readAutonumberCounter` in engine.ts by #6806, which added a SECOND engine consumer (`adoptExplicitAutonumber`, adopt-on-exempt-write resync) beside the seeding scan. Still objectql's private copy.", "packages/drivers/driver-sql": "UNCHANGED — still inline inside `scanMaxNumericTail`, byte-for-byte as PR #6553 left it.", "packages/spec": "no such export existed (verified against api-surface/data.json before the change)" }, "stop_condition_fired": false, "reasoning": "The CROSS-PACKAGE duplication #6560 is about stood in full and had grown a third caller behind objectql's copy; #6806 hoisting within objectql does not unify anything across packages. Repo-wide sweep (`endsWith(suffix)`, `scanMaxNumericTail`) found no third copy — only unrelated string-suffix matching in registry.ts, type-compat.ts, analytics-service.ts, memory-analytics.ts." }, "changes": [ "packages/spec/src/data/autonumber-format.ts — NEW export `readAutonumberCounter(value, prefix, suffix): number | undefined`, placed directly after `renderAutonumber`; TSDoc cites #6560's ruling and PR #6553, states it is the inverse companion of `renderAutonumber` and that both sides MUST call it rather than re-derive", "packages/spec/src/data/autonumber-format.test.ts — 15 new unit cases", "packages/objectql/src/engine.ts — local `readAutonumberCounter` renamed `readStoredAutonumberCounter`, now a thin wrapper (anchored => delegate to spec; unanchored => this engine's own legacy last-digit-run reading); TSDoc updated", "packages/drivers/driver-sql/src/sql-driver.ts — inline anchored branch of `scanMaxNumericTail` replaced by one call to spec's export; TSDoc updated", "packages/objectql/src/engine-autonumber-resync.test.ts — header comment follows the rename", "packages/spec/api-surface/data.json + packages/spec/export-origins/data.json — dual-snapshot regen, +1 line each, 0 removals, 1 shard each", ".changeset/autonumber-counter-readback-shared.md — patch x spec, objectql, driver-sql" ], "call_sites_replaced": [ "packages/drivers/driver-sql/src/sql-driver.ts :: scanMaxNumericTail — the copy PR #6553 wrote, replaced directly", "packages/objectql/src/engine.ts :: readStoredAutonumberCounter — behind which sit TWO engine consumers: seedAutonumber's scan and adoptExplicitAutonumber (#6806)" ], "gates": { "runtime_test_parity_suite_UNCHANGED": "118 files / 1837 tests passed", "objectql_test": "167 files / 2906 tests passed", "driver_sql_test": "81 passed + 4 skipped (85 files) / 1156 passed + 48 skipped", "spec_test": "360 files / 9416 tests passed (re-measured AFTER the regen)", "check_generated": "all 11 generated artifacts up to date", "check_api_surface": "PASS (re-run against the rebuilt dist)", "check_export_origins": "PASS (self-test + check; 4971 exports / 16 entry points)", "check_exported_any": "PASS (2402 types + 1506 schemas, none resolve to any)", "check_dual_source_exports": "PASS (0 new dual-source, baseline unchanged)", "typecheck": "PASS — spec (incl. test layer), objectql, driver-sql, runtime", "build_discipline": "spec built with REAL .d.ts BEFORE gen:api-surface / gen:export-origins (#7122 trap); snapshots re-verified a second time after a full `--filter @objectstack/runtime...` rebuild; OS_SKIP_DTS never used for a build a gate reads", "heavy_run_lock": "flock /tmp/os-heavy-verify.lock — no contention, this session was the only holder (0s wait)" }, "new_exports": [ "@objectstack/spec/data :: readAutonumberCounter(value: string, prefix: string, suffix: string): number | undefined" ], "deviations": [ "SCOPE OF WHAT MOVED: only the ANCHORED rule was hoisted to spec. The UNANCHORED case (both affixes empty) stays per-side because the two sides deliberately differ there and PR #6553 preserved both byte-for-byte — engine takes the LAST digit run ('SO-2024-0007' -> 7), driver-sql concatenates EVERY digit ('SO-2024-0007' -> 20240007), each pinned by its own existing tests. Spec returns undefined for an unanchored slot rather than picking one; hoisting either would make the shared contract claim an agreement that does not exist. Hoisting both would have been a behaviour change on one side, so it was not taken.", "NAMING: spec's export keeps the card's proposed name `readAutonumberCounter`. objectql's now-narrower module-local wrapper was renamed to `readStoredAutonumberCounter` to avoid shadowing the import — this rename is the only reason objectql's diff touches more than two lines of code." ], "docs_adr_touched": false, "special_inspection_notes": [ "Premise drift is the main thing to eyeball: the card was written before #6806 hoisted the engine copy locally and gave it a second consumer. Promoting the rule to spec (rather than leaving objectql's local one) is still the ruled shape — #6806 changed nothing cross-package — but the PM may want to confirm.", "The anchored/unanchored split is the single judgement call in this PR and the reason spec's export returns undefined for ('', ''). If the preference is that spec carry the engine's unanchored reading too, that is one line here PLUS a driver-side behaviour change — no longer a zero-behaviour-change refactor.", "Reverse-verification framing: this is a refactor, so the prediction was green-before and green-after on both sides, and that is what the runs report. No fabricated red baseline.", "content/docs/releases/ and docs/adr/** untouched. No auto-merge enabled — PM lands serially.", "No out-of-scope findings surfaced; no `finding` issues filed." ] }
Generated by Claude Code
Landed. PR #7247 merged to
mainvia the merge queue — the queue's speculative build (CI + Lint & Type Check + Spec Liveness, on the real merge commit) was green.packages/specnow exportsreadAutonumberCounter(value, prefix, suffix)as the declared inverse ofrenderAutonumber; the two hand-written readback copies (ObjectQL engine, SQL driver) call it. Option B as ruled, executed as one zero-behaviour-change refactor.Closing manually, and saying why so the next agent does not repeat it: the PR body carried the closing keyword as
`Closes #6560`— inside backticks. GitHub does not parse closing keywords inside code spans, so the link was inert and the merge did not auto-close this card. My error in authoring the body; the fix is to write the keyword as plain text, never code-formatted.
Generated by Claude Code
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Oct 7, 2026
来自 PR #6553(#6468,已验收落地)dev 报告的 open question,由 engine-core 席落成决策卡免得随合并蒸发。实施侧无人受阻 —— 这是可选的结构加固,不是缺陷。
问题
{prefix}{零填充序号}{suffix}的组合规则由packages/spec/src/data/autonumber-format.ts的renderAutonumber单点持有(文件头自陈:shared by the ObjectQL engine and the SQL driver so both paths render identical record numbers)。PR #6553 修复播种解析后,反解(从存量值读回计数器)的定位参数同样取自renderAutonumber,但「应用这对字符串」的 ~4 行在 engine 与 driver-sql 两侧各有一份,靠packages/runtime的跨侧一致性测试钉住不漂。两案(dev 报告原文要点)
readAutonumberCounter(value, prefix, suffix)),两侧共同调用。非 authorable 面、无 Zod、无新词汇;理由:本缺陷的实测成因正是「同一组合规则的两份手写读法给出两个不同错误答案」,而 core 在 spec 之上、不存在两侧共同可依赖的更低包 —— spec 是唯一落点。dev 推荐 B 但按「本单 spec 只读」的分诊红线未擅动。为什么挂 needs-user-decision
spec 是发布契约面,增导出属维护者权限(哪怕是纯函数);且分诊在 #6468 明文记录 "no spec change is implied by the default route",单方面扩面即程序越界。批 B 则为普通小改动单(spec 席或 engine-core 席执行皆可);批 A 则关卡即可。
Refs:PR #6553(convergence_design 与 open_questions 节)、#6468(缺陷与分诊裁定)、#6555(同区域 render 默认值分叉,另单在分诊)。