serverless-ircd 0.4.0 → 0.6.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 (243) hide show
  1. package/.github/workflows/ci.yml +34 -2
  2. package/.github/workflows/deploy-aws.yml +1 -2
  3. package/.github/workflows/deploy-cf-tcp.yml +1 -5
  4. package/.github/workflows/deploy-cf.yml +1 -2
  5. package/CHANGELOG.md +435 -0
  6. package/README.md +206 -27
  7. package/apps/aws-stack/README.md +37 -3
  8. package/apps/aws-stack/package.json +1 -1
  9. package/apps/aws-stack/src/aws-stack.ts +106 -13
  10. package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
  11. package/apps/aws-stack/tests/stack.test.ts +49 -3
  12. package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
  13. package/apps/aws-stack/tests/vitest.setup.ts +18 -0
  14. package/apps/aws-stack/vitest.config.ts +3 -0
  15. package/apps/cf-tcp-container/package.json +1 -1
  16. package/apps/cf-tcp-container/src/config-loader.ts +6 -7
  17. package/apps/cf-tcp-container/src/container-server.ts +3 -0
  18. package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
  19. package/apps/cf-tcp-container/wrangler.toml +1 -1
  20. package/apps/cf-worker/package.json +2 -1
  21. package/apps/cf-worker/vitest.config.ts +1 -1
  22. package/apps/cf-worker/wrangler.toml +5 -3
  23. package/apps/local-cli/package.json +1 -1
  24. package/apps/local-cli/src/config-loader.ts +6 -6
  25. package/apps/local-cli/src/main.ts +20 -0
  26. package/apps/local-cli/src/server.ts +138 -28
  27. package/apps/local-cli/tests/e2e.test.ts +113 -29
  28. package/apps/local-cli/tests/tcp.test.ts +71 -19
  29. package/apps/local-cli/tests/ws-subprotocol.test.ts +262 -0
  30. package/biome.json +1 -0
  31. package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
  32. package/docs/ADR-002-location-of-authority.md +82 -0
  33. package/docs/ADR-003-durable-object-sharding.md +110 -0
  34. package/docs/ADR-004-dynamodb-schema.md +96 -0
  35. package/docs/ADR-005-wss-only-transport-v1.md +83 -0
  36. package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
  37. package/docs/ADR-007-deterministic-ports.md +82 -0
  38. package/docs/ADR-008-monorepo-tooling.md +60 -0
  39. package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
  40. package/docs/ADR-Index.md +15 -0
  41. package/docs/AWS-Adapter-Architecture.md +496 -0
  42. package/docs/AWS-Deployment.md +1275 -0
  43. package/docs/AWS-TCP-Deployment.md +410 -0
  44. package/docs/Cloudflare-Deployment-Guide.md +719 -0
  45. package/docs/Cloudflare-TCP-Deployment.md +510 -0
  46. package/docs/Home.md +15 -0
  47. package/docs/Observability.md +87 -0
  48. package/docs/PlanExtensions.md +812 -0
  49. package/docs/PlanIRCv3Websocket.md +503 -0
  50. package/docs/PlanWebClient.md +451 -0
  51. package/docs/Release-Process.md +443 -0
  52. package/package.json +2 -2
  53. package/packages/aws-adapter/package.json +1 -1
  54. package/packages/aws-adapter/src/aws-runtime.ts +69 -0
  55. package/packages/aws-adapter/src/handlers/connect.ts +36 -5
  56. package/packages/aws-adapter/src/handlers/default.ts +66 -5
  57. package/packages/aws-adapter/src/handlers/index.ts +41 -2
  58. package/packages/aws-adapter/src/handlers/nlb-stream.ts +18 -0
  59. package/packages/aws-adapter/src/index.ts +2 -0
  60. package/packages/aws-adapter/src/serialize.ts +40 -2
  61. package/packages/aws-adapter/src/stats.ts +80 -0
  62. package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
  63. package/packages/aws-adapter/tests/account-store.test.ts +4 -7
  64. package/packages/aws-adapter/tests/aws-harness.ts +5 -2
  65. package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
  66. package/packages/aws-adapter/tests/aws-runtime.test.ts +140 -0
  67. package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
  68. package/packages/aws-adapter/tests/connect.test.ts +100 -4
  69. package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
  70. package/packages/aws-adapter/tests/global-setup.ts +55 -30
  71. package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
  72. package/packages/aws-adapter/tests/handlers.test.ts +238 -4
  73. package/packages/aws-adapter/tests/message-store.test.ts +6 -5
  74. package/packages/aws-adapter/tests/nlb-stream.test.ts +62 -8
  75. package/packages/aws-adapter/tests/stats.test.ts +317 -0
  76. package/packages/cf-adapter/package.json +6 -1
  77. package/packages/cf-adapter/src/cf-runtime.ts +66 -1
  78. package/packages/cf-adapter/src/channel-do.ts +2 -2
  79. package/packages/cf-adapter/src/connection-do.ts +185 -54
  80. package/packages/cf-adapter/src/env.ts +25 -6
  81. package/packages/cf-adapter/src/index.ts +2 -0
  82. package/packages/cf-adapter/src/registry-do.ts +22 -3
  83. package/packages/cf-adapter/src/serialize.ts +25 -4
  84. package/packages/cf-adapter/src/sharding.ts +1 -2
  85. package/packages/cf-adapter/src/stats.ts +65 -0
  86. package/packages/cf-adapter/tests/cf-harness.ts +1 -1
  87. package/packages/cf-adapter/tests/cf-integration.test.ts +4 -4
  88. package/packages/cf-adapter/tests/cf-runtime.test.ts +307 -2
  89. package/packages/cf-adapter/tests/channel-do.test.ts +119 -2
  90. package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
  91. package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +2 -2
  92. package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +2 -2
  93. package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
  94. package/packages/cf-adapter/tests/connection-do-ws-spec-contract.test.ts +289 -0
  95. package/packages/cf-adapter/tests/connection-do-ws-subprotocol.test.ts +184 -0
  96. package/packages/cf-adapter/tests/connection-do.test.ts +27 -2
  97. package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
  98. package/packages/cf-adapter/tests/registry-do.test.ts +108 -4
  99. package/packages/cf-adapter/tests/serialize.test.ts +308 -0
  100. package/packages/cf-adapter/tests/sharding.test.ts +1 -1
  101. package/packages/cf-adapter/tests/stats.test.ts +120 -0
  102. package/packages/cf-adapter/tests/worker/main.ts +7 -7
  103. package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +2 -2
  104. package/packages/cf-adapter/tests/worker/stubs/registry-stub.ts +8 -2
  105. package/packages/cf-adapter/vitest.config.ts +1 -1
  106. package/packages/cf-adapter/wrangler.test.toml +7 -0
  107. package/packages/in-memory-runtime/package.json +1 -1
  108. package/packages/in-memory-runtime/src/in-memory-runtime.ts +39 -0
  109. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +259 -0
  110. package/packages/irc-core/package.json +1 -1
  111. package/packages/irc-core/src/admission.ts +16 -15
  112. package/packages/irc-core/src/caps/capabilities.ts +38 -4
  113. package/packages/irc-core/src/caps/index.ts +1 -0
  114. package/packages/irc-core/src/caps/sts.ts +84 -0
  115. package/packages/irc-core/src/commands/account-notify.ts +53 -0
  116. package/packages/irc-core/src/commands/away.ts +9 -3
  117. package/packages/irc-core/src/commands/cap.ts +23 -5
  118. package/packages/irc-core/src/commands/chathistory.ts +40 -11
  119. package/packages/irc-core/src/commands/index.ts +42 -0
  120. package/packages/irc-core/src/commands/invite.ts +2 -4
  121. package/packages/irc-core/src/commands/isupport.ts +59 -2
  122. package/packages/irc-core/src/commands/kick.ts +2 -4
  123. package/packages/irc-core/src/commands/kill.ts +127 -0
  124. package/packages/irc-core/src/commands/list.ts +1 -1
  125. package/packages/irc-core/src/commands/lusers.ts +204 -0
  126. package/packages/irc-core/src/commands/mode.ts +12 -9
  127. package/packages/irc-core/src/commands/monitor.ts +327 -0
  128. package/packages/irc-core/src/commands/multiline.ts +256 -0
  129. package/packages/irc-core/src/commands/names.ts +3 -5
  130. package/packages/irc-core/src/commands/part.ts +2 -4
  131. package/packages/irc-core/src/commands/pre-away.ts +112 -0
  132. package/packages/irc-core/src/commands/privmsg.ts +19 -12
  133. package/packages/irc-core/src/commands/read-marker.ts +108 -0
  134. package/packages/irc-core/src/commands/registration.ts +8 -0
  135. package/packages/irc-core/src/commands/rehash.ts +119 -0
  136. package/packages/irc-core/src/commands/sasl.ts +24 -1
  137. package/packages/irc-core/src/commands/setname.ts +109 -0
  138. package/packages/irc-core/src/commands/stats.ts +152 -0
  139. package/packages/irc-core/src/commands/tagmsg.ts +71 -1
  140. package/packages/irc-core/src/commands/topic.ts +2 -4
  141. package/packages/irc-core/src/commands/trace.ts +137 -0
  142. package/packages/irc-core/src/commands/wallops.ts +118 -0
  143. package/packages/irc-core/src/commands/whois.ts +5 -0
  144. package/packages/irc-core/src/config.ts +72 -10
  145. package/packages/irc-core/src/effects.ts +41 -1
  146. package/packages/irc-core/src/index.ts +2 -0
  147. package/packages/irc-core/src/ports.ts +568 -0
  148. package/packages/irc-core/src/protocol/index.ts +14 -0
  149. package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
  150. package/packages/irc-core/src/protocol/numerics.ts +57 -11
  151. package/packages/irc-core/src/protocol/outbound.ts +36 -4
  152. package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
  153. package/packages/irc-core/src/state/connection.ts +32 -1
  154. package/packages/irc-core/src/types.ts +120 -1
  155. package/packages/irc-core/src/ws-framing.ts +132 -0
  156. package/packages/irc-core/src/ws-subprotocol.ts +66 -0
  157. package/packages/irc-core/stryker.commands.conf.json +1 -2
  158. package/packages/irc-core/tests/admission.test.ts +18 -0
  159. package/packages/irc-core/tests/away-store.test.ts +73 -0
  160. package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
  161. package/packages/irc-core/tests/caps/sts.test.ts +123 -0
  162. package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
  163. package/packages/irc-core/tests/commands/away.test.ts +85 -2
  164. package/packages/irc-core/tests/commands/cap.test.ts +165 -0
  165. package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
  166. package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
  167. package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
  168. package/packages/irc-core/tests/commands/kill.test.ts +243 -0
  169. package/packages/irc-core/tests/commands/lusers.test.ts +368 -0
  170. package/packages/irc-core/tests/commands/mode.test.ts +129 -0
  171. package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
  172. package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
  173. package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
  174. package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
  175. package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
  176. package/packages/irc-core/tests/commands/registration.test.ts +64 -0
  177. package/packages/irc-core/tests/commands/rehash.test.ts +171 -0
  178. package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
  179. package/packages/irc-core/tests/commands/setname.test.ts +225 -0
  180. package/packages/irc-core/tests/commands/stats.test.ts +294 -0
  181. package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
  182. package/packages/irc-core/tests/commands/trace.test.ts +282 -0
  183. package/packages/irc-core/tests/commands/wallops.test.ts +231 -0
  184. package/packages/irc-core/tests/commands/whois.test.ts +34 -0
  185. package/packages/irc-core/tests/config.test.ts +103 -13
  186. package/packages/irc-core/tests/dropped-s2s-and-obsolete-verbs.test.ts +90 -0
  187. package/packages/irc-core/tests/effects.test.ts +14 -0
  188. package/packages/irc-core/tests/labeled-response.test.ts +181 -0
  189. package/packages/irc-core/tests/message-tags.test.ts +114 -0
  190. package/packages/irc-core/tests/monitor-store.test.ts +200 -0
  191. package/packages/irc-core/tests/numerics.test.ts +90 -0
  192. package/packages/irc-core/tests/outbound.test.ts +51 -0
  193. package/packages/irc-core/tests/ports.test.ts +22 -0
  194. package/packages/irc-core/tests/raw-modules.d.ts +11 -0
  195. package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
  196. package/packages/irc-core/tests/standard-replies.test.ts +412 -0
  197. package/packages/irc-core/tests/stats-store.test.ts +222 -0
  198. package/packages/irc-core/tests/types.test.ts +27 -0
  199. package/packages/irc-core/tests/ws-framing.test.ts +213 -0
  200. package/packages/irc-core/tests/ws-subprotocol.test.ts +111 -0
  201. package/packages/irc-server/package.json +1 -1
  202. package/packages/irc-server/src/actor.ts +699 -19
  203. package/packages/irc-server/src/dispatch.ts +109 -16
  204. package/packages/irc-server/src/routing.ts +3 -0
  205. package/packages/irc-server/src/runtime.ts +31 -0
  206. package/packages/irc-server/src/transport.ts +10 -7
  207. package/packages/irc-server/tests/actor.test.ts +2523 -42
  208. package/packages/irc-server/tests/dispatch.test.ts +300 -2
  209. package/packages/irc-server/tests/raw-modules.d.ts +11 -0
  210. package/packages/irc-server/tests/routing.test.ts +1 -0
  211. package/packages/irc-server/tests/runtime.test.ts +7 -0
  212. package/packages/irc-test-support/package.json +1 -1
  213. package/packages/irc-test-support/src/index.ts +6 -0
  214. package/packages/irc-test-support/src/scenarios.ts +9 -1
  215. package/packages/irc-test-support/src/test-config.ts +54 -0
  216. package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +1 -1
  217. package/packages/irc-test-support/tests/test-config.test.ts +51 -0
  218. package/pnpm-workspace.yaml +1 -0
  219. package/tools/ci-hardening/package.json +1 -1
  220. package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
  221. package/tools/load-test/package.json +33 -0
  222. package/tools/load-test/src/client.ts +351 -0
  223. package/tools/load-test/src/config.ts +313 -0
  224. package/tools/load-test/src/harness.ts +116 -0
  225. package/tools/load-test/src/main.ts +120 -0
  226. package/tools/load-test/src/metrics.ts +168 -0
  227. package/tools/load-test/src/report.ts +106 -0
  228. package/tools/load-test/tests/client.test.ts +212 -0
  229. package/tools/load-test/tests/config.test.ts +152 -0
  230. package/tools/load-test/tests/framing.test.ts +37 -0
  231. package/tools/load-test/tests/harness.test.ts +165 -0
  232. package/tools/load-test/tests/metrics.test.ts +174 -0
  233. package/tools/load-test/tests/report.test.ts +161 -0
  234. package/tools/load-test/tests/smoke.test.ts +67 -0
  235. package/tools/load-test/tsconfig.build.json +12 -0
  236. package/tools/load-test/tsconfig.test.json +10 -0
  237. package/tools/load-test/vitest.config.ts +29 -0
  238. package/tools/package.json +6 -1
  239. package/tools/seed-cf-accounts.ts +4 -1
  240. package/tools/tcp-ws-forwarder/package.json +1 -1
  241. package/tools/tcp-ws-forwarder/src/forwarder.ts +57 -9
  242. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +34 -1
  243. package/tools/tcp-ws-forwarder/tests/framing.test.ts +65 -1
