Skip to content

feat(spec): declare {0000} as the contract default for format-less autonumber fields (#6555) - #7265

Merged
os-zhuang merged 2 commits into
mainfrom
claude/issue-6555-autonumber-default-format
Aug 10, 2026
Merged

feat(spec): declare {0000} as the contract default for format-less autonumber fields (#6555)#7265
os-zhuang merged 2 commits into
mainfrom
claude/issue-6555-autonumber-default-format

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Part of #6555. Not "Closes" — the card stays open until the two de-fallback halves land (#7262, #7263).

The maintainer ruling this executes

Comment 5225535766, 2026-08-08, verbatim:

Maintainer ruling (2026-08-08): Route 3, with the default fixed at {0000}. The default format for autonumber fields becomes a declared contract default in FieldSchema (spec side), and both hand-written fallbacks go away: driver-sql drops its hardcoded fallback and the engine fallback path stops emitting bare integers — both sides read the declared default.

Rationale (three-axis review): routes 1 and 2 both fix the fork but leave "the default" living in two hand-written fallbacks — and route 2 would break number-shape continuity for already-stored SQL data, the largest install base (business). Route 3 is the only one that puts the default itself into the contract: declared = enforced (long-term), and an AI metadata author sees the default in the schema instead of guessing per-driver behavior (containment).

Compatibility note (must be in the changeset): 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).

For the dispatch: contract-first ordering — the spec default lands first (spec lane), the two de-fallback changes follow (engine-core / drivers as routed by triage).

State: needs-user-decision removed; pm:queue restored — spec lane leads.

Maintainer directive (verbatim, covering all 14 inbox cards): 「你的建议全部接受」. Recorded by PM session session_01JaVVMrSxt7Tgi1uwEuDtH7.

Re-affirmed 2026-08-10 in the PM's claim comment: 「统一为 {0000}(推荐)」.

Scope: the spec half only, per the ruling's contract-first ordering. No engine or driver file is touched by this PR (git diff --stat -- packages/objectql packages/drivers is empty).

Premise re-measurement on fresh origin/main @ 3566e55

Premise from the dispatch Re-measured Note
driver-sql fallback, 2 sites ✅ still present sql-driver.ts:5353 and :5435 — drifted again from triage's :4330/:4412 (and from the body's :4322/:4404). Same const fmt = rawFmt || '{0000}';, unchanged.
engine fallback path ✅ still present engine.ts:2553-2554, not :2115 (that line is now audit-hook session code). const fmt = …autonumberFormat ?? …format; then parseAutonumberFormat(typeof fmt === 'string' ? fmt : '').
width === null bare-counter branch ✅ present autonumber-format.ts:210-213, comment still reads "legacy behaviour".
FieldSchema has no declared default yet ✅ confirmed No DEFAULT_AUTONUMBER_FORMAT anywhere in packages/; data/Field:autonumberFormat absent from authorable-defaults/data.json.

Nothing had landed ahead of this card.

Declaration mechanics — the decision, and the blast radius that drove it

The ruling says the default becomes "a declared contract default in FieldSchema". Two mechanics could carry that. The dispatch flagged the Zod .default() option as needing its blast radius measured first, so it was measured, not reasoned about.

Option A — autonumberFormat: z.string().optional().default('{0000}'). Applied to a scratch tree and run:

  • Parse output changes for every field type. autonumberFormat is a flat key on FieldSchema, shared by all 49 field types — it is not scoped to type: 'autonumber'. Measured directly: FieldSchema.parse({ type: 'text', label: 'Name' }) returns …,"autonumberFormat":"{0000}","externalId":false} — a counter format materialised onto a text field. Parse output is observed by metadata loaders, the metadata API, and both drivers' initObjects, so that shift travels well outside autonumber.
  • 28 spec tests go red, all in packages/spec/scripts/build-schemas-check-mode.test.ts (28 failed | 9369 passed). Control run on the same tree with the patch reverted: 60 passed in that file — so the reds are caused by the patch, not pre-existing.
  • z.output<typeof FieldSchema>['autonumberFormat'] narrows from string | undefined to string for every consumer typed on the parsed shape.

⇒ Rejected. It measurably breaks existing parse-output expectations, which is exactly the condition the dispatch named.

Option B — JSON-Schema default annotation (chosen). .meta({ description, default: DEFAULT_AUTONUMBER_FORMAT }), house style (approval.zod.ts, schemaless-node-config.zod.ts — "a single .meta() carries both description and annotation"). Parse output is byte-identical for every field type; the default is declared to schema consumers and AI metadata authors.

This is not a documentation-only escape hatch — the repo's own machinery treats it as the contract default. gen:schema refused the build until the change was declared:

❌ 1 authorable key(s) changed the DEFAULT they apply when the author omits them:
     - data/Field:autonumberFormat: (none) → "{0000}"  (added)

so the change is registered in DEFAULT_CHANGES_BY_MAJOR (scripts/lib/default-changes.ts, the #4666 ratchet) with a consumer-facing reason, and lands in the tracked authorable-defaults/data.json as data/Field:autonumberFormat = "{0000}". Both endpoints are re-verified on every build, so the declaration dies if it stops being true. Neither option was blocked, so no STOP was needed.

Changes

File What
packages/spec/src/data/autonumber-format.ts New DEFAULT_AUTONUMBER_FORMAT = '{0000}' beside renderAutonumber; new resolveAutonumberFormat(field) + AutonumberFormatSource; TSDoc note on the width === null branch.
packages/spec/src/data/field.zod.ts autonumberFormat declares the default via .meta({ description, default }); TSDoc section on omitting the key and on why this is an annotation.
packages/spec/scripts/lib/default-changes.ts Declares data/Field:autonumberFormat: (none) → "{0000}" under major 17, with the consumer-facing reason.
packages/spec/src/data/autonumber-format.test.ts +6 cases: the constant, resolution precedence, non-string/empty handling, 0001 rendering, the surviving bare branch.
packages/spec/src/data/field-autonumber-default-format.test.ts New. +4 cases pinning the FieldSchema contract: the JSON-Schema default, the key staying optional, parse output unchanged on 5 field types, and a parsed format-less field resolving to {0000}.
.changeset/autonumber-default-format-contract.md minor on @objectstack/spec, carrying the ruling's compatibility note verbatim.
authorable-defaults/data.json, api-surface/data.json, export-origins/data.json, content/docs/references/data/field.mdx Regenerated after a real build.

Precedence, and one decision inside it. resolveAutonumberFormat treats anything that is not a non-empty string as undeclared. That is driver-sql's long-standing truthiness rule, not the engine's ?? — the two disagreed on format: '' as well as on the missing key, and resolving '' to the default is the direction that leaves already-stored SQL numbers untouched, matching the ruling's compatibility note. Consequence: the driver-side de-fallback (#7263) becomes behaviour-neutral by construction, and the engine side (#7262) carries the entire behaviour move — which is what the ruling describes.

Reverse verification — direction predicted before running

Ran both sides: implementation applied, then packages/spec/src/data/{autonumber-format,field.zod}.ts reverted to HEAD with the tests kept.

Pin Predicted Pre-change Post-change
autonumber-format.test.ts — 6 new #6555 cases RED → GREEN ❌ 6 failed ✅ pass ✅ as predicted
field-autonumber-default-format.test.ts — JSON-Schema default + resolve RED → GREEN ❌ 2 failed ✅ pass ✅ as predicted
field-autonumber-default-format.test.ts — key optional, parse output unmoved GREEN both sides ✅ pass ✅ pass ✅ — pins the absence of the Option-A change, so it is green before and after by design
autonumber-format.test.ts — 20 pre-existing cases GREEN both sides ✅ pass ✅ pass
field-autonumber-readonly.test.ts (#5628) GREEN both sides ✅ pass ✅ pass

Totals: pre-change 8 failed | 26 passed, post-change 34 passed.

One prediction was imprecise in a way worth naming: I expected the pre-change run to fail at import (missing exports taking the whole file down). It did not — the missing exports arrived as undefined and only the 8 asserting cases failed, leaving the 26 pre-existing pins green on both sides. A cleaner signal than predicted, but not the mechanism predicted.

Gates

Heavy runs serialised on flock /tmp/os-heavy-verify.lock; no contention — every acquisition was immediate (0s wait).

Gate Result
pnpm --filter @objectstack/spec build ✅ (run before every generator — #7122 stale-dist trap)
gen:schema ✅ after declaring the default; first run correctly refused the undeclared default change
check:generated All 11 generated artifacts are up to date (3 stale → --fix → re-verified clean)
Dual snapshot — api-surface/ + export-origins/ ✅ both regenerated after the real build; +AutonumberFormatSource, +DEFAULT_AUTONUMBER_FORMAT, +resolveAutonumberFormat, 0 removed
pnpm --filter @objectstack/spec typecheck ✅ (incl. check:test-typecheck, debt unchanged)
pnpm --filter @objectstack/spec test 361 files / 9407 tests passed
packages/drivers/driver-sql — all 8 sql-driver-autonumber-* suites 64 passed, unchanged
packages/objectql — all 7 engine-autonumber-* suites 97 passed, unchanged
packages/runtime/src/autonumber-seed-cross-side-parity.integration.test.ts 5 passed, unchanged

The last three are the proof the dispatch asked for: the spec half alone breaks neither side.

Follow-up cards filed

Both unassigned, no routing labels (triage routes them), each Blocked-by this PR, each quoting the ruling verbatim. Dedup-searched first — nothing existing covers the render fallback.

For the PM — special inspection

  1. Line drift, third time. Both anchor sets had moved again (:4330/:4412:5353/:5435; :2115:2553). The follow-up cards carry re-measured line numbers plus the code text, so the next dev can re-anchor by content.
  2. A declared-but-not-yet-enforced window is open, deliberately. Between this PR and De-fallback the engine's autonumber render path — read the declared {0000} default instead of parsing the empty string (#6555 half 2/3) #7262/De-fallback driver-sql's autonumber format — drop the two hardcoded || '{0000}' sites for the declared default (#6555 half 3/3) #7263 the contract says {0000} while the engine still emits bare 1. That is the ruling's own contract-first ordering; it closes when both cards land, and 无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成 {0000} 发出 0001,引擎兜底路径发出裸 1 —— 同一份元数据换驱动号形不同 #6555 stays open until then.
  3. format: '' semantics move on the engine side too. Called out in De-fallback the engine's autonumber render path — read the declared {0000} default instead of parsing the empty string (#6555 half 2/3) #7262 — a second, smaller behaviour change beyond the ruling's compatibility note, and it belongs in that card's changeset.
  4. Not done, deliberately. The authoring form's autonumberFormat help text (object.form.ts:187) could state "leave blank → 0001", but that regenerates *.metadata-forms.generated.ts across 4 locales in platform-objects — a cross-package generated chain outside this card's scope. Worth a small follow-up if the PM wants it.
  5. content/docs/releases/ and docs/adr/**: untouched.
  6. No auto-merge, per dispatch.

Generated by Claude Code

…autonumber (#6555)

`FieldSchema.autonumberFormat` is optional, and the two sides that mint record
numbers each answered "no format declared" on their own — differently.
driver-sql substituted `'{0000}'` and issued `0001`; the ObjectQL engine's
in-memory fallback path parsed the empty string and fell through
`renderAutonumber`'s no-slot branch to a bare `1`. One metadata document, two
number shapes. The counter VALUE always agreed (#6468 pinned it) — the fork was
rendering width alone.

Per the maintainer's 2026-08-08 ruling (route 3, default fixed at `{0000}`),
this lands the CONTRACT half only:

- `DEFAULT_AUTONUMBER_FORMAT` — new export beside `renderAutonumber`; the one
  place the value is written down.
- `resolveAutonumberFormat(field)` — new export; canonical `autonumberFormat`,
  then the `format` shorthand (#1603), then the declared default. A key holding
  anything but a non-empty string counts as undeclared — driver-sql's
  truthiness rule, chosen so already-stored SQL numbers keep their shape.
- `FieldSchema.autonumberFormat` declares the default as a JSON-Schema
  annotation, registered in `DEFAULT_CHANGES_BY_MAJOR` (#4666 ratchet).

Deliberately an annotation, not a Zod `.default()`: the key is flat on
FieldSchema and shared by every field type, so a parse-time default was
measured to materialize `autonumberFormat: '{0000}'` on `text`/`number`/
`lookup` fields and turned 28 cases in `build-schemas-check-mode.test.ts` red.
Parse output is unchanged for every type.

Both generators keep their hand-written fallbacks for now; removing them
follows in two separate cards, so nothing about today's rendering moves.

Part of #6555.
@vercel

vercel Bot commented Aug 10, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 10, 2026 5:53am

Request Review

@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation protocol:data tests tooling labels Aug 10, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

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

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/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 packages/spec)
  • content/docs/concepts/north-star.mdx (via @objectstack/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/permissions/system-context.mdx (via packages/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/http-protocol.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/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/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 @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/apps.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/field-grouping-and-order.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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.

…onumber-default-format

# Conflicts:
#	packages/spec/src/data/autonumber-format.test.ts

Copy link
Copy Markdown
Contributor Author

PM → executor session: rebase needed (mergeable_state: dirty). PR #7247 merged and the predicted overlap materialized: packages/spec/src/data/autonumber-format.ts (its readAutonumberCounter addition sits beside your DEFAULT_AUTONUMBER_FORMAT/resolveAutonumberFormat additions) and the api-surface/data.json + export-origins/data.json shards. Note: a dirty PR dispatches ZERO CI workflows — this does not self-heal, and close/reopen does not fix it.

Please: merge current origin/main into claude/issue-6555-autonumber-default-format; resolve autonumber-format.ts by keeping BOTH additions (they are disjoint functions beside renderAutonumber); then follow the four-step os-regen for the generated files — pnpm --filter @objectstack/spec build first (stale-dist trap #7122), then regenerate api-surface/, export-origins/, authorable-defaults/ and the docs page from the merged tree rather than hand-merging JSON; re-run the spec suite + check:generated; push. Auto-merge is armed on this PR — once checks go green it queues on its own. Never git stash during the resolution.


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Merge-queue eviction (MERGE_CONFLICT) resolved — ready to re-queue.

main landed #7247 (refactor(spec,objectql,driver-sql): share the autonumber counter readback as spec's inverse of renderAutonumber (#6560)) into the same two files this PR touches. Merged origin/main in and pushed as 8c1dd41.

The conflict was one hunk: the import list in packages/spec/src/data/autonumber-format.test.ts. #7247 added readAutonumberCounter; this PR added resolveAutonumberFormat + DEFAULT_AUTONUMBER_FORMAT. Resolved by keeping all three — no logic on either side was dropped. packages/spec/src/data/autonumber-format.ts itself auto-merged: #7247's readAutonumberCounter is the counter read-back inverse, this PR's addition is the format default, and they touch different regions of the file.

Worth noting the two changes agree rather than collide: readAutonumberCounter's TSDoc declines to decide the unanchored-slot reading and points at "a format with no {0..0} slot" as a live case — which stays live here, since after this PR that branch is reached by a declared slot-less format rather than by a format-less field.

Generated artifacts were merge-driver deferred, then regenerated from the merged tree after a real @objectstack/spec build (api-surface/, export-origins/); pre-commit confirmed the deferral marker cleared. Delta vs main on both snapshots is still exactly this PR's three exports, 0 removed.

Re-verified on the merged tree (heavy runs serialised on flock /tmp/os-heavy-verify.lock):

Gate Result
check:generated ✅ all 11 artifacts up to date
pnpm --filter @objectstack/spec typecheck ✅ (debt unchanged)
pnpm --filter @objectstack/spec test 362 files / 9464 tests (up from 9407 — #7247's cases now run alongside)
autonumber pins (3 files) ✅ 53 passed
driver-sql — 8 sql-driver-autonumber-* suites ✅ 64 passed, unchanged
objectql — 7 engine-autonumber-* suites ✅ 97 passed, unchanged
runtime cross-side parity ✅ 5 passed, unchanged

main has since moved again (5777b1a); that range has no file overlap with this branch's diff.

Not re-queueing myself — leaving that to the PM's serial landing order, per the dispatch's no-auto-merge instruction.


Generated by Claude Code

@os-zhuang
os-zhuang added this pull request to the merge queue Aug 10, 2026
Merged via the queue into main with commit 88154be Aug 10, 2026
27 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-6555-autonumber-default-format branch August 10, 2026 06:51
os-zhuang pushed a commit that referenced this pull request Aug 10, 2026
Archive criteria (PR merged + report reviewed), the batch sweep, and the
one hard boundary: never archive before the PR merges — a live completed
session is the dirty-rescue executor (#7265/#7325).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…not a hardcoded fallback (objectstack-ai#7263) (objectstack-ai#7334)

Execution half 3/3 of the route-3 ruling on objectstack-ai#6555. `{0000}` is a declared
contract default now (`DEFAULT_AUTONUMBER_FORMAT` / `resolveAutonumberFormat`,
landed in `@objectstack/spec/data` by objectstack-ai#7265), so this driver stops keeping its
own copy of it.

`initObjects` and the external-object registration path each spelled the same
four lines by hand — canonical `autonumberFormat`, then the `format` shorthand,
then `|| '{0000}'`. Both are now `resolveAutonumberFormat(field)`: one symbol
added to an import this file already had, no new dependency.

Behaviour-neutral by construction and by measurement. The resolver's precedence
and its non-empty-string test were taken from these very lines, so a
differential check over 484 field documents (both spellings x 22 value shapes)
returns the identical string in every case — `format: ''`,
`autonumberFormat: ''` and the non-string values included. All eight driver-sql
autonumber suites (64 tests) and the cross-side parity integration test stay
green unedited; the one test-file change is prose, re-pointing a comment at the
declared default while keeping true its note that the engine still emits a bare
counter until objectstack-ai#7262.


Claude-Session: https://claude.ai/code/session_01TcsV4ES5JJjCZ9BLrfhTy8

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…fault, not the empty string (objectstack-ai#7262) (objectstack-ai#7433)

Execution half 2/3 -- the last one -- of the maintainer's route-3 ruling on
objectstack-ai#6555. `applyAutonumbers` resolved the format by hand (`autonumberFormat ??
format`, then `typeof fmt === 'string' ? fmt : ''`), so a format-LESS field
parsed the EMPTY string and rendered `renderAutonumber`'s no-slot branch as a
bare `1`, `2`, .... driver-sql substituted its own `'{0000}'` and issued `0001`.
One metadata document, two number shapes, decided by which driver served it.

It is now `resolveAutonumberFormat(def)` -- the resolver landed by objectstack-ai#7265 and
already read by driver-sql since objectstack-ai#7263.

Unlike the driver half this MOVES behaviour, two ways, both in the changeset:

  - a format-less field on the engine fallback path issues `0001` where it
    issued `1` (engine-fallback deployments only; stored driver-sql data is
    undisturbed, and objectstack-ai#6468's counter continuity is unaffected -- `{0000}`
    renders prefix '' / suffix '', so the seeding scan stays on its unanchored
    legacy reading);
  - `??` -> truthiness means `autonumberFormat: ''` / `format: ''` resolve to
    the default instead of rendering bare, and an empty canonical key no longer
    masks a declared `format` shorthand.

Tests. New `engine-autonumber-default-format.test.ts` covers the empty-string
inputs no suite on either side declared before (the gap the drivers seat
measured on this card), plus the format-less renders and the controls that must
NOT move. `autonumber-seed-cross-side-parity.integration.test.ts` gains the bug
report's own reproduction -- no format, stored 1/2/10, both sides `0011` --
which is what lets that file assert a shared RENDERING and not only a shared
counter. Five pre-existing cases asserting bare `'1'`/`'2'`/`'8'`/`'11'` on
format-less fields were re-pinned to the padded form, each with a objectstack-ai#6555 comment.

Closes objectstack-ai#7262. Part of objectstack-ai#6555.


Claude-Session: https://claude.ai/code/session_01PiRUoQkTSBBmpyXBY3cVn2

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…ary (objectstack-ai#7287) (objectstack-ai#7405)

Maintainer ruling on objectstack-ai#7287 (2026-08-10, treatment (2) 「宣告边界」): on an
UNANCHORED autonumber format (neither prefix nor suffix), a stored value
carrying non-digit content is OUT OF CONTRACT for counter readback, and
`readAutonumberCounter`'s `undefined` for that slot is the contract rather
than a gap.

Zero behavior movement. The two consumers keep their divergent legacy
readings — the engine's `readStoredAutonumberCounter` takes the last digit
run, driver-sql's `scanMaxNumericTail` concatenates every digit — each now
labelled implementation detail outside the declared boundary, pointing at
spec's TSDoc. Hoisting either reading was rejected: it would move live
behavior on the other side over record numbers already issued (the reason
objectstack-ai#7247 refused the hoist as a rider).

The boundary is drawn by CONTENT and deliberately wider than the observed
divergence: pure-digit values (what `renderAutonumber` emits for an
unanchored format) are in contract and read the same on both sides, while
every mixed-content value is outside — including ones the two readings
happen to agree on, since divergence needs two digit runs. Narrowing it to
"values the sides actually disagree on" would make membership undecidable
from the value alone.

Reachability: objectstack-ai#6555's Route-3 ruling (PR objectstack-ai#7265) made `{0000}` the declared
default for format-less autonumber fields, so the default authoring shape
now lands in this unanchored slot.

New pins in `autonumber-unanchored-boundary.test.ts` assert the `undefined`
for eight mixed-content shapes, show the two readings diverging on the
inputs the boundary excludes and agreeing on the ones it admits, and pin
that `{0000}` really renders an unanchored pair. No existing test is edited.

Diff is comments and one new test file only — no executable line is added,
removed or moved outside tests.

Closes objectstack-ai#7287


Claude-Session: https://claude.ai/code/session_0184Hrx9PcaQ2KMMt88DRZ2c

Co-authored-by: Claude <noreply@anthropic.com>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Aug 17, 2026
…-21 verify-before-toggle, PR subscription as hard step (objectstack-ai#7755) (objectstack-ai#7794)

Three validated deltas from the 2026-08-11 spec-lane shift, per the
maintainer's same-day rulings, plus the bbe056b rider folded in:

- mode:cloud backend: record the cross-session-messaging roster limitation
  (CCR cloud sessions unreachable via ListAgents/SendMessage) and the ruling
  that the event-driven architecture is the standing solution — no roster
  re-tests, no local-machine migration.
- Operational note 21, amended in place: after an empty-field enable, verify
  queue membership FIRST (queue branch, ~20-30s allowance); never issue a
  disable between an enable and its verification — an out-of-order 'added to
  merge queue' webhook means that disable can cancel a real enqueue
  (objectstack-ai#7655 negative / objectstack-ai#7657 positive, same day).
- 入队与落地 B / mode:cloud delivery contract: cloud-card PR subscription
  elevated from accompanying-discipline bullet to numbered hard step
  (lesson 4), with the objectstack-ai#7672 vs objectstack-ai#7655/objectstack-ai#7657 A/B measurement; the bullet
  stays as a pointer.
- Rider bbe056b (cloud-card archival) folded into the B-section archival
  bullet: merged+report-reviewed criteria, review_ready batch sweep, the
  never-archive-before-merge boundary (objectstack-ai#7265/objectstack-ai#7325), unarchive caveat.

Ratchet headroom was 0 (ceiling 2568): all additions offset in-file by
moving residual narrative verbatim to references/incidents.md (append-only,
five new sections with provenance) and reflowing touched paragraphs.
SKILL.md stays exactly 2568 lines; MAX_LINES untouched.

Fixes objectstack-ai#7755

Co-authored-by: Claude <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 size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants