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
Spun off from #312 (review feedback from @jalcine).
Problem
unwrapDmGiftWrapinsrc/lib/dm.tscollapses six distinct malformed conditions into a single{ ok: false; reason: 'malformed' }return:dm.ts:414)DM_SEAL_KINDOR seal sig invalid (dm.ts:416-418)dm.ts:430-431)isRumorEventshape OR kind !=DM_RUMOR_KIND(dm.ts:433-434)seal.pubkey !== rumor.pubkey— attestation mismatch (dm.ts:436-437)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.codeatsrc/lib/inviteApi.ts:21-31— acodediscriminator on the existingmalformedreason, not a new top-level reason:decode-failed= first four conditions.attestation-mismatch= last two.Acceptance criteria
DmUnwrapResult.malformedcarries acodediscriminatorsrc/lib/dm.test.tscovers both code valuesuseDmInboxStatusbehavior is unchanged (still triggers'unavailable'only whendecryptFailures === fetchedCount)fetchDmMessageskeeps a singlemalformedCount(telemetry readscodeper-event viadebugLog), OR splits intodecodeFailures/attestationMismatchesif a UI consumer is also in scope. Decide in the implementation PR.Out of scope
decrypt-failed