Skip to content

Use our capnweb fork with WebSocket-over-RPC support#1474

Merged
jonastemplestein merged 2 commits into
mainfrom
capnweb-websocket-fork
Jun 10, 2026
Merged

Use our capnweb fork with WebSocket-over-RPC support#1474
jonastemplestein merged 2 commits into
mainfrom
capnweb-websocket-fork

Conversation

@jonastemplestein

@jonastemplestein jonastemplestein commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Pins capnweb to our fork via a pnpm override in pnpm-workspace.yaml, so every workspace package (packages/streams, apps/os, the example app) and any transitive dependent (e.g. captun) resolves to it.

The fork adds support for passing WebSocket-bearing upgrade Responses over RPC — see iterate/capnweb#1 for the full design (stream-pair tunneling with flow control, claim-on-first-use lifetime, transport-equivalence test battery). This unblocks capnweb-over-capnweb tunneling for e2e tests of WebSocket-using services (upstream issue: cloudflare/capnweb#187).

How it's wired:

  • overrides.capnweb points at a prebuilt tarball attached to the fork's v0.8.0-websocket.1 release, built from iterate/capnweb@4d384fa (npm ci && npm run build && npm pack). The lockfile pins it by integrity hash. Same pattern as captun, which already installs from a pkg.pr.new tarball URL.
  • To update: build + npm pack in the fork, attach to a new release, bump the URL, pnpm install.

The first revision of this PR installed straight from git (github:iterate/capnweb#<sha>), relying on the fork's prepare script to build dist/ at install time. That worked locally but turned out to be environment-sensitive: CI's Linux runners produced a dist with JS but no type declarations (the test job passed, lint-typecheck failed with TS7016). The prebuilt tarball sidesteps install-time builds entirely and is byte-identical everywhere.

Verified locally: fresh install pulls the tarball with full dist/ including declarations, newWebSocketRpcSession et al. import fine, and packages/streams passes typecheck and all 67 node tests against it.

🤖 Generated with Claude Code

jonastemplestein and others added 2 commits June 10, 2026 20:42
Pins capnweb to iterate/capnweb (which adds support for passing
WebSocket-bearing upgrade Responses over RPC, iterate/capnweb#1) via a
pnpm override, so all workspace packages and transitive dependents get
the fork. Installed from git pinned to a sha; the fork's prepare script
builds dist on install, and capnweb is allowlisted in
onlyBuiltDependencies so pnpm runs it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CI showed that building the git dependency at install time is
environment-sensitive: the Linux runners produced a dist without type
declarations (runtime imports worked, typecheck failed), while local
installs built it completely. Instead of depending on install-time
builds at all, install a tarball built once (npm ci && npm run build &&
npm pack at iterate/capnweb@4d384fa) and attached to the fork's
v0.8.0-websocket.1 release. The lockfile pins it by integrity hash, and
capnweb no longer needs to be in onlyBuiltDependencies.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jonastemplestein jonastemplestein merged commit 9f55e2f into main Jun 10, 2026
5 checks passed
@jonastemplestein jonastemplestein deleted the capnweb-websocket-fork branch June 10, 2026 20:01
jonastemplestein added a commit that referenced this pull request Jun 10, 2026
… legacy define compat, nested workspace.git (#1476)

The parked debts from itx-next.md, bandaid pulled to the max per review
— **no backcompat anywhere** (prd/preview/dev itx data is erasable).

## CodemodeSession tombstone — deleted

The tombstone DO class, its `codemode-session-local` namespace, the
`CODEMODE_SESSION` binding, and the vitest wrangler entries are gone.
Alchemy emits the `deleted_classes` migration (mechanism proven in
#1464). Streams that still carry durable subscriber events dialing the
namespace will error on delivery — accepted, the data is being erased.

## executeCodemodeFunctionCall — protocol fully deleted (~2.5k lines)

Every capability entrypoint loses its legacy dispatch method (agents,
gmail, repos, secrets, slack, streams, workspace, openapi-bridge,
AiCapability, OrpcCapability, test entries), along with
`legacy-codemode-call.ts` and each wrapper's dead arg-validation
helpers. Two callers were still alive and got clean replacements:
- the agent chat/debug tool path now dials
`AgentDurableObject.callAgentTool({ tool, path, args, callId })`
- the ingress test entry upserts secrets via `OrpcCapability.call({
path: ["secrets", "upsert"], … })`

`packages/shared/src/codemode/` → `packages/shared/src/type-tree/`
(context-proxy deleted — only importer was a deleted legacy test
provider); generated typing identifiers de-codemoded (`ItxConsole`,
`generateContextTypesFromJsonSchema`, …).

## Registry legacy compat — deleted

`caps.define` takes a `target` (SerializableCapTarget) **only**, end to
end (registry, ContextDO, Project DO, handle, REPL typings): the legacy
`source`/`kind: "worker" | "facet"` inputs, the `codeId` spelling of
`cacheKey`, stored `worker`/`facet` kinds, the `source_json` rollback
column + sync writes, and all read-side normalization are gone. All
callers (browser REPL examples, every e2e suite) spell rpc/source
targets directly.

## Nested `itx.workspace.git.*` — deleted

Nested RpcTargets returned from entrypoint getters don't survive RPC
boundaries; the flat
`gitClone`/`gitAdd`/`gitCommit`/`gitPush`/`gitStatus` methods are the
surface. The agent preset prompt was actively teaching the broken nested
spelling — fixed.

## allowedHosts debt — closed as misdiagnosis

The config is `allowedHosts: true`; the 403/502s came from a wedged vite
process behind the still-connected cloudflared tunnel. Documented in
itx-next.md; e2e through `os.iterate-dev-jonas.com` verified passing.

## Verification

`pnpm typecheck` / `lint` / `knip` / `format` green; apps/os unit tests
green; workerd suites (`test:project-ingress` 6/6,
`test:itx-server-handle` 5/5 pre-#1472, `test:type-tree` 113/113) green;
itx e2e (itx, fork, http, subscribe — 20 tests) green against a local
dev server on the merged tree, including the post-#1474 capnweb fork.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Large breaking change to capability registration, MCP/ingress
execution paths, and production streams that may still dial removed
CodemodeSession subscribers; preview/dev data is intentionally erasable.
> 
> **Overview**
> This PR **removes the last codemode-era wiring** and tightens the itx
capability model with **no read-side backcompat**.
> 
> **CodemodeSession** is fully removed: the tombstone Durable Object,
`CODEMODE_SESSION` binding/namespace in Alchemy and vitest wrangler
configs, worker exports, and the dedicated test script. MCP and tests
now assume execution goes through the shared itx runner.
> 
> **Legacy `executeCodemodeFunctionCall` dispatch is deleted** across
capability entrypoints (~2.5k lines), along with
`legacy-codemode-call.ts` and shared `codemode/context-proxy`. Call
sites move to itx-native paths (`callAgentTool`, `OrpcCapability.call`,
OpenAPI `call({ path, args })`). **`packages/shared` codemode** is
renamed to **`type-tree`** with de-codemoded type-generation names.
> 
> **`caps.define` is target-only**: required `SerializableCapTarget`, no
`source`/`kind`/`codeId`, no `source_json` column or
`normalizeCapTarget` on read—stored rows use `target_json` verbatim.
Docs, REPL typings, e2e, and browser REPL examples all use `{ type:
"rpc", worker: { type: "source", source: { cacheKey, … } } }`.
> 
> **Workspace git** drops the nested `git` RpcTarget getter; agents,
presets, e2e, and preview scripts use flat **`gitClone` / `gitAdd` /
`gitCommit` / `gitPush` / `gitStatus`**. **itx-next.md** records
resolved debts (legacy define, nested git, allowedHosts misdiagnosis).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
9cf0edd. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- CLOUDFLARE_PREVIEW -->
## Environment Config Lease
<!-- CLOUDFLARE_PREVIEW_STATE -->
<!--
{
  "apps": {
    "os": {
      "appDisplayName": "OS",
      "appSlug": "os",
      "status": "deployed",
      "updatedAt": "2026-06-10T20:40:22.067Z",
      "headSha": "9cf0eddaebfbcf1516be75f3e6acffdc31004f08",
      "message": null,
      "publicUrl": "https://os.iterate-preview-6.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27304663825",
      "shortSha": "9cf0edd"
    },
    "semaphore": {
      "appDisplayName": "Semaphore",
      "appSlug": "semaphore",
      "status": "deployed",
      "updatedAt": "2026-06-10T20:31:34.680Z",
      "headSha": "0f50df98f943df63d8133e6be05dcdb33e77305c",
      "message": null,
      "publicUrl": "https://semaphore.iterate-preview-6.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27304258909",
      "shortSha": "0f50df9"
    }
  },
  "environmentConfigLease": {
    "dopplerConfig": "preview_6",
    "leasedUntil": 1781127403360,
    "leaseId": "6fe10f72-e7bb-431a-9402-1abae9841129",
    "slug": "preview-6",
    "type": "environment-config-lease"
  }
}
-->
<!-- /CLOUDFLARE_PREVIEW_STATE -->
Lease: `preview-6`
Doppler config: `preview_6`
Type: `environment-config-lease`
Leased until: 2026-06-10T21:36:43.360Z

### OS
Status: deployed
Commit: `9cf0edd`
Preview: https://os.iterate-preview-6.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27304663825)
Updated: 2026-06-10T20:40:22.067Z

### Semaphore
Status: deployed
Commit: `0f50df9`
Preview: https://semaphore.iterate-preview-6.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27304258909)
Updated: 2026-06-10T20:31:34.680Z
<!-- /CLOUDFLARE_PREVIEW -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
jonastemplestein added a commit that referenced this pull request Jun 10, 2026
… delegation; durable-object refs (#1482)

The remaining itx roadmap on latest main, no backcompat (prd is
redeployable): §8's endgame — the hardwired kernel becomes ordinary
capability definitions — plus the last unimplemented WorkerRef kind.

## repos / workspace / worker are platform defaults now

All three move into the `platform:project` code context
(`src/itx/code-contexts.ts`): `repos` and `workspace` as
ReposCapability/WorkspaceCapability loopbacks, `worker` through the
ProjectWorker forwarder (path-call hop, members inner replay against the
default export). Their handle getters are deleted, their names leave the
reserved list (shadowable — the point), and `callWorkerFunction` on the
Project DO dies (the forwarder path covers it). What remains hardwired
is exactly the composition a cap definition cannot express: `caps`,
`streams` (the access model and global-namespace gating live there),
`fetch` (Law 5), `fork`, `project`, `projects`, `describe`.

## Chain delegation carries the originating context

The enabling protocol change (allowed by no-backcompat): `itxInvoke`
gains `origin`, set by the first delegating hop and preserved up the
chain; the registry's dial-time `context` attribution prop uses it. This
is what makes a context-SCOPED cap correct as an inherited definition —
`workspace` resolves to `itx:ctx_…` for a forked child and `itx` for the
project even though the definition lives two links up.
WorkspaceCapability now derives its workspace id from the injected
context (explicit `workspaceId` still wins; the agent DO's pre-clone
wiring is unchanged and uses the same derivation).

New e2e: a child fork writes to its workspace; neither the project
context nor a sibling fork can read the file.

## durable-object WorkerRef refs ship, config-gated

`{ type: "durable-object", binding, name }` resolves via
`env[binding].getByName(name)` with the normal members/path-call
dispatch. The namespace allowlist (`DIALABLE_DURABLE_OBJECTS`) is
**empty by default**: every platform namespace (PROJECT, STREAM, …) is
keyed across all projects, so allowlisting one would let any project
handle dial any other project's objects by name. Deployments opt in via
`APP_CONFIG_ITX.dialableDurableObjects`; per-project name scoping is the
documented open design before any namespace can join the defaults. e2e
asserts the define-time refusal.

## Behavioral changes

- `itx.worker.fetch` no longer special-cases to project ingress — it
replays `fetch` on the worker's default export like any other member.
The project homepage is the ingress URL.
- `describe()` on a fresh project now lists four inherited caps (`ai`,
`repos`, `workspace`, `worker`) with `owner: "platform:project"`.

## Deferred, with reasons (in itx-next.md)

- `streams` stays kernel: resolution checks access, which a cap
definition cannot express.
- REPL-consumes-types.ts (§5): the hand-maintained editor typings stay,
comments updated.
- UrlDial through the egress intercept tunnel: the capnweb fork (#1474)
only helps capnweb hops; the UrlDial → Project DO hop is Workers jsrpc,
which still cannot carry a WebSocket-bearing Response.

## Verification

`pnpm typecheck` / `lint` / `knip` / `format` green; apps/os unit tests
green (210); itx e2e (itx, fork incl. the new isolation test, http,
subscribe — 21 tests) green against a local dev server.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **High Risk**
> Protocol and dispatch changes (`origin`, workspace scoping, removed
`callWorkerFunction`) affect fork isolation and worker routing;
empty-default DO allowlist is security-critical if misconfigured.
> 
> **Overview**
> Continues **§8 “cap #0 disappears”**: **`repos`**, **`workspace`**,
and **`worker`** move from hardwired `Itx` getters into the
**`platform:project`** code context as ordinary loopback caps
(`ReposCapability`, `WorkspaceCapability`, `ProjectWorker`). Those names
leave the reserved list (shadowable); **`callWorkerFunction`** on the
Project DO is removed in favor of the existing forwarder path.
> 
> **Chain delegation now carries the originating context**:
**`itxInvoke`** accepts optional **`origin`**, set on the first
delegating hop and forwarded up the chain; the registry uses it for
dial-time **`context`** attribution so inherited caps stay
**context-scoped**—notably **`workspace`** (`itx` vs `itx:ctx_…` per
fork). **`WorkspaceCapability`** derives **`workspaceId`** from injected
**`context`** when not explicit.
> 
> **`durable-object`** WorkerRefs are implemented behind an
**empty-by-default** **`DIALABLE_DURABLE_OBJECTS`** allowlist, widened
only via **`APP_CONFIG_ITX.dialableDurableObjects`**.
> 
> **Security / behavior**: **`itx.project`** blocks **`itx*`** registry
methods (spoofing **`origin`**); **`itx.worker.fetch`** no longer routes
to ingress—it replays the worker default export like any member. Docs,
REPL typings, and e2e (workspace isolation, DO define refusal) updated.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
e3e0f0a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

<!-- CLOUDFLARE_PREVIEW -->
## Environment Config Lease
<!-- CLOUDFLARE_PREVIEW_STATE -->
<!--
{
  "apps": {
    "os": {
      "appDisplayName": "OS",
      "appSlug": "os",
      "status": "deployed",
      "updatedAt": "2026-06-10T22:03:33.498Z",
      "headSha": "e3e0f0af06e6e51ae95a5c2c316eaf6271f9dc3d",
      "message": null,
      "publicUrl": "https://os.iterate-preview-5.com",
"runUrl": "https://github.com/iterate/iterate/actions/runs/27309107590",
      "shortSha": "e3e0f0a"
    }
  },
  "environmentConfigLease": {
    "dopplerConfig": "preview_5",
    "leasedUntil": 1781132433085,
    "leaseId": "826c2bc4-fe41-46ae-a4d4-19a1b088d8d7",
    "slug": "preview-5",
    "type": "environment-config-lease"
  }
}
-->
<!-- /CLOUDFLARE_PREVIEW_STATE -->
Lease: `preview-5`
Doppler config: `preview_5`
Type: `environment-config-lease`
Leased until: 2026-06-10T23:00:33.085Z

### OS
Status: deployed
Commit: `e3e0f0a`
Preview: https://os.iterate-preview-5.com
[Workflow
run](https://github.com/iterate/iterate/actions/runs/27309107590)
Updated: 2026-06-10T22:03:33.498Z
<!-- /CLOUDFLARE_PREVIEW -->

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant