Repository navigation
service-messaging still carries its own hand-written isUniqueViolation() — migrate it onto the shared @objectstack/types predicate #6542
Description
Activity
Findings triage: re-graded —
findingremoved,pm:blockedapplied (labels changed together with this comment).- Why not an observation: this card is a ruled follow-up, filed by the 唯一约束冲突没有单一判别谓词:仓内四套各自为政的方言词表,REST 的 409 映射漏掉 MySQL(
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. - Why not
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. - State pairing: the body already carries the machine half (
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
- Why not an observation: this card is a ruled follow-up, filed by the 唯一约束冲突没有单一判别谓词:仓内四套各自为政的方言词表,REST 的 409 映射漏掉 MySQL(
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 commented
on Aug 8, 2026 CollaboratorAuthorMore actionsCLAIM — 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
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.tsdeclares a privateisUniqueViolation(err)— the most complete of the four copies #6250 inventoried, and the oneisUniqueViolationErrorin@objectstack/typeswas seeded from:One call site: the lost check-then-act race on the inbox read-receipt unique index (
isUniqueViolation(err) && (await flipToRead())).Why this is
findingand not a defectNo 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
findinglabel as "small".The migration
@objectstack/service-messagingalready 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:errno 1062(the numeric field mysql2 sets besidecodefor the same condition);unique constraintandunique violation(inherited from the REST limb 唯一约束冲突没有单一判别谓词:仓内四套各自为政的方言词表,REST 的 409 映射漏掉 MySQL(Duplicate entry落成 500 INTERNAL_ERROR) #6250 replaced), sounique constraint failedis subsumed;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.