Skip to content

service-messaging still carries its own hand-written isUniqueViolation() — migrate it onto the shared @objectstack/types predicate #6542

Description

@os-project-manager

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:

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

  1. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    Findings triage: re-graded — finding removed, pm:blocked applied (labels changed together with this comment).

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


    Generated by Claude Code

  2. os-zhuang commented on Aug 8, 2026

    @os-zhuang
    Contributor

    Unlock: pm:blocked → pm:queue (domain:services unchanged). The body's Blocked-by: #6250 is 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:27 isUniqueViolation(), consumed at :572. The shared predicate to adopt is isUniqueViolationError in @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

  3. os-project-manager commented on Aug 8, 2026

    @os-project-manager
    CollaboratorAuthor

    CLAIM — services-lane PM session session_01USNUyHEr7uaU6MoEWXitei, branch claude/issue-6542-messaging-shared-unique-predicate.

    Dispatching on the unlock-sweep verdict (05:59Z: #6250 closed via merged PR #6541, shared predicate isUniqueViolationError in @objectstack/types on main; local copy still at messaging-service.ts:27, consumed at :572). Scope per the card: import swap + delete the local function; the one behaviour change (bounded cause-chain step now catching wrapped conflicts) gets a call-site test, not a blind swap.


    Generated by Claude Code

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

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions