serverless-ircd 0.7.0 → 0.8.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 (135) hide show
  1. package/.github/workflows/ci.yml +3 -3
  2. package/.gitmodules +1 -1
  3. package/CHANGELOG.md +273 -29
  4. package/README.md +155 -55
  5. package/apps/aws-stack/package.json +1 -1
  6. package/apps/aws-stack/src/aws-stack.ts +186 -18
  7. package/apps/aws-stack/tests/stack.test.ts +400 -56
  8. package/apps/cf-tcp-container/package.json +1 -1
  9. package/apps/cf-worker/package.json +1 -1
  10. package/apps/cf-worker/src/worker.ts +4 -4
  11. package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
  12. package/apps/cf-worker/tests/smoke.test.ts +5 -5
  13. package/apps/cf-worker/wrangler.test.toml +6 -6
  14. package/apps/cf-worker/wrangler.toml +7 -5
  15. package/apps/local-cli/package.json +1 -1
  16. package/apps/local-cli/src/config-loader.ts +8 -0
  17. package/apps/local-cli/src/main.ts +16 -0
  18. package/apps/local-cli/src/server.ts +1 -0
  19. package/apps/local-cli/tests/config-loader.test.ts +14 -0
  20. package/apps/web/landing/index.html +14 -16
  21. package/apps/web/package.json +2 -2
  22. package/apps/web/scripts/build.mjs +23 -16
  23. package/apps/web/src/build-env.ts +1 -1
  24. package/apps/web/src/config-schema.ts +6 -6
  25. package/apps/web/tests/build-smoke.test.ts +14 -14
  26. package/apps/web/tests/config-schema.test.ts +1 -1
  27. package/docs/AWS-Deployment.md +21 -8
  28. package/docs/Cloudflare-Deployment-Guide.md +22 -6
  29. package/docs/PlanExtensions.md +113 -3
  30. package/docs/Release-Process.md +23 -13
  31. package/docs/Services.md +546 -0
  32. package/docs/WebClientGuide.md +32 -31
  33. package/package.json +2 -2
  34. package/packages/aws-adapter/package.json +1 -1
  35. package/packages/aws-adapter/src/aws-runtime.ts +5 -0
  36. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
  37. package/packages/aws-adapter/src/config-loader.ts +11 -0
  38. package/packages/aws-adapter/src/connection-counter.ts +89 -0
  39. package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
  40. package/packages/aws-adapter/src/handlers/connect.ts +55 -51
  41. package/packages/aws-adapter/src/handlers/default.ts +36 -4
  42. package/packages/aws-adapter/src/handlers/index.ts +15 -0
  43. package/packages/aws-adapter/src/handlers/nlb-stream.ts +15 -0
  44. package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
  45. package/packages/aws-adapter/src/index.ts +4 -0
  46. package/packages/aws-adapter/src/stats.ts +6 -1
  47. package/packages/aws-adapter/src/tables.ts +34 -4
  48. package/packages/aws-adapter/tests/aws-harness.ts +3 -0
  49. package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
  50. package/packages/aws-adapter/tests/connect.test.ts +158 -32
  51. package/packages/aws-adapter/tests/connection-counter.test.ts +127 -0
  52. package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
  53. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
  54. package/packages/aws-adapter/tests/handlers.test.ts +105 -3
  55. package/packages/aws-adapter/tests/tables.test.ts +6 -1
  56. package/packages/cf-adapter/package.json +1 -1
  57. package/packages/cf-adapter/src/config-loader.ts +11 -0
  58. package/packages/cf-adapter/src/connection-do.ts +112 -2
  59. package/packages/cf-adapter/src/d1-services-store.ts +703 -0
  60. package/packages/cf-adapter/src/env.ts +8 -0
  61. package/packages/cf-adapter/src/index.ts +5 -0
  62. package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
  63. package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
  64. package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
  65. package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
  66. package/packages/cf-adapter/tests/serialize.test.ts +1 -0
  67. package/packages/in-memory-runtime/package.json +1 -1
  68. package/packages/irc-core/package.json +1 -1
  69. package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
  70. package/packages/irc-core/src/commands/account-auth.ts +172 -0
  71. package/packages/irc-core/src/commands/chanserv.ts +882 -0
  72. package/packages/irc-core/src/commands/hostserv.ts +487 -0
  73. package/packages/irc-core/src/commands/index.ts +12 -0
  74. package/packages/irc-core/src/commands/join.ts +164 -8
  75. package/packages/irc-core/src/commands/markread.ts +202 -0
  76. package/packages/irc-core/src/commands/memoserv.ts +319 -0
  77. package/packages/irc-core/src/commands/mode.ts +96 -4
  78. package/packages/irc-core/src/commands/nickserv.ts +390 -0
  79. package/packages/irc-core/src/commands/oper.ts +18 -1
  80. package/packages/irc-core/src/commands/operserv.ts +346 -0
  81. package/packages/irc-core/src/commands/pre-away.ts +3 -1
  82. package/packages/irc-core/src/commands/privmsg.ts +42 -0
  83. package/packages/irc-core/src/commands/read-marker.ts +8 -8
  84. package/packages/irc-core/src/commands/registration.ts +61 -6
  85. package/packages/irc-core/src/commands/sasl.ts +18 -49
  86. package/packages/irc-core/src/commands/tagmsg.ts +41 -6
  87. package/packages/irc-core/src/commands/topic.ts +37 -0
  88. package/packages/irc-core/src/config.ts +36 -5
  89. package/packages/irc-core/src/effects.ts +56 -1
  90. package/packages/irc-core/src/ports.ts +1653 -84
  91. package/packages/irc-core/src/protocol/numerics.ts +8 -0
  92. package/packages/irc-core/src/state/channel.ts +21 -1
  93. package/packages/irc-core/src/state/connection.ts +25 -1
  94. package/packages/irc-core/src/types.ts +48 -12
  95. package/packages/irc-core/tests/commands/chanserv.test.ts +1668 -0
  96. package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
  97. package/packages/irc-core/tests/commands/hostserv.test.ts +935 -0
  98. package/packages/irc-core/tests/commands/join.test.ts +393 -1
  99. package/packages/irc-core/tests/commands/markread.test.ts +361 -0
  100. package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
  101. package/packages/irc-core/tests/commands/mode.test.ts +381 -2
  102. package/packages/irc-core/tests/commands/nickserv.test.ts +807 -0
  103. package/packages/irc-core/tests/commands/oper.test.ts +13 -0
  104. package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
  105. package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
  106. package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
  107. package/packages/irc-core/tests/commands/registration.test.ts +788 -14
  108. package/packages/irc-core/tests/commands/sasl.test.ts +185 -12
  109. package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
  110. package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
  111. package/packages/irc-core/tests/commands/topic.test.ts +94 -2
  112. package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
  113. package/packages/irc-core/tests/config.test.ts +49 -5
  114. package/packages/irc-core/tests/effects.test.ts +19 -0
  115. package/packages/irc-core/tests/message-store.test.ts +63 -0
  116. package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
  117. package/packages/irc-core/tests/services-store.test.ts +1289 -0
  118. package/packages/irc-core/tests/state/channel.test.ts +3 -0
  119. package/packages/irc-server/package.json +1 -1
  120. package/packages/irc-server/src/actor.ts +71 -16
  121. package/packages/irc-server/src/dispatch.ts +94 -7
  122. package/packages/irc-server/src/routing.ts +19 -0
  123. package/packages/irc-server/tests/actor.test.ts +623 -12
  124. package/packages/irc-server/tests/dispatch.test.ts +270 -2
  125. package/packages/irc-server/tests/routing.test.ts +6 -0
  126. package/packages/irc-test-support/package.json +1 -1
  127. package/packages/irc-test-support/src/in-memory-harness.ts +29 -3
  128. package/packages/irc-test-support/src/index.ts +1 -0
  129. package/packages/irc-test-support/tests/in-memory-harness.test.ts +32 -0
  130. package/tools/ci-hardening/package.json +1 -1
  131. package/tools/load-test/package.json +1 -1
  132. package/tools/tcp-ws-forwarder/package.json +1 -1
  133. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +2 -2
  134. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +0 -18
  135. package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
