serverless-ircd 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/.github/workflows/ci.yml +7 -3
  2. package/.gitmodules +1 -1
  3. package/CHANGELOG.md +518 -29
  4. package/README.md +221 -161
  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-tcp-container/src/container-server.ts +21 -1
  10. package/apps/cf-tcp-container/tests/config-loader.test.ts +43 -0
  11. package/apps/cf-tcp-container/tests/container-server.test.ts +249 -1
  12. package/apps/cf-tcp-container/tests/persistence.test.ts +9 -0
  13. package/apps/cf-tcp-container/tests/tls-e2e.test.ts +24 -5
  14. package/apps/cf-worker/package.json +1 -1
  15. package/apps/cf-worker/src/worker.ts +4 -4
  16. package/apps/cf-worker/tests/fixtures/web-dist/webclient/index.html +18 -0
  17. package/apps/cf-worker/tests/smoke.test.ts +5 -5
  18. package/apps/cf-worker/wrangler.test.toml +6 -6
  19. package/apps/cf-worker/wrangler.toml +7 -5
  20. package/apps/local-cli/package.json +1 -1
  21. package/apps/local-cli/src/config-loader.ts +8 -0
  22. package/apps/local-cli/src/main.ts +16 -0
  23. package/apps/local-cli/src/server.ts +95 -31
  24. package/apps/local-cli/tests/config-loader.test.ts +14 -0
  25. package/apps/local-cli/tests/config-resolution.test.ts +65 -0
  26. package/apps/local-cli/tests/motd-file-non-error.test.ts +29 -0
  27. package/apps/local-cli/tests/rehash.test.ts +147 -0
  28. package/apps/local-cli/tests/server-helpers.test.ts +63 -0
  29. package/apps/local-cli/tests/tcp.test.ts +89 -0
  30. package/apps/local-cli/tests/ws-subprotocol.test.ts +92 -0
  31. package/apps/web/landing/index.html +237 -16
  32. package/apps/web/package.json +3 -2
  33. package/apps/web/scripts/build.mjs +46 -16
  34. package/apps/web/src/build-env.ts +1 -1
  35. package/apps/web/src/config-schema.ts +6 -6
  36. package/apps/web/src/render-docs.ts +292 -0
  37. package/apps/web/tests/build-smoke.test.ts +43 -14
  38. package/apps/web/tests/config-schema.test.ts +1 -1
  39. package/apps/web/tests/landing-content.test.ts +103 -0
  40. package/apps/web/tests/render-docs.test.ts +198 -0
  41. package/docs/AWS-Adapter-Architecture.md +3 -2
  42. package/docs/AWS-Deployment.md +21 -8
  43. package/docs/Cloudflare-Deployment-Guide.md +22 -6
  44. package/docs/PlanExtensions.md +113 -3
  45. package/docs/Release-Process.md +23 -13
  46. package/docs/Services.md +578 -0
  47. package/docs/WebClientGuide.md +32 -31
  48. package/package.json +2 -2
  49. package/packages/aws-adapter/package.json +1 -1
  50. package/packages/aws-adapter/src/aws-runtime.ts +20 -1
  51. package/packages/aws-adapter/src/cdk-table-defs.ts +6 -0
  52. package/packages/aws-adapter/src/config-loader.ts +11 -0
  53. package/packages/aws-adapter/src/connection-counter.ts +89 -0
  54. package/packages/aws-adapter/src/dynamo-services-store.ts +649 -0
  55. package/packages/aws-adapter/src/handlers/connect.ts +55 -51
  56. package/packages/aws-adapter/src/handlers/default.ts +36 -4
  57. package/packages/aws-adapter/src/handlers/index.ts +15 -0
  58. package/packages/aws-adapter/src/handlers/nlb-stream.ts +25 -2
  59. package/packages/aws-adapter/src/handlers/sweeper.ts +5 -1
  60. package/packages/aws-adapter/src/index.ts +4 -0
  61. package/packages/aws-adapter/src/stats.ts +6 -1
  62. package/packages/aws-adapter/src/tables.ts +34 -4
  63. package/packages/aws-adapter/tests/aws-harness.ts +3 -0
  64. package/packages/aws-adapter/tests/aws-runtime.test.ts +23 -1
  65. package/packages/aws-adapter/tests/config-loader.test.ts +8 -0
  66. package/packages/aws-adapter/tests/connect.test.ts +158 -32
  67. package/packages/aws-adapter/tests/connection-counter.test.ts +144 -0
  68. package/packages/aws-adapter/tests/dynamo-services-store-dynamo.test.ts +183 -0
  69. package/packages/aws-adapter/tests/dynamo-services-store-unit.test.ts +568 -0
  70. package/packages/aws-adapter/tests/global-setup.ts +28 -1
  71. package/packages/aws-adapter/tests/gone-exception.test.ts +21 -2
  72. package/packages/aws-adapter/tests/handlers.test.ts +105 -3
  73. package/packages/aws-adapter/tests/nlb-stream.test.ts +29 -1
  74. package/packages/aws-adapter/tests/sweeper.test.ts +20 -0
  75. package/packages/aws-adapter/tests/tables.test.ts +6 -1
  76. package/packages/cf-adapter/package.json +1 -1
  77. package/packages/cf-adapter/src/config-loader.ts +11 -0
  78. package/packages/cf-adapter/src/connection-do.ts +130 -8
  79. package/packages/cf-adapter/src/d1-services-store.ts +703 -0
  80. package/packages/cf-adapter/src/env.ts +8 -0
  81. package/packages/cf-adapter/src/index.ts +5 -0
  82. package/packages/cf-adapter/tests/config-loader.test.ts +19 -0
  83. package/packages/cf-adapter/tests/connection-do-nickserv-d1.test.ts +128 -0
  84. package/packages/cf-adapter/tests/connection-do-pure.test.ts +130 -0
  85. package/packages/cf-adapter/tests/connection-do.test.ts +150 -2
  86. package/packages/cf-adapter/tests/d1-services-store.test.ts +582 -0
  87. package/packages/cf-adapter/tests/serialize.test.ts +1 -0
  88. package/packages/in-memory-runtime/package.json +1 -1
  89. package/packages/irc-core/package.json +1 -1
  90. package/packages/irc-core/scripts/generate-build-info.mjs +26 -5
  91. package/packages/irc-core/src/commands/account-auth.ts +200 -0
  92. package/packages/irc-core/src/commands/chanserv.ts +1166 -0
  93. package/packages/irc-core/src/commands/hostserv.ts +522 -0
  94. package/packages/irc-core/src/commands/index.ts +13 -0
  95. package/packages/irc-core/src/commands/join.ts +171 -9
  96. package/packages/irc-core/src/commands/markread.ts +202 -0
  97. package/packages/irc-core/src/commands/memoserv.ts +319 -0
  98. package/packages/irc-core/src/commands/mode.ts +96 -4
  99. package/packages/irc-core/src/commands/nickserv.ts +402 -0
  100. package/packages/irc-core/src/commands/oper.ts +18 -1
  101. package/packages/irc-core/src/commands/operserv.ts +346 -0
  102. package/packages/irc-core/src/commands/pre-away.ts +3 -1
  103. package/packages/irc-core/src/commands/privmsg.ts +42 -0
  104. package/packages/irc-core/src/commands/read-marker.ts +8 -8
  105. package/packages/irc-core/src/commands/registration.ts +78 -12
  106. package/packages/irc-core/src/commands/sasl.ts +18 -49
  107. package/packages/irc-core/src/commands/service-aliases.ts +52 -0
  108. package/packages/irc-core/src/commands/tagmsg.ts +41 -6
  109. package/packages/irc-core/src/commands/topic.ts +60 -10
  110. package/packages/irc-core/src/config.ts +36 -5
  111. package/packages/irc-core/src/effects.ts +56 -1
  112. package/packages/irc-core/src/ports.ts +1653 -84
  113. package/packages/irc-core/src/protocol/numerics.ts +8 -0
  114. package/packages/irc-core/src/state/channel.ts +38 -1
  115. package/packages/irc-core/src/state/connection.ts +25 -1
  116. package/packages/irc-core/src/types.ts +48 -12
  117. package/packages/irc-core/tests/commands/chanserv.test.ts +2335 -0
  118. package/packages/irc-core/tests/commands/chathistory.test.ts +6 -0
  119. package/packages/irc-core/tests/commands/hostserv.test.ts +1006 -0
  120. package/packages/irc-core/tests/commands/join.test.ts +572 -1
  121. package/packages/irc-core/tests/commands/markread.test.ts +361 -0
  122. package/packages/irc-core/tests/commands/memoserv.test.ts +654 -0
  123. package/packages/irc-core/tests/commands/mode.test.ts +381 -2
  124. package/packages/irc-core/tests/commands/nickserv.test.ts +990 -0
  125. package/packages/irc-core/tests/commands/oper.test.ts +13 -0
  126. package/packages/irc-core/tests/commands/operserv.test.ts +656 -0
  127. package/packages/irc-core/tests/commands/privmsg.test.ts +147 -0
  128. package/packages/irc-core/tests/commands/read-marker.test.ts +28 -28
  129. package/packages/irc-core/tests/commands/registration.test.ts +1015 -20
  130. package/packages/irc-core/tests/commands/sasl.test.ts +229 -12
  131. package/packages/irc-core/tests/commands/server-info.test.ts +9 -5
  132. package/packages/irc-core/tests/commands/service-aliases.test.ts +52 -0
  133. package/packages/irc-core/tests/commands/tagmsg.test.ts +73 -33
  134. package/packages/irc-core/tests/commands/topic.test.ts +94 -2
  135. package/packages/irc-core/tests/commands/unified-account.test.ts +416 -0
  136. package/packages/irc-core/tests/config.test.ts +49 -5
  137. package/packages/irc-core/tests/effects.test.ts +19 -0
  138. package/packages/irc-core/tests/message-store.test.ts +63 -0
  139. package/packages/irc-core/tests/persistent-services-store.test.ts +582 -0
  140. package/packages/irc-core/tests/services-store.test.ts +1289 -0
  141. package/packages/irc-core/tests/state/channel.test.ts +3 -0
  142. package/packages/irc-server/package.json +1 -1
  143. package/packages/irc-server/src/actor.ts +121 -16
  144. package/packages/irc-server/src/dispatch.ts +94 -7
  145. package/packages/irc-server/src/routing.ts +19 -0
  146. package/packages/irc-server/tests/actor.test.ts +988 -15
  147. package/packages/irc-server/tests/dispatch.test.ts +270 -2
  148. package/packages/irc-server/tests/routing.test.ts +6 -0
  149. package/packages/irc-test-support/package.json +1 -1
  150. package/packages/irc-test-support/src/in-memory-harness.ts +37 -8
  151. package/packages/irc-test-support/src/index.ts +1 -0
  152. package/packages/irc-test-support/src/scenarios.ts +21 -6
  153. package/packages/irc-test-support/tests/in-memory-harness.test.ts +51 -0
  154. package/packages/irc-test-support/vitest.config.ts +6 -1
  155. package/tools/ci-hardening/package.json +1 -1
  156. package/tools/load-test/package.json +1 -1
  157. package/tools/load-test/src/client.ts +13 -13
  158. package/tools/load-test/tests/client.test.ts +258 -2
  159. package/tools/load-test/tests/config.test.ts +39 -0
  160. package/tools/load-test/tests/harness.test.ts +21 -0
  161. package/tools/load-test/tests/metrics.test.ts +7 -0
  162. package/tools/tcp-ws-forwarder/package.json +1 -1
  163. package/tools/tcp-ws-forwarder/tests/close-error.test.ts +40 -0
  164. package/tools/tcp-ws-forwarder/tests/defensive-branches.test.ts +78 -0
  165. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +53 -2
  166. package/tools/tcp-ws-forwarder/tests/logger.test.ts +31 -1
  167. package/apps/cf-worker/tests/fixtures/web-dist/app/index.html +0 -18
  168. package/packages/irc-core/tests/read-marker-store.test.ts +0 -108
