Skip to content

feat(autonumber): date, {field} & per-scope counter reset for autonumber formats#2043

Merged
os-zhuang merged 4 commits into
mainfrom
claude/autonumber-format-tokens
Jun 19, 2026
Merged

feat(autonumber): date, {field} & per-scope counter reset for autonumber formats#2043
os-zhuang merged 4 commits into
mainfrom
claude/autonumber-format-tokens

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

背景

autonumberFormat 此前只认一个 {0000} 序列槽 + 固定字面前缀 + 单一全局计数器,无法表达真实 MES/eHR 的编码规则(按日期重置、把记录字段拼进前缀、按父计划/分组独立编号)。

方案

把格式 tokenize 进一个放在 @objectstack/spec纯函数渲染器(parseAutonumberFormat / renderAutonumber),engine 内存兜底与 SQL driver 共用它,保证两条路径产出逐字节一致(#1603 parity)。

四类 token:

token 例子 效果
日期段 AD{YYYYMMDD}{0000} 业务时区 execCtx.timezone 取日历日(ADR-0053,UTC 兜底),每日重置
字段插值 {section}{island_zone}{000} 把记录字段值拼进前缀
父级/分组 {plan_no}{000} 按父计划号独立编号
固定前缀 CASE-{0000} scope 为空 → 单一全局计数器,完全向后兼容

核心设计:计数器 scope = 序列槽之前渲染出的前缀。于是「每日重置 / 每组编号 / 每父编号」全部由同一机制自然得出,无需额外 reset 配置。

改动

  • 新增 packages/spec/src/data/autonumber-format.ts — 共享渲染器
  • driver.zod.ts 新增 DriverOptions.timezone;engine.tsbuildDriverOptionsexecCtx.timezone 注入
  • sql-driver.ts_objectstack_sequencesscope 列,PK 拓宽为 (object, tenant_id, field, scope);遗留三列表首次使用时就地迁移,旧计数器并入 scope=''
  • field.zod.ts — 文档 / .describe() 说明三类 token 与 scope/重置语义

测试

  • spec autonumber-format.test.ts — 11 例(含时区咬合:上海 vs UTC)
  • driver-sql sql-driver-autonumber-tokens.test.ts(新)7 例 + 原有 autonumber 套件 12 例,含遗留表迁移 + 旧计数器续号
  • objectql engine-autonumber-defer.test.ts 补 2 例(fallback 的 {field} 分组 + 日期段),全套 667 例通过

向后兼容:固定前缀格式 scope 为空,既有序列不变;sequences 表迁移把所有遗留行带到 scope=''

changeset:@objectstack/spec / @objectstack/objectql / @objectstack/driver-sql minor。

Tokenize autonumberFormat via a shared pure renderer in @objectstack/spec
(parseAutonumberFormat / renderAutonumber) that both the engine fallback and
the SQL driver call, so they emit byte-identical numbers (#1603 parity):

- date tokens {YYYY}{YY}{MM}{DD}{YYYYMMDD} resolve the calendar day in the
  request's business timezone (ExecutionContext.timezone, ADR-0053; UTC
  fallback), threaded through new DriverOptions.timezone
- {field} interpolation substitutes record values into the prefix
- counter scope = rendered prefix before the sequence slot, so AD{YYYYMMDD}{0000}
  resets daily, {section}{island_zone}{000} numbers per group, {plan_no}{000}
  numbers per parent — one mechanism, no separate reset config

Fixed-prefix formats (CASE-{0000}) render an empty scope and keep their single
global counter. _objectstack_sequences gains a scope column (PK widened to
object,tenant_id,field,scope); legacy 3-column tables migrate in place on first
use, carrying existing counters to scope=''.
@vercel

vercel Bot commented Jun 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jun 19, 2026 10:03am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling size/l labels Jun 19, 2026
@github-actions

github-actions Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 4 package(s): @objectstack/cli, @objectstack/objectql, @objectstack/driver-sql, @objectstack/spec.

98 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/cloud-artifact-api.mdx (via packages/cli, packages/spec)
  • content/docs/concepts/cluster-semantics.mdx (via @objectstack/spec)
  • content/docs/concepts/core/plugins.mdx (via @objectstack/driver-sql)
  • content/docs/concepts/core/services.mdx (via @objectstack/objectql)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/implementation-status.mdx (via @objectstack/cli, @objectstack/objectql, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/concepts/packages.mdx (via @objectstack/cli, @objectstack/objectql, @objectstack/spec)
  • content/docs/concepts/setup-app.mdx (via @objectstack/spec)
  • content/docs/concepts/skills.mdx (via @objectstack/spec)
  • content/docs/concepts/terminology.mdx (via @objectstack/driver-sql)
  • content/docs/concepts/webhook-delivery.mdx (via @objectstack/spec)
  • content/docs/getting-started/architecture.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/getting-started/core-concepts.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/glossary.mdx (via @objectstack/driver-sql)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/guides/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/guides/ai-capabilities.mdx (via @objectstack/spec)
  • content/docs/guides/airtable-dashboard-analysis.mdx (via @objectstack/spec)
  • content/docs/guides/analytics-datasets.mdx (via @objectstack/spec)
  • content/docs/guides/api-reference.mdx (via @objectstack/spec)
  • content/docs/guides/authentication.mdx (via @objectstack/cli, @objectstack/objectql)
  • content/docs/guides/business-logic.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/error-catalog.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-type-gallery.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/field-validation-rules.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/protocol-diagram.mdx (via packages/spec)
  • content/docs/guides/cheatsheets/query-cheat-sheet.mdx (via @objectstack/spec)
  • content/docs/guides/cheatsheets/quick-reference.mdx (via @objectstack/spec)
  • content/docs/guides/client-sdk.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/guides/common-patterns.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/auth-service.mdx (via packages/spec)
  • content/docs/guides/contracts/cache-service.mdx (via packages/spec)
  • content/docs/guides/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/index.mdx (via @objectstack/spec)
  • content/docs/guides/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/guides/contracts/storage-service.mdx (via packages/spec)
  • content/docs/guides/data-modeling.mdx (via @objectstack/spec)
  • content/docs/guides/deployment-vercel.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/guides/driver-configuration.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/guides/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/guides/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/guides/formula.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/guides/hook-bodies.mdx (via packages/cli, packages/spec)
  • content/docs/guides/kernel-services.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/guides/metadata/dashboard.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/field.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/flow.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/index.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/object.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/validation.mdx (via @objectstack/spec)
  • content/docs/guides/metadata/workflow.mdx (via @objectstack/spec)
  • content/docs/guides/objectql-migration.mdx (via @objectstack/objectql)
  • content/docs/guides/packages.mdx (via @objectstack/cli, @objectstack/objectql, @objectstack/driver-sql, @objectstack/spec)
  • content/docs/guides/plugin-development.mdx (via @objectstack/spec)
  • content/docs/guides/plugins.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/guides/project-scoping.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/guides/public-forms.mdx (via @objectstack/spec)
  • content/docs/guides/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/guides/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/index.mdx (via packages/cli, packages/spec)
  • content/docs/guides/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/guides/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/guides/security.mdx (via @objectstack/spec)
  • content/docs/guides/seed-data.mdx (via @objectstack/spec)
  • content/docs/guides/skills.mdx (via packages/cli, @objectstack/spec)
  • content/docs/guides/standards.mdx (via @objectstack/spec)
  • content/docs/guides/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/objectos/index.mdx (via @objectstack/objectql, @objectstack/driver-sql)
  • content/docs/protocol/objectos/lifecycle.mdx (via @objectstack/driver-sql, @objectstack/spec)
  • content/docs/protocol/objectos/plugin-spec.mdx (via @objectstack/cli, @objectstack/spec)
  • content/docs/protocol/objectos/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/protocol/objectos/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via packages/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/objectql, @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

Comment thread packages/objectql/src/engine.ts Fixed
The empty-prefix legacy branch used /(\d+)(?!.*\d)/ to grab the last digit
run, whose negative lookahead is a polynomial-ReDoS sink on stored values
with many repeated zeros (CodeQL js/polynomial-redos, high). Replace both
branches with the linear /\d+/g, preserving the last-digit-run semantics.
Add three guardrails on top of the {field}/date/per-scope autonumber work:

- Empty interpolated {field} now throws (shared missingFieldValues helper)
  in both the SQL driver and the engine fallback, instead of silently
  collapsing the record into the wrong counter scope.
- Build-time lint (objectstack compile): unknown / self-referencing {field}
  fails the build; an optional {field} warns to mark it required.
- Legacy _objectstack_sequences PK-widen failure fails safe — fixed-prefix
  sequences keep working and a per-scope write raises an actionable error
  rather than an opaque DB primary-key violation at insert time.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…antics

Full hardening of the remaining items from review:

- #5 MySQL/length: key _objectstack_sequences by a single key_hash
  (SHA-256 of object,tenant_id,field,scope) instead of a 4-column natural
  PK. The natural PK exceeded MySQL's utf8mb4 index-length limit (a certain
  CREATE TABLE failure) and bounded how long a {field} scope could be. The
  hash PK keys every dialect uniformly and lets scope be a generous
  non-indexed column. Legacy 3-column and interim {scope}-column tables are
  migrated in place; migration fails safe (fixed-prefix keeps working, a
  per-scope write errors actionably).

- #1 scope ambiguity: confirmed NOT fixable by separating adjacent token
  boundaries — when two records render the same prefix they render the same
  visible number, so they MUST share a counter to stay unique (a separator
  would mint duplicates). Documented the semantics + the remedy (delimiter
  literal in the format), backed by tests. The compile lint already nudges
  authors toward unambiguous formats.

- #6 width overflow: confirmed by-design — the pad width is a MINIMUM, the
  counter grows past it and never wraps (mainstream autonumber semantics).
  Documented + regression test, no throw (throwing would break legitimate
  high-count sequences).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@os-zhuang
os-zhuang merged commit 36138c7 into main Jun 19, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/autonumber-format-tokens branch June 19, 2026 10:07
@os-zhuang

Copy link
Copy Markdown
Contributor

评审后加固总结(合并前补充的两个提交)

在原方案(date / {field} / per-scope 自动编号)基础上,针对评审发现的风险做了两轮加固。逐项说明,便于后续回溯。

6b695fd9{field} 插值的三道护栏

问题 处理
#2 空字段静默错号 被插值的 {field} 在生成时为空 → 渲染成空前缀,记录被并进错误的计数器 scope(最易发生) 新增共享 helper missingFieldValues();SQL driver 与 engine fallback 两条路径生成前检查,为空即抛出指名字段的清晰错误(保持 #1603 parity)
#3 无建模期校验 {field} 引用不存在/可选字段,一路绿灯到运行期才暴露 新增 lint-autonumber-formats.ts 接入 objectstack compile:引用不存在字段/自引用 → 构建失败;引用可选字段 → 警告提示标 required: true(对齐 broken→error / fragile→warning 两级护栏)
#4 迁移失败潜伏 旧序列表 PK 加宽 best-effort 失败只 warn → 后续 per-scope 写入撞不透明 PK 违反 失败即安全:固定前缀照常工作,per-scope 写入抛可操作错误(此项在 9813c420 随表结构重设计进一步收敛)

9813c420 — 序列表重设计 + 语义澄清

结论 处理
#5 MySQL/长度 四列自然主键 (object,tenant_id,field,scope) 在 MySQL utf8mb4 下 = 4080 字节 > 3072 索引上限 → CREATE TABLE 必现失败(非边缘),且限制 {field} scope 长度 _objectstack_sequences 改为 key_hash(SHA-256 of object,tenant_id,field,scope)单列主键:各方言统一、远低于索引上限、scope 变非索引 varchar(1024);旧 3 列表 / 中间态 scope 列表均就地迁移,迁移失败即安全降级
#1 拼接歧义 调查后纠正判断('AB','C')('A','BC') 渲染出相同前缀 ABC ⇒ 可见编号本就相同,必须共用计数器才能保唯一;加分隔符独立计数反而会产出重复编号。原 scope = prefix 是对的 回退分隔符改动;语义写进文档(真正解法=格式加分隔字面量,由 #3 lint 引导),测试锁定
#6 宽度溢出 核实为 by-design:pad 宽度是最小宽度,超出自然增宽({000}1000)、不环绕,与 Salesforce/Dynamics 一致;报错会破坏合法高计数序列 不抛错;文档 + 回归测试锁定

改动文件

  • packages/spec/src/data/autonumber-format.tsmissingFieldValues();scope 语义注释
  • packages/spec/src/data/field.zod.ts — 文档(字段须 required/已设;宽度为最小值)
  • packages/objectql/src/engine.ts — fallback 空字段抛错
  • packages/plugins/driver-sql/src/sql-driver.tskey_hash 表结构 + 迁移 + 空字段抛错 + 失败即安全
  • packages/cli/src/utils/lint-autonumber-formats.ts(新)+ commands/compile.ts — 建模期 lint

验证

spec autonumber 16/16 · driver-sql 全量 180/180 · objectql 670/670 · cli 490/490;四包 tsc/构建通过;CI 全绿后合并。

os-zhuang added a commit that referenced this pull request Jun 19, 2026
…rmat tokens (#2046)

Follow-up to #2043 — close the AI-authoring gap on top of the runtime guards:

- skills/objectstack-data field-types rules: the autonumber section only showed
  `CASE-{0000}`. Expanded with the date / {field} / per-scope tokens and the
  authoring rules that prevent silent mis-numbering — interpolated fields must be
  required, put a delimiter between adjacent variable tokens, pad width is a
  minimum, date tokens are exact/case-sensitive — plus an incorrect/correct example.

- compile lint: warn when an autonumber `{...}` token is not a counter/date/{field}
  token. Unrecognized groups (wrong case, spaces, a second {0..0} slot) render
  LITERALLY into the record number; the field-reference checks miss the
  non-identifier cases. New advisory rule `autonumber-unrecognized-token`.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/xl tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants