@zackbart/connecta 0.24.2 → 0.24.3
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/CHANGELOG.md +141 -0
- package/dist/auth/bearer.js +2 -0
- package/dist/auth/downstream-oauth.d.ts +12 -1
- package/dist/auth/downstream-oauth.js +147 -35
- package/dist/call-admission.d.ts +4 -0
- package/dist/call-admission.js +26 -0
- package/dist/catalog-drift.js +9 -4
- package/dist/catalog-service.d.ts +2 -0
- package/dist/catalog-service.js +25 -8
- package/dist/catalog.d.ts +2 -0
- package/dist/catalog.js +246 -121
- package/dist/connectors/api.js +11 -1
- package/dist/connectors/guarded-fetch.d.ts +1 -1
- package/dist/connectors/guarded-fetch.js +27 -20
- package/dist/connectors/remote-mcp.js +84 -53
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +58 -0
- package/dist/execute.js +85 -23
- package/dist/executor-result.js +3 -1
- package/dist/executors/quickjs-child.js +5 -1
- package/dist/executors/quickjs-protocol.d.ts +4 -0
- package/dist/executors/quickjs-runtime.d.ts +1 -1
- package/dist/executors/quickjs-runtime.js +38 -21
- package/dist/executors/quickjs.js +68 -27
- package/dist/index.d.ts +14 -0
- package/dist/index.js +24 -3
- package/dist/invocation.js +134 -93
- package/dist/mcp-result.js +3 -2
- package/dist/meta-tools.js +118 -39
- package/dist/registry.d.ts +14 -2
- package/dist/registry.js +87 -13
- package/dist/routes/mcp.d.ts +4 -1
- package/dist/routes/mcp.js +84 -13
- package/dist/routes/oauth.js +4 -0
- package/dist/routes/shared.d.ts +1 -0
- package/dist/routes/shared.js +4 -4
- package/dist/server.js +15 -3
- package/dist/skills.js +6 -5
- package/dist/storage/file.d.ts +6 -2
- package/dist/storage/file.js +312 -34
- package/dist/storage/memory.js +12 -1
- package/dist/validate.js +3 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/documentation/architecture.md +22 -6
- package/documentation/auth.md +42 -9
- package/documentation/call-admission.md +24 -8
- package/documentation/code-mode.md +34 -22
- package/documentation/connectors.md +47 -5
- package/documentation/meta-tools.md +74 -6
- package/documentation/operations.md +19 -19
- package/documentation/provider-conventions.md +7 -0
- package/documentation/request-admission.md +38 -4
- package/documentation/storage-and-credentials.md +54 -1
- package/documentation/upgrading.md +18 -4
- package/package.json +1 -1
- package/templates/node/package.json +1 -1
|
@@ -103,15 +103,28 @@ checks, and authorization operations stay outside the budget: they are not the
|
|
|
103
103
|
calls a provider is rate-limiting, and charging discovery for them would make
|
|
104
104
|
a program's first search cost it capacity to act.
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
`queueTimeoutMs` bounds only the wait for a permit. The per-call `timeoutMs`
|
|
107
|
+
is one deadline over catalog resolution, the permit wait, and the connector
|
|
108
|
+
call together, so a saturated call can never take longer than `timeoutMs`;
|
|
109
|
+
the queue timeout may only end it sooner. With
|
|
109
110
|
`diagnostics: true`, `admissionMs` reports the permit wait and `connectorMs`
|
|
110
111
|
the admitted attempt — which is the only way to tell "the provider is slow"
|
|
111
112
|
from "we are throttling ourselves".
|
|
112
113
|
|
|
113
114
|
## Enforcement scope
|
|
114
115
|
|
|
116
|
+
Shared connectors use the root registry's controller. Personal connectors use
|
|
117
|
+
the owning principal's registry, so two accounts each receive their own budget;
|
|
118
|
+
all requests and call paths for one principal still share it. `close()` rejects
|
|
119
|
+
queued and future admissions on both kinds of controller, including a personal
|
|
120
|
+
registry constructed after shutdown.
|
|
121
|
+
|
|
122
|
+
The principal cache is bounded at 1,024 registries. Eviction closes an idle
|
|
123
|
+
registry so an older request view cannot use an orphaned controller. A registry
|
|
124
|
+
with active calls, queued calls, or unexpired rolling entries is never evicted.
|
|
125
|
+
If all 1,024 are occupied, a new principal view fails closed with HTTP 403 until
|
|
126
|
+
one drains. Cache churn cannot reset a live account's quota.
|
|
127
|
+
|
|
115
128
|
This is deliberately **per-runtime**. It completely contains fan-out inside one
|
|
116
129
|
request, including parallel `connecta.call` calls in one Worker isolate. A rolling
|
|
117
130
|
budget is exact inside one Node process or Worker isolate, and best-effort
|
|
@@ -124,13 +137,16 @@ the bound actually is rather than implying a global one.
|
|
|
124
137
|
|
|
125
138
|
## Observations
|
|
126
139
|
|
|
127
|
-
`/health` exposes payload-free
|
|
128
|
-
`admission.downstreamCalls.
|
|
140
|
+
`/health` exposes one payload-free aggregate at
|
|
141
|
+
`admission.downstreamCalls.aggregate`, summed across root and retained personal
|
|
142
|
+
controllers: rule and retained partition counts, current
|
|
129
143
|
active and queued gauges, cumulative admitted/queued/rejected/rate-limited/
|
|
130
144
|
cancelled counts, and queue-wait count, total, and maximum. The open endpoint
|
|
131
|
-
never exposes partition keys, tool arguments, or results — a partition key can
|
|
145
|
+
never exposes connector ids, principal ids, partition keys, tool arguments, or results — a partition key can
|
|
132
146
|
be a customer identifier, which is precisely why it stays out of an unauthenticated
|
|
133
|
-
payload.
|
|
147
|
+
payload. Queue-wait maxima take the maximum, other counters sum, and `closed`
|
|
148
|
+
means every included controller is closed. Evicted idle controllers no longer
|
|
149
|
+
contribute to these runtime snapshots. Ordinary payload-free activity records the final call outcome and its
|
|
134
150
|
typed error code.
|
|
135
151
|
|
|
136
152
|
## Tests that enforce this
|
|
@@ -138,5 +154,5 @@ typed error code.
|
|
|
138
154
|
| Invariant | Suite |
|
|
139
155
|
| --- | --- |
|
|
140
156
|
| Independent partitions, exact rolling-window reset and retry, queued cancellation charging no budget, synchronous cancel during partition derivation, validated values snapshotted rather than read from mutable config, bounded partition state and contained `partitionKey` failures, empty and multi-rule policies refused | `test/call-admission.test.ts` (controller) |
|
|
141
|
-
|
|
|
157
|
+
| Shared and personal limiters shared by direct and program calls, independent principal budgets, shutdown covering both, eviction preserving live budgets, promise concurrency with input order preserved, cancellation threading, no dispatch or retry or health poisoning after cancellation, retry hints returned without waiting or poisoning health, payload-free `/health` aggregates | `test/call-admission.test.ts` (integration, Node + Workers) |
|
|
142
158
|
| Where provider budgets are allowed to come from at all | [provider conventions P12](./provider-conventions.md#p12--declare-an-admission-budget-only-when-the-provider-documents-a-number), [provider audit](https://github.com/zackbart/connecta/blob/main/records/provider-audit.md) |
|
|
@@ -180,11 +180,11 @@ const page = await connecta.search({
|
|
|
180
180
|
});
|
|
181
181
|
```
|
|
182
182
|
|
|
183
|
-
**S1.** Returns one flat page: `{ tools, total, offset, limit, hasMore }`, plus `nextOffset` when more remains and `matchMode: "partial"` when no tool matched every term. Top-level `search_tools` is different: it returns `{ connectors: [{ id, tools }], total, offset, limit, hasMore }`. Complete matches normally precede partial matches, but a partial candidate whose complete normalized tool name occurs in the normalized raw query competes by score; conversational cleanup applies only to scoring terms. Other candidates covering at least two terms fill the page after every complete match; when no complete match exists, the existing any-term fallback remains. Each entry in `tools` carries `address`, `name`, the configured `connectorTitle` when present (normalized whitespace, at most 120 UTF-8 bytes), and — when requested — `description`, `inputSchema`, `outputSchema`, `annotations`, and the connector's `guide`. An output shape learned under `S9` also carries `outputSchemaSource: "observed"`; provider declarations carry no source marker. Tool rows expose neither lexical scores nor per-result coverage. An empty or whitespace-only query browses. Non-empty input with no ASCII lexical terms returns no tools and bounded no-match analysis; mixed input searches with its ASCII terms. Compact shapes omit property prose, put required fields first, and cap each shape at 1,024 UTF-8 bytes. Each enum node gets 256 of those bytes. About three near-cap enum nodes can therefore coexist while leaving the final quarter for surrounding syntax; the unchanged global fallback still applies above 1,024 bytes. A capped enum preserves whole values before `unknown` and an exact omitted-value count, while an empty enum renders as `never`. Either cap carries `inputSchemaTruncated` or `outputSchemaTruncated`; a shape-wide cap remains structurally valid with `unknown` types plus `/* truncated */`. Small enums remain complete. Use `connecta.describe`
|
|
183
|
+
**S1.** Returns one flat page: `{ tools, total, offset, limit, hasMore }`, plus `nextOffset` when more remains and `matchMode: "partial"` when no tool matched every term. Top-level `search_tools` is different: it returns `{ connectors: [{ id, tools }], total, offset, limit, hasMore }`. Complete matches normally precede partial matches, but a partial candidate whose complete normalized tool name occurs in the normalized raw query competes by score; conversational cleanup applies only to scoring terms. Other candidates covering at least two terms fill the page after every complete match; when no complete match exists, the existing any-term fallback remains. Each entry in `tools` carries `address`, `name`, the configured `connectorTitle` when present (normalized whitespace, at most 120 UTF-8 bytes), and — when requested — `description`, `inputSchema`, `outputSchema`, `annotations`, and the connector's `guide`. An output shape learned under `S9` also carries `outputSchemaSource: "observed"`; provider declarations carry no source marker. Tool rows expose neither lexical scores nor per-result coverage. An empty or whitespace-only query browses. Non-empty input with no ASCII lexical terms returns no tools and bounded no-match analysis; mixed input searches with its ASCII terms. Compact shapes omit property prose, put required fields first, and cap each shape at 1,024 UTF-8 bytes. Each render spends at most 2,000 visits across schema nodes, property and required names, literal values, any constraint-free retry, and its key-only fallback. Resolved `$ref` text is reused within that walk; exhausted work yields `unknown /* truncated */` and the corresponding truncation flag. Each enum node gets 256 of those bytes. About three near-cap enum nodes can therefore coexist while leaving the final quarter for surrounding syntax; the unchanged global fallback still applies above 1,024 bytes. A capped enum preserves whole values before `unknown` and an exact omitted-value count, while an empty enum renders as `never`. Either cap carries `inputSchemaTruncated` or `outputSchemaTruncated`; a shape-wide cap remains structurally valid with `unknown` types plus `/* truncated */`. Small enums remain complete. `prefixItems` renders as a tuple with the declared `items` rest, an `unknown[]` rest when open, or no rest when `items` is false. `dependentSchemas` and `if`/`then`/`else` keep the base shape plus `/* conditional */` and set the truncation flag. `$dynamicRef` resolves a same-named definition like `$ref`; unresolved dynamic references become `unknown` with the truncation flag. These forms share the byte and work bounds above. Use `connecta.describe({ address, format: "json" })` or JSON search for omitted exact constraints.
|
|
184
184
|
|
|
185
185
|
**S1a.** `connector` loads only the named catalog; omit it only when the integration is ambiguous, because an unscoped search fans out across every configured connector. `safety: "readOnly"` returns exactly the tools available through `connecta.call`; `"approvalRequired"` returns the complementary fail-closed class, including false, missing, and contradictory annotations. Omitted or `"all"` preserves the complete catalog. These filters grant no authority and change no admission decision.
|
|
186
186
|
|
|
187
|
-
**S2.** A requested object schema carries `inputKeys`, `requiredInputKeys`, and `outputKeys`: the same names the rendered schema shows, ready to check before building arguments. Match inputs, truncation, safety, and outputs, not lexical
|
|
187
|
+
**S2.** A requested object schema carries `inputKeys`, `requiredInputKeys`, and `outputKeys`: the same names the rendered schema shows, ready to check before building arguments. `requiredInputKeys` includes only declared properties. Match inputs, truncation, safety, and outputs, not lexical
|
|
188
188
|
rank; search distinct operations separately and use `outputKeys`, not guessed roots. A non-object schema — a union, an array, an
|
|
189
189
|
unresolvable `$ref` — carries no lists rather than empty ones, because absent
|
|
190
190
|
means "read the schema" where `[]` would claim the tool takes no fields. The
|
|
@@ -199,9 +199,8 @@ carries the same metadata whenever schemas are requested. Code-mode callers
|
|
|
199
199
|
can set `includeSchemaKeys: false` to buy the bytes back.
|
|
200
200
|
|
|
201
201
|
**S3.** Discovery is bounded and the bounds throw rather than silently shrink: a
|
|
202
|
-
`limit` outside 1–100 is `
|
|
203
|
-
exceeds 256,000 bytes is `result_too_large
|
|
204
|
-
ask for less. The thrown error carries the stable `code`, `retryable`, and
|
|
202
|
+
`limit` outside 1–100, an `offset` that is not a non-negative integer, or a supplied `query` that is not a string is `invalid_args`. Omitted `offset` starts at 0; omitted `query` browses. A page whose serialized form
|
|
203
|
+
exceeds 256,000 bytes is `result_too_large`; a program's page is measured as its serialized value, while top-level `search_tools` measures the complete tool result including both copies and JSON escaping. Each error carries a hint for correcting the request. The thrown error carries the stable `code`, `retryable`, and
|
|
205
204
|
`details` fields (`E1`).
|
|
206
205
|
|
|
207
206
|
### connecta.describe
|
|
@@ -222,7 +221,7 @@ carry a route-aware `nextAction`; a close miss may add three canonical `suggesti
|
|
|
222
221
|
Catalog failures add only `retryAfterMs` when known. One bad address never fails the whole call. Each failed entry clamps its
|
|
223
222
|
caller-authored `address` to 512 UTF-8 bytes with an `…` marker. Entry order
|
|
224
223
|
correlates a clipped address with its request; successes keep canonical addresses. More than 100
|
|
225
|
-
addresses is `invalid_args`; the same 256,000-byte ceiling applies. A success whose output shape came from `S9` carries `outputSchemaSource: "observed"` beside the rendered schema.
|
|
224
|
+
addresses is `invalid_args`; the same 256,000-byte ceiling applies. Compact describe keeps property prose within a separate 8,192-byte UTF-8 shape cap and shares search's 2,000-visit work budget. A capped shape sets `inputSchemaTruncated` or `outputSchemaTruncated`; use `format: "json"` for the exact schema. A success whose output shape came from `S9` carries `outputSchemaSource: "observed"` beside the rendered schema.
|
|
226
225
|
|
|
227
226
|
### connecta.call
|
|
228
227
|
|
|
@@ -286,8 +285,9 @@ clauses are [Emitted output](#emitted-output) (`M1`–`M10`).
|
|
|
286
285
|
| Program or execution failure (`E5`, `E6`, a bridge bound in `L6`) | error text | no |
|
|
287
286
|
|
|
288
287
|
Both executor bridges reduce a rejected host call to `new Error(message)`. Connecta restores the typed failure in a trusted prelude with a per-execution authenticated frame (`X11`), without turning the rejection into a returned value.
|
|
289
|
-
`message` remains
|
|
288
|
+
`message` remains human text, capped at 2,000 JSON-serialized characters including quotes and an `…` marker when clipped. `code` and `retryable` are the stable branch fields; `details` carries the host classification. The complete details object fits 3,700 serialized characters. If optional recovery metadata would exceed that bound, it is omitted whole, preserving `code`, `message`, `retryable`, and `retryAfterMs`; a clipped recovery address or argument would describe a different call. This covers `call`, `search`, `describe`, `emit`, and the host-call budget.
|
|
290
289
|
Program-authored errors stay untyped, and code must never parse error prose.
|
|
290
|
+
An `unavailable` classification may include optional `details.host` as an HTTP(S) origin of at most 253 UTF-8 bytes and `details.code` as a validated network errno, undici transport code, or `timeout` of at most 32 bytes; these diagnostics never enter activity records.
|
|
291
291
|
|
|
292
292
|
**E2.** The taxonomy: `retryable` is what connecta reports, `Y3` what a program may do.
|
|
293
293
|
|
|
@@ -301,7 +301,7 @@ Program-authored errors stay untyped, and code must never parse error prose.
|
|
|
301
301
|
| `not_found` | the downstream answered and the resource is not there — the one code that says skip this id rather than stop, raised only where the provider tells absence from a permission gap ([H11](./provider-conventions.md#h11--errors-are-mapped-to-what-the-caller-does-next)) | false |
|
|
302
302
|
| `input_required_unsupported` | a downstream asked for mid-call input | false |
|
|
303
303
|
| `rate_limited` | the downstream reported a rate limit | true |
|
|
304
|
-
| `unavailable` | the downstream is down or unreachable | true |
|
|
304
|
+
| `unavailable` | the downstream is down or unreachable; optional sanitized `details.host` and `details.code` describe the transport failure without paths, queries, credentials, or provider prose | true |
|
|
305
305
|
| `timeout` | the per-call deadline (`execute.hostCallTimeoutMs`, default 15 s) expired | true |
|
|
306
306
|
| `cancelled` | the run ended while this call was in flight (`E5`) | false |
|
|
307
307
|
| `connector_call_failed` | anything else the connector threw | per message |
|
|
@@ -330,16 +330,16 @@ guest: admission rejection (`executor_overloaded`, retryable, with
|
|
|
330
330
|
reported to the model as an error result. One seam: a host call still in flight
|
|
331
331
|
when the run is cancelled fails with `cancelled`, catchable on the way out but
|
|
332
332
|
never worth acting on (`Y3`). When shutdown tears down a program that had
|
|
333
|
-
already started, accepted blocks are reported as discarded under `M4`; a failure before execution started carries no discard fields.
|
|
333
|
+
already started, accepted blocks are reported as discarded under `M4`; a failure before execution started carries no discard fields. A returned `error` field is a failure even when empty; an empty string reports `executor_failed` with `Error: Execution failed without an error message.`
|
|
334
334
|
|
|
335
335
|
**E6.** An error the program raises itself — a `TypeError`, a call to a
|
|
336
336
|
`connecta` member that is not a provider function (including an inherited one
|
|
337
337
|
like `toString`), a `throw` of its own — ends the run with an error result
|
|
338
338
|
carrying that message. It is not typed, because it is not a connector failure.
|
|
339
339
|
One precedence rule: connecta recognizes an escaped tool failure by its message —
|
|
340
|
-
exactly first, by containment second — so a program that *wraps* a failure's
|
|
340
|
+
exactly first, by containment second for messages of at least eight characters — so a program that *wraps* a failure's
|
|
341
341
|
message in its own text still reports the underlying typed failure. Keeping the
|
|
342
|
-
type beats keeping the prose.
|
|
342
|
+
type beats keeping the prose. Matching retains only the most recent 64 failures per execution, including caught refusals; an older escaped message remains an untyped execution failure. An empty terminal error uses the fixed message in `E5`.
|
|
343
343
|
|
|
344
344
|
**E7.** `retryable` for `unknown_address`, `unknown_tool`, and `destructive_tool_requires_approval` is pinned false, never inferred from an address containing `503`, `429`, or `temporar`. The first two carry `nextAction: { function: "connecta.search", arguments: { query, connector?, includeSchemas: "compact" } }` — the same scoped discovery the top-level record names, keyed to the surface the caller actually has. A program cannot call `search_tools`, so it is never told to. The message, the derived `query`, and a failed describe entry's `address` clamp caller-authored text to 512 UTF-8 bytes with an `…` marker. Those values land in the text content and `structuredContent`, so an invented 50 KB address would otherwise produce a refusal orders of magnitude past the deployment's result cap. A clipped address still identifies the mistake by its position; a short one — the common case — is exact and untagged.
|
|
345
345
|
|
|
@@ -383,7 +383,7 @@ anything, so paging its result would reward the one behavior code mode exists to
|
|
|
383
383
|
remove — and stashing every unprojected return value would spend the result store
|
|
384
384
|
on data nobody asked for.
|
|
385
385
|
|
|
386
|
-
**R5.** `console.log`, `console.warn`, and `console.error` are captured in call order and returned as a single `logs` string, capped at 4,000 characters with a truncation marker. Logs survive failure
|
|
386
|
+
**R5.** `console.log`, `console.warn`, and `console.error` are captured in call order and returned as a single `logs` string, capped at 4,000 characters with a truncation marker. Logs survive program failure through either a returned error result or a thrown error carrying `logs: string[]`. QuickJS streams captured entries to its parent and preserves the received prefix on cancellation, shutdown, deadline termination, child crashes, and IPC failures (`X4`). How a non-string argument renders is not contract (`X4`).
|
|
387
387
|
|
|
388
388
|
**R6.** Nothing else is added to a normal program result. Passing `diagnostics: true` adds one request-local, payload-free `diagnostics` block; a program that emitted adds `emitted: N` and its blocks (`M2`). Omitted, `false`, and emit-free are byte-for-byte the ordinary response path. Diagnostics exist so catalog, connector, and executor costs are distinguishable without persisting payloads or charging normal responses ([#247](https://github.com/zackbart/connecta/issues/247)).
|
|
389
389
|
|
|
@@ -433,8 +433,8 @@ media, not base64 text.
|
|
|
433
433
|
trusted exactly as much as the return value. Preservation is re-emission of
|
|
434
434
|
the raw downstream block, so `S5`'s uncapped fallthrough is contract.
|
|
435
435
|
|
|
436
|
-
**M7.** `emit` spends no host-call budget (`L4`); `
|
|
437
|
-
bounds.
|
|
436
|
+
**M7.** `emit` alone spends no host-call budget (`L4`); `search`, `describe`,
|
|
437
|
+
and `call` share it. `M5`'s bounds are emission's only bounds.
|
|
438
438
|
|
|
439
439
|
**M8.** Emission asks nothing of an executor: `emit` is a provider function,
|
|
440
440
|
blocks cross the guest boundary once as an argument, and `ExecuteResult` is
|
|
@@ -491,7 +491,7 @@ because connecta enforces them above the sandbox:
|
|
|
491
491
|
|
|
492
492
|
| Bound | Value |
|
|
493
493
|
| --- | --- |
|
|
494
|
-
| Host calls per execution | 20 |
|
|
494
|
+
| Host calls per execution, shared by `search`, `describe`, and `call` | 20 by default, `execute.maxHostCalls` |
|
|
495
495
|
| Deadline per host call | 15 s, `execute.hostCallTimeoutMs` |
|
|
496
496
|
| Discovery page | ≤ 100 tools, ≤ 256,000 serialized bytes |
|
|
497
497
|
| `describe` addresses | ≤ 100 |
|
|
@@ -500,7 +500,7 @@ because connecta enforces them above the sandbox:
|
|
|
500
500
|
| Result | 24,000 serialized characters |
|
|
501
501
|
| Logs presented to the model | 4,000 characters |
|
|
502
502
|
|
|
503
|
-
Exhausting the host-call budget fails that call with non-retryable `budget_exceeded` (`E2`) and a message naming the budget. No connector is reached, and the budget does not refill inside one execution.
|
|
503
|
+
Every `call` attempt spends one host call on entry, before address resolution, catalog lookup, safety checks, validation, or dispatch. Unknown addresses, unknown tools, catalog failures, and other pre-dispatch refusals spend the same budget as successful calls; catching a refusal does not refund it. `search` and `describe` likewise spend on entry. Exhausting the host-call budget fails that call with non-retryable `budget_exceeded` (`E2`) and a message naming the budget. No connector is reached, and the budget does not refill inside one execution.
|
|
504
504
|
|
|
505
505
|
**L5.** The guest is memory-, stack-, and CPU-bounded, and a program that
|
|
506
506
|
exhausts a bound ends the run with an error instead of degrading the host. The
|
|
@@ -588,7 +588,14 @@ arguments and captures `log`, `info`, `warn`, `error`, and `debug`; the Dynamic
|
|
|
588
588
|
Worker renders arguments with `String()` (so an object logs as
|
|
589
589
|
`[object Object]`) and captures only `log`, `warn`, and `error`, prefixing the
|
|
590
590
|
latter two. Only the three captured everywhere are contract (`R5`); rendering is
|
|
591
|
-
not.
|
|
591
|
+
not. QuickJS streams each accepted entry within the existing IPC envelope
|
|
592
|
+
bound while retaining its per-entry and cumulative child caps. The parent
|
|
593
|
+
keeps at most 4,001 joined characters for failure recovery, one beyond the
|
|
594
|
+
presentation cap so truncation stays visible. On a normal reply, its complete
|
|
595
|
+
log array takes precedence over that prefix; the two copies are never joined.
|
|
596
|
+
On termination or IPC failure, the parent attaches its retained prefix to the
|
|
597
|
+
thrown error. Admission rejection before the program starts has no guest logs
|
|
598
|
+
to recover.
|
|
592
599
|
|
|
593
600
|
**X5. Leftover authority.** QuickJS blocks imports and has no `fetch`, `process`, timers, `crypto`, or `WebSocket`. Its Node child starts with an explicitly empty process environment rather than inheriting deployment variables or `NODE_OPTIONS`. A Dynamic Worker has those globals plus a non-contract set of runtime builtins through `import()` and `process.getBuiltinModule()`, including `node:path`, `node:crypto`, `node:net`, `node:tls`, `node:dns`, `node:module`, and `cloudflare:workers`. The upstream set can drift; this list is not an allowlist.
|
|
594
601
|
The supported Worker construction is exactly `new DynamicWorkerExecutor({ loader })`. Do not pass `bindings`, `modules`, or `globalOutbound`: each can grant ambient configuration, code, or egress. Under it, `process.env`, lexical `this.env`, and `cloudflare:workers.env` are empty; `node:fs`, `node:http`, and `node:https` are unavailable through either access route; external `fetch`, `WebSocket`, `node:net`, and `node:tls` fail with workerd's outbound-denial error; DNS lookup ends unresolved; and `fetch("data:...")` resolves locally.
|
|
@@ -625,7 +632,12 @@ from one tool call therefore fails on Node and may succeed on Workers — reduce
|
|
|
625
632
|
inside the program either way (`R1`).
|
|
626
633
|
|
|
627
634
|
**X11. Typed host rejection.** Both executors rebuild Connecta's authenticated host-failure frame as a thrown guest `Error` (`E1`). The per-run secret stays in the trusted prelude closure, and the prelude locks `globalThis.Error`, so guest code and connector prose cannot forge the host transport frame.
|
|
628
|
-
The
|
|
635
|
+
The host bounds details before framing (`E1`), including JSON escapes. QuickJS
|
|
636
|
+
refuses an oversized authenticated frame whole rather than slicing through its
|
|
637
|
+
JSON, and the prelude hides an authenticated frame whose JSON is malformed.
|
|
638
|
+
QuickJS keeps the raw bridge and its JSON decoder in a private closure so guest
|
|
639
|
+
code cannot intercept the frame before the prelude handles it. A mismatched
|
|
640
|
+
frame is ordinary untyped prose.
|
|
629
641
|
|
|
630
642
|
## Changes from earlier code mode
|
|
631
643
|
|
|
@@ -659,7 +671,7 @@ the upstream `Executor` shape assignable.
|
|
|
659
671
|
| `S5` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`unwrapMcpResult`) |
|
|
660
672
|
| `S6` | `test/execute.test.ts` (fail-closed annotations, activity parity) |
|
|
661
673
|
| `S7` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (parallel calls and shared admission) |
|
|
662
|
-
| `S8`, `E1`, `X11` | both guest-contract executors (caught call, discovery, utility, budget, removed-function, and forgery cases; typed promise rejections) |
|
|
674
|
+
| `S8`, `E1`, `X11` | both guest-contract executors (caught call, discovery, utility, budget, removed-function, and forgery cases; typed promise rejections), `test/quickjs-executor.test.ts` (oversized messages, private transport, and forged frames) |
|
|
663
675
|
| `S9` | `test/result-shapes.test.ts` (value exclusion, bounds, merging, LRU and time expiry, runtime isolation, read-only admission, declared precedence, definition invalidation, unwrapped MCP results, discovery provenance, copy isolation, and failure isolation) |
|
|
664
676
|
| `E2`, `E8` | `test/guest-api-contract.test.ts` (code → `retryable`, caught, parallel, and uncaught validation recovery), `test/meta-tools.test.ts` (direct, destructive, provider fallback), `test/validate.test.ts` (bounded payload-free findings), `test/errors.test.ts` |
|
|
665
677
|
| `E3` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (`auth_required`) |
|
|
@@ -677,9 +689,9 @@ the upstream `Executor` shape assignable.
|
|
|
677
689
|
| `Y4` | `test/meta-tools-call.test.ts`, `test/call-admission.test.ts` (one attempt, retry hints, caller reissue) |
|
|
678
690
|
| `L1`, `L2` | `test/guest-api-contract.test.ts` (in-flight call fails `cancelled`), `test/execute.test.ts` (cancels outstanding host calls) |
|
|
679
691
|
| `L3`, `X1` | `test/guest-api-contract.test.ts` (short-deadline executors) |
|
|
680
|
-
| `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (budgets) |
|
|
692
|
+
| `L4`, `L8` | `test/guest-api-contract.test.ts`, `test/execute.test.ts` (shared discovery/call budgets) |
|
|
681
693
|
| `L5`, `X2` | `test/quickjs-executor.test.ts` (CPU, heap) |
|
|
682
|
-
| `L6`, `X10` | `test/quickjs-executor.test.ts` (bridge and IPC bounds for arguments and result; the address in the over-bound message) |
|
|
694
|
+
| `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) |
|
|
683
695
|
| `L7` | `test/execute.test.ts`, `test/executor-admission.test.ts` |
|
|
684
696
|
| `V1`–`V4` | `test/guest-api-contract.test.ts` (dispatched calls, every refusal class including an address no connector owns, the friction each derives, no event for the execution itself), `test/activity.test.ts` (the shared code → friction table, and the identity clamp) |
|
|
685
697
|
| `M1` | `test/guest-api-contract.test.ts` (invalid emits throw catchably, accept nothing), `test/execute-emit.test.ts` (every rejected shape) |
|
|
@@ -690,7 +702,7 @@ the upstream `Executor` shape assignable.
|
|
|
690
702
|
| `M8` | two arms passing one case table, `test/codemode-compat.test.ts` |
|
|
691
703
|
| `M10` | `test/execute-emit.test.ts` (aggregate present, numbers only, absent when nothing emitted) |
|
|
692
704
|
| `X3` | `test/quickjs-executor.test.ts` (cancels a running child) |
|
|
693
|
-
| `X4` | `test/guest-api-contract.test.ts` (string logs only) |
|
|
705
|
+
| `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) |
|
|
694
706
|
| `X6` | `test/quickjs-executor.test.ts` (never-settling await) |
|
|
695
707
|
| `X7` | `P3`'s tests; the Workers superset is deliberately unused |
|
|
696
708
|
|
|
@@ -188,6 +188,21 @@ downstream wrote them, and an unannotated or contradictory one stays
|
|
|
188
188
|
fail-closed onto `call_destructive_tool`. The contract binds the surfaces we
|
|
189
189
|
write, not the catalogs we relay.
|
|
190
190
|
|
|
191
|
+
An `unavailable` failure can carry `details.host` and `details.code` in its
|
|
192
|
+
classified error. `host` is an HTTP(S) origin, including a non-default port,
|
|
193
|
+
with no userinfo, path, query, or fragment and at most 253 UTF-8 bytes. `code`
|
|
194
|
+
is an allowlisted network errno, an undici transport code, or `timeout`, at
|
|
195
|
+
most 32 bytes. Invalid or oversized fields are omitted, never clipped.
|
|
196
|
+
`remoteMcp()` and the guarded transport know the failed fetch destination;
|
|
197
|
+
`api()` can classify a handler's structured runtime code but cannot recover
|
|
198
|
+
its destination from `ctx.baseUrl`, which names Connecta itself. A handler can
|
|
199
|
+
supply a known URL in a typed `ConnectorCallError` and the constructor reduces
|
|
200
|
+
it to an origin. AbortError and TimeoutError become diagnostic `timeout`;
|
|
201
|
+
workerd outbound denial supplies no errno, so a fetch boundary reports the
|
|
202
|
+
known origin alone. No provider prose supplies a code or retryability.
|
|
203
|
+
These diagnostics belong in tool failures and the bounded warning log, never
|
|
204
|
+
in payload-free activity records.
|
|
205
|
+
|
|
191
206
|
## The guarded fetch transport
|
|
192
207
|
|
|
193
208
|
Every hand-written HTTP surface re-derives the same safety machinery, and two
|
|
@@ -237,9 +252,13 @@ What it owns is mechanical and provider-independent:
|
|
|
237
252
|
as an absurd response is a fact about the API, not about HTTP. A declared
|
|
238
253
|
`Content-Length` past the ceiling fails before a byte is read, and a
|
|
239
254
|
streaming body is abandoned at the ceiling rather than buffered past it.
|
|
255
|
+
Without a body stream, text is measured in UTF-8 bytes before it is accepted;
|
|
256
|
+
JSON is parsed from that same bounded text. Such runtimes still buffer their
|
|
257
|
+
read internally, but cannot return an oversized body as a successful result.
|
|
240
258
|
- **Normalization.** An unreachable provider becomes a retryable `unavailable`
|
|
241
259
|
instead of whatever `TypeError` the runtime threw, and `ctx.signal` rides
|
|
242
|
-
every request.
|
|
260
|
+
every request. `Retry-After` hints accept delta-seconds and HTTP dates;
|
|
261
|
+
a date in the past means no further wait.
|
|
243
262
|
|
|
244
263
|
What it deliberately does not own is meaning. It never reads a status code and
|
|
245
264
|
never invents an authentication scheme: the provider's `authenticate` callback
|
|
@@ -288,9 +307,13 @@ Connecta deliberately sits between protocol generations
|
|
|
288
307
|
compatibility concession, so modern protocol support is still discovered.
|
|
289
308
|
- **Legacy sessions:** Connecta's own endpoint creates no protocol session, but
|
|
290
309
|
a stateful legacy downstream can still issue `Mcp-Session-Id`. Closing a
|
|
291
|
-
request scope
|
|
292
|
-
|
|
293
|
-
|
|
310
|
+
request scope, rotating credentials, retiring an OAuth generation, and
|
|
311
|
+
abandoning a connect all send the legacy DELETE before closing the transport.
|
|
312
|
+
The DELETE gets at most one second and failures are logged. Rotation and
|
|
313
|
+
abandoned connects start cleanup without waiting; the connector context has
|
|
314
|
+
no deferred-work hook. Scope teardown gives its bounded tail back to core,
|
|
315
|
+
which can attach the runtime's deferred channel. SDK v2 `Client.close()` does
|
|
316
|
+
not send DELETE on Connecta's behalf.
|
|
294
317
|
- **Modern cache hints:** `tools/list` is deployment-fixed and returns a
|
|
295
318
|
one-hour private cache hint. Downstream hints do not alter Connecta's existing
|
|
296
319
|
five-minute fingerprinted catalog cache; that remains gated in
|
|
@@ -352,7 +375,11 @@ For remote MCP tools, that path checks the catalog's advertised `inputSchema`
|
|
|
352
375
|
before provider dispatch. Supported mismatches become bounded, payload-free
|
|
353
376
|
`invalid_args` findings; a schema the local validator cannot evaluate passes
|
|
354
377
|
through unchanged. Connecta does not parse provider error prose to invent a
|
|
355
|
-
validation classification.
|
|
378
|
+
validation classification. A downstream JSON-RPC `-32602` is an explicit
|
|
379
|
+
`invalid_args` refusal, with a bounded server message and no retry. HTTP 4xx
|
|
380
|
+
refusals preserve a bounded JSON `message`, including `error.message`, and are
|
|
381
|
+
non-retryable; 429 becomes `rate_limited` and 408 becomes `timeout`. The SDK
|
|
382
|
+
still owns OAuth challenges and step-up authorization.
|
|
356
383
|
|
|
357
384
|
## Authentication
|
|
358
385
|
|
|
@@ -363,6 +390,21 @@ server issuer discovered and validated by the SDK; see
|
|
|
363
390
|
The callback route validates `state` before passing the complete callback query
|
|
364
391
|
to the SDK so RFC 9207 `iss` validation is not lost.
|
|
365
392
|
|
|
393
|
+
Within one runtime, overlapping refreshes share one completion gate per
|
|
394
|
+
credential partition and generation. A valid token response is a consumed
|
|
395
|
+
refresh token, so the coordinator keeps that response's tokens on the flight.
|
|
396
|
+
If the owner then fails before the SDK saves them — cancelled, redirected to
|
|
397
|
+
authorization, or invalidated — the host persists the rotation itself, holds
|
|
398
|
+
contenders behind the mutation marker until that write lands, and hands them
|
|
399
|
+
the saved rotation; a retired token is never redeemed twice. A write already
|
|
400
|
+
running in `saveTokens` clears the marker on its own success or failure, and a
|
|
401
|
+
late duplicate save from a detached owner is harmless. Nothing can leave a
|
|
402
|
+
permanent 503 gate ([#526](https://github.com/zackbart/connecta/issues/526)).
|
|
403
|
+
Refresh token response validation
|
|
404
|
+
reads at most 65,536 bytes, and credential reads stop after 64 revision races
|
|
405
|
+
with `temporarily_unavailable` so churn cannot spin indefinitely. Request
|
|
406
|
+
cancellation is checked before each attempt.
|
|
407
|
+
|
|
366
408
|
A remote MCP connector that authenticates with a static key has two ways to
|
|
367
409
|
receive one. `{ type: "headers", headers }` bakes the literal value into the
|
|
368
410
|
deployment file, which suits a secret the runtime already holds.
|
|
@@ -121,15 +121,25 @@ bytes. Within that unchanged total, each enum node and each constraint
|
|
|
121
121
|
annotation may spend at most 256 UTF-8 bytes. Numeric bounds, string length
|
|
122
122
|
bounds, patterns, and formats render beside their type. A constraint that does
|
|
123
123
|
not fit is dropped whole. If constraints push the full shape over 1,024 bytes,
|
|
124
|
-
search retries the shape without them. Compact describe keeps
|
|
125
|
-
constraints
|
|
124
|
+
search retries the shape without them. Compact describe keeps declared
|
|
125
|
+
constraints and property prose within its own 8,192-byte shape cap, sharing
|
|
126
|
+
search's 2,000-visit rendering budget; a capped shape sets
|
|
127
|
+
`inputSchemaTruncated` or `outputSchemaTruncated`, and `format: "json"` or
|
|
128
|
+
JSON search returns the exact schema. A large enum keeps the longest whole-value prefix that fits, then
|
|
126
129
|
adds `unknown` and a comment with the exact omitted-value count. An empty enum
|
|
127
130
|
renders as the valid `never` type. A capped object becomes a valid
|
|
128
131
|
required-first shape with `unknown` types; other shapes become
|
|
129
132
|
`unknown /* truncated */`. Any cap marks the match with
|
|
130
133
|
`inputSchemaTruncated` or `outputSchemaTruncated`; repeat the search with
|
|
131
134
|
`includeSchemas: "json"` or use the existing describe path when exact
|
|
132
|
-
constraints matter.
|
|
135
|
+
constraints matter. `prefixItems` renders as a tuple, with the `items` type
|
|
136
|
+
as its rest, an `unknown[]` rest when open, and no rest for `items: false`.
|
|
137
|
+
`dependentSchemas` and `if`/`then`/`else` preserve the base shape and append
|
|
138
|
+
`/* conditional */`, setting the truncation flag so the caller reads the exact
|
|
139
|
+
JSON schema. `$dynamicRef` resolves a same-named definition like `$ref`; an
|
|
140
|
+
unresolved dynamic reference renders as `unknown` with the truncation flag.
|
|
141
|
+
These shapes share the same byte and work budgets. Small enums and both exact
|
|
142
|
+
paths remain complete.
|
|
133
143
|
|
|
134
144
|
## Connector guide selection
|
|
135
145
|
|
|
@@ -206,12 +216,42 @@ removing or summarizing the text copy is deferred until host-forwarding
|
|
|
206
216
|
measurements demonstrate that supported clients do not need it.
|
|
207
217
|
|
|
208
218
|
Plain-text guidance and errors remain text-only. A downstream MCP tool's native
|
|
209
|
-
content blocks
|
|
210
|
-
|
|
219
|
+
content blocks pass through when `call_tool` uses MCP result mode. When no
|
|
220
|
+
text block exists and `structuredContent` is present, Connecta appends a text
|
|
221
|
+
block containing its compact JSON, then applies the same content size guard.
|
|
222
|
+
This preserves structured-only results, including `null`, arrays, and scalars.
|
|
223
|
+
An existing text mirror stays unchanged; Connecta does not add another copy. Newly stashed JSON and
|
|
211
224
|
downstream content envelopes use compact serialization, so `get_result` byte
|
|
212
225
|
offsets and totals refer to that exact compact text.
|
|
213
226
|
|
|
214
|
-
|
|
227
|
+
The direct-call stash keeps results for 15 minutes. `results.maxStashBytes`
|
|
228
|
+
defaults to 8 MiB and `results.maxStashEntries` to 64 per `createConnecta`
|
|
229
|
+
runtime, shared across all subjects and pools. Both accept non-negative safe
|
|
230
|
+
integers; zero disables stashing. The byte budget counts the stored ASCII
|
|
231
|
+
paging envelope, including base64 overhead, rather than only the result text.
|
|
232
|
+
Capacity is reserved before each storage write, so concurrent requests cannot
|
|
233
|
+
oversubscribe it. A full stash refuses new entries. A later stash attempt
|
|
234
|
+
deletes expired entries before reusing their capacity; a failed deletion keeps
|
|
235
|
+
the charge. These bounds cover writes by this runtime, not other processes,
|
|
236
|
+
Worker isolates, or entries left by a previous runtime.
|
|
237
|
+
|
|
238
|
+
Results belong to the authenticated subject whenever auth supplies a subject
|
|
239
|
+
or user id, independently of activity configuration. The provider's namespace
|
|
240
|
+
is used when present; otherwise the namespace is `connecta:auth:<provider kind>`.
|
|
241
|
+
Keep subject ids distinct within that namespace. An explicit principal is the
|
|
242
|
+
fallback subject when neither id is supplied. Open deployments and auth
|
|
243
|
+
providers that supply no identity share one partition.
|
|
244
|
+
|
|
245
|
+
New entries store UTF-8 bytes in a base64 envelope. After the KV read,
|
|
246
|
+
`get_result` decodes only the requested byte range and a few boundary bytes;
|
|
247
|
+
it does not encode the full text on every page. Storage still reads one full
|
|
248
|
+
value. Pre-upgrade raw-text entries remain readable during their TTL using
|
|
249
|
+
one full encoding per page. Offsets and `totalBytes` always describe the
|
|
250
|
+
original UTF-8 text, not the envelope. A supplied offset inside a character
|
|
251
|
+
moves back to its start; page ends also align to character boundaries, and a
|
|
252
|
+
page smaller than one character widens just enough to make progress.
|
|
253
|
+
|
|
254
|
+
A successfully stashed `call_tool` truncation notice carries both the historical `resultId` and an
|
|
215
255
|
exact `nextAction: { tool: "get_result", arguments: { id, offset: 0 } }`. The
|
|
216
256
|
handle is therefore
|
|
217
257
|
directly actionable without copying an identifier out of prose. Program results
|
|
@@ -219,6 +259,28 @@ and oversized discovery responses carry no such route — paging a program's
|
|
|
219
259
|
return value is a refused shape, because a program can shrink anything before
|
|
220
260
|
it returns.
|
|
221
261
|
|
|
262
|
+
A refused stash or failed stash write cannot undo a downstream success. Both direct call tools
|
|
263
|
+
return a truncated preview where usable, with a paging-unavailable notice and
|
|
264
|
+
no `resultId` or paging action. Activity records success; the operator logger
|
|
265
|
+
receives a fixed warning with the connector and tool, without storage error
|
|
266
|
+
prose. For read-only work, reduce the result inside `execute_code`; repeating
|
|
267
|
+
an approved write is not a way to recover its output. Other result-processing
|
|
268
|
+
failures use a fixed `result_processing_failed` message and are never retryable.
|
|
269
|
+
|
|
270
|
+
Downstream MCP `isError` text is bounded at its source to 512 UTF-8 bytes plus
|
|
271
|
+
an `…` marker. Error framing may shorten it further to fit the call's result
|
|
272
|
+
cap, counting JSON escaping and both copies in value mode.
|
|
273
|
+
|
|
274
|
+
The top-level discovery ceiling is 256,000 UTF-8 bytes for the serialized tool
|
|
275
|
+
result, including text, `structuredContent`, and JSON escaping. Measuring only
|
|
276
|
+
one copy would advertise half the bytes the adapter actually returns.
|
|
277
|
+
|
|
278
|
+
A per-call `timeoutMs` covers catalog resolution, admission, and connector
|
|
279
|
+
execution with one deadline. The admission queue's own timeout may expire
|
|
280
|
+
sooner, but it cannot extend the call deadline. Result processing happens after
|
|
281
|
+
that deadline ends, because a completed downstream call must not turn into a
|
|
282
|
+
retryable timeout while Connecta prepares its response.
|
|
283
|
+
|
|
222
284
|
## Lexical discovery
|
|
223
285
|
|
|
224
286
|
`search_tools` tokenizes tool names and descriptions at punctuation and
|
|
@@ -373,6 +435,12 @@ recovery query, each of which lands in both the text content and
|
|
|
373
435
|
the address is the thing being corrected, a clipped one still identifies the
|
|
374
436
|
mistake, and a short one — every real one — comes back exact and untagged.
|
|
375
437
|
|
|
438
|
+
Unknown `get_result.id`, `authorize_connector.connector`, and `skills.name`
|
|
439
|
+
echoes use the same 512-byte clamp. `search_tools.connector` instead rejects
|
|
440
|
+
values over 512 UTF-8 bytes with `invalid_args` before catalog lookup, so a
|
|
441
|
+
clipped scope can never select a different connector. A failed result-storage
|
|
442
|
+
read returns typed `unavailable` without exposing backend error text.
|
|
443
|
+
|
|
376
444
|
`call_destructive_tool` accepts an optional
|
|
377
445
|
`reason` of at most 500 characters for the host's human approval view. It is
|
|
378
446
|
outer-call context only: Connecta neither treats it as authority nor passes it
|
|
@@ -231,30 +231,30 @@ in.
|
|
|
231
231
|
| Suite | Covers |
|
|
232
232
|
| --- | --- |
|
|
233
233
|
| `activity.test.ts` | payload-free delivery: a rejected async write attaches to `waitUntil` instead of throwing, approved destructive calls record under their real entry point, result-size friction records without retaining the result, and a hallucinated connector id or invented identity is clamped so the event still cannot carry a payload |
|
|
234
|
-
| `api-connector.test.ts` | `api()` — kind, description, tool defs, dispatch, default args, unknown tools, handler throws, argument validation, and the construction contract |
|
|
234
|
+
| `api-connector.test.ts` | `api()` — kind, description, tool defs, dispatch, default args, unknown tools, handler throws, argument validation, structured transport diagnostics, and the construction contract |
|
|
235
235
|
| `bearer.test.ts` | constant-time bearer compare, case-insensitive scheme, 401 challenges, and the retired audience options refusing rather than silently unbinding |
|
|
236
236
|
| `branding.test.ts` | branding fallbacks and overrides across the operator shells, OAuth result pages, `/favicon.*`, page titles, and escaping — branding is not an injection vector |
|
|
237
|
-
| `call-admission.test.ts` | connector-scoped per-runtime downstream admission ([call admission](./call-admission.md)): independent partitions, exact rolling-window reset, cancellation that charges no budget, bounded partition state, local-refusal health isolation,
|
|
238
|
-
| `catalog-drift.test.ts` | `vettedCatalog()`, `detectCatalogDrift()`, and `withVettedCatalog()`; drift on the registry surface and on `/health`; the connector seam projected rather than echoed; and the drift types being public |
|
|
239
|
-
| `catalog.test.ts` | lexical ranking and the compact schema renderer
|
|
237
|
+
| `call-admission.test.ts` | connector-scoped per-runtime downstream admission ([call admission](./call-admission.md)): independent partitions, exact rolling-window reset, cancellation that charges no budget, bounded partition state, local-refusal health isolation, direct and program calls sharing the appropriate controller, independent personal budgets, shared budgets, shutdown of both controller kinds, eviction preserving live budgets, and id-free `/health` aggregates |
|
|
238
|
+
| `catalog-drift.test.ts` | `vettedCatalog()`, `detectCatalogDrift()`, and `withVettedCatalog()`; bounded schema-digest depth; drift on the registry surface and on `/health`; the connector seam projected rather than echoed; and the drift types being public |
|
|
239
|
+
| `catalog.test.ts` | lexical ranking and the compact schema renderer: `const`, `allOf` beside siblings, memoized `$ref`, node/depth/byte bounds, truncation flags, declared required-key metadata, typed search argument validation, per-schema caching, and 2020-12 tuples, conditional markers, and dynamic references in search and describe |
|
|
240
240
|
| `clerk.test.ts` | protected-resource metadata, the browser sign-in config, OAuth and session tokens, cached best-effort activity labels with their caps, the hand-applied `azp` rejection, and the `allowedDomains` allowlist including every lookalike that must not be repaired into a match |
|
|
241
241
|
| `cloudflare-access-auth.test.ts` | trusted `ctx.access` human and service identities, absent/error fail-closed behavior, service-token MCP admission without operator mutation, human same-origin mutation, and the Clerk-to-ambient shell switch |
|
|
242
242
|
| `cloudflare-provider.test.ts` | `cloudflare()` API and MCP construction, the code-mode safety manifest, API tool surface, current R2 and KV jurisdictions, useful output declarations, request building, projections including additive provider fields, typed failures, and credential test |
|
|
243
243
|
| `code-first-surface.test.ts` | the seven-tool surface itself — an executor required, every removed option and top-level tool refused, compact always-loaded routing pinned below 1,000 characters, complete on-demand usage served, and no rendering instructions |
|
|
244
244
|
| `codemode-compat.test.ts` | the `Executor` seam staying structurally compatible with `@cloudflare/codemode`'s `DynamicWorkerExecutor`, enforced by `tsc` |
|
|
245
|
-
| `config.test.ts` | the grouped `ConnectaConfig` boundary — each group forwarding to its internals, malformed admission bounds failing construction, and unknown own-properties rejected by their complete path before construction does work |
|
|
245
|
+
| `config.test.ts` | the grouped `ConnectaConfig` boundary — each group forwarding to its internals, malformed admission bounds, result stash limits, and origin lists failing construction, open-connector warnings including static API auth, and unknown own-properties rejected by their complete path before construction does work |
|
|
246
246
|
| `credentials.test.ts` | the pure stored-shape classifier (containment, not equality) and the AES-GCM vault: round-trip, ciphertext bound to its connector id, named field sets, masked metadata, wrong-key rejection, deletion, coexistence with OAuth keys |
|
|
247
247
|
| `d1-activity-example.test.ts` | the Worker example's deployment-owned D1 activity store: actor namespace round-trip, payload-free friction reconstructed from the persisted code, and agreement with the package's friction table |
|
|
248
|
-
| `downstream-oauth.test.ts` | `KvOAuthProvider` round-trips and generation races, runtime-local rotating-token refresh
|
|
249
|
-
| `errors.test.ts` | `ConnectorCallError` codes, retryable defaults and overrides, `retryAfterMs` round-trip, typed-over-heuristic classification, `AbortError` as a retryable timeout, and framing errors |
|
|
250
|
-
| `execute.test.ts` | the code-mode host bridge: identifier sanitization, account titles in program discovery, MCP-result unwrapping, sandbox provider construction, authenticated thrown-failure framing, fail-closed filtering of destructive and unannotated tools, MCP/code-mode invocation parity, and payload-free describe diagnostics |
|
|
248
|
+
| `downstream-oauth.test.ts` | `KvOAuthProvider` round-trips and generation races, two eight-scope waves of runtime-local rotating-token refresh, recovery after abort/redirect/invalidation, bounded token reads and revision churn, refresh failure/retry, `auth_required` versus `error`, `startAuth`/`finishAuth`, callback refusal equality and identity-free 401 versus explicit 403, bounded diagnostics, and HTML escaping |
|
|
249
|
+
| `errors.test.ts` | `ConnectorCallError` codes, retryable defaults and overrides, `retryAfterMs` round-trip, typed-over-heuristic classification, `AbortError` as a retryable timeout, sanitized bounded unavailable diagnostics, and framing errors |
|
|
250
|
+
| `execute.test.ts` | the code-mode host bridge: identifier sanitization, account titles in program discovery, MCP-result unwrapping, sandbox provider construction, authenticated thrown-failure framing, shared discovery/call budgets including pre-dispatch refusals, bounded recent-failure matching, empty terminal errors, logs on thrown executor failures, short-message failure matching, fail-closed filtering of destructive and unannotated tools, MCP/code-mode invocation parity, and payload-free describe diagnostics |
|
|
251
251
|
| `execute-emit.test.ts` | `connecta.emit` (M1–M10) — block validation, budgets, the provider, delivery after the result envelope on success only, and the defaults |
|
|
252
252
|
| `executor-admission.test.ts` | the portable bounded FIFO both pools use: active and queue ceilings, stable retryable overload, queue timeout, cancellation removal, idempotent release, shutdown |
|
|
253
|
-
| `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement,
|
|
253
|
+
| `guarded-fetch.test.ts` | the guarded transport — construction, request building, destination confinement, response handling, bounded no-stream text/JSON reads, sanitized transport diagnostics, and delta-seconds/HTTP-date Retry-After hints |
|
|
254
254
|
| `guest-api-contract.test.ts` | the shared guest contract on the Dynamic Worker, including caught call, typed inline describe recovery, discovery, utility, parallel-call, and budget failure codes; plus the real authority boundary — local `data:` fetch, denied egress, unresolved DNS, empty environment paths, unavailable filesystem/HTTP builtins, and present runtime globals |
|
|
255
|
-
| `identity-scope.test.ts` | identity-derived connector visibility, named pools at `/mcp/<pool>` (grant-gated, intersected with the identity ceiling, identical 404 for undeclared, refused, and throwing grants, construction-time refusals), exact `connector.tool` grants enforced identically across discovery, direct calls, the program host bridge, and the connection UI, fail-closed grant parsing, the once-per-isolate absent-grant warning, personal credential isolation, separate shared-auth and personal-auth management permissions, and personal OAuth callback ownership |
|
|
255
|
+
| `identity-scope.test.ts` | identity-derived connector visibility, named pools at `/mcp/<pool>` (grant-gated, intersected with the identity ceiling, identical 404 for undeclared, refused, and throwing grants, construction-time refusals), exact `connector.tool` grants enforced identically across discovery, direct calls, the program host bridge, and the connection UI, fail-closed grant parsing, the once-per-isolate absent-grant warning, personal credential isolation, separate shared-auth and personal-auth management permissions, bearer-subject result isolation without activity namespaces, connector authorization visibility for tool grants, and personal OAuth callback ownership |
|
|
256
256
|
| `linear-provider.test.ts` | the Linear proxy's construction, guide, plan-aware catalog superset, and current workspace, template, and issue-sharing classifications |
|
|
257
|
-
| `meta-tools-call.test.ts` | registry-backed calls: structured errors, truncation and `get_result`, per-connector result bounds, JSON representation failures, MCP content bounds,
|
|
257
|
+
| `meta-tools-call.test.ts` | registry-backed calls: structured errors, truncation and `get_result`, per-connector result bounds, JSON representation failures, MCP content bounds, offset alignment, runtime-wide stash quotas including pending writes and expiry reclamation, and page-proportional decoding |
|
|
258
258
|
| `meta-tools-search.test.ts` | registry-backed discovery: bounded search with page and address maxima, compact and JSON schemas with constraints, typed describe recovery and suggestions, and structured-result compatibility |
|
|
259
259
|
| `meta-tools.test.ts` | the remaining registry-backed meta-tools: the complete on-demand usage skill, connector-guide selection and summary bounds, stored-credential drift, catalog health, authorization, probe timeouts, and unavailable or unknown browse recovery |
|
|
260
260
|
| `mixpanel-provider.test.ts` | the Mixpanel proxy, its conditional-input guide, destructive metadata fill, and complete 64-tool schema-digest manifest |
|
|
@@ -264,21 +264,21 @@ in.
|
|
|
264
264
|
| `optional-modules.test.ts` | absent modules, UI-free OAuth, fast lists and independent detail deadlines, explicit auth-management grants, invalid-resolver refusal, and passive OAuth consent-state protection |
|
|
265
265
|
| `provider-conventions.test.ts` | the conventions a test can hold: hand-written providers refusing schemas they cannot enforce (H5), their compact discovery schemas staying complete (H7), Cloudflare stating its second pagination convention in the schema (H10), and Notion saying it has no escape hatch (H14) |
|
|
266
266
|
| `provider-registry.test.ts` | all seven maintained providers inside real deployments: boot, description, address, catalog, storage, credential, admission, and activity isolation; plus provider-specific discovery and guide contracts |
|
|
267
|
-
| `registry.test.ts` | construction and id validation, startup warnings, address resolution, version 2 catalog TTL/persistence/completeness, agent-only stale-while-revalidate with cross-request single-flight shared with blocking reads in both start orders, owned teardown, invalidation/fingerprint guards, blocking diagnostics,
|
|
268
|
-
| `remote-mcp.test.ts` | `remoteMcp()` against an in-process server through the `_transportFactory` seam: passthrough, downstream `isError`, Workers-safe output-schema validation, request-scoped client reuse and at-most-once scope close; plus the real transport's manual redirect policy, destination guard, credential containment, and
|
|
267
|
+
| `registry.test.ts` | construction and id validation, startup warnings, bounded absent-grant warning state, address resolution, version 2 catalog TTL/persistence/completeness, agent-only stale-while-revalidate with cross-request single-flight shared with blocking reads in both start orders, owned teardown, invalidation/fingerprint guards, blocking diagnostics, broken-connector isolation, and bounded opportunistic memory-storage expiry |
|
|
268
|
+
| `remote-mcp.test.ts` | `remoteMcp()` against an in-process server through the `_transportFactory` seam: passthrough, downstream `isError`, Workers-safe output-schema validation, request-scoped client reuse and at-most-once scope close; plus the real transport's manual redirect policy, destination guard, credential containment, session termination on scope close/rotation/abandonment, bounded JSON-RPC/HTTP error classification, and sanitized transport diagnostics |
|
|
269
269
|
| `remote-mcp-credential.test.ts` | `remoteMcp()` drawing a static key from the connection UI: the declared slot and its refusal of named fields and bad header names, header framing (bearer, bare, and the two `Basic` forms) observed on the wire, an empty slot failing as `auth_required` rather than reaching the downstream, a value carrying a control character refused before framing and absent from every surface — `call_tool`, `status`, the Test result, the payload-free activity event, and the thrown error — rotation replacing the cached client and a connect already in flight while a wiped value fails the next call, the Test action's catalog probe and scope close, the cleartext-destination warning, and the vault and `authorize_connector` handoff end to end |
|
|
270
270
|
| `remote-mcp-pagination.test.ts` | the `tools/list` cursor chain in both directions — exact cursor handoff, first-wins dedup, a failed later page rejecting rather than returning its prefix, the runaway backstops, the tool-metadata re-prime across pages, and paginated catalogs reaching the discovery path |
|
|
271
271
|
| `request-admission.test.ts` | `/mcp` bounded before auth, the stable 503 and `Retry-After`, health and operator responsiveness under saturation, payload-free counters, queued cancellation, shutdown rejection while active work drains, and the separate fallback code pool |
|
|
272
272
|
| `result-shapes.test.ts` | passive output-shape learning: value-free bounded inference, merging, 256-entry LRU eviction, 24-hour expiry, runtime isolation, read-only admission, declared-schema precedence, definition-change invalidation, discovery provenance, and failure isolation |
|
|
273
273
|
| `revenuecat-provider.test.ts` | the RevenueCat proxy's per-project key scoping and account-wide OAuth guides, its purpose-bearing summary, the refund-preference read and argued borderline verdicts in its digest-free manifest, and the deliberately unclassified `render-paywall-screenshot` |
|
|
274
|
-
| `server.test.ts` | end-to-end `/mcp` (401 → compact initialize instructions → seven compact definitions with bounded connector inventory, account titles, on-demand investigation guidance, and no Apps metadata or resource capability → complete usage skill → `call_tool`), conditional guide pointers, open routes, Clerk `.well-known` metadata without network, code mode, removed connector HTTP hooks rejected at construction, and deferred catalog reads through both discovery surfaces |
|
|
275
|
-
| `server-route-contracts.test.ts` | the route contracts `server.ts` must keep byte-identical: built-in routes and unknown-path 404s inside the security wrapper, open data-free shells with framing denied, per-route auth and same-origin requirements with exact 401/403/405 bodies, and OAuth `verifyState`-before-`finishAuth` ordering |
|
|
274
|
+
| `server.test.ts` | end-to-end `/mcp` (401 → compact initialize instructions → seven compact definitions with bounded connector inventory, account titles, on-demand investigation guidance, and no Apps metadata or resource capability → complete usage skill → `call_tool`), fixed seven-tool registration order across requests, conditional guide pointers, open routes, Clerk `.well-known` metadata without network, code mode, removed connector HTTP hooks rejected at construction, and deferred catalog reads through both discovery surfaces |
|
|
275
|
+
| `server-route-contracts.test.ts` | the route contracts `server.ts` must keep byte-identical: Origin refusal before admission/auth/redirects, exact-origin and wildcard CORS, SEP-2243 preflight headers, all pool suffixes behind auth, application overload/shutdown codes, id-free health preserving doctor drift counts, built-in routes and unknown-path 404s inside the security wrapper, open data-free shells with framing denied, per-route auth and same-origin requirements with exact 401/403/405 bodies, and OAuth `verifyState`-before-`finishAuth` ordering |
|
|
276
276
|
| `startup-warnings.test.ts` | every construction-time `logger.warn` and, as importantly, the conditions that must *not* trigger one: open mode with a credential or OAuth connector, `publicUrl` unset beside OAuth, dropped branding and `uiAuth` URLs, a missing `verifyState`, a credential test-hook mismatch, and an unusable `calls.maxResultBytes` |
|
|
277
277
|
| `stripe-provider.test.ts` | the Stripe proxy's mixed-mode OAuth and fixed-mode header contracts, current eleven-tool classifications, admission, exact account selectors, and no-guess rule |
|
|
278
278
|
| `operator-view.test.ts` | the app's pure state rules from `view.ts`: filtering, page routing, capability states, activity summaries, drift display, and identity reset |
|
|
279
279
|
| `ui-credentials.test.ts` | credential-management routes: save, test, delete, validation, authentication, same-origin checks, and multi-field credential shapes |
|
|
280
280
|
| `ui.test.ts` | the server shell and remaining `/ui/*` routes: gated `/ui/data` with broken-connector isolation and registry-owned catalog-observation containment, plus the URL safety gates |
|
|
281
|
-
| `validate.test.ts` | `validateToolInput()` — a returned (not thrown) `invalid_args` naming the path, `additionalProperties: false` enforcement, per-schema validator caching, and an unusable schema passed through with one warning |
|
|
281
|
+
| `validate.test.ts` | `validateToolInput()` — a returned (not thrown) `invalid_args` naming the path, `additionalProperties: false` enforcement, bounded UTF-8 validation detail, per-schema validator caching, and an unusable schema passed through with one warning |
|
|
282
282
|
| `vercel-provider.test.ts` | `vercel()` API and MCP construction, MCP inventory classification, team scoping, project and deployment projections, finite build and runtime logs, value-safe environment variables, domains, lifecycle writes, REST hatches, typed failures, and credential test |
|
|
283
283
|
|
|
284
284
|
### Node-bound (`NODE_ONLY_SUITES`)
|
|
@@ -292,15 +292,15 @@ justification for *not* re-running it in workerd, so "it was easier" is not one.
|
|
|
292
292
|
| `doc-links.test.ts` | the documentation checker itself — local file and fragment resolution, repository URLs resolved back to the checkout, duplicate heading slugs, fenced-code exclusion, and useful failures | spawns the Node checker against filesystem fixtures |
|
|
293
293
|
| `doctor-cli.test.ts` | `connecta doctor`'s executor line and credentials end to end — the sandbox the deployment reports is the one named, an unidentifiable executor gets an executor-neutral line, a hostile name is bounded, and a complete Cloudflare Access service-token pair is accepted while a partial pair is refused | spawns the CLI against a Node HTTP deployment over real sockets |
|
|
294
294
|
| `drift-check.test.ts` | the credential-free maintainer drift checker: recorded touched endpoints, heading, table, and inline MCP inventories, setup-only providers, live-schema ownership, a quiet revision bump, clear failures for unavailable inputs, `$ref` traversal, and one well-formed row per endpoint | spawns the checker against filesystem fixtures |
|
|
295
|
-
| `file-storage.test.ts` | `fileStorage()`
|
|
295
|
+
| `file-storage.test.ts` | `fileStorage()` round trips, exclusive writer locks, namespace-aware heartbeat expiry and stale-guard recovery, close/exit cleanup, unique temp files, logical TTL plus physical pruning, and corrupt-file quarantine | exercises the Node filesystem storage adapter |
|
|
296
296
|
| `guest-api-contract-quickjs.test.ts` | the shared guest-contract cases on the real QuickJS executor, including identical caught failure codes and inline describe recovery, its exact absent globals, and blocked runtime imports | runs the contract cases on the Node QuickJS executor |
|
|
297
297
|
| `node.test.ts` | the `listen()` adapter propagating an HTTP client disconnect through the Web `Request` and the MCP handler into a program's connector call, releasing both admission permits | exercises the Node HTTP adapter over real TCP sockets |
|
|
298
298
|
| `packed-links.test.ts` | the packed-link gate itself — shipped targets and repository URLs accepted, relative links into unshipped paths and directories rejected with the citation to write instead, reference definitions seen, fenced examples ignored, the changelog exempt | spawns the Node packed-link gate against filesystem fixtures |
|
|
299
299
|
| `package-surface.test.ts` | the published boundary — built output shipped, the `exports` map carrying exactly the documented subpaths plus `./package.json`, only generic factories, platform storage kept in examples, Clerk and QuickJS behind optional subpaths, dependency-free Cloudflare Access behind its Worker subpath, every provider independently importable, and the Cloudflare API provider free of bare specifiers | walks the package tree with Node filesystem APIs |
|
|
300
300
|
| `purity.test.ts` | the import-graph guardrail ([architecture](./architecture.md#import-graph-purity)) — the core stays Workers-clean | walks the source import graph with Node filesystem APIs |
|
|
301
301
|
| `quickjs-child-entry.test.ts` | a missing QuickJS child entry failing before `fork()`, with the expected path and the bundler-externalization constraint | mocks Node child-process and filesystem APIs |
|
|
302
|
-
| `quickjs-child-stderr.test.ts` | the QuickJS child-process boundary: an explicitly empty environment despite parent secrets and `NODE_OPTIONS`,
|
|
303
|
-
| `quickjs-executor.test.ts` | executable usage example with dependent, missing, and approval-required evidence; the child-process sandbox — code normalization, provider bridges and canonical connector calls, bounded IPC, separate guest-CPU and wall budgets, saturation, cancellation and shutdown, crash and OOM recovery, host-call hangs, stalled-promise detection | runs the Node QuickJS child-process executor |
|
|
302
|
+
| `quickjs-child-stderr.test.ts` | the QuickJS child-process boundary: an explicitly empty environment despite parent secrets and `NODE_OPTIONS`, abnormal exits retaining only an 8 KiB stderr tail in the parent-side diagnostic, host-result serialization failure returning a bounded error reply, streamed logs on real child crashes, bounded parent retention, and log recovery on shutdown, deadline termination, and IPC failures | mocks Node child-process streams and runs the Node QuickJS child-process executor |
|
|
303
|
+
| `quickjs-executor.test.ts` | executable usage example with dependent, missing, and approval-required evidence; the child-process sandbox — code normalization, provider bridges and canonical connector calls, private authenticated-failure transport and bounded error details, bounded IPC, separate guest-CPU and wall budgets, saturation, cancellation and shutdown, crash and OOM recovery, host-call hangs, stalled-promise detection, logs preserved on cancellation after a program starts | runs the Node QuickJS child-process executor |
|
|
304
304
|
| `quickjs-log-limits.test.ts` | bounded `console.*` capture — per-entry cut, cumulative character and transport budgets, escape-heavy floods preserving the guest result | runs the Node QuickJS child-process executor |
|
|
305
305
|
| `suite-partition.test.ts` | this partition, including itself: every `*.test.ts` in exactly one list, stale entries and empty reasons refused | walks the test directory to guard the partition |
|
|
306
306
|
| `template-file-activity.test.ts` | the Node template's own activity store — persistence across restart, torn-line repair, newest-first paging, and compaction past the slack window | runs it against real files |
|
|
@@ -231,6 +231,13 @@ convenient reading. A call that can only fail is refused locally as
|
|
|
231
231
|
`invalid_args` before the round trip. Provider error prose is never parsed to
|
|
232
232
|
invent a classification.
|
|
233
233
|
|
|
234
|
+
An unreachable transport may report `unavailable` with optional sanitized
|
|
235
|
+
`details.host` and `details.code`. The host is an HTTP(S) origin only, at most
|
|
236
|
+
253 UTF-8 bytes; the code is an allowlisted runtime network code or `timeout`,
|
|
237
|
+
at most 32 bytes. Omit what the runtime cannot establish. The typed error
|
|
238
|
+
constructor strips URL credentials, paths, queries, and fragments, and drops
|
|
239
|
+
invalid or oversized fields. Transport diagnostics never enter activity records.
|
|
240
|
+
|
|
234
241
|
**A downstream 404 is `not_found` — when the provider means it.** The code
|
|
235
242
|
exists because the next move is none of the others': you do not wait, you do
|
|
236
243
|
not send the agent to `authorize_connector`, you do not repair the argument
|