Make a failed send the whole message's answer, with no merged report after it #23

Merged
lilleman merged 2 commits from goal-2-whole into main 2026-09-23 23:39:25 +02:00
2 changed files with 10 additions and 12 deletions
+8 -5
View File
@@ -322,9 +322,10 @@ const { err, pduObjs, smsIds, unanswered } = await session.sendSms({ from, messa
- One id per segment. `smsIds` is positional with `pduObjs`, and an entry is `undefined` where the - One id per segment. `smsIds` is positional with `pduObjs`, and an entry is `undefined` where the
SMSC took the segment without naming an id; some name one for the first segment only. No receipt SMSC took the segment without naming an id; some name one for the first segment only. No receipt
ever carries an empty id, so an unnamed entry matches nothing. ever carries an empty id, so an unnamed entry matches nothing.
- `err` is set when the SMSC refuses a segment, naming the status. Every segment goes out together, - `err` is set when the SMSC refuses a segment, naming the status, or leaves one unanswered. Every
so `pduObjs` and `smsIds` then hold what was accepted: enough to reconcile a later receipt, not segment goes out together, so `pduObjs` and `smsIds` then hold only the accepted segments, in send
enough to resend the rest. Treat a partial failure as a failed message. order: enough to reconcile a later receipt, not enough to resend the rest. Treat a partial failure
as a failed message; its accepted segments report through `dlr` alone.
- `unanswered` counts segments that went out and were never answered. The SMSC may have taken each - `unanswered` counts segments that went out and were never answered. The SMSC may have taken each
and lost only the response, so a message with `unanswered` above zero cannot be resent without and lost only the response, so a message with `unanswered` above zero cannot be resent without
risking a duplicate. risking a duplicate.
@@ -480,7 +481,8 @@ carrying the worst status of the segments and each of them under `segments`. An
report never counts. Merging needs the SMSC to number its segment ids `<base>-<n>`, this library's report never counts. Merging needs the SMSC to number its segment ids `<base>-<n>`, this library's
own server's convention; an SMSC that hands out unrelated ids per segment never fires it. A base is own server's convention; an SMSC that hands out unrelated ids per segment never fires it. A base is
merged once: a later message the SMSC gives the same ids is reported through `dlr` alone, and an merged once: a later message the SMSC gives the same ids is reported through `dlr` alone, and an
earlier one still collecting loses its merged report. earlier one still collecting loses its merged report. A send that returned an `err` never fires `messageDlr`,
even where the SMSC took some of its segments; their receipts still arrive as `dlr`.
## Server in depth ## Server in depth
@@ -658,7 +660,8 @@ one wins. They do not override the hard rules below.
nothing, so nothing the library concludes may rest on one; a request the peer may already have nothing, so nothing the library concludes may rest on one; a request the peer may already have
taken is never re-sent on the library's own initiative; work the peer has no reason to send again taken is never re-sent on the library's own initiative; work the peer has no reason to send again
is not dropped; a call that reports a message as sent asserts that the wire carried what the caller is not dropped; a call that reports a message as sent asserts that the wire carried what the caller
wrote, so a value we cannot send as given is refused before anything goes out. wrote, so a value we cannot send as given is refused before anything goes out; each message gets
one outcome as a whole, so a send that fails is that outcome and no merged report follows it.
3. **Strict in what we send, generous in what we read.** The library's own senders follow 3.4, and 3. **Strict in what we send, generous in what we read.** The library's own senders follow 3.4, and
the codec parses whatever arrives. Where the letter of the spec would discard traffic a real SMSC the codec parses whatever arrives. Where the letter of the spec would discard traffic a real SMSC
sends, keep the traffic. sends, keep the traffic.
+2 -7
View File
@@ -202,13 +202,8 @@ and is also what the panel ranked hardest — two methods, one answer.
`broadcast_sm_resp` is declared, set once and read nowhere. This is the "Dormant filters" row `broadcast_sm_resp` is declared, set once and read nowhere. This is the "Dormant filters" row
of the 0.4.0 defect table in a new spelling — metadata that reads as a guarantee. of the 0.4.0 defect table in a new spelling — metadata that reads as a guarantee.
- [ ] **Arm the merge for the segments the SMSC did take, or say why not.** `collectSent()` sets - [ ] **Test that a multipart send which errors never fires `messageDlr`.** Goal 2 now says so and
`failure` if any segment errored, including the `UnansweredError` a mid-send drop produces, and README promises it; `session-extras.test.ts` covers a drop *after* the send, not one during it.
`session.ts` only calls `dlrMerger.expect(sent.smsIds)` when `!sent.err`. So a link drop during
a multipart send leaves per-segment `dlr` events firing while `messageDlr` never can, traced
only by one `debug` line. `session-extras.test.ts` has the adjacent case — a drop *after* the
send — and not this one. If goal 2 forbids reporting on a message we cannot fully account for,
that is the answer; it is stated in no file today either way.
- [ ] **Return an `err` where `message` is not a string, rather than throwing.** - [ ] **Return an `err` where `message` is not a string, rather than throwing.**
`sendSms({ message: undefined })` — a forgotten property — reaches `value.replace()` in `sendSms({ message: undefined })` — a forgotten property — reaches `value.replace()` in