package/README.md CHANGED
@@ -6,46 +6,58 @@ 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.7.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
- > (Phase 12)** 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 (Phase 14)**: a vendored **Kiwi IRC** SPA served at `/app/`
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. Remaining 0.x work: the client compatibility sweep
35
- > (WeeChat / HexChat / IRCCloud / TheLounge), the Playwright
36
- > browser-driven e2e for the SPA, IRC services (NickServ / ChanServ /
37
- > HostServ / OperServ / MemoServ), and driving the remaining
38
- > sub-100% packages to full coverage. See `CHANGELOG.md` for the
39
- > 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, with **Cross-Site WebSocket Hijacking (CSWSH)
36
+ > defense** wired into the WS upgrade path (same-origin auto-derive by
37
+ > default, optional `WEB_ORIGINS` allowlist for cross-origin
38
+ > deploys). The `docs/` wiki is rendered to standalone HTML at
39
+ > `/docs/`.
40
+ >
41
+ > **Integrated IRC services.** NickServ, ChanServ, HostServ,
42
+ > OperServ, and MemoServ run inside the daemon (no separate services
43
+ > process, no S2S link) backed by a `ServicesStore` port with
44
+ > **persistent D1 (Cloudflare) and DynamoDB (AWS) backends**
45
+ > (write-behind, surviving redeploys). The SASL `AccountStore` and
46
+ > NickServ accounts are **unified** under one scrypt-hashed credential
47
+ > store, `PASS <nick>:<password>` identifies at or after registration,
48
+ > and `draft/read-marker` / `draft/pre-away` persist through the
49
+ > `ServicesStore`.
50
+ >
51
+ > See `CHANGELOG.md` for the per-release manifests.
40
52
 
41
53
  ---
42
54
 
43
55
  ## Why
44
56
 
45
57
  IRC servers have historically been long-running stateful processes. This
46
- project explores what happens when the protocol brain is a set of **pure
47
- reducers** and the only platform-specific code is the side-effect plumbing
48
- (transports, registries, fanout). The payoff:
58
+ project moves the protocol logic into a set of **pure reducers** so that
59
+ the only platform-specific code is the side-effect layer (transports,
60
+ registries, fanout). The consequences:
49
61
 
50
62
  - Every command handler is a trivial unit test: arrange state, apply a message,
51
63
  assert the new state and emitted effects.
@@ -57,7 +69,7 @@ reducers** and the only platform-specific code is the side-effect plumbing
57
69
 
58
70
  ## Architecture
59
71
 
60
- Hexagonal / ports-and-adapters. The core reasons about IRC; adapters move bytes.
72
+ Hexagonal / ports-and-adapters. The core implements the IRC protocol; adapters handle I/O.
61
73
 
62
74
  ```
63
75
  ┌─────────────────────────────────────────────────────────────────┐
@@ -97,7 +109,7 @@ function. Both feed the same `ConnectionActor` through a `Transport`
97
109
  seam (`WsTextFrameTransport` vs. `TcpByteStreamTransport`); the
98
110
  parser/reducer/dispatch pipeline is identical.
99
111
 
100
- ### The key idea: pure reducers + location-of-authority
112
+ ### Design: pure reducers + location-of-authority
101
113
 
102
114
  Each command handler is a pure function:
103
115
 
@@ -122,6 +134,7 @@ Each reducer runs in whichever authority **owns** the state it mutates:
122
134
  | PRIVMSG/NOTICE to channel | Channel entity | Fanout (reads roster) |
123
135
  | Nick collision check | Registry entity | Nick → Connection map |
124
136
  | NAMES / WHO / WHOIS | Reads snapshots | (no mutation) |
137
+ | Services (`PRIVMSG NickServ`/`ChanServ`/…) | Connection entity | `ServicesStore` (account/channel/vhost/memo state) |
125
138
 
126
139
  ---
127
140
 
@@ -130,7 +143,7 @@ Each reducer runs in whichever authority **owns** the state it mutates:
130
143
  ```
131
144
  ServerlessIRCd/
132
145
  ├── packages/
133
- │ ├── irc-core/ pure protocol + reducers (the brain)
146
+ │ ├── irc-core/ pure protocol + reducers
134
147
  │ ├── irc-server/ orchestration, IrcRuntime port, dispatch
135
148
  │ ├── in-memory-runtime/ reference runtime (used by tests + local CLI)
136
149
  │ ├── irc-test-support/ parametrized IRC scenario suite + harness seam
@@ -140,11 +153,11 @@ ServerlessIRCd/
140
153
  │ ├── cf-worker/ worker entry, DO migrations, bindings, [assets] + CSWSH gate
141
154
  │ ├── cf-tcp-container/ Spectrum + Container origin for irc+tls :6697
142
155
  │ ├── aws-stack/ CDK stack (APIGW WS + NLB + Lambda streaming + DynamoDB)
143
- │ ├── web/ vendored Kiwi IRC SPA (→ /app/) + static landing page (→ /)
156
+ │ ├── web/ vendored Kiwi IRC SPA (→ /webclient/) + static landing page (→ /)
144
157
  │ └── local-cli/ runnable WS + TCP server using in-memory-runtime
145
158
  ├── tools/
146
159
  │ ├── tcp-ws-forwarder/ local TCP↔ws/wss bridge for stock IRC clients