@@ -29,6 +29,7 @@ jobs:
29
29
  verify:
30
30
  name: typecheck / lint / test / coverage
31
31
  runs-on: ubuntu-latest
32
+ timeout-minutes: 30
32
33
  steps:
33
34
  - name: Checkout
34
35
  uses: actions/checkout@v4
@@ -40,7 +41,6 @@ jobs:
40
41
  uses: actions/setup-node@v4
41
42
  with:
42
43
  node-version: 24
43
- cache: pnpm
44
44
 
45
45
  # DynamoDB Local capability for `aws-adapter` coverage.
46
46
  #
@@ -63,7 +63,7 @@ jobs:
63
63
  # (otherwise it blocks for ~60s waiting for Ryuk before falling
64
64
  # back, which is flaky and wastes CI minutes).
65
65
  - name: Setup Java (JRE for DynamoDB Local ZIP fallback)
66
- uses: actions/setup-java@v4
66
+ uses: actions/setup-java@v5
67
67
  with:
68
68
  distribution: temurin
69
69
  java-version: '11'
@@ -134,6 +134,7 @@ jobs:
134
134
  runs-on: ubuntu-latest
135
135
  # Stryker is the slowest single task; it is independent of the
136
136
  # typecheck/lint/coverage chain and runs in parallel.
137
+ timeout-minutes: 45
137
138
  steps:
138
139
  - name: Checkout
139
140
  uses: actions/checkout@v4
@@ -145,7 +146,6 @@ jobs:
145
146
  uses: actions/setup-node@v4
