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.
- package/.github/workflows/ci.yml +34 -2
- package/.github/workflows/deploy-aws.yml +1 -2
- package/.github/workflows/deploy-cf-tcp.yml +1 -5
- package/.github/workflows/deploy-cf.yml +1 -2
- package/CHANGELOG.md +435 -0
- package/README.md +206 -27
- package/apps/aws-stack/README.md +37 -3
- package/apps/aws-stack/package.json +1 -1
- package/apps/aws-stack/src/aws-stack.ts +106 -13
- package/apps/aws-stack/tests/coverage-command.test.ts +155 -0
- package/apps/aws-stack/tests/stack.test.ts +49 -3
- package/apps/aws-stack/tests/synth-no-bundle.test.ts +129 -0
- package/apps/aws-stack/tests/vitest.setup.ts +18 -0
- package/apps/aws-stack/vitest.config.ts +3 -0
- package/apps/cf-tcp-container/package.json +1 -1
- package/apps/cf-tcp-container/src/config-loader.ts +6 -7
- package/apps/cf-tcp-container/src/container-server.ts +3 -0
- package/apps/cf-tcp-container/tests/config-loader.test.ts +10 -3
- package/apps/cf-tcp-container/wrangler.toml +1 -1
- package/apps/cf-worker/package.json +2 -1
- package/apps/cf-worker/vitest.config.ts +1 -1
- package/apps/cf-worker/wrangler.toml +5 -3
- package/apps/local-cli/package.json +1 -1
- package/apps/local-cli/src/config-loader.ts +6 -6
- package/apps/local-cli/src/main.ts +20 -0
- package/apps/local-cli/src/server.ts +138 -28
- package/apps/local-cli/tests/e2e.test.ts +113 -29
- package/apps/local-cli/tests/tcp.test.ts +71 -19
- package/apps/local-cli/tests/ws-subprotocol.test.ts +262 -0
- package/biome.json +1 -0
- package/docs/ADR-001-pure-reducers-and-effect-system.md +74 -0
- package/docs/ADR-002-location-of-authority.md +82 -0
- package/docs/ADR-003-durable-object-sharding.md +110 -0
- package/docs/ADR-004-dynamodb-schema.md +96 -0
- package/docs/ADR-005-wss-only-transport-v1.md +83 -0
- package/docs/ADR-006-sasl-mechanism-scope.md +96 -0
- package/docs/ADR-007-deterministic-ports.md +82 -0
- package/docs/ADR-008-monorepo-tooling.md +60 -0
- package/docs/ADR-009-dual-transport-wss-and-irc-tls.md +138 -0
- package/docs/ADR-Index.md +15 -0
- package/docs/AWS-Adapter-Architecture.md +496 -0
- package/docs/AWS-Deployment.md +1275 -0
- package/docs/AWS-TCP-Deployment.md +410 -0
- package/docs/Cloudflare-Deployment-Guide.md +719 -0
- package/docs/Cloudflare-TCP-Deployment.md +510 -0
- package/docs/Home.md +15 -0
- package/docs/Observability.md +87 -0
- package/docs/PlanExtensions.md +812 -0
- package/docs/PlanIRCv3Websocket.md +503 -0
- package/docs/PlanWebClient.md +451 -0
- package/docs/Release-Process.md +443 -0
- package/package.json +2 -2
- package/packages/aws-adapter/package.json +1 -1
- package/packages/aws-adapter/src/aws-runtime.ts +69 -0
- package/packages/aws-adapter/src/handlers/connect.ts +36 -5
- package/packages/aws-adapter/src/handlers/default.ts +66 -5
- package/packages/aws-adapter/src/handlers/index.ts +41 -2
- package/packages/aws-adapter/src/handlers/nlb-stream.ts +18 -0
- package/packages/aws-adapter/src/index.ts +2 -0
- package/packages/aws-adapter/src/serialize.ts +40 -2
- package/packages/aws-adapter/src/stats.ts +80 -0
- package/packages/aws-adapter/tests/account-store-dynamo.test.ts +49 -8
- package/packages/aws-adapter/tests/account-store.test.ts +4 -7
- package/packages/aws-adapter/tests/aws-harness.ts +5 -2
- package/packages/aws-adapter/tests/aws-integration.test.ts +8 -4
- package/packages/aws-adapter/tests/aws-runtime.test.ts +140 -0
- package/packages/aws-adapter/tests/config-loader.test.ts +11 -2
- package/packages/aws-adapter/tests/connect.test.ts +100 -4
- package/packages/aws-adapter/tests/disconnect-fanout.test.ts +3 -4
- package/packages/aws-adapter/tests/global-setup.ts +55 -30
- package/packages/aws-adapter/tests/gone-exception.test.ts +4 -9
- package/packages/aws-adapter/tests/handlers.test.ts +238 -4
- package/packages/aws-adapter/tests/message-store.test.ts +6 -5
- package/packages/aws-adapter/tests/nlb-stream.test.ts +62 -8
- package/packages/aws-adapter/tests/stats.test.ts +317 -0
- package/packages/cf-adapter/package.json +6 -1
- package/packages/cf-adapter/src/cf-runtime.ts +66 -1
- package/packages/cf-adapter/src/channel-do.ts +2 -2
- package/packages/cf-adapter/src/connection-do.ts +185 -54
- package/packages/cf-adapter/src/env.ts +25 -6
- package/packages/cf-adapter/src/index.ts +2 -0
- package/packages/cf-adapter/src/registry-do.ts +22 -3
- package/packages/cf-adapter/src/serialize.ts +25 -4
- package/packages/cf-adapter/src/sharding.ts +1 -2
- package/packages/cf-adapter/src/stats.ts +65 -0
- package/packages/cf-adapter/tests/cf-harness.ts +1 -1
- package/packages/cf-adapter/tests/cf-integration.test.ts +4 -4
- package/packages/cf-adapter/tests/cf-runtime.test.ts +307 -2
- package/packages/cf-adapter/tests/channel-do.test.ts +119 -2
- package/packages/cf-adapter/tests/config-loader.test.ts +49 -0
- package/packages/cf-adapter/tests/connection-do-channel-registration.test.ts +2 -2
- package/packages/cf-adapter/tests/connection-do-no-batching-reservation.test.ts +2 -2
- package/packages/cf-adapter/tests/connection-do-pure.test.ts +91 -0
- package/packages/cf-adapter/tests/connection-do-ws-spec-contract.test.ts +289 -0
- package/packages/cf-adapter/tests/connection-do-ws-subprotocol.test.ts +184 -0
- package/packages/cf-adapter/tests/connection-do.test.ts +27 -2
- package/packages/cf-adapter/tests/d1-account-store.test.ts +48 -0
- package/packages/cf-adapter/tests/registry-do.test.ts +108 -4
- package/packages/cf-adapter/tests/serialize.test.ts +308 -0
- package/packages/cf-adapter/tests/sharding.test.ts +1 -1
- package/packages/cf-adapter/tests/stats.test.ts +120 -0
- package/packages/cf-adapter/tests/worker/main.ts +7 -7
- package/packages/cf-adapter/tests/worker/stubs/channel-stub.ts +2 -2
- package/packages/cf-adapter/tests/worker/stubs/registry-stub.ts +8 -2
- package/packages/cf-adapter/vitest.config.ts +1 -1
- package/packages/cf-adapter/wrangler.test.toml +7 -0
- package/packages/in-memory-runtime/package.json +1 -1
- package/packages/in-memory-runtime/src/in-memory-runtime.ts +39 -0
- package/packages/in-memory-runtime/tests/in-memory-runtime.test.ts +259 -0
- package/packages/irc-core/package.json +1 -1
- package/packages/irc-core/src/admission.ts +16 -15
- package/packages/irc-core/src/caps/capabilities.ts +38 -4
- package/packages/irc-core/src/caps/index.ts +1 -0
- package/packages/irc-core/src/caps/sts.ts +84 -0
- package/packages/irc-core/src/commands/account-notify.ts +53 -0
- package/packages/irc-core/src/commands/away.ts +9 -3
- package/packages/irc-core/src/commands/cap.ts +23 -5
- package/packages/irc-core/src/commands/chathistory.ts +40 -11
- package/packages/irc-core/src/commands/index.ts +42 -0
- package/packages/irc-core/src/commands/invite.ts +2 -4
- package/packages/irc-core/src/commands/isupport.ts +59 -2
- package/packages/irc-core/src/commands/kick.ts +2 -4
- package/packages/irc-core/src/commands/kill.ts +127 -0
- package/packages/irc-core/src/commands/list.ts +1 -1
- package/packages/irc-core/src/commands/lusers.ts +204 -0
- package/packages/irc-core/src/commands/mode.ts +12 -9
- package/packages/irc-core/src/commands/monitor.ts +327 -0
- package/packages/irc-core/src/commands/multiline.ts +256 -0
- package/packages/irc-core/src/commands/names.ts +3 -5
- package/packages/irc-core/src/commands/part.ts +2 -4
- package/packages/irc-core/src/commands/pre-away.ts +112 -0
- package/packages/irc-core/src/commands/privmsg.ts +19 -12
- package/packages/irc-core/src/commands/read-marker.ts +108 -0
- package/packages/irc-core/src/commands/registration.ts +8 -0
- package/packages/irc-core/src/commands/rehash.ts +119 -0
- package/packages/irc-core/src/commands/sasl.ts +24 -1
- package/packages/irc-core/src/commands/setname.ts +109 -0
- package/packages/irc-core/src/commands/stats.ts +152 -0
- package/packages/irc-core/src/commands/tagmsg.ts +71 -1
- package/packages/irc-core/src/commands/topic.ts +2 -4
- package/packages/irc-core/src/commands/trace.ts +137 -0
- package/packages/irc-core/src/commands/wallops.ts +118 -0
- package/packages/irc-core/src/commands/whois.ts +5 -0
- package/packages/irc-core/src/config.ts +72 -10
- package/packages/irc-core/src/effects.ts +41 -1
- package/packages/irc-core/src/index.ts +2 -0
- package/packages/irc-core/src/ports.ts +568 -0
- package/packages/irc-core/src/protocol/index.ts +14 -0
- package/packages/irc-core/src/protocol/labeled-response.ts +133 -0
- package/packages/irc-core/src/protocol/numerics.ts +57 -11
- package/packages/irc-core/src/protocol/outbound.ts +36 -4
- package/packages/irc-core/src/protocol/standard-replies.ts +277 -0
- package/packages/irc-core/src/state/connection.ts +32 -1
- package/packages/irc-core/src/types.ts +120 -1
- package/packages/irc-core/src/ws-framing.ts +132 -0
- package/packages/irc-core/src/ws-subprotocol.ts +66 -0
- package/packages/irc-core/stryker.commands.conf.json +1 -2
- package/packages/irc-core/tests/admission.test.ts +18 -0
- package/packages/irc-core/tests/away-store.test.ts +73 -0
- package/packages/irc-core/tests/caps/capabilities.test.ts +119 -2
- package/packages/irc-core/tests/caps/sts.test.ts +123 -0
- package/packages/irc-core/tests/commands/account-notify.test.ts +171 -0
- package/packages/irc-core/tests/commands/away.test.ts +85 -2
- package/packages/irc-core/tests/commands/cap.test.ts +165 -0
- package/packages/irc-core/tests/commands/chathistory.test.ts +148 -1
- package/packages/irc-core/tests/commands/echo-message.test.ts +9 -3
- package/packages/irc-core/tests/commands/isupport.test.ts +80 -0
- package/packages/irc-core/tests/commands/kill.test.ts +243 -0
- package/packages/irc-core/tests/commands/lusers.test.ts +368 -0
- package/packages/irc-core/tests/commands/mode.test.ts +129 -0
- package/packages/irc-core/tests/commands/monitor.test.ts +411 -0
- package/packages/irc-core/tests/commands/multiline.test.ts +428 -0
- package/packages/irc-core/tests/commands/pre-away.test.ts +241 -0
- package/packages/irc-core/tests/commands/privmsg.test.ts +154 -3
- package/packages/irc-core/tests/commands/read-marker.test.ts +211 -0
- package/packages/irc-core/tests/commands/registration.test.ts +64 -0
- package/packages/irc-core/tests/commands/rehash.test.ts +171 -0
- package/packages/irc-core/tests/commands/sasl.test.ts +116 -2
- package/packages/irc-core/tests/commands/setname.test.ts +225 -0
- package/packages/irc-core/tests/commands/stats.test.ts +294 -0
- package/packages/irc-core/tests/commands/tagmsg.test.ts +357 -0
- package/packages/irc-core/tests/commands/trace.test.ts +282 -0
- package/packages/irc-core/tests/commands/wallops.test.ts +231 -0
- package/packages/irc-core/tests/commands/whois.test.ts +34 -0
- package/packages/irc-core/tests/config.test.ts +103 -13
- package/packages/irc-core/tests/dropped-s2s-and-obsolete-verbs.test.ts +90 -0
- package/packages/irc-core/tests/effects.test.ts +14 -0
- package/packages/irc-core/tests/labeled-response.test.ts +181 -0
- package/packages/irc-core/tests/message-tags.test.ts +114 -0
- package/packages/irc-core/tests/monitor-store.test.ts +200 -0
- package/packages/irc-core/tests/numerics.test.ts +90 -0
- package/packages/irc-core/tests/outbound.test.ts +51 -0
- package/packages/irc-core/tests/ports.test.ts +22 -0
- package/packages/irc-core/tests/raw-modules.d.ts +11 -0
- package/packages/irc-core/tests/read-marker-store.test.ts +108 -0
- package/packages/irc-core/tests/standard-replies.test.ts +412 -0
- package/packages/irc-core/tests/stats-store.test.ts +222 -0
- package/packages/irc-core/tests/types.test.ts +27 -0
- package/packages/irc-core/tests/ws-framing.test.ts +213 -0
- package/packages/irc-core/tests/ws-subprotocol.test.ts +111 -0
- package/packages/irc-server/package.json +1 -1
- package/packages/irc-server/src/actor.ts +699 -19
- package/packages/irc-server/src/dispatch.ts +109 -16
- package/packages/irc-server/src/routing.ts +3 -0
- package/packages/irc-server/src/runtime.ts +31 -0
- package/packages/irc-server/src/transport.ts +10 -7
- package/packages/irc-server/tests/actor.test.ts +2523 -42
- package/packages/irc-server/tests/dispatch.test.ts +300 -2
- package/packages/irc-server/tests/raw-modules.d.ts +11 -0
- package/packages/irc-server/tests/routing.test.ts +1 -0
- package/packages/irc-server/tests/runtime.test.ts +7 -0
- package/packages/irc-test-support/package.json +1 -1
- package/packages/irc-test-support/src/index.ts +6 -0
- package/packages/irc-test-support/src/scenarios.ts +9 -1
- package/packages/irc-test-support/src/test-config.ts +54 -0
- package/packages/irc-test-support/tests/in-memory-scenarios.test.ts +1 -1
- package/packages/irc-test-support/tests/test-config.test.ts +51 -0
- package/pnpm-workspace.yaml +1 -0
- package/tools/ci-hardening/package.json +1 -1
- package/tools/ci-hardening/tests/ddb-local.test.ts +82 -0
- package/tools/load-test/package.json +33 -0
- package/tools/load-test/src/client.ts +351 -0
- package/tools/load-test/src/config.ts +313 -0
- package/tools/load-test/src/harness.ts +116 -0
- package/tools/load-test/src/main.ts +120 -0
- package/tools/load-test/src/metrics.ts +168 -0
- package/tools/load-test/src/report.ts +106 -0
- package/tools/load-test/tests/client.test.ts +212 -0
- package/tools/load-test/tests/config.test.ts +152 -0
- package/tools/load-test/tests/framing.test.ts +37 -0
- package/tools/load-test/tests/harness.test.ts +165 -0
- package/tools/load-test/tests/metrics.test.ts +174 -0
- package/tools/load-test/tests/report.test.ts +161 -0
- package/tools/load-test/tests/smoke.test.ts +67 -0
- package/tools/load-test/tsconfig.build.json +12 -0
- package/tools/load-test/tsconfig.test.json +10 -0
- package/tools/load-test/vitest.config.ts +29 -0
- package/tools/package.json +6 -1
- package/tools/seed-cf-accounts.ts +4 -1
- package/tools/tcp-ws-forwarder/package.json +1 -1
- package/tools/tcp-ws-forwarder/src/forwarder.ts +57 -9
- package/tools/tcp-ws-forwarder/tests/forwarder.test.ts +34 -1
- package/tools/tcp-ws-forwarder/tests/framing.test.ts +65 -1
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
# Release Process
|
|
2
|
+
|
|
3
|
+
End-to-end runbook for cutting, publishing, and rolling back a
|
|
4
|
+
ServerlessIRCd release. This is the canonical process — every release
|
|
5
|
+
follows this document top-to-bottom.
|
|
6
|
+
|
|
7
|
+
**Distribution model.** All workspace packages are `"private": true`. A
|
|
8
|
+
release is a **signed git tag** plus a **GitHub Release** whose body is
|
|
9
|
+
copied from `CHANGELOG.md`. There is no npm publish step in 0.x. See
|
|
10
|
+
§10 (Future: npm publish) for the criteria that would flip that on.
|
|
11
|
+
|
|
12
|
+
**Current cadence.** 0.x is "tag when ready" — there is no fixed
|
|
13
|
+
calendar release. Anyone with push rights may propose a release by
|
|
14
|
+
opening a PR that bumps versions and updates `CHANGELOG.md`; the
|
|
15
|
+
maintainer team cuts the tag.
|
|
16
|
+
|
|
17
|
+
Cross-reference: `README.md` (Roadmap), `progress.md` / `tickets.md`
|
|
18
|
+
(release content), `docs/deployment-cf.md` (prod deploy step §6),
|
|
19
|
+
`.github/workflows/ci.yml`, `.github/workflows/deploy-cf.yml`.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 1. Versioning
|
|
24
|
+
|
|
25
|
+
ServerlessIRCd follows [Semantic Versioning](https://semver.org/) with
|
|
26
|
+
the 0.x convention:
|
|
27
|
+
|
|
28
|
+
| Bump | When |
|
|
29
|
+
|-------------|-----------------------------------------------------------------|
|
|
30
|
+
| `0.x.0 → 0.(x+1).0` | Any user-visible behavior change, new command/cap, new adapter, schema change to persisted DO state. |
|
|
31
|
+
| `0.x.y → 0.x.(y+1)`* | Internal-only fixes: tests, docs, refactors that don't change runtime behavior, perf work with identical output. (*Reserved for after 1.0; in 0.x every change is allowed to be a minor.) |
|
|
32
|
+
| `1.0.0` | The Cloudflare **and** AWS adapters are deployed to staging, the parametrized contract suite passes on both, ≥3 reference clients connect cleanly (PLAN §9 exit criteria). |
|
|
33
|
+
|
|
34
|
+
**The 0.x disclaimer.** Per SemVer 0.x, anything may change at any
|
|
35
|
+
minor bump. Consumers of `@serverless-ircd/*` packages (today: only
|
|
36
|
+
this repo itself) should pin exact versions.
|
|
37
|
+
|
|
38
|
+
### 1.1 Workspace version lock
|
|
39
|
+
|
|
40
|
+
All eight `package.json` files (root + 7 workspace packages) carry the
|
|
41
|
+
**same** version string. They are bumped together in the release
|
|
42
|
+
commit. The pre-release checklist in §4 enforces this.
|
|
43
|
+
|
|
44
|
+
Rationale: even though the packages are private today, keeping a single
|
|
45
|
+
version simplifies future npm publishing, release notes, and
|
|
46
|
+
bug-report triage ("what version are you on?" has one answer).
|
|
47
|
+
|
|
48
|
+
### 1.2 Persisted-state schema
|
|
49
|
+
|
|
50
|
+
A change to `PERSISTED_STATE_VERSION` in
|
|
51
|
+
`packages/cf-adapter/src/serialize.ts` (or any equivalent versioned
|
|
52
|
+
blob in a future adapter) is a **minor bump minimum**, and requires a
|
|
53
|
+
migration entry in `CHANGELOG.md` under a **⚠️ Migration required**
|
|
54
|
+
heading. See §9 (Rollback) for why rollback across such a bump is
|
|
55
|
+
not always possible.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 2. Roles & permissions
|
|
60
|
+
|
|
61
|
+
| Role | Who has it today | Required for |
|
|
62
|
+
|-----------------------------|--------------------------|---------------------------------------|
|
|
63
|
+
| Push to `main` | Maintainers | Merging the release PR. |
|
|
64
|
+
| Push tags (`v*`) | Maintainers | `git push origin v0.x.0`. |
|
|
65
|
+
| Create GitHub Release | Maintainers | Publishing the release notes. |
|
|
66
|
+
| `CLOUDFLARE_API_TOKEN` secret | Repo Settings → Secrets | Local prod deploys. CI staging deploys. |
|
|
67
|
+
| `CF_SMOKE_URL` variable | Repo Settings → Variables | Staging smoke e2e in CI. |
|
|
68
|
+
|
|
69
|
+
There is no separate "release engineer" role. The maintainer who cuts
|
|
70
|
+
the tag owns the release end-to-end, including the production deploy
|
|
71
|
+
and the post-release smoke check.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 3. Branch & tag conventions
|
|
76
|
+
|
|
77
|
+
- **Releases are cut from `main`.** No release branches in 0.x.
|
|
78
|
+
- **Tags** are annotated, signed where possible, formatted `v<semver>`
|
|
79
|
+
(e.g. `v0.1.0`, `v0.1.1`, `v1.0.0-rc.1`).
|
|
80
|
+
- **Pre-release tags** use SemVer suffixes: `v0.2.0-rc.1`,
|
|
81
|
+
`v1.0.0-beta.3`. GitHub marks the release as a pre-release
|
|
82
|
+
automatically based on the suffix.
|
|
83
|
+
- The release commit message is `chore(release): vX.Y.Z` (matches the
|
|
84
|
+
tag).
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 4. Pre-release checklist
|
|
89
|
+
|
|
90
|
+
Run every box before tagging. Most are verifiable from the repo root.
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
# 0. Sanity: clean tree on main, up to date with origin.
|
|
94
|
+
git status # working tree clean
|
|
95
|
+
git checkout main
|
|
96
|
+
git pull --ff-only origin main
|
|
97
|
+
|
|
98
|
+
# 1. All desired work merged. No pending tickets targeted for this release.
|
|
99
|
+
# Cross-check progress.md — every ticket listed under this release is ✅.
|
|
100
|
+
|
|
101
|
+
# 2. CI is green on the tip of main.
|
|
102
|
+
# Open: https://github.com/<owner>/<repo>/actions
|
|
103
|
+
# - CI workflow: lint / typecheck / coverage all green.
|
|
104
|
+
# - deploy-cf workflow: staging deploy + smoke e2e green.
|
|
105
|
+
|
|
106
|
+
# 3. Local full verify.
|
|
107
|
+
pnpm install --frozen-lockfile
|
|
108
|
+
pnpm lint # biome check .
|
|
109
|
+
pnpm typecheck # tsc --noEmit across workspace
|
|
110
|
+
pnpm test # full suite
|
|
111
|
+
pnpm build # every package produces dist/
|
|
112
|
+
|
|
113
|
+
# 4. Coverage gate (enforced in CI per package).
|
|
114
|
+
# The per-package coverage gate (TICKET-048) runs in CI via
|
|
115
|
+
# `pnpm coverage`; Vitest fails the build on any threshold regression.
|
|
116
|
+
# Thresholds: 100% (lines/functions/branches/statements) on
|
|
117
|
+
# irc-core / irc-server / in-memory-runtime, and ≥90% on every
|
|
118
|
+
# other package (cf-adapter / local-cli / tcp-ws-forwarder). See the
|
|
119
|
+
# "Coverage gate" step in .github/workflows/ci.yml. CI must already
|
|
120
|
+
# be green (step 2), so locally re-run `pnpm coverage` as a
|
|
121
|
+
# belt-and-braces check before tagging.
|
|
122
|
+
|
|
123
|
+
# 5. Staging smoke e2e passes against the deployed tip-of-main.
|
|
124
|
+
node apps/cf-worker/scripts/smoke.mjs \
|
|
125
|
+
--url wss://serverless-ircd-staging.<subdomain>.workers.dev
|
|
126
|
+
|
|
127
|
+
# 6. CHANGELOG.md is complete and accurate (§5).
|
|
128
|
+
# 7. All package.json versions are bumped and lock-step (§6).
|
|
129
|
+
# 8. The Release PR is merged (§7).
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
If any box fails, **stop**. Fix forward on `main` and restart the
|
|
133
|
+
checklist — do not tag over a broken tip.
|
|
134
|
+
|
|
135
|
+
### 4.1 What is explicitly out of scope for a 0.x release
|
|
136
|
+
|
|
137
|
+
- AWS adapter (TICKET-039 through TICKET-044).
|
|
138
|
+
- SASL authentication (TICKET-026).
|
|
139
|
+
- Flood control (TICKET-028).
|
|
140
|
+
- Server password / rate limits / cloaking (TICKET-046).
|
|
141
|
+
- Config loader (TICKET-047).
|
|
142
|
+
|
|
143
|
+
These belong in a later 0.x release. Their absence is **not** a
|
|
144
|
+
blocker and should be called out under "Known limitations" in the
|
|
145
|
+
GitHub Release body.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 5. Writing `CHANGELOG.md`
|
|
150
|
+
|
|
151
|
+
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), newest
|
|
152
|
+
entry first. One `## [X.Y.Z] - YYYY-MM-DD` section per release.
|
|
153
|
+
|
|
154
|
+
Categories (use only what applies):
|
|
155
|
+
|
|
156
|
+
- **Added** — new features, commands, caps, adapters.
|
|
157
|
+
- **Changed** — behavior changes in existing functionality.
|
|
158
|
+
- **⚠️ Migration required** — anything that needs operator action on
|
|
159
|
+
upgrade (DO schema bumps, persisted-state version bumps, config
|
|
160
|
+
shape changes). One bullet per action item.
|
|
161
|
+
- **Deprecated** — functionality scheduled for removal.
|
|
162
|
+
- **Removed** — functionality removed in this release.
|
|
163
|
+
- **Fixed** — bug fixes.
|
|
164
|
+
- **Security** — vulnerability fixes (also request a CVE).
|
|
165
|
+
- **Known limitations** — explicit "not in this release" list so
|
|
166
|
+
operators aren't surprised.
|
|
167
|
+
|
|
168
|
+
Source material for the entry:
|
|
169
|
+
|
|
170
|
+
1. `progress.md` — the ticket table. Every `✅` row since the previous
|
|
171
|
+
release tag is a candidate bullet.
|
|
172
|
+
2. `tickets.md` — the per-ticket **Description** field, for the prose.
|
|
173
|
+
3. `git log v<previous>..HEAD --oneline` — for fixes that don't have a
|
|
174
|
+
ticket.
|
|
175
|
+
|
|
176
|
+
Group by user-facing theme (Registration, Channel ops, IRCv3,
|
|
177
|
+
Cloudflare adapter, …) rather than by ticket number. Operators read
|
|
178
|
+
themes; developers cross-reference tickets.
|
|
179
|
+
|
|
180
|
+
Rule of thumb: a new operator reading only `CHANGELOG.md` should
|
|
181
|
+
understand what changed and what they need to do.
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## 6. Bumping versions
|
|
186
|
+
|
|
187
|
+
There are eight `package.json` files. They must end up at the same
|
|
188
|
+
version:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
./package.json (workspace root)
|
|
192
|
+
packages/irc-core/package.json
|
|
193
|
+
packages/irc-server/package.json
|
|
194
|
+
packages/in-memory-runtime/package.json
|
|
195
|
+
packages/cf-adapter/package.json
|
|
196
|
+
apps/local-cli/package.json
|
|
197
|
+
apps/cf-worker/package.json
|
|
198
|
+
tools/tcp-ws-forwarder/package.json
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
To cut `v0.X.Y`:
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
# from the repo root, on a fresh branch off main:
|
|
205
|
+
git checkout -b release/v0.X.Y
|
|
206
|
+
|
|
207
|
+
# bump each file. The version field is the only change.
|
|
208
|
+
# (Optional helper: `pnpm -r exec -- node -e '...'` if you script it.)
|
|
209
|
+
|
|
210
|
+
# verify all eight files agree:
|
|
211
|
+
rg '"version":' package.json packages/*/package.json apps/*/package.json tools/*/package.json
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
The lockfile (`pnpm-lock.yaml`) is unaffected — none of the workspace
|
|
215
|
+
dependencies are version-pinned to the workspace root.
|
|
216
|
+
|
|
217
|
+
Do **not** run `pnpm publish` or `npm version` — those are for npm
|
|
218
|
+
distribution, which is not the current model.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 7. The release PR
|
|
223
|
+
|
|
224
|
+
The release PR contains **exactly two files changed**:
|
|
225
|
+
|
|
226
|
+
1. `CHANGELOG.md` (new entry under the version being cut).
|
|
227
|
+
2. Every `package.json` listed in §6 (version bump).
|
|
228
|
+
|
|
229
|
+
Nothing else. If a code change is needed for the release, it lands in
|
|
230
|
+
a separate PR first and the release PR is opened only after that PR
|
|
231
|
+
merges.
|
|
232
|
+
|
|
233
|
+
**PR title:** `chore(release): v0.X.Y`
|
|
234
|
+
**PR body:**
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
This cuts v0.X.Y. See CHANGELOG.md for the full diff vs. v0.W.Z.
|
|
238
|
+
|
|
239
|
+
Pre-release checklist: docs/release.md §4 — all green.
|
|
240
|
+
Out-of-scope tickets: docs/release.md §4.1.
|
|
241
|
+
|
|
242
|
+
After merge: tag + GitHub Release + manual prod deploy per
|
|
243
|
+
docs/release.md §8.
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
The PR is squash-merged with the same title as the commit message.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## 8. Cutting the release
|
|
251
|
+
|
|
252
|
+
Run **immediately** after the release PR merges, so the tag points at
|
|
253
|
+
the exact commit the changelog describes.
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
git checkout main
|
|
257
|
+
git pull --ff-only origin main
|
|
258
|
+
|
|
259
|
+
# Confirm the tip commit is the squashed release PR.
|
|
260
|
+
git log -1 --oneline # expect: chore(release): v0.X.Y
|
|
261
|
+
|
|
262
|
+
# Confirm versions are at the new release.
|
|
263
|
+
rg '"version":' package.json packages/*/package.json apps/*/package.json tools/*/package.json
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### 8.1 Tag and push
|
|
267
|
+
|
|
268
|
+
```bash
|
|
269
|
+
git tag -a v0.X.Y -m "v0.X.Y"
|
|
270
|
+
git push origin v0.X.Y
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Annotated (`-a`) tags carry tagger + date + message; use them over
|
|
274
|
+
lightweight tags. Sign with `-s` if you have a GPG / SSH signing key
|
|
275
|
+
configured (`git config tag.gpgSign true`).
|
|
276
|
+
|
|
277
|
+
### 8.2 Create the GitHub Release
|
|
278
|
+
|
|
279
|
+
1. <https://github.com/<owner>/<repo>/releases/new> → choose the tag
|
|
280
|
+
just pushed.
|
|
281
|
+
2. **Title:** `v0.X.Y` (matches the tag, nothing fancier).
|
|
282
|
+
3. **Body:** paste the matching section from `CHANGELOG.md`. Strip the
|
|
283
|
+
`## [X.Y.Z] - YYYY-MM-DD` heading line (it's redundant with the
|
|
284
|
+
release title).
|
|
285
|
+
4. **Set as latest release** unless this is a pre-release. Pre-release
|
|
286
|
+
tags (`-rc.N`, `-beta.N`, etc.) get the "Set as a pre-release"
|
|
287
|
+
checkbox instead.
|
|
288
|
+
5. **Publish release.**
|
|
289
|
+
|
|
290
|
+
### 8.3 Production deploy (manual)
|
|
291
|
+
|
|
292
|
+
There is **no CI auto-deploy to production**. The maintainer runs the
|
|
293
|
+
prod deploy by hand, from a clean checkout on the tag:
|
|
294
|
+
|
|
295
|
+
```bash
|
|
296
|
+
git checkout v0.X.Y
|
|
297
|
+
|
|
298
|
+
# Sanity-check the tag matches the released commit.
|
|
299
|
+
git log -1 --oneline # expect: chore(release): v0.X.Y
|
|
300
|
+
|
|
301
|
+
pnpm install --frozen-lockfile
|
|
302
|
+
pnpm build
|
|
303
|
+
pnpm typecheck
|
|
304
|
+
pnpm test
|
|
305
|
+
|
|
306
|
+
# Deploy the Cloudflare Worker to production.
|
|
307
|
+
pnpm deploy:cf:prod
|
|
308
|
+
# → wrangler deploy (default env = production)
|
|
309
|
+
|
|
310
|
+
# Tail prod logs while the first connections arrive.
|
|
311
|
+
npx wrangler tail
|
|
312
|
+
|
|
313
|
+
# Smoke-check prod with the same script CI uses on staging.
|
|
314
|
+
# You must point it at the prod wss:// URL:
|
|
315
|
+
node apps/cf-worker/scripts/smoke.mjs \
|
|
316
|
+
--url wss://<prod-workers-subdomain>.workers.dev
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
If smoke fails on prod:
|
|
320
|
+
|
|
321
|
+
1. **Do not roll forward blindly.** Tail logs, identify the regression.
|
|
322
|
+
2. If a rollback is required, follow §9.
|
|
323
|
+
3. If the regression is in the *deployed Worker code* (not the
|
|
324
|
+
protocol core), a hotfix follows the same process as a normal
|
|
325
|
+
release (new patch / minor → tag → GitHub Release → prod deploy).
|
|
326
|
+
|
|
327
|
+
### 8.4 Announce
|
|
328
|
+
|
|
329
|
+
- Update `progress.md` Status header (if a "current release" marker
|
|
330
|
+
was added there) — not currently present, optional.
|
|
331
|
+
- Open an issue with the body "v0.X.Y released" if external tracking
|
|
332
|
+
is desired. (Not currently wired to any external channel.)
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## 9. Rollback
|
|
337
|
+
|
|
338
|
+
There are two independent rollback axes: **code** and **data**.
|
|
339
|
+
|
|
340
|
+
### 9.1 Code rollback
|
|
341
|
+
|
|
342
|
+
To redeploy a previous release:
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
git checkout v0.W.Z # the previous tag
|
|
346
|
+
pnpm install --frozen-lockfile
|
|
347
|
+
pnpm build
|
|
348
|
+
pnpm deploy:cf:prod
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
This works as long as the previous release's code is compatible with
|
|
352
|
+
the persisted DO state shape (see 9.2).
|
|
353
|
+
|
|
354
|
+
There is no in-CI "prod rollback" workflow. The maintainer runs the
|
|
355
|
+
commands above from a checkout.
|
|
356
|
+
|
|
357
|
+
### 9.2 Data rollback (persisted DO state)
|
|
358
|
+
|
|
359
|
+
**Persisted-state version is one-way.** Once
|
|
360
|
+
`packages/cf-adapter/src/serialize.ts` ships a new
|
|
361
|
+
`PERSISTED_STATE_VERSION`, old code reading new blobs will reject
|
|
362
|
+
them, and new code reading old blobs may or may not migrate them
|
|
363
|
+
depending on whether a forward migration was shipped alongside.
|
|
364
|
+
|
|
365
|
+
Rules of thumb:
|
|
366
|
+
|
|
367
|
+
- If the release's `CHANGELOG.md` has **no ⚠️ Migration required**
|
|
368
|
+
entry, code rollback to the previous tag is safe — DO storage is
|
|
369
|
+
forward- and backward-compatible.
|
|
370
|
+
- If it **does** have a migration entry, assume code rollback will
|
|
371
|
+
not work without losing or transforming state. The migration entry
|
|
372
|
+
in `CHANGELOG.md` must spell out the recovery procedure.
|
|
373
|
+
|
|
374
|
+
In a catastrophic prod failure where the new version's migration
|
|
375
|
+
already ran, the path forward is:
|
|
376
|
+
|
|
377
|
+
1. Freeze new connections (set `MOTD_LINES` to a maintenance banner,
|
|
378
|
+
or block at the edge with a Route rule).
|
|
379
|
+
2. Decide: fix forward on the new code, or wipe staging-state (NOT
|
|
380
|
+
prod-state) and re-test the migration offline.
|
|
381
|
+
3. **Production DO namespaces must never be deleted** — the data is
|
|
382
|
+
gone permanently (per `docs/deployment-cf.md` §12.1).
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## 10. Future: npm publish
|
|
387
|
+
|
|
388
|
+
When `@serverless-ircd/irc-core` becomes the first package consumers
|
|
389
|
+
outside this repo depend on (likely: the in-memory runtime is pulled
|
|
390
|
+
into a third-party test harness, or someone ships an alternate
|
|
391
|
+
adapter), the following gates apply before flipping `"private": false`:
|
|
392
|
+
|
|
393
|
+
1. **Public API audit.** Every export from `src/index.ts` is one we're
|
|
394
|
+
willing to support across a minor. Anything experimental goes under
|
|
395
|
+
a `/internal` subpath.
|
|
396
|
+
2. **Stable type surface.** No `any`, no `@ts-expect-error` in the
|
|
397
|
+
public surface, no `exactOptionalPropertyTypes` surprises.
|
|
398
|
+
3. **README per package**, not just the root.
|
|
399
|
+
4. **`CHANGELOG.md` is already disciplined** (Keep a Changelog format,
|
|
400
|
+
one section per version, dated).
|
|
401
|
+
5. **CI coverage gate ≥90%** (TICKET-048) is wired and green.
|
|
402
|
+
6. **Provenance & provenanced verification** — npm publish from CI
|
|
403
|
+
with Sigstore signatures, not a local laptop.
|
|
404
|
+
|
|
405
|
+
At that point this document grows a §11 "Publishing to npm" with
|
|
406
|
+
`pnpm -r publish --filter @serverless-ircd/irc-core --access public`
|
|
407
|
+
and a new `publish.yml` workflow. The git-tag-plus-GitHub-Release flow
|
|
408
|
+
above stays unchanged — npm publish becomes an *additional* step, not
|
|
409
|
+
a replacement.
|
|
410
|
+
|
|
411
|
+
---
|
|
412
|
+
|
|
413
|
+
## 11. Quick reference: cutting v0.X.Y
|
|
414
|
+
|
|
415
|
+
```bash
|
|
416
|
+
# 1. Pre-release checklist (docs/release.md §4).
|
|
417
|
+
git checkout main && git pull --ff-only origin main
|
|
418
|
+
pnpm install --frozen-lockfile
|
|
419
|
+
pnpm lint && pnpm typecheck && pnpm test && pnpm build
|
|
420
|
+
|
|
421
|
+
# 2. Branch, bump versions + CHANGELOG, open PR (§6, §7).
|
|
422
|
+
git checkout -b release/v0.X.Y
|
|
423
|
+
# ... edit 8 × package.json + CHANGELOG.md ...
|
|
424
|
+
git commit -am "chore(release): v0.X.Y"
|
|
425
|
+
git push -u origin release/v0.X.Y
|
|
426
|
+
# ... open PR, get it merged ...
|
|
427
|
+
|
|
428
|
+
# 3. Tag and push (§8.1).
|
|
429
|
+
git checkout main && git pull --ff-only origin main
|
|
430
|
+
git tag -a v0.X.Y -m "v0.X.Y"
|
|
431
|
+
git push origin v0.X.Y
|
|
432
|
+
|
|
433
|
+
# 4. GitHub Release (§8.2): paste CHANGELOG entry, publish.
|
|
434
|
+
|
|
435
|
+
# 5. Prod deploy (§8.3).
|
|
436
|
+
git checkout v0.X.Y
|
|
437
|
+
pnpm install --frozen-lockfile && pnpm build
|
|
438
|
+
pnpm deploy:cf:prod
|
|
439
|
+
node apps/cf-worker/scripts/smoke.mjs --url wss://<prod>.workers.dev
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Total wall-clock for a routine 0.x release: ~30 minutes including the
|
|
443
|
+
prod deploy and smoke check.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "serverless-ircd",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Serverless IRC daemon with a platform-agnostic core and Cloudflare Workers + AWS adapters",
|
|
6
6
|
"license": "BSD-3-Clause",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"typescript": "^5.9.3",
|
|
18
18
|
"vite": "^7.3.6",
|
|
19
19
|
"vitest": "^4.1.10",
|
|
20
|
-
"@serverless-ircd/aws-adapter": "0.
|
|
20
|
+
"@serverless-ircd/aws-adapter": "0.6.0"
|
|
21
21
|
},
|
|
22
22
|
"scripts": {
|
|
23
23
|
"build": "turbo run build",
|
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
type Nick,
|
|
52
52
|
type RawLine,
|
|
53
53
|
type RosterEntry,
|
|
54
|
+
type ServerConfig,
|
|
54
55
|
SystemClock,
|
|
55
56
|
emptyModes,
|
|
56
57
|
toSnapshot as toConnSnapshot,
|
|
@@ -119,6 +120,14 @@ export interface AwsRuntimeOptions {
|
|
|
119
120
|
*/
|
|
120
121
|
managementApi: ApiGatewayManagementApi | PostToConnection | null;
|
|
121
122
|
clock?: Clock;
|
|
123
|
+
/**
|
|
124
|
+
* Config-reload source for `REHASH`. The Lambda handler binds this to a
|
|
125
|
+
* closure that re-invokes {@link loadServerConfigFromLambdaEnv} against the
|
|
126
|
+
* invocation env (Secrets Manager / SSM / env vars). When omitted
|
|
127
|
+
* `reloadConfig` rejects so the actor applies a graceful error-suffixed
|
|
128
|
+
* `382` and retains the prior config.
|
|
129
|
+
*/
|
|
130
|
+
readonly configLoader?: () => Promise<ServerConfig>;
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
/**
|
|
@@ -133,6 +142,7 @@ export class AwsRuntime implements IrcRuntime {
|
|
|
133
142
|
private readonly handlers: AwsRuntimeHandlers;
|
|
134
143
|
private readonly managementApi: ApiGatewayManagementApi | PostToConnection | null;
|
|
135
144
|
private readonly clock: Clock;
|
|
145
|
+
private readonly configLoader: (() => Promise<ServerConfig>) | undefined;
|
|
136
146
|
|
|
137
147
|
constructor(opts: AwsRuntimeOptions) {
|
|
138
148
|
this.dynamo = opts.dynamo;
|
|
@@ -141,6 +151,7 @@ export class AwsRuntime implements IrcRuntime {
|
|
|
141
151
|
this.handlers = opts.handlers;
|
|
142
152
|
this.managementApi = opts.managementApi;
|
|
143
153
|
this.clock = opts.clock ?? SystemClock;
|
|
154
|
+
this.configLoader = opts.configLoader;
|
|
144
155
|
}
|
|
145
156
|
|
|
146
157
|
// -------------------------------------------------------------------------
|
|
@@ -210,6 +221,51 @@ export class AwsRuntime implements IrcRuntime {
|
|
|
210
221
|
}
|
|
211
222
|
}
|
|
212
223
|
|
|
224
|
+
/**
|
|
225
|
+
* Global cross-connection fan-out for `WALLOPS`. `Scan`s the `Connections`
|
|
226
|
+
* table filtered to rows whose `userModes.wallops` is `true`, then
|
|
227
|
+
* `PostToConnection`s each one (skipping `except`). The bound connection's
|
|
228
|
+
* own delivery goes through the in-process handlers (no APIGW round-trip),
|
|
229
|
+
* mirroring `send`.
|
|
230
|
+
*
|
|
231
|
+
* Cost: a DynamoDB `Scan` reads the ENTIRE table (chargeable per row read,
|
|
232
|
+
* not per match), so this is expensive at high connection counts. A fleet
|
|
233
|
+
* of 10k connections costs one full scan per WALLOPS plus one
|
|
234
|
+
* `PostToConnection` per `+w` recipient. Deployments should bound the
|
|
235
|
+
* oper's WALLOPS rate; a projected GSI on `userModes.wallops` would lift
|
|
236
|
+
* the scan to a targeted query (documented follow-up). The scan uses
|
|
237
|
+
* `FilterExpression` so only `+w` rows survive to the `PostToConnection`
|
|
238
|
+
* phase — the APIGW call count is bounded by the genuine recipient set.
|
|
239
|
+
*/
|
|
240
|
+
async broadcastWallops(lines: RawLine[], except?: ConnId): Promise<void> {
|
|
241
|
+
let startKey: Record<string, NativeAttributeValue> | undefined;
|
|
242
|
+
// Paginate the scan: DynamoDB caps a single Scan at 1 MB. Loop until
|
|
243
|
+
// no `LastEvaluatedKey` remains so every +w connection is reached.
|
|
244
|
+
do {
|
|
245
|
+
const result = await this.dynamo.send(
|
|
246
|
+
new ScanCommand({
|
|
247
|
+
TableName: this.tables.Connections,
|
|
248
|
+
FilterExpression: 'userModes.#m = :true',
|
|
249
|
+
ExpressionAttributeNames: { '#m': 'wallops' },
|
|
250
|
+
ExpressionAttributeValues: { ':true': true },
|
|
251
|
+
...(startKey !== undefined ? { ExclusiveStartKey: startKey } : {}),
|
|
252
|
+
}),
|
|
253
|
+
);
|
|
254
|
+
const items = (result.Items ?? []) as unknown as MarshalledConnection[];
|
|
255
|
+
for (const row of items) {
|
|
256
|
+
const connId = row.connectionId;
|
|
257
|
+
if (connId === except) continue;
|
|
258
|
+
// `send` short-cuts the bound connection through the in-process
|
|
259
|
+
// handlers and catches GoneException so vanished sockets are
|
|
260
|
+
// cleaned up rather than aborting the fan-out mid-loop.
|
|
261
|
+
if (row.userModes.wallops) {
|
|
262
|
+
await this.send(connId, lines);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
startKey = result.LastEvaluatedKey as Record<string, NativeAttributeValue> | undefined;
|
|
266
|
+
} while (startKey !== undefined);
|
|
267
|
+
}
|
|
268
|
+
|
|
213
269
|
// -------------------------------------------------------------------------
|
|
214
270
|
// Nick registry
|
|
215
271
|
// -------------------------------------------------------------------------
|
|
@@ -365,6 +421,19 @@ export class AwsRuntime implements IrcRuntime {
|
|
|
365
421
|
return out;
|
|
366
422
|
}
|
|
367
423
|
|
|
424
|
+
/**
|
|
425
|
+
* Re-invokes the bound config loader (Secrets Manager / SSM / env) and
|
|
426
|
+
* returns the fresh {@link ServerConfig}. The Lambda handler binds the
|
|
427
|
+
* loader at construction; when no loader was supplied this rejects so the
|
|
428
|
+
* actor's graceful failure path applies.
|
|
429
|
+
*/
|
|
430
|
+
async reloadConfig(): Promise<ServerConfig> {
|
|
431
|
+
if (this.configLoader === undefined) {
|
|
432
|
+
throw new Error('no config loader bound to AwsRuntime');
|
|
433
|
+
}
|
|
434
|
+
return this.configLoader();
|
|
435
|
+
}
|
|
436
|
+
|
|
368
437
|
// -------------------------------------------------------------------------
|
|
369
438
|
// Public helpers (re-exported surface)
|
|
370
439
|
// -------------------------------------------------------------------------
|
|
@@ -14,7 +14,13 @@
|
|
|
14
14
|
|
|
15
15
|
import type { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
|
|
16
16
|
import { PutCommand, ScanCommand } from '@aws-sdk/lib-dynamodb';
|
|
17
|
-
import
|
|
17
|
+
import {
|
|
18
|
+
type ParsedServerConfig,
|
|
19
|
+
type WsSubprotocol,
|
|
20
|
+
parseSecWsProtocolOffers,
|
|
21
|
+
selectSubprotocol,
|
|
22
|
+
wsFrameModeFor,
|
|
23
|
+
} from '@serverless-ircd/irc-core';
|
|
18
24
|
import { type AdmissionOutcome, decideConnectAdmission } from '../admission.js';
|
|
19
25
|
import { marshalConnection } from '../serialize.js';
|
|
20
26
|
import type { TablesConfig } from '../tables.js';
|
|
@@ -29,10 +35,24 @@ export interface ConnectParams {
|
|
|
29
35
|
now?: number;
|
|
30
36
|
/** Server config — consulted for the `maxClients` admission cap. */
|
|
31
37
|
serverConfig: ParsedServerConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Raw `Sec-WebSocket-Protocol` header offered by the client on the
|
|
40
|
+
* `$connect` upgrade, if present. Parsed into an ordered offer list and
|
|
41
|
+
* the first supported IRCv3 subprotocol is agreed (legacy fallback when
|
|
42
|
+
* none match or the header is absent).
|
|
43
|
+
*/
|
|
44
|
+
secWebSocketProtocol?: string | null;
|
|
32
45
|
}
|
|
33
46
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Outcome of a `$connect`. Mirrors {@link AdmissionOutcome} on the rejection
|
|
49
|
+
* branch and carries the negotiated IRCv3 {@link WsSubprotocol} on success so
|
|
50
|
+
* the dispatcher can echo it back to the client in the integration response.
|
|
51
|
+
* `subprotocol === null` signals a legacy (no-subprotocol) connection.
|
|
52
|
+
*/
|
|
53
|
+
export type ConnectOutcome =
|
|
54
|
+
| { readonly admitted: true; readonly subprotocol: WsSubprotocol | null }
|
|
55
|
+
| { readonly admitted: false; readonly statusCode: number; readonly reason: string };
|
|
36
56
|
|
|
37
57
|
/**
|
|
38
58
|
* Enforces the `maxClients` admission cap, then inserts the Connections
|
|
@@ -58,20 +78,31 @@ export type { AdmissionOutcome as ConnectOutcome } from '../admission.js';
|
|
|
58
78
|
* into {@link decideConnectAdmission}'s `perIp` branch. That schema
|
|
59
79
|
* change is tracked as a follow-up; the pure policy already supports it.
|
|
60
80
|
*/
|
|
61
|
-
export async function handleConnect(params: ConnectParams): Promise<
|
|
81
|
+
export async function handleConnect(params: ConnectParams): Promise<ConnectOutcome> {
|
|
62
82
|
const now = params.now ?? Date.now();
|
|
63
83
|
const total = await countConnections(params.dynamo, params.tables.Connections);
|
|
64
84
|
const outcome = decideConnectAdmission({ total }, { maxClients: params.serverConfig.maxClients });
|
|
65
85
|
if (!outcome.admitted) return outcome;
|
|
86
|
+
// IRCv3 WebSocket subprotocol negotiation: parse the offered
|
|
87
|
+
// `Sec-WebSocket-Protocol` list, select the first supported entry, and
|
|
88
|
+
// persist the resulting frame mode so every subsequent `$default`
|
|
89
|
+
// invocation recovers it (a Lambda `$connect` has no live socket to
|
|
90
|
+
// tag, unlike the CF adapter's hibernation tags). `null` means no
|
|
91
|
+
// supported subprotocol was offered → legacy framing, no mode column.
|
|
92
|
+
const chosen = selectSubprotocol(parseSecWsProtocolOffers(params.secWebSocketProtocol));
|
|
93
|
+
const mode = wsFrameModeFor(chosen ?? undefined);
|
|
66
94
|
const state = createInitialConnectionState(params.connectionId, now);
|
|
67
95
|
const row = marshalConnection(state, now);
|
|
96
|
+
if (mode !== 'legacy') {
|
|
97
|
+
row.wsMode = mode;
|
|
98
|
+
}
|
|
68
99
|
await params.dynamo.send(
|
|
69
100
|
new PutCommand({
|
|
70
101
|
TableName: params.tables.Connections,
|
|
71
102
|
Item: row,
|
|
72
103
|
}),
|
|
73
104
|
);
|
|
74
|
-
return
|
|
105
|
+
return { admitted: true, subprotocol: chosen };
|
|
75
106
|
}
|
|
76
107
|
|
|
77
108
|
/**
|