147
- │ ├── load-test/ synthetic WebSocket IRC client farm (10k conns, p50/p95/p99, drop rate)
160
+ │ ├── load-test/ synthetic WebSocket IRC client pool (10k conns, p50/p95/p99, drop rate)
148
161
  │ ├── ci-hardening/ coverage-gate + mutation-config validators
149
162
  │ ├── seed-aws-accounts.ts scrypt-hash SASL PLAIN accounts into DynamoDB
150
163
  │ └── seed-cf-accounts.ts scrypt-hash SASL PLAIN accounts into Cloudflare D1
@@ -201,7 +214,7 @@ needs the upstream sources (`git submodule update --init apps/web/upstream`)
201
214
  and `yarn` (Kiwi ships a `yarn.lock`; `corepack enable` provides it):
202
215
 
203
216
  ```bash
204
- pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/app/ (/app/)
217
+ pnpm --filter web build # builds the Kiwi SPA into apps/web/dist/webclient/ (/webclient/)
205
218
  ```
206
219
 
207
220
  Coverage reports are written to `packages/*/coverage/`. CI (`.github/workflows/ci.yml`)
@@ -223,7 +236,7 @@ that share one runtime, so a TCP client and a WS client can see each other
223
236
 
224
237
  - a **WebSocket** listener (the serverless transport) on `--port`, and
225
238
  - an **RFC-style TCP** listener on `--tcp-port` (default `<port> + 1`) so
226
- real IRC clients (WeeChat, HexChat, irssi, …) can dial in directly.
239
+ real IRC clients (WeeChat, HexChat, irssi, …) can connect directly.
227
240
 
228
241
  First build the workspace (the CLI runs from compiled `dist/`):
229
242
 
@@ -256,11 +269,60 @@ On startup it logs a JSON line like:
256
269
  | `--motd-file <p>` | built-in | Read MOTD lines from this file (one per line). |
257
270
  | `--server-name <h>`| `irc.localhost` | Server hostname advertised in `001`/`005`. Production MUST override. |
258
271
  | `--network-name <n>`| `LocalNet` | Network name advertised in `005 NETWORK=…`. |
272
+ | `--server-password <p>`| | Server-password gate. When set, every connection must supply `PASS <p>` (or SASL-identify) before `001`. Treat as a secret. |
259
273
  | `-h, --help` | | Show help and exit. |
260
274
 
261
275
  Pass `--host 0.0.0.0` to expose the server on all interfaces. `SIGINT` /
262
276
  `SIGTERM` perform a graceful shutdown (closes active sockets, then exits).
263
277
 
278
+ ### Server password (`SERVER_PASSWORD` / `--server-password`)
279
+
280
+ All three adapters (Cloudflare Worker, AWS Lambda, local CLI) support an
281
+ optional **server-password gate**. When the knob is set, every connection
282
+ must supply the matching `PASS <value>` before `001 RPL_WELCOME` is
283
+ emitted; when unset or empty the gate is disabled (the default).
284
+
285
+ | Adapter | Knob |
286
+ |----------------|-------------------------------------------------------------------------------|
287
+ | Cloudflare | `wrangler secret put SERVER_PASSWORD` (Workers secret; never a `[vars]` entry). |
288
+ | AWS | `SERVER_PASSWORD` Lambda env var (sourced from Secrets Manager / SSM). |
289
+ | local CLI | `--server-password <p>` flag, or `serverPassword` on `StartServerOptions`. |
290
+
291
+ On a mismatched/missing `PASS` the server emits `464 ERR_PASSWDMISMATCH`
292
+ and disconnects with reason `Bad Password`. **SASL short-circuit:** a
293
+ connection that has authenticated an account via SASL
294
+ (`AUTHENTICATE PLAIN` / `EXTERNAL`) is exempt — the reducer's gate
295
+ treats `state.account !== undefined` as already authorised, so a
296
+ deployment with both `SERVER_PASSWORD` and configured SASL accounts
297
+ does not need to hand the shared password to identified users. The
298
+ server-wide gate is a shared deployment secret (never log it); per-user
299
+ credentials still go through the hashed SASL account store
300
+ (`HashedAccountStore` / D1 / DynamoDB), which is unaffected.
301
+
302
+ **PASS-based account login:** in addition to SASL, a client may identify
303
+ to its NickServ account by sending `PASS <nick>:<password>` (the same
304
+ `<nick>:<password>` literal the `SASL_ACCOUNTS` seed tooling uses).
305
+ When an `AccountStore` is configured, `NICK alice` + `PASS alice:hunter2`
306
+ + `USER …` verifies the credentials against the account store and, on
307
+ success, logs the connection in as `alice` — emitting `900 RPL_LOGGEDIN`
308
+ + `903 RPL_SASLSUCCESS` (the same "account is set" numerics SASL uses)
309
+ before `001 RPL_WELCOME`, stamping user mode `+r`, and running the same
310
+ read-marker / away / memo replay as a SASL login. The login is also
311
+ honoured when `PASS <nick>:<password>` arrives **after** registration
312
+ has completed (e.g. a client that sent `NICK`+`USER` before `PASS`, or
313
+ any `PASS` arriving after `CAP END` resolved): the same verify +
314
+ `applyAccountSuccess` pipeline runs, emitting `900`/`903` and stamping
315
+ `+r` in-band. The nick left of the `:` must match the connection's
316
+ current nick; a bare `PASS <value>` with no colon is always treated as
317
+ a server-password candidate (silent no-op once registration has
318
+ completed), never as account credentials. A wrong password or unknown
319
+ nick is indistinguishable (no `904`/`464` from the auth path) and the
320
+ connection simply proceeds un-identified when no server password is
321
+ set. This composes with the gate above by precedence: an
322
+ already-identified connection (SASL or PASS-auth) satisfies the gate;
323
+ a `<nick>:<password>` that fails verify never matches a bare shared
324
+ secret, so a server with both configured still rejects it with `464`.
325
+
264
326
  ### Connecting
