@zackbart/connecta 0.24.4 → 0.25.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/AGENTS.md +40 -10
- package/CHANGELOG.md +253 -0
- package/dist/advertised-schema.js +53 -0
- package/dist/auth/downstream-oauth.js +490 -106
- package/dist/call-admission.js +139 -113
- package/dist/catalog-service.js +141 -105
- package/dist/catalog.js +258 -33
- package/dist/concurrency.js +24 -23
- package/dist/connector-scope.js +6 -37
- package/dist/connectors/api.d.ts +2 -2
- package/dist/connectors/remote-mcp.d.ts +6 -5
- package/dist/connectors/remote-mcp.js +317 -241
- package/dist/credential-contract.d.ts +9 -0
- package/dist/credential-rules.js +2 -2
- package/dist/credentials.d.ts +6 -1
- package/dist/credentials.js +45 -3
- package/dist/execute.js +363 -257
- package/dist/executor-admission.js +67 -50
- package/dist/executors/quickjs-runtime.js +4 -2
- package/dist/executors/quickjs.js +251 -210
- package/dist/fix-prompt.js +37 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.js +51 -20
- package/dist/invocation.js +324 -285
- package/dist/meta-tools.js +206 -92
- package/dist/oauth-callback-outcome.js +94 -0
- package/dist/oauth-sealing.js +29 -0
- package/dist/operator-ui/fix-prompts.js +87 -0
- package/dist/operator-ui/generated.js +2 -2
- package/dist/operator-ui/model.d.ts +46 -1
- package/dist/operator-ui/setup-commands.js +53 -0
- package/dist/operator-ui/view.js +38 -2
- package/dist/program-source.js +205 -0
- package/dist/providers/notion.js +2 -2
- package/dist/providers/revenuecat.js +32 -5
- package/dist/providers/stripe.d.ts +3 -2
- package/dist/providers/vercel.js +1 -1
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +305 -260
- package/dist/routes/activity.js +120 -137
- package/dist/routes/credentials.js +174 -163
- package/dist/routes/mcp.js +129 -70
- package/dist/routes/oauth-management.js +60 -71
- package/dist/routes/oauth.js +44 -16
- package/dist/routes/operator.js +82 -0
- package/dist/routes/shared.d.ts +2 -0
- package/dist/routes/ui-data.js +210 -0
- package/dist/routes/ui.js +64 -32
- package/dist/runtime/admission.js +43 -0
- package/dist/runtime/call-admission.js +67 -0
- package/dist/runtime/connector-scope.js +72 -0
- package/dist/runtime/run.js +174 -0
- package/dist/runtime/services.js +130 -0
- package/dist/runtime/storage.js +66 -0
- package/dist/server.js +111 -89
- package/dist/skills.js +1 -1
- package/dist/storage/file.js +39 -2
- package/dist/storage/memory.js +28 -16
- package/dist/timeout.js +17 -47
- package/dist/types.d.ts +27 -7
- package/dist/ui.d.ts +16 -2
- package/dist/ui.js +50 -166
- package/dist/url-safety.js +157 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +184 -10
- package/documentation/auth.md +80 -0
- package/documentation/code-mode.md +69 -24
- package/documentation/meta-tools.md +164 -27
- package/ethos.md +44 -47
- package/examples/worker/README.md +51 -5
- package/examples/worker/src/d1-storage.ts +116 -0
- package/examples/worker/src/index.ts +6 -6
- package/examples/worker/wrangler.jsonc +16 -4
- package/package.json +11 -3
- package/templates/node/package.json +1 -1
- package/dist/auth/downstream-oauth.d.ts +0 -200
- package/dist/catalog-fingerprint.d.ts +0 -19
- package/dist/catalog-limits.d.ts +0 -9
- package/dist/catalog-service.d.ts +0 -263
- package/dist/catalog.d.ts +0 -87
- package/dist/concurrency.d.ts +0 -3
- package/dist/connectors/guarded-fetch.d.ts +0 -96
- package/dist/credential-rules.d.ts +0 -85
- package/dist/execute.d.ts +0 -154
- package/dist/executor-result.d.ts +0 -10
- package/dist/executors/quickjs-child.d.ts +0 -1
- package/dist/executors/quickjs-protocol.d.ts +0 -67
- package/dist/favicon.d.ts +0 -1
- package/dist/identity.d.ts +0 -4
- package/dist/invocation.d.ts +0 -74
- package/dist/mcp-result.d.ts +0 -8
- package/dist/meta-tools.d.ts +0 -129
- package/dist/operator-ui/generated.d.ts +0 -2
- package/dist/operator-ui/view.d.ts +0 -188
- package/dist/result-shapes.d.ts +0 -13
- package/dist/retired-toolkits.d.ts +0 -2
- package/dist/routes/activity.d.ts +0 -2
- package/dist/routes/credentials.d.ts +0 -2
- package/dist/routes/mcp.d.ts +0 -10
- package/dist/routes/oauth-management.d.ts +0 -2
- package/dist/routes/oauth.d.ts +0 -2
- package/dist/routes/ui.d.ts +0 -2
- package/dist/server.d.ts +0 -9
- package/dist/skills.d.ts +0 -44
- package/dist/timeout.d.ts +0 -21
- package/dist/tool-safety.d.ts +0 -9
package/dist/version.js
CHANGED
|
@@ -43,7 +43,7 @@ channel, `ctx.waitUntil`, threaded through `fetch(request, env, ctx)` — activi
|
|
|
43
43
|
writes use it, as does a stale-window catalog refresh, which owns a fresh scope
|
|
44
44
|
and deadline rather than carrying the inbound one past the request. And a fresh
|
|
45
45
|
`McpServer` per request is what makes the deployment stateless: no sessions, no
|
|
46
|
-
server push, no resumability, scope resolved rather than remembered.
|
|
46
|
+
server push, no stream resumability, scope resolved rather than remembered.
|
|
47
47
|
|
|
48
48
|
## Request lifecycle
|
|
49
49
|
|
|
@@ -77,7 +77,8 @@ An admitted non-preflight `/mcp` request then takes five steps in
|
|
|
77
77
|
|
|
78
78
|
1. **Admit.** One permit from the deployment-wide pool, taken before auth so an
|
|
79
79
|
unauthenticated flood costs a permit rather than a Clerk lookup, and held
|
|
80
|
-
until the response *body* completes, not until the
|
|
80
|
+
until the response *body* completes or the caller leaves, not until the
|
|
81
|
+
handler returns.
|
|
81
82
|
2. **Authorize.** Each `InboundAuth` provider's `authorize` in order, bearer
|
|
82
83
|
before interactive. First `ok` admits; if all fail, the last provider's
|
|
83
84
|
challenge is returned. No providers means open — development only, and it
|
|
@@ -104,9 +105,9 @@ or hands out, and a change usually belongs in exactly one of them:
|
|
|
104
105
|
| Module | Owns |
|
|
105
106
|
| --- | --- |
|
|
106
107
|
| `src/registry.ts` | The connector set, identity-scoped views, personal storage partitions, address resolution, catalog TTL/persistence/completeness, refresh single-flight, connector health, per-connector call limiters, and drift. Construction-time refusals live here. |
|
|
107
|
-
| `src/catalog-service.ts` | Request-local listing, search, and describe.
|
|
108
|
+
| `src/catalog-service.ts` | Request-local listing, search, and describe. Caches catalogs inside one request, fans discovery probes out under deadlines, and opts agent reads into the runtime's deferred catalog channel when one exists. |
|
|
108
109
|
| `src/invocation.ts` | One tool call: argument validation, call admission, one-attempt timeout, provider retry hints, result unwrapping, size capping, and the activity record. |
|
|
109
|
-
| `src/catalog.ts` | Ranking, description summarizing, and the compact schema
|
|
110
|
+
| `src/catalog.ts` | Ranking, description summarizing, and the compact and TypeScript schema renderers discovery shows. |
|
|
110
111
|
| `src/result-shapes.ts` | Bounded runtime-only inference and merging for output shapes learned from successful read-only calls whose providers declared none. |
|
|
111
112
|
|
|
112
113
|
`src/meta-tools.ts` and `src/execute.ts` are two front doors onto the same two
|
|
@@ -144,9 +145,19 @@ accounting error a budget exists to prevent. Both layers are pinned by
|
|
|
144
145
|
connector state, catalogs, and result paging — a 15-minute TTL with one
|
|
145
146
|
runtime-wide accounting of stash bytes and entries, where a full stash returns the
|
|
146
147
|
successful call's preview and a paging-unavailable notice rather than a result id.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
A second optional method, `compareAndSet(key, expected, next)`, is an atomic
|
|
149
|
+
claim: `null` means absent (expired counts) on the way in and delete on the way
|
|
150
|
+
out. Nothing in core requires it yet. Downstream OAuth uses it where the store
|
|
151
|
+
has it, so resealing legacy plaintext and discarding a refused grant cannot
|
|
152
|
+
overwrite a consent that landed in between, and falls back to a read and a
|
|
153
|
+
write where it does not. A subsystem that needs an exactly-once claim will
|
|
154
|
+
require it explicitly rather than emulate it that way.
|
|
155
|
+
Adapters: `src/storage/memory.ts` and `src/storage/file.ts` (Node) both provide
|
|
156
|
+
it, and the namespaced views core hands connectors forward it only when the
|
|
157
|
+
underlying store has it. `examples/worker/` carries two more: Cloudflare KV,
|
|
158
|
+
eventually consistent and so declaring none, and a D1 store that provides it —
|
|
159
|
+
copyable reference source beside the D1 activity store, deliberately not an
|
|
160
|
+
importable subpath. The shared cases live in `test/storage-contract.ts`.
|
|
150
161
|
|
|
151
162
|
`src/credentials.ts` is the AES-GCM vault behind the root-exported
|
|
152
163
|
`CredentialVault` contract, selected through the `vault` slot. It binds connector
|
|
@@ -187,8 +198,12 @@ material code explicitly permitted; it never edits the connector set, catalog,
|
|
|
187
198
|
annotations, scopes, or permission rules. Two invariants shape it: a status read
|
|
188
199
|
never starts authorization, since OAuth begins with an explicit authorized POST,
|
|
189
200
|
and each lazy details request owns a bounded downstream scope, so one failing
|
|
190
|
-
provider leaves the other connections usable.
|
|
191
|
-
|
|
201
|
+
provider leaves the other connections usable. A connector's status message
|
|
202
|
+
never reaches the page: it can quote a downstream error body, and that body can
|
|
203
|
+
quote the secret it rejected, so the details payload carries only a classified
|
|
204
|
+
`problem` — which picks fixed on-screen copy and a fixed fix prompt — and the raw
|
|
205
|
+
text goes to the server log. Credential handoff URLs exist only while the UI is
|
|
206
|
+
mounted; OAuth callbacks never need it.
|
|
192
207
|
|
|
193
208
|
Its appearance is one token layer. `src/operator-ui/browser.css` resolves every
|
|
194
209
|
color, radius, and font through a custom property and mixes the rest from those
|
|
@@ -218,6 +233,165 @@ boundary in the published tarball. The failure mode is not theoretical: one
|
|
|
218
233
|
convenience import of `node:crypto` in a shared helper stops the whole Worker
|
|
219
234
|
shape from building, in someone else's repository rather than this one.
|
|
220
235
|
|
|
236
|
+
## Effect inside
|
|
237
|
+
|
|
238
|
+
The core runs on Effect v4; no API a deployment touches does. `createConnecta`,
|
|
239
|
+
`remoteMcp()`, `api()`, the `Connector` contract, and every shipped `.d.ts`
|
|
240
|
+
are Promise-shaped and name no Effect type, so a connector author never meets a
|
|
241
|
+
second async paradigm. The reason for the rewrite is the first section of this
|
|
242
|
+
guide: the bugs here are lifetime bugs, and Effect makes a lifetime a value —
|
|
243
|
+
a Scope that closes on every exit, an interrupt that reaches whatever a fiber
|
|
244
|
+
is waiting on, a Deferred that one party completes and any number join. The
|
|
245
|
+
conversion found and closed a dozen of them, each with a test that fails on the
|
|
246
|
+
code before it: permits and leases held after their caller left, waits with no
|
|
247
|
+
bound, and races between requests.
|
|
248
|
+
|
|
249
|
+
### Shell and core
|
|
250
|
+
|
|
251
|
+
Each converted module is a shell and a core. The shell keeps its exported class
|
|
252
|
+
or function exactly — same members, same error classes, same `.d.ts`, private
|
|
253
|
+
member names included, because TypeScript ships those too. The core is an
|
|
254
|
+
Effect program behind it. In a published class it lives in a `static` block,
|
|
255
|
+
the one place that can read private state without adding a member to the
|
|
256
|
+
declarations; elsewhere it lives in an unexported function or in
|
|
257
|
+
`src/runtime/`, which no `exports` target reaches. Errors keep their classes
|
|
258
|
+
(`ConnectorCallError`, `ExecutorAdmissionError`, `CallAdmissionError`) because
|
|
259
|
+
callers and tests check `instanceof`, `name`, and `message`.
|
|
260
|
+
|
|
261
|
+
Effect callers skip the Promise and take the program: `admit` and
|
|
262
|
+
`acquireScoped` for request admission (`src/runtime/admission.ts`), `admitCall`
|
|
263
|
+
and `acquireCallScoped` for call admission, `closeScope` and `closeScopeOnExit`
|
|
264
|
+
for a connector scope, `withDeadlineEffect` where Promise code has
|
|
265
|
+
`withDeadline`. The build prunes every declaration no `exports` target reaches,
|
|
266
|
+
and `npm run check:declarations` fails if what remains names an Effect type.
|
|
267
|
+
|
|
268
|
+
### One runner
|
|
269
|
+
|
|
270
|
+
`src/runtime/run.ts` is the only place a fiber starts, and
|
|
271
|
+
`test/purity.test.ts` fails if any other file calls `Effect.run*`, `runFork`,
|
|
272
|
+
`forkDaemon`, or `ManagedRuntime.make`. It exports two ways out:
|
|
273
|
+
|
|
274
|
+
- **`runEdge(effect, { signal, runtime? })`** runs an effect at a Promise
|
|
275
|
+
boundary. It rethrows the original failure or defect, never a wrapper. It
|
|
276
|
+
turns an interrupt into the caller's `signal.reason` rather than Effect's
|
|
277
|
+
"All fibers interrupted without error". And it runs every fiber on a
|
|
278
|
+
scheduler that yields through microtasks: Effect's default yields through
|
|
279
|
+
`setImmediate`, which vitest's fake timers freeze and Workers never had. The
|
|
280
|
+
price is that a fiber never yields to I/O, so CPU-heavy work stays out of
|
|
281
|
+
Effect loops.
|
|
282
|
+
- **`detach(effect, ctx)`** is the only fire-and-forget, and it hands the work
|
|
283
|
+
to `ctx.waitUntil` when there is one.
|
|
284
|
+
|
|
285
|
+
Beside them sit `withDeadlineEffect`, which aborts the operation's own signal
|
|
286
|
+
with the labelled timeout error *before* interrupting it, so work that honors
|
|
287
|
+
the signal sees the same reason the caller does, and `fromSignal`, which turns
|
|
288
|
+
an `AbortSignal` into a failure to race against. Nothing runs Effect at module
|
|
289
|
+
scope, where a Worker may not start work, and nothing logs through
|
|
290
|
+
`Effect.log`: logging goes through the configured `Logger`, which is what
|
|
291
|
+
honors `logger: "silent"`.
|
|
292
|
+
|
|
293
|
+
Each Connecta also gets a runtime of its own (`src/runtime/services.ts`):
|
|
294
|
+
`Storage`, `Vault`, `ActivityRecorder` (one that records nothing when the module
|
|
295
|
+
is omitted), `Logger`, and `ResolvedConfig`. Creating it builds nothing, since
|
|
296
|
+
a Worker constructs its Connecta at global scope; the first run that needs the
|
|
297
|
+
services builds them, and `close()` disposes the runtime last. Two things
|
|
298
|
+
deliberately do not run on it. The request pipeline runs on no runtime, so
|
|
299
|
+
`/health` and a closed deployment's 503 keep answering after `close()`. And a
|
|
300
|
+
registry provides its own storage and logger to its programs
|
|
301
|
+
(`runOnPartition` in `src/runtime/storage.ts`), because a personal registry's
|
|
302
|
+
storage is the root's namespaced to its principal and the runtime's `Storage`
|
|
303
|
+
would be another partition's.
|
|
304
|
+
|
|
305
|
+
### What runs on Effect
|
|
306
|
+
|
|
307
|
+
| Area | Shape |
|
|
308
|
+
| --- | --- |
|
|
309
|
+
| Requests (`src/server.ts`, `src/routes/mcp.ts`, `src/routes/oauth.ts`) | One fiber per `fetch`, tied to `request.signal`. An `/mcp` request's admission permit and every `McpServer` it builds live in a Scope the response carries out, closed when the body ends, fails, or is cancelled, when the signal aborts, or at once if the handler fails first. The OAuth callback is uninterruptible: a single-use code's exchange and catalog invalidation are one commitment. |
|
|
310
|
+
| Admission (`executor-admission.ts`, `call-admission.ts`) | Queued waiters are Deferreds settled by whoever removes them from the queue; a wait is one flat race of grant, Clock timeout, and signal. The uncontended path stays synchronous. Two controllers, as [#453](https://github.com/zackbart/connecta/issues/453) requires. |
|
|
311
|
+
| One tool call (`invocation.ts`) | One fiber: resolution, the read-only and schema refusals, admission, and the downstream attempt sit under a single `withDeadlineEffect`, whose expiry interrupts the call wherever it is. The permit is an `acquireRelease`; the connector call is `Effect.tryPromise` over the unchanged `Connector`. |
|
|
312
|
+
| `execute_code` (`execute.ts`) | One fiber whose Scope owns the run's signal and executor lease, so a result, a throw, the watchdog, and cancellation all release the lease and abort the signal the same way. The executor's `acquire()` and `execute()` stay Promises raced against the signal and `execute.watchdogMs`. Each guest host call is a fiber of its own. |
|
|
313
|
+
| Discovery (`catalog-service.ts`) | A request-scoped cache: one Deferred per connector, completed by the first asker's read from that read's own settlement, so a probe deadline ends one asker's wait and never the read the others share. Fan-out is `Effect.forEach` under the discovery concurrency. |
|
|
314
|
+
| Registry (`registry.ts`) | Catalog persistence and the result stash are programs over `Storage`. A refresh flight is a Deferred its publishing request completes; persisted-catalog writes take per-connector turns, each a Deferred its own request completes. |
|
|
315
|
+
| Remote MCP (`connectors/remote-mcp.ts`) | Each request scope's state holds a Scope, each connection is a lease forked from it, and a connect in flight is a Deferred carrying the client it connected. Closing a session and the transport are each bounded to a second. |
|
|
316
|
+
| Downstream OAuth (`auth/downstream-oauth.ts`) | A refresh flight is a Deferred; the owner's redemption is a fiber its abort interrupts, and committing an answer that already exists is uninterruptible. |
|
|
317
|
+
| Operator and activity data (`routes/operator.ts`) | Each JSON route is one program run by `serveOperator` behind the Promise `handle()`. Reads run under the request's signal; writes do not, so a vault write or OAuth disconnect that started reaches its cache invalidation. |
|
|
318
|
+
| QuickJS pool (`executors/quickjs.ts`, Node only) | Each child is a scoped resource whose release sends SIGTERM, then SIGKILL after a second; crash respawn backoff is a `Schedule`. |
|
|
319
|
+
|
|
320
|
+
### What stays plain
|
|
321
|
+
|
|
322
|
+
- **Everything a deployment writes against.** The `Connector` contract,
|
|
323
|
+
`api()`, the providers, custom connectors, storage adapters, and the vault
|
|
324
|
+
are Promises. A connector is called with `Effect.tryPromise`, never asked to
|
|
325
|
+
return an Effect.
|
|
326
|
+
- **Inbound authorization.** `authorize` in `src/routes/shared.ts` ships in
|
|
327
|
+
the declarations and in the `./activity` bundle; converting it cost 32 KB
|
|
328
|
+
gzip there for nothing a caller could see.
|
|
329
|
+
- **Pure synchronous code.** Ranking, schema rendering, classification,
|
|
330
|
+
`validateToolInput`, the host-call budget, the emit collector, failure
|
|
331
|
+
framing, and result shaping. The microtask scheduler never yields to I/O, so
|
|
332
|
+
wrapping them would buy nothing and could starve a request.
|
|
333
|
+
- **The MCP edges.** `@modelcontextprotocol/server` upward and the SDK client
|
|
334
|
+
downstream; Effect's own `McpServer` is not used.
|
|
335
|
+
- **The Node and browser leaves.** `node.ts`, `fileStorage`, the QuickJS
|
|
336
|
+
child, runtime, and protocol, and the operator UI's browser app.
|
|
337
|
+
|
|
338
|
+
### Across requests
|
|
339
|
+
|
|
340
|
+
Work shared across requests meets only through a Deferred that the owning
|
|
341
|
+
request completes, never through a fiber that outlives its request. Two
|
|
342
|
+
workerd rules follow, and both have bitten.
|
|
343
|
+
|
|
344
|
+
**Waiting on a shared Deferred is its own edge.** Deferred resumes a waiting
|
|
345
|
+
fiber synchronously, inside the call that completed it, which on Workers means
|
|
346
|
+
inside the completing request's I/O context. A waiter that goes on to touch its
|
|
347
|
+
own body, transport, or storage there fails with "Cannot perform I/O on behalf
|
|
348
|
+
of a different request", or hangs until its deadline. So the wait runs through
|
|
349
|
+
`runEdge`, and the waiter does its own I/O after awaiting that promise, which
|
|
350
|
+
workerd resumes in the waiter's request as it does any promise resolved from
|
|
351
|
+
another. The same holds one level out for every queue: a queued call, a queued
|
|
352
|
+
`execute_code`, and a queued `/mcp` request are each handed their permit from
|
|
353
|
+
inside the request that released one, so each awaits the controller's
|
|
354
|
+
Promise-shaped `acquire` rather than yielding its Effect program, and does its
|
|
355
|
+
own work only once that promise resolves. Effect's `Semaphore` is not used
|
|
356
|
+
across requests for the same reason, and because a same-tick newcomer can take
|
|
357
|
+
a released permit ahead of a waiter that queued earlier.
|
|
358
|
+
|
|
359
|
+
**Never read another request's signal.** On workerd, reading `aborted` or
|
|
360
|
+
`reason`, or calling `abort()`, on a signal that belongs to another request
|
|
361
|
+
throws; adding and removing listeners does not. Code that runs from another
|
|
362
|
+
request's completion — a queue handing out a permit, `close()` — must not look
|
|
363
|
+
at a waiter's signal. A waiter whose signal aborts leaves the queue from its
|
|
364
|
+
own listener, in its own request.
|
|
365
|
+
|
|
366
|
+
One corollary is easy to break by tidying. Workerd cancels a request as hung
|
|
367
|
+
when its only pending work is waiting on another request and it has no timer or
|
|
368
|
+
I/O of its own. Connecta's deadline timers are what keep a waiting request
|
|
369
|
+
alive and bounded, so no wait may lose its timer.
|
|
370
|
+
|
|
371
|
+
### Why not Schema or HttpApi
|
|
372
|
+
|
|
373
|
+
Both were measured and both lost. `effect` is imported through its barrel,
|
|
374
|
+
which defeats tree-shaking: the first conversion step measured about +20 ms
|
|
375
|
+
of Worker cold start for it, and that cost was accepted over deep imports at
|
|
376
|
+
every call site. The catch is that a module the barrel reaches is paid for in
|
|
377
|
+
full, however little of it is used. Effect Schema for config validation and the
|
|
378
|
+
meta-tool inputs came to about +62 KB gzip and +13 ms of cold start in the
|
|
379
|
+
root, for a validator no clearer than the hand-written `CONFIG_SCHEMA`; the
|
|
380
|
+
meta-tool inputs stay zod, which the MCP SDK bundles regardless. `HttpApi` for
|
|
381
|
+
the operator and activity routes cost about +100 KB gzip on `./ui` and +130 KB
|
|
382
|
+
on `./activity`, and matching the wire format meant opting out of most of what
|
|
383
|
+
it does: unowned paths fall through rather than 404, a wrong method is a JSON
|
|
384
|
+
405, and the content-type and size checks run before a body is read. The root
|
|
385
|
+
entry may import only `effect` itself (`test/purity.test.ts`); nothing in
|
|
386
|
+
`src/` uses `effect/unstable/*`, which would have to stay behind a subpath.
|
|
387
|
+
|
|
388
|
+
The core's cost is recorded rather than guessed. Against 0.24.4 the root entry
|
|
389
|
+
grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to
|
|
390
|
+
314,336, and each provider by about 36 KB, since each reaches Effect through
|
|
391
|
+
`remoteMcp()`; `npm run check:bundle` caps every entry against
|
|
392
|
+
`scripts/bundle-budget.json`. An install carries about 53 MB of `effect`, with
|
|
393
|
+
no dependencies of its own.
|
|
394
|
+
|
|
221
395
|
## Where else to look
|
|
222
396
|
|
|
223
397
|
Beyond the modules already named:
|
|
@@ -250,7 +424,7 @@ compiling and configuring the real thing.
|
|
|
250
424
|
`OPTIONS` opts it into CORS preflight; reordering admission after auth makes
|
|
251
425
|
the cheapest possible attack the most expensive request.
|
|
252
426
|
- **`close()` is idempotent and ordered.** Both admission pools, then the
|
|
253
|
-
connector limiters, then the executor; Node's `listen()` calls it on
|
|
427
|
+
connector limiters, then the executor, then the Connecta's runtime; Node's `listen()` calls it on
|
|
254
428
|
SIGTERM/SIGINT.
|
|
255
429
|
- **Structural mistakes throw at construction.** A duplicate connector id, an
|
|
256
430
|
invalid admission rule, the removed `accessTokens` option, a missing executor:
|
package/documentation/auth.md
CHANGED
|
@@ -149,6 +149,86 @@ partitions. Literal `auth: { type: "headers" }` cannot be personal, because its
|
|
|
149
149
|
secret lives in deployment code; `remoteMcp()` refuses that combination at
|
|
150
150
|
construction.
|
|
151
151
|
|
|
152
|
+
## Downstream OAuth state at rest
|
|
153
|
+
|
|
154
|
+
With a vault configured, a `remoteMcp()` OAuth connector's tokens, dynamically
|
|
155
|
+
registered client (secret included), and PKCE verifier are sealed with the
|
|
156
|
+
vault's AES-GCM key before they reach storage. The additional authenticated data
|
|
157
|
+
names the connector, the owner partition, and the physical key, which carries
|
|
158
|
+
the authorization epoch, so ciphertext copied to another connector, principal,
|
|
159
|
+
or epoch does not open. Anything that fails to open — a tampered value, a
|
|
160
|
+
rotated key — reads as absent: the connector reports `auth_required` and logs a
|
|
161
|
+
warning. The flow bookkeeping stays plaintext: `state`, the pending URL,
|
|
162
|
+
discovery metadata, and the generation. The callback route reads `state`
|
|
163
|
+
directly, and none of it authenticates anything by itself.
|
|
164
|
+
|
|
165
|
+
Plaintext left by an older release is read, then sealed where it lies through
|
|
166
|
+
the same generation fence as any write, so an upgrade keeps the grant. Sealing
|
|
167
|
+
is one-way. An older release reads sealed state as unusable, so rolling back
|
|
168
|
+
means authorizing again. A vault without the optional `seal`/`open` members
|
|
169
|
+
keeps these values plaintext and draws a startup warning. Without any vault,
|
|
170
|
+
nothing changes: the state is plaintext, as it always was.
|
|
171
|
+
|
|
172
|
+
## Refresh failures
|
|
173
|
+
|
|
174
|
+
A failed token refresh means one of two things, and connecta decides which
|
|
175
|
+
from the token endpoint's answer, not the SDK's reading of it. A **dead grant**
|
|
176
|
+
is any 4xx except 408, 425, and 429, or a 2xx carrying an OAuth `error` (GitHub
|
|
177
|
+
answers `200 {"error":"bad_refresh_token"}`). It ends as `auth_required`, and
|
|
178
|
+
the refused tokens are deleted on the spot, so no later request or isolate
|
|
179
|
+
sends them again and the next `authorize_connector` goes straight to consent.
|
|
180
|
+
An **outage** is a 5xx, 408, 425, 429, a network failure, or a 2xx that is not
|
|
181
|
+
a token response. It ends as retryable `unavailable`, or `rate_limited` for a
|
|
182
|
+
429, with `retryAfterMs` when the server sent `Retry-After`. The grant is kept,
|
|
183
|
+
and a passive call writes no consent URL. Every request joined on the same
|
|
184
|
+
in-flight refresh gets the same verdict, even when the request that sent the
|
|
185
|
+
refresh is cancelled after the answer arrives: the refused tokens are deleted
|
|
186
|
+
before anyone waiting is released, so a request arriving meanwhile joins the
|
|
187
|
+
refusal instead of sending the dead token again.
|
|
188
|
+
|
|
189
|
+
The SDK needs this help. On a refresh failure it cannot parse, or one marked
|
|
190
|
+
`server_error`, it falls through to starting authorization, which reports a
|
|
191
|
+
healthy grant as needing consent. It rethrows every other OAuth error
|
|
192
|
+
untouched, which reports a dead grant as a generic outage and keeps re-sending
|
|
193
|
+
it. So the refresh coordinator hands the SDK an answer it classifies correctly,
|
|
194
|
+
and the provider hooks it calls next finish the job. The pinned SDK behavior is
|
|
195
|
+
spelled out beside `refreshResponseOutcome`. An explicit authorization during
|
|
196
|
+
an outage still falls through to consent, since that is what it asked for.
|
|
197
|
+
|
|
198
|
+
## URLs a downstream advertises
|
|
199
|
+
|
|
200
|
+
A `remoteMcp()` OAuth connector learns most of the URLs it fetches from the
|
|
201
|
+
downstream itself: the `resource_metadata` in its 401 challenge, the
|
|
202
|
+
`authorization_servers` in that metadata, and the token and registration
|
|
203
|
+
endpoints the authorization server publishes. Connecta fetches every one of
|
|
204
|
+
them server-side, so a compromised downstream could otherwise aim a Node or
|
|
205
|
+
Docker host at its own network — cloud metadata at `169.254.169.254`, an admin
|
|
206
|
+
panel on the LAN. Config is the security model, so the rule splits on it:
|
|
207
|
+
|
|
208
|
+
- A URL on the connector's configured origin is trusted. The operator wrote it
|
|
209
|
+
down.
|
|
210
|
+
- Any other URL must be `https` and must not name a private host: `localhost`
|
|
211
|
+
and `*.localhost`, `127/8`, `10/8`, `172.16/12`, `192.168/16`, `169.254/16`,
|
|
212
|
+
`100.64/10`, `0/8`, `::`, `::1`, `fc00::/7`, `fe80::/10`, or an IPv4-mapped
|
|
213
|
+
IPv6 form of any of those.
|
|
214
|
+
- A connector configured on a loopback host may also learn loopback URLs, over
|
|
215
|
+
`http` too, so a local MCP server with a local authorization server keeps
|
|
216
|
+
working. LAN and link-local addresses stay refused.
|
|
217
|
+
|
|
218
|
+
A refused URL is never requested. Discovery, registration, and code exchange
|
|
219
|
+
fail with a non-retryable `connector_call_failed` that names the host and
|
|
220
|
+
nothing else from the URL. A refused refresh never reaches the token endpoint,
|
|
221
|
+
so there is no verdict to act on: the grant is kept, and the SDK falls through
|
|
222
|
+
to consent as it does for any refresh it could not complete. Redirects cannot
|
|
223
|
+
route around the rule, because the redirect policy only follows same-origin
|
|
224
|
+
hops.
|
|
225
|
+
|
|
226
|
+
The check is syntactic. It reads the host after the WHATWG URL parser has
|
|
227
|
+
folded `2130706433` and `0x7f.1` into `127.0.0.1`, and it never resolves a
|
|
228
|
+
name, because the Workers-safe core has no DNS. A public name that resolves to
|
|
229
|
+
a private address, or rebinds to one after the check, is out of scope; a host
|
|
230
|
+
that must stop that runs connecta behind an egress policy that does.
|
|
231
|
+
|
|
152
232
|
## Management permissions
|
|
153
233
|
|
|
154
234
|
Visibility alone grants no authentication-management permission. Two independent
|
|
@@ -90,7 +90,9 @@ Connecta passes exactly one provider, named `connecta`. An executor must:
|
|
|
90
90
|
5. **Capture `console.log`, `console.warn`, and `console.error`** into `logs` in
|
|
91
91
|
call order (`R5`), bounding what it retains.
|
|
92
92
|
6. **Bound the guest**: wall clock, memory, stack, and CPU (`L3`, `L5`), staying
|
|
93
|
-
inside the tested `P2`/`X5` boundary.
|
|
93
|
+
inside the tested `P2`/`X5` boundary. Settle `execute()` on your own
|
|
94
|
+
deadline: connecta stops waiting at `execute.watchdogMs` and reports the
|
|
95
|
+
sandbox unresponsive (`L3`).
|
|
94
96
|
7. **Grant no ambient authority of its own.** Never back this with `eval` or
|
|
95
97
|
`node:vm`: the sandbox is a containment layer on top of connecta's boundary,
|
|
96
98
|
not a replacement for it, and every capability arrives through `fns`.
|
|
@@ -106,10 +108,15 @@ is just another provider function (`M8`).
|
|
|
106
108
|
## The program
|
|
107
109
|
|
|
108
110
|
**P1.** A program is one JavaScript `async` arrow-function expression, evaluated
|
|
109
|
-
once; its resolved value is the result.
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
111
|
+
once; its resolved value is the result. The expression may end with a `;`, and
|
|
112
|
+
with whitespace or comments after it: `execute_code` drops those terminators
|
|
113
|
+
before either executor sees the program, because both evaluate it inside
|
|
114
|
+
parentheses, where `};` was a syntax error on QuickJS and on a Dynamic Worker
|
|
115
|
+
either the same error or a program that quietly returned `undefined`. Only
|
|
116
|
+
semicolons that trail the expression go; `async () => 1; 2` is still more than
|
|
117
|
+
one expression. Both executors also accept markdown-fenced code and a bare
|
|
118
|
+
statement body, normalizing those differently — a courtesy to model output,
|
|
119
|
+
not contract, so anything else may be accepted, rejected, or reinterpreted.
|
|
113
120
|
|
|
114
121
|
**P2.** The only capabilities in the contract are `connecta.search`,
|
|
115
122
|
`connecta.describe`, `connecta.call`, `connecta.emit`, and `console.log` /
|
|
@@ -129,7 +136,9 @@ storage carried to the next program, and no request-bound object outliving its
|
|
|
129
136
|
request. Within one execution, host calls share one downstream request scope.
|
|
130
137
|
`S9`'s output observation is host-owned catalog metadata, not guest memory — a
|
|
131
138
|
later program receives a labeled field/type schema through discovery, never a
|
|
132
|
-
prior value or object.
|
|
139
|
+
prior value or object. Planned resumable writes
|
|
140
|
+
([ethos](../ethos.md#decisions)) will let a paused run's journal survive,
|
|
141
|
+
bounded and expiring; the program still gets no state of its own.
|
|
133
142
|
|
|
134
143
|
**P5.** Plain JavaScript only; TypeScript syntax is a syntax error. Portable
|
|
135
144
|
code does not import: QuickJS blocks imports, Dynamic Workers expose the `X5`
|
|
@@ -164,7 +173,7 @@ const page = await connecta.search({
|
|
|
164
173
|
limit: 8, // 1–100, default 8
|
|
165
174
|
offset: 0,
|
|
166
175
|
fullDescriptions: false,
|
|
167
|
-
includeSchemas: "compact", // or "json"
|
|
176
|
+
includeSchemas: "compact", // or "json" / "typescript"
|
|
168
177
|
includeSchemaKeys: true, // default true in code mode
|
|
169
178
|
});
|
|
170
179
|
```
|
|
@@ -211,6 +220,18 @@ truncation flag; `$dynamicRef` resolves a same-named definition like `$ref`, and
|
|
|
211
220
|
an unresolved one becomes `unknown` with the flag. For omitted exact constraints
|
|
212
221
|
use `format: "json"` or JSON search.
|
|
213
222
|
|
|
223
|
+
`includeSchemas: "typescript"` spends those same bounds on a different
|
|
224
|
+
rendering: one `signature` string in place of `inputSchema` and
|
|
225
|
+
`outputSchema`, reading `(args: { runId: number }) => Promise<Run>` for the
|
|
226
|
+
value `connecta.call` returns. It is documentation for the program's author —
|
|
227
|
+
the program itself is still JavaScript, and a type annotation copied into it
|
|
228
|
+
is a syntax error (`P5`). Truncation keeps compact's flags and
|
|
229
|
+
turns what compact would print as a name or raw JSON into a marked `unknown`;
|
|
230
|
+
an observed output opens with `/* observed, not declared */`. The dialect and
|
|
231
|
+
its degradation rules are specified once, under
|
|
232
|
+
[TypeScript signatures](./meta-tools.md#typescript-signatures), and the
|
|
233
|
+
signature is byte-identical to the one `search_tools` returns.
|
|
234
|
+
|
|
214
235
|
**S1a.** `connector` loads only the named catalog; omit it only when the
|
|
215
236
|
integration is ambiguous, because an unscoped search fans out across every
|
|
216
237
|
configured connector. `safety: "readOnly"` returns exactly the tools available
|
|
@@ -249,7 +270,7 @@ hint and the stable `code`, `retryable`, and `details` fields (`E1`).
|
|
|
249
270
|
const one = await connecta.describe({ address: "ci.get_run" });
|
|
250
271
|
const many = await connecta.describe({
|
|
251
272
|
addresses: ["ci.get_run", "ci.get_job_logs"], // ≤ 100
|
|
252
|
-
format: "compact", // or "json"
|
|
273
|
+
format: "compact", // or "json" / "typescript"
|
|
253
274
|
fullDescriptions: false,
|
|
254
275
|
});
|
|
255
276
|
```
|
|
@@ -265,8 +286,11 @@ canonical addresses. More than 100 addresses is `invalid_args`, and the same
|
|
|
265
286
|
256,000-byte ceiling applies. Compact describe keeps property prose within a
|
|
266
287
|
separate 8,192-byte UTF-8 shape cap and shares search's 2,000-visit budget; a
|
|
267
288
|
capped shape sets `inputSchemaTruncated` or `outputSchemaTruncated`, and
|
|
268
|
-
`format: "json"` gives the exact schema.
|
|
269
|
-
`
|
|
289
|
+
`format: "json"` gives the exact schema. `format: "typescript"` returns a
|
|
290
|
+
`signature` instead of both schema fields, under the same 8,192-byte cap per
|
|
291
|
+
half with property prose as JSDoc. A success whose output shape came from
|
|
292
|
+
`S9` carries `outputSchemaSource: "observed"`, and a TypeScript signature also
|
|
293
|
+
says so inside its `Promise<…>`.
|
|
270
294
|
|
|
271
295
|
### connecta.call
|
|
272
296
|
|
|
@@ -385,12 +409,15 @@ and let the failure reach the model.
|
|
|
385
409
|
to `call_destructive_tool`, plus the original arguments when they fit the
|
|
386
410
|
512-byte echo budget — whole or not at all, since a clipped copy is a different
|
|
387
411
|
call. The model's short `reason` for the human reviewer grants no authority,
|
|
388
|
-
never goes downstream, and generated code cannot mint the capability.
|
|
412
|
+
never goes downstream, and generated code cannot mint the capability. Planned
|
|
413
|
+
resumable writes ([ethos](../ethos.md#decisions)) replace this refusal with a
|
|
414
|
+
pause the host approves; until they ship, it stands as written.
|
|
389
415
|
|
|
390
416
|
**E5.** Failures of the *execution*, not of a call, never appear inside the
|
|
391
417
|
guest: admission rejection (`executor_overloaded`, retryable, with
|
|
392
418
|
`retryAfterMs`), cancellation (`executor_cancelled`), shutdown
|
|
393
|
-
(`executor_closed`), deadline expiry,
|
|
419
|
+
(`executor_closed`), deadline expiry, an executor that never settles (`L3`),
|
|
420
|
+
and sandbox crashes end the run and reach
|
|
394
421
|
the model as an error result. One seam: a host call still in flight when the run
|
|
395
422
|
is cancelled fails with `cancelled`, catchable on the way out but never worth
|
|
396
423
|
acting on (`Y3`). When shutdown tears down a program that had already started,
|
|
@@ -582,12 +609,25 @@ block runs — a cancelled QuickJS child is terminated outright. Write programs
|
|
|
582
609
|
that need no cleanup.
|
|
583
610
|
|
|
584
611
|
**L2.** What cancellation guarantees: in-flight host calls abort, no further
|
|
585
|
-
host call is admitted
|
|
586
|
-
|
|
612
|
+
host call is admitted — `search` and `describe` included, which fail
|
|
613
|
+
`cancelled` rather than go on loading catalogs — the response returns
|
|
614
|
+
`executor_cancelled` without waiting for the executor to settle, or for an
|
|
615
|
+
`acquire()` that ignores the signal to grant, the admission lease is released
|
|
616
|
+
(a lease granted after the run gave up is released on arrival), and nothing
|
|
617
|
+
request-bound survives the request. Whether the program itself stops is the
|
|
618
|
+
executor's (`X3`).
|
|
587
619
|
|
|
588
620
|
**L3.** Every execution runs under a wall-clock deadline that includes time
|
|
589
621
|
spent waiting on host calls. Expiry ends the run with an execution error and no
|
|
590
|
-
partial result; the deadline's length is executor configuration (`X1`).
|
|
622
|
+
partial result; the deadline's length is executor configuration (`X1`). Above
|
|
623
|
+
it sits a ceiling connecta enforces outside the sandbox, `execute.watchdogMs`
|
|
624
|
+
(120 s by default, above both executors' own deadlines), because an executor's
|
|
625
|
+
deadline may live inside the sandbox it is meant to stop: a wedged Dynamic
|
|
626
|
+
Worker never fires its in-isolate timer. A run whose executor has not settled
|
|
627
|
+
by the ceiling ends as a non-retryable `executor_failed` whose message calls
|
|
628
|
+
the sandbox unresponsive, with no partial result, and its admission lease is
|
|
629
|
+
released, so wedged runs cannot fill the code pool. Raising an executor's
|
|
630
|
+
deadline past the ceiling means raising the ceiling too.
|
|
591
631
|
|
|
592
632
|
**L4.** Per-execution bounds that are contract, identical in both executors
|
|
593
633
|
because connecta enforces them above the sandbox:
|
|
@@ -688,10 +728,15 @@ and guest-CPU limits (`L5`); the Dynamic Worker has no such knobs, so workerd's
|
|
|
688
728
|
isolate limits apply untuned. A specific heap ceiling is a Node-only option.
|
|
689
729
|
|
|
690
730
|
**X3. Mid-flight cancellation.** The QuickJS pool receives the request's
|
|
691
|
-
`AbortSignal` and kills the child
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
731
|
+
`AbortSignal` and kills the child — SIGTERM, then SIGKILL if it has not exited
|
|
732
|
+
within a second — and releasing a lease whose child is still running recycles
|
|
733
|
+
that child, so a run the watchdog abandons (`L3`) ends too.
|
|
734
|
+
The Dynamic Worker executor's `execute()` takes no signal, so a cancelled or
|
|
735
|
+
abandoned program runs on in its isolate until its host calls fail or its own
|
|
736
|
+
deadline expires. `L2` and `L3` hold either way — the calls abort, connecta
|
|
737
|
+
stops awaiting the executor and frees the admission slot — but "the run ends"
|
|
738
|
+
is best-effort on Workers, and for that tail the code pool bounds what
|
|
739
|
+
connecta is waiting on, not what the platform is still running.
|
|
695
740
|
|
|
696
741
|
**X4. Log rendering and capture.** QuickJS JSON-stringifies non-string arguments
|
|
697
742
|
and captures `log`, `info`, `warn`, `error`, and `debug`; the Dynamic Worker
|
|
@@ -776,12 +821,12 @@ passing one table is also the check on the executor duties above, with
|
|
|
776
821
|
|
|
777
822
|
| Clauses | Test |
|
|
778
823
|
| --- | --- |
|
|
779
|
-
| `P1`, `P5` | `test/guest-api-contract.test.ts` (TypeScript syntax), `test/quickjs-executor.test.ts` (`normalizeCode`) |
|
|
824
|
+
| `P1`, `P5` | `test/guest-api-contract.test.ts` (TypeScript syntax, trailing terminators), `test/program-source.test.ts` (which semicolons are terminators), `test/quickjs-executor.test.ts` (`normalizeCode`) |
|
|
780
825
|
| `P2`, `X5` | `test/guest-api-contract.test.ts` (Dynamic globals plus loader-only filesystem, HTTP, environment, egress, DNS, and local `data:` boundaries), `test/guest-api-contract-quickjs.test.ts` (exact absent globals and blocked imports), `test/quickjs-child-stderr.test.ts` (empty child-process environment), `test/deployment-shapes.test.ts` (loader-only Worker construction) |
|
|
781
826
|
| `P3`, `X9` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` |
|
|
782
827
|
| `P4` | `test/guest-api-contract.test.ts` (no cross-run leakage), `test/execute.test.ts` (one catalog load per connector per execution) |
|
|
783
828
|
| `A1`, `A2` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (canonical addressing), `test/server.test.ts` (bounded live connector inventory) |
|
|
784
|
-
| `S1`, `S1a`, `S2` | `test/guest-api-contract.test.ts` (flat page, connector guides, schema keys, unfiltered browse), `test/execute.test.ts` (guide pagination/partial/no-match behavior and `$ref`/`allOf`), `test/meta-tools-search.test.ts` (mixed complete/partial ranking, stable pagination, the two safety classes) |
|
|
829
|
+
| `S1`, `S1a`, `S2` | `test/guest-api-contract.test.ts` (flat page, connector guides, schema keys, unfiltered browse), `test/execute.test.ts` (guide pagination/partial/no-match behavior and `$ref`/`allOf`), `test/meta-tools-search.test.ts` (mixed complete/partial ranking, stable pagination, the two safety classes), `test/typescript-signatures.test.ts` (the TypeScript format over provider and pathological schemas, search/describe parity, observed labeling), `test/typescript-signatures-parse.test.ts` (every rendering parses) |
|
|
785
830
|
| `S3` | `test/guest-api-contract.test.ts` (typed uncaught bound), `test/execute.test.ts` (count limits, fan-out bound) |
|
|
786
831
|
| `S4` | both guest-contract executors (ordered mixed describe results with unknown-address, unknown-tool suggestion, and catalog-failure details), `test/meta-tools-search.test.ts` (top-level routing, no-suggestion, catalog-failure, and hostile-input bounds) |
|
|
787
832
|
| `S5`, `S6` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`, fail-closed annotations, activity parity) |
|
|
@@ -799,8 +844,8 @@ passing one table is also the check on the executor duties above, with
|
|
|
799
844
|
| `R6`–`R8` | `test/guest-api-contract.test.ts` (normal result keys), `test/execute.test.ts` (opt-in operation aggregates, failure paths, payload exclusion) |
|
|
800
845
|
| `Y1`, `Y2`, `Y3` | `test/guest-api-contract.test.ts` (one attempt per call, retryable flags by code) |
|
|
801
846
|
| `Y4` | `test/meta-tools-call.test.ts`, `test/call-admission.test.ts` (one attempt, retry hints, caller reissue) |
|
|
802
|
-
| `L1`, `L2` | `test/guest-api-contract.test.ts` (in-flight call fails `cancelled`), `test/execute.test.ts` (cancels outstanding host calls) |
|
|
803
|
-
| `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors) |
|
|
847
|
+
| `L1`, `L2` | `test/guest-api-contract.test.ts` (in-flight call fails `cancelled`), `test/execute.test.ts` (cancels outstanding host calls, discovery included, and refuses discovery after the run; a cancelled wedged executor, or one whose `acquire()` ignores the signal, returns promptly and releases its lease) |
|
|
848
|
+
| `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors), `test/execute.test.ts` (the watchdog ends a never-settling executor, frees the default pool, spares a slow run, and falls back from an unusable value) |
|
|
804
849
|
| `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets) |
|
|
805
850
|
| `L5`, `L7`, `X2` | `test/quickjs-executor.test.ts` (CPU, heap), `test/execute.test.ts` and `test/executor-admission.test.ts` (bounded admission and queue) |
|
|
806
851
|
| `L6`, `X10` | `test/quickjs-executor.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message), `test/quickjs-child-stderr.test.ts` (outer reply serialization failure settles the call) |
|
|
@@ -812,7 +857,7 @@ passing one table is also the check on the executor duties above, with
|
|
|
812
857
|
| `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
|
|
813
858
|
| `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
|
|
814
859
|
| `X4` | `test/guest-api-contract.test.ts` (string logs only), `test/quickjs-executor.test.ts` (logs before cancellation), `test/quickjs-child-stderr.test.ts` (crash, shutdown, deadline, IPC failure, bounded parent retention), `test/quickjs-log-limits.test.ts` (unchanged successful logs) |
|
|
815
|
-
| `X3`, `X6` | `test/quickjs-executor.test.ts` (cancels a running child, never-settling await) |
|
|
860
|
+
| `X3`, `X6` | `test/quickjs-executor.test.ts` (cancels a running child, never-settling await), `test/execute.test.ts` (a wedged executor stops being awaited) |
|
|
816
861
|
| `X7` | `P3`'s tests; the Workers superset is deliberately unused |
|
|
817
862
|
|
|
818
863
|
The surface itself is checked by `test/server.test.ts` (the exact seven-tool
|