146
147
  with:
147
148
  node-version: 24
148
- cache: pnpm
149
149
 
150
150
  - name: Install dependencies
151
151
  run: pnpm install --frozen-lockfile
package/.gitmodules CHANGED
@@ -1,6 +1,6 @@
1
1
  [submodule "docs"]
2
2
  path = docs
3
- url = https://gitea.com/fowlmouth/ServerlessIRCd.wiki.git
3
+ url = https://gitea.com/ServerlessIRCd/ServerlessIRCd.wiki.git
4
4
  [submodule "apps/web/upstream"]
5
5
  path = apps/web/upstream
6
6
  url = https://github.com/kiwiirc/kiwiirc.git
package/CHANGELOG.md CHANGED
@@ -7,24 +7,268 @@ and this project adheres to [Semantic Versioning](https://semver.org/) with
7
7
  the 0.x convention (any release may change anything).
8
8
 
9
9
  For the release process itself — versioning policy, pre-release checklist,
10
- cutting a tag, rolling back — see [`docs/release.md`](docs/release.md).
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.8.0] - 2026-08-08
14
+
15
+ The main change in this release is **integrated IRC services**: NickServ,
16
+ ChanServ, HostServ, OperServ, and MemoServ ship as part of the daemon
17
+ itself — there is **no separate services process and no S2S link**. The
18
+ service nicks are reserved and routed to dedicated pure reducers backed
19
+ by a new `ServicesStore` port, with **persistent D1 (Cloudflare) and
20
+ DynamoDB (AWS) backends** so registrations survive redeploys. Alongside
21
+ it: the SASL `AccountStore` and NickServ accounts are **unified** under
22
+ one scrypt-hashed credential store, `PASS <nick>:<password>` identifies
23
+ at registration, the `draft/read-marker` `MARKREAD` verb lands, and a
24
+ round of **AWS adapter hardening** (O(1) admission counter, strongly-
25
+ consistent `$default` reads, WebSocket subprotocol echo fix).
26
+
27
+ ### Added — Integrated IRC services (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
28
+
29
+ The ten services tickets add the full
30
+ services surface. Service names are reserved nicks recognized at
31
+ `PRIVMSG` time and routed to dedicated pure reducers when a
32
+ `ServicesStore` is bound; when no store is bound every services command
33
+ replies `501` and the rest of the daemon is unaffected. See
34
+ `docs/Services.md` for the operator reference.
35
+
36
+ - **`ServicesStore` port + in-memory reference impl**: a synchronous,
37
+ injectable store holding NickServ / ChanServ / HostServ / MemoServ /
38
+ OperServ / read-marker state. Reducers stay pure — all side effects
39
+ flow through the store port. The `ReadMarkerStore` from v0.6.0 is
40
+ **folded into** `ServicesStore` so read-markers, `draft/pre-away`,
41
+ and account state share one persistence path.
42
+ - **NickServ** (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET`):
43
+ registers the current nick, identifies to an account (setting the
44
+ read-only user mode **`+r`**, fanning `ACCOUNT` to `account-notify`
45
+ peers, then delivering unread MemoServ memos), and supports
46
+ `SET ENFORCE` — a registered nick held by a non-identified
47
+ connection is reclaimed on the `NICK` path (enforcement policy
48
+ mirrors Atheme/Anope). `IDENTIFY` falls back to SASL account
49
+ credentials when no NickServ row exists.
50
+ - **ChanServ** (`REGISTER` / `DROP` / `SET` / `INFO` / `ACCESS` /
51
+ `LEVELS`): channel registration with a founder, the services-only
52
+ channel modes **`+r`** (registered), **`+R`** (block unidentified
53
+ join/message), and **`+M`** (moderated-identified), `ACCESS`/`LEVELS`
54
+ (SOP/AOP/VOP/HOP access lists), and auto-op on `JOIN` per access
55
+ level. The three services modes are settable only via ChanServ, not
56
+ via `MODE` (`472 ERR_UNKNOWNMODE` / `501`).
57
+ - **HostServ** (`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` /
58
+ `REJECT` / `LIST`): per-account vhost with `CHGHOST` fanout to shared
59
+ channels. `REQUEST` enters an **oper approval queue**; opers `APPROVE`
60
+ to activate or `REJECT` to drop it.
61
+ - **OperServ** (`AKILL` / `JUPE` / `UNJUPE` / `RAW`): oper-gated
62
+ network management. `AKILL` records an `*@host` mask, `JUPE` reserves
63
+ a juped nick (rejected at `NICK` with `432 ERR_ERRONEUSNICKNAME`),
64
+ `RAW` is the escape hatch. Wired end-to-end: `PRIVMSG OperServ :…`
65
+ routes to the OperServ reducer.
66
+ - **MemoServ** (`SEND` / `LIST` / `READ` / `DEL`): per-account memos
67
+ with queue delivery — unread memos are replayed at identify (NickServ
68
+ `IDENTIFY` / SASL login / PASS-based login), the same path that
69
+ replays `draft/read-marker` and `draft/pre-away`.
70
+
71
+ ### Added — Persistent services backends (`@serverless-ircd/cf-adapter`, `@serverless-ircd/aws-adapter`)
72
+
73
+ Both adapters ship write-behind persistence for the `ServicesStore` so
74
+ registrations, channel access lists, vhosts, memos, and read-markers
75
+ survive a redeploy/reconnect.
76
+
77
+ - **Cloudflare (D1)**: `D1ServicesStore` over the shared `ACCOUNTS_DB`
78
+ binding (the same D1 database used for SASL accounts since v0.4.0).
79
+ The ten services tables **auto-provision on cold start** (`CREATE
80
+ TABLE IF NOT EXISTS` inside `loadD1ServicesStore`), so a fresh deploy
81
+ self-provisions and an existing deploy re-runs the batch as a cheap
82
+ no-op. `ConnectionDO` load/flush hooks wrap each frame and teardown.
83
+ - **AWS (DynamoDB)**: `DynamoServicesStore` plus a new `Services`
84
+ table definition and handler wiring so NLB-stream / `$default`
85
+ invocations hydrate + flush services alongside the existing account
86
+ store. Enabled when the `SERVICES_TABLE` env var / table name is
87
+ supplied.
88
+ - **`PersistentServicesStore` base** (`irc-core`): the shared
89
+ write-behind base — mutates the inherited cache synchronously and
90
+ enqueues matching writes for `flush()`, so reducers stay free of IO.
91
+
92
+ ### Added — Unified accounts & registration login (`@serverless-ircd/irc-core`)
93
+
94
+ - **Unified SASL + NickServ account store**: the SASL `AccountStore`
95
+ and NickServ `ServicesStore` now share one **scrypt-hashed**
96
+ credential shape. A NickServ registration *is* a SASL account —
97
+ SASL `PLAIN`'s services fallback verifies against a row created by
98
+ NickServ `REGISTER` without a second registration, and vice versa.
99
+ The plaintext password on `ServicesNickRow` is replaced with a
100
+ `HashedAccountCredential` (scrypt + random salt). `ingestAccountCredential`
101
+ adopts a pre-hashed SASL credential as a registered nick (idempotent;
102
+ existing records win).
103
+ - **PASS-based account login**: a client may identify to its NickServ
104
+ account at registration by sending `PASS <nick>:<password>` (the same
105
+ literal the `SASL_ACCOUNTS` seed tooling uses). On success the
106
+ connection is logged in — emitting `900`/`903`, stamping `+r`, and
107
+ running the same read-marker / away / memo replay as SASL. A bare
108
+ `PASS <value>` (no colon) is always treated as a server-password
109
+ candidate. The shared login reduction (`applyAccountSuccess`) is
110
+ extracted and reused from SASL.
111
+ - **Server-password gate with SASL short-circuit**: an optional gate
112
+ enforced in the registration reducer at `001` — when
113
+ `ServerConfig.serverPassword` is set, every connection must supply a
114
+ matching `PASS` (or authenticate via SASL / PASS-based account login)
115
+ before `RPL_WELCOME`, else `464 ERR_PASSWDMISMATCH` + disconnect.
116
+ Wired through all three adapters (`SERVER_PASSWORD` Worker secret /
117
+ Lambda env var / `--server-password` CLI flag).
118
+
119
+ ### Added — `draft/read-marker` `MARKREAD` verb (`@serverless-ircd/irc-core`)
120
+
121
+ - The timestamp-based `MARKREAD` command from the read-marker draft
122
+ lands alongside the existing `+draft/read-marker=<msgid>` tag path.
123
+ `MARKREAD <target>` (GET) replies with the stored marker's timestamp;
124
+ `MARKREAD <target> <timestamp>` (SET) advances the marker, with
125
+ timestamp↔msgid translation against the message store. Failure modes
126
+ use `standard-replies` `FAIL MARKREAD …`. The mark fanout is
127
+ **scoped to the originator's account** (spec compliance), not
128
+ broadcast to every cap-enabled peer.
129
+
130
+ ### Added — Cloudflare adapter (`@serverless-ircd/cf-adapter`)
131
+
132
+ - **Client source IP capture at WebSocket upgrade**: reads
133
+ `CF-Connecting-IP` (with `X-Real-IP` fallback) from the upgrade
134
+ request and seeds `ConnectionState.host`, so WHOIS / hostmasks carry
135
+ a real `@host` segment instead of an `undefined` fallback.
136
+ - **Ghost-member pruning via alarm**: when a WebSocket `close` event is
137
+ lost (client drop, edge hiccup), the DO's alarm prunes the ghost
138
+ member from its channels on the next sweep, preventing stale roster
139
+ entries.
140
+
141
+ ### Changed — AWS adapter (`@serverless-ircd/aws-adapter`, `@serverless-ircd/aws-stack`)
142
+
143
+ - **O(1) admission counter**: `$connect`'s full-table `Scan COUNT` is
144
+ replaced with an atomic increment/decrement on a single meta row,
145
+ dropping admission cost from O(connections) to O(1). Reservations
146
+ roll back on rejection or a failed `Put`; the sweeper and stats
147
+ exclude the meta row.
148
+ - **Strongly-consistent `$default` reads + observability**: `$default`
149
+ now reads the `Connections` row strongly consistent (closes a
150
+ read-after-write `410` window), warns on a missing row, logs
151
+ `postToConnection` failures (previously silently swallowed), and
152
+ emits a summary log per invocation.
153
+ - **`ServerName` / `NetworkName` as CFN parameters**: server identity
154
+ is now a CloudFormation parameter (was env-var-only), and the
155
+ sweeper Lambda's `loadServerConfigFromLambdaEnv` is wired with the
156
+ server-identity env vars it requires. `SweeperScheduleExpression`,
157
+ `PingCheckerScheduleExpression`, and `ApiGatewayLoggingLevel` are
158
+ also exposed as parameters.
159
+ - **WebSocket subprotocol echo fixed**: API Gateway WebSocket
160
+ `IntegrationResponse` uses `route.response.header.*` (not
161
+ `method.response.header.*`, which is REST API only), so the
162
+ `Sec-WebSocket-Protocol` header was not being echoed back to clients.
163
+ AWS now correctly negotiates the IRCv3 subprotocol. Subprotocol
164
+ advertisement is forced text-only (`text.ircv3.net`) to match the
165
+ Lambda response-stream path.
166
+ - **APIGW logging role corrected** and Biome lint/format applied.
167
+
168
+ ### Changed
169
+
170
+ - **`MODE +o` echo on oper-up**: the connection that grants oper now
171
+ receives a `MODE` echo, matching the cross-connection oper-up path.
172
+ - **NICK change broadcast fix**: nick changes now broadcast to every
173
+ shared-channel peer (a code path that dropped peers under certain
174
+ roster shapes is corrected).
175
+ - **Server version from `package.json`**: the default `serverVersion`
176
+ advertised in `002`/`003` is derived from the workspace
177
+ `package.json` at build time (was a hardcoded placeholder when the
178
+ config omitted it).
179
+ - **Web client SPA path `/app/` → `/webclient/`**: the vendored Kiwi
180
+ IRC SPA is now served at `/webclient/` (the `[assets]` publicPath
181
+ and config are repointed). Operators with custom Kiwi configs or
182
+ bookmarks must update to `/webclient/`.
183
+ - **Source / issue links migrated to Gitea**; landing-page copy
184
+ tightened and the upstream-install guard hardened.
185
+
186
+ ### Changed — CI / tooling
187
+
188
+ - `actions/setup-java` bumped to v5; pnpm pinned to 11.20.0. CI jobs
189
+ are bounded by explicit timeouts and the unreachable pnpm cache step
190
+ is dropped.
191
+
192
+ ### ⚠️ Migration required
193
+
194
+ - **Cloudflare: services are enabled when `ACCOUNTS_DB` is bound.** The
195
+ D1 binding is the same one introduced in v0.4.0 for SASL accounts;
196
+ the ten services tables **self-provision on cold start** (no manual
197
+ `wrangler d1 execute` needed). If `ACCOUNTS_DB` is already bound,
198
+ services are now live after deploy — no action. If it is unset,
199
+ services commands reply `501` and the daemon is otherwise unchanged.
200
+ - **AWS: new `Services` DynamoDB table + `SERVICES_TABLE`.** To enable
201
+ services, supply a services table name (`SERVICES_TABLE` env var /
202
+ CDK `servicesTableName` prop); the table is created by the CDK stack.
203
+ Without it, services are unbound.
204
+ - **NickServ / SASL credentials are now scrypt-hashed at rest.** A
205
+ NickServ `REGISTER` creates the same hashed credential shape SASL
206
+ uses; the `password` column on the NickServ row is replaced by
207
+ `algorithm` / `salt` / `hash`. Existing seeded SASL accounts are
208
+ adopted as registered nicks idempotently. Operators re-seeding via
209
+ `tools/seed-*-accounts.ts` see no change (the tools already hashed).
210
+ - **Web client SPA moved to `/webclient/`.** Update any bookmarks,
211
+ custom Kiwi configs, or `WEB_ORIGINS` entries pointing at `/app/`.
212
+ - **No persisted-DO-state schema change.** `PERSISTED_STATE_VERSION`
213
+ stays `1`. The new read-only user mode `r` (registered) and the
214
+ services channel modes `r`/`R`/`M` are runtime state, not persisted
215
+ DO fields; the `ReadMarkerStore` fold moves read-markers into the
216
+ services backend (D1/DynamoDB), out of DO storage. **Code rollback
217
+ across this release is safe.**
218
+ - No toolchain change. Node ≥ 24 / pnpm 11 (from v0.4.0) still apply.
219
+
220
+ ### Known limitations
221
+
222
+ - **Services are not binary-compatible with Atheme or Anope.** There is
223
+ no S2S protocol, so an existing Atheme/Anope services process cannot
224
+ connect, and existing services databases cannot be imported.
225
+ Operators migrating from a services-aware network must re-register
226
+ nicks and channels. See `docs/Services.md` §12.
227
+ - **The web client remains partial.** The Playwright headless-browser
228
+ e2e against the deployed staging frontend is still pending; the SPA
229
+ is exercised via the WS smoke (`scripts/smoke.mjs`) and the `apps/web`
230
+ unit / build-smoke suite. The web client remains Cloudflare-only (the
231
+ `[assets]` binding is a Worker feature).
232
+ - Same transport limits as v0.7.0: the **CF TCP path requires
233
+ Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
234
+ origin; the **AWS TCP path** uses NLB + Lambda streaming and is
235
+ subject to Lambda idle-timeout / stream-duration limits. The
236
+ WebSocket path remains the zero-extra-deps default.
237
+ - **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
238
+ CA/trust-store** on both platforms and is not enabled by default.
239
+ - **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
240
+ TheLounge / matrix-IRC bridge) remains pending.
241
+ - **Coverage hardening is partial.** `aws-adapter` and `aws-stack`
242
+ clear the 90% gate; the remaining packages (`cf-adapter`,
243
+ `local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
244
+ `irc-test-support`, `web`) are **above the gate but below 100%** —
245
+ follow-ups drive each to 100%.
246
+
247
+ ### Security
248
+
249
+ - **NickServ / SASL credentials are now uniformly scrypt-hashed.** The
250
+ unified account store means a NickServ-registered nick and a SASL
251
+ account share one hashed credential — never plaintext, in memory or
252
+ at rest. The server-password gate and CSWSH defense are unchanged
253
+ from v0.7.0; no other auth path changed.
254
+
255
+ ---
256
+
13
257
  ## [0.7.0] - 2026-08-04
