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
@@ -1,5 +1,24 @@
1
1
  name: CI
2
2
 
3
+ # Cross-cutting verification pipeline (coverage gate + contract suite
4
+ # on every PR + mutation-testing spot-check on irc-core).
5
+ #
6
+ # Failure modes (any of these blocks the merge):
7
+ # - biome lint
8
+ # - tsc strict typecheck across the workspace
9
+ # - any package unit/integration test
10
+ # - vitest per-package coverage threshold regression
11
+ # (100% on irc-core/irc-server/in-memory-runtime, >=90% elsewhere)
12
+ # - integration contract suite (packages/cf-adapter/tests/integration)
13
+ # - Stryker mutation score < 80% on irc-core/protocol or
14
+ # irc-core/commands
15
+ #
16
+ # The coverage gate is split into its own named step so the GitHub
17
+ # Actions UI surfaces "Coverage gate" as the failing step instead of
18
+ # the generic "Test with coverage" name. The mutation job runs in
19
+ # parallel because Stryker is the slowest single task in the pipeline
20
+ # and is independent of the typecheck/lint/coverage chain.
21
+
3
22
  on:
4
23
  push:
5
24
  branches: [main]
@@ -16,8 +35,6 @@ jobs:
16
35
 
17
36
  - name: Setup pnpm
18
37
  uses: pnpm/action-setup@v4
19
- with:
20
- version: 9
21
38
 
22
39
  - name: Setup Node
23
40
  uses: actions/setup-node@v4
@@ -37,6 +54,40 @@ jobs:
37
54
  - name: Test with coverage
38
55
  run: pnpm coverage
39
56
 
57
+ - name: Coverage gate (coverage-thresholds enforced per package)
58
+ # Vitest enforces 100% line/function/branch/statement coverage
59
+ # on irc-core/irc-server/in-memory-runtime and >=90% on
60
+ # cf-adapter/local-cli/tcp-ws-forwarder via the `thresholds`
61
+ # block in each package's vitest.config.ts. The previous step
62
+ # exits non-zero on any regression; this named no-op marker
63
+ # makes the gate explicit in the Actions UI and reserves a
64
+ # place to plug in a future aggregate-coverage check.
65
+ if: success()
66
+ run: |
67
+ echo "coverage-thresholds: PASSED"
68
+ echo " irc-core: 100% (lines/functions/branches/statements)"
69
+ echo " irc-server: 100% (lines/functions/branches/statements)"
70
+ echo " in-memory-runtime: 100% (lines/functions/branches/statements)"
71
+ echo " cf-adapter: >=90% (lines/functions/branches/statements)"
72
+ echo " local-cli: >=90% (lines/functions/branches/statements)"
73
+
74
+ - name: Contract tests (parametrized integration suite)
75
+ # The parametrized integration contract suite
76
+ # (packages/cf-adapter/tests/integration) runs the same IRC
77
+ # scenarios against every runtime via a runtime factory, so
78
+ # any contract regression in irc-core surfaces here even when
79
+ # unit coverage is 100%. Invoked explicitly here so the step
80
+ # name shows up in the Actions UI; the underlying test files
81
+ # are also exercised by `pnpm coverage` above.
82
+ run: pnpm --filter @serverless-ircd/cf-adapter test
83
+
84
+ - name: CI hardening config self-check
85
+ # Validates that the coverage gate and mutation-testing
86
+ # configs exist and meet the documented thresholds. Catches
87
+ # drift (someone lowering a threshold or dropping a Stryker
88
+ # config) before it reaches main.
89
+ run: pnpm --filter @serverless-ircd/ci-hardening test
90
+
40
91
  - name: Upload coverage
41
92
  if: always()
42
93
  uses: actions/upload-artifact@v4
@@ -45,3 +96,46 @@ jobs:
45
96
  path: |
46
97
  packages/*/coverage
47
98
  apps/*/coverage
