serverless-ircd 0.1.0 → 0.2.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 (198) hide show
  1. package/.github/workflows/ci.yml +96 -2
  2. package/.github/workflows/deploy-aws.yml +129 -0
  3. package/.github/workflows/deploy-cf.yml +0 -2
  4. package/.gitmodules +3 -0
  5. package/AGENTS.md +5 -0
  6. package/CHANGELOG.md +352 -0
  7. package/MOTD.txt +3 -0
  8. package/PLAN-FIXES.md +358 -0
  9. package/PLAN.md +420 -0
  10. package/README.md +115 -81
  11. package/apps/aws-stack/README.md +73 -0
  12. package/apps/aws-stack/bin/aws.ts +49 -0
  13. package/apps/aws-stack/cdk.json +10 -0
  14. package/apps/aws-stack/package.json +41 -0
  15. package/apps/aws-stack/scripts/smoke-helpers.d.mts +22 -0
  16. package/apps/aws-stack/scripts/smoke-helpers.mjs +89 -0
  17. package/apps/aws-stack/scripts/smoke.mjs +142 -0
  18. package/apps/aws-stack/src/aws-stack.ts +263 -0
  19. package/apps/aws-stack/src/tables.ts +10 -0
  20. package/apps/aws-stack/tests/localstack.test.ts +46 -0
  21. package/apps/aws-stack/tests/smoke-helpers.test.ts +98 -0
  22. package/apps/aws-stack/tests/stack.test.ts +464 -0
  23. package/apps/aws-stack/tsconfig.build.json +11 -0
  24. package/apps/aws-stack/tsconfig.test.json +8 -0
  25. package/apps/aws-stack/vitest.config.ts +20 -0
  26. package/apps/cf-worker/package.json +1 -1
  27. package/apps/cf-worker/wrangler.toml +25 -0
  28. package/apps/local-cli/package.json +1 -1
  29. package/apps/local-cli/src/config-loader.ts +56 -0
  30. package/apps/local-cli/src/server.ts +241 -32
  31. package/apps/local-cli/tests/config-loader.test.ts +107 -0
  32. package/apps/local-cli/tests/e2e.test.ts +74 -0
  33. package/apps/local-cli/tests/security-e2e.test.ts +239 -0
  34. package/biome.json +3 -1
  35. package/dashboards/cloudwatch-irc.json +139 -0
  36. package/docs/AWS-Adapter-Architecture.md +494 -0
  37. package/docs/AWS-Deployment.md +1107 -0
  38. package/docs/Cloudflare-Deployment-Guide.md +660 -0
  39. package/docs/Home.md +1 -0
  40. package/docs/Observability.md +87 -0
  41. package/docs/PlanIRCv3Websocket.md +489 -0
  42. package/docs/PlanWebClient.md +451 -0
  43. package/docs/Release-Process.md +440 -0
  44. package/package.json +8 -2
  45. package/packages/aws-adapter/README.md +35 -0
  46. package/packages/aws-adapter/package.json +52 -0
  47. package/packages/aws-adapter/src/aws-runtime.ts +783 -0
  48. package/packages/aws-adapter/src/cdk-table-defs.ts +69 -0
  49. package/packages/aws-adapter/src/config-loader.ts +96 -0
  50. package/packages/aws-adapter/src/dynamo.ts +61 -0
  51. package/packages/aws-adapter/src/handlers/connect.ts +44 -0
  52. package/packages/aws-adapter/src/handlers/default.ts +322 -0
  53. package/packages/aws-adapter/src/handlers/disconnect.ts +29 -0
  54. package/packages/aws-adapter/src/handlers/index.ts +248 -0
  55. package/packages/aws-adapter/src/handlers/ping-checker.ts +217 -0
  56. package/packages/aws-adapter/src/handlers/state.ts +13 -0
  57. package/packages/aws-adapter/src/handlers/sweeper.ts +84 -0
  58. package/packages/aws-adapter/src/index.ts +67 -0
  59. package/packages/aws-adapter/src/message-store.ts +34 -0
  60. package/packages/aws-adapter/src/serialize.ts +283 -0
  61. package/packages/aws-adapter/src/tables.ts +53 -0
  62. package/packages/aws-adapter/tests/aws-harness.ts +408 -0
  63. package/packages/aws-adapter/tests/aws-integration.test.ts +17 -0
  64. package/packages/aws-adapter/tests/aws-runtime.test.ts +654 -0
  65. package/packages/aws-adapter/tests/config-loader.test.ts +158 -0
  66. package/packages/aws-adapter/tests/dynamo.test.ts +57 -0
  67. package/packages/aws-adapter/tests/global-setup.ts +301 -0
  68. package/packages/aws-adapter/tests/gone-exception.test.ts +271 -0
  69. package/packages/aws-adapter/tests/handlers.test.ts +427 -0
  70. package/packages/aws-adapter/tests/message-store.test.ts +55 -0
  71. package/packages/aws-adapter/tests/ping-checker.test.ts +571 -0
  72. package/packages/aws-adapter/tests/serialize.test.ts +249 -0
  73. package/packages/aws-adapter/tests/smoke.test.ts +48 -0
  74. package/packages/aws-adapter/tests/sweeper.test.ts +420 -0
  75. package/packages/aws-adapter/tests/tables.test.ts +74 -0
  76. package/packages/aws-adapter/tests/transactions.test.ts +446 -0
  77. package/packages/aws-adapter/tsconfig.build.json +16 -0
  78. package/packages/aws-adapter/tsconfig.test.json +14 -0
  79. package/packages/aws-adapter/vitest.config.ts +23 -0
  80. package/packages/cf-adapter/package.json +2 -1
  81. package/packages/cf-adapter/src/config-loader.ts +106 -0
  82. package/packages/cf-adapter/src/connection-do.ts +34 -0
  83. package/packages/cf-adapter/tests/cf-harness.ts +2 -2
  84. package/packages/cf-adapter/tests/cf-integration.test.ts +2 -2
  85. package/packages/cf-adapter/tests/config-loader.test.ts +149 -0
  86. package/packages/cf-adapter/tests/connection-do.test.ts +42 -0
  87. package/packages/in-memory-runtime/package.json +1 -1
  88. package/packages/in-memory-runtime/src/in-memory-runtime.ts +108 -3
  89. package/packages/in-memory-runtime/src/index.ts +1 -1
  90. package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +115 -0
  91. package/packages/irc-core/package.json +12 -2
  92. package/packages/irc-core/src/admission.ts +216 -0
  93. package/packages/irc-core/src/caps/capabilities.ts +1 -0
  94. package/packages/irc-core/src/cloak.ts +81 -0
  95. package/packages/irc-core/src/commands/cap.ts +1 -2
  96. package/packages/irc-core/src/commands/chathistory.ts +305 -0
  97. package/packages/irc-core/src/commands/index.ts +7 -0
  98. package/packages/irc-core/src/commands/join.ts +59 -7
  99. package/packages/irc-core/src/commands/privmsg.ts +24 -0
  100. package/packages/irc-core/src/commands/registration.ts +71 -14
  101. package/packages/irc-core/src/commands/sasl.ts +251 -0
  102. package/packages/irc-core/src/config.ts +247 -0
  103. package/packages/irc-core/src/flood-control.ts +175 -0
  104. package/packages/irc-core/src/index.ts +5 -0
  105. package/packages/irc-core/src/ports.ts +655 -0
  106. package/packages/irc-core/src/protocol/base64.ts +16 -0
  107. package/packages/irc-core/src/protocol/index.ts +2 -1
  108. package/packages/irc-core/src/protocol/numerics.ts +11 -0
  109. package/packages/irc-core/src/protocol/parser.ts +27 -2
  110. package/packages/irc-core/src/state/connection.ts +41 -0
  111. package/packages/irc-core/src/types.ts +66 -2
  112. package/packages/irc-core/stryker.commands.conf.json +41 -0
  113. package/packages/irc-core/stryker.protocol.conf.json +26 -0
  114. package/packages/irc-core/tests/admission.test.ts +229 -0
  115. package/packages/irc-core/tests/caps/capabilities.test.ts +22 -0
  116. package/packages/irc-core/tests/cloak.test.ts +78 -0
  117. package/packages/irc-core/tests/commands/chathistory.test.ts +996 -0
  118. package/packages/irc-core/tests/commands/join.test.ts +246 -2
  119. package/packages/irc-core/tests/commands/privmsg.test.ts +146 -2
  120. package/packages/irc-core/tests/commands/registration.test.ts +277 -9
  121. package/packages/irc-core/tests/commands/sasl.test.ts +536 -0
  122. package/packages/irc-core/tests/config.test.ts +646 -0
  123. package/packages/irc-core/tests/flood-control.test.ts +394 -0
  124. package/packages/irc-core/tests/message-store.test.ts +530 -0
  125. package/packages/irc-core/tests/parser.test.ts +44 -1
  126. package/packages/irc-core/tests/ports.test.ts +263 -0
  127. package/packages/irc-server/package.json +1 -1
  128. package/packages/irc-server/src/actor.ts +162 -44
  129. package/packages/irc-server/src/dispatch.ts +89 -5
  130. package/packages/irc-server/src/index.ts +2 -0
  131. package/packages/irc-server/src/routing.ts +151 -0
  132. package/packages/irc-server/tests/actor.test.ts +470 -14
  133. package/packages/irc-server/tests/dispatch.test.ts +84 -0
  134. package/packages/irc-server/tests/routing.test.ts +201 -0
  135. package/packages/irc-test-support/README.md +32 -0
  136. package/packages/irc-test-support/package.json +37 -0
  137. package/packages/{cf-adapter/tests/integration → irc-test-support/src}/in-memory-harness.ts +1 -1
  138. package/packages/irc-test-support/src/index.ts +24 -0
  139. package/packages/{cf-adapter/tests/integration/harness.test.ts → irc-test-support/tests/in-memory-harness.test.ts} +1 -1
  140. package/packages/{cf-adapter/tests/integration/scenarios.test.ts → irc-test-support/tests/in-memory-scenarios.test.ts} +1 -2
  141. package/packages/irc-test-support/tests/smoke.test.ts +11 -0
  142. package/packages/irc-test-support/tsconfig.build.json +16 -0
  143. package/packages/irc-test-support/tsconfig.test.json +14 -0
  144. package/packages/irc-test-support/vitest.config.ts +20 -0
  145. package/progress.md +107 -0
  146. package/tickets.md +2485 -0
  147. package/tools/ci-hardening/package.json +30 -0
  148. package/tools/ci-hardening/src/index.ts +6 -0
  149. package/tools/ci-hardening/src/validate.ts +103 -0
  150. package/tools/ci-hardening/tests/__no_thresholds__.txt +11 -0
  151. package/tools/ci-hardening/tests/__partial_thresholds__.txt +16 -0
  152. package/tools/ci-hardening/tests/__stryker_bad__.json +4 -0
  153. package/tools/ci-hardening/tests/validate.test.ts +177 -0
  154. package/tools/ci-hardening/tsconfig.build.json +12 -0
  155. package/tools/ci-hardening/tsconfig.test.json +10 -0
  156. package/tools/ci-hardening/vitest.config.ts +21 -0
  157. package/tools/tcp-ws-forwarder/package.json +1 -1
  158. package/tools/tcp-ws-forwarder/src/forwarder.ts +34 -23
  159. package/tools/tcp-ws-forwarder/src/logger.ts +155 -0
  160. package/tools/tcp-ws-forwarder/src/main.ts +33 -14
  161. package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +86 -0
  162. package/tools/tcp-ws-forwarder/tests/logger.test.ts +136 -0
  163. package/webircgateway/LICENSE +201 -0
  164. package/webircgateway/Makefile +44 -0
  165. package/webircgateway/README.md +134 -0
  166. package/webircgateway/config.conf.example +135 -0
  167. package/webircgateway/go.mod +16 -0
  168. package/webircgateway/go.sum +89 -0
  169. package/webircgateway/main.go +118 -0
  170. package/webircgateway/pkg/dnsbl/dnsbl.go +121 -0
  171. package/webircgateway/pkg/identd/identd.go +86 -0
  172. package/webircgateway/pkg/identd/rpcclient.go +59 -0
  173. package/webircgateway/pkg/irc/isupport.go +56 -0
  174. package/webircgateway/pkg/irc/message.go +217 -0
  175. package/webircgateway/pkg/irc/state.go +79 -0
  176. package/webircgateway/pkg/proxy/proxy.go +129 -0
  177. package/webircgateway/pkg/proxy/server.go +237 -0
  178. package/webircgateway/pkg/recaptcha/recaptcha.go +59 -0
  179. package/webircgateway/pkg/webircgateway/client.go +741 -0
  180. package/webircgateway/pkg/webircgateway/client_command_handlers.go +495 -0
  181. package/webircgateway/pkg/webircgateway/config.go +385 -0
  182. package/webircgateway/pkg/webircgateway/gateway.go +278 -0
  183. package/webircgateway/pkg/webircgateway/gateway_utils.go +133 -0
  184. package/webircgateway/pkg/webircgateway/hooks.go +152 -0
  185. package/webircgateway/pkg/webircgateway/letsencrypt.go +41 -0
  186. package/webircgateway/pkg/webircgateway/messagetags.go +103 -0
  187. package/webircgateway/pkg/webircgateway/transport_kiwiirc.go +206 -0
  188. package/webircgateway/pkg/webircgateway/transport_sockjs.go +107 -0
  189. package/webircgateway/pkg/webircgateway/transport_tcp.go +113 -0
  190. package/webircgateway/pkg/webircgateway/transport_websocket.go +126 -0
  191. package/webircgateway/pkg/webircgateway/utils.go +147 -0
  192. package/webircgateway/plugins/example/plugin.go +11 -0
  193. package/webircgateway/plugins/stats/plugin.go +52 -0
  194. package/webircgateway/staticcheck.conf +1 -0
  195. package/webircgateway/webircgateway.svg +3 -0
  196. package/packages/cf-adapter/tests/integration/index.ts +0 -17
  197. /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/harness.ts +0 -0
  198. /package/packages/{cf-adapter/tests/integration → irc-test-support/src}/scenarios.ts +0 -0
@@ -0,0 +1,201 @@
1
+ import {
2
+ type ConnectionState,
3
+ type Effect as EffectType,
4
+ EmptyMotdProvider,
5
+ FakeClock,
6
+ type FloodControlConfig,
7
+ SequentialIdFactory,
8
+ type ServerConfig,
9
+ buildCtx,
10
+ createConnection,
11
+ } from '@serverless-ircd/irc-core';
12
+ import { describe, expect, it } from 'vitest';
13
+ import { type RoutedReducers, buildRoutedReducers, resolveFloodControl } from '../src/routing';
14
+
15
+ const baseServerConfig: ServerConfig = {
16
+ serverName: 'irc.example.com',
17
+ networkName: 'ExampleNet',
18
+ maxChannelsPerUser: 30,
19
+ maxTargetsPerCommand: 10,
20
+ maxListEntries: 50,
21
+ nickLen: 30,
22
+ channelLen: 50,
23
+ topicLen: 390,
24
+ quitMessage: 'Client Quit',
25
+ };
26
+
27
+ function makeConn(id = 'c1'): ConnectionState {
28
+ const conn = createConnection({ id, connectedSince: 0 });
29
+ conn.nick = 'alice';
30
+ conn.user = 'alice';
31
+ conn.host = 'example.com';
32
+ conn.realname = 'Alice';
33
+ conn.registration = 'registered';
34
+ return conn;
35
+ }
36
+
37
+ function ctxFor(conn: ConnectionState, clock = new FakeClock(1_000)) {
38
+ return buildCtx({
39
+ serverConfig: baseServerConfig,
40
+ clock,
41
+ ids: new SequentialIdFactory(),
42
+ motd: EmptyMotdProvider,
43
+ connection: conn,
44
+ });
45
+ }
46
+
47
+ const motd = (): { command: string; params: string[]; tags: Record<string, string> } => ({
48
+ command: 'MOTD',
49
+ params: [],
50
+ tags: {},
51
+ });
52
+
53
+ const ping = (): { command: string; params: string[]; tags: Record<string, string> } => ({
54
+ command: 'PING',
55
+ params: ['tok'],
56
+ tags: {},
57
+ });
58
+
59
+ /** Counts Disconnect effects across one reducer invocation. */
60
+ function isDisconnect(out: { effects: EffectType[] }): boolean {
61
+ return out.effects.some((e) => e.tag === 'Disconnect');
62
+ }
63
+
64
+ describe('resolveFloodControl', () => {
65
+ it('returns null when flood control is explicitly disabled', () => {
66
+ expect(resolveFloodControl({ ...baseServerConfig, floodControl: null })).toBeNull();
67
+ });
68
+
69
+ it('applies the documented default bucket (with control-plane exemption) when the field is omitted', () => {
70
+ const fc = resolveFloodControl(baseServerConfig);
71
+ expect(fc).not.toBeNull();
72
+ expect(fc?.capacity).toBe(10);
73
+ expect(fc?.refillRatePerSecond).toBe(1);
74
+ expect(fc?.disconnectThreshold).toBe(0);
75
+ expect(typeof fc?.commandCost).toBe('function');
76
+ });
77
+
78
+ it('honours caller-supplied thresholds and attaches the default cost when none is supplied', () => {
79
+ const fc = resolveFloodControl({
80
+ ...baseServerConfig,
81
+ floodControl: { capacity: 7, refillRatePerSecond: 2, disconnectThreshold: 1 },
82
+ });
83
+ expect(fc?.capacity).toBe(7);
84
+ expect(fc?.refillRatePerSecond).toBe(2);
85
+ expect(fc?.disconnectThreshold).toBe(1);
86
+ expect(typeof fc?.commandCost).toBe('function');
87
+ // PING is in the exempt set under the default cost.
88
+ expect(fc?.commandCost?.('PING')).toBe(0);
89
+ expect(fc?.commandCost?.('PRIVMSG')).toBe(1);
90
+ });
91
+
92
+ it('preserves a caller-supplied commandCost verbatim', () => {
93
+ const custom: FloodControlConfig['commandCost'] = () => 5;
94
+ const fc = resolveFloodControl({
95
+ ...baseServerConfig,
96
+ floodControl: {
97
+ capacity: 7,
98
+ refillRatePerSecond: 2,
99
+ disconnectThreshold: 1,
100
+ commandCost: custom,
101
+ },
102
+ });
103
+ expect(fc?.commandCost).toBe(custom);
104
+ });
105
+ });
106
+
107
+ describe('buildRoutedReducers', () => {
108
+ it('exposes every routed command in the map', () => {
109
+ const reducers = buildRoutedReducers(baseServerConfig);
110
+ expect(Object.keys(reducers).sort()).toEqual(
111
+ [
112
+ 'authenticate',
113
+ 'away',
114
+ 'cap',
115
+ 'channelMode',
116
+ 'invite',
117
+ 'join',
118
+ 'kick',
119
+ 'motd',
120
+ 'names',
121
+ 'nick',
122
+ 'noticeChannel',
123
+ 'noticeUser',
124
+ 'part',
125
+ 'pass',
126
+ 'ping',
127
+ 'pong',
128
+ 'privmsgChannel',
129
+ 'privmsgUser',
130
+ 'quit',
131
+ 'topic',
132
+ 'user',
133
+ 'userMode',
134
+ ].sort(),
135
+ );
136
+ });
137
+
138
+ it('wraps reducers so a non-exempt burst past capacity trips Disconnect (default config)', () => {
139
+ const reducers = buildRoutedReducers(baseServerConfig);
140
+ const conn = makeConn();
141
+ const ctx = ctxFor(conn);
142
+
143
+ let disconnects = 0;
144
+ // MOTD is a charged command (cost 1); capacity 10 / threshold 0 ⇒ 10
145
+ // pass, the 11th onward disconnect.
146
+ for (let i = 0; i < 20; i++) {
147
+ const out = reducers.motd(conn, motd(), ctx);
148
+ if (isDisconnect(out)) disconnects += 1;
149
+ }
150
+ expect(disconnects).toBe(10);
151
+ });
152
+
153
+ it('exempts control-plane traffic (PING) so a pure PING burst never disconnects', () => {
154
+ const reducers = buildRoutedReducers(baseServerConfig);
155
+ const conn = makeConn();
156
+ const ctx = ctxFor(conn);
157
+
158
+ let disconnects = 0;
159
+ for (let i = 0; i < 50; i++) {
160
+ const out = reducers.ping(conn, ping(), ctx);
161
+ if (isDisconnect(out)) disconnects += 1;
162
+ }
163
+ expect(disconnects).toBe(0);
164
+ });
165
+
166
+ it('returns raw (unwrapped) reducers when flood control is disabled', () => {
167
+ const reducers = buildRoutedReducers({ ...baseServerConfig, floodControl: null });
168
+ const conn = makeConn();
169
+ const ctx = ctxFor(conn);
170
+
171
+ let disconnects = 0;
172
+ for (let i = 0; i < 20; i++) {
173
+ const out = reducers.motd(conn, motd(), ctx);
174
+ if (isDisconnect(out)) disconnects += 1;
175
+ }
176
+ // Disabled → the bucket is never consulted; every MOTD reaches the reducer.
177
+ expect(disconnects).toBe(0);
178
+ // And the connection never even acquired a bucket.
179
+ expect(conn.floodBucket).toBeUndefined();
180
+ });
181
+
182
+ it('uses the same Clock source the ctx carries (no ambient time)', () => {
183
+ const reducers = buildRoutedReducers(baseServerConfig);
184
+ const conn = makeConn();
185
+ const clock = new FakeClock(42_000);
186
+ const ctx = ctxFor(conn, clock);
187
+
188
+ reducers.motd(conn, motd(), ctx);
189
+ // The wrapper stamps the bucket's lastRefill with the injected clock.
190
+ expect(conn.floodBucket?.lastRefill).toBe(42_000);
191
+ });
192
+ });
193
+
194
+ describe('buildRoutedReducers — type shape', () => {
195
+ it('returns a readonly map typed as RoutedReducers', () => {
196
+ const reducers: RoutedReducers = buildRoutedReducers(baseServerConfig);
197
+ // Touching each field confirms the public type is usable as documented.
198
+ expect(reducers.ping).toBeTypeOf('function');
199
+ expect(reducers.privmsgChannel).toBeTypeOf('function');
200
+ });
201
+ });
@@ -0,0 +1,32 @@
1
+ # @serverless-ircd/irc-test-support
2
+
3
+ Shared IRC scenario suite + harness seam used by every adapter's integration tests.
4
+
5
+ ## Purpose
6
+
7
+ PLAN §8 calls for the same scenario suite to run against every runtime
8
+ implementation (in-memory, Cloudflare Workers, AWS Lambda + DynamoDB).
9
+ This package owns:
10
+
11
+ - `IrcHarnessFactory` / `IrcHarness` / `ClientHarness` ports — the seam
12
+ each adapter implements.
13
+ - `runIrcScenarios(factories)` — registers the 31 Phase 2 scenarios as a
14
+ `describe` block per factory.
15
+ - `inMemoryHarnessFactory` — the reference factory used to keep the suite
16
+ honest; also useful as a template when implementing a new adapter.
17
+ - `HARNESS_SERVER_CONFIG` / `HARNESS_MOTD_LINES` — the constants scenario
18
+ bodies assert against.
19
+
20
+ ## Adding a runtime
21
+
22
+ 1. Implement `IrcHarnessFactory` in your adapter package.
23
+ 2. From a vitest test file, call `runIrcScenarios([yourFactory])`.
24
+
25
+ See `packages/cf-adapter/tests/cf-harness.ts` and
26
+ `packages/aws-adapter/tests/aws-harness.ts` for examples.
27
+
28
+ ## Scripts
29
+
30
+ - `pnpm --filter @serverless-ircd/irc-test-support test` — runs the smoke
31
+ test plus the in-memory scenario matrix.
32
+ - `pnpm --filter @serverless-ircd/irc-test-support build` — emits `dist/`.
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@serverless-ircd/irc-test-support",
3
+ "version": "0.2.0",
4
+ "private": true,
5
+ "description": "Shared IRC scenario runner + harness seam used by every adapter's integration suite",
6
+ "license": "BSD-3-Clause",
7
+ "type": "module",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.build.json",
18
+ "typecheck": "tsc -p tsconfig.test.json --noEmit",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "coverage": "vitest run --coverage",
22
+ "clean": "rimraf dist coverage .tsbuildinfo .turbo"
23
+ },
24
+ "files": ["dist", "src", "README.md"],
25
+ "dependencies": {
26
+ "@serverless-ircd/irc-core": "workspace:*",
27
+ "@serverless-ircd/irc-server": "workspace:*"
28
+ },
29
+ "devDependencies": {
30
+ "@serverless-ircd/in-memory-runtime": "workspace:*",
31
+ "@types/node": "^26.1.1",
32
+ "@vitest/coverage-v8": "^4.1.0",
33
+ "rimraf": "^6.0.0",
34
+ "typescript": "^5.6.0",
35
+ "vitest": "^4.1.0"
36
+ }
37
+ }
@@ -57,7 +57,7 @@ export const HARNESS_SERVER_CONFIG = {
57
57
  * command after registration reads these lines via the {@link MotdProvider}
58
58
  * port. Tests assert against this content (scenario: MOTD).
59
59
  */
60
- const HARNESS_MOTD_LINES = [
60
+ export const HARNESS_MOTD_LINES = [
61
61
  'Welcome to the ServerlessIRCd integration-test harness.',
62
62
  'All scenarios run parametrized over the IrcRuntime factory matrix.',
63
63
  ];
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @serverless-ircd/irc-test-support
3
+ *
4
+ * Shared IRC scenario suite + harness seam. Every adapter package
5
+ * (`cf-adapter`, `aws-adapter`, …) registers its own
6
+ * {@link IrcHarnessFactory} and re-runs the same scenarios under its
7
+ * native runtime, keeping the runtime contract (PLAN §8) honest: the
8
+ * same tests pass in-memory, against workerd, and against
9
+ * dynamodb-local.
10
+ */
11
+
12
+ export type {
13
+ ClientHarness,
14
+ IrcHarness,
15
+ IrcHarnessFactory,
16
+ SpawnClientOptions,
17
+ } from './harness.js';
18
+ export {
19
+ HARNESS_MOTD_LINES,
20
+ HARNESS_SERVER_CONFIG,
21
+ InMemoryHarness,
22
+ inMemoryHarnessFactory,
23
+ } from './in-memory-harness.js';
24
+ export { runIrcScenarios } from './scenarios.js';
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  import { describe, expect, it } from 'vitest';
11
- import { InMemoryHarness, inMemoryHarnessFactory } from './in-memory-harness.js';
11
+ import { InMemoryHarness, inMemoryHarnessFactory } from '../src/index.js';
12
12
 
13
13
  describe('inMemoryHarnessFactory', () => {
14
14
  it('exposes a stable name for the describe.each matrix', () => {
@@ -13,8 +13,7 @@
13
13
  * requires only registering a factory").
14
14
  */
15
15
 
16
- import { inMemoryHarnessFactory } from './in-memory-harness.js';
17
- import { runIrcScenarios } from './scenarios.js';
16
+ import { inMemoryHarnessFactory, runIrcScenarios } from '../src/index.js';
18
17
 
19
18
  /** The runtime matrix. Adding a runtime = appending one entry here. */
20
19
  runIrcScenarios([inMemoryHarnessFactory]);
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Smoke test for the extracted `@serverless-ircd/irc-test-support` package.
3
+ *
4
+ * Confirms the in-memory factory still runs the full scenario suite after
5
+ * the move out of `cf-adapter/tests/integration`. Adapter packages import
6
+ * the same surface to register their own factories.
7
+ */
8
+
9
+ import { inMemoryHarnessFactory, runIrcScenarios } from '../src/index.js';
10
+
11
+ runIrcScenarios([inMemoryHarnessFactory]);
@@ -0,0 +1,16 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "composite": true,
5
+ "rootDir": "./src",
6
+ "outDir": "./dist",
7
+ "tsBuildInfoFile": "./.tsbuildinfo",
8
+ "types": ["node"]
9
+ },
10
+ "include": ["src/**/*.ts"],
11
+ "exclude": ["dist", "tests", "**/*.test.ts"],
12
+ "references": [
13
+ { "path": "../irc-core/tsconfig.build.json" },
14
+ { "path": "../irc-server/tsconfig.build.json" }
15
+ ]
16
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "noEmit": true,
5
+ "types": ["node"]
6
+ },
7
+ "include": ["src/**/*.ts", "tests/**/*.ts"],
8
+ "exclude": ["dist"],
9
+ "references": [
10
+ { "path": "../irc-core/tsconfig.build.json" },
11
+ { "path": "../irc-server/tsconfig.build.json" },
12
+ { "path": "../in-memory-runtime/tsconfig.build.json" }
13
+ ]
14
+ }
@@ -0,0 +1,20 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ include: ['tests/**/*.test.ts'],
6
+ coverage: {
7
+ provider: 'v8',
8
+ all: false,
9
+ include: ['src/**/*.ts'],
10
+ exclude: ['src/**/index.ts'],
11
+ reporter: ['text', 'html', 'json-summary'],
12
+ thresholds: {
13
+ lines: 90,
14
+ functions: 90,
15
+ branches: 90,
16
+ statements: 90,
17
+ },
18
+ },
19
+ },
20
+ });
package/progress.md ADDED
@@ -0,0 +1,107 @@
1
+ # ServerlessIRCd — Progress
2
+
3
+ Tickets derived from `PLAN.md` §7 (Phase 0 already complete). See
4
+ `tickets.md` for full detail.
5
+
6
+ | Status | Ticket | Points | Title |
7
+ |:------:|:-------|-------:|:------|
8
+ | ✅ | — | — | **Phase 0 — Foundation (DONE before ticketing)** |
9
+ | ✅ | TICKET-001 | 5 | Core state shapes, `Effect` taxonomy, `dispatch` interpreter |
10
+ | ✅ | TICKET-002 | 1 | `PING` / `PONG` reducer |
11
+ | ✅ | TICKET-003 | 2 | `QUIT` reducer |
12
+ | ✅ | TICKET-004 | 5 | Registration reducers: `NICK` / `USER` / `PASS` (core) |
13
+ | ✅ | TICKET-005 | 5 | `JOIN` reducer |
14
+ | ✅ | TICKET-006 | 2 | `PART` reducer |
15
+ | ✅ | TICKET-007 | 3 | `PRIVMSG` / `NOTICE` reducer (channel + private) |
16
+ | ✅ | TICKET-008 | 3 | `TOPIC` reducer |
17
+ | ✅ | TICKET-009 | 2 | `NAMES` reducer |
18
+ | ✅ | TICKET-010 | 1 | `MOTD` reducer |
19
+ | ✅ | TICKET-011 | 8 | `MODE` reducer (channel + user) |
20
+ | ✅ | TICKET-012 | 3 | `KICK` reducer |
21
+ | ✅ | TICKET-013 | 3 | `INVITE` reducer |
22
+ | ✅ | TICKET-014 | 3 | `WHO` reducer |
23
+ | ✅ | TICKET-015 | 3 | `WHOIS` reducer |
24
+ | ✅ | TICKET-016 | 2 | `LIST` reducer |
25
+ | ✅ | TICKET-017 | 3 | IRCv3 `CAP` negotiation (`LS`/`REQ`/`ACK`/`NAK`/`END`/`LIST`/`NEW`/`DEL`) |
26
+ | ✅ | TICKET-018 | 3 | IRCv3 `message-tags` + `server-time` |
27
+ | ✅ | TICKET-019 | 1 | IRCv3 `echo-message` |
28
+ | ✅ | TICKET-020 | 2 | IRCv3 `multi-prefix` |
29
+ | ✅ | TICKET-021 | 1 | IRCv3 `extended-join` |
30
+ | ✅ | TICKET-022 | 2 | IRCv3 `chghost` |
31
+ | ✅ | TICKET-023 | 2 | IRCv3 `away-notify` |
32
+ | ✅ | TICKET-024 | 1 | IRCv3 `invite-notify` |
33
+ | ✅ | TICKET-025 | 5 | IRCv3 `batch` |
34
+ | ✅ | TICKET-026 | 5 | SASL authentication (`PLAIN` via `AccountStore`, `EXTERNAL` stubbed) |
35
+ | ✅ | TICKET-027 | 2 | `isupport` (`005`) generator |
36
+ | ✅ | TICKET-028 | 3 | Flood control (token bucket) as pure reducer wrapper |
37
+ | ✅ | TICKET-029 | 5 | Define `IrcRuntime` port; implement `in-memory-runtime` |
38
+ | ✅ | TICKET-030 | 5 | `ConnectionActor`: bytes → framed messages → reducer → dispatch |
39
+ | ✅ | TICKET-031 | 3 | `apps/local-cli`: runnable WebSocket server using in-memory runtime |
40
+ | ✅ | TICKET-032 | 5 | Parametrized integration test suite over `IrcRuntime` |
41
+ | ✅ | TICKET-033 | 8 | `ConnectionDO` (hibernatable WS), state migration, alarms |
42
+ | ✅ | TICKET-034 | 5 | `RegistryDO` with sharding plan |
43
+ | ✅ | TICKET-035 | 8 | `ChannelDO` (roster + modes + fanout via ConnectionDO stubs) |
44
+ | ✅ | TICKET-036 | 5 | `CfRuntime` implementing `IrcRuntime` |
45
+ | ✅ | TICKET-037 | 3 | Cloudflare deploy pipeline (wrangler), staging env, smoke e2e |
46
+ | ✅ | TICKET-038 | 1 | Docs: `docs/deployment-cf.md` |
47
+ | ✅ | TICKET-039 | 8 | CDK stack: APIGW WebSocket, Lambda, DynamoDB tables, IAM |
48
+ | ✅ | TICKET-040 | 8 | `$connect` / `$disconnect` / `$default` handlers + `AwsRuntime` |
49
+ | ✅ | TICKET-041 | 5 | DynamoDB transactions for membership; gone-connection sweep |
50
+ | ✅ | TICKET-042 | 3 | EventBridge idle / PING timers |
51
+ | ✅ | TICKET-043 | 3 | AWS deploy pipeline (CDK), staging env, smoke e2e |
52
+ | ✅ | TICKET-044 | 1 | Docs: `docs/deployment-aws.md` |
53
+ | ✅ | TICKET-045 | 3 | Observability: structured logs, trace IDs, dashboards |
54
+ | ✅ | TICKET-046 | 5 | Security: server password, SASL, cloaking, rate limits, caps |
55
+ | ✅ | TICKET-047 | 2 | Config: MOTD, server/network name, channel prefixes, max clients, oper creds |
56
+ | ✅ | TICKET-048 | 3 | CI hardening: coverage gate ≥90%, contract tests on every PR, mutation testing |
57
+ | ⬜ | TICKET-049 | 5 | Load test: 10k concurrent connections per platform |
58
+ | ⬜ | TICKET-050 | 3 | Compatibility sweep: WeeChat, HexChat, IRCCloud, TheLounge, matrix-IRC bridge |
59
+ | ⬜ | TICKET-051 | 2 | ADRs for irreversible decisions |
60
+ | ⬜ | — | — | **Phase 7 — TLS hardening & raw TCP transport (v1.x)** |
61
+ | ⬜ | TICKET-052 | 2 | Revise transport decision: dual wss + irc+tls (ADR); update PLAN §3/§9 |
62
+ | ⬜ | TICKET-053 | 3 | Generalize `ConnectionActor` transport seam (TCP byte-stream framing) |
63
+ | ⬜ | TICKET-054 | 5 | mTLS support: edge config + `AccountStore` plumbing (enables SASL EXTERNAL) |
64
+ | ⬜ | TICKET-055 | 8 | CF TCP+TLS adapter via Cloudflare Spectrum + Container origin |
65
+ | ⬜ | TICKET-056 | 8 | AWS TCP+TLS adapter via Network Load Balancer (TLS) + Lambda streaming |
66
+ | ⬜ | TICKET-057 | 3 | Extend integration contract suite over transport (wss × TCP+TLS) |
67
+ | ⬜ | TICKET-058 | 2 | Docs: `deployment-cf-tcp.md` and `deployment-aws-tcp.md` (TCP+TLS variants) |
68
+ | ✅ | TICKET-059 | 2 | Wire `WHOIS` into the `ConnectionActor` dispatcher |
69
+ | ✅ | TICKET-060 | 1 | Wire `AUTHENTICATE` into the `ConnectionActor` dispatcher |
70
+ | ✅ | TICKET-061 | 1 | Wire `AWAY` into the `ConnectionActor` dispatcher |
71
+ | ✅ | TICKET-062 | 1 | IRCv3 `safelist` capability + `SAFELIST` ISUPPORT token |
72
+ | ✅ | TICKET-063 | 8 | IRCv3 `draft/chathistory` playback (bouncer-style backlog on JOIN + explicit query) |
73
+ | ✅ | TICKET-064 | 2 | Flood control: per-command cost + control-plane exemption |
74
+ | ✅ | TICKET-065 | 3 | Wire `wrapWithFloodControl` into the `ConnectionActor` |
75
+ | ✅ | TICKET-066 | 3 | CDK `environmentName` parameter + prefixed resource names |
76
+ | ✅ | TICKET-067 | 1 | Fix MOTD parsing mismatch (CDK default vs config loader) |
77
+ | ✅ | TICKET-068 | 2 | Make hardcoded stack identity vars configurable via construct props |
78
+
79
+ | ✅ | TICKET-069 | 3 | Wire `CHATHISTORY` into the `ConnectionActor` dispatcher + `MessageStore` plumbing |
80
+ | ⬜ | — | — | **Phase 8 — PLAN-FIXES Remediation** |
81
+ | ✅ | TICKET-070 | 5 | Wire `MessageStore` into all three adapters (chathistory end-to-end) |
82
+ | ⬜ | TICKET-071 | 8 | Wire `AccountStore` into the `ConnectionActor` (SASL PLAIN end-to-end) |
83
+ | ⬜ | TICKET-072 | 8 | Decide fate of AWS `Accounts` DynamoDB table (implement AccountStore or remove) |
84
+ | ⬜ | TICKET-073 | 13 | Cloudflare runtime: implement stubbed `getConnection`/`getChannelConnections`/`listChannels`/cross-connection `disconnect` |
85
+ | ⬜ | TICKET-074 | 5 | AWS disconnect: broadcast QUIT to peers |
86
+ | ⬜ | TICKET-075 | 5 | Implement `OPER` command (verify creds, set oper mode, emit `381`) |
87
+ | ⬜ | TICKET-076 | 3 | Implement `TAGMSG` command (IRCv3 `message-tags` recorder) |
88
+ | ⬜ | TICKET-077 | 5 | Implement `WHOWAS` (or drop the numerics) |
89
+ | ⬜ | TICKET-078 | 5 | Implement (or drop) remaining standard IRC verbs |
90
+ | ⬜ | TICKET-079 | 2 | SASL `EXTERNAL`: implement mTLS-backed auth OR stop advertising |
91
+ | ⬜ | TICKET-080 | 2 | Welcome MOTD: drive from `MotdProvider` instead of hardcoded placeholder |
92
+ | ⬜ | TICKET-081 | 3 | Registration placeholders: drive `SERVER_VERSION`/`CREATED_TEXT` from `ServerConfig` |
93
+ | ⬜ | TICKET-082 | 3 | Require `serverName` at adapter config-load (drop `irc.example.com` placeholder) |
94
+ | ⬜ | TICKET-083 | 5 | AWS `handleConnect`: implement max-clients admission gating |
95
+ | ⬜ | TICKET-084 | 1 | AWS Lambda transport `disconnect` no-op: document the platform limit |
96
+ | ⬜ | TICKET-085 | 2 | CF `webSocketMessage` channel-registration: wire ChannelDO registration OR remove dead loop |
97
+ | ⬜ | TICKET-086 | 2 | Remove (or wire) dead lookup Effects: `LookupNick`, `GetConnectionInfo`, `GetChannelSnapshot` |
98
+ | ⬜ | TICKET-087 | 2 | CF outbound batching: implement OR drop the reserved optimization |
99
+ | ⬜ | TICKET-088 | 2 | Update stale doc-comments advertising DOs as stubbed |
100
+ | ⬜ | TICKET-089 | 5 | Implement rfc1459 case-mapping (replace ad-hoc `toLowerCase`) |
101
+ | ⬜ | TICKET-090 | 1 | Remove unused `_disabledRecordId()` helper |
102
+ | ⬜ | TICKET-091 | 1 | Update stale CLI doc comment ("fuller config-loader lands in a later ticket") |
103
+ | ⬜ | TICKET-092 | 1 | Update stale release-process doc (90% coverage gate IS wired in CI) |
104
+
105
+ **Totals:** 92 tickets · 322 points
106
+
107
+ **Status legend:** ⬜ pending · 🔄 in-progress · ✅ done · 🚫 blocked