@@ -42,6 +42,32 @@ jobs:
42
42
  node-version: 24
43
43
  cache: pnpm
44
44
 
45
+ # DynamoDB Local capability for `aws-adapter` coverage.
46
+ #
47
+ # The `aws-adapter` package's vitest `globalSetup` boots a real
48
+ # DynamoDB Local instance so the integration suite (handlers,
49
+ # aws-runtime, transactions, …) can be exercised under coverage.
50
+ # Without it, 171 of 294 tests `describe.skipIf` away and the
51
+ # package drops to ~42% coverage, blowing the 90% gate.
52
+ #
53
+ # The self-hosted Gitea runner image (`ubuntu-latest`) ships with
54
+ # neither Docker (testcontainers' Ryuk reaper fails with "Failed
55
+ # to connect to Reaper" without a `docker.sock` mount or
56
+ # privileged mode) nor a JRE. We install a JRE here via the
57
+ # canonical `setup-java` action so the globalSetup's ZIP fallback
58
+ # can fire — this is the only strategy the runner image can
59
+ # support without host-level privileged-mode configuration.
60
+ #
61
+ # `DDB_LOCAL_STRATEGY=zip` is pinned on the coverage step below
62
+ # so the globalSetup skips the testcontainers probe entirely
63
+ # (otherwise it blocks for ~60s waiting for Ryuk before falling
64
+ # back, which is flaky and wastes CI minutes).
65
+ - name: Setup Java (JRE for DynamoDB Local ZIP fallback)
66
+ uses: actions/setup-java@v4
67
+ with:
68
+ distribution: temurin
69
+ java-version: '11'
70
+
45
71
  - name: Install dependencies