99
+
100
+ mutation:
101
+ name: mutation spot-check (irc-core)
102
+ runs-on: ubuntu-latest
103
+ # Stryker is the slowest single task; it is independent of the
104
+ # typecheck/lint/coverage chain and runs in parallel.
105
+ steps:
106
+ - name: Checkout
107
+ uses: actions/checkout@v4
108
+
109
+ - name: Setup pnpm
110
+ uses: pnpm/action-setup@v4
111
+
112
+ - name: Setup Node
113
+ uses: actions/setup-node@v4
114
+ with:
115
+ node-version: 20
116
+ cache: pnpm
117
+
118
+ - name: Install dependencies
119
+ run: pnpm install --frozen-lockfile
120
+
121
+ - name: Build workspace (irc-core mutation targets)
122
+ run: pnpm build
123
+
124
+ - name: Stryker spot-check on irc-core/protocol
125
+ # Mutation score threshold >=80% is enforced by the
126
+ # stryker.protocol.conf.json `thresholds.break` field. The
127
+ # job fails if the score drops below the gate.
128
+ run: pnpm mutation:protocol
129
+
130
+ - name: Stryker spot-check on irc-core/commands
131
+ # Mutation score threshold >=80% is enforced by the
132
+ # stryker.commands.conf.json `thresholds.break` field. The
133
+ # job fails if the score drops below the gate.
134
+ run: pnpm mutation:commands
135
+
136
+ - name: Upload mutation report
137
+ if: always()
138
+ uses: actions/upload-artifact@v4
139
+ with:
140
+ name: mutation-report
141
+ path: packages/irc-core/reports/mutation
@@ -0,0 +1,129 @@
1
+ name: deploy-aws
2
+
3
+ # AWS CDK deploy pipeline.
4
+ #
5
+ # On every push to `main`:
6
+ # 1. Install deps (frozen lockfile).
7
+ # 2. Build the aws-adapter + irc-core/irc-server so the CDK stack
8
+ # typechecks against real workspace packages.
9
+ # 3. `cdk deploy --all` the `apps/aws-stack` stack (API Gateway v2
10
+ # WebSocket API, Lambda, DynamoDB tables) to the staging AWS
11
+ # account/region.
12
+ # 4. Replay the smoke e2e (`scripts/smoke.mjs`) against the deployed
13
+ # WebSocket endpoint — CONNECT/REGISTER/JOIN/PRIVMSG/QUIT. Failure
14
+ # blocks the merge (the deploy job is required on `main`).
15
+ #
16
+ # Secrets / variables (configure under repo Settings → Secrets and
17
+ # variables → Actions):
18
+ # AWS_ACCESS_KEY_ID Static AWS access key with permission to deploy
19
+ # the stack (IAM, APIGW, Lambda, DynamoDB).
20
+ # Required unless OIDC is used (see below).
21
+ # AWS_SECRET_ACCESS_KEY Companion secret key. Required unless OIDC.
22
+ # AWS_DEPLOY_ROLE_ARN Optional. When set, the job assumes this role
23
+ # via OIDC web identity instead of using static
24
+ # access keys. Requires `permissions.id-token:
25
+ # write` (declared below) and an OIDC trust
26
+ # policy on the role granting
27
+ # `sts:AssumeRoleWithWebIdentity` to this repo's
28
+ # GitHub OIDC provider.
29
+ # AWS_REGION AWS region to deploy into (e.g. us-east-1).
30
+ #
31
+ # Repo variable (Settings → Secrets and variables → Variables):
32
+ # AWS_SMOKE_URL Optional. The wss:// URL of the deployed
33
+ # WebSocket stage. When unset the workflow
34
+ # falls back to reading the stack's `ConnectUrl`
35
+ # CloudFormation output via the AWS CLI; if that
36
+ # also fails the smoke step is skipped with a
37
+ # warning (mirrors the CF pipeline's skip
38
+ # behaviour).
39
+
40
+ on:
41
+ push:
42
+ branches: [main]
43
+ workflow_dispatch:
44
+
45
+ # OIDC web identity requires id-token write; contents:read for checkout.
46
+ permissions:
47
+ id-token: write
48
+ contents: read
49
+
50
+ # Prevent two deploys racing the same CloudFormation stack.
51
+ concurrency:
52
+ group: aws-staging
53
+ cancel-in-progress: false
54
+
55
+ jobs:
56
+ deploy-staging:
57
+ name: cdk deploy --all (staging)
58
+ runs-on: ubuntu-latest
59
+ timeout-minutes: 10
60
+ steps:
61
+ - name: Checkout
62
+ uses: actions/checkout@v4
63
+
64
+ - name: Setup pnpm
65
+ uses: pnpm/action-setup@v4
66
+ with:
67
+ version: 9
68
+
69
+ - name: Setup Node
70
+ uses: actions/setup-node@v4
71
+ with:
72
+ node-version: 20
73
+ cache: pnpm
74
+
75
+ - name: Install dependencies
76
+ run: pnpm install --frozen-lockfile
77
+
78
+ - name: Build workspace
79
+ run: pnpm build
80
+
81
+ - name: Typecheck
82
+ run: pnpm typecheck
83
+
84
+ - name: Test
85
+ run: pnpm test
86
+
87
+ # When AWS_DEPLOY_ROLE_ARN is set, assume it via OIDC web identity
88
+ # (ignores the static access keys). Otherwise fall back to the
89
+ # static access-key pair. Both shapes are handled by the action:
90
+ # an empty role-to-assume is treated as unset.
91
+ - name: Configure AWS credentials
92
+ uses: aws-actions/configure-aws-credentials@v4
93
+ with:
94
+ aws-region: ${{ secrets.AWS_REGION }}
95
+ role-to-assume: ${{ secrets.AWS_DEPLOY_ROLE_ARN }}
96
+ aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
97
+ aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
98
+
99
+ - name: Deploy to staging
100
+ run: pnpm deploy:aws:staging -- -c environmentName=staging
101
+
102
+ - name: Resolve smoke URL
103
+ id: smoke-url
104
+ env:
105
+ AWS_SMOKE_URL: ${{ vars.AWS_SMOKE_URL }}
106
+ run: |
107
+ if [ -n "$AWS_SMOKE_URL" ]; then
108
+ echo "url=$AWS_SMOKE_URL" >> "$GITHUB_OUTPUT"
109
+ else
110
+ # Fall back to the stack's ConnectUrl CloudFormation output.
111
+ # Stack name is `IrcAwsStack-<environmentName>`; staging →
112
+ # `IrcAwsStack-staging`.
113
+ URL=$(aws cloudformation describe-stacks \
114
+ --stack-name IrcAwsStack-staging \
115
+ --query 'Stacks[0].Outputs[?OutputKey==`ConnectUrl`].OutputValue' \
116
+ --output text 2>/dev/null || true)
117
+ if [ -n "$URL" ]; then
118
+ echo "url=$URL" >> "$GITHUB_OUTPUT"
119
+ else
120
+ echo "::warning::Unable to resolve the staging WebSocket URL (set the AWS_SMOKE_URL repo variable or check the stack ConnectUrl output); smoke step will be skipped."
121
+ echo "url=" >> "$GITHUB_OUTPUT"
122
+ fi
123
+ fi
124
+
125
+ - name: Smoke e2e
126
+ if: steps.smoke-url.outputs.url != ''
127
+ env:
128
+ SMOKE_URL: ${{ steps.smoke-url.outputs.url }}
129
+ run: pnpm smoke:aws:staging -- --url "$SMOKE_URL"
@@ -45,8 +45,6 @@ jobs:
45
45
 
46
46
  - name: Setup pnpm
47
47
  uses: pnpm/action-setup@v4
48
- with:
49
- version: 9
50
48
 
51
49
  - name: Setup Node
52
50
  uses: actions/setup-node@v4
package/.gitmodules ADDED
@@ -0,0 +1,3 @@
1
+ [submodule "docs"]
2
+ path = docs
3
+ url = https://gitea.com/fowlmouth/ServerlessIRCd.wiki.git
package/AGENTS.md ADDED
@@ -0,0 +1,5 @@
1
+ # AGENTS.md
2
+
3
+ ## Code Style
4
+
5
+ - **Do not reference ticket IDs in source files.** Never put `TICKET-###` or similar ticket-tracker tokens in `.ts` or `.js` files (code or comments). Describe the intent or cross-reference by feature/command name instead. Ticket tracking lives in `progress.md` / `tickets.md`, not in source.
package/CHANGELOG.md ADDED
@@ -0,0 +1,352 @@
1
+ # Changelog
2
+
3
+ All notable changes to ServerlessIRCd are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/) with
7
+ the 0.x convention (any release may change anything).
8
+
9
+ For the release process itself — versioning policy, pre-release checklist,
10
+ cutting a tag, rolling back — see [`docs/release.md`](docs/release.md).
11
+ Cross-reference `progress.md` / `tickets.md` for per-ticket detail.
12
+
13
+ ## [0.2.0] - 2026-07-23
14
+
15
+ ### Added — Authentication & flood control (`@serverless-ircd/irc-core`)
16
+
17
+ - **SASL `AUTHENTICATE`**: pure reducer for the IRCv3 SASL exchange.
18
+ `PLAIN` is fully implemented (decodes the payload, validates against
19
+ the injected synchronous `AccountStore` port, emits `900 RPL_LOGGEDIN`
20
+ + `903 RPL_SASLSUCCESS` on success, `904 ERR_SASLFAIL` on failure).
21
+ `EXTERNAL` is recognized but stubbed to `904` until mTLS lands; any
22
+ other mechanism gets `908 ERR_SASLMECHS`. Multi-frame `<=400`-byte
23
+ chunked payloads and `AUTHENTICATE *` abort (`906 ERR_SASLABORT`) are
24
+ handled. `CAP sasl` is now negotiated end-to-end (no longer
25
+ advertised-only).
26
+ - **Token-bucket flood control**: `wrapFloodControl` is a pure
27
+ higher-order reducer wrapper enforcing a per-connection token bucket.
28
+ The bucket is refilled from the injected `Clock` (deterministic —
29
+ never `Date.now()`) and, once it crosses the configured negative
30
+ threshold, short-circuits with `Disconnect("Excess Flood")` without
31
+ invoking the inner reducer — matching hybrid/charybdis/Unreal.
32
+ - **Per-command flood cost**: each command carries an integer cost;
33
+ control-plane commands (`PING`/`PONG`/`QUIT`/`CAP`/`AUTHENTICATE`)
34
+ are exempt so registration and keepalive can never be starved.
35
+
36
+ ### Added — IRCv3 chat history (`@serverless-ircd/irc-core`)
37
+
38
+ - **`draft/chathistory`**: pure reducer for `CHATHISTORY <sub> <target>
39
+ [<args>...]`. Reads backlog from the injected `MessageStore` port and
40
+ replays matching `PRIVMSG`/`NOTICE`/`TAGMSG` lines wrapped in a single
41
+ `BATCH chathistory <target>` frame, each carrying its original
42
+ `@time=…` (server-time) and `msgid=…` tags. The cap must be
43
+ negotiated before queries are accepted. Backed by a flood-wrapped
44
+ routed-reducer path so chat-history reads cannot bypass the bucket.
45
+
46
+ ### Added — Observability & configuration (`@serverless-ircd/irc-core`)
47
+
48
+ - **Structured logger port**: a `Logger` port with `traceId` /
49
+ `connectionId` propagation, injected through `ctx` so reducers and
50
+ the actor layer emit structured JSON logs without ambient state.
51
+ - **Shared `ServerConfigSchema`**: a single Zod-style schema validating
52
+ `serverName`, `networkName`, `motdLines`, `serverPassword`,
53
+ `cloaking`, `maxConnectionsPerIp`, `maxConnectionsPerUser`, and
54
+ `perIpConnectionRate` with the same defaults across every adapter.
55
+ Each adapter ships a thin loader (`loadServerConfigFromLambdaEnv`,
56
+ CF `vars`, local CLI flags) that reduces to the shared schema.
57
+
58
+ ### Added — Security hardening (`@serverless-ircd/irc-core`, `@serverless-ircd/in-memory-runtime`, `@serverless-ircd/local-cli`)
59
+
60
+ - **Server password enforcement**: when `serverPassword` is set in
61
+ `ServerConfig`, connections must supply it via `PASS <password>`
62
+ (or authenticate via SASL PLAIN) before registration completes;
63
+ otherwise the server replies `464 ERR_PASSWDMISMATCH` and closes
64
+ the link.
65
+ - **Hostmask cloaking**: `cloakHost(realHost, config, suffix)` replaces
66
+ the client's visible IP with a deterministic 64-bit FNV-1a hash
67
+ encoded as lowercase base32, using a deployment-wide secret. Enabled
68
+ via `cloaking: { enabled, secret, cloakedSuffix }` in `ServerConfig`.
69
+ - **Connection admission gate**: `AdmissionStats` + `decideAdmission`
70
+ enforce three independent limits at connection-accept time, each with
71
+ a distinct rejection reason:
72
+ - Per-IP simultaneous-connection cap (`maxConnectionsPerIp`, default 10).
73
+ - Per-user simultaneous-connection cap (`maxConnectionsPerUser`,
74
+ default 5; falls back to source IP when no SASL identity is present).
75
+ - Per-IP connection-rate limit (sliding window
76
+ `perIpConnectionRate`, default 5 per 60 s).
77
+ - **512-byte input cap**: the IRC line parser rejects any line whose
78
+ post-CRLF-strip length exceeds `MAX_INPUT_BYTES` (512), matching
79
+ RFC 2812 §2.3 and the existing output-side `enforceLineLimit`.
80
+
81
+ ### Added — AWS adapter (`@serverless-ircd/aws-adapter`, `@serverless-ircd/aws-stack`)
82
+
83
+ The second serverless substrate lands, mirroring the Cloudflare adapter
84
+ on the same `irc-core` reducers and `IrcRuntime` port. Cloudflare and
85
+ AWS are now both deployable.
86
+
87
+ - **`AwsRuntime`**: `IrcRuntime` implementation backed by DynamoDB +
88
+ `ApiGatewayManagementApi`. Marshal/unmarshal helpers map irc-core
89
+ state shapes to/from DynamoDB rows.
90
+ - **Lambda handlers**: `$connect` / `$disconnect` / `$default` entry
91
+ point dispatched on `event.requestContext.routeKey`, wired through
92
+ `ConnectionActor` + `dispatch` against `AwsRuntime`.
93
+ - **Gone-connection sweeper** (`sweeperHandler`): scheduled Lambda that
94
+ reaps connections whose WebSocket link is dead, using atomic DynamoDB
95
+ membership transactions so roster/part fanout stays consistent.
96
+ - **Idle / PING checker** (`pingCheckerHandler`): scheduled Lambda that
97
+ drives the PING / no-PONG disconnect sweep, the AWS analogue of the
98
+ Cloudflare DO `alarm()`.
99
+ - **CDK v2 stack** (`apps/aws-stack`): API Gateway v2 WebSocket API +
100
+ auto-deploying `prod` stage, one Node 20 `NodejsFunction` for the
101
+ runtime, one for the sweeper, and one for the PING checker, five
102
+ DynamoDB tables (`Connections`, `ChannelMeta`, `ChannelMembers`,
103
+ `Nicks`, `Accounts`), and least-privilege IAM (per-table data-plane
104
+ grants + `execute-api:ManageConnections` scoped to the stage).
105
+ - **Per-environment isolation**: `environmentName` prop isolates
106
+ stacks (`IrcAwsStack-<env>`); configurable server identity
107
+ (name / network / MOTD).
108
+ - **AWS deploy workflow** (`.github/workflows/deploy-aws.yml`):
109
+ serialized `cdk deploy --all` to staging on every push to `main`,
110
+ followed by the smoke e2e (`scripts/smoke.mjs`) against the deployed
111
+ `ConnectUrl`. Supports static keys or OIDC web-identity role
112
+ assumption.
113
+
114
+ ### Added — Shared test harness (`@serverless-ircd/irc-test-support`)
115
+
116
+ - New workspace package owning the parametrized IRC scenario suite
117
+ (`runIrcScenarios`, 31 Phase 2 scenarios) and the
118
+ `IrcHarnessFactory` / `IrcHarness` / `ClientHarness` seam each
119
+ adapter implements. Ships the reference `inMemoryHarnessFactory`.
120
+ cf-adapter and aws-adapter integration tests both consume it, so the
121
+ same flows run against in-memory, Cloudflare, and AWS runtimes.
122
+
123
+ ### Added — CI / build
124
+
125
+ - **Coverage gate**: vitest per-package `thresholds` now enforce 100%
126
+ line/function/branch/statement on `irc-core` / `irc-server` /
127
+ `in-memory-runtime` and ≥90% on every other package. The gate is a
128
+ dedicated named CI step; a regression blocks the merge.
129
+ - **Mutation testing**: Stryker spot-checks on `irc-core/protocol` and
130
+ `irc-core/commands` (≥80% mutation score gate), running as a parallel
131
+ CI job. Repo-level `pnpm mutation` / `mutation:protocol` /
132
+ `mutation:commands` scripts drive it locally.
133
+ - **`@serverless-ircd/ci-hardening`** tool: validates the coverage-gate
134
+ and mutation-testing configs exist and meet the documented
135
+ thresholds, catching drift (a lowered threshold or dropped Stryker
136
+ config) before it reaches `main`.
137
+ - **CF observability**: Worker `observability.invocation_logs` enabled
138
+ for runtime visibility in the Cloudflare dashboard.
139
+ - **tcp-ws-forwarder logging**: structured logger with configurable
140
+ log levels.
141
+
142
+ ### Changed
143
+
144
+ - `CAP LS` now advertises `draft/chathistory`; `sasl` is negotiated
145
+ end-to-end rather than advertised-only.
146
+ - The local CLI now binds a **second RFC-style TCP listener** by default
147
+ (`--tcp-port`, default `<port> + 1`) alongside the WebSocket listener,
148
+ so stock IRC clients can dial in directly without the forwarder.
149
+ Pass `--no-tcp` for WebSocket-only. `--motd-file` loads the MOTD from
150
+ disk.
151
+
152
+ ### Known limitations
153
+
154
+ - **No config loader for oper credentials / max clients.** Server name,
155
+ network, MOTD, cloaking secret, and admission limits are configurable,
156
+ but oper credentials and dynamic max-client limits remain hardcoded
157
+ defaults.
158
+ - **No TLS / raw TCP transport in production.** The local CLI exposes a
159
+ TCP listener for development; deployed stacks (CF + AWS) are
160
+ WebSocket text frames only. `irc+tls` is 0.x work.
161
+ - **SASL `EXTERNAL` is reserved.** It is recognized but returns `904`
162
+ until mTLS lands.
163
+ - **`chathistory` storage is adapter-supplied.** The `MessageStore` port
164
+ is implemented per adapter; clients that query backlog against a
165
+ runtime with no store configured will see an empty result.
166
+
167
+ ## [0.1.0] - 2026-07-21
168
+
169
+ First tagged release. Establishes the pure-reducer protocol core, the
170
+ runtime port + reference implementation, a runnable local CLI server,
171
+ and a working Cloudflare Workers adapter deployed to staging.
172
+
173
+ This is a **preview release**. The AWS adapter, SASL authentication,
174
+ flood control, server-password enforcement, and a CI coverage gate
175
+ are not yet implemented — see **Known limitations** below. The
176
+ Cloudflare staging deployment is functional; production deploys are
177
+ manual per `docs/release.md` §8.3.
178
+
179
+ ### Added — Protocol core (`@serverless-ircd/irc-core`)
180
+
181
+ - Pure reducer architecture: every command handler is
182
+ `(state, msg, ctx) => { state, effects }`. Side effects are values
183
+ (`Effect[]`), interpreted by a separate `dispatch` step.
184
+ - Discriminated `Effect` taxonomy (`Send`, `Broadcast`, `Disconnect`,
185
+ `ReserveNick`, `ChangeNick`, `ReleaseNick`, `ApplyChannelDelta`,
186
+ `Lookup`, …) covering every side effect referenced in PLAN §2.1.
187
+ - Determinism ports `Clock` and `IdFactory` injected into `ctx` so
188
+ reducers never touch real timers or randomness.
189
+ - RFC 1459 / 2812 command set: registration (`NICK` / `USER` / `PASS`),
190
+ `PING` / `PONG`, `QUIT`, `JOIN`, `PART`, `PRIVMSG`, `NOTICE`,
191
+ `TOPIC`, `NAMES`, `MOTD`, `MODE` (channel + user), `KICK`, `INVITE`,
192
+ `WHO`, `WHOIS`, `LIST`.
193
+ - Channel modes: `o v b i k l t n m s p`. User modes: `i`, `o` (local
194
+ only), `w`, `s`.
195
+ - Welcome numeric sequence `001`–`005` with `isupport` (`005`)
196
+ generator emitting `NETWORK=`, `CHANLIMIT=`, `CASEMAPPING=`,
197
+ `SAFELIST`, and the negotiated IRCv3 caps.
198
+ - IRCv3 `CAP` negotiation: full `LS` / `REQ` / `ACK` / `NAK` / `END` /
199
+ `LIST` / `NEW` / `DEL` lifecycle.
200
+ - IRCv3 extensions: `message-tags`, `server-time`, `echo-message`,
201
+ `multi-prefix`, `extended-join`, `chghost`, `away-notify`,
202
+ `invite-notify`, `batch`, and the `safelist` capability +
203
+ `SAFELIST` isupport token.
204
+
205
+ ### Added — Orchestration (`@serverless-ircd/irc-server`)
206
+
207
+ - The `IrcRuntime` port: side-effect interface (transport + state IO)
208
+ that every adapter implements.
209
+ - `dispatch(effects, runtime)` interpreter that walks an `Effect[]`
210
+ against a bound runtime, with ordering and dedup verified.
211
+ - `ConnectionActor`: bytes → framed messages → reducer → dispatch.
212
+ Drives the full registration handshake, PING/PONG cadence, and
213
+ command dispatch.
214
+
215
+ ### Added — In-memory runtime (`@serverless-ircd/in-memory-runtime`)
216
+
217
+ - Single-process reference implementation of `IrcRuntime` backed by
218
+ in-memory Maps. Used by integration tests and the local CLI server.
219
+
220
+ ### Added — Local CLI server (`@serverless-ircd/local-cli`)
221
+
222
+ - Runnable WebSocket IRC server on `ws://127.0.0.1:6667/` (default),
223
+ built on the in-memory runtime. Speaks WebSocket text frames, one
224
+ IRC message per frame. `SIGINT` / `SIGTERM` perform graceful
225
+ shutdown. Manual-test harness and e2e fixture target.
226
+ - CLI flags: `--port`, `--host`, `-h/--help`.
227
+
228
+ ### Added — Cloudflare adapter (`@serverless-ircd/cf-adapter`)
229
+
230
+ - `ConnectionDO`: hibernatable WebSocket handler (one DO per
231
+ connection). Owns `ConnectionState`, drives `ConnectionActor`,
232
+ persists state across hibernation, drives the PING / no-PONG
233
+ disconnect sweep via `alarm()`.
234
+ - `RegistryDO`: sharded nick-uniqueness authority. Shard assignment is
235
+ `fnv1a32(lowercased(nick)) mod N`, default fleet size 32. DO
236
+ single-threadedness makes the uniqueness invariant structural.
237
+ - `ChannelDO`: per-lowercased-channel-name DO holding roster, modes,
238
+ topic, and fanout via `ConnectionDO` stubs.
239
+ - `CfRuntime`: implements the `IrcRuntime` port against the three DO
240
+ classes.
241
+
242
+ ### Added — Cloudflare Worker (`@serverless-ircd/cf-worker`)
243
+
244
+ - Worker edge entry point: WebSocket upgrade → allocates
245
+ `connectionId = crypto.randomUUID()` → routes to `ConnectionDO`.
246
+ Non-upgrade requests get a human-readable 200 health string.
247
+ - `wrangler.toml` with default (production) + `[env.staging]`
248
+ environments, DO bindings, `[[migrations]]` block.
249
+ - `scripts/smoke.mjs`: replay CONNECT → NICK/USER → JOIN → PRIVMSG →
250
+ QUIT and assert the server emits `001`, `376`, `353`, `366`, then
251
+ closes. Run by CI against staging, runnable locally against
252
+ `wrangler dev`.
253
+
254
+ ### Added — TCP ↔ WebSocket forwarder (`@serverless-ircd/tcp-ws-forwarder`)
255
+
256
+ - Local bridge so stock TCP IRC clients (WeeChat, HexChat, irssi) can
257
+ reach the WebSocket-only server. Listens on a TCP port, opens one
258
+ WebSocket per connection, reassembles the TCP byte stream into one
259
+ message per WS frame outbound, normalizes inbound frames back to
260
+ canonical CRLF. Transport-agnostic — ships no
261
+ `@serverless-ircd/*` dependency.
262
+
263
+ ### Added — CI / build
264
+
265
+ - GitHub Actions CI (`.github/workflows/ci.yml`): lint, typecheck,
266
+ test with coverage on every push / pull request to `main`.
267
+ - GitHub Actions CF deploy (`.github/workflows/deploy-cf.yml`):
268
+ serialized staging deploy on every push to `main`, followed by the
269
+ smoke e2e against the deployed URL. Failure blocks the merge.
270
+ - Toolchain: pnpm 9 workspaces, turbo, TypeScript (strict +
271
+ `exactOptionalPropertyTypes`), vitest + `@vitest/coverage-v8`,
272
+ fast-check, Biome.
273
+
274
+ ### Added — Documentation
275
+
276
+ - `README.md` — architecture, repository layout, toolchain, local
277
+ server + forwarder usage, testing strategy, roadmap, protocol scope.
278
+ - `docs/architecture-aws.md` — AWS adapter design notes (adapter
279
+ itself lands in a later release).
280
+ - `docs/deployment-cf.md` — end-to-end Cloudflare deployment guide
281
+ (prerequisites, account setup, local dev, staging + production
282
+ deploys, config reference, sharding, cost, CI/CD, troubleshooting).
283
+ - `docs/release.md` — this release process runbook.
284
+
285
+ ### Changed
286
+
287
+ - None (this is the first tagged release).
288
+
289
+ ### ⚠️ Migration required
290
+
291
+ - None. No prior releases to migrate from. The DO `[[migrations]]`
292
+ block in `wrangler.toml` is `tag = "v1"` and will provision cleanly
293
+ on a fresh account.
294
+
295
+ ### Known limitations
296
+
297
+ - **No AWS adapter.** Tickets TICKET-039 through TICKET-044 (CDK
298
+ stack, APIGW WebSocket handlers, DynamoDB transactions, EventBridge
299
+ timers, deploy pipeline, deployment doc) are pending. Cloudflare is
300
+ the only deployable platform in 0.1.0.
301
+ - **No SASL.** `CAP sasl` is advertised in the capability list but
302
+ `AUTHENTICATE` is not wired into the dispatcher. Plain-text password
303
+ authentication via `PASS` is also not enforced (TICKET-046).
304
+ - **No flood control.** A token-bucket wrapper (TICKET-028) is
305
+ pending. Misbehaving clients can send unbounded commands.
306
+ - **No server-password enforcement, rate limits, cloaking, or oper
307
+ credentials** (TICKET-046).
308
+ - **No config loader.** Server name, network name, MOTD, channel
309
+ prefixes, max clients, oper creds are hardcoded defaults
310
+ (TICKET-047). The `MOTD.txt` file at the repo root is informational
311
+ only; the live MOTD is compiled into the Worker from
312
+ `[vars].MOTD_LINES`.
313
+ - **No CI coverage gate.** Coverage is computed and uploaded as an
314
+ artifact, but no threshold is enforced (TICKET-048). Inspect
315
+ `packages/*/coverage/` before deploying.
316
+ - **No TLS / raw TCP transport.** v1 is WebSocket text frames only.
317
+ TLS-hardened and `irc+tls` transports are 0.x work (TICKET-052
318
+ through TICKET-058). Stock TCP IRC clients reach the server through
319
+ the local `tcp-ws-forwarder` bridge.
320
+ - **No `chathistory` playback.** TICKET-063 is pending; clients that
321
+ request backlog on JOIN will see an empty batch or no response.
322
+
323
+ ### Security
324
+
325
+ - None. No vulnerabilities have been reported or fixed in this
326
+ release. Report security issues per the contributing guide.
327
+
328
+ ---
329
+
330
+ <!-- Release template — copy for the next release, fill in, move above this line.
331
+
332
+ ## [Unreleased]
333
+
334
+ ### Added
335
+ -
336
+
337
+ ### Changed
338
+ -
339
+
340
+ ### ⚠️ Migration required
341
+ -
342
+
343
+ ### Fixed
344
+ -
345
+
346
+ ### Known limitations
347
+ -
348
+
349
+ ### Security
350
+ -
351
+
352
+ -->
package/MOTD.txt ADDED
@@ -0,0 +1,3 @@
1
+ Welcome to the test network for Serverless IRCd project.
2
+
3
+ Join #general for chat.