Repository navigation
Description
Blocked-by: #6250 (the shared predicate must land first — PR #6541)
Filed by the #6250 developer agent. This consumer was in #6250's declared scope and was removed mid-task by a PM scope narrowing, so it is recorded here rather than silently dropped.
Why it was deferred (not forgotten)
#4633 was dispatched in parallel and its consuming half lands squarely in the import path, packages/rest/src/import-runner.ts included. Two agents editing that file is a merge race that costs more than serialising. The migration was always the incidental half of #6250, never the user-visible fix — that half (MySQL conflicts returning 409 UNIQUE_VIOLATION) shipped in PR #6541.
What is left
sanitizeRowError in import-runner.ts:
const unique =
/unique constraint failed:\s*([^\s,)]+)/i.exec(msg) ?? // sqlite
/duplicate entry .* for key '([^']+)'/i.exec(msg) ?? // mysql
/duplicate key value violates unique constraint.*?[Kk]ey \(([^)]+)\)/is.exec(msg); // postgresCorrection to #6250's inventory, for whoever picks this up: that issue's table records this row as "一条 Postgres 专用正则 ... 仅 Postgres". That has drifted. All three dialect limbs have been present since #3572 (git log -S "duplicate entry .* for key" confirms) — the line number the issue cites is the third limb of a three-way chain, not the whole of it. The row is still a private vocabulary; it is just not the narrowest one.
Why this is not a mechanical swap
This site does more than answer yes/no: it extracts the offending column so the importer can say "A record with this email already exists." isUniqueViolationError deliberately answers the boolean only — #6250's ruling put the structured conflict-column export out of scope as a new contract surface.
So this migration needs one of:
- (a) keep the column-extracting regexes and use the predicate only as the gate — buys little, and leaves the vocabulary forked; or
- (b) land a
uniqueViolationColumn(error)sibling in@objectstack/typesfirst, which is the actual convergence. PR fix(types,rest): one named unique-violation predicate — MySQL conflicts return 409 UNIQUE_VIOLATION, not 500 (#6250) #6541's structure already separates the channels per dialect, so the shape slots in without restructuring — but it is a contract decision, not a refactor: when a dialect names an index (for key 'idx_email_unique') rather than a column (Postgres'Key (email)=(...)), which does the export promise? A caller that renders "already exists" into a form field needs the column; an index name mis-rendered there is worse than no answer.
Recommend (b), and note that #5495's autonumber-retry branch wants the same export for a different reason (retry only when the conflicting column IS the autonumber field), so the two callers should agree on the answer before either is built. That decision is the maintainer's, per #6250's ruling — this card should not settle it unilaterally.
Activity
os-zhuang commentedon Aug 8, 2026
Findings round: held —
findingstays, with the blocker note updated.Stale-premise check: the body's
Blocked-by: #6250is now satisfied — #6250 closed as completed at 04:55Z via merged PR #6541, which shipsisUniqueViolationErrorin@objectstack/typesand explicitly names this migration "a follow-up per lane, now unblocked". The remaining private vocabulary is confirmed still onorigin/main@a36db28(import-runner.ts'ssanitizeRowErrorthree-limb chain).Why held rather than queued or inboxed: the card's own analysis is that the honest route is (b) — a
uniqueViolationColumn(error)sibling — and that this is a contract decision #6250's ruling explicitly reserved to the maintainer (column vs index-name promise), with #5495's autonumber-retry branch needing the same answer. Route (a) is mechanical but "buys little" by the card's own words. No user-visible defect remains on this path (the 409 fix shipped), so parking a low-urgency contract question in an inbox that already holds five pending rulings would be queue-jumping.Restart condition: #5495's autonumber-retry branch is picked up, or any consumer concretely needs the conflicting-column export — at that moment this promotes to
needs-user-decisionwith the column-vs-index question as the single ask, and the two callers get one answer.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
维护者裁定(2026-08-08,cli 席存档):取路线 (b) —— 先在
@objectstack/types落uniqueViolationColumn导出,再迁 import-runner。Blocked-by: #6250已解除(PR #6541 已合并88f9d94bb)。卡转可派。语义裁定(本卡此前最大的未决点):当方言报的是索引名(MySQL 的
for key 'idx_email_unique')而非列名(Postgres 的Key (email)=(...))时,uniqueViolationColumn返回undefined—— 只在能确定是列名时才给值。理由:调用方要把它渲染到表单字段上,一个被当成列名的索引名比没有答案更糟 —— 前者会把用户指向一个不存在的字段,后者只是退回通用文案。#5495 的 autonumber 重试分支同理:它要判断「冲突列是否就是 autonumber 字段」,一个索引名在那里会给出错误的重试决策。代价是 MySQL 部署上常常拿不到列名,这是明知并接受的。
Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
CLAIM — session
session_017uFVNMmTxLpmfQYiuKM1Yx, branchclaude/issue-6544-unique-violation-column.按维护者 2026-08-08 裁定(comment 5226102517)实施路线 (b):先在
@objectstack/types落uniqueViolationColumn导出(索引名一律返回undefined),再把packages/rest/src/import-runner.ts的sanitizeRowError迁移到该导出上。不触碰rest-server.ts/rest-api-plugin.ts(#6633 在改),也不迁service-messaging/driver-sql的副本(按 lane 另行跟进)。Generated by Claude Code
fix(rest): name the conflicting field in the UNIQUE_VIOLATION 409 body (
fix(driver-memory): enforce object-level declared `indexes[]` uniquen…
fix(driver-memory): enforce object-level declared `indexes[]` uniquen…