diff --git a/AGENTS.md b/AGENTS.md index d032a79..42e8372 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -12,45 +12,11 @@ not for structure or style. ## Goals -In priority order, and the order is the point: where two of them pull against each other, the earlier -one wins. They do not override the hard rules below. +The nine goals, in priority order, live in +[README.md](https://gitea.larvit.se/larvit/smpp-js/src/branch/main/README.md#goals) — they say where this library is heading, which an outside +reader judges it by. The README states the audience alongside them. Everything below cites a goal by +number. -1. **Correct on the wire.** SMPP 3.4 as SMSCs actually run it. Every other goal yields to this one; - the defect table below is what the alternative costs. -2. **Never give the application a wrong answer about what happened.** An outcome we cannot determine - is reported as undetermined rather than guessed; a report the peer marked as not final settles - 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 - is not dropped. -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 - sends, keep the traffic. -4. **A peer an operator never has to complain about.** No bind flooding, nothing a bind direction - forbids, no optional parameters to a peer that declared none, nothing held without a bound. -5. **The session layer is in here, and its defaults are what most applications should run.** - Keepalive, reconnect, the send window, reassembly and receipt correlation. What the network says - about a message the application sent reaches it as a report rather than as an inbound message, and - says whether it is final, so nothing has to read the PDU to tell those apart. An option retunes a - default or opts out of it; an option does not switch on the thing the caller obviously wanted. -6. **Configurable and extendable, never at the defaults' expense.** Where an application needs other - than the default and cannot build it from what is exported — a rate limit counted per PDU, an - alphabet, a receipt format — it gets an option or a hook rather than a fork. A call that passes no - options stays exactly as easy and as safe, and a hook is a seam the library calls, never a way into - its internals. -7. **A small, stable public surface over reshapeable internals.** Only what `src/index.ts` exports is - published. A new option has to beat "the application can do this itself", and has to keep a - promise this library can verify. The low-level surface is a passthrough: policy binds what the - library composes, never what the caller wrote. -8. **State wider than one session goes through one store.** A pool of sessions, a limit shared - between processes, and what has to survive a restart — receipts still awaited, a message half - reassembled — are held through a store interface and never beside it. Without a store the - application supplies, that state is in memory and ends with the process, and the defaults need - none. The interface carries the library's own versioned records, never an internal shape handed - to the application to persist. Coordinating processes any other way is declined without a fresh - argument each time. -9. **It builds, tests and runs the same everywhere.** Container-only toolchain, no runtime - dependencies, the Node 18 floor verified in CI rather than asserted, every README example executed - by the suite. ## Hard rules @@ -245,12 +211,13 @@ decision under [The wire](#the-wire). Each file answers one question, and a fact belongs to the file whose question it answers: -- **README.md — what you can rely on.** Observable behaviour, for someone using the package. It - carries a reason only where the reason changes how you would call the thing. +- **README.md — what you can rely on, and where this is heading.** Observable behaviour, for + someone using the package, plus the goals and the audience. It carries a reason only where the + reason changes how you would call the thing. - **MIGRATION.md — what a 0.4.0 consumer has to change.** Renamed and removed surface, and the behaviour that changed on the wire. -- **AGENTS.md — what may not change, and why.** Goals, hard rules, architecture, conventions, and the - decisions the goals do not already settle. It does not restate behaviour README states. +- **AGENTS.md — what may not change, and why.** Hard rules, architecture, conventions, and the + decisions the goals do not already settle. It does not restate behaviour or goals README states. - **todo.md** is a working file that sets its own rules; nothing here governs it. A sentence living in two of them is a defect: delete the copy in the file whose question it does not diff --git a/README.md b/README.md index 72268eb..c4fcf87 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,8 @@ window, long messages and delivery receipts. TypeScript, ESM, no dependencies. [Send options](#send-options) · [Session](#session) · [Receiving in depth](#receiving-in-depth) · [Server in depth](#server-in-depth) · [Logging](#logging) · [PDUs and the low-level API](#pdus-and-the-low-level-api) · -[Migrating from 0.4.0](#migrating-from-larvitsmpp-040) · [Development](#development) +[Migrating from 0.4.0](#migrating-from-larvitsmpp-040) · [Goals](#goals) · +[Audience](#audience) · [Development](#development) ## Install @@ -632,6 +633,72 @@ if (isCommand(pduObj, 'submit_sm')) { See [MIGRATION.md](https://gitea.larvit.se/larvit/smpp-js/src/branch/main/MIGRATION.md). +## Goals + +In priority order, and the order is the point: where two of them pull against each other, the earlier +one wins. They do not override the hard rules below. + +1. **Correct on the wire.** SMPP 3.4 as SMSCs actually run it. Every other goal yields to this one; + the defect table below is what the alternative costs. +2. **Never give the application a wrong answer about what happened.** An outcome we cannot determine + is reported as undetermined rather than guessed; a report the peer marked as not final settles + 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 + is not dropped. +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 + sends, keep the traffic. +4. **A peer an operator never has to complain about.** No bind flooding, nothing a bind direction + forbids, no optional parameters to a peer that declared none, nothing held without a bound. +5. **The session layer is in here, and its defaults are what most applications should run.** + Keepalive, reconnect, the send window, reassembly and receipt correlation. What the network says + about a message the application sent reaches it as a report rather than as an inbound message, and + says whether it is final, so nothing has to read the PDU to tell those apart. An option retunes a + default or opts out of it; an option does not switch on the thing the caller obviously wanted. +6. **Configurable and extendable, never at the defaults' expense.** Where an application needs other + than the default and cannot build it from what is exported — a rate limit counted per PDU, an + alphabet, a receipt format — it gets an option or a hook rather than a fork. A call that passes no + options stays exactly as easy and as safe, and a hook is a seam the library calls, never a way into + its internals. +7. **A small, stable public surface over reshapeable internals.** Only what `src/index.ts` exports is + published. A new option has to beat "the application can do this itself", and has to keep a + promise this library can verify. The low-level surface is a passthrough: policy binds what the + library composes, never what the caller wrote. +8. **State wider than one session goes through one store.** A pool of sessions, a limit shared + between processes, and what has to survive a restart — receipts still awaited, a message half + reassembled — are held through a store interface and never beside it. Without a store the + application supplies, that state is in memory and ends with the process, and the defaults need + none. The interface carries the library's own versioned records, never an internal shape handed + to the application to persist. Coordinating processes any other way is declined without a fresh + argument each time. +9. **It builds, tests and runs the same everywhere.** Container-only toolchain, no runtime + dependencies, the Node 18 floor verified in CI rather than asserted, every README example executed + by the suite. + +## Audience + +Who depends on this library, and what they may rely on. + +- **The public npm audience, not only larvit's own applications.** Only what `src/index.ts` exports + is public; everything behind it is reshaped freely. +- **Node 18 and newer, ESM only, no runtime dependencies.** The floor is verified in CI rather than + asserted, so the library drops into a service or a container without pulling a tree behind it. +- **Real SMSCs and ESMEs as operators actually run them**, not a reference implementation. Jasmin, + SMPPSim, Kannel, jsmpp, Cloudhopper, python-smpplib and php-smpp are the interop targets, and what + they do in practice outranks what the specification says they should do. +- **The SMSC operator on the far end**, who never sees this API but carries what it does to their + link. A peer they have to complain about is a defect however well the library reads. +- **Pre-1.0, so the minor is the breaking unit** and a patch never breaks. What a 0.4.0 consumer has + to change is in [MIGRATION.md](https://gitea.larvit.se/larvit/smpp-js/src/branch/main/MIGRATION.md). + +Personas this README serves, in order: + +1. The application developer sending or receiving SMS on the defaults, who should need no options. +2. The application developer who needs one thing retuned — an alphabet, a rate limit, a receipt + format — through an option or a hook rather than a fork. +3. The operator coordinating sessions across processes through a store. +4. The developer migrating from `larvitsmpp` 0.4.0. + ## Development Everything runs in the container; nothing is installed on the host. diff --git a/todo.md b/todo.md index 3f12d21..dfcc7df 100644 --- a/todo.md +++ b/todo.md @@ -1,7 +1,7 @@ # todo.md -Remaining work for `@larvit/smpp`. Read [AGENTS.md](AGENTS.md) first — the goals and hard rules -there constrain every item below. +Remaining work for `@larvit/smpp`. Read [README.md](README.md)'s goals and [AGENTS.md](AGENTS.md)'s +hard rules first — they constrain every item below. This is a working file that sets its own rules. The documentation conventions in AGENTS.md do not govern it, and nothing here is a source anything else may cite. @@ -262,7 +262,7 @@ the rewrite, for a dependency added later. Maintainer's call, 2026-09-14. From comparing 0.5.0 with `smpp`, `@semyonf/smpp`, `@leissner/node-red-smpp`, `node-smpp-next`, `smpp-js-sdk`, `smppjs`, cloudhopper-smpp, jsmpp, go-smpp, Kannel, Jasmin and php-smpp, 2026-09-14. -Each lands under AGENTS.md goal 6: an option or a hook, with the call that passes none unchanged. +Each lands under goal 6: an option or a hook, with the call that passes none unchanged. ### Sending @@ -424,7 +424,7 @@ Each lands under AGENTS.md goal 6: an option or a hook, with the call that passe - [ ] **Pooling, and state that survives a restart, through an optional store.** Maintainer's call, 2026-09-14. It replaces two declines — merge state surviving a restart, and a pool of sessions — - and AGENTS.md goal 8 was rewritten for it. Big: design before code. + and goal 8 was rewritten for it. Big: design before code. - **What it holds.** Receipts still awaited and the groups `DlrMerger` collects. Segments of a message already answered but not yet whole, which the peer will not send again (goal 2). The concatenation reference, so a restart does not reuse one. For a pool, the ids every session