14
258
 
15
- The headline is the **web client (Phase 14)**: a vendored **Kiwi IRC**
259
+ The main change in this release is the **web client**: a vendored **Kiwi IRC**
16
260
  SPA served at `/app/` and a static project landing page served at `/`
17
261
  directly by the Cloudflare Worker, with **Cross-Site WebSocket
18
262
  Hijacking (CSWSH) defense** wired into the WS upgrade path. The browser
19
- opens a native `wss://` straight to the existing IRC-over-WebSocket
20
- endpoint — no proxy, gateway, or transport adaptation in the hot path.
263
+ opens a native `wss://` directly to the IRC-over-WebSocket
264
+ endpoint — no proxy, gateway, or transport adaptation in the request path.
21
265
  Alongside it: a round of **test hardening** (mutation-killing tests for
22
266
  `irc-core`'s parser / batch / numerics, and CF-harness / scenario fixes
23
267
  for slow transports).
24
268
 
25
269
  ### Added — Web client (`@serverless-ircd/web`, `@serverless-ircd/cf-worker`)
26
270
 
27
- Six Phase 14 tickets light up the project's browser surface. The Worker
271
+ Six tickets add the project's browser surface. The Worker
28
272
  becomes the single origin for the landing page, the SPA, **and** the
29
273
  WebSocket. Same-origin by default — which is what makes the CSWSH
