serverless-ircd 0.1.0 → 0.3.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 (204) hide show
  1. package/.github/workflows/ci.yml +96 -2
  2. package/.github/workflows/deploy-aws.yml +129 -0
  3. package/.github/workflows/deploy-cf.yml +0 -2
  4. package/.gitmodules +3 -0
  5. package/CHANGELOG.md +436 -0
  6. package/README.md +127 -84
  7. package/apps/aws-stack/README.md +73 -0
  8. package/apps/aws-stack/bin/aws.ts +49 -0
  9. package/apps/aws-stack/cdk.json +10 -0
  10. package/apps/aws-stack/package.json +41 -0
  11. package/apps/aws-stack/scripts/smoke-helpers.d.mts +22 -0
  12. package/apps/aws-stack/scripts/smoke-helpers.mjs +89 -0
  13. package/apps/aws-stack/scripts/smoke.mjs +142 -0
  14. package/apps/aws-stack/src/aws-stack.ts +263 -0
  15. package/apps/aws-stack/src/tables.ts +10 -0
  16. package/apps/aws-stack/tests/localstack.test.ts +46 -0
  17. package/apps/aws-stack/tests/smoke-helpers.test.ts +98 -0
  18. package/apps/aws-stack/tests/stack.test.ts +464 -0
  19. package/apps/aws-stack/tsconfig.build.json +11 -0
  20. package/apps/aws-stack/tsconfig.test.json +8 -0
  21. package/apps/aws-stack/vitest.config.ts +20 -0
  22. package/apps/cf-worker/package.json +1 -1
  23. package/apps/cf-worker/scripts/smoke.mjs +0 -0
  24. package/apps/cf-worker/src/worker.ts +8 -2
  25. package/apps/cf-worker/tests/smoke.test.ts +1 -0
  26. package/apps/cf-worker/wrangler.test.toml +5 -1
  27. package/apps/cf-worker/wrangler.toml +66 -17
  28. package/apps/local-cli/package.json +1 -1
  29. package/apps/local-cli/src/config-loader.ts +57 -0
  30. package/apps/local-cli/src/main.ts +1 -1
  31. package/apps/local-cli/src/server.ts +267 -32
  32. package/apps/local-cli/tests/config-loader.test.ts +107 -0
  33. package/apps/local-cli/tests/e2e.test.ts +126 -0
  34. package/apps/local-cli/tests/security-e2e.test.ts +239 -0
  35. package/biome.json +3 -1
  36. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  37. package/docs/ADR-002-location-of-authority.md +82 -0
  38. package/docs/ADR-003-durable-object-sharding.md +93 -0
  39. package/docs/ADR-004-dynamodb-schema.md +96 -0
  40. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  41. package/docs/ADR-006-sasl-mechanism-scope.md +86 -0
  42. package/docs/ADR-007-deterministic-ports.md +82 -0
  43. package/docs/ADR-008-monorepo-tooling.md +60 -0
  44. package/docs/AWS-Adapter-Architecture.md +496 -0
  45. package/docs/AWS-Deployment.md +1186 -0
  46. package/docs/Cloudflare-Deployment-Guide.md +660 -0
  47. package/docs/Home.md +11 -0
  48. package/docs/Observability.md +87 -0
  49. package/docs/PlanIRCv3Websocket.md +489 -0
  50. package/docs/PlanWebClient.md +451 -0
  51. package/docs/Release-Process.md +443 -0
  52. package/package.json +19 -14
  53. package/packages/aws-adapter/README.md +35 -0
  54. package/packages/aws-adapter/package.json +52 -0
  55. package/packages/aws-adapter/src/account-store.ts +121 -0
  56. package/packages/aws-adapter/src/aws-runtime.ts +871 -0
  57. package/packages/aws-adapter/src/cdk-table-defs.ts +73 -0
  58. package/packages/aws-adapter/src/config-loader.ts +126 -0
  59. package/packages/aws-adapter/src/dynamo-account-store.ts +156 -0
  60. package/packages/aws-adapter/src/dynamo.ts +61 -0
  61. package/packages/aws-adapter/src/handlers/connect.ts +44 -0
  62. package/packages/aws-adapter/src/handlers/default.ts +330 -0
  63. package/packages/aws-adapter/src/handlers/disconnect.ts +48 -0
  64. package/packages/aws-adapter/src/handlers/index.ts +293 -0
  65. package/packages/aws-adapter/src/handlers/ping-checker.ts +217 -0
  66. package/packages/aws-adapter/src/handlers/state.ts +13 -0
  67. package/packages/aws-adapter/src/handlers/sweeper.ts +84 -0
  68. package/packages/aws-adapter/src/index.ts +74 -0
  69. package/packages/aws-adapter/src/message-store.ts +34 -0
  70. package/packages/aws-adapter/src/serialize.ts +283 -0
  71. package/packages/aws-adapter/src/tables.ts +53 -0
  72. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +171 -0
  73. package/packages/aws-adapter/tests/account-store.test.ts +280 -0
  74. package/packages/aws-adapter/tests/aws-harness.ts +408 -0
  75. package/packages/aws-adapter/tests/aws-integration.test.ts +17 -0
  76. package/packages/aws-adapter/tests/aws-runtime.test.ts +654 -0
  77. package/packages/aws-adapter/tests/config-loader.test.ts +213 -0
  78. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +336 -0
  79. package/packages/aws-adapter/tests/dynamo.test.ts +57 -0
  80. package/packages/aws-adapter/tests/global-setup.ts +301 -0
  81. package/packages/aws-adapter/tests/gone-exception.test.ts +271 -0
  82. package/packages/aws-adapter/tests/handlers.test.ts +473 -0
  83. package/packages/aws-adapter/tests/message-store.test.ts +55 -0
  84. package/packages/aws-adapter/tests/ping-checker.test.ts +571 -0
  85. package/packages/aws-adapter/tests/serialize.test.ts +249 -0
  86. package/packages/aws-adapter/tests/smoke.test.ts +48 -0
  87. package/packages/aws-adapter/tests/sweeper.test.ts +420 -0
  88. package/packages/aws-adapter/tests/tables.test.ts +74 -0
  89. package/packages/aws-adapter/tests/transactions.test.ts +446 -0
  90. package/packages/aws-adapter/tsconfig.build.json +16 -0
  91. package/packages/aws-adapter/tsconfig.test.json +14 -0
  92. package/packages/aws-adapter/vitest.config.ts +23 -0
  93. package/packages/cf-adapter/package.json +2 -1
  94. package/packages/cf-adapter/src/cf-runtime.ts +42 -22
  95. package/packages/cf-adapter/src/channel-do.ts +40 -1
  96. package/packages/cf-adapter/src/channel-registry-do.ts +58 -0
  97. package/packages/cf-adapter/src/config-loader.ts +135 -0
  98. package/packages/cf-adapter/src/connection-do.ts +119 -0
  99. package/packages/cf-adapter/src/env.ts +27 -1
  100. package/packages/cf-adapter/src/index.ts +2 -1
  101. package/packages/cf-adapter/tests/cf-harness.ts +2 -2
  102. package/packages/cf-adapter/tests/cf-integration.test.ts +2 -2
  103. package/packages/cf-adapter/tests/cf-runtime.test.ts +91 -0
  104. package/packages/cf-adapter/tests/config-loader.test.ts +149 -0
  105. package/packages/cf-adapter/tests/connection-do.test.ts +82 -0
  106. package/packages/cf-adapter/tests/worker/main.ts +2 -1
  107. package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +7 -1
  108. package/packages/cf-adapter/wrangler.test.toml +10 -1
  109. package/packages/in-memory-runtime/package.json +1 -1
  110. package/packages/in-memory-runtime/src/in-memory-runtime.ts +107 -16
  111. package/packages/in-memory-runtime/src/index.ts +1 -1
  112. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +134 -0
  113. package/packages/irc-core/package.json +13 -2
  114. package/packages/irc-core/src/admission.ts +216 -0
  115. package/packages/irc-core/src/caps/capabilities.ts +1 -0
  116. package/packages/irc-core/src/case-fold.ts +64 -0
  117. package/packages/irc-core/src/cloak.ts +81 -0
  118. package/packages/irc-core/src/commands/cap.ts +1 -2
  119. package/packages/irc-core/src/commands/chathistory.ts +305 -0
  120. package/packages/irc-core/src/commands/index.ts +9 -0
  121. package/packages/irc-core/src/commands/invite.ts +6 -9
  122. package/packages/irc-core/src/commands/isupport.ts +1 -1
  123. package/packages/irc-core/src/commands/join.ts +63 -10
  124. package/packages/irc-core/src/commands/kick.ts +4 -11
  125. package/packages/irc-core/src/commands/list.ts +5 -4
  126. package/packages/irc-core/src/commands/mode.ts +5 -9
  127. package/packages/irc-core/src/commands/motd-lines.ts +127 -0
  128. package/packages/irc-core/src/commands/motd.ts +6 -110
  129. package/packages/irc-core/src/commands/oper.ts +151 -0
  130. package/packages/irc-core/src/commands/privmsg.ts +24 -0
  131. package/packages/irc-core/src/commands/registration.ts +79 -21
  132. package/packages/irc-core/src/commands/sasl.ts +251 -0
  133. package/packages/irc-core/src/commands/tagmsg.ts +205 -0
  134. package/packages/irc-core/src/config.ts +270 -0
  135. package/packages/irc-core/src/flood-control.ts +175 -0
  136. package/packages/irc-core/src/index.ts +7 -0
  137. package/packages/irc-core/src/ports.ts +719 -0
  138. package/packages/irc-core/src/protocol/base64.ts +16 -0
  139. package/packages/irc-core/src/protocol/index.ts +2 -1
  140. package/packages/irc-core/src/protocol/numerics.ts +11 -0
  141. package/packages/irc-core/src/protocol/parser.ts +27 -2
  142. package/packages/irc-core/src/state/connection.ts +41 -0
  143. package/packages/irc-core/src/types.ts +88 -2
  144. package/packages/irc-core/stryker.commands.conf.json +41 -0
  145. package/packages/irc-core/stryker.protocol.conf.json +26 -0
  146. package/packages/irc-core/tests/account-store.test.ts +88 -0
  147. package/packages/irc-core/tests/admission.test.ts +229 -0
  148. package/packages/irc-core/tests/caps/capabilities.test.ts +22 -0
  149. package/packages/irc-core/tests/case-fold.test.ts +80 -0
  150. package/packages/irc-core/tests/cloak.test.ts +78 -0
  151. package/packages/irc-core/tests/commands/chathistory.test.ts +996 -0
  152. package/packages/irc-core/tests/commands/join.test.ts +246 -2
  153. package/packages/irc-core/tests/commands/kick.test.ts +15 -0
  154. package/packages/irc-core/tests/commands/oper.test.ts +257 -0
  155. package/packages/irc-core/tests/commands/privmsg.test.ts +146 -2
  156. package/packages/irc-core/tests/commands/registration.test.ts +380 -20
  157. package/packages/irc-core/tests/commands/sasl.test.ts +536 -0
  158. package/packages/irc-core/tests/commands/tagmsg.test.ts +688 -0
  159. package/packages/irc-core/tests/commands/who.test.ts +22 -4
  160. package/packages/irc-core/tests/commands/whois.test.ts +8 -1
  161. package/packages/irc-core/tests/config.test.ts +721 -0
  162. package/packages/irc-core/tests/flood-control.test.ts +394 -0
  163. package/packages/irc-core/tests/message-store.test.ts +530 -0
  164. package/packages/irc-core/tests/parser.test.ts +44 -1
  165. package/packages/irc-core/tests/ports.test.ts +263 -0
  166. package/packages/irc-server/package.json +1 -1
  167. package/packages/irc-server/src/actor.ts +186 -44
  168. package/packages/irc-server/src/dispatch.ts +89 -5
  169. package/packages/irc-server/src/index.ts +2 -0
  170. package/packages/irc-server/src/routing.ts +160 -0
  171. package/packages/irc-server/tests/actor.test.ts +690 -15
  172. package/packages/irc-server/tests/dispatch.test.ts +84 -0
  173. package/packages/irc-server/tests/routing.test.ts +204 -0
  174. package/packages/irc-test-support/README.md +32 -0
  175. package/packages/irc-test-support/package.json +37 -0
  176. package/packages/{cf-adapter/tests/integration → irc-test-support/src}/in-memory-harness.ts +6 -5
  177. package/packages/irc-test-support/src/index.ts +24 -0
  178. package/packages/{cf-adapter/tests/integration/harness.test.ts → irc-test-support/tests/in-memory-harness.test.ts} +1 -1
  179. package/packages/{cf-adapter/tests/integration/scenarios.test.ts → irc-test-support/tests/in-memory-scenarios.test.ts} +1 -2
  180. package/packages/irc-test-support/tests/smoke.test.ts +11 -0
  181. package/packages/irc-test-support/tsconfig.build.json +16 -0
  182. package/packages/irc-test-support/tsconfig.test.json +14 -0
  183. package/packages/irc-test-support/vitest.config.ts +20 -0
  184. package/pnpm-workspace.yaml +1 -0
  185. package/tools/ci-hardening/package.json +30 -0
  186. package/tools/ci-hardening/src/index.ts +6 -0
  187. package/tools/ci-hardening/src/validate.ts +103 -0
  188. package/tools/ci-hardening/tests/__no_thresholds__.txt +11 -0
  189. package/tools/ci-hardening/tests/__partial_thresholds__.txt +16 -0
  190. package/tools/ci-hardening/tests/__stryker_bad__.json +4 -0
  191. package/tools/ci-hardening/tests/validate.test.ts +177 -0
  192. package/tools/ci-hardening/tsconfig.build.json +12 -0
  193. package/tools/ci-hardening/tsconfig.test.json +10 -0
  194. package/tools/ci-hardening/vitest.config.ts +21 -0
  195. package/tools/seed-aws-accounts.ts +80 -0
  196. package/tools/tcp-ws-forwarder/package.json +1 -1
  197. package/tools/tcp-ws-forwarder/src/forwarder.ts +34 -23
  198. package/tools/tcp-ws-forwarder/src/logger.ts +155 -0
  199. package/tools/tcp-ws-forwarder/src/main.ts +33 -14
  200. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +86 -0
  201. package/tools/tcp-ws-forwarder/tests/logger.test.ts +136 -0
  202. package/packages/cf-adapter/tests/integration/index.ts +0 -17
  203. /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/harness.ts +0 -0
  204. /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/scenarios.ts +0 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,436 @@