46
72
  run: pnpm install --frozen-lockfile
47
73
 
@@ -52,6 +78,12 @@ jobs:
52
78
  run: pnpm typecheck
53
79
 
54
80
  - name: Test with coverage
81
+ # `DDB_LOCAL_STRATEGY=zip` pins the DynamoDB Local boot to the
82
+ # Java ZIP fallback (see the `Setup Java` step above for why).
83
+ # This skips the testcontainers probe so the runner does not
84
+ # block ~60s waiting for Ryuk before falling back.
85
+ env:
86
+ DDB_LOCAL_STRATEGY: zip
55
87
  run: pnpm coverage
56
88
 
57
89
  - name: Coverage gate (coverage-thresholds enforced per package)
@@ -90,7 +122,7 @@ jobs:
90
122
 
91
123
  - name: Upload coverage
92
124
  if: always()
93
- uses: actions/upload-artifact@v4
125
+ uses: actions/upload-artifact@v3
94
126
  with:
95
127
  name: coverage
96
128
  path: |
@@ -135,7 +167,7 @@ jobs:
135
167
 
136
168
  - name: Upload mutation report
137
169
  if: always()
138
- uses: actions/upload-artifact@v4
170
+ uses: actions/upload-artifact@v3
139
171
  with:
140
172
  name: mutation-report
