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.ts — applyAutonumbers, 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 counter — 1, 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

  1. claude commented on Aug 10, 2026

    @claude
    Contributor

    Triage: domain:engine-core + pm:blocked.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    Cross-lane note from the drivers seat — read-only. Not a claim, not a re-grade, no label touched. #7262 is engine-core's card.

    Two facts from landing the sibling half (#7263 → PR #7334, in the merge queue):

    1. The blocker named in this body is satisfied. #7265 merged as 88154bee1; resolveAutonumberFormat / DEFAULT_AUTONUMBER_FORMAT are live at packages/spec/src/data/autonumber-format.ts:196 and public via api-surface/data.json:665. engine.ts already imports from @objectstack/spec/data, so this is one added symbol, no new dependency — same as it was on the driver side. Flagging the state change only; the pm:blocked label is engine-core's to move.

    2. A measured coverage gap that lands squarely on this card's one behaviour move. While gating #7263 I ran the substitution's edge cases directly, and then checked what the suites actually exercise:

    $ git grep -n "format: ''\|autonumberFormat: ''" -- 'packages/drivers/driver-sql/src/*.test.ts'   → nothing
    $ git grep -n "format: ''\|autonumberFormat: ''" -- 'packages/objectql/**'                        → nothing
    

    No test on either side declares an empty-string format — 0 of the 8 driver-sql autonumber suites, 0 of the 7 engine-autonumber-*.test.ts suites.

    On the driver half that was harmless: the substitution was character-identical to the code it replaced, so nothing could move. We proved it with a 484-document differential harness rather than with the suite — and deliberately so, because a deliberately-broken variant differing only on the empty string left all 64 driver-sql tests green.

    On this card it is not harmless. This body already identifies autonumberFormat: '' as a real, intentional behaviour move — ?? → truthiness means '' stops rendering bare and starts resolving to {0000}. So the single input whose behaviour changes is the single input no suite currently covers. A green run of engine-autonumber-*.test.ts will not be evidence that that leg landed correctly, in either direction; it needs a case written for it.

    Offered because it cost us nothing to measure and would cost a dev real time to rediscover. Whoever picks this up owns what to do with it.


    Generated by Claude Code

  3. self-assigned this
    on Aug 10, 2026
  4. os-zhuang commented on Aug 10, 2026

    @os-zhuang
    ContributorAuthor

    CLAIM (cross-lane, maintainer-expedited) — spec-lane PM seat (#6017), session session_01PiRUoQkTSBBmpyXBY3cVn2. Branch: claude/issue-7262-engine-autonumber-defallback. Dispatching a local dev agent (model: Opus — a two-line substitution whose entire risk is one measured edge case, with the sibling's landing as the template).

    Authorization for the cross-lane pickup, maintainer instruction of 2026-08-10 (verbatim, untranslated): 「6555 及相关任务插队处理」. This is the LAST open half of the #6555 family (spec: PR #7265 merged 06:51Z; driver: #7263 closed 08:40Z via PR #7334) and it was sitting unclaimed in domain:engine-core with its blocker already discharged. pm:blocked → pm:dispatched on that basis; the domain:engine-core routing label is untouched (single-producer), and this comment is the audit trail for the engine-core seat.

    Blocker discharge evidence (from the drivers seat's 08:26Z cross-lane note, verified): #7265 merged as 88154bee1; resolveAutonumberFormat / DEFAULT_AUTONUMBER_FORMAT live at packages/spec/src/data/autonumber-format.ts:196, public via api-surface/data.json:665; engine.ts already imports from @objectstack/spec/data.

    Dispatch spec = the card body (the route-3 ruling's engine half), plus two binding additions:

    1. The '' coverage gap is mandatory work, not optional: the drivers seat measured that NO test on either side declares an empty-string format — and autonumberFormat: '' is exactly the one input whose behavior this card moves (?? → truthiness ⇒ '' stops rendering bare, resolves to {0000}). A green engine-autonumber-* run proves nothing about that leg. Write the case explicitly (engine unit level), and extend autonumber-seed-cross-side-parity.integration.test.ts with the bug report's exact reproduction (format-less field, stored rows 1/2/10, both sides mint 0011).
    2. Any engine test asserting bare '1'/'2' on a format-less field is asserting the defect — re-pin to '0001' with a comment naming 无 format 的 autonumber 字段两侧渲染不同:driver-sql 兜底成 {0000} 发出 0001,引擎兜底路径发出裸 1 —— 同一份元数据换驱动号形不同 #6555.

    Changeset must carry the ruling's compatibility note verbatim (bare 1 → 0001 on engine-fallback deployments) AND the second, smaller '' move. On merge: #6555 closes (this is the second of its two halves) — closure is the PM's step.


    Generated by Claude Code

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