Skip to content

De-fallback the engine's autonumber render path — read the declared {0000} default instead of parsing the empty string (#6555 half 2/3) #7262

Description

@os-zhuang

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 engine-core).

The ruling (verbatim, comment 5225535766)

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).

The change

packages/objectql/src/engine.tsapplyAutonumbers, currently (re-measured on origin/main @ 3566e55; the line drifted from the :2115 triage cited):

// :2553
const fmt = (def as any).autonumberFormat ?? (def as any).format;
// :2554
const tokens = parseAutonumberFormat(typeof fmt === 'string' ? fmt : '');

An undeclared format parses to an empty token list, which renderAutonumber renders through its width === null branch as a bare counter1, 2, …. Replace both lines with the contract:

const tokens = parseAutonumberFormat(resolveAutonumberFormat(def as never));

resolveAutonumberFormat / DEFAULT_AUTONUMBER_FORMAT are exported from @objectstack/spec/data by the blocking PR; engine.ts:24 already imports from that entry.

This one MOVES behaviour — unlike its driver-sql sibling

This is the half the ruling's compatibility note is about. A deployment whose driver does not advertise supports.autonumber (the in-memory driver, any driver without the capability) flips from 1 to 0001 for newly issued numbers on format-less fields. Counter continuity is untouched — #6468 pinned seeding parity, and both sides already agreed on the counter value.

Two consequences worth handling explicitly:

Suggested gates

  • packages/objectql autonumber suites (engine-autonumber-*.test.ts).
  • packages/runtime/src/autonumber-seed-cross-side-parity.integration.test.ts — the cross-side pin; this card is what should make it able to assert a shared rendering, not just a shared counter. Worth extending it with the bug report's exact reproduction: { rec_no: { type: 'autonumber' } }, rows 1 / 2 / 10 already stored, both sides expected to mint 0011.
  • Changeset carrying the compatibility note verbatim.

Not in this card

driver-sql's two || '{0000}' sites — its own card. #6555 stays open until both land.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions