Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,14 @@ an unclassified, double-classified, stale, or reasonless entry.
Promise-based and name no Effect type: a deployment author should never need
a second async paradigm to write a connector, so convert at the boundary,
not in the caller. Effect is a hard dependency pinned to one exact version,
never a range, and it is the version Alchemy pins, so a deployment that uses
both resolves one Effect rather than two. `effect/unstable/*` modules are
allowed, which is exactly why the pin is exact: they break in minor releases,
never a range, and compatible with Alchemy's peer requirement, so a deployment
that uses both resolves one Effect rather than two. Modules tagged
`@stability unstable` are allowed behind subpaths: they can break in minor releases,
so an upgrade is its own deliberate pull request, never a drive-by in another
change. `effect/testing` stays out of `src/` (see import-graph purity). The
MCP edges do not move with the core — `@modelcontextprotocol/server` upward,
the SDK client downstream; Effect's `McpServer` is parked until the Effect
core is stable and those edges are re-proven.
the SDK client downstream. Replacing either requires separate proof of the
wire contracts and request lifetimes; the stable core release is not that proof.
- **Style.** There is no formatter. Match the surrounding code. The docs voice
is precise, occasionally wry, and always explains *why* — don't flatten it
into boilerplate.
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,44 @@ All notable changes to this package are documented here.

## Unreleased

The runtime now uses stable Effect v4, with fixes for cancellation, resumed-write
limits, storage failures, and operator pages. Deployment APIs and the eight MCP
tools keep their existing contracts; no configuration migration is required.
Duplicate API tool names now fail at construction, and doctor refuses redirects
instead of forwarding deployment credentials. Artifact render checks use the
deployment's viewer theme.

### Changed

- Pin Effect to `4.0.0`, replacing `4.0.0-rc.117`, and align the optional
Alchemy setup instructions with the stable pin.
- Require TypeScript 5.9.3 or newer within 5.x for repository and Node-template
development, matching Effect v4's TypeScript 5.9 minimum.

### Fixed

