Skip to content

rest/metadata-protocol: one unique conflict, two wire spellings on ONE route — a batch/import ROW reports DUPLICATE_RECORD while the whole-request failure on the same route reports UNIQUE_VIOLATION #14723

Description

@os-trump

Filed under the contract review on #14541 (verdict PASS WITH CONDITIONS, condition 2), which required the residue to be disclosed and given a card rather than implied away. ⛔ This card does not pick a winner; that is the decision it exists to carry.

The fork

Two spellings of one condition — a driver's unique-constraint refusal — reach a client on the same route, chosen by whether the failure was whole-request or per-row.

Row level — DUPLICATE_RECORD. packages/metadata-protocol/src/protocol.ts's toRowApiError puts a thrown REGISTERED code on the row verbatim (ErrorCode.safeParse(err.code).success ? err.code : …), so a DuplicateRecordError raised for one row of POST /api/v1/data/:object/batch or POST /api/v1/batch reports code: 'DUPLICATE_RECORD', httpStatus: 409. Pinned at packages/runtime/src/batch-row-driver-text-real-driver.integration.test.ts:282. packages/rest/src/import-runner.ts:293 (first?.code ?? e?.code ?? 'IMPORT_ROW_FAILED') does the same for import row reports, and #14095's changeset records that as deliberate ("Import row reports improve … now DUPLICATE_RECORD").

Whole-request level — UNIQUE_VIOLATION. The single-record /data door has answered UNIQUE_VIOLATION since #6250/#7821, and #14389 restored it there for the envelope; #14541 restores it on the bulk / import / metadata / UI doors. So after #14541 a whole-request failure on POST /data/:object/batch or POST /data/:object/import answers UNIQUE_VIOLATION while a row-level failure on that same route answers DUPLICATE_RECORD.

Neither half is a regression and neither was introduced by #14541 — the fork has existed between the single door and the rows since #14389. What #14541 does is move the UNIQUE_VIOLATION side onto the routes whose ROWS speak the other spelling, so the two now sit side by side in one response for the first time, and that is why it wants deciding rather than living on.

The text this sits against

packages/spec/src/api/error-code-ledger.zod.ts's header:

a code either IS the standard member for its condition, or it is registered here — and if it merely re-spells a standard member, that registration is a recorded waiver

There is no waiver pairing UNIQUE_VIOLATION with DUPLICATE_RECORD, and standardSynonymOf('UNIQUE_VIOLATION') answers undefined because the token prong under-matches by design. ADR-0112's "one name per concept" is the other half.

What a decision has to weigh (not decided here)

Not claimed

  • No measurement here says a consumer is broken today. Both codes are registered, both bodies parse, and the fork is stable rather than newly introduced.
  • I did not census out-of-repo consumers of either spelling.

Activity

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions