docs/effect-baseline
Effect baseline (WP-T3-32) reference
Owner document for spec 10 §2 B5 — "one pinned Effect, centrally owned by WP-T3-32; no per-package Effect versions" (docs/specs/platform-gaps/10-t3code-migration.md). Stage 1 (this document, landed 2026-09-28 on phase0/hard-split-registry-guards) puts the pin, the patch mechanism and the register in place before any Effect code is imported (roadmap 50 §6 risk 1). Stage 2 (apps re-platform, engine floors in CI) depends on WP-T3-01 and is listed in §7 as deferred, not done.
Continue reading
Nearby pages in the same section.
Effect baseline (WP-T3-32)
Owner document for spec 10 §2 **B5** — "one pinned Effect, centrally owned by WP-T3-32; no per-package Effect versions" (docs/specs/platform-gaps/10-t3code-migration.md). Stage 1 (this document, landed 2026-09-28 on phase0/hard-split-registry-guards) puts the pin, the patch mechanism and the register in place **before any Effect code is imported** (roadmap 50 §6 risk 1). Stage 2 (apps re-platform, engine floors in CI) depends on WP-T3-01 and is listed in §7 as deferred, not done.
1. The pin
| Package | Version | Mechanism |
|---|---|---|
effect | 4.0.0-rc.115 | root package.json overrides.effect (npm's workspace-wide pin) + root devDependencies.effect so the version is resolved in package-lock.json even while no workspace imports it |
@effect/vitest | 4.0.0-rc.115 | root overrides["@effect/vitest"] + root devDependencies (pinned, installed **unpatched** — §3) |
patch-package | 8.0.1 | root devDependencies; postinstall: patch-package --error-on-fail |
Root devDependencies is where this repository keeps shared dev tooling (vitest, @vitest/coverage-v8, docusaurus, markdownlint-cli2), and no @fabricstack/* unit exists yet to own the dependency, so the pin lives at the root rather than in a dedicated workspace (which would also have needed a spec 15 §3.3 name). When the tracked forks land (WP-T3-01…04), each declares "effect": "4.0.0-rc.115" verbatim; scripts/ci/effect-baseline.test.mjs rejects any other spec (range, caret, different version) in any workspace manifest, and any @effect/* package that is not an exact version.
**Pin owner:** fabric maintainers (Tal). Spec 10 assigns B5 to WP-T3-32 as a work package and names no person; the owner acts through WP-T3-32 (stage 1 here, stage 2 later) and carries the §4 rc→stable watch item into the WP-RM-02 cadence register (docs/specs/platform-gaps/50-roadmap-workpackages.md "WP-RM-02" AC3 lists "the Effect rc→stable watch item with its trigger criteria from docs/EFFECT-BASELINE.md").
**Enforced by** npm run test:effect-baseline (13 node:test cases, CI test job right after npm ci): one exact version in overrides mirrored in devDependencies; npm ls effect --all --json and npm ls @effect/vitest --all --json each resolve exactly one version equal to the pin; no per-package versions; the patch is applied (§2) and @effect/vitest is unpatched (§3); the inventory and this document carry what the spec requires.
2. Patch provenance
| Upstream project | T3 Code (t3code), **T3 Tools Inc., MIT** (intent D15). Attribution: third_party/t3code-patches/LICENSE + PATCHES.md. |
| Snapshot | commit de251fc2971a884cb5b1305ba4daf309dc8cccb0 (read-only reference; never imported wholesale) |
| Upstream file | patches/effect@4.0.0-rc.115.patch, wired by pnpm-workspace.yaml patchedDependencies effect@4.0.0-rc.115 |
| Fabric file | patches/effect+4.0.0-rc.115.patch (patch-package naming) |
| Port | path prefixes only — a/<file> → a/node_modules/effect/<file> on the diff --git, ---, +++ lines; hunk bodies byte-identical to upstream (verified by diff of the non-header lines at port time) |
| Applied by | postinstall: patch-package --error-on-fail — a patch that no longer applies fails the install instead of being skipped |
What the patch changes (5 files, 23 hunks) and how each is pinned by the regression test:
| Area | Hunks | Behaviour | Regression assertion |
|---|---|---|---|
dist/unstable/rpc/RpcClient.js + .d.ts | 17 + 2 | New RequestHooks service (effect/rpc/RpcClient/RequestHooks) with onRequestStart/onRequestChunk/onRequestExit/onRequestInterrupt, threaded through makeNoSerialization; ConnectionHooks gains onPing/onPong/onPingTimeout; the socket pinger tolerates three missed pongs before it times out and onPong becomes an Effect | import('effect/unstable/rpc/RpcClient') → RequestHooks is a service class with that key, and the .d.ts declares it (public surface). The pinger internals are pinned on the patched source text (fallback, marked in the test). |
dist/unstable/http/HttpClientResponse.js + src/…/HttpClientResponse.ts | 1 + 1 | cookies reads headers.getSetCookie?.() ?? [] so a response whose headers is not a web Headers no longer throws | HttpClientResponse.fromWeb(req, { status, headers: {…} }).cookies is empty instead of a TypeError; a real Response still parses Set-Cookie (control). |
dist/unstable/ai/McpServer.js | 2 | HTTP DELETE <path> ends the MCP session (400 without the session header, 404 unknown, 204 deleted) instead of the 405 the stock layerHttp returned | Pinned on the patched source text (fallback: needs an HttpRouter + RpcServer protocol stack to exercise). |
The patch file is additionally parsed with patch-package's own parser (5 files, the hunk counts above) and re-applied with --error-on-fail inside the test, so a corrupted or pnpm-named file is caught, not ignored.
The 22 other upstream patches are inventoried with a decision each in docs/specs/platform-gaps/phase0/t3code-patches-inventory.md (1 ported, 1 dropped as toolchain-only, **17** dropped as mobile-only — not the 19 the WP brief assumed, because @legendapp/list and @pierre/diffs are also consumed by t3:apps/web — and 4 deferred to the WPs that land their consumers).
3. `@effect/vitest`: pinned, deliberately unpatched
The upstream @effect__vitest@4.0.0-rc.115.patch has exactly one effect: every import … from "vitest" / export * from "vitest" in dist/ and src/ becomes vite-plus/test, so the package runs under Voidzero's private vite-plus test runner. Spec 10 §2 excludes vite-plus and WP-T3-32 stage 2 re-platforms on stock vitest, under which the *unpatched* package's vitest imports are exactly what is wanted. Porting the patch would make the package import a module fabric does not install. Decision: **DROPPED-toolchain**; the test asserts the installed dist/index.js, dist/internal/internal.js and dist/utils.js import vitest and never vite-plus, and that no @effect+vitest*.patch exists.
**Stage-2 input, recorded here so it is not a surprise:** @effect/vitest@4.0.0-rc.115 declares peerDependencies.vitest: ">=5.0.0 <6.0.0" while fabric pins vitest 4.1.6 (root devDependencies). The pin is therefore resolved and locked today (installs pass under --legacy-peer-deps, which CI already uses) but the package is **not yet usable as a test harness in fabric** — the vitest major bump is part of the stage-2 Vite/vitest re-platform decision, together with the vite/@vitejs/plugin-react lines the apps will need.
4. Unstable-namespace usage register
Research R2 §15 / §18.12 item 3: adopting the server, contracts and the protocol forks means adopting effect/unstable/{rpc,sql,process,cli,http} — namespaces the Effect team may still rename or reshape before 4.0.0 stable. Counts are non-test-excluded *.ts/*.tsx files importing the namespace in the t3code snapshot (grep -rl), by workspace; "fabric unit" is the spec 15 §3.3 name the code lands under.
| Namespace | t3code usage at the snapshot (files) | Fabric units that will use it (landing WP) | Used in fabric today | Owner | Transition criterion (rc→stable) |
|---|---|---|---|---|---|
effect/unstable/rpc | server 7, effect-acp 6, client-runtime 6, contracts 1, relay 1, mobile 1 | @fabricstack/contracts (WP-T3-02), @fabricstack/acp (WP-T3-03), @fabricstack/codex-protocol (WP-T3-04), @fabricstack/client-runtime (WP-T3-24), @fabricstack/environment (WP-T3-19), @fabricstack/relay (WP-T3-31) | none | fabric maintainers (Tal) | Rpc/RpcGroup/RpcClient/RpcServer published outside unstable/ (or documented as final), and the RequestHooks behaviour of §2 either upstream or re-ported; the 144-Rpc.make contracts surface (R2 §18.12 item 4) compiles unchanged or with a mechanical codemod |
effect/unstable/sql | server 118, desktop 6, shared 2, relay 1 | @fabricstack/environment (WP-T3-19: the node:sqlite Effect SQL client, t3:packages/shared/src/nodeSqliteClient.ts), @fabricstack/relay (WP-T3-31: @effect/sql-pg) | none | fabric maintainers (Tal) | SqlClient/SqlSchema/migrator stable; the event-sourced persistence (decider/projector commit in one transaction — R2 §18.12 item 5) replays a captured event log unchanged across the bump |
effect/unstable/process | server 97, desktop 23, codex-app-server 6, effect-acp 6, ssh 5, tailscale 2, shared 2, scripts 19 | @fabricstack/environment (WP-T3-19; provider spawns for the ACP/Codex lanes WP-T3-06…13), @fabricstack/acp / @fabricstack/codex-protocol (WP-T3-03/04 client spawners), @fabricstack/ssh (WP-T3-28), @fabricstack/tailscale (WP-T3-29), @fabricstack/console-desktop (WP-T3-26) | none | fabric maintainers (Tal) | ChildProcessSpawner/ChildProcess stable with the same spawn/stdio/kill semantics; the ACP mock-agent protocol tests (spec 30 Q-07) green |
effect/unstable/cli | server 26, effect-acp 1, scripts 16 | @fabricstack/environment (WP-T3-19: the environment-server bin is an effect/unstable/cli Command tree, t3:apps/server/src/bin.ts); build scripts if adopted by spec 40 | none | fabric maintainers (Tal) | Command/Flag/Argument stable; the environment-server subcommand tree and --help output golden-tested before and after |
effect/unstable/http | server 94, relay 14, client-runtime 13, desktop 13, web 10, shared 6, contracts 5, mobile 5, ssh 2, tailscale 1, codex-app-server 1, effect-acp 1, scripts 5 | @fabricstack/environment (WP-T3-19: HTTP + WS via Effect platform, t3:apps/server/src/{http,ws}.ts), @fabricstack/relay (WP-T3-31), @fabricstack/client-runtime (WP-T3-24), @fabricstack/contracts (WP-T3-02: environmentHttp.ts HttpApi groups), @fabricstack/console-web/console-desktop (WP-T3-25/26), @fabricstack/ssh/tailscale (WP-T3-28/29) | none | fabric maintainers (Tal) | HttpClient/HttpRouter/HttpServer/HttpApi stable; the getSetCookie fallback of §2 upstream or unnecessary; DPoP/pairing auth (WP-T3-30) conformance suite green |
Watch: other unstable/* seen in the snapshot | reactivity 218, httpapi 36, observability 14, socket 11, ai 9 (the MCP server patch of §2 lives here), persistence 6, net 4, encoding 3, arbitrary 1 | added to this table when the landing WP first imports them | none | fabric maintainers (Tal) | same rule: no namespace is imported by a fabric unit before it has a row here |
**rc→stable watch item** (standing WP-RM-02 cadence-register entry; owner as above). The pin moves from 4.0.0-rc.115 to a stable 4.x only when all of the following hold, and only via the §6 playbook:
1. npm view effect dist-tags.latest is a non-prerelease 4.x, and its changelog covers every unstable/* namespace in the table (graduated, renamed with a codemod, or explicitly kept unstable with a stability promise). 2. The three patched behaviours of §2 are either in the stable release, re-ported as a new patches/effect+<version>.patch with the regression test updated, or dropped with a written reason in the inventory. 3. @effect/vitest has a matching stable release whose vitest peer range includes the vitest fabric runs after stage 2. 4. Every tracked fork (contracts, acp, codex-protocol, ssh, tailscale) and apps/environment compiles and tests on the candidate (the stage-2 build matrix, AC3). 5. npm run test:effect-baseline is green on the candidate (singleton, no per-package versions, patch state as decided).
Until then the pin is exact everywhere: no ^, ~ or latest for effect or @effect/* in any manifest (the test rejects them).
5. Node floors (recorded now as stage-2 input; not enforced by this stage)
Quoted from spec 10 "WP-T3-32" and research R2 §15 so stage 2 encodes them without re-deriving:
- Workspace engines **≥22.16** — "workspace engines ≥22.16 (environment server
^22.16 || ^23.11 || >=24.10— R2 §15)"; the environment server's ownenginesrange is^22.16 || ^23.11 || >=24.10(t3:apps/server/package.json). node:sqlite**≥22.5** — "node:sqlitedependency (Node 22.5+)" (R2 §15); fabric already runs the gateway on a22.13.0floor for the same module (.github/workflows/ci.ymlgateway-node-engine, FIX-008).- SEA build hosts **≥25.7** — "SEA build requires Node 25.7+ host" (R2 §15,
t3:apps/server/scripts/cli.ts:160); consumed by spec 40 P-03 / WP-PKG-03 AC6, never by the workspace engines floor (spec 40 §1.4 keeps the two apart). - Today: the root manifest declares no
engines; workspace floors range from>=18.0.0(packages/adapters/extensions) to>=22.13.0(adapters-cli,adapters-gateway); CI runs Node 22 with a22.13.0matrix leg. Raising the workspace floor to ≥22.16 and adding the sub-floor negative run (AC5) is stage 2.
6. Upgrade playbook
1. **Decide** under §4 (rc→rc bumps need only items 2 and 5; rc→stable needs all five). Record the decision in a new section of docs/specs/platform-gaps/phase0/effect-baseline.md (or its successor record). 2. **Move the pin in one place per package**: root package.json overrides.effect and devDependencies.effect (same string), likewise @effect/vitest; every workspace manifest that declares effect (verbatim copies of the same string — the test enforces it). 3. **Re-port the patch**: git mv patches/effect+<old>.patch patches/effect+<new>.patch; npm install --legacy-peer-deps (CI's flags); if patch-package --error-on-fail rejects a hunk, open node_modules/effect/... at the rejected site, decide per hunk (upstream already has it → drop the hunk; drifted → re-apply by hand), then regenerate with npx patch-package effect and diff the result against the old file hunk by hunk. Never delete package-lock.json to "fix" an install. 4. **Update the regression test** for any hunk whose behaviour changed (scripts/ci/effect-baseline.test.mjs: EXPECTED_PATCH_FILES hunk counts and the behaviour assertions) and the tables in §2 and third_party/t3code-patches/PATCHES.md. 5. **Prove**: npm run test:effect-baseline; npm ls effect --all shows one version; the stage-2 build matrix (tracked forks + apps/environment) once it exists; node scripts/ci/publish-isolation-guard.cjs --self-test and npm run guard:packages unaffected. 6. **Refresh the inventory** if the t3code snapshot moved (t3code-patches-inventory.md: new upstream patch list, decisions re-checked) and the attribution record's snapshot SHA. 7. **Commit** with the pin in the subject (build(effect): bump pin to effect@<version>, re-port patch) and push; the CI test job runs the baseline after npm ci.
7. Stage 2 — explicitly deferred (depends on WP-T3-01)
Not done in stage 1, per spec 10 "WP-T3-32" and the roadmap (M2 exit criterion 7; risk rows 2–3):
- **Apps re-platform**: stock Vite + vitest + fabric's
tscreplace vite-plus (vite: npm:@voidzero-dev/vite-plus-core) and TS7/tsgo inapps/console-web,apps/console-desktop,apps/environmentbuild scripts; grep gate for zero@voidzero-dev/*in the dependency graph (AC4). Includes the vitest major decision that unblocks@effect/vitest(§3). - **Engine floors in CI**: root and workspace
engines≥22.16, a sub-floor Node run that fails the engines check (AC5), the Node matrix inci.yml/live-stack.yml, SEA host floor recorded for WP-PKG-03. - **Build matrix**: all five tracked forks +
apps/environmentcompile and test under stage-1 tooling (AC3) — impossible until WP-T3-01 imports them. - **AC1–2, AC6** are the stage-1 acceptance criteria and are what this document and
npm run test:effect-baselinedeliver.
8. Files
| Path | Role |
|---|---|
package.json (root) | overrides, devDependencies, postinstall, test:effect-baseline |
patches/effect+4.0.0-rc.115.patch, patches/README.md | the ported patch and the directory's rules |
scripts/ci/effect-baseline.test.mjs | singleton, per-package-version, patch-regression, patch-file, @effect/vitest, inventory, doc and CI-wiring tests |
.github/workflows/ci.yml | test job step "Effect baseline (single pin, patch regression, inventory)" |
docs/specs/platform-gaps/phase0/t3code-patches-inventory.md | the 23 upstream patches with decisions |
docs/specs/platform-gaps/phase0/effect-baseline.md | execution record of stage 1 |
third_party/t3code-patches/ | MIT attribution and provenance (D15) |