265
327
 
266
328
  The WebSocket listener speaks WebSocket text frames (one IRC message per
@@ -283,9 +345,9 @@ given.
283
345
  The local CLI ships a built-in TCP listener, so no bridge is needed for local
284
346
  development. Deployed stacks (Cloudflare Workers and AWS API Gateway) are
285
347
  WebSocket-only, so a stock TCP IRC client (WeeChat, HexChat, irssi, …) cannot
286
- dial them directly. `tools/tcp-ws-forwarder` is a local bridge for that case:
348
+ connect to them directly. `tools/tcp-ws-forwarder` is a local bridge for that case:
287
349
  it listens on a TCP port and, for each connection, opens one WebSocket to a
288
- `ws://` / `wss://` target and pumps IRC lines both ways — reassembling the TCP
350
+ `ws://` / `wss://` target and forwards IRC lines both directions — reassembling the TCP
289
351
  byte stream into one message per WS frame outbound, and normalizing inbound
290
352
  frames back to canonical CRLF.
291
353
 
@@ -330,9 +392,9 @@ against any line-oriented WebSocket endpoint.
330
392
 
331
393
  ## Load testing (`tools/load-test`)
332
394
 
333
- A synthetic WebSocket IRC client farm (PLAN §7, Phase 7). It opens N
395
+ A synthetic WebSocket IRC client pool. It opens N
334
396
  connections to a `ws://` / `wss://` target, registers each (NICK/USER),
335
- joins a channel, and optionally chats. Per-phase **p50 / p95 / p99
397
+ joins a channel, and optionally chats. Per-stage **p50 / p95 / p99
336
398
  latency** (connect / register / join / message) and the **drop rate**
337
399
  are captured, then printed as a markdown report to stdout (and written
338
400
  to `--report <path>` when given).
@@ -379,12 +441,12 @@ report).
379
441
 
380
442
  ## Web client (`apps/web`)
381
443
 
382
- A vendored **Kiwi IRC** SPA served at `/app/` and a static project
444
+ A vendored **Kiwi IRC** SPA served at `/webclient/` and a static project
383
445
  landing page served at `/`, both baked out of `apps/web/dist/` and
384
446
  served directly by the Cloudflare Worker via its `[assets]` binding.
385
- The browser opens a native `wss://` straight to the Worker's existing
447
+ The browser opens a native `wss://` directly to the Worker's
386
448
  IRC-over-WebSocket endpoint — no proxy, gateway, or transport
387
- adaptation in the hot path. See `docs/WebClientGuide.md` for the
449
+ adaptation in the request path. See `docs/WebClientGuide.md` for the
388
450
  end-to-end contributor/operator guide.
389
451
 