30
274
  defense almost zero-config.
@@ -67,7 +311,7 @@ defense almost zero-config.
67
311
  with a `200 OK` (the WS endpoint and the landing page share path
68
312
  `/`; only the Worker can tell them apart).
69
313
  - **Static landing page** (`apps/web/landing/index.html`): framework-
70
- free HTML + inline CSS, no JavaScript. The project's front door
314
+ free HTML + inline CSS, no JavaScript. The project's entry page
71
315
  explains what ServerlessIRCd is, links to `/app/` (the IRC client),
72
316
  the docs, and the source repo. The build copies it verbatim to
73
317
  `dist/index.html`. Renders without JS; responsive; the build-smoke
@@ -182,7 +426,7 @@ contributor can deploy a staging SPA following only this doc.
182
426
 
183
427
  ### Known limitations
184
428
 
185
- - **Phase 14 is partial.** The Playwright headless-browser e2e
429
+ - **The web client is partial.** The Playwright headless-browser e2e
186
430
  against the deployed staging frontend remains pending
187
431
  — the SPA is exercised via the existing WS smoke
188
432
  (`scripts/smoke.mjs`) and the new `apps/web` unit / build-smoke
@@ -206,7 +450,7 @@ contributor can deploy a staging SPA following only this doc.
206
450
  (`cf-adapter`, `local-cli`, `load-test`, `cf-tcp-container`,
207
451
  `tcp-ws-forwarder`, `irc-test-support`, and now `web`) are **above