1
+ # Changelog
2
+
3
+ All notable changes to ServerlessIRCd are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/) with
7
+ the 0.x convention (any release may change anything).
8
+
9
+ For the release process itself — versioning policy, pre-release checklist,
10
+ cutting a tag, rolling back — see [`docs/release.md`](docs/release.md).
11
+ Cross-reference `progress.md` / `tickets.md` for per-ticket detail.
12
+
13
+ ## [0.3.0] - 2026-07-25
14
+
15
+ ### Added — Operator authentication (`@serverless-ircd/irc-core`)
16
+
17
+ - **`OPER` command reducer**: `OPER <name> <password>` authenticates the
18
+ caller against the deployment's configured operator credentials
19
+ (`OperCred[]`) and, on success, grants the `o` user mode, replying
20
+ `381 RPL_YOUREOPER`. Missing parameters → `461 ERR_NEEDMOREPARAMS`;
21
+ no credentials configured → `491 ERR_NOOPERHOST`; wrong name/password
22
+ → `464 ERR_PASSWDMISMATCH`. Already an operator is an idempotent
23
+ no-op that re-confirms with `381`. `OPER` is the *only* path to oper
24
+ — user `MODE +o` is hard-rejected with `481 ERR_NOPRIVILEGES` — and
25
+ unlocks the `313 RPL_WHOISOPERATOR` branch of WHOIS and the `*`
26
+ oper flag of WHO.
27
+
28
+ ### Added — IRCv3 `TAGMSG` (`@serverless-ircd/irc-core`)
29
+
30
+ - **`TAGMSG` reducer** (message-tags extension): carries tags only, no
31
+ text body. Gated behind the `message-tags` capability (clients that
32
+ did not negotiate it see `421 ERR_UNKNOWNCOMMAND`). Follows NOTICE
33
+ semantics — no numeric error replies on any rejection path — for both
34
+ channel and user targets, and is recorded so `draft/chathistory`
35
+ replay picks up `TAGMSG` lines.
36
+
37
+ ### Added — RFC 1459 case-mapping (`@serverless-ircd/irc-core`)
38
+
39
+ - **`case-fold.ts`**: RFC 1459 case-insensitive comparison for nicks
40
+ and channels, advertised via `005 CASEMAPPING=rfc1459`. Replaces the
41
+ prior ASCII-only comparison across nick collision/registry, kick and
42
+ invite target lookup, and membership matching.
43
+
44
+ ### Added — Registration MOTD (`@serverless-ircd/irc-core`)
45
+
46
+ - The registration welcome now renders the **real configured MOTD**
47
+ (`motd-lines.ts`), replacing the placeholder banner. The `MOTD`
48
+ command and the welcome flow share one rendering path.
49
+
50
+ ### Added — Cloudflare adapter (`@serverless-ircd/cf-adapter`)
51
+
52
+ - **`ChannelRegistryDO`**: a fourth Durable Object holding the channel
53
+ directory (channel name → `ChannelDO` routing), alongside
54
+ `ConnectionDO`, `ChannelDO`, and `RegistryDO`. Wired into the Worker
55
+ exports, bindings, and SQLite migrations.
56
+ - **Cloudflare config loader**: reduces Workers `vars` to the shared
57
+ `ServerConfigSchema`, matching the AWS (`loadServerConfigFromLambdaEnv`)
58
+ and local-CLI loaders.
59
+ - `CfRuntime` completed — the previously stubbed `IrcRuntime` methods
60
+ (`getConnection`, `getChannelConnections`, `listChannels`, and
61
+ cross-connection `disconnect`) are now implemented.
62
+
63
+ ### Added — SASL account persistence (`@serverless-ircd/aws-adapter`)
64
+
65
+ - SASL `PLAIN` accounts are now persisted in the DynamoDB `Accounts`
66
+ table with **scrypt** password hashes (never plaintext). The Lambda
67
+ cold-start path scans the table once and loads credentials into the
68
+ synchronous `AccountStore` port.
69
+ - **`tools/seed-aws-accounts.ts`**: provisioning script that scrypt-hashes
70
+ `username:password` pairs (from CLI args or a file) and writes them
71
+ to the `Accounts` table. Re-running with the same username overwrites
72
+ the prior row.
73
+
74
+ ### Changed
75
+
76
+ - `@serverless-ircd/in-memory-runtime`: `admitConnection` now returns a
77
+ stable empty `recordId` when admission is disabled, dropping the
78
+ per-call id minting (the id was never consulted).
79
+ - Deploy scripts: root `pnpm deploy:aws:staging` / `deploy:aws:prod`
80
+ now run `pnpm build` before `cdk deploy`, so the esbuild-bundled
81
+ Lambda picks up fresh workspace `dist/` — matching what CI already
82
+ did and fixing local deploys shipping stale code.
83
+
84
+ ### Known limitations
85
+
86
+ - SASL `EXTERNAL` (mTLS) and production TLS transport remain pending
87
+ (0.x work).
88
+ - Deployed stacks remain WebSocket-only; stock TCP IRC clients still
89
+ reach them through the local `tcp-ws-forwarder` bridge.
90
+
91
+ ### Security
92
+
93
+ - None. No vulnerabilities have been reported or fixed in this release.
94
+
95
+ ---
96
+
97
+ ## [0.2.0] - 2026-07-23
98
+
99
+ ### Added — Authentication & flood control (`@serverless-ircd/irc-core`)
100
+
101
+ - **SASL `AUTHENTICATE`**: pure reducer for the IRCv3 SASL exchange.
102
+ `PLAIN` is fully implemented (decodes the payload, validates against
103
+ the injected synchronous `AccountStore` port, emits `900 RPL_LOGGEDIN`
104
+ + `903 RPL_SASLSUCCESS` on success, `904 ERR_SASLFAIL` on failure).
105
+ `EXTERNAL` is recognized but stubbed to `904` until mTLS lands; any
106
+ other mechanism gets `908 ERR_SASLMECHS`. Multi-frame `<=400`-byte
107
+ chunked payloads and `AUTHENTICATE *` abort (`906 ERR_SASLABORT`) are
108
+ handled. `CAP sasl` is now negotiated end-to-end (no longer
109
+ advertised-only).
110
+ - **Token-bucket flood control**: `wrapFloodControl` is a pure
111
+ higher-order reducer wrapper enforcing a per-connection token bucket.
112
+ The bucket is refilled from the injected `Clock` (deterministic —
113
+ never `Date.now()`) and, once it crosses the configured negative
114
+ threshold, short-circuits with `Disconnect("Excess Flood")` without
115
+ invoking the inner reducer — matching hybrid/charybdis/Unreal.
116
+ - **Per-command flood cost**: each command carries an integer cost;
117
+ control-plane commands (`PING`/`PONG`/`QUIT`/`CAP`/`AUTHENTICATE`)
118
+ are exempt so registration and keepalive can never be starved.
119
+
120
+ ### Added — IRCv3 chat history (`@serverless-ircd/irc-core`)
121
+
122
+ - **`draft/chathistory`**: pure reducer for `CHATHISTORY <sub> <target>
123
+ [<args>...]`. Reads backlog from the injected `MessageStore` port and
124
+ replays matching `PRIVMSG`/`NOTICE`/`TAGMSG` lines wrapped in a single
125
+ `BATCH chathistory <target>` frame, each carrying its original
126
+ `@time=…` (server-time) and `msgid=…` tags. The cap must be
127
+ negotiated before queries are accepted. Backed by a flood-wrapped
128
+ routed-reducer path so chat-history reads cannot bypass the bucket.
129
+
130
+ ### Added — Observability & configuration (`@serverless-ircd/irc-core`)
131
+
132
+ - **Structured logger port**: a `Logger` port with `traceId` /
133
+ `connectionId` propagation, injected through `ctx` so reducers and
134
+ the actor layer emit structured JSON logs without ambient state.
135
+ - **Shared `ServerConfigSchema`**: a single Zod-style schema validating
136
+ `serverName`, `networkName`, `motdLines`, `serverPassword`,
137
+ `cloaking`, `maxConnectionsPerIp`, `maxConnectionsPerUser`, and
138
+ `perIpConnectionRate` with the same defaults across every adapter.
139
+ Each adapter ships a thin loader (`loadServerConfigFromLambdaEnv`,
140
+ CF `vars`, local CLI flags) that reduces to the shared schema.
141
+
142
+ ### Added — Security hardening (`@serverless-ircd/irc-core`, `@serverless-ircd/in-memory-runtime`, `@serverless-ircd/local-cli`)
143
+
144
+ - **Server password enforcement**: when `serverPassword` is set in
145
+ `ServerConfig`, connections must supply it via `PASS <password>`
146
+ (or authenticate via SASL PLAIN) before registration completes;
147
+ otherwise the server replies `464 ERR_PASSWDMISMATCH` and closes
148
+ the link.
149
+ - **Hostmask cloaking**: `cloakHost(realHost, config, suffix)` replaces
150
+ the client's visible IP with a deterministic 64-bit FNV-1a hash
151
+ encoded as lowercase base32, using a deployment-wide secret. Enabled
152
+ via `cloaking: { enabled, secret, cloakedSuffix }` in `ServerConfig`.
153
+ - **Connection admission gate**: `AdmissionStats` + `decideAdmission`
154
+ enforce three independent limits at connection-accept time, each with
155
+ a distinct rejection reason:
156
+ - Per-IP simultaneous-connection cap (`maxConnectionsPerIp`, default 10).
157
+ - Per-user simultaneous-connection cap (`maxConnectionsPerUser`,
158
+ default 5; falls back to source IP when no SASL identity is present).
159
+ - Per-IP connection-rate limit (sliding window
160
+ `perIpConnectionRate`, default 5 per 60 s).
161
+ - **512-byte input cap**: the IRC line parser rejects any line whose
162
+ post-CRLF-strip length exceeds `MAX_INPUT_BYTES` (512), matching
163
+ RFC 2812 §2.3 and the existing output-side `enforceLineLimit`.
164
+
165
+ ### Added — AWS adapter (`@serverless-ircd/aws-adapter`, `@serverless-ircd/aws-stack`)
166
+
167
+ The second serverless substrate lands, mirroring the Cloudflare adapter
168
+ on the same `irc-core` reducers and `IrcRuntime` port. Cloudflare and
169
+ AWS are now both deployable.
170
+
171
+ - **`AwsRuntime`**: `IrcRuntime` implementation backed by DynamoDB +
172
+ `ApiGatewayManagementApi`. Marshal/unmarshal helpers map irc-core
173
+ state shapes to/from DynamoDB rows.
174
+ - **Lambda handlers**: `$connect` / `$disconnect` / `$default` entry
175
+ point dispatched on `event.requestContext.routeKey`, wired through
176
+ `ConnectionActor` + `dispatch` against `AwsRuntime`.
177
+ - **Gone-connection sweeper** (`sweeperHandler`): scheduled Lambda that
178
+ reaps connections whose WebSocket link is dead, using atomic DynamoDB
179
+ membership transactions so roster/part fanout stays consistent.
180
+ - **Idle / PING checker** (`pingCheckerHandler`): scheduled Lambda that
181
+ drives the PING / no-PONG disconnect sweep, the AWS analogue of the
182
+ Cloudflare DO `alarm()`.
183
+ - **CDK v2 stack** (`apps/aws-stack`): API Gateway v2 WebSocket API +
184
+ auto-deploying `prod` stage, one Node 20 `NodejsFunction` for the
185
+ runtime, one for the sweeper, and one for the PING checker, five
186
+ DynamoDB tables (`Connections`, `ChannelMeta`, `ChannelMembers`,
187
+ `Nicks`, `Accounts`), and least-privilege IAM (per-table data-plane
188
+ grants + `execute-api:ManageConnections` scoped to the stage).
189
+ - **Per-environment isolation**: `environmentName` prop isolates
190
+ stacks (`IrcAwsStack-<env>`); configurable server identity
191
+ (name / network / MOTD).
192
+ - **AWS deploy workflow** (`.github/workflows/deploy-aws.yml`):
193
+ serialized `cdk deploy --all` to staging on every push to `main`,
194
+ followed by the smoke e2e (`scripts/smoke.mjs`) against the deployed
195
+ `ConnectUrl`. Supports static keys or OIDC web-identity role
196
+ assumption.
197
+
198
+ ### Added — Shared test harness (`@serverless-ircd/irc-test-support`)
199
+
200
+ - New workspace package owning the parametrized IRC scenario suite
201
+ (`runIrcScenarios`, 31 Phase 2 scenarios) and the
202
+ `IrcHarnessFactory` / `IrcHarness` / `ClientHarness` seam each
203
+ adapter implements. Ships the reference `inMemoryHarnessFactory`.
204
+ cf-adapter and aws-adapter integration tests both consume it, so the
205
+ same flows run against in-memory, Cloudflare, and AWS runtimes.
206
+
207
+ ### Added — CI / build
208
+
209
+ - **Coverage gate**: vitest per-package `thresholds` now enforce 100%
210
+ line/function/branch/statement on `irc-core` / `irc-server` /
211
+ `in-memory-runtime` and ≥90% on every other package. The gate is a
212
+ dedicated named CI step; a regression blocks the merge.
213
+ - **Mutation testing**: Stryker spot-checks on `irc-core/protocol` and
214
+ `irc-core/commands` (≥80% mutation score gate), running as a parallel
215
+ CI job. Repo-level `pnpm mutation` / `mutation:protocol` /
216
+ `mutation:commands` scripts drive it locally.
217
+ - **`@serverless-ircd/ci-hardening`** tool: validates the coverage-gate
218
+ and mutation-testing configs exist and meet the documented
219
+ thresholds, catching drift (a lowered threshold or dropped Stryker
220
+ config) before it reaches `main`.
221
+ - **CF observability**: Worker `observability.invocation_logs` enabled
222
+ for runtime visibility in the Cloudflare dashboard.
223
+ - **tcp-ws-forwarder logging**: structured logger with configurable
224
+ log levels.
225
+
226
+ ### Changed
227
+
228
+ - `CAP LS` now advertises `draft/chathistory`; `sasl` is negotiated
229
+ end-to-end rather than advertised-only.
230
+ - The local CLI now binds a **second RFC-style TCP listener** by default
231
+ (`--tcp-port`, default `<port> + 1`) alongside the WebSocket listener,
232
+ so stock IRC clients can dial in directly without the forwarder.
233
+ Pass `--no-tcp` for WebSocket-only. `--motd-file` loads the MOTD from
234
+ disk.
235
+
236
+ ### Known limitations
237
+
238
+ - **No config loader for oper credentials / max clients.** Server name,
239
+ network, MOTD, cloaking secret, and admission limits are configurable,
240
+ but oper credentials and dynamic max-client limits remain hardcoded
241
+ defaults.
242
+ - **No TLS / raw TCP transport in production.** The local CLI exposes a
243
+ TCP listener for development; deployed stacks (CF + AWS) are
244
+ WebSocket text frames only. `irc+tls` is 0.x work.
245
+ - **SASL `EXTERNAL` is reserved.** It is recognized but returns `904`
246
+ until mTLS lands.
247
+ - **`chathistory` storage is adapter-supplied.** The `MessageStore` port
248
+ is implemented per adapter; clients that query backlog against a
249
+ runtime with no store configured will see an empty result.
250
+
251
+ ## [0.1.0] - 2026-07-21
252
+
253
+ First tagged release. Establishes the pure-reducer protocol core, the
254
+ runtime port + reference implementation, a runnable local CLI server,
255
+ and a working Cloudflare Workers adapter deployed to staging.
256
+
257
+ This is a **preview release**. The AWS adapter, SASL authentication,
258
+ flood control, server-password enforcement, and a CI coverage gate
259
+ are not yet implemented — see **Known limitations** below. The
260
+ Cloudflare staging deployment is functional; production deploys are
261
+ manual per `docs/release.md` §8.3.
262
+
263
+ ### Added — Protocol core (`@serverless-ircd/irc-core`)
264
+
265
+ - Pure reducer architecture: every command handler is
266
+ `(state, msg, ctx) => { state, effects }`. Side effects are values
267
+ (`Effect[]`), interpreted by a separate `dispatch` step.
268
+ - Discriminated `Effect` taxonomy (`Send`, `Broadcast`, `Disconnect`,
269
+ `ReserveNick`, `ChangeNick`, `ReleaseNick`, `ApplyChannelDelta`,
270
+ `Lookup`, …) covering every side effect referenced in PLAN §2.1.
271
+ - Determinism ports `Clock` and `IdFactory` injected into `ctx` so
272
+ reducers never touch real timers or randomness.
273
+ - RFC 1459 / 2812 command set: registration (`NICK` / `USER` / `PASS`),
274
+ `PING` / `PONG`, `QUIT`, `JOIN`, `PART`, `PRIVMSG`, `NOTICE`,
275
+ `TOPIC`, `NAMES`, `MOTD`, `MODE` (channel + user), `KICK`, `INVITE`,
276
+ `WHO`, `WHOIS`, `LIST`.
277
+ - Channel modes: `o v b i k l t n m s p`. User modes: `i`, `o` (local
278
+ only), `w`, `s`.
279
+ - Welcome numeric sequence `001`–`005` with `isupport` (`005`)
280
+ generator emitting `NETWORK=`, `CHANLIMIT=`, `CASEMAPPING=`,
281
+ `SAFELIST`, and the negotiated IRCv3 caps.
282
+ - IRCv3 `CAP` negotiation: full `LS` / `REQ` / `ACK` / `NAK` / `END` /
283
+ `LIST` / `NEW` / `DEL` lifecycle.
284
+ - IRCv3 extensions: `message-tags`, `server-time`, `echo-message`,
285
+ `multi-prefix`, `extended-join`, `chghost`, `away-notify`,
286
+ `invite-notify`, `batch`, and the `safelist` capability +
287
+ `SAFELIST` isupport token.
288
+
289
+ ### Added — Orchestration (`@serverless-ircd/irc-server`)
290
+
291
+ - The `IrcRuntime` port: side-effect interface (transport + state IO)
292
+ that every adapter implements.
293
+ - `dispatch(effects, runtime)` interpreter that walks an `Effect[]`
294
+ against a bound runtime, with ordering and dedup verified.
295
+ - `ConnectionActor`: bytes → framed messages → reducer → dispatch.
296
+ Drives the full registration handshake, PING/PONG cadence, and
297
+ command dispatch.
298
+
299
+ ### Added — In-memory runtime (`@serverless-ircd/in-memory-runtime`)
300
+
301
+ - Single-process reference implementation of `IrcRuntime` backed by
302
+ in-memory Maps. Used by integration tests and the local CLI server.
303
+
304
+ ### Added — Local CLI server (`@serverless-ircd/local-cli`)
305
+
306
+ - Runnable WebSocket IRC server on `ws://127.0.0.1:6667/` (default),
307
+ built on the in-memory runtime. Speaks WebSocket text frames, one
308
+ IRC message per frame. `SIGINT` / `SIGTERM` perform graceful
309
+ shutdown. Manual-test harness and e2e fixture target.
310
+ - CLI flags: `--port`, `--host`, `-h/--help`.
311
+
312
+ ### Added — Cloudflare adapter (`@serverless-ircd/cf-adapter`)
313
+
314
+ - `ConnectionDO`: hibernatable WebSocket handler (one DO per
315
+ connection). Owns `ConnectionState`, drives `ConnectionActor`,
316
+ persists state across hibernation, drives the PING / no-PONG
317
+ disconnect sweep via `alarm()`.
318
+ - `RegistryDO`: sharded nick-uniqueness authority. Shard assignment is
319
+ `fnv1a32(lowercased(nick)) mod N`, default fleet size 32. DO
320
+ single-threadedness makes the uniqueness invariant structural.
321
+ - `ChannelDO`: per-lowercased-channel-name DO holding roster, modes,
322
+ topic, and fanout via `ConnectionDO` stubs.
323
+ - `CfRuntime`: implements the `IrcRuntime` port against the three DO
324
+ classes.
325
+
326
+ ### Added — Cloudflare Worker (`@serverless-ircd/cf-worker`)
327
+
328
+ - Worker edge entry point: WebSocket upgrade → allocates
329
+ `connectionId = crypto.randomUUID()` → routes to `ConnectionDO`.
330
+ Non-upgrade requests get a human-readable 200 health string.
331
+ - `wrangler.toml` with default (production) + `[env.staging]`
332
+ environments, DO bindings, `[[migrations]]` block.
333
+ - `scripts/smoke.mjs`: replay CONNECT → NICK/USER → JOIN → PRIVMSG →
334
+ QUIT and assert the server emits `001`, `376`, `353`, `366`, then
335
+ closes. Run by CI against staging, runnable locally against
336
+ `wrangler dev`.
337
+
338
+ ### Added — TCP ↔ WebSocket forwarder (`@serverless-ircd/tcp-ws-forwarder`)
339
+
340
+ - Local bridge so stock TCP IRC clients (WeeChat, HexChat, irssi) can
341
+ reach the WebSocket-only server. Listens on a TCP port, opens one
342
+ WebSocket per connection, reassembles the TCP byte stream into one
343
+ message per WS frame outbound, normalizes inbound frames back to
344
+ canonical CRLF. Transport-agnostic — ships no
345
+ `@serverless-ircd/*` dependency.
346
+
347
+ ### Added — CI / build
348
+
349
+ - GitHub Actions CI (`.github/workflows/ci.yml`): lint, typecheck,
350
+ test with coverage on every push / pull request to `main`.
351
+ - GitHub Actions CF deploy (`.github/workflows/deploy-cf.yml`):
352
+ serialized staging deploy on every push to `main`, followed by the
353
+ smoke e2e against the deployed URL. Failure blocks the merge.
354
+ - Toolchain: pnpm 9 workspaces, turbo, TypeScript (strict +
355
+ `exactOptionalPropertyTypes`), vitest + `@vitest/coverage-v8`,
356
+ fast-check, Biome.
357
+
358
+ ### Added — Documentation
359
+
360
+ - `README.md` — architecture, repository layout, toolchain, local
361
+ server + forwarder usage, testing strategy, roadmap, protocol scope.
362
+ - `docs/architecture-aws.md` — AWS adapter design notes (adapter
363
+ itself lands in a later release).
364
+ - `docs/deployment-cf.md` — end-to-end Cloudflare deployment guide
365
+ (prerequisites, account setup, local dev, staging + production
366
+ deploys, config reference, sharding, cost, CI/CD, troubleshooting).
367
+ - `docs/release.md` — this release process runbook.
368
+
369
+ ### Changed
370
+
371
+ - None (this is the first tagged release).
372
+
373
+ ### ⚠️ Migration required
374
+
375
+ - None. No prior releases to migrate from. The DO `[[migrations]]`
376
+ block in `wrangler.toml` is `tag = "v1"` and will provision cleanly
377
+ on a fresh account.
378
+
379
+ ### Known limitations
380
+
381
+ - **No AWS adapter.** Tickets TICKET-039 through TICKET-044 (CDK
382
+ stack, APIGW WebSocket handlers, DynamoDB transactions, EventBridge
383
+ timers, deploy pipeline, deployment doc) are pending. Cloudflare is
384
+ the only deployable platform in 0.1.0.
385
+ - **No SASL.** `CAP sasl` is advertised in the capability list but
386
+ `AUTHENTICATE` is not wired into the dispatcher. Plain-text password
387
+ authentication via `PASS` is also not enforced (TICKET-046).
388
+ - **No flood control.** A token-bucket wrapper (TICKET-028) is
389
+ pending. Misbehaving clients can send unbounded commands.
390
+ - **No server-password enforcement, rate limits, cloaking, or oper
391
+ credentials** (TICKET-046).
392
+ - **No config loader.** Server name, network name, MOTD, channel
393
+ prefixes, max clients, oper creds are hardcoded defaults
394
+ (TICKET-047). The `MOTD.txt` file at the repo root is informational
395
+ only; the live MOTD is compiled into the Worker from
396
+ `[vars].MOTD_LINES`.
397
+ - **No CI coverage gate.** Coverage is computed and uploaded as an
398
+ artifact, but no threshold is enforced (TICKET-048). Inspect
399
+ `packages/*/coverage/` before deploying.
400
+ - **No TLS / raw TCP transport.** v1 is WebSocket text frames only.
401
+ TLS-hardened and `irc+tls` transports are 0.x work (TICKET-052
402
+ through TICKET-058). Stock TCP IRC clients reach the server through
403
+ the local `tcp-ws-forwarder` bridge.
404
+ - **No `chathistory` playback.** TICKET-063 is pending; clients that
405
+ request backlog on JOIN will see an empty batch or no response.
406
+
407
+ ### Security
408
+
409
+ - None. No vulnerabilities have been reported or fixed in this
410
+ release. Report security issues per the contributing guide.
411
+
412
+ ---
413
+
414
+ <!-- Release template — copy for the next release, fill in, move above this line.
415
+
416
+ ## [Unreleased]
417
+
418
+ ### Added
419
+ -
420
+
421
+ ### Changed
422
+ -
423
+
424
+ ### ⚠️ Migration required
425
+ -
426
+
427
+ ### Fixed
428
+ -
429
+
430
+ ### Known limitations
431
+ -
432
+
433
+ ### Security
434
+ -
435
+
436
+ -->