serverless-ircd 0.8.0 → 0.10.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 (179) hide show
  1. package/.github/workflows/ci.yml +4 -0
  2. package/.github/workflows/deploy-aws.yml +156 -32
  3. package/.github/workflows/deploy-cf-tcp.yml +11 -9
  4. package/.github/workflows/deploy-cf.yml +14 -14
  5. package/CHANGELOG.md +550 -0
  6. package/README.md +275 -222
  7. package/apps/aws-stack/README.md +3 -5
  8. package/apps/aws-stack/bin/aws.ts +82 -9
  9. package/apps/aws-stack/cdk.json +0 -3
  10. package/apps/aws-stack/package.json +3 -4
  11. package/apps/aws-stack/src/aws-stack.ts +177 -52
  12. package/apps/aws-stack/src/static-site.ts +323 -0
  13. package/apps/aws-stack/tests/smoke-helpers.test.ts +1 -1
  14. package/apps/aws-stack/tests/stack.test.ts +267 -92
  15. package/apps/aws-stack/tests/static-site.test.ts +491 -0
  16. package/apps/aws-stack/tests/synth-no-bundle.test.ts +0 -1
  17. package/apps/cf-tcp-container/package.json +2 -3
  18. package/apps/cf-tcp-container/src/container-server.ts +33 -10
  19. package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
  20. package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
  21. package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
  22. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
  23. package/apps/cf-tcp-container/wrangler.toml +1 -10
  24. package/apps/cf-worker/package.json +3 -4
  25. package/apps/cf-worker/wrangler.toml +12 -71
  26. package/apps/local-cli/package.json +1 -1
  27. package/apps/local-cli/src/server.ts +115 -48
  28. package/apps/local-cli/tests/config-resolution.test.ts +65 -0
  29. package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
  30. package/apps/local-cli/tests/rehash.test.ts +147 -0
  31. package/apps/local-cli/tests/server-helpers.test.ts +63 -0
  32. package/apps/local-cli/tests/tcp.test.ts +89 -0
  33. package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
  34. package/apps/web/landing/favicon.ico +0 -0
  35. package/apps/web/landing/index.html +227 -3
  36. package/apps/web/package.json +3 -2
  37. package/apps/web/scripts/build.mjs +91 -6
  38. package/apps/web/src/build-env.ts +125 -4
  39. package/apps/web/src/config-schema.ts +20 -6
  40. package/apps/web/src/render-docs.ts +292 -0
  41. package/apps/web/static/{config.staging.json → config.prod-aws.json} +3 -2
  42. package/apps/web/tests/build-env.test.ts +210 -9
  43. package/apps/web/tests/build-smoke.test.ts +33 -4
  44. package/apps/web/tests/config-schema.test.ts +149 -25
  45. package/apps/web/tests/landing-content.test.ts +103 -0
  46. package/apps/web/tests/render-docs.test.ts +198 -0
  47. package/docs/AWS-Adapter-Architecture.md +3 -2
  48. package/docs/AWS-Deployment.md +670 -96
  49. package/docs/AWS-TCP-Deployment.md +20 -45
  50. package/docs/Cloudflare-Deployment-Guide.md +87 -113
  51. package/docs/Cloudflare-TCP-Deployment.md +25 -49
  52. package/docs/Release-Process.md +27 -23
  53. package/docs/Services.md +102 -23
  54. package/docs/WebClientGuide.md +35 -26
  55. package/package.json +7 -10
  56. package/packages/aws-adapter/package.json +1 -1
  57. package/packages/aws-adapter/src/aws-runtime.ts +15 -1
  58. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -11
  59. package/packages/aws-adapter/src/config-loader.ts +19 -2
  60. package/packages/aws-adapter/src/dynamo-services-store.ts +7 -0
  61. package/packages/aws-adapter/src/handlers/connect.ts +26 -0
  62. package/packages/aws-adapter/src/handlers/default.ts +190 -123
  63. package/packages/aws-adapter/src/handlers/index.ts +67 -23
  64. package/packages/aws-adapter/src/handlers/nlb-stream.ts +13 -8
  65. package/packages/aws-adapter/src/index.ts +5 -7
  66. package/packages/aws-adapter/src/origin-allowlist.ts +94 -0
  67. package/packages/aws-adapter/src/serialize.ts +15 -0
  68. package/packages/aws-adapter/src/tables.ts +2 -12
  69. package/packages/aws-adapter/tests/aws-harness.ts +0 -1
  70. package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
  71. package/packages/aws-adapter/tests/config-loader.test.ts +66 -0
  72. package/packages/aws-adapter/tests/connect.test.ts +124 -1
  73. package/packages/aws-adapter/tests/connection-counter.test.ts +17 -0
  74. package/packages/aws-adapter/tests/default-occ.test.ts +219 -0
  75. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +11 -0
  76. package/packages/aws-adapter/tests/global-setup.ts +28 -1
  77. package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
  78. package/packages/aws-adapter/tests/handlers.test.ts +117 -11
  79. package/packages/aws-adapter/tests/migrate-accounts-to-services.test.ts +164 -0
  80. package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
  81. package/packages/aws-adapter/tests/origin-allowlist.test.ts +110 -0
  82. package/packages/aws-adapter/tests/ping-checker.test.ts +0 -1
  83. package/packages/aws-adapter/tests/stats.test.ts +0 -3
  84. package/packages/aws-adapter/tests/sweeper.test.ts +20 -1
  85. package/packages/aws-adapter/tests/tables.test.ts +1 -8
  86. package/packages/aws-adapter/tests/transactions.test.ts +0 -1
  87. package/packages/cf-adapter/package.json +1 -5
  88. package/packages/cf-adapter/src/cf-runtime.ts +59 -8
  89. package/packages/cf-adapter/src/channel-do.ts +13 -3
  90. package/packages/cf-adapter/src/connection-do.ts +284 -115
  91. package/packages/cf-adapter/src/d1-services-store.ts +63 -26
  92. package/packages/cf-adapter/src/env.ts +11 -10
  93. package/packages/cf-adapter/src/index.ts +0 -6
  94. package/packages/cf-adapter/tests/cf-runtime.test.ts +101 -1
  95. package/packages/cf-adapter/tests/channel-do.test.ts +118 -1
  96. package/packages/cf-adapter/tests/connection-do-coverage.test.ts +460 -0
  97. package/packages/cf-adapter/tests/connection-do-pure.test.ts +222 -51
  98. package/packages/cf-adapter/tests/connection-do-sasl-d1.test.ts +62 -38
  99. package/packages/cf-adapter/tests/d1-services-store.test.ts +53 -2
  100. package/packages/cf-adapter/tests/serialize.test.ts +25 -0
  101. package/packages/in-memory-runtime/package.json +1 -1
  102. package/packages/irc-core/package.json +1 -1
  103. package/packages/irc-core/src/account-migration.ts +140 -0
  104. package/packages/irc-core/src/commands/account-auth.ts +60 -35
  105. package/packages/irc-core/src/commands/chanserv.ts +288 -4
  106. package/packages/irc-core/src/commands/hostserv.ts +38 -3
  107. package/packages/irc-core/src/commands/index.ts +1 -0
  108. package/packages/irc-core/src/commands/join.ts +41 -35
  109. package/packages/irc-core/src/commands/memoserv.ts +1 -1
  110. package/packages/irc-core/src/commands/nickserv.ts +138 -15
  111. package/packages/irc-core/src/commands/registration.ts +28 -17
  112. package/packages/irc-core/src/commands/sasl.ts +22 -31
  113. package/packages/irc-core/src/commands/service-aliases.ts +52 -0
  114. package/packages/irc-core/src/commands/topic.ts +23 -10
  115. package/packages/irc-core/src/config.ts +35 -9
  116. package/packages/irc-core/src/credential-hashing.ts +11 -54
  117. package/packages/irc-core/src/index.ts +1 -0
  118. package/packages/irc-core/src/ports.ts +159 -179
  119. package/packages/irc-core/src/state/channel.ts +17 -0
  120. package/packages/irc-core/src/types.ts +38 -10
  121. package/packages/irc-core/tests/account-migration.test.ts +133 -0
  122. package/packages/irc-core/tests/commands/chanserv.test.ts +668 -1
  123. package/packages/irc-core/tests/commands/hostserv.test.ts +71 -0
  124. package/packages/irc-core/tests/commands/join.test.ts +179 -0
  125. package/packages/irc-core/tests/commands/markread.test.ts +54 -0
  126. package/packages/irc-core/tests/commands/memoserv.test.ts +19 -0
  127. package/packages/irc-core/tests/commands/nickserv.test.ts +422 -3
  128. package/packages/irc-core/tests/commands/oper.test.ts +15 -0
  129. package/packages/irc-core/tests/commands/registration.test.ts +336 -108
  130. package/packages/irc-core/tests/commands/sasl.test.ts +194 -169
  131. package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
  132. package/packages/irc-core/tests/commands/unified-account.test.ts +102 -84
  133. package/packages/irc-core/tests/credential-hashing.test.ts +0 -78
  134. package/packages/irc-core/tests/message-store.test.ts +5 -0
  135. package/packages/irc-core/tests/persistent-services-store.test.ts +71 -12
  136. package/packages/irc-core/tests/ports.test.ts +71 -0
  137. package/packages/irc-core/tests/services-store.test.ts +204 -0
  138. package/packages/irc-core/vitest.config.ts +6 -1
  139. package/packages/irc-server/package.json +1 -1
  140. package/packages/irc-server/src/actor.ts +80 -44
  141. package/packages/irc-server/tests/actor.test.ts +384 -50
  142. package/packages/irc-test-support/package.json +1 -1
  143. package/packages/irc-test-support/src/in-memory-harness.ts +8 -5
  144. package/packages/irc-test-support/src/scenarios.ts +21 -6
  145. package/packages/irc-test-support/tests/in-memory-harness.test.ts +19 -0
  146. package/packages/irc-test-support/vitest.config.ts +6 -1
  147. package/pnpm-workspace.yaml +1 -0
  148. package/scripts/__tests__/deploy-web-aws.test.ts +491 -0
  149. package/scripts/deploy-web-aws.mjs +290 -0
  150. package/scripts/package.json +23 -0
  151. package/scripts/tsconfig.test.json +12 -0
  152. package/scripts/vitest.config.ts +19 -0
  153. package/tools/ci-hardening/package.json +1 -1
  154. package/tools/ci-hardening/src/index.ts +2 -0
  155. package/tools/ci-hardening/src/validate.ts +57 -0
  156. package/tools/ci-hardening/tests/deploy-aws-oidc.test.ts +96 -0
  157. package/tools/ci-hardening/tests/validate.test.ts +42 -0
  158. package/tools/load-test/package.json +1 -1
  159. package/tools/load-test/src/client.ts +13 -13
  160. package/tools/load-test/tests/client.test.ts +258 -2
  161. package/tools/load-test/tests/config.test.ts +39 -0
  162. package/tools/load-test/tests/harness.test.ts +21 -0
  163. package/tools/load-test/tests/metrics.test.ts +7 -0
  164. package/tools/migrate-accounts-to-services.ts +270 -0
  165. package/tools/package.json +2 -1
  166. package/tools/seed-aws-accounts.ts +35 -10
  167. package/tools/seed-cf-accounts.ts +42 -9
  168. package/tools/tcp-ws-forwarder/package.json +1 -1
  169. package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
  170. package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
  171. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +51 -0
  172. package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
  173. package/packages/aws-adapter/src/account-store.ts +0 -121
  174. package/packages/aws-adapter/src/dynamo-account-store.ts +0 -95
  175. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +0 -223
  176. package/packages/aws-adapter/tests/account-store.test.ts +0 -276
  177. package/packages/cf-adapter/src/d1-account-store.ts +0 -198
  178. package/packages/cf-adapter/tests/d1-account-store.test.ts +0 -274
  179. package/packages/irc-core/tests/account-store.test.ts +0 -131