390
452
  Build the SPA + landing page (needs the submodule + `yarn`, provided by
@@ -392,12 +454,22 @@ Build the SPA + landing page (needs the submodule + `yarn`, provided by
392
454
 
393
455
  ```bash
394
456
  git submodule update --init apps/web/upstream # one-time per clone
457
+ git submodule update --init docs # one-time per clone (docs site)
395
458
  pnpm --filter web build:staging # or :prod / default
396
459
  # → apps/web/dist/index.html (landing page, served at /)
397
- # → apps/web/dist/app/index.html (Kiwi SPA, served at /app/)
398
- # → apps/web/dist/app/static/config.json (baked, env-specific)
460
+ # → apps/web/dist/webclient/index.html (Kiwi SPA, served at /webclient/)
461
+ # → apps/web/dist/webclient/static/config.json (baked, env-specific)
462
+ # → apps/web/dist/docs/<slug>.html (rendered docs/, served at /docs/)
399
463
  ```
400
464
 
465
+ The build also renders the `docs/` submodule (the Gitea wiki, 23 GFM
466
+ markdown files) to standalone HTML under `apps/web/dist/docs/` so the
467
+ existing `[assets]` binding serves them at `/docs/<slug>.html` (and
468
+ `/docs/` for the index — `Home.md` becomes `index.html`). Requires the
469
+ `docs/` submodule checkout; the build fails loudly with the recovery
470
+ command if it is missing or empty (mirroring the Kiwi `upstream/` guard).
471
+ `Home.md` is the docs landing page; `ADR-Index.md` lists every ADR.
472
+
401
473
  Then run everything locally (Worker serves SPA + landing page + WS on
402
474
  one origin):
403
475
 
@@ -405,7 +477,8 @@ one origin):
405
477
  pnpm build # workspace packages
406
478
  pnpm --filter @serverless-ircd/cf-worker dev # http://localhost:8787
407
479
  # / → landing page
408
- # /app/ → Kiwi SPA (opens ws://localhost:8787/)
480
+ # /webclient/ → Kiwi SPA (opens ws://localhost:8787/)
481
+ # /docs/ → rendered docs/ (Home → index, per-page HTML)
409
482
  # /health → plaintext liveness
410
483
  ```
411
484
 
@@ -496,84 +569,33 @@ port and an `IdFactory` port are injected so tests are fully deterministic.
496
569
 
497
570
  ## Roadmap
498
571
 
499
- A v1 ships when `irc-core` is at 100% coverage, the parametrized contract
500
- suite passes against every runtime, both adapters are deployed to staging,
501
- and ≥3 reference clients (WeeChat, HexChat, IRCCloud, TheLounge) connect
502
- cleanly. Per-release manifests live in `CHANGELOG.md`.
503
-
504
- - **Phase 0** — Foundation (monorepo, turbo, vitest, biome, CI). ✅
505
- - **Phase 1** — Pure protocol engine: reducers, IRCv3 caps, isupport,
506
- SASL (`PLAIN`), token-bucket flood control, chat history. (most in
507
- **v0.1.0**; SASL/flood/chathistory landed in **v0.2.0**).
508
- - **Phase 2** `IrcRuntime` port, in-memory runtime, `ConnectionActor`,
509
- local CLI, parametrized contract suite. ✅ landed in **v0.1.0**.
510
- - **Phase 3** — Cloudflare adapter (ConnectionDO / ChannelDO / RegistryDO).
511
- landed in **v0.1.0** (staging deploy via CI; prod deploy is manual).
512
- - **Phase 4** — AWS adapter (APIGW WS + Lambda + DynamoDB + CDK).
513
- landed in **v0.2.0** (staging deploy via CI; prod deploy is manual).
514
- - **Phase 5** — Observability, security hardening, config, CI gates.
515
- landed across v0.2.0–v0.4.0 (logger port, server-password, cloaking,
516
- admission limits, coverage + mutation gates, OPER credential auth, the
517
- Cloudflare config loader, and AWS max-clients admission); the
518
- required-`serverName` gate closed in **v0.6.0**.
519
- - **Phase 6** TLS hardening & raw TCP transport (`irc+tls :6697`).
520
- ✅ landed in **v0.4.0**: generalized `ConnectionActor` transport seam,
521
- Cloudflare Spectrum + Container origin, AWS NLB + Lambda streaming,
522
- mTLS → SASL `EXTERNAL`, transport-parametrized contract suite, ADR-009.
523
- - **Phase 7 — Load testing & compatibility sweep.** 🔄 partial as of
524
- **v0.6.0**: the `tools/load-test` 10k-connection harness is in-tree;
525
- the formal 10k-connection report per platform and the client
526
- compatibility matrix (WeeChat / HexChat / IRCCloud / TheLounge) are
527
- still pending.
528
- - **Phase 8 — PLAN-FIXES remediation.** ✅ landed across v0.3.0–v0.4.0
529
- (MessageStore wired into all three adapters, AccountStore end-to-end,
530
- the DynamoDB `Accounts` table decided, CF enumeration RPCs, AWS QUIT
531
- fanout, `OPER` / `TAGMSG` / `WHOWAS`, RFC 1459 case-mapping, real
532
- MOTD, config-driven server identity, AWS max-clients admission,
533
- CF D1 SASL store, and a sweep of dead-code / stale-comment cleanup).
534
- - **Phase 9 — Runtime & tooling baseline.** ✅ landed in **v0.4.0**
535
- (Node ≥ 24 / pnpm 11).
536
- - **Phase 10 — IRCv3 WebSocket extension.** ✅ landed in **v0.4.0**:
537
- `text.ircv3.net` / `binary.ircv3.net` subprotocol negotiation +
538
- per-message framing across the CF Worker, AWS APIGW, local CLI, and
539
- `tcp-ws-forwarder` (510-byte budget, `1009`/`1003` close codes,
540
- legacy fallback).
541
- - **Phase 11 — Deferred IRC verbs.** ✅ landed in **v0.5.0**: `KILL`,
542
- `REHASH`, `LUSERS` + `STATS`, `TRACE`, `WALLOPS`, `SETNAME`, plus
543
- the formal drop of the S2S (`CONNECT`/`SQUIT`/`LINKS`) and obsolete
544
- RFC 2812 (`SERVICE`/`SUMMON`/`USERS`) verbs. The protocol surface is
545
- feature-complete for modern clients.
546
- - **Phase 12 — IRCv3 extension sweep.** ✅ landed in **v0.6.0**: ten
547
- negotiated caps (`account-notify`, `msgid`, `standard-replies`,
548
- `MONITOR`, `labeled-response`, `sts`, `draft/typing`,
549
- `draft/multiline`, `draft/read-marker`, `draft/pre-away`), the
550
- ISUPPORT tokens for the new caps (`MONITOR`/`MULTILINE`/`TYPING`/
551
- `STATUSMSG`/`EXTBAN`/`ACCOUNTEXTBAN`), the read-only user mode `S`
552
- (TLS connected), and the async push fanout seam (Registry hooks →
553
- `MONITOR` online/offline, `BroadcastWallops`-style caps-gated
554
- delivery).
555
- - **Phase 13 — IRC services.** ⬳ pending: `ServicesStore` port +
556
- NickServ / ChanServ / HostServ / OperServ / MemoServ. Adapter
557
- backends (D1 / DynamoDB) ship alongside; until then
558
- `draft/read-marker` and `draft/pre-away` use in-memory reference
559
- stores and do not survive an adapter restart.
560
- - **Phase 14 — Web client.** 🔄 partial as of **v0.7.0**: a vendored
561
- Kiwi IRC SPA is served at `/app/` and a static landing page at `/`
562
- by the Cloudflare Worker via an `[assets]` binding, with a Zod-
563
- validated per-env config matrix and **CSWSH defense** (same-origin
564
- auto-derive + optional `WEB_ORIGINS` allowlist). The Playwright
565
- headless-browser e2e is the remaining open ticket.
566
- See `docs/WebClientGuide.md` for the contributor/operator guide.
567
- - **Coverage hardening.** 🔄 partial as of **v0.7.0**: `aws-adapter`
568
- and `aws-stack` clear the 90% gate (v0.6.0); the CF packages run
569
- under istanbul (v0.6.0). The remaining packages (`cf-adapter`,
570
- `local-cli`, `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
571
- `irc-test-support`, `web`) are **above the gate but below 100%** —
572
- follow-ups drive each to 100%.
572
+ A v1 ships when the parametrized contract suite passes against every
573
+ runtime, the client compatibility matrix is signed off, and every
574
+ package clears its coverage gate. Per-release manifests live in
575
+ `CHANGELOG.md`.
576
+
577
+ Active follow-ups:
578
+
579
+ - **Client compatibility sweep** formal sign-off on ≥3 reference
580
+ clients (WeeChat / HexChat / IRCCloud / TheLounge) against both
581
+ deployed adapters, on both the WebSocket and `irc+tls :6697`
582
+ surfaces.
583
+ - **Web client e2e** — Playwright headless-browser e2e exercising the
584
+ vendored Kiwi IRC SPA against a deployed stack.
585
+ - **Coverage hardening** — `aws-adapter` and `aws-stack` clear the 90%
586
+ gate; the CF packages run under istanbul. The remaining packages
587
+ (`cf-adapter`, `local-cli`, `load-test`, `cf-tcp-container`,
588
+ `tcp-ws-forwarder`, `irc-test-support`, `web`) sit above the gate
589
+ but below 100%; follow-ups drive each to full coverage. `irc-core`,
590
+ `irc-server`, and `in-memory-runtime` are at 100%.
591
+ - **Persistent ChanServ ban list** — ban masks currently live on
592
+ `ChannelState.banMasks` and do not survive an empty-recreate of a
593
+ channel. Extending `ServicesStore` with a persistent ban list is the
594
+ documented next step.
573
595
 
