Cut the prose this change made false or redundant
Mirror / push (push) Successful in 4s
Test / lint (pull_request) Successful in 21s
Test / test (18) (pull_request) Successful in 29s
Test / test (20) (pull_request) Successful in 29s
Test / test (22) (pull_request) Successful in 33s
Test / test (24) (pull_request) Successful in 35s
Test / test (26) (pull_request) Successful in 33s

This commit is contained in:
2026-09-29 11:18:32 +02:00
parent f3938de634
commit 87166ffbee
7 changed files with 17 additions and 25 deletions
+3 -10
View File
@@ -1,8 +1,5 @@
# AGENTS.md
Guidance for LLM agents working in this repository. What each file in it is for is under
[Documentation](#documentation).
## What this is
A ground-up TypeScript rewrite of `larvitsmpp` 0.4.0, published as `@larvit/smpp` 0.5.0. The branch
@@ -13,7 +10,7 @@ not for structure or style.
## Goals
The goals, in priority order, live in
[README.md](https://gitea.larvit.se/larvit/smpp-js/src/branch/main/README.md#goals). The README states the audience alongside them.
[README.md](https://gitea.larvit.se/larvit/smpp-js/src/branch/main/README.md#goals).
## Hard rules
@@ -87,8 +84,8 @@ src/
Imports point one way: `defs` knows nothing above it but `result.ts`, `pdu` uses `defs`, `session`
uses `pdu`, and `client`/`server` use `session`. The ways back up are the `Session` handed to
`createSms()`, `HeldMessages` and `IncomingRequests`, which call back into it, and to `OnRequest` and `onConnected`
in `session-options.ts`, all imported as a type only.
`createSms()`, `HeldMessages` and `IncomingRequests`, which call back into it, and to `OnRequest`
and `onConnected` in `session-options.ts`, all imported as a type only.
**Parameter order is wire order.** The key order inside `cmds.*.params` is the order the fields are
written to and read from the buffer. Never sort those alphabetically — the alphabetical-ordering
@@ -225,10 +222,6 @@ this is not a changelog.
## Decisions
The decisions themselves live in [docs/decisions.md](docs/decisions.md). Their titles are indexed
here, so a reader sees that a decision exists without carrying its reasoning; the reasoning is in
the file.
### [The public surface](docs/decisions.md#the-public-surface)
- `Session` is publicly constructible, which is what makes `SessionOptions` and `ReconnectOptions`
+2 -2
View File
@@ -74,8 +74,8 @@
`alert_on_message_delivery` and `broadcast_area_identifier`, the names they read back under. The
two alternate names are gone from `tlvs` too, which is now typed by `TlvName`: narrow a `string` with `isTlvName()` before indexing it.
- `cmds.broadcast_sm_resp.tlvMap` is removed; nothing read it.
- The `SmsInput` type is no longer exported; nothing exported took one. Annotate with `Sms`, or
`Pick<Sms, 'from' | 'message' | 'to'>` where you typed a shape of your own.
- The `SmsInput` type is no longer exported; nothing exported took one. Annotate with `Sms`, or a
`Pick<Sms, …>` of the fields you use.
- `session.boundAs` and `session.peerInterfaceVersion` are read-only, and `session.loggedIn` is
removed: read `session.boundAs !== undefined`. A session you wire yourself records the bind it
accepted or had accepted with `session.bound(bindType, declaredVersion)`, which returns `err` for a
+3 -4
View File
@@ -713,10 +713,9 @@ one wins. They do not override the [hard rules](https://gitea.larvit.se/larvit/s
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.
8. **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. **A small, stable public surface over reshapeable internals.** 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.
9. **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
+3 -6
View File
@@ -8,8 +8,7 @@ rule and an index of the titles below.
- **`Session` is publicly constructible, which is what makes `SessionOptions` and `ReconnectOptions`
public too.** Raised twice as a leak; it is not one. The collaborators `session.ts` delegates to
(`IncomingRequests`, `OutgoingRequests`, `ReconnectLoop`, `LinkTimers`, `DlrMerger`,
`PduTransport`, `submitSms`) stay unpublished so they can be reshaped.
stay unpublished so they can be reshaped.
- **`acceptsOptionalParams()` and `bindAllows()` are predicates, not chokepoints.** The library's own
senders consult them; `session.send({ tlvs })` is passed through as written, because silently
@@ -418,8 +417,7 @@ rule and an index of the titles below.
caller would get wrong, where asking the codec is, and publishing it would freeze this library's
error prose as API for an application whose own refusal should read like itself. Goal 8, from the
architecture review of [#99](https://github.com/larvit/larvitsmpp/pull/99), 2026-09-09. It is
reached through
`encodeBody()` in `message.ts`, which is where the `data_coding`-to-text pair already lives:
reached through `encodeBody()` in `message.ts`, which is where the `data_coding`-to-text pair already lives:
`encodeBody(text, dataCoding)` is `decodeMessage(buffer, dataCoding)`'s mirror and resolves the
alphabet through the same `encodingByDataCoding()`. `send()` and `sendReturn()` inherit it,
since both build through `buildPdu()`; `sendSms()` does not, and keeps its own guard, because
@@ -641,8 +639,7 @@ rule and an index of the titles below.
failure belongs to one session's request, and that channel already carries every failure of one.
The hook is consulted before the bind-direction gate, so it sees a `submit_sm` a receiver-bound
peer may not send; first refusal means first, and one it declines still gets `ESME_RINVBNDSTS`.
Nothing is held for a request the hook answered: `HeldMessages` is opened by the `sms` event the
hook skipped, so the drain waits on none of it. `OnRequest` stays unexported where
Nothing is held for a request the hook answered, so the drain waits on none of it. `OnRequest` stays unexported where
`AuthenticateInput` is exported, because that hook's argument is a shape this library invents and
this one's are two types already published. Rejected: consulting the hook first, which puts
bind and authentication inside the application's reach for nothing. Rejected: a narrower hook
-2
View File
@@ -16,7 +16,6 @@ export type HeldMessagesOptions = {
maxOctets: number;
/** Injected so expiry can be exercised without a wall clock. */
now?: (() => number) | undefined;
/** Past a drain's refusal, for a receipt the drain is itself waiting for. */
sendPastDrain: SmsHandlers['send'];
session: Session;
timeout: number;
@@ -135,7 +134,6 @@ export class HeldMessages {
return hold;
}
/** Hands a whole message to the application as an `sms` event, held until it is answered. */
offer(pduObjs: PduObject[], answeredAs?: string): MessageHold | undefined {
const key = keyOf(pduObjs);
-1
View File
@@ -65,7 +65,6 @@ export type SmsInput = {
session: Session;
};
/** What the session's incoming side gives a message so it can be answered and accounted for. */
export type SmsHandlers = {
answered: () => void;
lostLink: () => boolean;
+6
View File
@@ -352,6 +352,12 @@ below.
### Self-sufficiency — 6–7 today, and the gate is 7
- [ ] **Move the fixture-copy reasoning out of AGENTS.md's Conventions, and split the longest
decision entries.** The fixtures bullet holds four justifications for tolerated copies — decisions,
so they belong in `docs/decisions.md` under Internals and tests; the entries under the wire's
alphabet and body rules run 30–40 lines with their `Rejected:` clauses inline, where a reader
who knows the answer still hunts for it. From the 2026-09-29 prose pass.
- [ ] **Move the one-line facts out of the decision log and back to the code.** Five of nine readers
independently reported being sent to `docs/decisions.md` for a question they hit while reading,
with no link from the code; one counted roughly fifty index redirects. The three left to inline