208
452
  the gate but below 100%** — follow-ups drive each to 100%.
209
- - The IRC services Phase 13 work (NickServ / ChanServ / HostServ /
453
+ - The IRC services work (NickServ / ChanServ / HostServ /
210
454
  OperServ / MemoServ) is pending; `draft/read-marker` and
211
455
  `draft/pre-away` ship with in-memory reference stores, so marker /
212
456
  away persistence does not survive an adapter restart until a
@@ -226,20 +470,20 @@ contributor can deploy a staging SPA following only this doc.
226
470
 
227
471
  ## [0.6.0] - 2026-08-03
228
472
 
229
- The headline is the **IRCv3 extension sweep (Phase 12)**: ten negotiated
473
+ The main change in this release is the **IRCv3 extension sweep**: ten negotiated
230
474
  caps and the user-mode-`S` TLS indicator land in one release, taking the
231
475
  negotiated capability surface from "modern baseline" to "draft-leading-
232
476
  edge". Alongside it: the **required-`serverName` config gate** (the
233
- last open v0.5.0 follow-up), a new **`tools/load-test`** client farm
234
- (Phase 7's 10k-connection harness), and the **coverage / CI unblocks**
477
+ last open v0.5.0 follow-up), a new **`tools/load-test`** client pool
478
+ (the 10k-connection harness), and the **coverage / CI unblocks**
235
479
  that lift `aws-adapter` over the 90% gate and switch `cf-adapter` /
