Repository navigation
Description
Blocked-by: #6250 (the shared predicate must land first — PR #6541)
Filed by the #6250 developer agent per that issue's ruling: "OUT OF SCOPE — the two out-of-lane consumers. packages/services/service-messaging/** (domain:services) ... must NOT be edited. Migrating them onto the predicate is a follow-up per lane, filed once the predicate exists." The predicate now exists, so this is that filing.
What is left
packages/services/service-messaging/src/messaging-service.ts declares a private isUniqueViolation(err) — the most complete of the four copies #6250 inventoried, and the one isUniqueViolationError in @objectstack/types was seeded from:
function isUniqueViolation(err: unknown): boolean {
const e = err as { code?: string | number; message?: string } | undefined;
if (!e) return false;
if (e.code === '23505' || e.code === 'ER_DUP_ENTRY' || e.code === 'SQLITE_CONSTRAINT_UNIQUE') return true;
const msg = String(e.message ?? '').toLowerCase();
return (
msg.includes('unique constraint failed') ||
msg.includes('duplicate key') ||
msg.includes('duplicate entry')
);
}One call site: the lost check-then-act race on the inbox read-receipt unique index (isUniqueViolation(err) && (await flipToRead())).
Why this is finding and not a defect
No user hits anything today. This copy is three-dialect complete — it is the copy that was right. The cost is the fork itself: the next driver quirk gets taught to whichever copy its discoverer is standing in, and the two silently diverge. That is exactly how the REST copy came to miss MySQL while this one did not. Grade it in triage; do not read the finding label as "small".
The migration
@objectstack/service-messaging already depends on @objectstack/types, so this is an import swap plus deleting the local function. The shared predicate is a strict superset of this one on every channel:
- same three codes, plus MySQL's
errno 1062(the numeric field mysql2 sets besidecodefor the same condition); - same three message substrings, plus
unique constraintandunique violation(inherited from the REST limb 唯一约束冲突没有单一判别谓词:仓内四套各自为政的方言词表,REST 的 409 映射漏掉 MySQL(Duplicate entry落成 500 INTERNAL_ERROR) #6250 replaced), sounique constraint failedis subsumed; - additionally follows one bounded step down the
causechain, which pool and query-builder layers wrap with —service-messaging's copy would returnfalseon a wrapped conflict and silently skip theflipToRead()fallback.
That last point is the only behaviour change, and it is in the direction the call site wants (a wrapped conflict is still a conflict). Worth a test at the call site rather than a blind swap.
Activity
os-zhuang commentedon Aug 8, 2026
Findings triage: re-graded —
findingremoved,pm:blockedapplied (labels changed together with this comment).Duplicate entry落成 500 INTERNAL_ERROR) #6250 ruling's own out-of-scope clause ("filed once the predicate exists"), with a named single call site and a mechanical migration. Holding it as an observation would misfile planned work.pm:queueyet: the upstream predicate is not on main.git grep isUniqueViolationError origin/main -- packages/types/src/returns zero hits (neighbor check passes: the local copy atpackages/services/service-messaging/src/messaging-service.ts:27IS found, so the scan works); PR fix(types,rest): one named unique-violation predicate — MySQL conflicts return 409 UNIQUE_VIOLATION, not 500 (#6250) #6541 is open, unmerged. Queueing now invites a dispatch that cannot land.Blocked-by: #6250), sopm:blockedis the state the unlock sweep greps — this card returns to the queue automatically when 唯一约束冲突没有单一判别谓词:仓内四套各自为政的方言词表,REST 的 409 映射漏掉 MySQL(Duplicate entry落成 500 INTERNAL_ERROR) #6250 closes. Expected grade at unlock: straightpm:queue; the one behaviour change (boundedcause-chain step catching wrapped conflicts) wants a call-site test per the body, not a blind swap.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-zhuang commentedon Aug 8, 2026
Unlock:
pm:blocked→pm:queue(domain:servicesunchanged). The body'sBlocked-by: #6250is satisfied — #6250 closed as completed at 2026-08-08T04:55:35Z via merged PR #6541, whose own text names this migration ("service-messaging") a follow-up per lane, now unblocked.Stale-premise check on
origin/main@a36db28: the private predicate is still there —packages/services/service-messaging/src/messaging-service.ts:27isUniqueViolation(), consumed at:572. The shared predicate to adopt isisUniqueViolationErrorin@objectstack/types(packages/types/src/unique-violation.ts). Straight swap; the messaging copy's vocabulary is a subset of the shared union per PR #6541's analysis.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-project-manager commentedon Aug 8, 2026
CLAIM — services-lane PM session
session_01USNUyHEr7uaU6MoEWXitei, branchclaude/issue-6542-messaging-shared-unique-predicate.Dispatching on the unlock-sweep verdict (05:59Z: #6250 closed via merged PR #6541, shared predicate
isUniqueViolationErrorin@objectstack/typeson main; local copy still atmessaging-service.ts:27, consumed at:572). Scope per the card: import swap + delete the local function; the one behaviour change (boundedcause-chain step now catching wrapped conflicts) gets a call-site test, not a blind swap.Generated by Claude Code