574
596
  ---
575
597
 
576
- ## Protocol scope (v1)
598
+ ## Protocol scope
577
599
 
578
600
  **Core (RFC 1459/2812 subset):** registration (`NICK`/`USER`/`CAP`/`PASS`),
579
601
  `PING`/`PONG`, `QUIT`, `JOIN`, `PART`, `PRIVMSG`, `NOTICE`, `MODE` (user +
@@ -590,14 +612,59 @@ disconnect), `REHASH` (config reload via `IrcRuntime.reloadConfig()`),
590
612
  `WALLOPS` (global broadcast to every `+w` user).
591
613
 
592
614
  (Out of scope: the S2S verbs `CONNECT`/`SQUIT`/`LINKS` and the obsolete
593
- RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped** as of
594
- v0.5.0 — they return `421 ERR_UNKNOWNCOMMAND`, and their reserved
595
- numerics have been removed. S2S linking is a PLAN non-goal.)
596
-
597
- **Channel modes:** `o v b i k l t n m s p`.
615
+ RFC 2812 verbs `SERVICE`/`SUMMON`/`USERS` are **formally dropped**
616
+ they return `421 ERR_UNKNOWNCOMMAND`, and their reserved numerics have
617
+ been removed. S2S linking is a stated non-goal.)
618
+
619
+ **Channel modes:** `o v b i k l t n m s p`, plus the
620
+ services-derived modes `r` (registered), `R` (block unidentified
621
+ join/message), `M` (moderated-identified) — settable only via ChanServ,
622
+ not via `MODE`.
598
623
  **User modes:** `i`, `o` (local only), `w`, `s`, and the read-only `S`
599
624
  (TLS connected — set by the transport at registration, surfaced in WHOIS
600
- via `276 RPL_WHOISSECURE`, not settable via `MODE`).
625
+ via `276 RPL_WHOISSECURE`, not settable via `MODE`) and the read-only
626
+ `r` (registered — set by NickServ `IDENTIFY` / SASL login, surfaced as
627
+ `+r`, not settable via `MODE`).
628
+
629
+ **Integrated IRC services:** the service nicks `NickServ`, `ChanServ`,
630
+ `HostServ`, `OperServ`, and `MemoServ` are reserved and routed to
631
+ dedicated pure reducers when a `ServicesStore` is bound — there is **no
632
+ separate services process and no S2S link**. Each service also accepts a
633
+ **shortcut verb** (`/NICKSERV`, `/NS`, `/CS`, `/HS`, `/MS`, `/OS`, …)
634
+ that the daemon rewrites to the equivalent `PRIVMSG <Service> :<args>`
635
+ for clients with a dedicated slash-command UX. Reached via `PRIVMSG
636
+ <Service> :<subcommand>`:
637
+
638
+ - **NickServ** — `REGISTER` / `IDENTIFY` (alias: `ID`) / `DROP` / `INFO`
639
+ (owner + oper only for the `Email:` line) / `SET ENFORCE` + nick
640
+ enforcement on the `NICK` path. `INFO <nick>` with no target resolves
641
+ to the caller's own account.
642
+ - **ChanServ** — `REGISTER` / `DROP` / `SET` (`FOUNDER` / `MLOCK` /
643
+ `RESTRICTED` / `KEEPTOPIC`) / `INFO` / `ACCESS` (`SOP` / `AOP` / `HOP`
644
+ / `VOP`, each `ADD` / `DEL` / `LIST`) / `LEVELS`, plus the prefix /
645
+ roster mutation verbs `OP`, `DEOP`, `VOICE`, `DEVOICE`, `KICK`, and
646
+ `BAN` / `UNBAN` (each emits the corresponding `:ChanServ MODE` /
647
+ `KICK` broadcast and `ApplyChannelDelta` against the channel
648
+ authority). Auto-op / auto-voice on `JOIN` follows the access list;
649
+ the first joiner of a registered channel no longer gets auto-opped
650
+ merely for being first.
651
+ - **HostServ** — `ON` / `OFF` / `REQUEST` + oper `SET` / `APPROVE` /
652
+ `ACTIVATE` / `REJECT` / `LIST`, with `CHGHOST` fanout. An assigned
653
+ vhost is **auto-applied on identify** (SASL, NickServ `IDENTIFY`, or
654
+ PASS-auth): `state.host` is set, `state.vhostActive` is stamped, and
655
+ any currently-joined `chghost`-capable peers see the broadcast.
656
+ Oper-only subcommands are hidden from non-opers in the help NOTICE.
657
+ - **OperServ** — `AKILL` / `JUPE` / `UNJUPE` / `RAW` (oper-only).
658
+ - **MemoServ** — `SEND` / `LIST` / `READ` / `DEL` with queue delivery
659
+ at identify.
660
+
661
+ The SASL `AccountStore` and NickServ accounts share one scrypt-hashed
662
+ credential store, so a registered nick is also a SASL login and vice
663
+ versa. Backends: D1 on Cloudflare, DynamoDB on AWS, in-memory for the
664
+ local CLI / tests (all write-behind; registrations survive redeploys).
665
+ When no store is bound, services commands reply `501` and the rest of
666
+ the daemon is unaffected. See `docs/Services.md` for the full
667
+ reference.
601
668
 