236
480
  `cf-worker` to istanbul (clearing the v0.5.0 workerd-incompatibility
237
481
  caveat). Deploy workflows are now **manual-only** (`workflow_dispatch`).
238
482
 
239
483
  ### Added — IRCv3 extensions (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
240
484
 
241
- The ten Phase 12 cap tickets light up the remaining negotiated-cap
242
- surface, and the two non-cap Phase 12 tickets (ISUPPORT tokens and
485
+ The ten cap tickets add the remaining negotiated-cap
486
+ surface, and the two non-cap tickets (ISUPPORT tokens and
243
487
  user mode `S`) round out the wire-visible advertisement. Each cap
244
488
  ships a pure reducer (where applicable), an actor route case,
245
489
  advertisement in `SUPPORTED_CAPABILITIES` (or conditional
@@ -349,10 +593,10 @@ with a readable, field-listing error.
349
593
 
350
594
  ### Added — Load-test harness (`@serverless-ircd/load-test`)
351
595
 
352
- PLAN §7 / Phase 7's 10k-connection harness. A synthetic WebSocket IRC
353
- client farm: opens N connections, registers (NICK/USER), joins a
596
+ The PLAN §7 10k-connection harness. A synthetic WebSocket IRC
597
+ client pool: opens N connections, registers (NICK/USER), joins a
354
598
  channel, and optionally chats. Captures **p50 / p95 / p99 latency per
355
- phase** (connect / register / join / message) plus the **drop rate**,
599
+ stage** (connect / register / join / message) plus the **drop rate**,
356
600
  and prints a markdown report to stdout (or `--report <path>`).
357
601
 
358
602
  ```bash
@@ -467,11 +711,11 @@ resolves it.
467
711
  `irc-test-support`) are **above the gate but below 100%** —
468
712
  follow-ups drive each to 100%.
469
713
  - The IRC services (NickServ / ChanServ / HostServ / OperServ /
470
- MemoServ) Phase 13 work is pending; `draft/read-marker` and
714
+ MemoServ) work is pending; `draft/read-marker` and
471
715
  `draft/pre-away` ship with in-memory reference stores, so marker /
472
716
  away persistence does not survive an adapter restart until a
473
717
  services backend lands.
474
- - The web client Phase 14 work (vendor Kiwi IRC, serve SPA from the
718
+ - The web client work (vendor Kiwi IRC, serve SPA from the
475
719
  Worker, WS Origin allowlist) is pending.
476
720
 
477
721
  ### Security
@@ -486,8 +730,8 @@ resolves it.
486
730
 
487
731
  ## [0.5.0] - 2026-08-02
488
732
 
489
- The headline is **protocol completion**: the deferred IRC verbs land
490
- (Phase 11), and the S2S / obsolete RFC 2812 verbs are **formally
733
+ The main change in this release is **protocol completion**: the deferred IRC verbs land,
734
+ and the S2S / obsolete RFC 2812 verbs are **formally
491
735
  dropped** rather than left "deferred". With this release every standard
492
736
  IRC verb a modern client expects is implemented — the only remaining
493
737
  `421 ERR_UNKNOWNCOMMAND` paths are the deliberately-unimplemented S2S
@@ -497,10 +741,10 @@ client compatibility matrix) and the required-`serverName` config gate.
497
741
 
498
742
  ### Added — Deferred IRC verbs (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
499
743
 
500
- Six Phase 11 tickets light up the remaining command surface. Each ships
744
+ Six tickets add the remaining command surface. Each ships
501
745
  a pure reducer, an actor route case (no more `421`), new numerics in
502
746
  `protocol/numerics.ts`, and unit + actor coverage. (The remaining two
503
- Phase 11 tickets are drops — see below.)
747
+ tickets are drops — see below.)
504
748
 
505
749
  - **`KILL`** (oper-gated force-disconnect): `KILL <nick>
506
750
  <comment>` resolves the target via the nick registry + a live
@@ -554,7 +798,7 @@ Phase 11 tickets are drops — see below.)
554
798
  channel-scoped). The originator is always excluded (skip-self,
555
799
  matching NOTICE/PRIVMSG semantics). Empty message → `461`. The `+w`
556
800
  user mode was already accepted by `MODE` and advertised in `004
557
- RPL_MYINFO`; this lights up the consumer.
801
+ RPL_MYINFO`; this enables the consumer.
558
802
  - **`SETNAME`** (realname change): `SETNAME :<realname>`
559
803
  updates `state.realname` so a subsequent `WHOIS` reflects the new
560
804
  value in `311 RPL_WHOISUSER`. No broadcast is emitted in v1 (there
@@ -564,7 +808,7 @@ Phase 11 tickets are drops — see below.)
564
808
 
565
809
  ### Changed — S2S & obsolete verbs formally dropped (`@serverless-ircd/irc-core`)
566
810
 
567
- The remaining two Phase 11 tickets convert the "deferred" framing into
811
+ The remaining two tickets convert the "deferred" framing into
568
812
  an explicit **drop**. There is no behaviour change on the wire — the verbs
569
813
  already returned `421 ERR_UNKNOWNCOMMAND` — but the framing, comments,
570
814
  and reserved numerics are cleaned up so the codebase no longer carries
@@ -661,13 +905,13 @@ dead scaffolding.
661
905
 
662
906
  ## [0.4.0] - 2026-07-31
663
907
 
664
- The headline is **dual transport**: both adapters now speak a real
908
+ The main change in this release is **dual transport**: both adapters now speak a real
665
909
  `irc+tls :6697` (TLS-over-TCP) surface for stock IRC clients in
666
910
  production — not just the local CLI — alongside the WebSocket path. This
667
911
  lands **SASL `EXTERNAL` via mTLS**, the **remaining standard IRC verbs**
668
912
  (WHOWAS, VERSION, TIME, ADMIN, INFO, USERHOST, ISON), a **Cloudflare
669
913
  D1-backed SASL account store**, config-driven server identity, and a
670
- **Node 24 / pnpm 11** toolchain. Phase 7 (TLS hardening & raw TCP) is
914
+ **Node 24 / pnpm 11** toolchain. TLS hardening & raw TCP is
671
915
  complete.
672
916
 
673
917
  ### Added — Dual transport: WebSocket + `irc+tls :6697`
@@ -737,7 +981,7 @@ pipeline is unchanged.
737
981
 
738
982
  - The `002` / `003` / `004` registration replies now reflect
739
983
  `ServerConfig.serverVersion` / `createdAt` — the hardcoded
740
- `'0.1.0'` / `'Phase 1'` placeholders are gone. A build step
984
+ `'0.1.0'` version / placeholder strings are gone. A build step
741
985
  (`scripts/generate-build-info.mjs`) bakes the package version and a
742
986
  build timestamp into `RPL_CREATED` (`003`) when the config omits them.
743
987
 
@@ -1005,7 +1249,7 @@ AWS are now both deployable.
1005
1249
  ### Added — Shared test harness (`@serverless-ircd/irc-test-support`)
1006
1250
 
1007
1251
  - New workspace package owning the parametrized IRC scenario suite
1008
- (`runIrcScenarios`, 31 Phase 2 scenarios) and the
1252
+ (`runIrcScenarios`, 31 scenarios) and the
1009
1253
  `IrcHarnessFactory` / `IrcHarness` / `ClientHarness` seam each
1010
1254
  adapter implements. Ships the reference `inMemoryHarnessFactory`.
1011
1255
  cf-adapter and aws-adapter integration tests both consume it, so the
@@ -1036,7 +1280,7 @@ AWS are now both deployable.
1036
1280
  end-to-end rather than advertised-only.
1037
1281
  - The local CLI now binds a **second RFC-style TCP listener** by default
1038
1282
  (`--tcp-port`, default `<port> + 1`) alongside the WebSocket listener,
1039
- so stock IRC clients can dial in directly without the forwarder.
1283
+ so stock IRC clients can connect directly without the forwarder.
1040
1284
  Pass `--no-tcp` for WebSocket-only. `--motd-file` loads the MOTD from
1041
1285
  disk.
1042
1286