141
173
  path: packages/irc-core/reports/mutation
@@ -38,8 +38,7 @@ name: deploy-aws
38
38
  # behaviour).
39
39
 
40
40
  on:
41
- push:
42
- branches: [main]
41
+ # Automatic push trigger disabled; deploy only runs manually.
43
42
  workflow_dispatch:
44
43
 
45
44
  # OIDC web identity requires id-token write; contents:read for checkout.
@@ -16,11 +16,7 @@ name: deploy-cf-tcp
16
16
  # CF_TCP_ORIGIN_ADDRESS Hostname/IP of the container origin.
17
17
 
18
18
  on:
19
- push:
20
- branches: [main]
21
- paths:
22
- - 'apps/cf-tcp-container/**'
23
- - '.github/workflows/deploy-cf-tcp.yml'
19
+ # Automatic push trigger disabled; deploy only runs manually.
24
20
  workflow_dispatch:
25
21
 
26
22
  concurrency:
@@ -25,8 +25,7 @@ name: deploy-cf
25
25
  # custom routes are wired.
26
26
 
27
27
  on:
28
- push:
29
- branches: [main]
28
+ # Automatic push trigger disabled; deploy only runs manually.
30
29
  workflow_dispatch:
31
30
 
32
31
  # Prevent two deploys racing the same DO namespace.
package/CHANGELOG.md CHANGED
@@ -10,6 +10,441 @@ For the release process itself — versioning policy, pre-release checklist,
10
10
  cutting a tag, rolling back — see [`docs/release.md`](docs/release.md).
11
11
  Cross-reference `progress.md` / `tickets.md` for per-ticket detail.
12
12
 