- Keep queued catalog mutations alive across Worker requests while preserving
cancellation and storage ordering (#623).
- Release failed access-token reservations before lookup publication, close
failed OAuth callback transports, and persist accepted rotating refresh tokens
after owner cancellation without reviving disconnected generations (#624)
(#625) (#626).
- Count historical writes across divergent resumptions and bound resumed write
answers by the journal limit, retaining truthful outcomes for writes already
sent (#627) (#628).
- Roll back rejected file-storage mutations, preserve prototype-named keys,
expire entries at their TTL boundary, and quarantine invalid snapshot roots
without losing their original bytes (#629) (#630) (#631) (#632).
- Ignore superseded artifact-list responses, preserve newly created tokens when
an older list finishes, and reload a confined artifact shell when signing in
again (#633) (#634) (#635).
- Advance artifact listings through empty continuation pages, refuse already
cancelled render checks, and validate Markdown with the viewer's theme across
writes and refreshes (#636) (#637) (#638).
- Prevent doctor redirects from forwarding Cloudflare Access credentials,
reject unreadable successful Notion responses, and reject duplicate API tool
names before classification and dispatch can disagree (#639) (#640) (#641).

## 0.26.3 — 2026-09-28

This patch lets downstream OAuth connections use a public client metadata
Expand Down
10 changes: 9 additions & 1 deletion bin/connecta.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -132,10 +132,18 @@ const DOCTOR_TIMEOUT_MS = 10_000;

async function doctorFetch(url, init = {}) {
try {
return await fetch(url, {
const response = await fetch(url, {
...init,
redirect: "manual",
signal: AbortSignal.timeout(DOCTOR_TIMEOUT_MS),
});
if ([301, 302, 303, 307, 308].includes(response.status)) {
await response.body?.cancel().catch(() => {});
throw new Error(
`HTTP ${response.status} redirect refused: doctor sends authentication only to the configured deployment URL.`,
);
}
return response;
} catch (error) {
if (
error instanceof Error &&
Expand Down
47 changes: 44 additions & 3 deletions documentation/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,8 @@ shape from building, in someone else's repository rather than this one.

## Effect inside

The core runs on Effect v4; no API a deployment touches does. `createConnecta`,
The core runs on stable Effect v4, exact-pinned to `4.0.0`; no API a deployment
touches does. `createConnecta`,
`remoteMcp()`, `api()`, the `Connector` contract, and every shipped `.d.ts`
are Promise-shaped and name no Effect type, so a connector author never meets a
second async paradigm. The reason for the rewrite is the first section of this
Expand Down Expand Up @@ -569,8 +570,48 @@ the operator and activity routes cost about +100 KB gzip on `./ui` and +130 KB
on `./activity`, and matching the wire format meant opting out of most of what
it does: unowned paths fall through rather than 404, a wrong method is a JSON
405, and the content-type and size checks run before a body is read. The root
entry may import only `effect` itself (`test/purity.test.ts`); nothing in
`src/` uses `effect/unstable/*`, which would have to stay behind a subpath.
entry may import only `effect` itself (`test/purity.test.ts`). Effect v4's
area imports, such as `effect/http-api` and `effect/ai`, would have to stay
behind a subpath. APIs tagged `@stability unstable` can still change in minor
releases, so stable v4 keeps the exact pin.

The stable `4.0.0` release was re-evaluated on 2026-10-01 with
[`scripts/probes/effect-v4-evaluation.mjs`](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation.mjs).
Run `npm run build`, then `node scripts/probes/effect-v4-evaluation.mjs output.json`.
The [raw observations](https://github.com/zackbart/connecta/blob/main/scripts/probes/effect-v4-evaluation-2026-10-01.json)
are synthetic Node requests and neutral esbuild bundles, not live-client or
workerd lifetime verification. Bundle figures add representative prototypes
alongside the current code; they do not claim savings from removing old code.

Two native input schemas with validation and JSON Schema rendering add 62,148
bytes gzip to the root, taking it from 303,083 to 365,231 against a 339,526
cap. Effect can reject excess properties when both decoding and rendering use
`onExcessProperty: "error"`; its default strips them. The generated schemas
still differ from the exact meta-tool goldens, including record rendering and
composed numeric bounds. A Schema replacement would need to preserve that
contract, memoized rendering, and the raw-argument checks used by resumable
writes. Both MCP SDK packages still depend on Zod, so changing our inputs alone
does not remove Zod from an installation.

A one-endpoint `HttpApi` prototype adds 106,634 bytes gzip to `./ui` and 106,453
to `./activity`. Even plain `HttpRouter` adds 45,324 to `./ui`, taking it to
130,023 against a 128,983 cap. Both default handlers return an empty 404 for a
wrong method and an unowned path; our routes need a JSON 405 for the former
and module fallthrough for the latter. Their `toWebHandler` also builds its
layer immediately and owns a runner, so adopting it would require proving
global-scope safety and preserving our scheduler and response-lifetime rules.
The existing Effect route programs keep those rules without this routing layer.

Effect's MCP server has a separate compatibility gate. Its `2026-07-28`
adapter accepted a stateless `tools/list`, but its `2025-06-18` adapter refused
a fresh request with 400. Initialization issued a session, and that session's
next request returned 404 on a fresh handler; Connecta served the same fresh
legacy list with 200 JSON and no session. The stable package exports no MCP
client, so it also cannot replace our downstream SDK and OAuth implementation.
[Issue #622](https://github.com/zackbart/connecta/issues/622) records the server
parity requirements. These measurements support keeping the current MCP,
validation, and routing implementations; stable v4 alone does not establish a
replacement's benefit or compatibility.

The core's cost is recorded rather than guessed. Against 0.24.4 the root entry
grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to
Expand Down
7 changes: 4 additions & 3 deletions documentation/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,10 @@ also work on older adapters without `compareAndSet`; **new issuance requires
atomic `compareAndSet`**, because counting records before writing admits too many
concurrent creates. Active capacity defaults to 100, configurable with
`accessTokens(storage, { maxActive: 200 })`, up to 1,000. A durable reservation
counts before a secret is written. An interrupted create can consume capacity
without returning a token; it is never automatically retried or released after
an uncertain write. Avoid creating new tokens through old-version instances once
counts before a secret is written. Failures before lookup publication release
their reservation; an uncertain lookup write retains its metadata and capacity
so an operator can revoke it, even when creation returned no secret. Creation is
never automatically retried. Avoid creating new tokens through old-version instances once
new-version issuance has started, since those instances do not honor reservations.

Secrets contain 256 random bits and only their SHA-256 digests persist. Creation
Expand Down
7 changes: 4 additions & 3 deletions examples/worker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ Keep this example's Worker on Wrangler. A copied deployment may use
[Alchemy](https://alchemy.run) to manage its KV namespace, optional D1 databases
and R2 bucket, and the Access application without adding Alchemy to connecta or
changing this template. In that deployment, install `alchemy@2.0.0-beta.79`
and `effect@4.0.0-rc.117` at exact versions. The latter is connecta's own
and `effect@4.0.0` at exact versions. The latter is connecta's own
Effect pin; check `npm ls effect` for one resolved copy after installation.
The following `alchemy.run.ts` is a resource stack, not a Worker deployment:

Expand Down Expand Up @@ -193,8 +193,9 @@ deleting the state store loses Alchemy's record of what it owns.

Verification for this optional path stops at the API and types. The fenced
snippet was copied to an isolated `alchemy.run.ts` and passed `tsc --noEmit`
with TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0-rc.117; no
Alchemy deploy or adoption was run.
with TypeScript 5.9.3, Alchemy 2.0.0-beta.79, and Effect 4.0.0, with
`npm ls effect` confirming one resolved copy. No Alchemy deploy or adoption
was run.
[Alchemy's Worker resource](https://alchemy.run/cloudflare/compute/workers/)
can declare `WorkerLoader`, domains, Browser Rendering, and cron wiring, but
those properties are coupled to its script deploy. Its current full deploy
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@
"@cfworker/json-schema": "^4.1.1",
"@modelcontextprotocol/client": "2.0.0",
"@modelcontextprotocol/server": "2.0.0",
"effect": "4.0.0-rc.117",
"effect": "4.0.0",
"zod": "^4.4.3"
},
"peerDependencies": {
Expand Down Expand Up @@ -186,7 +186,7 @@
"preact": "^10.29.8",
"quickjs-emscripten": "^0.32.0",
"tsx": "^4.23.1",
"typescript": "^5.6.0",
"typescript": "^5.9.3",
"vitest": "^4.1.6",
"wrangler": "^4.114.0"
}
Expand Down
Loading
Loading