From 9724dd1751dda653402e3e0646784fda646b49ba Mon Sep 17 00:00:00 2001 From: Lilleman auf Larv Date: Sun, 27 Sep 2026 16:41:47 +0200 Subject: [PATCH] Record #30 merging under the comprehension floor, and put Locality first in 0.6.0 --- AGENTS.md | 1 + docs/decisions.md | 6 +++ todo.md | 104 ++++++++++++++++++++++++---------------------- 3 files changed, 61 insertions(+), 50 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3b858a5..b93c9c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -315,6 +315,7 @@ the file. ### [Internals and tests](docs/decisions.md#internals-and-tests) +- #30 merged under the comprehension floor, and Locality is the next work. - A listener that rejects is routed by Node's `captureRejections`, not by hand-dispatching. - The four-line abort dance is copied across `LinkGate`, `IdleWaiters`, `PendingRequests` and `SendWindow` rather than extracted. diff --git a/docs/decisions.md b/docs/decisions.md index 28df36d..6f1e095 100644 --- a/docs/decisions.md +++ b/docs/decisions.md @@ -775,6 +775,12 @@ rule and an index of the titles below. ## Internals and tests +- **#30 merged under the comprehension floor, and Locality is the next work.** Maintainer's call, + 2026-09-27. A four-seat scoring run, depth 1, read the project at 6, 6, 7 and 6 (mean 6.25), every + seat capped by Locality in the held-message and shutdown code #30 does not touch, where the floor + is 7.0. The chunks after #30 lift Locality to 7 before any other work. Serves goal 8's reshapeable + internals, which a reader has to understand before reshaping. Valid until a scoring run reads 7.0 or above. + - **A listener that rejects is routed by Node's `captureRejections`, not by hand-dispatching.** Both emitters construct with `captureRejections: true` and implement `[EventEmitter.captureRejectionSymbol]`, which lands a rejected `async` listener on `sessionError` diff --git a/todo.md b/todo.md index 79cc2bb..94a0cc6 100644 --- a/todo.md +++ b/todo.md @@ -187,57 +187,11 @@ dimensions, higher where it is cheap. Maintainer's call, 2026-09-20. A systems-a same day returned ALIGN with one blocking-severity finding, which is the first item under Locality and is also what the panel ranked hardest — two methods, one answer. -### Correctness, ahead of everything below +A four-seat scoring run on 2026-09-27 read #30 at 6, 6, 7 and 6, every seat capped by Locality in +the held-message and shutdown code; #30 merged under the floor on condition that Locality is the +next work ([decision](docs/decisions.md#internals-and-tests)). -- [ ] **Settle what a repeated tag not marked `multiple` reads as, and pin it in a test.** A vendor - tag or a known single-value tag a peer sends twice keeps the last occurrence and drops the - rest silently, which goal 3 argues against; listing it would change every such tag's shape. - From the architecture review of #25. - -- [ ] **Test that a multipart send which errors never fires `messageDlr`.** Goal 2 now says so and - README promises it; `session-extras.test.ts` covers a drop *after* the send, not one during it. - -- [ ] **Return an `err` where `message` is not a string, rather than throwing.** - `sendSms({ message: undefined })` — a forgotten property — reaches `value.replace()` in - `defs/encodings.ts` through the alphabet detection `checkOptions()` runs, and the `TypeError` - escapes `submitSms()` into the caller's process; `NaN` and `12345` do the same. README promises - "Never throws. Every fallible call resolves to `{ err?, … }`" and AGENTS.md hard rule 1 says it - again, so the docs are false for the likeliest caller mistake there is. From the stability - review of #18. - -- [ ] **Derive `sm_length` for a numeric body, or refuse one.** `resolveBody()` in `pdu.ts` reads the - length only where the body is a Buffer or a string, so - `objToPdu({ cmdName: 'submit_sm', params: { short_message: 12345 } })` writes `sm_length: 0`, - then five octets after it, and reports success — and this library's own parser refuses what it - built, as "TLV 12594 runs past the end of the PDU". Goals 1 and 2. From the stability review - of #18. - -- [ ] **Settle which numbers may spell a text field, refuse the rest, and say so where a consumer - reads it.** `wantText()` takes every finite number through `String()`, so `message_id: 1e21` - writes `1e+21`, `from: 0.1 + 0.2` writes `0.30000000000000004` and `source_addr: -5` writes - `-5` — none of them is the id or the address the caller meant, and all three are reported as - sent. The numeric branch exists for a digit sequence (`message_id: 123`); the product-owner - review of #18 recommends `Number.isSafeInteger(value) && value >= 0` with the refusal naming - the fix, since a 64-bit SMSC id loses digits to a JS number before this library ever sees it. - Goals 2 then 3: `from: 1e21` is reported as sent to an address that reaches nobody, which is - the wrong answer about what happened before it is laxness in what we send. That a number is - accepted at all reaches a consumer in no sentence either: only the type comment at - `defs/commands.ts:239`, and one CHANGELOG line that stops being visible when - 0.7.0 is cut, while README's Building bullet reads as the whole rule for a text field. Whether - this is a supported spelling or 0.4.0 tolerance decides whether that sentence lands in - README.md or in MIGRATION.md — write it in the same change as the rule, so it is worded once. - From the stability and product-owner reviews of #18. - -### Throughput — goal 6, and the default window is where we are slowest - -- [ ] **Close the gap to jsmpp at `maxOutstanding: 10`.** Measured 2026-09-20 against the same sink, - 100,000 messages each: this library 25,358/s, jsmpp 30,771/s, Cloudhopper 27,945/s — we are - last at the one window most callers will ever run, while leading Cloudhopper and trailing jsmpp - by only 5% at 50 and 200. So the cost is not the codec, which the higher windows exercise just - as hard; it is something per-request that the window hides once enough requests overlap. - `benchmarks/` reproduces all three. Goal 6. - -### Locality — 5–6 today, and the gate is 7 +### Locality — next, ahead of everything below; 5–6 today, and the gate is 7 - [ ] **Give `IncomingRequests` a port instead of the `Session` it drives.** It holds its owner and calls eight members of it 18 times, including `this.session.close()` on an inbound `unbind` — @@ -296,6 +250,56 @@ and is also what the panel ranked hardest — two methods, one answer. the unit they would least want to touch, because a mistake here does not throw, does not fail the types, and reaches the peer as somebody's message rendered wrong. +### Correctness + +- [ ] **Settle what a repeated tag not marked `multiple` reads as, and pin it in a test.** A vendor + tag or a known single-value tag a peer sends twice keeps the last occurrence and drops the + rest silently, which goal 3 argues against; listing it would change every such tag's shape. + From the architecture review of #25. + +- [ ] **Test that a multipart send which errors never fires `messageDlr`.** Goal 2 now says so and + README promises it; `session-extras.test.ts` covers a drop *after* the send, not one during it. + +- [ ] **Return an `err` where `message` is not a string, rather than throwing.** + `sendSms({ message: undefined })` — a forgotten property — reaches `value.replace()` in + `defs/encodings.ts` through the alphabet detection `checkOptions()` runs, and the `TypeError` + escapes `submitSms()` into the caller's process; `NaN` and `12345` do the same. README promises + "Never throws. Every fallible call resolves to `{ err?, … }`" and AGENTS.md hard rule 1 says it + again, so the docs are false for the likeliest caller mistake there is. From the stability + review of #18. + +- [ ] **Derive `sm_length` for a numeric body, or refuse one.** `resolveBody()` in `pdu.ts` reads the + length only where the body is a Buffer or a string, so + `objToPdu({ cmdName: 'submit_sm', params: { short_message: 12345 } })` writes `sm_length: 0`, + then five octets after it, and reports success — and this library's own parser refuses what it + built, as "TLV 12594 runs past the end of the PDU". Goals 1 and 2. From the stability review + of #18. + +- [ ] **Settle which numbers may spell a text field, refuse the rest, and say so where a consumer + reads it.** `wantText()` takes every finite number through `String()`, so `message_id: 1e21` + writes `1e+21`, `from: 0.1 + 0.2` writes `0.30000000000000004` and `source_addr: -5` writes + `-5` — none of them is the id or the address the caller meant, and all three are reported as + sent. The numeric branch exists for a digit sequence (`message_id: 123`); the product-owner + review of #18 recommends `Number.isSafeInteger(value) && value >= 0` with the refusal naming + the fix, since a 64-bit SMSC id loses digits to a JS number before this library ever sees it. + Goals 2 then 3: `from: 1e21` is reported as sent to an address that reaches nobody, which is + the wrong answer about what happened before it is laxness in what we send. That a number is + accepted at all reaches a consumer in no sentence either: only the type comment at + `defs/commands.ts:239`, and one CHANGELOG line that stops being visible when + 0.7.0 is cut, while README's Building bullet reads as the whole rule for a text field. Whether + this is a supported spelling or 0.4.0 tolerance decides whether that sentence lands in + README.md or in MIGRATION.md — write it in the same change as the rule, so it is worded once. + From the stability and product-owner reviews of #18. + +### Throughput — goal 6, and the default window is where we are slowest + +- [ ] **Close the gap to jsmpp at `maxOutstanding: 10`.** Measured 2026-09-20 against the same sink, + 100,000 messages each: this library 25,358/s, jsmpp 30,771/s, Cloudhopper 27,945/s — we are + last at the one window most callers will ever run, while leading Cloudhopper and trailing jsmpp + by only 5% at 50 and 200. So the cost is not the codec, which the higher windows exercise just + as hard; it is something per-request that the window hides once enough requests overlap. + `benchmarks/` reproduces all three. Goal 6. + ### Shape — 6 today, and the gate is 7 - [ ] **Group `src/` into a second level, and retire whichever record loses.** 34 files on one