602
669
  **IRCv3 extensions (negotiated via `CAP`):** `message-tags` (incl. the
603
670
  `TAGMSG` command), `server-time`, `account-tag`, `account-notify`
@@ -614,7 +681,9 @@ shared between live and `draft/chathistory` replay), `standard-replies`
614
681
  so compliant clients upgrade from plaintext to TLS and pin the secure
615
682
  listener), `draft/chathistory`, `safelist`, `draft/typing` (typing-indicator
616
683
  broadcast), `draft/multiline=<n>` (multi-line `BATCH`, default 4096-byte
617
- budget), `draft/read-marker` (per-account persisted last-read), and
684
+ budget), `draft/read-marker` (per-account persisted last-read, with the
685
+ timestamp-based `MARKREAD <target> [timestamp]` verb plus the
686
+ `+draft/read-marker` tag fanout), and
618
687
  `draft/pre-away` (per-account persisted away reason, replayed at
619
688
  identify). Case-insensitive nick/channel comparison uses **RFC 1459**
620
689
  case-mapping (advertised via `005 CASEMAPPING=rfc1459`). The new
@@ -634,32 +703,23 @@ bridges a stock TCP client to a deployed WebSocket endpoint.
634
703
 
635
704
  ## Further reading
636
705
 
637
- - `CHANGELOG.md` — per-release manifests (Keep a Changelog format), including
638
- the v0.2.0 work (AWS adapter, SASL, flood control, security hardening, CI
639
- gates), v0.3.0 (OPER, TAGMSG, RFC 1459 case-mapping, real MOTD,
640
- ChannelRegistryDO, SASL account persistence), v0.4.0 (dual transport
641
- `irc+tls :6697`, SASL `EXTERNAL`/mTLS, WHOWAS + remaining verbs, D1 account
642
- store, Node 24/pnpm 11), v0.5.0 (deferred IRC verbs `KILL` / `REHASH`
643
- / `LUSERS` + `STATS` / `TRACE` / `WALLOPS` / `SETNAME`, plus the formal
644
- drop of the S2S and obsolete RFC 2812 verbs), v0.6.0 (the IRCv3
645
- extension sweep`account-notify`, `msgid`, `standard-replies`,
646
- `MONITOR`, `labeled-response`, `sts`, `draft/typing`, `draft/multiline`,
647
- `draft/read-marker`, `draft/pre-away`, plus user mode `S`; the
648
- required-`serverName` gate; `tools/load-test`; aws-adapter / aws-stack
649
- coverage unblocks; CF packages moved to istanbul; deploys now
650
- manual-only), and **v0.7.0** (the web client — vendored Kiwi IRC SPA
651
- at `/app/`, static landing page at `/`, Cloudflare Worker `[assets]`
652
- binding, per-env Zod-validated config matrix, and CSWSH defense via
653
- same-origin auto-derive + optional `WEB_ORIGINS`; `/health` becomes
654
- an explicit route; mutation-killing tests for `irc-core`'s parser /
655
- batch / numerics).
656
- - `docs/WebClientGuide.md` — end-to-end contributor/operator doc for the
657
- web client: build pipeline, per-env config matrix, CSWSH rationale and
658
- the optional `WEB_ORIGINS` var, local dev, optional Cloudflare Pages
659
- alternative, and troubleshooting.
660
- - `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md` —
661
- end-to-end guides for the `:6697` TCP+TLS variants (Spectrum/Container on
662
- CF, NLB + Lambda streaming on AWS), including mTLS trust-store setup.
663
- - `README.md` in each `packages/*` and `apps/*` — per-package notes (e.g.
664
- `packages/aws-adapter/README.md` for the DynamoDB-Local test setup,
665
- `apps/aws-stack/README.md` for CDK commands and localstack validation).
706
+ - `CHANGELOG.md` — per-release manifests in Keep a Changelog format.
707
+ - `docs/Services.md` operator and contributor reference for the
708
+ integrated IRC services: architecture (integrated vs. pseudo-client),
709
+ the per-service command tables (including ChanServ `OP`/`DEOP`/
710
+ `VOICE`/`DEVOICE`/`KICK`/`BAN`/`UNBAN`, HostServ oper approval flow,
711
+ the `+r`/`+R`/`+M` modes, nick enforcement, the unified scrypt
712
+ account store, service shortcut verbs), adapter backends, and an
713
+ end-to-end registration walkthrough.
714
+ - `docs/WebClientGuide.md`end-to-end contributor/operator doc for
715
+ the web client: build pipeline, per-env config matrix, CSWSH
716
+ rationale and the optional `WEB_ORIGINS` var, local dev, optional
717
+ Cloudflare Pages alternative, and troubleshooting.
718
+ - `docs/Cloudflare-TCP-Deployment.md` and `docs/AWS-TCP-Deployment.md`
719
+ — end-to-end guides for the `:6697` TCP+TLS variants
720
+ (Spectrum/Container on CF, NLB + Lambda streaming on AWS), including
721
+ mTLS trust-store setup.
722
+ - `README.md` in each `packages/*` and `apps/*` — per-package notes
723
+ (e.g. `packages/aws-adapter/README.md` for the DynamoDB-Local test
724
+ setup, `apps/aws-stack/README.md` for CDK commands and localstack
725
+ validation).
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@serverless-ircd/aws-stack",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "private": true,
5
5
  "description": "AWS CDK v2 stack: API Gateway v2 WebSocket API, Lambda, DynamoDB tables, least-privilege IAM",
6
6
  "license": "BSD-3-Clause",