package/README.md CHANGED
@@ -6,48 +6,68 @@ platform-agnostic core, and two thin adapters run it on **Cloudflare Workers**
6
6
 
7
7
  One TypeScript codebase. Two serverless substrates.
8
8
 
9
- > **Status:** **v0.8.0 (preview).** The pure protocol core, the
10
- > `IrcRuntime` port + in-memory runtime, a runnable local CLI server,
11
- > the **Cloudflare Workers** adapter, and the **AWS** (API Gateway
9
+ > **Status:** **preview.** The pure protocol core, the `IrcRuntime`
10
+ > port + in-memory runtime, a runnable local CLI server, the
11
+ > **Cloudflare Workers** adapter, and the **AWS** (API Gateway
12
12
  > WebSocket + Lambda + DynamoDB + CDK) adapter are all functional and
13
- > deployed to staging. v0.4.0 shipped **dual transport**: both adapters
14
- > speak a real `irc+tls :6697` (TLS-over-TCP) surface for stock IRC
15
- > clients Cloudflare via Spectrum + a Container origin, AWS via a
16
- > Network Load Balancer + Lambda streamingalongside the WebSocket
17
- > path, plus **SASL `EXTERNAL` via mTLS** and a **Cloudflare D1-backed
18
- > SASL account store**. v0.5.0 completed the **protocol surface**: the
19
- > deferred IRC verbs (`KILL`, `REHASH`, `LUSERS` + `STATS`, `TRACE`,
20
- > `WALLOPS`, `SETNAME`) all land, and the S2S (`CONNECT` / `SQUIT` /
21
- > `LINKS`) and obsolete RFC 2812 (`SERVICE` / `SUMMON` / `USERS`) verbs
22
- > are formally dropped. v0.6.0 landed the **IRCv3 extension sweep**
23
- > ten caps (`account-notify`, `msgid`,
24
- > `standard-replies`, `MONITOR`, `labeled-response`, `sts`,
25
- > `draft/typing`, `draft/multiline`, `draft/read-marker`,
26
- > `draft/pre-away`), the ISUPPORT tokens for the new caps, and the
27
- > read-only user mode `S` (TLS connected). **v0.7.0 lands the web
28
- > client**: a vendored **Kiwi IRC** SPA served at `/webclient/`
29
- > and a static landing page at `/` directly by the Cloudflare Worker,
30
- > with **Cross-Site WebSocket Hijacking (CSWSH) defense** wired into
31
- > the WS upgrade path (same-origin auto-derive by default, optional
32
- > `WEB_ORIGINS` allowlist for cross-origin deploys) — the browser
33
- > opens a native `wss://` straight to the existing IRC-over-WebSocket
34
- > endpoint. **v0.8.0 lands integrated IRC services** NickServ
35
- > (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET ENFORCE` +
36
- > nick enforcement), ChanServ (`REGISTER` / `DROP` / `SET` /
37
- > `ACCESS` / `LEVELS` + the `+r` / `+R` / `+M` channel modes),
38
- > HostServ (`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE`),
39
- > OperServ (`AKILL` / `JUPE` / `RAW`), and MemoServ
40
- > (`SEND` / `LIST` / `READ` / `DEL`) backed by a `ServicesStore`
41
- > port with **persistent D1 (Cloudflare) and DynamoDB (AWS)
42
- > backends** (write-behind, surviving redeploys). The SASL
43
- > `AccountStore` and NickServ accounts are **unified** under one
44
- > scrypt-hashed credential store, `PASS <nick>:<password>` identifies
45
- > at registration, and `draft/read-marker` gains the timestamp-based
46
- > `MARKREAD` verb. Remaining 0.x work: the client compatibility sweep
47
- > (WeeChat / HexChat / IRCCloud / TheLounge), the Playwright
48
- > browser-driven e2e for the SPA, and driving the remaining
49
- > sub-100% packages to full coverage. See `CHANGELOG.md` for the
50
- > per-release manifests.
13
+ > deployed to staging.
14
+ >
15
+ > **Dual transport.** Both adapters speak a real `irc+tls :6697`
16
+ > (TLS-over-TCP) surface for stock IRC clientsCloudflare via
17
+ > Spectrum + a Container origin, AWS via a Network Load Balancer +
18
+ > Lambda streaming alongside the WebSocket path, plus **SASL
19
+ > `EXTERNAL` via mTLS** and a **Cloudflare D1-backed SASL account
20
+ > store**.
21
+ >
22
+ > **Complete protocol surface.** The deferred IRC verbs (`KILL`,
23
+ > `REHASH`, `LUSERS` + `STATS`, `TRACE`, `WALLOPS`, `SETNAME`) are
24
+ > implemented, and the S2S (`CONNECT` / `SQUIT` / `LINKS`) and obsolete
25
+ > RFC 2812 (`SERVICE` / `SUMMON` / `USERS`) verbs are formally dropped.
26
+ >
27
+ > **IRCv3 extensions.** Negotiated caps include `account-notify`,
28
+ > `msgid`, `standard-replies`, `MONITOR`, `labeled-response`, `sts`,
29
+ > `draft/typing`, `draft/multiline`, `draft/read-marker`, and
30
+ > `draft/pre-away`, plus the ISUPPORT tokens for each and the
31
+ > read-only user mode `S` (TLS connected).
32
+ >
33
+ > **Web client.** A vendored **Kiwi IRC** SPA is served at
34
+ > `/webclient/` and a static landing page at `/` directly by the
35
+ > Cloudflare Worker (via its `[assets]` binding) **or** by AWS via an
36
+ > **S3 + CloudFront + OAC** `StaticSite` construct (opt-in via CDK
37
+ > context). One `apps/web/dist/` artifact ships on both platforms
38
+ > unchanged. The `docs/` wiki is rendered to standalone HTML at
39
+ > `/docs/`. Both platforms wire **Cross-Site WebSocket Hijacking
40
+ > (CSWSH) defense** into the WS upgrade path: the CF Worker uses
41
+ > same-origin auto-derive by default with an optional `WEB_ORIGINS`
42
+ > allowlist for cross-origin deploys; the AWS `$connect` Lambda uses
43
+ > an explicit `WEB_ORIGINS` allowlist only (the SPA and the API
44
+ > Gateway wss endpoint are on different origins, so auto-derive does
45
+ > not apply). AWS deploys are **stack-output-driven** (a two-phase
46
+ > `scripts/deploy-web-aws.mjs` bakes the real `ConnectUrl` into the
47
+ > SPA config, then `s3 sync` + CloudFront invalidation).
48
+ >
49
+ > **Deploy security.** The AWS CI deploy is **OIDC-only** the
50
+ > GitHub Actions workflow assumes an IAM role via GitHub OIDC web
51
+ > identity (no long-lived access keys, with an in-workflow assertion
52
+ > that the assumed role matches `AWS_DEPLOY_ROLE_ARN` before any
53
+ > `cdk deploy`). API Gateway full-frame body logging
54
+ > (`DataTraceEnabled`) is **off by default and hard-locked**, so IRC
55
+ > frames (`PASS`, `AUTHENTICATE <SASL-PLAIN>`, channel keys,
56
+ > `PRIVMSG`/`NOTICE`) are never written to CloudWatch; the only way
57
+ > back on is an explicit two-flag sandbox escape hatch.
58
+ >
59
+ > **Integrated IRC services.** NickServ, ChanServ, HostServ,
60
+ > OperServ, and MemoServ run inside the daemon (no separate services
61
+ > process, no S2S link) backed by a `ServicesStore` port with
62
+ > **persistent D1 (Cloudflare) and DynamoDB (AWS) backends**
63
+ > (write-behind, surviving redeploys). The `ServicesStore` is the
64
+ > **single credential home**: SASL PLAIN, SASL EXTERNAL (CertFP),
65
+ > NickServ `IDENTIFY`, and `PASS <nick>:<password>` all verify through
66
+ > the same scrypt-hashed `verifyNick` / `verifyCertFP` surface, and
67
+ > `draft/read-marker` / `draft/pre-away` persist through the
68
+ > `ServicesStore`.
69
+ >
70
+ > See `CHANGELOG.md` for the per-release manifests.
51
71
 
52
72
  ---
53
73
 
@@ -160,6 +180,8 @@ ServerlessIRCd/
160
180
  │ ├── ci-hardening/ coverage-gate + mutation-config validators
161
181
  │ ├── seed-aws-accounts.ts scrypt-hash SASL PLAIN accounts into DynamoDB
162
182
  │ └── seed-cf-accounts.ts scrypt-hash SASL PLAIN accounts into Cloudflare D1
183
+ ├── scripts/
184
+ │ └── deploy-web-aws.mjs stack-output-driven AWS web client deploy (describe → bake → s3 sync → invalidate)
163
185
  ├── pnpm-workspace.yaml turbo.json tsconfig.base.json
164
186
  └── README.md CHANGELOG.md
165
187
  ```
@@ -295,27 +317,33 @@ treats `state.account !== undefined` as already authorised, so a
295
317
  deployment with both `SERVER_PASSWORD` and configured SASL accounts
296
318
  does not need to hand the shared password to identified users. The
297
319
  server-wide gate is a shared deployment secret (never log it); per-user
298
- credentials still go through the hashed SASL account store
299
- (`HashedAccountStore` / D1 / DynamoDB), which is unaffected.
320
+ credentials still go through the unified services credential store
321
+ (`ServicesStore.verifyNick` D1 `nickserv_accounts` / DynamoDB
322
+ `Services`), which is unaffected.
300
323
 
301
324
  **PASS-based account login:** in addition to SASL, a client may identify
302
- to its NickServ account at registration by sending
303
- `PASS <nick>:<password>` (the same `<nick>:<password>` literal the
304
- `SASL_ACCOUNTS` seed tooling uses). When an `AccountStore` is configured,
305
- `NICK alice` + `PASS alice:hunter2` + `USER …` verifies the credentials
306
- against the account store and, on success, logs the connection in as
307
- `alice` — emitting `900 RPL_LOGGEDIN` + `903 RPL_SASLSUCCESS` (the same
308
- "account is set" numerics SASL uses) before `001 RPL_WELCOME`, stamping
309
- user mode `+r`, and running the same read-marker / away / memo replay as
310
- a SASL login. The nick left of the `:` must match the `NICK` sent; a bare
311
- `PASS <value>` with no colon is always treated as a server-password
312
- candidate, never as account credentials. A wrong password or unknown nick
313
- is indistinguishable (no `904`/`464` from the auth path) and the
314
- connection simply proceeds un-identified when no server password is set.
315
- This composes with the gate above by precedence: an already-identified
316
- connection (SASL or PASS-auth) satisfies the gate; a `<nick>:<password>`
317
- that fails verify never matches a bare shared secret, so a server with
318
- both configured still rejects it with `464`.
325
+ to its NickServ account by sending `PASS <nick>:<password>` (the same
326
+ `<nick>:<password>` literal the `SASL_ACCOUNTS` seed tooling uses).
327
+ When a `ServicesStore` is configured, `NICK alice` + `PASS alice:hunter2`
328
+ + `USER …` verifies the credentials via `services.verifyNick` and, on
329
+ success, logs the connection in as `alice` — emitting `900 RPL_LOGGEDIN`
330
+ + `903 RPL_SASLSUCCESS` (the same "account is set" numerics SASL uses)
331
+ before `001 RPL_WELCOME`, stamping user mode `+r`, and running the same
332
+ read-marker / away / memo replay as a SASL login. The login is also
333
+ honoured when `PASS <nick>:<password>` arrives **after** registration
334
+ has completed (e.g. a client that sent `NICK`+`USER` before `PASS`, or
335
+ any `PASS` arriving after `CAP END` resolved): the same verify +
336
+ `applyAccountSuccess` pipeline runs, emitting `900`/`903` and stamping
337
+ `+r` in-band. The nick left of the `:` must match the connection's
338
+ current nick; a bare `PASS <value>` with no colon is always treated as
339
+ a server-password candidate (silent no-op once registration has
340
+ completed), never as account credentials. A wrong password or unknown
341
+ nick is indistinguishable (no `904`/`464` from the auth path) and the
342
+ connection simply proceeds un-identified when no server password is
343
+ set. This composes with the gate above by precedence: an
344
+ already-identified connection (SASL or PASS-auth) satisfies the gate;
345
+ a `<nick>:<password>` that fails verify never matches a bare shared
346
+ secret, so a server with both configured still rejects it with `464`.
319
347
 
320
348
  ### Connecting
321
349
 
@@ -386,7 +414,7 @@ against any line-oriented WebSocket endpoint.
386
414
 
387
415
  ## Load testing (`tools/load-test`)
388
416
 
389
- A synthetic WebSocket IRC client pool (PLAN §7). It opens N
417
+ A synthetic WebSocket IRC client pool. It opens N
390
418
  connections to a `ws://` / `wss://` target, registers each (NICK/USER),
391
419
  joins a channel, and optionally chats. Per-stage **p50 / p95 / p99
392
420
  latency** (connect / register / join / message) and the **drop rate**
@@ -436,47 +464,104 @@ report).
436
464
  ## Web client (`apps/web`)
437
465
 
438
466
  A vendored **Kiwi IRC** SPA served at `/webclient/` and a static project
439
- landing page served at `/`, both baked out of `apps/web/dist/` and
440
- served directly by the Cloudflare Worker via its `[assets]` binding.
441
- The browser opens a native `wss://` directly to the Worker's
442
- IRC-over-WebSocket endpoint no proxy, gateway, or transport
443
- adaptation in the request path. See `docs/WebClientGuide.md` for the
444
- end-to-end contributor/operator guide.
467
+ landing page served at `/`, both baked out of `apps/web/dist/`. The same
468
+ artifact is served by **both** platforms:
469
+
470
+ | Platform | HTTP/SPA origin | WebSocket origin | Same-origin? |
471
+ |-------------|----------------------------------------------------|---------------------------------------------------|--------------|
472
+ | Cloudflare | the Worker (`*.workers.dev` / custom domain) | the same Worker | **Yes** — one domain serves both |
473
+ | AWS | CloudFront (`*.cloudfront.net` / custom domain) | API Gateway (`*.execute-api.*.amazonaws.com`) | **No** — two different endpoints |
474
+
475
+ On Cloudflare the Worker's `[assets]` binding serves the SPA and the WS
476
+ upgrade on one origin — the browser opens a native `wss://` directly to
477
+ the Worker, no proxy or gateway. On AWS the SPA is fronted by an
478
+ **S3 + CloudFront + OAC** `StaticSite` construct
479
+ (`apps/aws-stack/src/static-site.ts`, opt-in via CDK context) and a
480
+ two-phase deploy bakes the real `ConnectUrl` into the SPA config (see
481
+ "Deploying on AWS" below). See `docs/WebClientGuide.md` for the CF
482
+ end-to-end guide and `docs/AWS-Deployment.md` §16 for the AWS path.
445
483
 
446
484
  Build the SPA + landing page (needs the submodule + `yarn`, provided by
447
485
  `corepack enable`):
448
486
 
449
487
  ```bash
450
488
  git submodule update --init apps/web/upstream # one-time per clone
451
- pnpm --filter web build:staging # or :prod / default
489
+ git submodule update --init docs # one-time per clone (docs site)
490
+ pnpm --filter web build # or build:prod (CF) / build:prod-aws (AWS)
452
491
  # → apps/web/dist/index.html (landing page, served at /)
453
492
  # → apps/web/dist/webclient/index.html (Kiwi SPA, served at /webclient/)
454
493
  # → apps/web/dist/webclient/static/config.json (baked, env-specific)
494
+ # → apps/web/dist/docs/<slug>.html (rendered docs/, served at /docs/)
455
495
  ```
456
496
 
457
- Then run everything locally (Worker serves SPA + landing page + WS on
458
- one origin):
497
+ The build also renders the `docs/` submodule (the Gitea wiki, 23 GFM
498
+ markdown files) to standalone HTML under `apps/web/dist/docs/` so the
499
+ existing `[assets]` binding serves them at `/docs/<slug>.html` (and
500
+ `/docs/` for the index — `Home.md` becomes `index.html`). Requires the
501
+ `docs/` submodule checkout; the build fails loudly with the recovery
502
+ command if it is missing or empty (mirroring the Kiwi `upstream/` guard).
503
+ `Home.md` is the docs landing page; `ADR-Index.md` lists every ADR.
504
+
505
+ ### Deploying on Cloudflare
506
+
507
+ Run everything locally (Worker serves SPA + landing page + WS on one
508
+ origin):
459
509
 
460
510
  ```bash
461
511
  pnpm build # workspace packages
462
512
  pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
463
513
  # / → landing page
464
514
  # /webclient/ → Kiwi SPA (opens ws://localhost:8787/)
515
+ # /docs/ → rendered docs/ (Home → index, per-page HTML)
465
516
  # /health → plaintext liveness
466
517
  ```
467
518
 
468
- Deploy staging (Worker + assets in one command):
519
+ Deploy the Worker (Worker + assets in one command):
520
+
521
+ ```bash
522
+ pnpm deploy:cf # wrangler deploy
523
+ ```
524
+
525
+ ### Deploying on AWS
526
+
527
+ The web client is **opt-in**: provision the `StaticSite` construct by
528
+ passing `webSite*` CDK context on the stack deploy, and (once the SPA
529
+ ships) set `webOrigins` for the CSWSH defence:
530
+
531
+ ```bash
532
+ pnpm deploy:aws -- \
533
+ -c webSiteCustomDomain=app.example.com \
534
+ -c webSiteCertificateArn=arn:aws:acm:us-east-1:... \
535
+ -c webSiteHostedZoneName=example.com. -c webSiteHostedZoneId=... \
536
+ -c webOrigins=https://app.example.com
537
+ ```
538
+
539
+ Then bake + ship the SPA (stack-output-driven — reads `ConnectUrl`,
540
+ `WebsiteBucketName`, `WebsiteDistributionId` from the stack outputs,
541
+ bakes the real wss URL into `config.json`, `s3 sync`s the build, and
542
+ invalidates the CloudFront edge cache):
469
543
 
470
544
  ```bash
471
- pnpm deploy:cf:staging # wrangler deploy --env staging
545
+ node scripts/deploy-web-aws.mjs
472
546
  ```
473
547
 
548
+ The ACM certificate **must** be in `us-east-1` (CloudFront requirement).
549
+ See `docs/AWS-Deployment.md` §16 for the full two-phase flow, the
550
+ `server` / `direct_path` config split (irc-framework prepends `wss://`
551
+ itself, so the host is baked scheme-less), and the custom-domain setup.
552
+
474
553
  ### WebSocket Origin policy (CSWSH defense)
475
554
 
476
- WebSocket upgrades do **not** follow the same-origin policy, so the
477
- Worker rejects browser upgrades whose `Origin` is not allowed see
478
- `apps/cf-worker/src/origin-allowlist.ts` and the SPA guide §5. Two
479
- modes, evaluated in order:
555
+ WebSocket upgrades do **not** follow the same-origin policy, so a
556
+ malicious page can open a WebSocket to the IRC server from a victim's
557
+ browser and drive the session with their credentials (Cross-Site
558
+ WebSocket Hijacking, CSWSH). Both adapters enforce an `Origin` policy
559
+ on the upgrade — but the **modes differ**, because the CF Worker serves
560
+ the SPA and the wss endpoint on the same origin while AWS serves them
561
+ on different origins:
562
+
563
+ **Cloudflare** — see `apps/cf-worker/src/origin-allowlist.ts` and the
564
+ SPA guide §5. Two modes, evaluated in order:
480
565
 
481
566
  1. **Explicit allowlist** — set `WEB_ORIGINS` (comma-separated) for
482
567
  cross-origin deploys (SPA on a different domain than the Worker,
@@ -485,11 +570,27 @@ modes, evaluated in order:
485
570
  unset/empty, the Worker compares the browser's `Origin` against the
486
571
  request's own origin. Match → proceed; mismatch → `403 Forbidden`.
487
572
 
488
- `WEB_ORIGINS` is **optional** — same-origin auto-derive needs zero
489
- per-env config and works for `*.workers.dev`, custom domains, and
490
- preview URLs alike. Non-browser clients (curl, WeeChat, the
491
- `tcp-ws-forwarder`, scripted harnesses) never send `Origin` and pass
492
- through unchanged in both modes.
573
+ `WEB_ORIGINS` is **optional** on CF — same-origin auto-derive needs
574
+ zero per-env config and works for `*.workers.dev`, custom domains, and
575
+ preview URLs alike.
576
+
577
+ **AWS** see `packages/aws-adapter/src/origin-allowlist.ts` and
578
+ `docs/AWS-Deployment.md` §8.2. **Explicit allowlist only** (no
579
+ auto-derive): the SPA is on a CloudFront origin and the wss endpoint is
580
+ on an API Gateway origin, so the request's own host is never the SPA's
581
+ origin. The defence is **opt-in** — unset `WEB_ORIGINS` skips the check
582
+ entirely (existing bare-IRC deployments without a web frontend are
583
+ unchanged on upgrade); set it to the SPA's origin(s) once the web client
584
+ ships:
585
+
586
+ ```bash
587
+ pnpm deploy:aws -- -c webOrigins=https://app.example.com
588
+ # or as a stack prop: webOrigins: 'https://app.example.com,https://staging.app.example.com'
589
+ ```
590
+
591
+ Non-browser clients (curl, WeeChat, the `tcp-ws-forwarder`, scripted
592
+ harnesses) never send `Origin` and pass through unchanged on both
593
+ platforms.
493
594
 
494
595
  ---
495
596
 
@@ -552,92 +653,33 @@ port and an `IdFactory` port are injected so tests are fully deterministic.
552
653
 
553
654
  ## Roadmap
554
655
 
555
- A v1 ships when `irc-core` is at 100% coverage, the parametrized contract
556
- suite passes against every runtime, both adapters are deployed to staging,
557
- and ≥3 reference clients (WeeChat, HexChat, IRCCloud, TheLounge) connect
558
- cleanly. Per-release manifests live in `CHANGELOG.md`.
559
-
560
- - **Foundation** (monorepo, turbo, vitest, biome, CI). ✅
561
- - **Pure protocol engine** — reducers, IRCv3 caps, isupport,
562
- SASL (`PLAIN`), token-bucket flood control, chat history. (most in
563
- **v0.1.0**; SASL/flood/chathistory landed in **v0.2.0**).
564
- - **`IrcRuntime` port** in-memory runtime, `ConnectionActor`,
565
- local CLI, parametrized contract suite. ✅ landed in **v0.1.0**.
566
- - **Cloudflare adapter** (ConnectionDO / ChannelDO / RegistryDO).
567
- landed in **v0.1.0** (staging deploy via CI; prod deploy is manual).
568
- - **AWS adapter** (APIGW WS + Lambda + DynamoDB + CDK).
569
- landed in **v0.2.0** (staging deploy via CI; prod deploy is manual).
570
- - **Observability, security hardening, config, CI gates.**
571
- landed across v0.2.0–v0.4.0 (logger port, server-password, cloaking,
572
- admission limits, coverage + mutation gates, OPER credential auth, the
573
- Cloudflare config loader, and AWS max-clients admission); the
574
- required-`serverName` gate closed in **v0.6.0**.
575
- - **TLS hardening & raw TCP transport** (`irc+tls :6697`).
576
- ✅ landed in **v0.4.0**: generalized `ConnectionActor` transport seam,
577
- Cloudflare Spectrum + Container origin, AWS NLB + Lambda streaming,
578
- mTLS → SASL `EXTERNAL`, transport-parametrized contract suite, ADR-009.
579
- - **Load testing & compatibility sweep.** 🔄 partial as of
580
- **v0.6.0**: the `tools/load-test` 10k-connection harness is in-tree;
581
- the formal 10k-connection report per platform and the client
582
- compatibility matrix (WeeChat / HexChat / IRCCloud / TheLounge) are
583
- still pending.
584
- - **PLAN-FIXES remediation.** ✅ landed across v0.3.0–v0.4.0
585
- (MessageStore wired into all three adapters, AccountStore end-to-end,
586
- the DynamoDB `Accounts` table decided, CF enumeration RPCs, AWS QUIT
587
- fanout, `OPER` / `TAGMSG` / `WHOWAS`, RFC 1459 case-mapping, real
588
- MOTD, config-driven server identity, AWS max-clients admission,
589
- CF D1 SASL store, and a sweep of dead-code / stale-comment cleanup).
590
- - **Runtime & tooling baseline.** ✅ landed in **v0.4.0**
591
- (Node ≥ 24 / pnpm 11).
592
- - **IRCv3 WebSocket extension.** ✅ landed in **v0.4.0**:
593
- `text.ircv3.net` / `binary.ircv3.net` subprotocol negotiation +
594
- per-message framing across the CF Worker, AWS APIGW, local CLI, and
595
- `tcp-ws-forwarder` (510-byte budget, `1009`/`1003` close codes,
596
- legacy fallback).
597
- - **Deferred IRC verbs.** ✅ landed in **v0.5.0**: `KILL`,
598
- `REHASH`, `LUSERS` + `STATS`, `TRACE`, `WALLOPS`, `SETNAME`, plus
599
- the formal drop of the S2S (`CONNECT`/`SQUIT`/`LINKS`) and obsolete
600
- RFC 2812 (`SERVICE`/`SUMMON`/`USERS`) verbs. The protocol surface is
601
- feature-complete for modern clients.
602
- - **IRCv3 extension sweep.** ✅ landed in **v0.6.0**: ten
603
- negotiated caps (`account-notify`, `msgid`, `standard-replies`,
604
- `MONITOR`, `labeled-response`, `sts`, `draft/typing`,
605
- `draft/multiline`, `draft/read-marker`, `draft/pre-away`), the
606
- ISUPPORT tokens for the new caps (`MONITOR`/`MULTILINE`/`TYPING`/
607
- `STATUSMSG`/`EXTBAN`/`ACCOUNTEXTBAN`), the read-only user mode `S`
608
- (TLS connected), and the async push fanout seam (Registry hooks →
609
- `MONITOR` online/offline, `BroadcastWallops`-style caps-gated
610
- delivery).
611
- - **IRC services.** ✅ landed in **v0.8.0**: `ServicesStore` port
612
- + integrated NickServ (`REGISTER`/`IDENTIFY`/`DROP`/`INFO`/`SET
613
- ENFORCE` + nick enforcement), ChanServ (`REGISTER`/`DROP`/`SET`/
614
- `ACCESS`/`LEVELS` + `+r`/`+R`/`+M`), HostServ
615
- (`ON`/`OFF`/`REQUEST` + oper `SET`/`APPROVE`), OperServ
616
- (`AKILL`/`JUPE`/`RAW`), MemoServ (`SEND`/`LIST`/`READ`/`DEL`).
617
- Persistent backends ship alongside — **D1** on Cloudflare,
618
- **DynamoDB** on AWS — both write-behind so registrations survive
619
- redeploys. The SASL `AccountStore` and NickServ accounts are
620
- **unified** under one scrypt-hashed credential store, `PASS
621
- <nick>:<password>` identifies at registration, and `draft/read-marker`
622
- / `draft/pre-away` now persist through the `ServicesStore` (no longer
623
- in-memory-only). See `docs/Services.md`.
624
- - **Web client.** 🔄 partial as of **v0.7.0**: a vendored
625
- Kiwi IRC SPA is served at `/webclient/` and a static landing page at `/`
626
- by the Cloudflare Worker via an `[assets]` binding, with a Zod-
627
- validated per-env config matrix and **CSWSH defense** (same-origin
628
- auto-derive + optional `WEB_ORIGINS` allowlist). The Playwright
629
- headless-browser e2e is the remaining open ticket.
630
- See `docs/WebClientGuide.md` for the contributor/operator guide.
631
- - **Coverage hardening.** 🔄 partial as of **v0.7.0**: `aws-adapter`
632
- and `aws-stack` clear the 90% gate (v0.6.0); the CF packages run
633
- under istanbul (v0.6.0). The remaining packages (`cf-adapter`,
634
- `local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
635
- `irc-test-support`, `web`) are **above the gate but below 100%** —
636
- follow-ups drive each to 100%.
656
+ A v1 ships when the parametrized contract suite passes against every
657
+ runtime, the client compatibility matrix is signed off, and every
658
+ package clears its coverage gate. Per-release manifests live in
659
+ `CHANGELOG.md`.
660
+
661
+ Active follow-ups:
662
+
663
+ - **Client compatibility sweep** formal sign-off on ≥3 reference
664
+ clients (WeeChat / HexChat / IRCCloud / TheLounge) against both
665
+ deployed adapters, on both the WebSocket and `irc+tls :6697`
666
+ surfaces.
667
+ - **Web client e2e** Playwright headless-browser e2e exercising the
668
+ vendored Kiwi IRC SPA against a deployed stack.
669
+ - **Coverage hardening** `aws-adapter` and `aws-stack` clear the 90%
670
+ gate; the CF packages run under istanbul. The remaining packages
671
+ (`cf-adapter`, `local-cli`, `load-test`, `cf-tcp-container`,
672
+ `tcp-ws-forwarder`, `irc-test-support`, `web`) sit above the gate
673
+ but below 100%; follow-ups drive each to full coverage. `irc-core`,
674
+ `irc-server`, and `in-memory-runtime` are at 100%.
675
+ - **Persistent ChanServ ban list** — ban masks currently live on
676
+ `ChannelState.banMasks` and do not survive an empty-recreate of a
677
+ channel. Extending `ServicesStore` with a persistent ban list is the
678
+ documented next step.
637
679
 
638
680
  ---
639
681
 
640
- ## Protocol scope (v1)
682
+ ## Protocol scope
641
683
 
642
684
  **Core (RFC 1459/2812 subset):** registration (`NICK`/`USER`/`CAP`/`PASS`),
643
685
  `PING`/`PONG`, `QUIT`, `JOIN`, `PART`, `PRIVMSG`, `NOTICE`, `MODE` (user +
@@ -654,9 +696,9 @@ disconnect), `REHASH` (config reload via `IrcRuntime.reloadConfig()`),
654
696
  `WALLOPS` (global broadcast to every `+w` user).
655
697
 
656
698
  (Out of scope: the S2S verbs `CONNECT`/`SQUIT`/`LINKS` and the obsolete
657
- RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped** as of
658
- v0.5.0 — they return `421 ERR_UNKNOWNCOMMAND`, and their reserved
659
- numerics have been removed. S2S linking is a PLAN non-goal.)
699
+ RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped**
700
+ they return `421 ERR_UNKNOWNCOMMAND`, and their reserved numerics have
701
+ been removed. S2S linking is a stated non-goal.)
660
702
 
661
703
  **Channel modes:** `o v b i k l t n m s p`, plus the
662
704
  services-derived modes `r` (registered), `R` (block unidentified
@@ -668,24 +710,48 @@ via `276 RPL_WHOISSECURE`, not settable via `MODE`) and the read-only
668
710
  `r` (registered — set by NickServ `IDENTIFY` / SASL login, surfaced as
669
711
  `+r`, not settable via `MODE`).
670
712
 
671
- **Integrated IRC services** (v0.8.0): the service nicks `NickServ`,
672
- `ChanServ`, `HostServ`, `OperServ`, and `MemoServ` are reserved and
673
- routed to dedicated pure reducers when a `ServicesStore` is bound —
674
- there is **no separate services process and no S2S link**. Reached via
675
- `PRIVMSG <Service> :<subcommand>`:
676
- NickServ (`REGISTER` / `IDENTIFY` / `DROP` / `INFO` / `SET ENFORCE` +
677
- nick enforcement on the `NICK` path), ChanServ (`REGISTER` / `DROP` /
678
- `SET` / `INFO` / `ACCESS` / `LEVELS` + auto-op on `JOIN`), HostServ
679
- (`ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` / `LIST`, with
680
- `CHGHOST` fanout), OperServ (`AKILL` / `JUPE` / `UNJUPE` / `RAW`), and
681
- MemoServ (`SEND` / `LIST` / `READ` / `DEL` with queue delivery at
682
- identify). The SASL `AccountStore` and NickServ accounts share one
683
- scrypt-hashed credential store, so a registered nick is also a SASL
684
- login and vice versa. Backends: D1 on Cloudflare, DynamoDB on AWS,
685
- in-memory for the local CLI / tests (all write-behind; registrations
686
- survive redeploys). When no store is bound, services commands reply
687
- `501` and the rest of the daemon is unaffected. See
688
- `docs/Services.md` for the full reference.
713
+ **Integrated IRC services:** the service nicks `NickServ`, `ChanServ`,
714
+ `HostServ`, `OperServ`, and `MemoServ` are reserved and routed to
715
+ dedicated pure reducers when a `ServicesStore` is bound — there is **no
716
+ separate services process and no S2S link**. Each service also accepts a
717
+ **shortcut verb** (`/NICKSERV`, `/NS`, `/CS`, `/HS`, `/MS`, `/OS`, …)
718
+ that the daemon rewrites to the equivalent `PRIVMSG <Service> :<args>`
719
+ for clients with a dedicated slash-command UX. Reached via `PRIVMSG
720
+ <Service> :<subcommand>`:
721
+
722
+ - **NickServ** `REGISTER` / `IDENTIFY` (alias: `ID`) / `DROP` / `INFO`
723
+ (owner + oper only for the `Email:` line) / `SET ENFORCE` + nick
724
+ enforcement on the `NICK` path / `SET PASSWORD` (self-service password
725
+ change; requires the current password re-supplied). `INFO <nick>` with
726
+ no target resolves to the caller's own account.
727
+ - **ChanServ** `REGISTER` / `DROP` / `SET` (`FOUNDER` / `MLOCK` /
728
+ `RESTRICTED` / `KEEPTOPIC`) / `INFO` / `ACCESS` (`SOP` / `AOP` / `HOP`
729
+ / `VOP`, each `ADD` / `DEL` / `LIST`) / `LEVELS`, plus the prefix /
730
+ roster mutation verbs `OP`, `DEOP`, `VOICE`, `DEVOICE`, `KICK`, and
731
+ `BAN` / `UNBAN` (each emits the corresponding `:ChanServ MODE` /
732
+ `KICK` broadcast and `ApplyChannelDelta` against the channel
733
+ authority). Auto-op / auto-voice on `JOIN` follows the access list;
734
+ the first joiner of a registered channel no longer gets auto-opped
735
+ merely for being first.
736
+ - **HostServ** — `ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` /
737
+ `ACTIVATE` / `REJECT` / `LIST`, with `CHGHOST` fanout. An assigned
738
+ vhost is **auto-applied on identify** (SASL, NickServ `IDENTIFY`, or
739
+ PASS-auth): `state.host` is set, `state.vhostActive` is stamped, and
740
+ any currently-joined `chghost`-capable peers see the broadcast.
741
+ Oper-only subcommands are hidden from non-opers in the help NOTICE.
742
+ - **OperServ** — `AKILL` / `JUPE` / `UNJUPE` / `RAW` (oper-only).
743
+ - **MemoServ** — `SEND` / `LIST` / `READ` / `DEL` with queue delivery
744
+ at identify.
745
+
746
+ The `ServicesStore` is the **single credential home**: SASL PLAIN, SASL
747
+ EXTERNAL (CertFP), NickServ `IDENTIFY`, and `PASS <nick>:<password>` all
748
+ verify through the same scrypt-hashed `verifyNick` / `verifyCertFP`
749
+ surface, so a registered nick is also a SASL login and vice versa.
750
+ Backends: D1 on Cloudflare, DynamoDB on AWS, in-memory for the
751
+ local CLI / tests (all write-behind; registrations survive redeploys).
752
+ When no store is bound, services commands reply `501` and the rest of
753
+ the daemon is unaffected. See `docs/Services.md` for the full
754
+ reference.
689
755
 
690
756
  **IRCv3 extensions (negotiated via `CAP`):** `message-tags` (incl. the
691
757
  `TAGMSG` command), `server-time`, `account-tag`, `account-notify`
@@ -724,42 +790,29 @@ bridges a stock TCP client to a deployed WebSocket endpoint.
724
790
 
725
791
  ## Further reading
726
792
 
727
- - `CHANGELOG.md` — per-release manifests (Keep a Changelog format), including
728
- the v0.2.0 work (AWS adapter, SASL, flood control, security hardening, CI
729
- gates), v0.3.0 (OPER, TAGMSG, RFC 1459 case-mapping, real MOTD,
730
- ChannelRegistryDO, SASL account persistence), v0.4.0 (dual transport
731
- `irc+tls :6697`, SASL `EXTERNAL`/mTLS, WHOWAS + remaining verbs, D1 account
732
- store, Node 24/pnpm 11), v0.5.0 (deferred IRC verbs `KILL` / `REHASH`
733
- / `LUSERS` + `STATS` / `TRACE` / `WALLOPS` / `SETNAME`, plus the formal
734
- drop of the S2S and obsolete RFC 2812 verbs), v0.6.0 (the IRCv3
735
- extension sweep — `account-notify`, `msgid`, `standard-replies`,
736
- `MONITOR`, `labeled-response`, `sts`, `draft/typing`, `draft/multiline`,
737
- `draft/read-marker`, `draft/pre-away`, plus user mode `S`; the
738
- required-`serverName` gate; `tools/load-test`; aws-adapter / aws-stack
739
- coverage unblocks; CF packages moved to istanbul; deploys now
740
- manual-only), and **v0.7.0** (the web client — vendored Kiwi IRC SPA
741
- at `/webclient/`, static landing page at `/`, Cloudflare Worker
742
- `[assets]` binding, per-env Zod-validated config matrix, and CSWSH
743
- defense via same-origin auto-derive + optional `WEB_ORIGINS`;
744
- `/health` becomes an explicit route; mutation-killing tests for
745
- `irc-core`'s parser / batch / numerics), and **v0.8.0** (integrated
746
- IRC services — NickServ / ChanServ / HostServ / OperServ / MemoServ
747
- backed by a `ServicesStore` port with persistent D1 + DynamoDB
748
- backends; unified SASL + NickServ scrypt-hashed account store;
749
- `PASS <nick>:<password>` registration login; the `draft/read-marker`
750
- `MARKREAD` verb).
793
+ - `CHANGELOG.md` — per-release manifests in Keep a Changelog format.
751
794
  - `docs/Services.md` — operator and contributor reference for the
752
795
  integrated IRC services: architecture (integrated vs. pseudo-client),
753
- the per-service command tables, the `+r`/`+R`/`+M` modes, nick
754
- enforcement, adapter backends, and an end-to-end registration
755
- walkthrough.
756
- - `docs/WebClientGuide.md` end-to-end contributor/operator doc for the
757
- web client: build pipeline, per-env config matrix, CSWSH rationale and
758
- the optional `WEB_ORIGINS` var, local dev, optional Cloudflare Pages
759
- alternative, and troubleshooting.
760
- - `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md`
761
- end-to-end guides for the `:6697` TCP+TLS variants (Spectrum/Container on
762
- CF, NLB + Lambda streaming on AWS), including mTLS trust-store setup.
763
- - `README.md` in each `packages/*` and `apps/*` — per-package notes (e.g.
764
- `packages/aws-adapter/README.md` for the DynamoDB-Local test setup,
765
- `apps/aws-stack/README.md` for CDK commands and localstack validation).
796
+ the per-service command tables (including ChanServ `OP`/`DEOP`/
797
+ `VOICE`/`DEVOICE`/`KICK`/`BAN`/`UNBAN`, HostServ oper approval flow,
798
+ the `+r`/`+R`/`+M` modes, nick enforcement, the unified scrypt
799
+ account store, service shortcut verbs), adapter backends, and an
800
+ end-to-end registration walkthrough.
801
+ - `docs/WebClientGuide.md` end-to-end contributor/operator doc for
802
+ the **Cloudflare** web client: build pipeline, per-env config matrix,
803
+ CSWSH rationale and the optional `WEB_ORIGINS` var, local dev,
804
+ optional Cloudflare Pages alternative, and troubleshooting.
805
+ - `docs/AWS-Deployment.md` end-to-end AWS deploy guide: first-time
806
+ setup, CDK reference, DynamoDB capacity planning, region strategy,
807
+ cost notes, **OIDC-only CI** (§17), **APIGW `DataTraceEnabled` hard-
808
+ lock** (§7.6), and the **S3 + CloudFront + OAC web client** (§16,
809
+ including the two-phase stack-output-driven deploy and the
810
+ `webOrigins` CSWSH knob).
811
+ - `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md`
812
+ — end-to-end guides for the `:6697` TCP+TLS variants
813
+ (Spectrum/Container on CF, NLB + Lambda streaming on AWS), including
814
+ mTLS trust-store setup.
815
+ - `README.md` in each `packages/*` and `apps/*` — per-package notes
816
+ (e.g. `packages/aws-adapter/README.md` for the DynamoDB-Local test
817
+ setup, `apps/aws-stack/README.md` for CDK commands and localstack
818
+ validation).