Skip to content

feat(dm): sub-classify DmUnwrapResult.malformed via code discriminator #321

@realmeylisdev

Description

@realmeylisdev

Spun off from #312 (review feedback from @jalcine).

Problem

unwrapDmGiftWrap in src/lib/dm.ts collapses six distinct malformed conditions into a single { ok: false; reason: 'malformed' } return:

  • seal JSON parse failure (dm.ts:414)
  • seal kind != DM_SEAL_KIND OR seal sig invalid (dm.ts:416-418)
  • rumor JSON parse failure (dm.ts:430-431)
  • rumor doesn't match isRumorEvent shape OR kind != DM_RUMOR_KIND (dm.ts:433-434)
  • seal.pubkey !== rumor.pubkey — attestation mismatch (dm.ts:436-437)
  • rumor hash mismatch (dm.ts:439-440)

The first four are "this isn't a valid kind-13/kind-14 event." The last two are "the event is structurally valid but its claims don't add up — someone may have forged it." These are not operationally equivalent, but the current shape makes them indistinguishable to consumers.

Proposed shape

Mirror InviteApiError.code at src/lib/inviteApi.ts:21-31 — a code discriminator on the existing malformed reason, not a new top-level reason:

export type DmUnwrapResult =
  | { ok: true; rumor: DmRumorEvent }
  | { ok: false; reason: 'decrypt-failed'; cause: unknown }
  | { ok: false; reason: 'malformed'; code: 'decode-failed' | 'attestation-mismatch' };

decode-failed = first four conditions. attestation-mismatch = last two.

Acceptance criteria

  • DmUnwrapResult.malformed carries a code discriminator
  • src/lib/dm.test.ts covers both code values
  • useDmInboxStatus behavior is unchanged (still triggers 'unavailable' only when decryptFailures === fetchedCount)
  • Either: fetchDmMessages keeps a single malformedCount (telemetry reads code per-event via debugLog), OR splits into decodeFailures/attestationMismatches if a UI consumer is also in scope. Decide in the implementation PR.

Out of scope

  • UI surfacing of attestation mismatch (forgery banner, etc.)
  • Changes to decrypt-failed

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions