serverless-ircd 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (80) hide show
  1. package/.github/workflows/ci.yml +4 -0
  2. package/CHANGELOG.md +245 -0
  3. package/README.md +160 -200
  4. package/apps/aws-stack/package.json +1 -1
  5. package/apps/cf-tcp-container/package.json +1 -1
  6. package/apps/cf-tcp-container/src/container-server.ts +21 -1
  7. package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
  8. package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
  9. package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
  10. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
  11. package/apps/cf-worker/package.json +1 -1
  12. package/apps/local-cli/package.json +1 -1
  13. package/apps/local-cli/src/server.ts +94 -31
  14. package/apps/local-cli/tests/config-resolution.test.ts +65 -0
  15. package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
  16. package/apps/local-cli/tests/rehash.test.ts +147 -0
  17. package/apps/local-cli/tests/server-helpers.test.ts +63 -0
  18. package/apps/local-cli/tests/tcp.test.ts +89 -0
  19. package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
  20. package/apps/web/landing/index.html +226 -3
  21. package/apps/web/package.json +2 -1
  22. package/apps/web/scripts/build.mjs +25 -2
  23. package/apps/web/src/render-docs.ts +292 -0
  24. package/apps/web/tests/build-smoke.test.ts +31 -2
  25. package/apps/web/tests/landing-content.test.ts +103 -0
  26. package/apps/web/tests/render-docs.test.ts +198 -0
  27. package/docs/AWS-Adapter-Architecture.md +3 -2
  28. package/docs/Services.md +33 -1
  29. package/package.json +2 -2
  30. package/packages/aws-adapter/package.json +1 -1
  31. package/packages/aws-adapter/src/aws-runtime.ts +15 -1
  32. package/packages/aws-adapter/src/handlers/nlb-stream.ts +10 -2
  33. package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
  34. package/packages/aws-adapter/tests/connection-counter.test.ts +17 -0
  35. package/packages/aws-adapter/tests/global-setup.ts +28 -1
  36. package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
  37. package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
  38. package/packages/aws-adapter/tests/sweeper.test.ts +20 -0
  39. package/packages/cf-adapter/package.json +1 -1
  40. package/packages/cf-adapter/src/connection-do.ts +18 -6
  41. package/packages/cf-adapter/tests/connection-do-pure.test.ts +130 -0
  42. package/packages/in-memory-runtime/package.json +1 -1
  43. package/packages/irc-core/package.json +1 -1
  44. package/packages/irc-core/src/commands/account-auth.ts +46 -18
  45. package/packages/irc-core/src/commands/chanserv.ts +288 -4
  46. package/packages/irc-core/src/commands/hostserv.ts +38 -3
  47. package/packages/irc-core/src/commands/index.ts +1 -0
  48. package/packages/irc-core/src/commands/join.ts +41 -35
  49. package/packages/irc-core/src/commands/nickserv.ts +16 -4
  50. package/packages/irc-core/src/commands/registration.ts +27 -16
  51. package/packages/irc-core/src/commands/service-aliases.ts +52 -0
  52. package/packages/irc-core/src/commands/topic.ts +23 -10
  53. package/packages/irc-core/src/state/channel.ts +17 -0
  54. package/packages/irc-core/tests/commands/chanserv.test.ts +668 -1
  55. package/packages/irc-core/tests/commands/hostserv.test.ts +71 -0
  56. package/packages/irc-core/tests/commands/join.test.ts +179 -0
  57. package/packages/irc-core/tests/commands/nickserv.test.ts +185 -2
  58. package/packages/irc-core/tests/commands/registration.test.ts +227 -6
  59. package/packages/irc-core/tests/commands/sasl.test.ts +44 -0
  60. package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
  61. package/packages/irc-server/package.json +1 -1
  62. package/packages/irc-server/src/actor.ts +80 -30
  63. package/packages/irc-server/tests/actor.test.ts +365 -3
  64. package/packages/irc-test-support/package.json +1 -1
  65. package/packages/irc-test-support/src/in-memory-harness.ts +8 -5
  66. package/packages/irc-test-support/src/scenarios.ts +21 -6
  67. package/packages/irc-test-support/tests/in-memory-harness.test.ts +19 -0
  68. package/packages/irc-test-support/vitest.config.ts +6 -1
  69. package/tools/ci-hardening/package.json +1 -1
  70. package/tools/load-test/package.json +1 -1
  71. package/tools/load-test/src/client.ts +13 -13
  72. package/tools/load-test/tests/client.test.ts +258 -2
  73. package/tools/load-test/tests/config.test.ts +39 -0
  74. package/tools/load-test/tests/harness.test.ts +21 -0
  75. package/tools/load-test/tests/metrics.test.ts +7 -0
  76. package/tools/tcp-ws-forwarder/package.json +1 -1
  77. package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
  78. package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
  79. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +51 -0
  80. package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