13
+ ## [0.6.0] - 2026-08-03
14
+
15
+ The headline is the **IRCv3 extension sweep (Phase 12)**: ten negotiated
16
+ caps and the user-mode-`S` TLS indicator land in one release, taking the
17
+ negotiated capability surface from "modern baseline" to "draft-leading-
18
+ edge". Alongside it: the **required-`serverName` config gate** (the
19
+ last open v0.5.0 follow-up), a new **`tools/load-test`** client farm
20
+ (Phase 7's 10k-connection harness), and the **coverage / CI unblocks**
21
+ that lift `aws-adapter` over the 90% gate and switch `cf-adapter` /
22
+ `cf-worker` to istanbul (clearing the v0.5.0 workerd-incompatibility
23
+ caveat). Deploy workflows are now **manual-only** (`workflow_dispatch`).
24
+
25
+ ### Added — IRCv3 extensions (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
26
+
27
+ The ten Phase 12 cap tickets light up the remaining negotiated-cap
28
+ surface, and the two non-cap Phase 12 tickets (ISUPPORT tokens and
29
+ user mode `S`) round out the wire-visible advertisement. Each cap
30
+ ships a pure reducer (where applicable), an actor route case,
31
+ advertisement in `SUPPORTED_CAPABILITIES` (or conditional
32
+ advertisement for `sts`), and unit + actor coverage. The
33
+ pure-reducer / dispatch pipeline is unchanged — every cap is plumbed
34
+ through `ctx` and emitted `Effect[]`, never performed in the handler.
35
+
36
+ - **`account-notify`**: pushes `:nick ACCOUNT <name|*>`
37
+ to peers in shared channels (excluding the sender unless
38
+ `echo-message`) on SASL login / logout. Closes the
39
+ `account-tag` / `extended-join` / `account-notify` triple modern
40
+ clients render fully.
41
+ - **`msgid`**: every PRIVMSG / NOTICE / TAGMSG fanned out
42
+ to a cap-enabled peer carries `@+msgid=<id>` from
43
+ `IdFactory.nonce()`. The nonce is **shared between the live fanout
44
+ and the `draft/chathistory` record** so a peer sees the same id live
45
+ and on replay. Legacy peers see no `msgid` tag.
46
+ - **`standard-replies`**: a data-driven
47
+ `numericToStandardReply` table replaces a curated subset of bare
48
+ numerics (`461`, `403`, `432`, `433`, SASL failures, …) with
49
+ `FAIL`/`WARN`/`NOTE COMMAND code context :description` for
50
+ cap-enabled clients. Legacy clients still receive the unchanged
51
+ numeric. Numeric-only replies (MOTD lines, NAMES, …) stay on their
52
+ numeric form.
53
+ - **`MONITOR`**: the modern `ISON` replacement. Implements
54
+ `MONITOR + nick{,nick}` / `- nick` / `C` / `L` / `S` with the
55
+ `730 RPL_MONONLINE` / `731 RPL_MONOFFLINE` / `732 RPL_MONLIST` /
56
+ `734 ERR_MONLISTFULL` numerics. Online/offline transitions push
57
+ `730`/`731` **asynchronously** via the Registry's nick reserve /
58
+ release hooks (a new `MonitorStore` port + push callback). The
59
+ ceiling is config-driven (`MONITOR=<n>`, default 30).
60
+ - **`labeled-response`**: a client-sent `+label=<id>` is
61
+ plumbed through `ctx` and every reply caused by that message is
62
+ wrapped in a single `BATCH +id labeled-response … BATCH -id`. A
63
+ command that produces no reply emits an empty labeled batch (spec-
64
+ mandated). The label travels on every reducer's emitted effects,
65
+ so multi-reply commands accumulate under one batch.
66
+ - **`sts`**: Strict Transport Security. The cap is
67
+ **conditionally advertised** — only when `ServerConfig.sts`
68
+ (`{ duration, port, preload? }`) is supplied. On a plaintext
69
+ listener a compliant client upgrades; on the TLS listener the
70
+ duration is still sent to lock in the policy. Defaults:
71
+ `duration = 30 days`, `port = 6697`.
72
+ - **`draft/typing`**: `TAGMSG <target> +draft/typing=
73
+ active|paused|done` broadcasts to channel members (or routes to a
74
+ nick target), excluding the sender unless `echo-message`. The
75
+ `+draft/typing=*` client tag is whitelisted for clients without
76
+ `message-tags` (per the spec's client-tag exception). Broadcast
77
+ delivery uses **OR-semantics**: a recipient qualifies if it
78
+ negotiated `message-tags` **or** `draft/typing` (a new `caps` field
79
+ on `BroadcastEffect`).
80
+ - **`draft/multiline`**: a cap-enabled
81
+ client sends `BATCH +id draft/multiline :target` + N PRIVMSG/NOTICE
82
+ lines + `BATCH -id`. The server concatenates per spec (with `\n`),
83
+ enforces a configurable byte budget (`multilineMaxBytes`, default
84
+ 4096), and broadcasts the batched replay to multiline peers / a
85
+ single joined PRIVMSG (carrying `+draft/multiline`) to legacy
86
+ peers. **Nick-target routing**, **chathistory recording** (one
87
+ record per batch carrying the joined body, assigned `msgid`, and
88
+ the `+draft/multiline` client tag), and **tag gating** (the
89
+ `+draft/multiline` tag is stripped for peers without
90
+ `message-tags`) all land in the completion ticket.
91
+ - **`draft/read-marker`**: persists the per-`(account,
92
+ channel)` last-read marker in a new `ReadMarkerStore` port,
93
+ propagates updates via `+draft/read-marker=<msgid>` tags, and
94
+ seeds a fresh connection's marker from the persisted value at
95
+ identify time. `CHATHISTORY BEFORE` honours the marker when no
96
+ explicit pivot is supplied.
97
+ - **`draft/pre-away`**: persists the away reason in a
98
+ new `AwayStore` port keyed by account. On identify (or SASL login)
99
+ the server re-applies the away state and emits the `AWAY` line +
100
+ `306 RPL_NOWAWAY` as if the user had sent `AWAY :reason`
101
+ themselves. `AWAY` (unset) clears the persisted reason.
102
+ - **ISUPPORT tokens for the new caps**: `005
103
+ RPL_ISUPPORT` now advertises `STATUSMSG=@+`, `EXTBAN=<prefix>,q`,
104
+ `ACCOUNTEXTBAN=a` (services-gated), `TYPING`, and `MULTILINE`.
105
+ `MONITOR=<n>` honours `serverConfig.monitorLimit`. Tokens are
106
+ config-derived where applicable.
107
+ - **User mode `S` (TLS connected)**: the transport's
108
+ TLS fact is surfaced end-to-end as a read-only user mode. Set
109
+ once by the registration reducer from `ConnectionState.secure`;
110
+ emitted in `221 … +S`; rejected by `MODE nick +/-S` with `501
111
+ ERR_UMODEUNKNOWNFLAG`; surfaced in WHOIS via the new `276
112
+ RPL_WHOISSECURE`. Each adapter asserts `secure: true` where the
113
+ platform terminates TLS (API Gateway, CF edge, Spectrum, NLB TLS
114
+ listener).
115
+
116
+ ### Changed — Required server identity
117
+
118
+ The last open v0.5.0 follow-up closes. The `irc.example.com`
119
+ placeholder `DEFAULT_SERVER_CONFIG` is **removed** from
120
+ `irc-core`; the shared `ServerConfigSchema` rejects a missing
121
+ `serverName` / `networkName` so every adapter **fails fast at boot**
122
+ with a readable, field-listing error.
123
+
124
+ - **`@serverless-ircd/irc-core`**: `DEFAULT_SERVER_CONFIG` removed.
125
+ - **`@serverless-ircd/irc-test-support`**: ships a clearly test-scoped
126
+ `makeTestServerConfig()` baseline (`irc.test.local`) so unit tests
127
+ construct a `ServerConfig` without re-introducing a production
128
+ default.
129
+ - `apps/cf-worker` + `apps/cf-tcp-container`: loaders require
130
+ `SERVER_NAME`; `wrangler.toml` samples switched to
131
+ `irc.your-domain.invalid`.
132
+ - `apps/aws-stack` + `apps/local-cli`: keep obviously-local fallbacks
133
+ (`irc.localhost`) documented as *"production MUST override"*;
134
+ `local-cli` gains `--server-name` / `--network-name` flags.
135
+
136
+ ### Added — Load-test harness (`@serverless-ircd/load-test`)
137
+
138
+ PLAN §7 / Phase 7's 10k-connection harness. A synthetic WebSocket IRC
139
+ client farm: opens N connections, registers (NICK/USER), joins a
140
+ channel, and optionally chats. Captures **p50 / p95 / p99 latency per
141
+ phase** (connect / register / join / message) plus the **drop rate**,
142
+ and prints a markdown report to stdout (or `--report <path>`).
143
+
144
+ ```bash
145
+ pnpm --filter load-test start -- \
146
+ --target wss://irc.staging.example.com/ \
147
+ --connections 10000 --concurrency 200 --ramp-ms 60000 \
148
+ --channel '#loadtest' --messages 3 --platform cf-staging \
149
+ --report reports/cf-staging-2026-08-03.md
150
+ ```
151
+
152
+ The package ships its own vitest suite (config parsing, framing,
153
+ harness, metrics, report) at ≥90% coverage; no production source in
154
+ the irc-core / irc-server packages changed to support it.
155
+
156
+ ### Added — Coverage / CI unblocks
157
+
158
+ - **`aws-adapter` clears the 90% gate.** The self-hosted runner image
159
+ ships with neither Docker nor a JRE, so the package's
160
+ testcontainers / Java-ZIP DynamoDB Local boot fell through to
161
+ strategy 3 and 171 of 294 integration tests `describe.skipIf`-ed
162
+ away — dropping coverage to ~42% and blowing the gate. CI now
163
+ installs Temurin JRE 11 via `actions/setup-java@v4` and pins
164
+ `DDB_LOCAL_STRATEGY=zip` on the coverage step so the ZIP fallback
165
+ fires deterministically. The full aws-adapter suite now runs on
166
+ every coverage job. New integration tests cover
167
+ `broadcastWallops` fan-out, `reserveNick` non-conditional error
168
+ propagation, `refreshChannel` empty-members / topic-preservation
169
+ paths, NLB header fallbacks, scan-row type guards, and
170
+ `CREATED_AT` overflow handling. `tools/ci-hardening` gained a
171
+ self-check asserting the JRE install + strategy pin remain wired.
172
+ - **`aws-stack` coverage command repaired.** The CDK `NodejsFunction`
173
+ in the test synth re-bundled the Lambda asset via esbuild ~80
174
+ times per coverage run and OOM-ed CI before vitest could print a
175
+ report. A new `makeLambda()` factory switches between
176
+ `NodejsFunction` (production synth) and an inline-zip `Function`
177
+ stub (tests) via the `IRC_AWS_STACK_TEST_NO_BUNDLE` env var. The
178
+ stub preserves every CloudFormation shape the suite asserts on
179
+ while making bundling a no-op. New
180
+ `tests/synth-no-bundle.test.ts` + `tests/coverage-command.test.ts`
181
+ lock both branches and assert the `pnpm coverage` run prints an
182
+ `All files` row.
183
+ - **`cf-adapter` / `cf-worker` switch to istanbul coverage.** This
184
+ **resolves the v0.5.0 known limitation**: `@vitest/coverage-v8`
185
+ v4.1.x imports `node:inspector/promises`, which the
186
+ `@cloudflare/vitest-pool-workers` (workerd) pool cannot resolve.
187
+ Both packages now use `@vitest/coverage-istanbul` so `pnpm
188
+ coverage` loads under workerd. Edge-case suites were expanded
189
+ (serialize/deserialize/migrate, connection-do pure helpers,
190
+ `broadcastWallops` fan-out, `changeNick` shard routing,
191
+ ChannelDO broadcast/cache, RegistryDO `listEntries`, SASL seed
192
+ parsing).
193
+
194
+ ### Changed — Deploy workflows are now manual-only
195
+
196
+ `deploy-cf.yml`, `deploy-cf-tcp.yml`, and `deploy-aws.yml` dropped
197
+ their `push: branches: [main]` triggers and now run on
198
+ `workflow_dispatch` only. Staging deploys are no longer fired
199
+ automatically on every merge to `main`; a maintainer kicks them off
200
+ by hand. The release runbook (`docs/Release-Process.md`) is the
201
+ canonical reference either way — there was never a CI auto-deploy to
202
+ production.
203
+
204
+ ### Changed — `@vitest/coverage-istanbul` peer
205
+
206
+ The root lockfile (`pnpm-lock.yaml`) registers the
207
+ `@vitest/coverage-istanbul` peer introduced by the cf-adapter /
208
+ cf-worker coverage switch. No operator action; `pnpm install`
209
+ resolves it.
210
+
211
+ ### ⚠️ Migration required
212
+
213
+ - **`serverName` is now required at boot.** Operators who relied on
214
+ the silent `irc.example.com` placeholder must supply an explicit
215
+ value via their adapter's config knob:
216
+ - **Cloudflare Worker / TCP container**: set `SERVER_NAME` (and
217
+ `NETWORK_NAME` if desired) in `wrangler.toml` `[vars]` or via
218
+ `wrangler secret put`. The samples switched to
219
+ `irc.your-domain.invalid` — replace with your real hostname.
220
+ - **AWS**: the CDK stack's `serverName` prop / `SERVER_NAME` env
221
+ var. The local fallback is `irc.localhost` and is explicitly
222
+ documented as "production MUST override".
223
+ - **`local-cli`**: new `--server-name <h>` / `--network-name <n>`
224
+ flags (default `irc.localhost` / `LocalNet`).
225
+ A missing value now throws an aggregated `"Invalid server
226
+ config:"` error at boot listing every offending field.
227
+ - **No persisted-state schema change.** `PERSISTED_STATE_VERSION`
228
+ stays `1`. The user-mode-`S` work adds a `secure` field and a
229
+ `userModes.tls` field to the persisted shape, but both are purely
230
+ **additive with `false` defaults** and the cf-adapter `migrate()`
231
+ path backfills them on pre-feature records. **Code rollback across
232
+ this release is safe.**
233
+ - No toolchain change. Node ≥ 24 / pnpm 11 (from v0.4.0) still apply.
234
+
235
+ ### Known limitations
236
+
237
+ - Same transport limits as v0.5.0: the **CF TCP path requires
238
+ Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
239
+ origin; the **AWS TCP path** uses NLB + Lambda streaming and is
240
+ subject to Lambda idle-timeout / stream-duration limits. The
241
+ WebSocket path remains the zero-extra-deps default.
242
+ - **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
243
+ CA/trust-store** on both platforms and is not enabled by default.
244
+ - `STATS` ships the `u` + `l` query letters; the full RFC matrix
245
+ (`k`, `K`, `q`, `g`, `x`, `z`, …) is intentionally out of scope for
246
+ v1 — unknown letters get the charybdis-style empty body + `219`.
247
+ - **Client compatibility sweep** (WeeChat / HexChat / IRCCloud /
248
+ TheLounge / matrix-IRC bridge) remains pending.
249
+ - **Coverage hardening is partial.** `aws-adapter` and `aws-stack`
250
+ now clear the 90% gate, and `cf-adapter` / `cf-worker` run under
251
+ istanbul; the remaining packages (`cf-adapter`, `local-cli`,
252
+ `load-test`, `cf-tcp-container`, `tcp-ws-forwarder`,
253
+ `irc-test-support`) are **above the gate but below 100%** —
254
+ follow-ups drive each to 100%.
255
+ - The IRC services (NickServ / ChanServ / HostServ / OperServ /
256
+ MemoServ) Phase 13 work is pending; `draft/read-marker` and
257
+ `draft/pre-away` ship with in-memory reference stores, so marker /
258
+ away persistence does not survive an adapter restart until a
259
+ services backend lands.
260
+ - The web client Phase 14 work (vendor Kiwi IRC, serve SPA from the
261
+ Worker, WS Origin allowlist) is pending.
262
+
263
+ ### Security
264
+
265
+ - None. No vulnerabilities have been reported or fixed in this
266
+ release. The new caps add surface area but ship no new auth path
267
+ (the SASL `PLAIN` / `EXTERNAL` paths and the oper gate are
268
+ unchanged); the required-`serverName` change rejects misconfigured
269
+ deploys at boot rather than advertising a placeholder identity.
270
+
271
+ ---
272
+
273
+ ## [0.5.0] - 2026-08-02
274
+
275
+ The headline is **protocol completion**: the deferred IRC verbs land
276
+ (Phase 11), and the S2S / obsolete RFC 2812 verbs are **formally
277
+ dropped** rather than left "deferred". With this release every standard
278
+ IRC verb a modern client expects is implemented — the only remaining
279
+ `421 ERR_UNKNOWNCOMMAND` paths are the deliberately-unimplemented S2S
280
+ and obsolete verbs. The roadmap's 0.x protocol surface is complete;
281
+ what remains is the load/compat sweep (10k-connection load test +
282
+ client compatibility matrix) and the required-`serverName` config gate.
283
+
284
+ ### Added — Deferred IRC verbs (`@serverless-ircd/irc-core`, `@serverless-ircd/irc-server`)
285
+
286
+ Six Phase 11 tickets light up the remaining command surface. Each ships
287
+ a pure reducer, an actor route case (no more `421`), new numerics in
288
+ `protocol/numerics.ts`, and unit + actor coverage. (The remaining two
289
+ Phase 11 tickets are drops — see below.)
290
+
291
+ - **`KILL`** (oper-gated force-disconnect): `KILL <nick>
292
+ <comment>` resolves the target via the nick registry + a live
293
+ connection fetch (mirroring WHOIS), checks the oper gate (`481
294
+ ERR_NOPRIVILEGES`), and emits a `Disconnect` effect carrying the
295
+ classic `Killed (<oper> (<comment>))` reason. Peer `QUIT` fanout in
296
+ the target's shared channels happens via the runtime's existing
297
+ `disconnect` path. Missing params → `461`; unknown nick → `401`; an
298
+ oper may `KILL` themselves (RFC-permitted).
299
+ **Platform asymmetry:** on Cloudflare the `disconnect` RPC closes the
300
+ target DO's socket immediately; on AWS a `$default` Lambda invocation
301
+ cannot close a *remote* APIGW WebSocket, so the target's
302
+ live socket only closes on its next `$disconnect` / sweeper cycle —
303
+ the QUIT fanout to peers still fires. The reducer is identical; the
304
+ difference is the bound runtime's `disconnect` semantics.
305
+ - **`REHASH`** (oper-gated config reload): emits `382
306
+ RPL_REHASHING :<source>` and re-fetches `ServerConfig` from the
307
+ adapter's bound source via a new `IrcRuntime.reloadConfig()` port
308
+ method. The refreshed value swaps into the actor's live config
309
+ reference so subsequent commands observe it (rotated oper passwords,
310
+ new MOTD source, etc.). On reload failure the previous config stays
311
+ in effect and a graceful error-suffixed `382` follows — the oper is
312
+ **not** disconnected (a bad store read must never sever a live link).
313
+ Already-open connections keep their negotiated caps and registration;
314
+ transport/TLS reload requires a redeploy.
315
+ - **`LUSERS` + `STATS`** (network statistics): backed by a
316
+ new async `ServerStats` port (`getStats(): Promise<ServerStatsSnapshot>`)
317
+ and an `InMemoryStats` reference impl. `LUSERS` emits the full
318
+ `251`–`255` sequence (only the non-zero counts, per RFC) plus the
319
+ optional `265` / `266` local/global counts. `STATS <query>` supports
320
+ the `u` (uptime, `242 RPL_STATSUPTIME`) and `l` (link info,
321
+ `211 RPL_STATSLINKINFO`) letters; unknown letters get the
322
+ charybdis-style empty body terminated by `219 RPL_ENDOFSTATS`. A
323
+ `<server>` argument that does not match the local server (case-
324
+ insensitive) yields `402 ERR_NOSUCHSERVER` (single-server network —
325
+ S2S aggregation is a PLAN non-goal). The `computeStatsSnapshot`
326
+ helper centralizes the oper/invisible/unknown classification so each
327
+ adapter's backend only has to gather raw counts.
328
+ - **`TRACE`** (routing diagnostics): in a single-server
329
+ serverless IRCd `TRACE` collapses to a local-server line + the
330
+ `262 RPL_ENDOFTRACE` terminator. Per-connection `204
331
+ RPL_TRACEOPERATOR` / `205 RPL_TRACEUSER` detail is **oper-gated** —
332
+ non-opers see only the server line + `262` (no enumeration of `+i`
333
+ users they don't share a channel with). `TRACE <server>` where
334
+ `<server>` is not the local server → `402`.
335
+ - **`WALLOPS`** (oper broadcast): `WALLOPS :<message>` is
336
+ oper-only to send (modern ircd convention — non-oper → `481`);
337
+ delivery fans out to every connection whose `+w` user mode is set via
338
+ a new `BroadcastWallops` effect interpreted by the runtime's
339
+ `broadcastWallops` method (global cross-connection fanout, not
340
+ channel-scoped). The originator is always excluded (skip-self,
341
+ matching NOTICE/PRIVMSG semantics). Empty message → `461`. The `+w`
342
+ user mode was already accepted by `MODE` and advertised in `004
343
+ RPL_MYINFO`; this lights up the consumer.
344
+ - **`SETNAME`** (realname change): `SETNAME :<realname>`
345
+ updates `state.realname` so a subsequent `WHOIS` reflects the new
346
+ value in `311 RPL_WHOISUSER`. No broadcast is emitted in v1 (there
347
+ is no standard IRCv3 cap for relaying a realname change — a
348
+ `draft/setname` relay is a documented follow-up). Empty/missing
349
+ realname → `461`; the registration-time realname length cap applies.
350
+
351
+ ### Changed — S2S & obsolete verbs formally dropped (`@serverless-ircd/irc-core`)
352
+
353
+ The remaining two Phase 11 tickets convert the "deferred" framing into
354
+ an explicit **drop**. There is no behaviour change on the wire — the verbs
355
+ already returned `421 ERR_UNKNOWNCOMMAND` — but the framing, comments,
356
+ and reserved numerics are cleaned up so the codebase no longer carries
357
+ dead scaffolding.
358
+
359
+ - **S2S verbs** (`CONNECT`, `SQUIT`, `LINKS`): dropped as a PLAN
360
+ non-goal (single-server serverless IRCd; no mesh to connect, split,
361
+ or list). The now-unused numerics `RPL_LINKS (364)`,
362
+ `RPL_ENDOFLINKS (365)`, and `ERR_CANTKILLSERVER (483)` are removed
363
+ from `numerics.ts` and its auto-derived `numericToName` reverse map.
364
+ `ERR_NOSUCHSERVER (402)` is retained — `LUSERS` / `STATS` / `TRACE`
365
+ use it for remote-server gating.
366
+ - **Obsolete RFC 2812 verbs** (`SERVICE`, `SUMMON`, `USERS`): dropped
367
+ (never widely implemented; no modern client uses them). The now-unused
368
+ numerics `RPL_YOURESERVICE (383)`, `ERR_NOSUCHSERVICE (408)`,
369
+ `ERR_SUMMONDISABLED (445)`, and `ERR_USERSDISABLED (446)` are removed.
370
+ - The `actor.ts` default-branch triage comment was rewritten: the
371
+ six verbs are documented as **deliberately unimplemented** rather
372
+ than "deferred".
373
+ - Source-level regression guards
374
+ (`packages/irc-core/tests/dropped-s2s-and-obsolete-verbs.test.ts`)
375
+ and a behaviour guard in `actor.test.ts` (the verbs still emit
376
+ `421`) pin the drop so the numerics or the "deferred" framing cannot
377
+ creep back in.
378
+
379
+ ### Added — Runtime ports for the new verbs
380
+
381
+ - **`ServerStats` port** (`packages/irc-core/src/ports.ts`): async
382
+ `getStats()` returning `ServerStatsSnapshot` (users / invisible /
383
+ opers / unknown / channels / servers / local+global conn counts /
384
+ uptime anchor). Asynchronous because the aggregation crosses every
385
+ connection and channel — it cannot be pre-loaded into a synchronous
386
+ view the way MOTD or a credential table can. Each adapter implements
387
+ it against its authoritative store; `InMemoryStats` is the reference
388
+ impl. The `computeStatsSnapshot` helper holds the shared
389
+ classification logic (oper / invisible / unknown).
390
+ - **`reloadConfig()` on `IrcRuntime`** (`packages/irc-server/src/runtime.ts`):
391
+ request/response port method the `REHASH` route calls to re-fetch
392
+ `ServerConfig` from the adapter's bound source (CF: KV/secret env;
393
+ AWS: Secrets Manager/SSM env; local-cli: the config file/options).
394
+ Modelled as a runtime method rather than a fire-and-forget effect
395
+ because the refreshed value must come back to the actor.
396
+
397
+ ### ⚠️ Migration required
398
+
399
+ - **For adapter authors wiring the new ports.** `ServerStats` and
400
+ `reloadConfig` are optional at the type level (the actor falls back
401
+ to a zero-count snapshot when no `ServerStats` is bound, and
402
+ `REHASH` still emits `382` even if `reloadConfig` rejects — the
403
+ previous config is retained). To surface real counts and live
404
+ reloads, each adapter wires its own backend. No Durable-Object
405
+ persisted-state schema change (`PERSISTED_STATE_VERSION` stays `1`);
406
+ code rollback across this release is safe.
407
+ - No toolchain change. Node ≥ 24 / pnpm 11 (from v0.4.0) still apply.
408
+
409
+ ### Known limitations
410
+
411
+ - Same as v0.4.0's transport limits: the **CF TCP path requires
412
+ Cloudflare Spectrum (Enterprise tier)** plus a stateful Container
413
+ origin; the **AWS TCP path** uses NLB + Lambda streaming and is
414
+ subject to Lambda idle-timeout / stream-duration limits. The
415
+ WebSocket path remains the zero-extra-deps default.
416
+ - **mTLS (for SASL `EXTERNAL`) requires a custom domain + uploaded
417
+ CA/trust-store** on both platforms and is not enabled by default.
418
+ - `serverName` still defaults to the `irc.example.com` placeholder when
419
+ an adapter config omits it (pending).
420
+ - `STATS` ships the `u` + `l` query letters; the full RFC matrix
421
+ (`k`, `K`, `q`, `g`, `x`, `z`, …) is intentionally out of scope for
422
+ v1 — unknown letters get the charybdis-style empty body + `219`.
423
+ - Load testing (10k concurrent connections) and the client
424
+ compatibility sweep (WeeChat / HexChat / IRCCloud / TheLounge)
425
+ remain pending.
426
+ - **Local coverage instrumentation on the Cloudflare-backed packages
427
+ (`@serverless-ircd/cf-adapter`, `@serverless-ircd/cf-worker`) is
428
+ incompatible with the current vitest toolchain.** `@vitest/coverage-v8`
429
+ v4.1.x imports `node:inspector/promises`, which the
430
+ `@cloudflare/vitest-pool-workers` (workerd) test pool cannot resolve.
431
+ The packages' test suites themselves pass (cf-adapter 185/185,
432
+ cf-worker 3/3); only `pnpm coverage` fails to load the coverage
433
+ module under the workerd pool. This is a documented [vitest-pool-workers
434
+ known issue](https://developers.cloudflare.com/workers/testing/vitest-integration/known-issues/#module-resolution)
435
+ and is expected to resolve with an upstream `@vitest/coverage-v8` or
436
+ pool-workers release. No production code is affected; CI behavior
437
+ depends on its installed versions.
438
+
439
+ ### Security
440
+
441
+ - None. No vulnerabilities have been reported or fixed in this release.
442
+ The dropped S2S / obsolete verbs and their reserved numerics carried
443
+ no reachable code path; their removal reduces surface area but fixes
444
+ no defect.
445
+
446
+ ---
447
+
13
448
  ## [0.4.0] - 2026-07-31
14
449
 
15
450
  The headline is **dual transport**: both adapters now speak a real