Record the smsIds decision under the surface it changes

This commit is contained in:
2026-09-12 22:41:02 +02:00
parent 13e3ff8f1f
commit 549b3260c9
+25 -24
View File
@@ -320,6 +320,31 @@ Grouped by what each one constrains.
without types gets. `smppTime.encode()` is where that reasoning lands the other way and is recorded without types gets. `smppTime.encode()` is where that reasoning lands the other way and is recorded
under [The wire](#the-wire): `Date | number | string` is not a closed set, so it is a `Result`. under [The wire](#the-wire): `Date | number | string` is not a closed set, so it is a `Result`.
- **A segment the SMSC took and named no id for is `undefined` in `smsIds`, not an empty string.**
Maintainer's call, 2026-09-12: `paramText()` resolves an absent `message_id` and one a peer wrote
empty to the same `''`, which `string[]` then presented as an id — taking `smsIds[0]`, or keying a
correlation table by the array, compiled and then misbehaved, and one message's empty entry
collides with another's. Telesign names an id for the first segment of a concatenated submit only,
so it is a documented operator's shape rather than a hypothesis. Nothing else moves:
`parseSegmentId('')` matched nothing, so `DlrMerger` already abandoned such a send and `undefined`
reaches that same refusal. `expect()` takes the wider type rather than a filtered `string[]`
because the arity is what `idNumbering()` refuses on: filtering `['a-1', undefined, 'a-3']` leaves
a numbering that spells out a whole message, and merges one that was never whole. `dlrFromPdu()`
reads an id through `nonEmptyText()`, so no receipt could ever have matched an empty entry — and
that reading stays separate from this one rather than sharing a helper, since it must leave a
Buffer-valued `receipted_message_id` unresolved for `messageType()` to read the PDU as unmarked.
What settles it here is the resolved text rather than the parameter, because `writeParams()`
substitutes the field's own default: a peer that omits `message_id` and one that writes it empty
build the same octets, leaving a raw-parameter test nothing to tell apart. Rejected: keeping `''`
and documenting it, which leaves the published type promising what the value does not keep —
goal 2, a wrong answer about what the peer named. Rejected: dropping the unnamed entries, which
breaks the positional correspondence with `pduObjs` that README promises and loses which segment a
PDU belongs to. Rejected: `{ id?: string; pduObj: PduObject }[]`, which makes that positional
promise structural where today the compiler cannot check it; deferred to the next major, the first
place two documented fields may become one. Accepted: every consumer reading `smsIds` narrows,
including the majority whose SMSC names every id; indexing narrows too, except for the consumer
who sets `noUncheckedIndexedAccess`, which typed `smsIds[0]` as `string | undefined` already.
### The wire ### The wire
- **The declared interface version is an option on both `client()` and `server()`, and is not the - **The declared interface version is an option on both `client()` and `server()`, and is not the
@@ -944,30 +969,6 @@ Grouped by what each one constrains.
only because nothing stops the reconnect loop without `emitClose()` following it: `drain()` and only because nothing stops the reconnect loop without `emitClose()` following it: `drain()` and
`end()` are the only callers of `stop()`. A third caller has to shut the gate itself. `end()` are the only callers of `stop()`. A third caller has to shut the gate itself.
- **A segment the SMSC took and named no id for is `undefined` in `smsIds`, not an empty string.**
Maintainer's call, 2026-09-12: `paramText()` resolves an absent `message_id` and one a peer wrote
empty to the same `''`, which `string[]` then presented as an id — taking `smsIds[0]`, or keying a
correlation table by the array, compiled and then misbehaved, and one message's empty entry
collides with another's. Telesign names an id for the first segment of a concatenated submit only,
so it is a documented operator's shape rather than a hypothesis. Nothing else moves:
`parseSegmentId('')` matched nothing, so `DlrMerger` already abandoned such a send and `undefined`
reaches that same refusal. `expect()` takes the wider type rather than a filtered `string[]`
because the arity is what `idNumbering()` refuses on: filtering `['a-1', undefined, 'a-3']` leaves
a numbering that spells out a whole message, and merges one that was never whole. `dlrFromPdu()`
reads an id through `nonEmptyText()`, so no receipt could ever have matched an empty entry — and
that reading stays separate from this one rather than sharing a helper, since it must leave a
Buffer-valued `receipted_message_id` unresolved for `messageType()` to read the PDU as unmarked.
What settles it here is the resolved text rather than the parameter, because `writeParams()`
substitutes the field's own default: a peer that omits `message_id` and one that writes it empty
build the same octets, leaving a raw-parameter test nothing to tell apart. Rejected: keeping `''`
and documenting it, which leaves the published type promising what the value does not keep —
goal 2, a wrong answer about what the peer named. Rejected: dropping the unnamed entries, which
breaks the positional correspondence with `pduObjs` that README promises and loses which segment a
PDU belongs to. Rejected: `{ id?: string; pduObj: PduObject }[]`, which makes that positional
promise structural where today the compiler cannot check it; deferred to the next major, the first
place two documented fields may become one. Accepted: every consumer reading `smsIds` narrows,
including the majority whose SMSC names every id; indexing narrows too, except for the consumer
who sets `noUncheckedIndexedAccess`, which typed `smsIds[0]` as `string | undefined` already.
### Internals and tests ### Internals and tests