@@ -33,6 +33,10 @@ jobs:
33
33
  steps:
34
34
  - name: Checkout
35
35
  uses: actions/checkout@v4
36
+ with:
37
+ # `apps/web` render-docs tests need the docs/ submodule populated.
38
+ # `apps/web/upstream` (Kiwi SPA) is large and unneeded here.
39
+ submodules: true
36
40
 
37
41
  - name: Setup pnpm
38
42
  uses: pnpm/action-setup@v4
package/CHANGELOG.md CHANGED
@@ -10,6 +10,251 @@ For the release process itself — versioning policy, pre-release checklist,
10
10
  cutting a tag, rolling back — see [`docs/Release-Process.md`](docs/Release-Process.md).
11
11
  Cross-reference `progress.md` / `tickets.md` for per-ticket detail.
12
12
 
13
+ ## [0.9.0] - 2026-08-10
14
+
15
+ The main change in this release is **rounding out the integrated IRC
16
+ services surface** that landed in v0.8.0: ChanServ gains the prefix and
17
+ roster mutation verbs operators expect (`OP` / `DEOP` / `VOICE` /
18
+ `DEVOICE` / `KICK` / `BAN` / `UNBAN`), HostServ **auto-applies an
19
+ assigned vhost on identify** (no per-session `HostServ ON`), HostServ
20
+ HELP hides oper-only commands from non-opers, NickServ grows an `ID`
21
+ alias for `IDENTIFY`, and **NickServ `INFO` no longer leaks the
22
+ registrant's email** to non-owners. Alongside it: service **shortcut
23
+ verbs** (`/NICKSERV`, `/NS`, `/CS`, `/HS`, `/MS`, `/OS`, …) that the
24
+ daemon rewrites to the equivalent `PRIVMSG <Service>`, `echo-message`
25
+ reflection of `PRIVMSG` to service nicks, and the long-missing **JOIN
26
+ `332`/`333` topic numerics** on reconnect/rejoin (regardless of how the
27
+ topic came to be set). A real PASS-login bug is fixed: a client that
28
+ completes registration before sending `PASS <nick>:<password>` is now
29
+ logged in instead of receiving a silent no-op. The AWS adapter gets
30
+ another round of consistency hardening — every `Connections` `GetCommand`
31
+ now uses `ConsistentRead: true`. The web client renders the `docs/` wiki
32
+ to standalone HTML at `/docs/` and gains a client-specific "how to
33
+ connect" section on the landing page.
34
+
35
+ ### Added — Services surface expansion (`@serverless-ircd/irc-core`)
36
+
37
+ - **ChanServ `OP` / `DEOP` / `VOICE` / `DEVOICE` / `KICK` / `BAN` /
38
+ `UNBAN`**: founder-or-`AUTOOP`-level callers can grant/remove prefix
39
+ modes on demand, kick through ChanServ, and manage channel bans. Each
40
+ verb emits the corresponding `:ChanServ!ChanServ@services MODE` /
41
+ `KICK` broadcast and an `ApplyChannelDelta` against the channel
42
+ authority (the same mechanism MLOCK reasserts use), so the roster
43
+ and ban list update atomically with the broadcast. A target not on
44
+ the channel produces a NOTICE and no state change. Privilege gating
45
+ reuses the existing access-list check — no new store methods.
46
+ - **HostServ auto-applies an assigned vhost on identify.** SASL
47
+ PLAIN/EXTERNAL, NickServ `IDENTIFY`, and PASS-auth now consult
48
+ `services.getVhost(account)` on a successful login: when an assigned
49
+ vhost exists, `applyVhost` runs immediately — `state.host` is set,
50
+ `state.vhostActive` is stamped, and any currently-joined
51
+ `chghost`-capable peers see the `CHGHOST` broadcast. At SASL/PASS-auth
52
+ time (pre-JOIN) there are no peers so no broadcast, but the vhost is
53
+ visible in the subsequent `001` welcome. Users no longer need to run
54
+ `HostServ ON` every session.
55
+ - **NickServ `ID` alias for `IDENTIFY`.** `PRIVMSG NickServ :ID <pw>`
56
+ and `ID <nick> <pw>` behave identically to `IDENTIFY` — the alias
57
+ is not advertised in HELP (mirrors Atheme's hidden shorthand).
58
+ - **Service shortcut verbs** (`@serverless-ircd/irc-server`):
59
+ `/NICKSERV`, `/NS`, `/CS`, `/HS`, `/MS`, `/OS` (and their long forms)
60
+ are rewritten at the actor to the equivalent
61
+ `PRIVMSG <ServiceNick> :<args>`. Clients with a dedicated
62
+ slash-command UX (WeeChat, HexChat, IRCCloud) now work without a
63
+ custom alias file. The aliases are case-insensitive and accept both
64
+ colon-trailing and space-joined arg shapes.
65
+ - **`echo-message` reflection for service PRIVMSG**: a client that
66
+ has negotiated `echo-message` now sees its own
67
+ `PRIVMSG NickServ :IDENTIFY hunter2` reflected back before the
68
+ NickServ NOTICE reply, matching the echo behaviour for every other
69
+ PRIVMSG target. The echo is emitted before the reply so labeled-
70
+ response batches wrap both correctly.
71
+
72
+ ### Added — Web client (`apps/web`)
73
+
74
+ - **`docs/` wiki rendered to `/docs/`.** The Gitea-wiki markdown under
75
+ the `docs/` submodule is now rendered to standalone HTML at
76
+ `apps/web/dist/docs/<slug>.html` and served by the existing
77
+ `[assets]` binding at `/docs/<slug>.html` (with `Home.md` →
78
+ `/docs/index.html`). Intra-docs Markdown links are rewritten to
79
+ `.html`, a shared stylesheet is emitted, and an `ADR-Index` page
80
+ links every ADR. Requires `git submodule update --init docs` before
81
+ build; the build fails loudly with the recovery command if the
82
+ submodule is missing or empty.
83
+ - **Landing-page "how to connect" section.** The static landing page
84
+ gains a CSS-only tabbed picker with copy-paste connect snippets for
85
+ the common stock IRC clients (WeeChat, HexChat, irssi, IRCCloud),
86
+ replacing the previous per-client static guides. The picker covers
87
+ both the WebSocket path and the `irc+tls :6697` path.
88
+
89
+ ### Changed — PASS-based account login (`@serverless-ircd/irc-core`)
90
+
91
+ - **A late `PASS <nick>:<password>` now logs the connection in.**
92
+ Previously, a client that completed registration (`NICK` + `USER`
93
+ resolving before `PASS` arrived, or any `PASS` sent after `CAP END`
94
+ resolved) was silently rejected — the `passReducer` short-circuited
95
+ at `state.registration === 'registered'` and emitted `462
96
+ ERR_ALREADYREGISTRED` for any post-registration `PASS`. The shared
97
+ verify + `applyAccountSuccess` pipeline now runs on a
98
+ post-registration `PASS <nick>:<password>`: on success it emits
99
+ `900 RPL_LOGGEDIN` + `903 RPL_SASLSUCCESS`, stamps `+r`, and runs
100
+ the read-marker / away / memo replay in-band, exactly like the
101
+ at-registration path. A bare `PASS <value>` (no colon) is a silent
102
+ no-op once registration has completed — a server password is
103
+ meaningless post-registration, and emitting `462` for it was
104
+ counterproductive. The pre-registration duplicate-PASS `462` (two
105
+ `PASS`es before `NICK`+`USER`) is preserved unchanged.
106
+
107
+ ### Fixed — Channel correctness (`@serverless-ircd/irc-core`)
108
+
109
+ - **JOIN now emits `332 RPL_TOPIC` / `333 RPL_TOPICWHOISTIME` from any
110
+ source of the topic.** The previous JOIN reducer keyed topic
111
+ emission solely on the ChanServ KEEPTOPIC restore path (an empty
112
+ channel this join), so reconnecting clients after a deploy saw the
113
+ NAMES list but no topic — even when the topic survived the deploy
114
+ in storage. The emission now keys off `state.topic`, so a topic
115
+ restored from a KEEPTOPIC snapshot, a deploy snapshot with
116
+ KEEPTOPIC off, or an earlier joiner all produce the correct
117
+ numerics. The KEEPTOPIC restore path still maintains `state.topic`
118
+ for an empty channel; only the emission trigger changed.
119
+ - **No auto-op for the first joiner of a registered channel off the
120
+ access list.** A registered channel that had been emptied (so its
121
+ roster snapshot was fresh) used to auto-op the first joiner
122
+ unconditionally as a "founder fallback", which let any client claim
123
+ `+o` by joining an empty registered channel. The fallback now only
124
+ fires when the joiner is identified as the founder (or is on the
125
+ access list); an unidentified first joiner joins without prefix
126
+ modes.
127
+
128
+ ### Fixed — AWS adapter (`@serverless-ircd/aws-adapter`)
129
+
130
+ - **Strongly-consistent `Connections` reads across every handler.**
131
+ `$disconnect`'s `cleanupConnection` and the NLB streaming handler's
132
+ per-chunk row read were eventually consistent, so under DynamoDB
133
+ replication lag they could miss the row that was just written (or
134
+ updated) — dropping the first frame of a raw `:6697` connection or
135
+ skipping nick release + membership fanout on `$disconnect`. Every
136
+ `GetCommand` against the `Connections` table in `aws-adapter/src/**`
137
+ now sets `ConsistentRead: true`, matching the `$default` fix from
138
+ v0.8.0. A unit test asserts each call site carries the flag.
139
+ - **Bounded `docker info` probe in the test global-setup.** On hosts
140
+ with Docker Desktop installed but not running, the macOS `docker`
141
+ CLI hangs indefinitely waiting for the daemon to spin up — and
142
+ `testcontainers` inherits that hang, stalling the whole `pnpm test`
143
+ run. The aws-adapter `globalSetup` now pre-probes `docker info`
144
+ with a 5-second exec timeout; if Docker is not reachable, it logs
145
+ and falls through to the Java ZIP fallback (or skip) instead of
146
+ hanging. No behaviour change when Docker is up.
147
+
148
+ ### Fixed — HostServ help leak (`@serverless-ircd/irc-core`)
149
+
150
+ - **HostServ HELP hides oper-only commands from non-opers.** The
151
+ help/unknown NOTICE previously listed all seven HostServ commands
152
+ (`ON, OFF, REQUEST, SET, APPROVE, REJECT, LIST`) to every caller,
153
+ leaking the existence and names of oper commands. The notice
154
+ helpers now branch on `state.userModes.oper`: a non-oper sees only
155
+ `ON, OFF, REQUEST`; an oper sees the full list. The per-handler
156
+ oper gates are unchanged, so a non-oper who guesses an oper
157
+ command name still gets the existing permission-denied NOTICE.
158
+
159
+ ### Fixed — NickServ INFO privacy (`@serverless-ircd/irc-core`)
160
+
161
+ - **NickServ `INFO` no longer leaks the registrant's email to
162
+ non-owners.** `handleInfo` previously returned the `Email:` line
163
+ unconditionally to any caller, identified or not — a remote,
164
+ unidentified user could enumerate registered nicks and harvest
165
+ email addresses. The `Email:` line is now gated behind
166
+ `state.account === rec.account` (the owner) or
167
+ `state.userModes.oper === true` (an oper). The owner path still
168
+ shows the full record; `INFO` with no target defaults to the
169
+ caller's own nick (owner path). The `Nick:` / `Account:` lines
170
+ remain visible to all callers (consistent with how a WHOIS against
171
+ a registered nick surfaces the account name).
172
+
173
+ ### Changed — Local CLI (`apps/local-cli`)
174
+
175
+ - **`operCreds` plumbed through the config schema.** The local CLI's
176
+ `ServerConfig` now carries `operCreds` end-to-end (was wired ad-hoc
177
+ through constructor options); OPER auth against configured creds
178
+ works the same way it does in the cloud adapters. The transport
179
+ seams (WS / TCP) are also extracted into named helpers for
180
+ readability.
181
+
182
+ ### Changed — CI / tooling
183
+
184
+ - CI workflows now explicitly checkout every submodule (`docs/`,
185
+ `apps/web/upstream/`) so the render-docs and Kiwi build steps run
186
+ deterministically on every runner, not just maintainer laptops.
187
+
188
+ ### ⚠️ Migration required
189
+
190
+ - **No persisted-DO-state schema change.** `PERSISTED_STATE_VERSION`
191
+ stays `1`. The ChanServ roster commands, HostServ auto-vhost, and
192
+ the JOIN topic-numeric fix are runtime behaviour, not persisted DO
193
+ fields. **Code rollback across this release is safe.**
194
+ - **No D1 / DynamoDB schema change.** The existing services tables
195
+ (`nickserv_accounts`, `chanserv_channels`, …) carry the new
196
+ behaviour unchanged. The `cert_subjects` column flagged for the
197
+ in-progress services credential consolidation is **not yet
198
+ present** — operators do not need to run any migration command for
199
+ this release.
200
+ - **Behaviour change: late `PASS` no longer emits `462`.** A
201
+ post-registration `PASS <value>` used to reply
202
+ `462 ERR_ALREADYREGISTRED`; it is now silent. Clients that keyed
203
+ off the `462` to detect "you already sent PASS" will no longer see
204
+ it. No client is known to do this (the numeric is informational),
205
+ but scripted regression tests against this behaviour must be
206
+ updated (the in-tree `irc-test-support` scenario was updated).
207
+ - **Behaviour change: late `PASS <nick>:<password>` now logs in.**
208
+ Clients that intentionally sent the colon form post-registration
209
+ expecting it to be ignored will now be logged in. This is the
210
+ intended fix; flag it in operator runbooks if the deployment was
211
+ relying on the silent-reject for some access-control flow.
212
+
213
+ ### Known limitations
214
+
215
+ - **The `AccountStore` / `ServicesStore` credential drift is still
216
+ present.** A nick registered via NickServ `REGISTER` after the
217
+ worker booted still cannot authenticate via SASL PLAIN/EXTERNAL or
218
+ `PASS <nick>:<password>` against the *fresh* password — those paths
219
+ consult the SASL `AccountStore` (`accounts` table), which
220
+ `registerNick` does not write back to. The PASS-login fix above
221
+ makes the at-registration and post-registration paths symmetric,
222
+ but the underlying cross-store drift remains tracked for a
223
+ follow-up release that consolidates the credential into a single
224
+ store. Operators who need a NickServ-registered nick to work with
225
+ SASL/PASS immediately should re-seed via
226
+ `tools/seed-cf-accounts.ts` (D1) / `tools/seed-aws-accounts.ts`
227
+ (DynamoDB) with the same password.
228
+ - Same transport limits as v0.8.0: the **CF TCP path requires
229
+ Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
230
+ origin; the **AWS TCP path** uses NLB + Lambda streaming and is
231
+ subject to Lambda idle-timeout / stream-duration limits. The
232
+ WebSocket path remains the zero-extra-deps default.
233
+ - **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
234
+ CA/trust-store** on both platforms and is not enabled by default.
235
+ - **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
236
+ TheLounge / matrix-IRC bridge) remains pending.
237
+ - **Coverage hardening is partial.** `irc-core`, `irc-server`, and
238
+ `in-memory-runtime` sit at 100%; `aws-adapter` and `aws-stack`
239
+ clear the 90% gate. The remaining packages (`cf-adapter`,
240
+ `local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
241
+ `irc-test-support`, `web`) are **above the gate but below 100%** —
242
+ follow-ups drive each to full coverage.
243
+
244
+ ### Security
245
+
246
+ - **NickServ `INFO` email disclosure** (Fixed above): a remote,
247
+ unidentified client could harvest email addresses from every
248
+ registered nick. The disclosure is now gated behind owner-or-oper.
249
+ - **First-joiner op-takeover on empty registered channels** (Fixed
250
+ above): an unidentified client could claim `+o` by being the first
251
+ to join an empty registered channel. The auto-op fallback now
252
+ requires founder/access-list identification.
253
+ - No other auth path changed. The scrypt-hashed credential store,
254
+ server-password gate, and CSWSH defense are unchanged from v0.8.0.
255
+
256
+ ---
257
+
13
258
  ## [0.8.0] - 2026-08-08
14
259
 
15
260
  The main change in this release is **integrated IRC services**: NickServ,