@zackbart/connecta 0.37.1 → 0.39.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/CHANGELOG.md +94 -0
- package/README.md +5 -4
- package/bin/connecta.mjs +1 -39
- package/bin/version.mjs +1 -1
- package/dist/access-tokens.js +2 -4
- package/dist/agent-identity.js +1 -1
- package/dist/agent-routes.js +231 -0
- package/dist/auth/clerk-sdk/generated.js +2 -1
- package/dist/auth/downstream-oauth/consent.js +519 -0
- package/dist/auth/downstream-oauth/grant-store.js +439 -0
- package/dist/auth/downstream-oauth/refresh-coordinator.js +562 -0
- package/dist/auth/downstream-oauth/refresh-records.js +447 -0
- package/dist/auth/downstream-oauth/sdk-hooks.js +242 -0
- package/dist/auth/downstream-oauth.js +66 -2516
- package/dist/auth/downstream-revocation.js +1 -1
- package/dist/auth/native.js +27 -0
- package/dist/auth/oauth-grant.js +24 -0
- package/dist/auth/oauth-token-response.js +1 -1
- package/dist/auth/static-oauth.js +153 -131
- package/dist/auth/vercel-agent.js +2 -1
- package/dist/auth-elicitation.js +9 -3
- package/dist/authorization-handoff.js +11 -5
- package/dist/branding.d.ts +1 -15
- package/dist/branding.js +2 -2
- package/dist/bytes.js +42 -0
- package/dist/call-admission-context.js +2 -5
- package/dist/call-recovery.js +0 -3
- package/dist/catalog-cache.js +130 -43
- package/dist/catalog-fingerprint.js +2 -18
- package/dist/catalog-policy.js +18 -0
- package/dist/catalog-service.js +285 -48
- package/dist/catalog.js +18 -2
- package/dist/config-defaults.d.ts +1 -0
- package/dist/config-defaults.js +1 -0
- package/dist/config-schema.d.ts +1 -1
- package/dist/config-schema.js +6 -2
- package/dist/config.d.ts +57 -25
- package/dist/config.js +30 -77
- package/dist/connector-caller.d.ts +1 -1
- package/dist/connector-health.js +59 -0
- package/dist/connector-status.js +117 -0
- package/dist/connectors/api-connector.d.ts +7 -2
- package/dist/connectors/api-connector.js +11 -7
- package/dist/connectors/guarded-fetch.d.ts +0 -6
- package/dist/connectors/guarded-fetch.js +1 -11
- package/dist/connectors/negotiation-cache.js +22 -4
- package/dist/connectors/remote-mcp/call.js +164 -0
- package/dist/connectors/remote-mcp/catalog.js +278 -0
- package/dist/connectors/remote-mcp/config.js +263 -0
- package/dist/connectors/remote-mcp/connection.js +671 -0
- package/dist/connectors/remote-mcp/errors.js +488 -0
- package/dist/connectors/remote-mcp/oauth.js +469 -0
- package/dist/connectors/remote-mcp/resources.js +473 -0
- package/dist/connectors/remote-mcp/runtime.js +1 -0
- package/dist/connectors/remote-mcp/transport.js +544 -0
- package/dist/connectors/remote-mcp.js +61 -2898
- package/dist/connectors/resource-uri.js +0 -4
- package/dist/credential-contract.d.ts +23 -0
- package/dist/credential-reads.js +17 -0
- package/dist/credential-verification.js +53 -0
- package/dist/credentials.d.ts +7 -0
- package/dist/credentials.js +60 -25
- package/dist/d1.d.ts +0 -89
- package/dist/d1.js +0 -34
- package/dist/describe-config.d.ts +12 -2
- package/dist/describe-config.js +2 -0
- package/dist/discovery-health.js +199 -0
- package/dist/downstream-input.js +4 -2
- package/dist/errors.d.ts +55 -17
- package/dist/errors.js +95 -41
- package/dist/execute.js +172 -125
- package/dist/executor-result.js +5 -2
- package/dist/executors/quickjs-runtime.js +2 -25
- package/dist/executors/quickjs.js +1 -1
- package/dist/guest-functions.js +83 -0
- package/dist/guest-runtime.js +63 -20
- package/dist/guest-types.d.ts +24 -10
- package/dist/http/argument-problems.js +35 -0
- package/dist/http/client.js +1 -1
- package/dist/http/errors.js +7 -6
- package/dist/http/fields.js +192 -13
- package/dist/http/hatch-response.js +73 -0
- package/dist/http/hatch.js +88 -23
- package/dist/http/json-schema.js +1 -1
- package/dist/http/node-transport.js +0 -1
- package/dist/http/oauth.js +4 -3
- package/dist/http/paging.js +6 -9
- package/dist/http/provider.js +33 -5
- package/dist/http/safety.js +9 -1
- package/dist/http/services.js +1 -8
- package/dist/http/tools.js +45 -30
- package/dist/http/vendor.js +7 -7
- package/dist/identity.js +2 -6
- package/dist/index.d.ts +1 -1
- package/dist/index.js +9 -2
- package/dist/invocation-auth.js +2 -1
- package/dist/invocation.js +21 -6
- package/dist/membership.d.ts +13 -5
- package/dist/membership.js +34 -2
- package/dist/meta-tools.js +50 -35
- package/dist/native-recovery.js +4 -16
- package/dist/native-tools.js +6 -2
- package/dist/oauth-handoff.js +2 -2
- package/dist/oauth-sealing.js +10 -0
- package/dist/operation-vocabulary.js +136 -14
- package/dist/operator-record.d.ts +2 -2
- package/dist/operator-record.js +2 -49
- package/dist/operator-ui/contract.d.ts +12 -2
- package/dist/operator-ui/generated.js +3 -3
- package/dist/operator-ui/model.d.ts +22 -1
- package/dist/program-writes.js +3 -8
- package/dist/provider-call.js +0 -7
- package/dist/provider.d.ts +1 -1
- package/dist/providers/_shared/google/kit.js +1 -6
- package/dist/providers/_shared/google/service-account.js +1 -3
- package/dist/providers/_shared/google/workspace.d.ts +1 -1
- package/dist/providers/_shared/guide.js +3 -0
- package/dist/providers/_shared/json.js +12 -0
- package/dist/providers/_shared/rest/compact-index.js +1 -1
- package/dist/providers/_shared/rest/kit.js +1 -3
- package/dist/providers/_shared/rest/operation-index.js +49 -53
- package/dist/providers/_shared/rest/value-safety.js +106 -28
- package/dist/providers/breeze/api.js +9 -8
- package/dist/providers/breeze/index.js +4 -2
- package/dist/providers/breeze/skill.generated.js +1 -1
- package/dist/providers/breeze/tools.js +7 -7
- package/dist/providers/ccb/api.js +3 -7
- package/dist/providers/ccb/index.js +7 -1
- package/dist/providers/ccb/skill.generated.js +1 -1
- package/dist/providers/ccb/tools.js +1 -6
- package/dist/providers/cloudflare/api.js +10 -3
- package/dist/providers/cloudflare/index.js +2 -1
- package/dist/providers/cloudflare/safety.js +3 -3
- package/dist/providers/cloudflare/skill.generated.js +1 -1
- package/dist/providers/docs/lib/schemas.js +24 -22
- package/dist/providers/docs/safety.js +2 -0
- package/dist/providers/docs/skill.generated.js +1 -1
- package/dist/providers/docs/tools.js +4 -14
- package/dist/providers/drive/api.js +1 -0
- package/dist/providers/drive/lib/contracts.js +8 -5
- package/dist/providers/drive/skill.generated.js +1 -1
- package/dist/providers/drive/tools.js +23 -41
- package/dist/providers/forms/api.js +1 -0
- package/dist/providers/forms/skill.generated.js +1 -1
- package/dist/providers/forms/tools.js +11 -16
- package/dist/providers/github/api.js +1 -0
- package/dist/providers/github/lib/discovery.js +1 -1
- package/dist/providers/github/skill.generated.js +1 -1
- package/dist/providers/github/tools.js +61 -17
- package/dist/providers/gmail/lib/schemas.js +60 -50
- package/dist/providers/gmail/skill.generated.js +1 -1
- package/dist/providers/gmail/tools.js +12 -26
- package/dist/providers/granola/index.js +2 -0
- package/dist/providers/granola/skill.generated.js +1 -1
- package/dist/providers/overflow/api.js +8 -4
- package/dist/providers/overflow/index.js +2 -1
- package/dist/providers/overflow/skill.generated.js +1 -1
- package/dist/providers/overflow/tools.js +3 -7
- package/dist/providers/planning-center/api.js +5 -2
- package/dist/providers/planning-center/lib/constants.d.ts +1 -1
- package/dist/providers/planning-center/lib/json-api.js +1 -9
- package/dist/providers/planning-center/skill.generated.js +1 -1
- package/dist/providers/planning-center/tools.js +2 -1
- package/dist/providers/quickbooks/safety.js +80 -39
- package/dist/providers/quickbooks/skill.generated.js +1 -1
- package/dist/providers/sheets/api.js +1 -0
- package/dist/providers/sheets/skill.generated.js +1 -1
- package/dist/providers/sheets/tools.js +14 -23
- package/dist/providers/slides/skill.generated.js +1 -1
- package/dist/providers/slides/tools.js +19 -26
- package/dist/providers/stripe/index.js +2 -1
- package/dist/providers/stripe/safety.js +2 -2
- package/dist/providers/stripe/skill.generated.js +1 -1
- package/dist/providers/tithely/api.js +5 -8
- package/dist/providers/tithely/index.js +3 -2
- package/dist/providers/tithely/skill.generated.js +1 -1
- package/dist/providers/tithely/tools.js +7 -5
- package/dist/providers/vercel/api.js +2 -7
- package/dist/providers/vercel/lib/pin.js +1 -1
- package/dist/providers/vercel/safety.js +1 -1
- package/dist/providers/vercel/skill.generated.js +1 -1
- package/dist/providers/vercel/tools.js +6 -4
- package/dist/redaction-marker.js +2 -2
- package/dist/registry.d.ts +11 -5
- package/dist/registry.js +46 -20
- package/dist/request-principal.js +1 -1
- package/dist/request-state.js +2 -2
- package/dist/routes/access-tokens.js +1 -1
- package/dist/routes/activity-disclosure.js +1 -1
- package/dist/routes/connect.js +2 -2
- package/dist/routes/credentials.js +130 -61
- package/dist/routes/dispatch.js +5 -1
- package/dist/routes/mcp.js +34 -17
- package/dist/routes/oauth-management.js +1 -1
- package/dist/routes/operator.js +6 -5
- package/dist/routes/shared.d.ts +13 -8
- package/dist/routes/shared.js +15 -9
- package/dist/routes/ui-config.js +3 -3
- package/dist/routes/ui-data.js +126 -20
- package/dist/routes/ui.js +17 -1
- package/dist/runtime/call-admission.js +1 -1
- package/dist/runtime/call.js +8 -3
- package/dist/runtime/native-storage.js +9 -0
- package/dist/runtime/request-reads.js +182 -0
- package/dist/runtime/request-scope.js +16 -0
- package/dist/runtime/settlement.js +1 -1
- package/dist/runtime/shared-read.js +2 -15
- package/dist/runtime/storage.js +12 -0
- package/dist/sent-secrets.d.ts +1 -2
- package/dist/sent-secrets.js +3 -8
- package/dist/server.js +4 -4
- package/dist/skills.js +44 -54
- package/dist/sqlite.d.ts +0 -19
- package/dist/sqlite.js +2 -83
- package/dist/storage/keys.js +69 -128
- package/dist/storage/memory.js +8 -2
- package/dist/storage/sql-native.js +27 -101
- package/dist/storage/sql.d.ts +1 -24
- package/dist/storage/sql.js +13 -14
- package/dist/storage-digest.js +32 -0
- package/dist/timeout.js +8 -0
- package/dist/tool-error.js +4 -0
- package/dist/types.d.ts +33 -0
- package/dist/url-safety.js +0 -4
- package/dist/usage-guide.js +18 -37
- package/dist/validate.d.ts +3 -0
- package/dist/validate.js +220 -158
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker.js +1 -1
- package/dist/write-outcome.js +0 -3
- package/documentation/README.md +19 -55
- package/documentation/architecture.md +436 -515
- package/documentation/auth.md +229 -88
- package/documentation/code-mode.md +201 -94
- package/documentation/deploying.md +25 -43
- package/documentation/integrating.md +18 -19
- package/documentation/meta-tools.md +225 -51
- package/documentation/migration-0.33.md +10 -6
- package/documentation/migration-0.34.md +5 -5
- package/documentation/migration-retired-layouts.md +74 -0
- package/documentation/operator-tests.md +20 -120
- package/documentation/operator-ui.md +108 -16
- package/documentation/pool-membership-upgrade.md +44 -0
- package/documentation/provider-auth-migration.md +4 -10
- package/documentation/provider-migration-0.29.md +4 -15
- package/documentation/providers.md +105 -0
- package/examples/worker/AGENTS.md +14 -16
- package/examples/worker/README.md +24 -118
- package/examples/worker/src/connecta.config.ts +1 -0
- package/guides/connecting-agents.md +9 -31
- package/guides/how-it-works.md +6 -4
- package/guides/safety-and-trust.md +4 -1
- package/package.json +19 -19
- package/templates/node/AGENTS.md +13 -12
- package/templates/node/README.md +9 -26
- package/templates/node/package.json +1 -1
- package/templates/node/src/connecta.config.ts +1 -0
- package/dist/auth/oauth-v2-migration.js +0 -135
- package/dist/provider-call-facts.d.ts +0 -115
- package/dist/provider-call.d.ts +0 -112
- package/dist/storage/d1-copy.js +0 -255
- package/dist/storage/sql-schema.d.ts +0 -6
- package/examples/worker/kv-to-d1.wrangler.jsonc +0 -26
- package/examples/worker/scripts/copy-kv-to-d1.mjs +0 -162
|
@@ -31,9 +31,8 @@ implementation boundary. Use these sections for the affected subsystem.
|
|
|
31
31
|
| Find code and deployment evidence | [Source map](#where-else-to-look), [sharp edges](#sharp-edges), [repository policy](https://github.com/zackbart/connecta/blob/main/AGENTS.md) |
|
|
32
32
|
|
|
33
33
|
Source paths and test names in each section identify its implementation and
|
|
34
|
-
regression evidence.
|
|
35
|
-
|
|
36
|
-
benchmarks or proof that planned replacements have shipped.
|
|
34
|
+
regression evidence. Decision records preserve historical measurements; current
|
|
35
|
+
contracts and executable evidence describe the implementation.
|
|
37
36
|
|
|
38
37
|
## The two lifetimes
|
|
39
38
|
|
|
@@ -48,7 +47,8 @@ as the isolate — on Workers a lazy module-scope singleton, which is why both
|
|
|
48
47
|
deployment shapes construct it outside the request handler.
|
|
49
48
|
|
|
50
49
|
An OAuth connector, `remoteMcp()` or `api()` with a static grant, owns a
|
|
51
|
-
runtime-local refresh completion gate in
|
|
50
|
+
runtime-local refresh completion gate in
|
|
51
|
+
`src/auth/downstream-oauth/refresh-coordinator.ts`.
|
|
52
52
|
Joined scopes share the outcome while keeping their own request I/O and
|
|
53
53
|
cancellation. Grant use and the caller's wait belong to the call. Renewal
|
|
54
54
|
preparation is call-owned only until the first durable coordinator write
|
|
@@ -100,6 +100,18 @@ never retried and requires re-consent. Valid rotations retry their grant commit
|
|
|
100
100
|
up to 32 times without another HTTP request; exhausted commits and all dispatched
|
|
101
101
|
provider failures require re-consent. [Auth](./auth.md#refresh-failures) describes the lease and failure
|
|
102
102
|
contracts.
|
|
103
|
+
The grant body stores `expiresAt` beside the tokens it describes; reads never
|
|
104
|
+
add it (INV-10). A call renews a token within `OAUTH_REFRESH_SKEW_MS` (60 s)
|
|
105
|
+
of it before sending: the invocation's authorization check (`registerInvocationAuth`
|
|
106
|
+
in `src/connectors/remote-mcp.ts` and `src/connectors/api-connector.ts`) runs
|
|
107
|
+
the 401 flow before entering the connector, so a failed renewal is a
|
|
108
|
+
pre-dispatch authorization failure, never an unknown write (INV-9).
|
|
109
|
+
`KvOAuthProvider.renewalDue` decides from the grant read that check already
|
|
110
|
+
made, in memory. The token request carries the access token it replaces
|
|
111
|
+
(`earlyRenewalFetch`), and the coordinator adopts any stored token that differs
|
|
112
|
+
instead of redeeming again. Status, listing, discovery and the connectionless
|
|
113
|
+
catalog read never renew. [Auth](./auth.md#refreshing-before-expiry) has the
|
|
114
|
+
operator contract.
|
|
103
115
|
|
|
104
116
|
**Per request, and no longer.** The MCP server, its transport, downstream MCP
|
|
105
117
|
clients, abort signals, and the connector scope a probe opens all belong to the
|
|
@@ -108,7 +120,10 @@ across requests on Workers is a cross-request capability leak, and a promise awa
|
|
|
108
120
|
work the runtime may already have torn down. Deferred work has one sanctioned
|
|
109
121
|
channel, `ctx.waitUntil`, threaded through `fetch(request, env, ctx)` — activity
|
|
110
122
|
writes and OAuth renewals from claim submission use it. Catalog listing and SQL cache
|
|
111
|
-
publication finish in the requesting scope.
|
|
123
|
+
publication finish in the requesting scope. The operator surface's Promise
|
|
124
|
+
edge (`ui.handle`) owns the request's signal and is awaited to its end even
|
|
125
|
+
when the caller leaves, so a storage write it has started, such as a stored
|
|
126
|
+
status observation, settles before the request does. A fresh
|
|
112
127
|
`McpServer` per request is what makes the deployment stateless: no sessions, no
|
|
113
128
|
server push, no stream resumability, scope resolved rather than remembered.
|
|
114
129
|
|
|
@@ -226,7 +241,7 @@ An admitted non-preflight `/mcp` request then takes five steps in
|
|
|
226
241
|
([#178](https://github.com/zackbart/connecta/issues/178)) but their URLs were
|
|
227
242
|
handed out, and retiring a scoping boundary into fail-open is worse than any
|
|
228
243
|
404 — then register the six meta-tools on a fresh `McpServer`
|
|
229
|
-
(`test/server.test.ts
|
|
244
|
+
(`test/server.test.ts`).
|
|
230
245
|
|
|
231
246
|
### Modern request metadata and transport refusals
|
|
232
247
|
|
|
@@ -443,139 +458,24 @@ Only an omitted or `undefined` `callAdmission` selects a connector's default.
|
|
|
443
458
|
The shared schema rejects malformed defined policies at factory construction,
|
|
444
459
|
and the registry validates custom connector policies before registration.
|
|
445
460
|
Falsy values such as `false`, `null`, `0`, and `""` are construction errors
|
|
446
|
-
(INV-11), never a way to disable admission.
|
|
447
|
-
exercise these refusals and unchanged defaults in
|
|
448
|
-
`test/call-admission-options.test.ts`.
|
|
461
|
+
(INV-11), never a way to disable admission.
|
|
449
462
|
|
|
450
463
|
### Production Worker disconnects
|
|
451
464
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
client. A raw Worker stream established the runtime behavior; a custom auth
|
|
456
|
-
provider returning a synthetic streaming 401 exercised Connecta's real admission
|
|
457
|
-
wrapper. Each case had its own Connecta instance with one request permit and no
|
|
458
|
-
queue. Health checks and subsequent MCP requests confirmed the same isolate ID.
|
|
459
|
-
The [recorded observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-2026-09-25.json)
|
|
460
|
-
contain all twelve cases, including served flags and client-end timing.
|
|
461
|
-
|
|
462
|
-
| Response and flag | Raw stream abort / cancel | Connecta source abort / cancel | Admission after response; next request |
|
|
463
|
-
| ----------------------------------------------------- | ------------------------- | ------------------------------ | -------------------------------------- |
|
|
464
|
-
| Heartbeats, no flag | neither | neither | active 1; 503 |
|
|
465
|
-
| Idle with a pending timer, no flag | neither | neither | active 1; 503 |
|
|
466
|
-
| Heartbeats, request signal enabled | abort only | both | active 0; 200 |
|
|
467
|
-
| Idle with a pending timer, request signal enabled | abort only | both | active 0; 200 |
|
|
468
|
-
| One chunk, then no timer or I/O, either configuration | neither | neither | active 1; 503 |
|
|
469
|
-
|
|
470
|
-
The last row is a different failure. The client observed the response end
|
|
471
|
-
_before_ its planned disconnect, although the source never closed itself.
|
|
472
|
-
The runtime ended the response without running JavaScript cleanup. This does
|
|
473
|
-
not show a missing abort on a still-live request. The other cases remained open
|
|
474
|
-
until the client aborted. Admission was sampled 1.5 seconds later, followed by
|
|
475
|
-
an actual MCP request; this measures retained capacity, not an infinite leak.
|
|
476
|
-
|
|
477
|
-
Enable `enable_request_signal` when deploying on Workers: it let Connecta's
|
|
478
|
-
abort listener cancel the source and release admission for live responses in
|
|
479
|
-
this experiment. It does not solve runtime-ended streams. The admitted-request
|
|
480
|
-
bound above recovers capacity on the first later request at or after its
|
|
465
|
+
Enable `enable_request_signal` when deploying on Workers so Connecta can cancel
|
|
466
|
+
the source and release admission for live responses. It does not solve
|
|
467
|
+
runtime-ended streams. The admitted-request bound above recovers capacity on the first later request at or after its
|
|
481
468
|
deadline, even if the old request's timer never ran. This is capacity recovery,
|
|
482
469
|
not proof that workerd cancelled an old stream. A still-live request reaches
|
|
483
470
|
the same hard deadline and is aborted in its own context.
|
|
484
471
|
Cloudflare documents the flag in its
|
|
485
472
|
[Request API](https://developers.cloudflare.com/workers/runtime-apis/request/).
|
|
486
473
|
|
|
487
|
-
### Authentication release by collection on workerd
|
|
488
|
-
|
|
489
|
-
Measured on 2026-10-20 with workerd 1.20260722.1 (Miniflare 4.20260722.0),
|
|
490
|
-
compatibility date `2025-01-01`:
|
|
491
|
-
|
|
492
|
-
- `FinalizationRegistry` and `WeakRef` are undefined without
|
|
493
|
-
`enable_weak_ref`; the flag's default-on date is 2025-05-05.
|
|
494
|
-
- With `enable_weak_ref` and `enable_request_signal`, an object reachable only
|
|
495
|
-
through a fetch continuation is finalized once its invocation ends and
|
|
496
|
-
workerd cancels the fetch; while the invocation runs it is not. Pending
|
|
497
|
-
timers and listeners on the request's signal do not keep it.
|
|
498
|
-
- Strong values in a WeakMap keyed by a `Request` (even one the handler
|
|
499
|
-
created), properties set on a `Request`, and WeakMap values keyed by its
|
|
500
|
-
`Headers`, `ctx` or `env` stay reachable after the invocation ends. The
|
|
501
|
-
lifetime registry in `src/auth-admission.ts` therefore stores `WeakRef`s.
|
|
502
|
-
- No `gc()` is exposed. Allocating large arrays (32 × 1 MiB per request)
|
|
503
|
-
triggers collections, but the most recent unreachable object often survives
|
|
504
|
-
them; V8 in this build scans the native stack conservatively. The inspector's
|
|
505
|
-
precise `HeapProfiler.collectGarbage` freed it.
|
|
506
|
-
- In the vitest-pool-workers harness, an isolate that had already collected
|
|
507
|
-
one scenario's cancelled work sometimes kept a later scenario's cancelled
|
|
508
|
-
work reachable through 400 rounds of pressure; standalone Miniflare never
|
|
509
|
-
did. `test/auth-admission-binding.test.ts` gives each collection scenario a
|
|
510
|
-
fresh isolate and requires two of four sentinels collected before asserting
|
|
511
|
-
that live charges stayed counted.
|
|
512
|
-
|
|
513
|
-
A second production run on the same date tested the request bound at 2,000 ms.
|
|
514
|
-
All twelve raw-stream and MCP cases passed with both flag configurations. Live
|
|
515
|
-
MCP requests still rejected a competing request with 503 before the deadline;
|
|
516
|
-
the first MCP request after the deadline returned all eight tools with 200 in
|
|
517
|
-
the original isolate. Admission then reported zero active requests. Without the
|
|
518
|
-
flag, the source still recorded no abort or cancellation, so recovery did not
|
|
519
|
-
rely on runtime cleanup. The [recovery observations](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-recovered-2026-09-25.json)
|
|
520
|
-
also retain two inconclusive queue-handoff cases: concurrent client connections
|
|
521
|
-
reached different isolates and could not establish an abandoned queued waiter.
|
|
522
|
-
A [second trial using HTTP/2](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect-h2-2026-09-25.json)
|
|
523
|
-
repeated the twelve passing core cases and verified queued cancellation with
|
|
524
|
-
request signals enabled. Even on one HTTP/2 connection, the no-signal queue case
|
|
525
|
-
crossed isolates and remained inconclusive. The Node and workerd regression
|
|
526
|
-
suites cover expired waiters, orphaned handoffs, and identity-safe late release
|
|
527
|
-
deterministically.
|
|
528
|
-
|
|
529
|
-
The [historical probe source](https://github.com/zackbart/connecta/blob/1ab92695f2636f904274d900ce209553358ff1f3/scripts/probes/worker-disconnect.mjs)
|
|
530
|
-
and observations are retained in Git history, outside the current checkout and
|
|
531
|
-
npm package. The probe deployed a disposable Worker containing only synthetic
|
|
532
|
-
data, verified the served configuration, saved observations, and deleted the
|
|
533
|
-
Worker in `finally`. It used no `waitUntil`, which would change the lifetime
|
|
534
|
-
being measured. The production runtime build number was not exposed by the
|
|
535
|
-
deployment; these findings are tied to the date and flags above.
|
|
536
|
-
|
|
537
474
|
### Production Access bypass for agent endpoints
|
|
538
475
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
`ctx.access`. It can. The probe ran on 2026-10-15 against a disposable Worker on
|
|
543
|
-
its `workers.dev` hostname, with compatibility date `2026-09-01`, `cf`
|
|
544
|
-
1.0.0-beta.14, Wrangler 4.114.0, and curl 8.7.1 with `--path-as-is`, so raw
|
|
545
|
-
paths reached the edge unnormalized. The Worker-level application allowed only
|
|
546
|
-
a probe service token. Its unauthenticated refusal was therefore Access's 403
|
|
547
|
-
page rather than a login redirect. The [observations](https://github.com/zackbart/connecta/blob/main/scripts/probes/access-agent-bypass-2026-10-15.json)
|
|
548
|
-
record three identical rounds after each configuration change had produced
|
|
549
|
-
three consecutive matching responses.
|
|
550
|
-
|
|
551
|
-
- **Precedence.** A self-hosted application whose destination is
|
|
552
|
-
`{ "type": "public", "uri": "<host>/agent/*" }`, with one `bypass` policy
|
|
553
|
-
including `everyone`, overrides the Worker-level application on matching
|
|
554
|
-
paths only. Unauthenticated requests reach the Worker there. `/mcp`, `/`,
|
|
555
|
-
and every unmatched path still get the Access refusal. A service token is
|
|
556
|
-
still admitted on them with `ctx.access` from the Worker-level application.
|
|
557
|
-
This confirms Cloudflare's documented hierarchy, and that Worker-level
|
|
558
|
-
protection still covers the `workers.dev` hostname.
|
|
559
|
-
- **`ctx.access` on a bypassed request is present, not absent.** Its `aud` is
|
|
560
|
-
nonempty and is the _path_ application's AUD, not the Worker-level one.
|
|
561
|
-
`getIdentity()` resolved to `undefined` for every credential tested: none, a
|
|
562
|
-
valid service token, a `cta_`-style bearer token, and a forged
|
|
563
|
-
`Cf-Access-Jwt-Assertion`. A service token on a protected path also gave
|
|
564
|
-
`undefined` identity, carrying the Worker-level AUD. A human
|
|
565
|
-
`CF_Authorization` cookie on a bypassed path was not tested, so what
|
|
566
|
-
`getIdentity()` returns there is unknown. That is one more reason the agent
|
|
567
|
-
route ignores Access context rather than relying on its contents.
|
|
568
|
-
- **API shape.** A Worker destination is exclusive to one application. A
|
|
569
|
-
second application with `{ "type": "worker", "worker_id" }` plus `uri` or
|
|
570
|
-
`path` was refused with 409 `destination belongs to another application`. The
|
|
571
|
-
bypass must be hostname-and-path, stored with domain `<host>/agent/*`, and it
|
|
572
|
-
covers only that exact hostname.
|
|
573
|
-
- **Propagation.** Creating or deleting each application took 15–19 seconds to
|
|
574
|
-
settle across runs. While a deployment or application change was propagating,
|
|
575
|
-
the edge returned transient 500 (`error code: 1104`) and 404 (`error code:
|
|
576
|
-
1042` or `1043`) responses. The recorded run has one such 404, on the
|
|
577
|
-
Worker-level-only service-token control for `/agent/x`. Deleting the path
|
|
578
|
-
application restored the 403 on `/agent/x`.
|
|
476
|
+
Access uses a hostname-and-path bypass application for `/agent/*`; the
|
|
477
|
+
Worker-level application protects other paths. A bypassed request can still
|
|
478
|
+
carry `ctx.access`, so agent routes ignore that context.
|
|
579
479
|
|
|
580
480
|
| Raw request path | Access outcome | Worker pathname (+ search) |
|
|
581
481
|
| -------------------------- | -------------- | -------------------------- |
|
|
@@ -607,139 +507,32 @@ leading `//`, and treats `%2f` as data, not a separator. The effective Worker
|
|
|
607
507
|
pathname, `new URL(request.url).pathname`, is the raw request path with case,
|
|
608
508
|
escapes, and slashes untouched, except that dot segments arrive resolved:
|
|
609
509
|
`/mcp/../agent/x` arrives as `/agent/x`. Every path that
|
|
610
|
-
resolves outside `/agent/*`, including every `/mcp` form,
|
|
510
|
+
resolves outside `/agent/*`, including every `/mcp` form, remains protected. The
|
|
611
511
|
inverse is the risk: Access bypasses paths
|
|
612
512
|
that connecta must not treat as an agent pool. Connecta's router compares raw
|
|
613
513
|
pathnames without decoding, case folding, or slash collapsing, so an `/agent/`
|
|
614
|
-
route
|
|
615
|
-
`/agent/<pool>`, with `<pool>` byte-equal to a declared pool name. It
|
|
616
|
-
|
|
617
|
-
variants, and a leading `//`, all with the same 404. It
|
|
514
|
+
route keeps that property. It accepts exactly
|
|
515
|
+
`/agent/<pool>`, with `<pool>` byte-equal to a declared pool name. It refuses
|
|
516
|
+
an empty segment, a trailing slash, any extra segment, `%`, uppercase
|
|
517
|
+
variants, and a leading `//`, all with the same 404. It ignores
|
|
618
518
|
`ctx.access` entirely, since a bypassed request still carries an AUD.
|
|
619
519
|
[Agent endpoints](./auth.md#agent-endpoints) implement exactly this in
|
|
620
520
|
`src/routes/agent-path.ts`, and `test/agent-endpoints.test.ts` replays every
|
|
621
521
|
row of the table above. The hyphenated row stands for any two-part pool name
|
|
622
|
-
such as `<agent>-<user>`.
|
|
623
|
-
with placeholders after the run; the edge behavior for that path shape was
|
|
624
|
-
unaffected.
|
|
625
|
-
|
|
626
|
-
To configure the bypass, keep the Worker-level application described in
|
|
627
|
-
[Cloudflare Access on Workers](./auth.md#cloudflare-access-on-workers). Then,
|
|
628
|
-
for each hostname agents use (the `workers.dev` hostname, each Custom Domain,
|
|
629
|
-
or route host), add a self-hosted Access application. In the dashboard, set
|
|
630
|
-
domain to that hostname and path to `agent/*`, with one policy whose action is
|
|
631
|
-
**Bypass** and whose include rule is **Everyone**. Through the API, that is:
|
|
632
|
-
|
|
633
|
-
```json
|
|
634
|
-
{
|
|
635
|
-
"type": "self_hosted",
|
|
636
|
-
"name": "connecta agents",
|
|
637
|
-
"destinations": [{ "type": "public", "uri": "connecta.example.com/agent/*" }],
|
|
638
|
-
"policies": [{ "name": "agents bypass", "decision": "bypass", "include": [{ "everyone": {} }] }]
|
|
639
|
-
}
|
|
640
|
-
```
|
|
522
|
+
such as `<agent>-<user>`.
|
|
641
523
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
rely on a fixed wait after a change. Changes settled in 15–19 seconds, yet one
|
|
645
|
-
earlier round still saw a 403 on a bypassed path more than 30 seconds after
|
|
646
|
-
the change. Test only after the positive and negative controls agree together
|
|
647
|
-
for several consecutive rounds. The positive controls are bypassed paths
|
|
648
|
-
reaching the Worker with the path AUD and a service token reaching a protected
|
|
649
|
-
path with the Worker-level AUD; the negative controls are `/mcp` and `/`
|
|
650
|
-
returning the Access refusal. The current script requires five such rounds
|
|
651
|
-
after creating the bypass and three after other changes. The recorded run
|
|
652
|
-
predates this: after the bypass it converged only the positive `/agent/x`
|
|
653
|
-
control, then waited a fixed 90 seconds, and its negative controls were checked
|
|
654
|
-
in the recorded rounds rather than before them.
|
|
655
|
-
|
|
656
|
-
To repeat from a repository checkout, run
|
|
657
|
-
`node scripts/probes/access-agent-bypass.mjs --profile <name> [output.json]`.
|
|
658
|
-
`--profile` is required and has no default; every `cf` call passes it
|
|
659
|
-
explicitly. Name the profile for the account that should host the probe; it
|
|
660
|
-
needs
|
|
661
|
-
Access application, policy, and service-token writes, plus Workers script
|
|
662
|
-
writes. The script deploys and deletes the Worker with `cf`. `cf deploy` builds
|
|
663
|
-
through Wrangler 4.136.0 or newer, so the probe installs Wrangler 4.145.0 into
|
|
664
|
-
its temporary project. Wrangler needs no login. The script records only the
|
|
665
|
-
count of existing Access applications, then names everything it creates
|
|
666
|
-
`connecta-probe-<run>`. It force-deletes and verifies each resource in
|
|
667
|
-
`finally`, then checks that the non-probe application ids are unchanged.
|
|
668
|
-
Observations record statuses, outcome classes, pathnames, and which
|
|
669
|
-
application's AUD matched, compared by digest inside the Worker. They contain no
|
|
670
|
-
hostnames, account ids, tokens, cookies, or JWTs. `cf` delete commands prompt
|
|
671
|
-
for confirmation and exit 0 without deleting when stdin is not a terminal, so
|
|
672
|
-
the script passes `--force`. `cf deploy` printed `Deploy complete` and then
|
|
673
|
-
kept running, so the script stops it after that line. The recorded run deployed
|
|
674
|
-
with Wrangler 4.114.0 and used `cf` for Access. A standalone deploy and delete
|
|
675
|
-
verified the `cf` deploy and delete path. The full probe has not been rerun on
|
|
676
|
-
that path.
|
|
677
|
-
|
|
678
|
-
Every subprocess (`cf`, `curl`, `npm`) runs through one runner. It enforces a
|
|
679
|
-
deadline by killing the process group, keeps stdout and stderr in memory, and
|
|
680
|
-
returns only facts: success, exit code, timeout, HTTP status, numeric API
|
|
681
|
-
code, and an error slug from a fixed set (anything else is `unknown`). Commands
|
|
682
|
-
that return data are parsed inside the runner against schemas that copy only
|
|
683
|
-
whitelisted fields: ids, names, counts, booleans, and the Worker's `ctx.access`
|
|
684
|
-
summary. Credentials stay in a holder that prints as `[secret]`. An unexpected
|
|
685
|
-
shape, including `null` where a list belongs, is a failure, never an empty
|
|
686
|
-
result. Errors are fixed diagnostics plus those facts. Teardown deletes every
|
|
687
|
-
tracked id on its own. Then Access applications, service tokens, and Workers
|
|
688
|
-
each get their own bounded discovery-and-delete pass by name, for resources
|
|
689
|
-
whose create response was lost, so a failure in one family never skips
|
|
690
|
-
another. Each discovered resource is deleted independently. Access policies are
|
|
691
|
-
inline on their applications and go with them. A resource counts as gone only
|
|
692
|
-
when a successful lookup omits it, and the final inventory verifies each family
|
|
693
|
-
separately. Run
|
|
694
|
-
`node scripts/probes/access-agent-bypass.mjs --self-test` to check the runner
|
|
695
|
-
offline. It feeds canary secrets, suffixed slugs, malformed JSON, and a stalled
|
|
696
|
-
process tree through it. It also runs teardown against a fake `cf` whose
|
|
697
|
-
application listing always fails, after a lost token-create response, and
|
|
698
|
-
checks that the token is still found and deleted. It also checks that a Worker
|
|
699
|
-
that refuses deletion does not block another Worker's deletion. It runs no real
|
|
700
|
-
`cf`, `curl`, or network command. This
|
|
701
|
-
hardening and the joint convergence are verified offline only and have not run
|
|
702
|
-
against Cloudflare.
|
|
524
|
+
For the operator procedure and Access application JSON, see
|
|
525
|
+
[Agent endpoints behind Cloudflare Access](./deploying.md#agent-endpoints-behind-cloudflare-access).
|
|
703
526
|
|
|
704
527
|
### Alchemy Worker deployment
|
|
705
528
|
|
|
706
|
-
Two throwaway deployments on 2026-10-20 verified the Worker example's
|
|
707
|
-
Alchemy path with alchemy 2.0.0-beta.83, effect 4.0.5, and Alchemy local
|
|
708
|
-
state, then deleted every resource and confirmed by re-listing. Alchemy
|
|
709
|
-
uploads the full Worker with `PUT /workers/scripts/<name>`, the endpoint an
|
|
710
|
-
earlier full deploy had been suspected of losing `ctx.access` through.
|
|
711
|
-
|
|
712
|
-
The first deployment ran a near copy of the stack with a temporary diagnostic
|
|
713
|
-
route. With an Access service token in a non-identity policy, the Worker saw
|
|
714
|
-
`ctx.access` carrying the Worker-level application's AUD and no identity, as
|
|
715
|
-
expected for a service token. D1, the Worker Loader, R2, and secret bindings
|
|
716
|
-
worked; `/agent/*` reached connecta through the bypass; `/ui/api/config`
|
|
717
|
-
returned 403 `Cloudflare Access human identity required`; and
|
|
718
|
-
`connecta doctor` passed with a `cta_` token issued inside the Worker. A code
|
|
719
|
-
update through `alchemy deploy` kept the script tag, `ctx.access`, and D1 rows.
|
|
720
|
-
Three consecutive probe rounds agreed.
|
|
721
|
-
|
|
722
|
-
The second deployment ran the shipped `alchemy.run.ts` from the packed package
|
|
723
|
-
in a separate project on Node 26, changing only resource names and the Access policy
|
|
724
|
-
(a service token). The edge checks in the example's "Verify after deploying"
|
|
725
|
-
held for three or more rounds after each deploy. `alchemy destroy` deleted the
|
|
726
|
-
Worker and both Access applications and orphaned the retained D1 database; the
|
|
727
|
-
next deploy adopted it by name with its rows. After deleting local state, a
|
|
728
|
-
deploy reused the Worker with the same script tag, and `applicationId` took
|
|
729
|
-
both Access applications over with unchanged AUDs and no duplicates. Removing
|
|
730
|
-
`applicationId` afterwards planned an in-place update, and a further deploy
|
|
731
|
-
planned no changes. The account API token needed only Workers Scripts Write,
|
|
732
|
-
D1 Write, Access: Apps and Policies Write, and Access: Organizations, Identity
|
|
733
|
-
Providers, and Groups Read.
|
|
734
|
-
|
|
735
529
|
Four findings shape the operator rules. Alchemy local state under
|
|
736
530
|
`.alchemy/state/` records secret binding values in plaintext. Alchemy sends no
|
|
737
531
|
`keep_bindings`, so each deploy deletes secrets set outside the stack.
|
|
738
532
|
`alchemy dev` does not provide `ctx.access` to a plain fetch Worker, so local
|
|
739
533
|
development stays on `wrangler dev` and its `access.dev` identity. D1 needs
|
|
740
534
|
`RemovalPolicy.retain()` so `alchemy destroy` cannot delete a deployment's
|
|
741
|
-
state.
|
|
742
|
-
taking over a Worker first deployed with Wrangler.
|
|
535
|
+
state.
|
|
743
536
|
|
|
744
537
|
npm strips `.gitignore` from the package, so the copied Worker example's
|
|
745
538
|
ignore rules ship as `examples/worker/gitignore`, renamed by the README's copy
|
|
@@ -760,15 +553,8 @@ activity table beside it. `src/storage/sql.ts` owns the Promise edges.
|
|
|
760
553
|
Each store creates its tables on first use. `memoryStorage()` is the default
|
|
761
554
|
and the test double. Workers KV and the 0.28 JSON file store are gone: KV is
|
|
762
555
|
eventually consistent and cannot compare-and-set, and the file store rewrote
|
|
763
|
-
its whole file on every write.
|
|
764
|
-
|
|
765
|
-
entries verbatim, because physical keys did not change, and both keep any key
|
|
766
|
-
the target already holds. `copyKvToD1` reports counts per key family
|
|
767
|
-
(`familyOfKey` in `src/storage/keys.ts`), never a key. It resumes through a
|
|
768
|
-
source-bound, atomically claimed token, because Workers KV's own list cursor
|
|
769
|
-
can spell a key. Copy under maintenance after KV stabilizes, verify hashes
|
|
770
|
-
and expiries, and mark cutover before reopening traffic; the permanent D1
|
|
771
|
-
marker refuses stale copies afterward.
|
|
556
|
+
its whole file on every write. Their one-shot copies were removed; deployments with old state must finish
|
|
557
|
+
conversion on 0.37.1 before upgrading. See [Retired layout migrations](./migration-retired-layouts.md).
|
|
772
558
|
|
|
773
559
|
`KVStorage` is `get`/`set`/`delete`/`list(prefix)`/`compareAndSet`, all
|
|
774
560
|
required, and `createConnecta` refuses storage missing one (INV-11).
|
|
@@ -808,12 +594,10 @@ Keys and list prefixes must not contain U+0000 (NUL). D1, SQLite, and memory
|
|
|
808
594
|
storage reject them with `TypeError` before accessing storage: Node 22's
|
|
809
595
|
`node:sqlite` truncates TEXT results at NUL. Builders reject NUL in unencoded
|
|
810
596
|
components.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
the file (INV-6). Database failures report only a fixed import step and an
|
|
816
|
-
entry's position, including errors raised by target-database triggers.
|
|
597
|
+
The `connecta_kv` table keeps its existing TEXT keys, including compatibility
|
|
598
|
+
with the 0.28 schema. Storage writes no log lines. JSON state-file import is
|
|
599
|
+
retired; deployments needing it must use the 0.37.1 bridge release before
|
|
600
|
+
upgrading. See [Retired layout migrations](./migration-retired-layouts.md).
|
|
817
601
|
|
|
818
602
|
Remote MCP catalogs use the SDK `ResponseCacheStore` over this same SQL KV.
|
|
819
603
|
The `response-cache:v1:` family binds connector id, a hash of its configured
|
|
@@ -835,6 +619,142 @@ Only complete, intake-redacted tools/list, resources/list and
|
|
|
835
619
|
resources/templates/list successes are stored. Resource payloads are not cached. SDK default
|
|
836
620
|
in-memory caching is replaced, so raw catalogs never enter it.
|
|
837
621
|
|
|
622
|
+
Stored catalog and negotiation keys use deployment-keyed partitions. A remote
|
|
623
|
+
connector's whole cache configuration and negotiation input enter a stored key
|
|
624
|
+
only through HMAC-SHA-256 (`src/storage-digest.ts`) under a key that never
|
|
625
|
+
enters storage: every configured URL in full (the endpoint, a client metadata
|
|
626
|
+
document, a static client's issuer and the redirect URI), static auth header
|
|
627
|
+
values, resource headers, and a static OAuth client's secret. The
|
|
628
|
+
operator-managed or request-resolved credential (joined with the forwarded
|
|
629
|
+
principal), a custom connector's credential values, and the OAuth token
|
|
630
|
+
response that fences a listing are keyed the same way. The key is the vault's
|
|
631
|
+
`storageDigestKey()`, which `encryptedCredentialVault()` derives with HKDF from
|
|
632
|
+
its credential key. Without a vault, or with a custom vault that lacks the
|
|
633
|
+
method, a random key held only in the process is used: the partitions stay
|
|
634
|
+
exact, but another process or isolate addresses different entries and fills
|
|
635
|
+
its own cache, so cross-isolate reuse of remote catalogs, verdicts and health
|
|
636
|
+
records needs a vault. A different URL, header, credential, token response or
|
|
637
|
+
epoch addresses a different entry at once, including a value changed by
|
|
638
|
+
redeploy, and an earlier entry is never served under the new value; rotating
|
|
639
|
+
back to an earlier value within its TTL finds its entries again. A new
|
|
640
|
+
deployment key addresses new entries. Entries written by earlier releases are
|
|
641
|
+
not read and expire on their own TTLs (catalogs within `catalogMaxTtlSeconds`,
|
|
642
|
+
refresh baselines within 48 hours, verdicts within six hours), so each remote
|
|
643
|
+
connector's partitions refill once after the upgrade with one handshake and
|
|
644
|
+
listing.
|
|
645
|
+
|
|
646
|
+
A remote connector's `listTools` without a live connection in its request
|
|
647
|
+
reads this cache before connecting. It derives the same partition a connect
|
|
648
|
+
binds (the admitted principal, the caller's credential digest, or the owner's
|
|
649
|
+
OAuth epoch and token digest) from storage reads only, then makes the SDK's
|
|
650
|
+
two-probe read with the pre-connect empty server identity: the private slot,
|
|
651
|
+
then a shared connector's public slot, served only when written public. OAuth
|
|
652
|
+
grants are read by a non-mutating snapshot: no plaintext grant is sealed in
|
|
653
|
+
place, no epoch is bound, nothing is refreshed. Every identity check during
|
|
654
|
+
cache acceptance reads the grant again and refuses the entry unless the epoch
|
|
655
|
+
its partition was derived from and the token digest both still hold. A missing
|
|
656
|
+
or expired generation is a miss at every check and is never claimed or
|
|
657
|
+
recreated. An unexpired complete entry is returned with no transport,
|
|
658
|
+
handshake, session cleanup or storage write. A miss, an expired entry, an
|
|
659
|
+
explicit `ttlMs: 0`, an invalidated generation, a credential, token or epoch
|
|
660
|
+
change, an operator-disconnected or token-less grant, a cache read failure,
|
|
661
|
+
and a downstream whose stored negotiation verdict is modern all connect as
|
|
662
|
+
before. Modern downstreams connect without a handshake and read the cache
|
|
663
|
+
through the SDK. The connect reuses the credential and negotiation verdict that
|
|
664
|
+
read resolved. `list_changed` arrives only over a live connection, so an
|
|
665
|
+
unconnected catalog changes at its TTL or on invalidation.
|
|
666
|
+
|
|
667
|
+
A catalog served from this cache, before or after connecting, does not
|
|
668
|
+
validate downstream credential health. An expired or vendor-revoked credential
|
|
669
|
+
can still expose cached tools, and discovery reports no auth-related
|
|
670
|
+
`catalogErrors` entry for it until the cache misses. Tool calls and resource
|
|
671
|
+
reads reach the downstream and report auth failures with their handoffs. A
|
|
672
|
+
status probe connects: a legacy downstream answers a handshake, while a modern
|
|
673
|
+
downstream with a stored verdict connects without a request. A credential test
|
|
674
|
+
always sends a live `tools/list` that bypasses the cache in both directions.
|
|
675
|
+
|
|
676
|
+
In `"auto"` mode a remote connector stores its negotiation verdict, data only,
|
|
677
|
+
in the `negotiation` key family, partitioned by connector configuration,
|
|
678
|
+
credential and OAuth epoch, with secret-bearing inputs keyed as above. A later connect reuses it instead of probing with
|
|
679
|
+
`server/discover`. A modern verdict lasts five minutes. A legacy verdict lasts
|
|
680
|
+
six hours, because a server that adds the modern era still answers the legacy
|
|
681
|
+
handshake. Two signals end a verdict early:
|
|
682
|
+
|
|
683
|
+
- A legacy-verdict handshake refused with a 4xx other than 401, 403 or 429 (a
|
|
684
|
+
modern-only server answers 400 with its supported versions) is negotiated
|
|
685
|
+
again at once in the same connect. The new verdict replaces the old.
|
|
686
|
+
- On a modern connection (adopted from the verdict or just negotiated), an
|
|
687
|
+
HTTP 400 is read through a bounded clone of its body, as the transport's
|
|
688
|
+
backward-compatibility rule requires. A modern server also answers 400 with
|
|
689
|
+
`HeaderMismatch` (-32020) and `MissingRequiredClientCapability` (-32021),
|
|
690
|
+
which concern the request alone and keep the verdict and the connection.
|
|
691
|
+
`UnsupportedProtocolVersion` (-32022) keeps them only while its
|
|
692
|
+
`data.supported` list (at most 64 strings) still names the request's
|
|
693
|
+
`MCP-Protocol-Version`; a missing, malformed or other list makes the verdict
|
|
694
|
+
unusable. Any other body, empty or not a recognized modern error (a legacy
|
|
695
|
+
server's sessionless refusal, a gateway's page), is a legacy refusal. Either
|
|
696
|
+
way the verdict is deleted and that client retired. The refused request
|
|
697
|
+
fails and is never repeated. The next connect, in the same request or a
|
|
698
|
+
later one, probes again. The clone read ends with the call, the request or
|
|
699
|
+
the connection scope: an abort cancels it, cancels the unread original and
|
|
700
|
+
takes no evidence (INV-7).
|
|
701
|
+
|
|
702
|
+
An upgrade to a server that still answers the legacy handshake is therefore
|
|
703
|
+
adopted within six hours. Losing the legacy handshake costs one refused
|
|
704
|
+
request, and a downgrade costs the one request that met the refusal.
|
|
705
|
+
|
|
706
|
+
**Discovery health.** An unscoped search (`search_tools` or `connecta.search`
|
|
707
|
+
without `connector`) probes each connector for at most 5,000 ms, or
|
|
708
|
+
`discovery.probeTimeoutMs` when that is shorter, and its probes end within a
|
|
709
|
+
9,000 ms discovery budget. A tenth of the budget, at most 500 ms, is kept for
|
|
710
|
+
recording outcomes: a probe that starts late gets only what remains before
|
|
711
|
+
that slice, and one that cannot start reports `timeout`. Health reads, claims
|
|
712
|
+
and writes start only while budget remains and the caller is still waiting;
|
|
713
|
+
health is otherwise unknown. Once started, each is awaited to its end before
|
|
714
|
+
the search returns, never raced or detached, since a storage write cannot be
|
|
715
|
+
withdrawn once sent. The budget bounds when health work starts, not when it
|
|
716
|
+
finishes, so slow storage can carry a search past it by one operation's
|
|
717
|
+
latency. A scoped search,
|
|
718
|
+
`connecta.describe`, native `tools/list` and tool calls keep the full
|
|
719
|
+
`discovery.probeTimeoutMs`. A probe that times out on its own deadline, or a
|
|
720
|
+
retryable failure that itself took at least one second or named its own wait,
|
|
721
|
+
writes a record in the `connector-health:v1:` family: the connecta
|
|
722
|
+
classification code, observation time, retry time (60 seconds later, or the
|
|
723
|
+
downstream's shorter named wait, at least one second), and the connector's
|
|
724
|
+
catalog invalidation generation. Each probe's time is its own, measured as it
|
|
725
|
+
settles. Records hold no downstream text, tools, arguments or credentials.
|
|
726
|
+
Each is keyed by connector id and the deployment-keyed digest (as for the
|
|
727
|
+
catalog cache above) of the catalog owner partition (principal, subject,
|
|
728
|
+
caller identity and pool, origin and public URL), the admitted endpoint (plain
|
|
729
|
+
`/mcp`, `/mcp/<pool>` or `/agent/<pool>`), the connector's described
|
|
730
|
+
configuration, whose endpoint keeps its path, and the package version. One owner's
|
|
731
|
+
or endpoint's record never skips another's search. A change `describe()`
|
|
732
|
+
cannot see, such as a static header rotated in deployment code, takes effect
|
|
733
|
+
on redeploy; a record written before it keeps skipping for at most its retry
|
|
734
|
+
time, about 60 seconds. Until its retry time an unscoped search skips that connector
|
|
735
|
+
and reports it in `catalogErrors` with the recorded code, `retryable: true`,
|
|
736
|
+
`retryAfterMs`, and a fixed message that names the scoped search to try it
|
|
737
|
+
now; no tools from the failed walk are ever served. A scoped search still
|
|
738
|
+
probes and settles the record. A record whose generation no longer matches
|
|
739
|
+
lapses, so a vault credential change, OAuth connect or disconnect, or operator
|
|
740
|
+
invalidation clears every partition at once. A success, or an auth or
|
|
741
|
+
permission failure that shows the connector answers, clears the record this
|
|
742
|
+
request saw. Caller cancellation, a probe the budget cut short, a fast
|
|
743
|
+
failure, and a non-retryable failure record nothing. After its retry time a
|
|
744
|
+
record lingers for 60 seconds: the request that dispatches the next probe
|
|
745
|
+
claims it by compare-and-set, with a lease to an absolute time, the end of
|
|
746
|
+
that request's discovery budget plus two seconds, so a slow claim write cannot
|
|
747
|
+
shorten it; the others keep skipping until it settles or the lease lapses. Health reads are one `getMany` per unscoped search, made
|
|
748
|
+
once per request; a health storage failure is logged and never fails
|
|
749
|
+
discovery.
|
|
750
|
+
|
|
751
|
+
A healthy connector whose complete catalog always takes longer than 5 seconds
|
|
752
|
+
to list can therefore stay out of unscoped discovery: each unscoped probe
|
|
753
|
+
times out and records it. A scoped search, or any operation with the longer
|
|
754
|
+
`discovery.probeTimeoutMs` deadline that completes the listing (describe, a
|
|
755
|
+
tool call), warms a reusable complete catalog that later unscoped searches
|
|
756
|
+
read without the slow listing, and a success clears the record.
|
|
757
|
+
|
|
838
758
|
`discovery.catalogTtlSeconds` (300 by default) is the fallback for legacy
|
|
839
759
|
listings without `ttlMs`. Positive hints are bounded by
|
|
840
760
|
`catalogMinTtlSeconds` (0) and `catalogMaxTtlSeconds` (86400, also the SDK
|
|
@@ -885,7 +805,15 @@ activity write. The first complete catalog is a baseline.
|
|
|
885
805
|
Hash-only baselines publish bounded chunks before a manifest. They store the
|
|
886
806
|
catalog digest and hashes of each tool name and fact, so added, removed and changed counts require no persisted tool names or
|
|
887
807
|
schemas. They use the exact host auth/config cache partition, survive catalog
|
|
888
|
-
expiry and invalidation, and expire
|
|
808
|
+
expiry and invalidation, and expire 48 hours after they are written. A refresh
|
|
809
|
+
reads the manifest first. When its digest matches a baseline written within the
|
|
810
|
+
last hour, the refresh writes nothing; an older matching baseline is rewritten
|
|
811
|
+
with fresh chunks, so every refresh leaves a baseline for at least 47 hours.
|
|
812
|
+
A manifest's lifetime is never extended without new chunks, so its chunks
|
|
813
|
+
always outlive it. A changed digest writes the new baseline and reports drift
|
|
814
|
+
against the previous one. An unchanged zero-TTL listing therefore writes no
|
|
815
|
+
baseline; it still lists live every time and deletes its cache slots, since a
|
|
816
|
+
concurrent request may have published an older reusable catalog there. There is no connection-wide
|
|
889
817
|
fallback baseline across partitions. Private catalog events use the admitted
|
|
890
818
|
principal actor basis even for shared-auth connectors; activity disclosure
|
|
891
819
|
requires that same owner and the recorded pool's grants. Ownerless private
|
|
@@ -1283,9 +1211,8 @@ parse as text, so connecta reads downstream bodies as bytes
|
|
|
1283
1211
|
(`src/byte-read-response.ts`), `ctx.oauth.fetch()` answers included. The
|
|
1284
1212
|
operator page may show catalog metadata the operator loaded (descriptions,
|
|
1285
1213
|
schemas), but withholds a tool name outside the grammar as it does in records.
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
the next one. Recorded HTTP status facts survive the bare write-401 fallback,
|
|
1214
|
+
Fix the sink, not the source: a filter at each source missed the next one.
|
|
1215
|
+
Recorded HTTP status facts survive the bare write-401 fallback,
|
|
1289
1216
|
Google's uncertain-write wording, and oversized-response mapping; see
|
|
1290
1217
|
[Read failure records](./operating.md#read-failure-records) for their operator
|
|
1291
1218
|
meaning. These facts do not change agent error fields or write outcomes.
|
|
@@ -1361,7 +1288,11 @@ switched by the environment, and keep their entries under 30 lines.
|
|
|
1361
1288
|
A maintained provider is one `defineProvider()` call (`src/provider.ts`): a
|
|
1362
1289
|
name, title, `kind: "api"`, maintained skill, closed `options` shape and a
|
|
1363
1290
|
synchronous `create`. Common values are checked before construction and the
|
|
1364
|
-
factory stamps its provider name onto `describe().source.provider`.
|
|
1291
|
+
factory stamps its provider name onto `describe().source.provider`. The kit's
|
|
1292
|
+
`defineApiProvider` also takes optional `searchSynonyms` groups (single
|
|
1293
|
+
lowercase words, at most 32 groups of 2–8), checks them at definition and
|
|
1294
|
+
attaches them to each connector for
|
|
1295
|
+
[lexical discovery](./meta-tools.md#lexical-discovery). API
|
|
1365
1296
|
implementations and their contract evidence remain vendored. Hosted MCP servers
|
|
1366
1297
|
use generic `remoteMcp()` entries; provider reviews and hosted defaults are
|
|
1367
1298
|
removed. Legacy hosted selectors fail at construction with the generic migration
|
|
@@ -1456,9 +1387,7 @@ acknowledgement, then reconciles vendor state with exactly one physical send.
|
|
|
1456
1387
|
Focused provider suites retain MIME/draft restrictions, revision conflicts,
|
|
1457
1388
|
CCB routing, Planning Center product exclusions and deployment pins. Native
|
|
1458
1389
|
HTTP framing, cursor and request-lifetime fault probes remain in the HTTP suites.
|
|
1459
|
-
|
|
1460
|
-
and unused paging strategy tests are retired under #1072. Provider workflows
|
|
1461
|
-
belong in the existing shared outcomes; a new tool requires no failure-matrix row.
|
|
1390
|
+
Provider workflows belong in the existing shared outcomes.
|
|
1462
1391
|
No legacy REST tool adapter remains.
|
|
1463
1392
|
Large documents opt into generator options that drop restated operation ids
|
|
1464
1393
|
and plain path parameters and cap one operation's details (`opBudget`).
|
|
@@ -1732,8 +1661,7 @@ path, or fields a schema leaves undeclared; such paths still apply at
|
|
|
1732
1661
|
runtime).
|
|
1733
1662
|
|
|
1734
1663
|
`test/fixtures/value-safety.ts` is the shared harness each vendor's
|
|
1735
|
-
`provider.safety.node.test.ts` runs; `reviewProblems` is pure
|
|
1736
|
-
`test/value-safety-harness.node.test.ts` proves wrong paths fail it. It fails
|
|
1664
|
+
`provider.safety.node.test.ts` runs; `reviewProblems` is pure. It fails
|
|
1737
1665
|
on candidates from another pin, format, or option set; an unreviewed
|
|
1738
1666
|
candidate; a verdict for an operation the index lacks; a table path the stamp
|
|
1739
1667
|
lacks (unresolved, or edited since `providers:spec`); a flagged field no path,
|
|
@@ -1761,12 +1689,6 @@ and reports only added, removed and changed counts in the caller's partition.
|
|
|
1761
1689
|
|
|
1762
1690
|
#### Behavioral evidence (contributor)
|
|
1763
1691
|
|
|
1764
|
-
The characterization snapshot engine, provider JSON snapshots, source scheduler
|
|
1765
|
-
and its self-tests are retired under
|
|
1766
|
-
[#1072](https://github.com/zackbart/connecta/issues/1072). Provider changes no
|
|
1767
|
-
longer require golden enrollment, snapshot updates, byte equivalence or nightly
|
|
1768
|
-
capture determinism. Production invariants remain unchanged.
|
|
1769
|
-
|
|
1770
1692
|
Use the existing stateful fixture vendors and published-edge deployment adapters:
|
|
1771
1693
|
|
|
1772
1694
|
- `pnpm run eval:outcomes` drives representative joins, retained paging, endpoint
|
|
@@ -1785,7 +1707,10 @@ Use the existing stateful fixture vendors and published-edge deployment adapters
|
|
|
1785
1707
|
Node/SQLite/QuickJS and native Workers/D1. It covers transcript-too-large
|
|
1786
1708
|
recovery, empty intermediate pages, selected fields, withheld photo/credential
|
|
1787
1709
|
values and shared-vault reuse after application reconstruction. No-op,
|
|
1788
|
-
fabricated-read and erased-delivery controls must fail.
|
|
1710
|
+
fabricated-read and erased-delivery controls must fail. Its fake, tasks and
|
|
1711
|
+
deployment wiring live in `src/providers/granola/eval/`, unshipped;
|
|
1712
|
+
`providers:generate` registers a provider's `eval/` wiring with the harness
|
|
1713
|
+
(`eval/README.md`, provider-local eval wiring).
|
|
1789
1714
|
- `test/identity-write-journey.node.test.ts` uses the existing Node deployment
|
|
1790
1715
|
adapter, stateful MCP fake, SQLite vault and QuickJS to grade admitted human
|
|
1791
1716
|
and machine identities, pool isolation, fail-closed discovery, endpoint and
|
|
@@ -1794,14 +1719,6 @@ Use the existing stateful fixture vendors and published-edge deployment adapters
|
|
|
1794
1719
|
returned private values and credential echoes; positive record facts must
|
|
1795
1720
|
still arrive. An explicitly writable pool also exercises reopening a
|
|
1796
1721
|
read-only deployment without bypassing connector policy.
|
|
1797
|
-
- `test/cancellation-journey.node.test.ts` observes a queued client's departure
|
|
1798
|
-
without another vendor send, and an active client's departure after the
|
|
1799
|
-
vendor commits. The vendor must see its held response socket close before
|
|
1800
|
-
releasing it; a later MCP read recovers the committed intent exactly once.
|
|
1801
|
-
`test/cancellation-faults.test.ts` keeps signal-less token-resolution,
|
|
1802
|
-
warmed-credential digest, stalled body-cleanup and abort/grant races on Node
|
|
1803
|
-
and workerd. These replace the universal Promise-method/seam/owner matrix
|
|
1804
|
-
and its enrollment self-test, plus migration-only runtime wrapper checks.
|
|
1805
1722
|
- `test/browser/operator-flows.spec.ts` serves the actual operator shell and
|
|
1806
1723
|
routes over TCP with file-backed SQLite. Sign-in, Settings provenance, OAuth
|
|
1807
1724
|
connect/disconnect and credential save/test/replace/remove survive closing and
|
|
@@ -1835,17 +1752,10 @@ Use the existing stateful fixture vendors and published-edge deployment adapters
|
|
|
1835
1752
|
after HTML, malformed JSON and oversized acknowledgements with one physical
|
|
1836
1753
|
send. It distinguishes Stripe's reviewed pre-execution 401 refusal, and checks
|
|
1837
1754
|
read failure facts, secrecy and recovery through direct and QuickJS calls.
|
|
1838
|
-
`test/
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
`test/request-secret-redaction.test.ts` and `test/catalog-secret-redaction.test.ts`
|
|
1843
|
-
cover secrecy before projection, truncation, caching, paging and diagnostics.
|
|
1844
|
-
- `test/write-evidence.node.test.ts`, `test/remote-mcp-attempts.node.test.ts` and
|
|
1845
|
-
`test/oauth-credential-attempts.node.test.ts` inspect physical socket sends.
|
|
1846
|
-
Native transport parity, OAuth and write-provenance suites retain their
|
|
1847
|
-
workerd coverage. The shared throwaway RSA key in `test/fixtures/rsa.ts` is
|
|
1848
|
-
used by write-provenance and unknown-write-advice fixtures.
|
|
1755
|
+
Existing `test/sent-secrets.test.ts` covers secrecy before projection,
|
|
1756
|
+
truncation, caching, paging and diagnostics.
|
|
1757
|
+
- The shared throwaway RSA key in `test/fixtures/rsa.ts` is used by
|
|
1758
|
+
write-provenance and unknown-write-advice fixtures.
|
|
1849
1759
|
- `test/executor-requests.test.ts` runs overlapping `execute_code` programs
|
|
1850
1760
|
as separate workerd requests through the real Worker Loader executor, with
|
|
1851
1761
|
real TCP host calls that read their own request's signal. Queued programs
|
|
@@ -1860,23 +1770,21 @@ Use the existing stateful fixture vendors and published-edge deployment adapters
|
|
|
1860
1770
|
`test/request-context.node.test.ts` pins the same wake order and async context
|
|
1861
1771
|
on Node for executor and call admission and QuickJS host calls, and gives back
|
|
1862
1772
|
grants that shutdown overtook.
|
|
1863
|
-
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
the retired capture scheduler's source guards were only harness constraints.
|
|
1773
|
+
- `test/request-context.node.test.ts` and `test/auth-admission-binding.test.ts`
|
|
1774
|
+
exercise cancellation, held native work and garbage collection. These test
|
|
1775
|
+
actual request ownership.
|
|
1867
1776
|
|
|
1868
1777
|
The remaining probes defend distinct reachable failures: auth admission and
|
|
1869
1778
|
native scheduler ownership, grant/callback fencing and refresh coordination,
|
|
1870
1779
|
SDK replay/redirect/SSE attempts, nested write provenance and refusal evidence,
|
|
1871
1780
|
unawaited program drain and failed-program accounting, catalog drift and paging
|
|
1872
1781
|
revocation, and hostile serializers, structural echoes, truncation and persisted
|
|
1873
|
-
pages.
|
|
1874
|
-
post-401 result/error-shape tables are retired. Protocol claims link executable
|
|
1782
|
+
pages. Protocol claims link executable
|
|
1875
1783
|
evidence files in `spec/coverage.json`; the invariant contracts above are defended
|
|
1876
1784
|
by the actual behavior assertions in these journeys and focused probes.
|
|
1877
1785
|
The required CI gate includes build/declarations, Node and Workers probes,
|
|
1878
1786
|
outcomes, browser flows, installed consumers and Docker, audits and real Eve
|
|
1879
|
-
compatibility.
|
|
1787
|
+
compatibility.
|
|
1880
1788
|
|
|
1881
1789
|
Add a compact fault journey only for a meaningful outcome those suites cannot
|
|
1882
1790
|
observe. Reuse their fixtures and adapters; do not reintroduce a snapshot
|
|
@@ -1904,8 +1812,7 @@ behind the same redirect wrapper and a same-origin guard.
|
|
|
1904
1812
|
client replacement, transport confinement and secrecy on Node, and a real
|
|
1905
1813
|
service binding on workerd.
|
|
1906
1814
|
`test/google-service-account-token.test.ts` exercises signing/cache behavior on
|
|
1907
|
-
Node
|
|
1908
|
-
real-socket token/MCP requests, invalidation and one physical attempt per write.
|
|
1815
|
+
Node.
|
|
1909
1816
|
Operator setup lives in [Google hosted MCP](./auth.md#google-service-account-tokens-and-hosted-mcp).
|
|
1910
1817
|
|
|
1911
1818
|
`createConnecta` takes closed typed `ui`, `vault`, and `activity`
|
|
@@ -2070,10 +1977,10 @@ storage is the root's namespaced to its principal.
|
|
|
2070
1977
|
| 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. |
|
|
2071
1978
|
| 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`. |
|
|
2072
1979
|
| `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. Public executor methods stay Promise-shaped; registered built-in execution composes directly as an Effect under the signal and `execute.watchdogMs`. Queued acquisition retains its Promise request handoff. Each guest host call is a fiber of its own. |
|
|
2073
|
-
| Discovery (`catalog-service.ts`) | A request-scoped cache: one shared read per connector (`runtime/shared-read.ts`), settled by the read itself and carrying its own signal and the probe timeout whichever asker starts it. Each asker waits under its own deadline and signal, so one that times out or is cancelled fails alone, and the read is cancelled only once every asker has gone. Fan-out is `Effect.forEach` under the discovery concurrency.
|
|
1980
|
+
| Discovery (`catalog-service.ts`) | A request-scoped cache: one shared read per connector (`runtime/shared-read.ts`), settled by the read itself and carrying its own signal and the probe timeout whichever asker starts it. Each asker waits under its own deadline and signal, so one that times out or is cancelled fails alone, and the read is cancelled only once every asker has gone. Fan-out is `Effect.forEach` under the discovery concurrency; unscoped search adds the discovery budget and health record (`discovery-health.ts`). |
|
|
2074
1981
|
| Registry (`registry.ts`) | The result stash is a program over `Storage`. Same-request catalog loads share one read the way discovery's do. Remote MCP caching uses the SDK ResponseCacheStore adapter over SQL-backed KV, with no cross-request catalog flight. |
|
|
2075
|
-
| Remote MCP (`connectors/remote-mcp.ts`)
|
|
2076
|
-
| Downstream OAuth (`auth/downstream-oauth
|
|
1982
|
+
| Remote MCP (`connectors/remote-mcp.ts`, `connectors/remote-mcp/`) | 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. |
|
|
1983
|
+
| Downstream OAuth (`auth/downstream-oauth*`, `auth/static-oauth.ts`) | A refresh flight is a Deferred. Before the first durable coordinator write (claim CAS) is submitted, call/request closure stops preparation without a CAS, token request or state change. Submission transfers renewal to the storage coordinator under its own 35-second preparation deadline and 20-second token-request/body deadline, followed by the fenced rotation commit through the runtime deferred-work hook. A closed caller promptly fails without consuming the result, marking its ledger renewed or sending/resending a resource request. |
|
|
2077
1984
|
| 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. |
|
|
2078
1985
|
| 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`. |
|
|
2079
1986
|
|
|
@@ -2186,10 +2093,8 @@ request fixture and native-output suites supply runtime parity evidence.
|
|
|
2186
2093
|
|
|
2187
2094
|
### Native SQL storage (contributor)
|
|
2188
2095
|
|
|
2189
|
-
`src/storage/sql-native.ts` composes KV, activity schema upgrades, retention
|
|
2190
|
-
|
|
2191
|
-
Workers KV copy, source-bound resume claims, verification, cutover guard, and
|
|
2192
|
-
recovery. Public factories and copy functions retain their Promise contracts;
|
|
2096
|
+
`src/storage/sql-native.ts` composes KV, activity schema upgrades, and retention
|
|
2097
|
+
as Effects. Public factories retain their Promise contracts;
|
|
2193
2098
|
custom Promise SQL drivers retain the fallback lift.
|
|
2194
2099
|
|
|
2195
2100
|
`src/storage/sql-driver.ts` constructs a public Effect 4.0.5 `SqlClient`
|
|
@@ -2208,17 +2113,8 @@ is the existing exact `effect` pin. Connecta does not adopt `@effect/sql-d1`:
|
|
|
2208
2113
|
its 4.0.5 batch API drops metadata and its binding contract is wider than
|
|
2209
2114
|
Connecta's published structural interface.
|
|
2210
2115
|
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
read-only bundle artifacts: `/d1` grew from 62,516 to 88,305 bytes gzip
|
|
2214
|
-
(+25,789); the Worker example grew from 729,847 to 756,375 (+26,528).
|
|
2215
|
-
Final typed-driver failure normalization measures 88,319 and 756,377 bytes
|
|
2216
|
-
on that same official runtime. Executing real services and statement operations requires those runtime
|
|
2217
|
-
semantics; the earlier identity-compiler facade did not provide them. The
|
|
2218
|
-
explicit allowance changes only `/d1`'s cap from 62,542 to 90,000 bytes and
|
|
2219
|
-
the Worker example's cap from 743,000 to 760,000. Historical baselines and
|
|
2220
|
-
all other caps stay fixed. Final integration must remeasure the combined
|
|
2221
|
-
implementation and satisfy the same compatibility and privacy contracts.
|
|
2116
|
+
Bundle caps and baselines are authoritative in
|
|
2117
|
+
[`scripts/bundle-budget.json`](https://github.com/zackbart/connecta/blob/main/scripts/bundle-budget.json).
|
|
2222
2118
|
|
|
2223
2119
|
The platform connection capability contains no request state. D1 prepares in
|
|
2224
2120
|
the current request and retains no prepared handles or promises. SQLite keeps
|
|
@@ -2245,14 +2141,16 @@ value, matching that fallback. Interruption remains interruption. The lookup
|
|
|
2245
2141
|
module imports no SQL or platform implementation and does not change the
|
|
2246
2142
|
published storage interfaces.
|
|
2247
2143
|
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2144
|
+
`NativeStorage.getMany` reads several keys in one statement, answering by
|
|
2145
|
+
position exactly as one `get` per key would; the SQL drivers share it, a
|
|
2146
|
+
replaced `get` answers each key itself, and other stores fall back to one `get`
|
|
2147
|
+
at a time. Catalog reads fetch up to eight chunks per round trip with it.
|
|
2148
|
+
|
|
2149
|
+
`openSqlite` retains file modes, WAL, busy timeout,
|
|
2251
2150
|
and database ownership. `test/sql-effect-contract.ts` retains affected-row,
|
|
2252
2151
|
50-claim, typed-failure, cancellation, and tracing probes on SQLite and native
|
|
2253
|
-
D1.
|
|
2254
|
-
|
|
2255
|
-
activity upgrades, and copy recovery.
|
|
2152
|
+
D1. The existing SQL contracts prove rollback, database-clock TTL, encodings,
|
|
2153
|
+
and activity upgrades.
|
|
2256
2154
|
|
|
2257
2155
|
### Internal provider HTTP failures (contributor)
|
|
2258
2156
|
|
|
@@ -2293,7 +2191,7 @@ Stripe declares HTTP 409 as a conflict, alongside its fact-driven 429
|
|
|
2293
2191
|
classification to an oversized 424 reply, preserving the dependency-failure
|
|
2294
2192
|
meaning when the bounded body cannot be read. The original ceiling diagnostic
|
|
2295
2193
|
and answered status remain; the core still decides write outcomes. It does not
|
|
2296
|
-
apply to 401 or
|
|
2194
|
+
apply to 401 or change write-outcome settlement. A fixed read-phase fact distinguishes failed body
|
|
2297
2195
|
reads from malformed JSON only for vendors declaring that wording; raw
|
|
2298
2196
|
exceptions never enter diagnostics. Other providers retain their mapping and
|
|
2299
2197
|
wording.
|
|
@@ -2310,14 +2208,13 @@ The mapping records each request's own served provenance and raw 401 facts.
|
|
|
2310
2208
|
reservation's answer even for a read-classified tool issuing a physical write.
|
|
2311
2209
|
An accepted write's blocked retry retains its original HTTP status; a slot
|
|
2312
2210
|
occupied by an auxiliary read instead names the separate write step needed.
|
|
2313
|
-
Structured resend advice reaches output only when the core permits it.
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2211
|
+
Structured resend advice reaches output only when the core permits it. Unknown
|
|
2212
|
+
write outcomes are never retryable; a generic one keeps the core's diagnostic
|
|
2213
|
+
`retryAfterMs`, and one settled by a 401 drops it. The mapping
|
|
2214
|
+
adds no Promise entry or lifetime owner. `call-services.test.ts` and the native
|
|
2317
2215
|
transport suites exercise these contracts through invocation, physical sockets
|
|
2318
2216
|
and request closure. `http-kit.test.ts` retains focused numeric/text fact,
|
|
2319
|
-
request-equivalence and bulk-capacity fault probes.
|
|
2320
|
-
kit/core verdict comparison is retired under #1072. Maintained API providers use the kit through their
|
|
2217
|
+
request-equivalence and bulk-capacity fault probes. Maintained API providers use the kit through their
|
|
2321
2218
|
explicit provider subpaths; it stays off the root import graph and published
|
|
2322
2219
|
declarations expose Promises.
|
|
2323
2220
|
|
|
@@ -2328,8 +2225,7 @@ declarations expose Promises.
|
|
|
2328
2225
|
are Promises. A connector is called with `Effect.tryPromise`, never asked to
|
|
2329
2226
|
return an Effect.
|
|
2330
2227
|
- **Inbound authorization.** `authorize` in `src/routes/shared.ts` ships in
|
|
2331
|
-
the declarations and in the `./activity` bundle
|
|
2332
|
-
gzip there for nothing a caller could see.
|
|
2228
|
+
the declarations and in the `./activity` bundle.
|
|
2333
2229
|
- **Pure synchronous code.** Ranking, schema rendering, classification,
|
|
2334
2230
|
`validateToolInput`, the host-call budget, the emit collector, failure
|
|
2335
2231
|
framing, and result shaping. The microtask scheduler never yields to I/O, so
|
|
@@ -2409,6 +2305,93 @@ within five minutes of expiry in the same auth partition. Partial walks and
|
|
|
2409
2305
|
auth or permission failures refuse fallback; resources require unexpired
|
|
2410
2306
|
inventories, and stale fallback cannot authorize post-entry auth recovery.
|
|
2411
2307
|
|
|
2308
|
+
**Repeated reads share one request's memo.** `src/runtime/request-reads.ts`
|
|
2309
|
+
gives each request owner a memo of storage reads, emptied when the request
|
|
2310
|
+
closes (INV-7). It holds plain strings keyed by the physical key in the
|
|
2311
|
+
underlying store, so namespaced views of one partition share an entry and two
|
|
2312
|
+
principals' partitions never do. A read consults it only where the call site
|
|
2313
|
+
opts in and states why staleness within one request is safe; every other read
|
|
2314
|
+
goes to storage.
|
|
2315
|
+
|
|
2316
|
+
- Every write through the SQL drivers, `memoryStorage()` or a namespaced view
|
|
2317
|
+
over them drops that key from every open memo in the same isolate, before
|
|
2318
|
+
submission and again on settlement. A read that such a write overlapped is
|
|
2319
|
+
not recorded.
|
|
2320
|
+
- Only the latest read of a key that the request started records. An earlier
|
|
2321
|
+
read that settles later never replaces a newer fresh observation.
|
|
2322
|
+
- A fresh read records what it saw, so a memoized reader is never older than
|
|
2323
|
+
its request's latest fresh read of the key.
|
|
2324
|
+
- A custom store, or a built-in one with a replaced method, is never memoized.
|
|
2325
|
+
- A runtime without `WeakRef` and `FinalizationRegistry` memoizes nothing:
|
|
2326
|
+
workerd before compatibility date 2025-05-05 without `enable_weak_ref` reads
|
|
2327
|
+
storage every time.
|
|
2328
|
+
|
|
2329
|
+
The memo cannot see another isolate, another process sharing the database, or
|
|
2330
|
+
an entry expiring. After the first read, a memoized key can lag such a change
|
|
2331
|
+
for the rest of the request. So nothing that gates authority, sends a
|
|
2332
|
+
credential, or decides a write is memoized.
|
|
2333
|
+
|
|
2334
|
+
Memoized:
|
|
2335
|
+
|
|
2336
|
+
- the negotiation verdict, which only orders handshake versions;
|
|
2337
|
+
- catalog generation reads that only locate an entry, accepted or refused by a
|
|
2338
|
+
fresh fence;
|
|
2339
|
+
- the credential reads behind connector status, drift and the operator UI card,
|
|
2340
|
+
which only label a view. Only the built-in vault with its original `getAll`
|
|
2341
|
+
and `metadata` shares a read; a custom vault, a subclass, or an instance
|
|
2342
|
+
that overrides either method is called itself for every read, dispatch
|
|
2343
|
+
included.
|
|
2344
|
+
|
|
2345
|
+
Always fresh:
|
|
2346
|
+
|
|
2347
|
+
- every OAuth grant read, including the token and epoch check behind each SDK
|
|
2348
|
+
message (a disconnect need not revoke the vendor token), `peekGrant` and
|
|
2349
|
+
every cache-acceptance identity check, compare-and-set loops, refresh claims
|
|
2350
|
+
and leases, resets and disconnects;
|
|
2351
|
+
- the catalog generation fence before a cached entry is served or published;
|
|
2352
|
+
- the credential a call sends.
|
|
2353
|
+
|
|
2354
|
+
`test/request-reads.node.test.ts` holds the memo's contract, including a
|
|
2355
|
+
disconnect written by another isolate between paginated `tools/list` pages.
|
|
2356
|
+
|
|
2357
|
+
### Request budgets (contributor)
|
|
2358
|
+
|
|
2359
|
+
`test/request-reads.node.test.ts` also guards the common request shapes the way
|
|
2360
|
+
bundle caps guard size. Each case counts downstream HTTP requests, D1 round
|
|
2361
|
+
trips (`all`, `run` or `batch`) and SQL statements separately, since a batch is
|
|
2362
|
+
one round trip but several statements. The fixture is production-shaped: D1
|
|
2363
|
+
over `node:sqlite`, a sealed vault holding the API credential and the OAuth
|
|
2364
|
+
grant, Cloudflare Access with `accessTokens()`, a D1 activity sink settled
|
|
2365
|
+
through `waitUntil`, the operator UI and QuickJS standing in for Dynamic Worker
|
|
2366
|
+
startup. "Cold isolate" is a new deployment instance over the populated
|
|
2367
|
+
database. The ceilings, measured value plus about 10% D1 headroom and exact
|
|
2368
|
+
downstream counts, live in its `BUDGETS` table. Measured on main in October 2026:
|
|
2369
|
+
|
|
2370
|
+
| Request | Downstream | D1 round trips | SQL statements |
|
|
2371
|
+
| ---------------------------------------- | ---------: | -------------: | -------------: |
|
|
2372
|
+
| Legacy remote MCP call, warm | 5 | 6 | 7 |
|
|
2373
|
+
| Legacy remote MCP call, cold isolate | 5 | 9 | 12 |
|
|
2374
|
+
| Modern remote MCP call, warm | 1 | 7 | 8 |
|
|
2375
|
+
| Modern remote MCP call, cold isolate | 1 | 10 | 13 |
|
|
2376
|
+
| OAuth remote MCP call, warm | 5 | 19 | 20 |
|
|
2377
|
+
| OAuth remote MCP call, cold isolate | 5 | 22 | 25 |
|
|
2378
|
+
| OAuth call after a 401, with refresh | 9 | 39 | 48 |
|
|
2379
|
+
| OAuth call with proactive refresh | 6 | 35 | 43 |
|
|
2380
|
+
| `api()` credential call, warm | 1 | 3 | 4 |
|
|
2381
|
+
| `api()` credential call, cold isolate | 1 | 6 | 9 |
|
|
2382
|
+
| Unscoped search, warm (3 remote, 1 api) | 0 | 20 | 20 |
|
|
2383
|
+
| `execute_code`, 5 calls to one connector | 9 | 10 | 15 |
|
|
2384
|
+
| UI connector detail, `api()` credential | 0 | 2 | 2 |
|
|
2385
|
+
| UI connector detail, credential remote | 4 | 9 | 9 |
|
|
2386
|
+
| UI connector detail, OAuth remote | 4 | 18 | 18 |
|
|
2387
|
+
|
|
2388
|
+
Header-auth calls to the legacy (session) server handshake per request:
|
|
2389
|
+
`initialize`, `notifications/initialized`, the SSE `GET`, the call and the
|
|
2390
|
+
session `DELETE`. The modern server advertises a catalog TTL, so a warm call is
|
|
2391
|
+
the call alone. OAuth calls read the grant fresh before every SDK message.
|
|
2392
|
+
Run with `D1_BUDGET_REPORT=1` to print each measurement; a failing case prints
|
|
2393
|
+
its request and statement log.
|
|
2394
|
+
|
|
2412
2395
|
### Contributor provider kit
|
|
2413
2396
|
|
|
2414
2397
|
Provider declarations use `src/http/vendor.ts` for HttpApi operations and
|
|
@@ -2427,17 +2410,29 @@ exact vendor count as `page.total` from its page callback. The kit validates a
|
|
|
2427
2410
|
nonnegative safe integer and includes it on continuing and terminal pages;
|
|
2428
2411
|
without a vendor count the property is absent. Lists without the opt-in keep
|
|
2429
2412
|
their existing schema and result bytes. Never derive an exact total from a
|
|
2430
|
-
partial page or an estimate. `src/http/fields.ts`
|
|
2431
|
-
|
|
2432
|
-
|
|
2413
|
+
partial page or an estimate. `src/http/fields.ts` derives each `fields`
|
|
2414
|
+
description's anchor and example from the contract that validates it. It
|
|
2415
|
+
refuses before dispatch a path whose first segment is not a declared property,
|
|
2416
|
+
and a deeper segment only inside an object the schema closes with
|
|
2417
|
+
`additionalProperties: false`; a selected result renders against a sparse
|
|
2418
|
+
output schema. `src/http/hatch.ts` is the
|
|
2433
2419
|
standard hatch (`_api_search`, `_api_details`, `_api_read`, `_api_write`) over
|
|
2434
2420
|
an `OperationIndex` with explicit verdicts. It keeps the REST family's
|
|
2435
2421
|
argument and envelope names and validates `fields` against the operation's
|
|
2436
|
-
declared response roots before dispatch.
|
|
2422
|
+
declared response roots before dispatch. A deeper miss is reported in
|
|
2423
|
+
bounded `warnings` naming declared properties only, never refused, because the
|
|
2424
|
+
pinned index declares paths only to a bounded depth. Search answers stay within 16 KiB
|
|
2437
2425
|
(at most eight compact entries, long labels cut and marked, continuation by
|
|
2438
2426
|
cursor); details stay within 32 KiB, paging whole parameters and body
|
|
2439
2427
|
properties, and chunking any piece larger than one answer, behind a bound
|
|
2440
|
-
cursor.
|
|
2428
|
+
cursor. `src/http/hatch-response.ts` builds details' capped `response` section
|
|
2429
|
+
from the index's declared response paths in `fields` coordinates. It lists
|
|
2430
|
+
selectable paths and promises no readability. Its `withheld` hint comes from
|
|
2431
|
+
`ResultSafety.withholds`: `ValueSafety.withholds` reads the rules `redact`
|
|
2432
|
+
applies that change every non-null value they reach, matched with list
|
|
2433
|
+
markers. Custom redactions may declare their own hint. Rules that can pass a
|
|
2434
|
+
value through (`url:`, `origin:`, `[?…]`, `@keys`, string-only scrubbing) are
|
|
2435
|
+
never hints. Standard hatch search removes conversational framing; framing-only
|
|
2441
2436
|
queries return no matches. It admits partial lexical matches alongside full
|
|
2442
2437
|
matches. Action/read queries require all content nouns in the operation text,
|
|
2443
2438
|
or any noun in the path/id with at least half the nouns in the text. Their
|
|
@@ -2584,9 +2579,10 @@ A binding may opt into `truncated: true` to supply reviewed size-cut paths
|
|
|
2584
2579
|
(such as `data.content`) in its answer. The hatch then declares and propagates
|
|
2585
2580
|
an optional top-level `truncated: string[]` on reads and writes. Without the
|
|
2586
2581
|
opt-in, the schemas and results are unchanged; size-cut metadata is not inferred
|
|
2587
|
-
from vendor booleans or added to other providers.
|
|
2588
|
-
|
|
2589
|
-
|
|
2582
|
+
from vendor booleans or added to other providers. Every hatch read and write
|
|
2583
|
+
declares an optional `warnings: string[]` for `fields` entries that matched
|
|
2584
|
+
nothing in a nonempty body. A separate `warnings: true` opt-in also preserves
|
|
2585
|
+
reviewed vendor warnings there, even when `fields` narrows the vendor body.
|
|
2590
2586
|
|
|
2591
2587
|
A binding may expand deployment-owned path defaults (Cloudflare) or canonicalize
|
|
2592
2588
|
repository contents paths (GitHub) before indexed resolution, and may prepare a
|
|
@@ -3089,17 +3085,9 @@ including structural lookalikes and genuine router errors, retain their identity
|
|
|
3089
3085
|
|
|
3090
3086
|
Four public `effect/http/*` modules are allowed only in the private dispatcher;
|
|
3091
3087
|
all other root-purity and optional-implementation boundaries remain enforced.
|
|
3092
|
-
Direct public module imports avoid the area's barrel cost.
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
repository bundle options). The barrel alternative measures 423,348/787,614.
|
|
3096
|
-
The parent accepted this measured native-routing cost with a Worker cap of
|
|
3097
|
-
785,000 bytes, including room for separately measured native-auth and reviewed
|
|
3098
|
-
host/transport additions. Root and all other caps and historical baselines are
|
|
3099
|
-
unchanged by D3. Separate-input costs are not a combined gzip measurement;
|
|
3100
|
-
the integrated deployment must be measured again.
|
|
3101
|
-
|
|
3102
|
-
`test/server.test.ts` asserts production routing directly on Node and workerd:
|
|
3088
|
+
Direct public module imports avoid the area's barrel cost.
|
|
3089
|
+
|
|
3090
|
+
`test/server.test.ts` asserts production routing directly on Node:
|
|
3103
3091
|
raw prefixes and malformed suffixes, extension methods, CIMD before auth/UI/body
|
|
3104
3092
|
reads, Response/error identity, unmatched fallthrough, concurrent request
|
|
3105
3093
|
isolation, and body-held permits through end/failure/cancel/abort/abandonment.
|
|
@@ -3111,85 +3099,65 @@ Root purity, declaration and bundle checks own import boundaries and size caps.
|
|
|
3111
3099
|
The frozen legacy dispatcher, source transforms, diagnostic latency loops and
|
|
3112
3100
|
rejected Effect MCP adapters were migration evidence; their historical source
|
|
3113
3101
|
is linked from decision 0016 rather than rebuilt by ordinary tests.
|
|
3114
|
-
The upward MCP protocol leaves remain SDK implementations
|
|
3115
|
-
|
|
3116
|
-
|
|
3117
|
-
###
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
`
|
|
3142
|
-
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
|
|
3151
|
-
|
|
3152
|
-
|
|
3153
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
a fresh request with 400. Initialization issued a session, and that session's
|
|
3174
|
-
next request returned 404 on a fresh handler; Connecta served the same fresh
|
|
3175
|
-
legacy list with 200 JSON and no session. The stable package exports no MCP
|
|
3176
|
-
client, so it also cannot replace our downstream SDK and OAuth implementation.
|
|
3177
|
-
[Issue #622](https://github.com/zackbart/connecta/issues/622) records the server
|
|
3178
|
-
parity requirements. These historical measurements supported retaining MCP protocol and validation
|
|
3179
|
-
implementations. Production HTTP dispatch now uses the separately proven narrow
|
|
3180
|
-
public adapter above; stable v4 alone did not establish its compatibility.
|
|
3181
|
-
|
|
3182
|
-
The core's cost is recorded rather than guessed. Against 0.24.4 the root entry
|
|
3183
|
-
grew from 235,346 to 279,526 bytes gzip, the Worker example from 263,949 to
|
|
3184
|
-
314,336, and each provider by about 36 KB, since each reaches Effect through
|
|
3185
|
-
`remoteMcp()`. `pnpm run check:bundle` caps the deployed shapes (the root,
|
|
3186
|
-
`./worker`, `./ui`, the operator UI assets, `./d1`, `./auth/access-tokens`,
|
|
3187
|
-
the Worker example, and the other non-provider entries) against
|
|
3188
|
-
`scripts/bundle-budget.json`. Per-provider entries are reported with their
|
|
3189
|
-
size and change from baseline but not capped: every deployment already imports
|
|
3190
|
-
the root, which carries the Effect core, so a provider's standalone bundle
|
|
3191
|
-
counts that core a second time. An install carries about 53 MB of `effect`, with
|
|
3192
|
-
no dependencies of its own.
|
|
3102
|
+
The upward MCP protocol leaves remain SDK implementations with native
|
|
3103
|
+
orchestration and compatibility evidence.
|
|
3104
|
+
|
|
3105
|
+
### Validation and HTTP boundaries
|
|
3106
|
+
|
|
3107
|
+
Config validation uses `src/config.ts`; meta-tool inputs use Zod, which the MCP
|
|
3108
|
+
SDK also depends on. Core routing uses the narrow public HTTP adapter above.
|
|
3109
|
+
Effect is pinned exactly; deployments using Effect must resolve the same version.
|
|
3110
|
+
`test/package-surface.node.test.ts` and `scripts/check-package.mjs` check one copy.
|
|
3111
|
+
Per-provider bundles are reported, not capped, because their standalone graphs
|
|
3112
|
+
include the shared core again. Deployed non-provider shapes are capped by
|
|
3113
|
+
`scripts/bundle-budget.json`.
|
|
3114
|
+
The always-loaded agent surface has fixed-cost caps of the same kind: the
|
|
3115
|
+
`always-loaded agent surface budgets` cases in
|
|
3116
|
+
`test/meta-tool-schemas.test.ts` cap the instructions plus `tools/list` total,
|
|
3117
|
+
each tool description and output schema, the `usage` skill, and one default
|
|
3118
|
+
and one compact search row, measured on a pinned four-connector fixture.
|
|
3119
|
+
|
|
3120
|
+
### Route names
|
|
3121
|
+
|
|
3122
|
+
Connecta-authored agent text names a route in the wording of the surface that
|
|
3123
|
+
serves it, resolved when the text is built (`src/agent-routes.ts`). Nothing
|
|
3124
|
+
renders afterwards: no code scans or rewrites replies, program values,
|
|
3125
|
+
downstream results, resources, guides or caller data, so paging, previews,
|
|
3126
|
+
totals and offsets measure the final text.
|
|
3127
|
+
|
|
3128
|
+
Two tables per surface. `AgentRouteNames` names the tools (`call_tool`,
|
|
3129
|
+
`call_destructive_tool`, `search_tools`, `execute_code`, `authorize_connector`,
|
|
3130
|
+
`skills` on the meta surface); a sentence uses a name only when written for a
|
|
3131
|
+
surface that lists that tool. `RoutePhrases` holds complete phrases for
|
|
3132
|
+
sentences every surface shares, such as "for a direct call_tool only" against
|
|
3133
|
+
"when calling this tool directly", "call directly (call_tool, connecta.result
|
|
3134
|
+
to page)" against "call this tool directly and page with read_result", and the
|
|
3135
|
+
authorization sentences, which on native pools tell the agent to give the
|
|
3136
|
+
error's nextAction to the user or operator. `program` is undefined on a native
|
|
3137
|
+
pool without `execute_code`, so text about program capacity branches on it.
|
|
3138
|
+
The eval code arm's phrases keep the text its comparator protocol recorded.
|
|
3139
|
+
|
|
3140
|
+
Where each text gets its wording:
|
|
3141
|
+
|
|
3142
|
+
| Text | Wording |
|
|
3143
|
+
| --------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
3144
|
+
| Invocation refusals, authorization handoffs, paging notices | The request's names, from the MCP route through meta-tools, programs and native pools |
|
|
3145
|
+
| Connector and provider call text | `routePhrases(ctx)`; inside a call's Effect, the `Routes` reference; vendor tables' `words(detail, routes)`; an `api()` connection's write-401 refusal, configured at construction, receives the call's `Routes` |
|
|
3146
|
+
| Contexts the registry builds | The deployment's names (meta, or the eval code arm's); a call carries its request's names |
|
|
3147
|
+
| Static provider schema descriptions | `routeDescription(build)`: the declared meta text; each request's catalog sets them on its own copy of each `api()` tool, which keeps the tool's source classification, built for its serving surface. Description paths are cached per connector and tool name, and rendered text per declared description and surface. Downstream catalogs are never touched |
|
|
3148
|
+
| Meta instructions, meta-only tool descriptions, built-in guides | Meta names, built once; built-in guides keep one digest across deployments |
|
|
3149
|
+
| Native instructions, the `execute_code` description | Built per surface |
|
|
3150
|
+
| Program-return and program-call paging notes | Meta names; they name only `execute_code`, which every surface running programs lists |
|
|
3151
|
+
| The OAuth adapter's `UnauthorizedError` | Meta names; only its class reaches callers, which word their own error from the call |
|
|
3152
|
+
|
|
3153
|
+
The provider standard's S7 check refuses a spelled-out meta-tool name in
|
|
3154
|
+
provider code. Remaining literals are not agent route text: `execute_code`'s own
|
|
3155
|
+
budget and timeout errors, operator log lines, construction errors, the
|
|
3156
|
+
configuration reference, the OAuth callback page and operator fix prompts. The
|
|
3157
|
+
eval code arm marks its configuration with `renderRoutesAsPrograms`, an internal
|
|
3158
|
+
hook no package entry exports; no configuration key selects it.
|
|
3159
|
+
|
|
3160
|
+
[Decision 0019](https://github.com/zackbart/connecta/blob/main/decisions/0019-core-validation-http-boundaries.md) records the rejected Schema/HttpApi measurements.
|
|
3193
3161
|
|
|
3194
3162
|
## Where else to look
|
|
3195
3163
|
|
|
@@ -3243,9 +3211,7 @@ compiling and configuring the real thing.
|
|
|
3243
3211
|
accounting and claim ownership keep their existing ordering. Late
|
|
3244
3212
|
rejections are observed; dispatch and lease guards still contain detached
|
|
3245
3213
|
work. Per-await I/O cancellation remains useful for releasing resources,
|
|
3246
|
-
while prompt refusal is enforced at the boundary
|
|
3247
|
-
(`test/cancellation-faults.test.ts`, `test/operation-cancellation.test.ts`
|
|
3248
|
-
and `test/cancellation-journey.node.test.ts`). Invocation owns
|
|
3214
|
+
while prompt refusal is enforced at the boundary. Invocation owns
|
|
3249
3215
|
the recovery and logical-call records it reads after teardown. Closing an
|
|
3250
3216
|
outer call also revokes nested calls paused in preparation. Request-owned
|
|
3251
3217
|
MCP sessions and token flights outlive individual calls and close with the
|
|
@@ -3262,11 +3228,9 @@ compiling and configuring the real thing.
|
|
|
3262
3228
|
transfers preparation, token exchange and fenced rotation commit to the
|
|
3263
3229
|
storage coordinator under its own deadlines and deferred-work hook; only
|
|
3264
3230
|
the caller's wait still closes with the call. That completion
|
|
3265
|
-
cannot resume a closed call or resend its resource request
|
|
3266
|
-
|
|
3267
|
-
|
|
3268
|
-
aborts pending authorization-code exchange and refuses late token writes
|
|
3269
|
-
(`test/operation-cancellation.test.ts`). Request closure
|
|
3231
|
+
cannot resume a closed call or resend its resource request. Explicit request
|
|
3232
|
+
revocation also aborts pending authorization-code exchange and refuses late
|
|
3233
|
+
token writes. Request closure
|
|
3270
3234
|
revokes views and calls first, then runs every finalizer in reverse order,
|
|
3271
3235
|
collecting failures. The caller's original cancellation signal is retained
|
|
3272
3236
|
separately from the lease signal, so normal teardown never looks like caller
|
|
@@ -3326,7 +3290,10 @@ compiling and configuring the real thing.
|
|
|
3326
3290
|
is a known failure only on refusal provenance recorded where the evidence is
|
|
3327
3291
|
observed, never inferred from an error code: (a) the ledger proves it never
|
|
3328
3292
|
left, or (b) the downstream served an HTTP refusal, a 4xx other than 408
|
|
3329
|
-
(`refusedStatus` and `vendorRefusal` in `src/provider-call.ts`)
|
|
3293
|
+
(`refusedStatus` and `vendorRefusal` in `src/provider-call.ts`); for
|
|
3294
|
+
`remoteMcp()` only when that 4xx carries a JSON-RPC error body, since a bare
|
|
3295
|
+
one may be a gateway's answer after the server acted (`ambiguous` on the
|
|
3296
|
+
call's dispatch in `src/connectors/remote-mcp/transport.ts`), under the
|
|
3330
3297
|
provider's existing served classification (a write's HTTP 409 is a `conflict`). One helper, `servedRefusal` in `src/write-outcome.ts`, decides
|
|
3331
3298
|
(b) for the ledger and for a transport outside one: a refusing status the
|
|
3332
3299
|
provider reads as `unavailable` or a timeout (Stripe's 424) is not proof, so
|
|
@@ -3340,13 +3307,13 @@ compiling and configuring the real thing.
|
|
|
3340
3307
|
as a `manual` check in its `drift.json`): an API v1 answer carrying Stripe's
|
|
3341
3308
|
`Request-Id`. The real HTTP hostile-response journey checks this refusal
|
|
3342
3309
|
against an ambiguous committed write; there is no copied declaration list.
|
|
3343
|
-
|
|
3310
|
+
Write outcomes are decided in one place, `settleWrite` in
|
|
3344
3311
|
`src/write-outcome.ts`, once per logical call and over every request it
|
|
3345
3312
|
recorded. The invocation calls it for every failure of a served write: a
|
|
3346
3313
|
thrown or mapped error, a custom connector's, a program's call, and its
|
|
3347
3314
|
own deadline or the caller's cancellation, when no connector failure
|
|
3348
3315
|
arrives at all. It reads the logical call's requests
|
|
3349
|
-
(`
|
|
3316
|
+
(`LogicalLedger`'s `sent`), which hold every resource request dispatched
|
|
3350
3317
|
under the call from any ledger: nested `api()` calls, remote MCP, guarded
|
|
3351
3318
|
transports, `ctx.fetch` and the kit. Without an invocation (a direct call,
|
|
3352
3319
|
or a context no invocation opened), only the outermost boundary calls it,
|
|
@@ -3415,9 +3382,8 @@ compiling and configuring the real thing.
|
|
|
3415
3382
|
Remote MCP captures
|
|
3416
3383
|
each physical read's entry before awaiting its response; a later send
|
|
3417
3384
|
cannot overwrite it. The kit's response identity and Google's init identity
|
|
3418
|
-
identify the request whose credential was renewed.
|
|
3419
|
-
|
|
3420
|
-
renewal provenance outside the kit.
|
|
3385
|
+
identify the request whose credential was renewed. Guarded mappers that throw
|
|
3386
|
+
an authorization refusal identify its request through `refusalRequest(error)`.
|
|
3421
3387
|
The decider normalizes every failure it settles,
|
|
3422
3388
|
typed or not (a raw network error, an `AbortError`, a thrown string). Its
|
|
3423
3389
|
verdict is final (`settledWrite`, which follows copies and redaction): the
|
|
@@ -3591,23 +3557,8 @@ compiling and configuring the real thing.
|
|
|
3591
3557
|
unchanged for the invocation to report.
|
|
3592
3558
|
Shared provider outcomes and the real HTTP hostile-response journey check
|
|
3593
3559
|
returned data, failure/recovery, privacy and physical writes without a
|
|
3594
|
-
catalog-wide status matrix.
|
|
3595
|
-
|
|
3596
|
-
by refusal, GitHub preflight failures, typed handler errors after successful
|
|
3597
|
-
sends, Forms cursor conflicts after 200, generic-code vendor refusals,
|
|
3598
|
-
auxiliary failures after resource dispatch and concurrent direct-call isolation.
|
|
3599
|
-
It also holds siblings awaiting credentials or already in flight when another
|
|
3600
|
-
write fails, refuses requests after call closure, and probes network errors,
|
|
3601
|
-
refusal-shaped abort reasons and untracked sends. Native transport and
|
|
3602
|
-
request-lifetime suites retain their platform-specific evidence.
|
|
3603
|
-
`test/write-evidence.test.ts` uses real refused, reset, and held sockets on
|
|
3604
|
-
both runtimes: a refused write is unknown and a refused read retryable,
|
|
3605
|
-
including a write aborted with another fetch's real refusal as its reason
|
|
3606
|
-
after the server read its body; `test/write-evidence.node.test.ts` covers a
|
|
3607
|
-
Planning Center POST that undici resends after a 421 to a closed listener, a
|
|
3608
|
-
DELETE that a `RetryAgent` resends after a reset, and a streamed body and a
|
|
3609
|
-
`Blob` with an overridden `stream()` that undici partly sends before its
|
|
3610
|
-
producer fails. Each has the server holding the write and reports unknown.
|
|
3560
|
+
catalog-wide status matrix. Native transport and request-lifetime suites
|
|
3561
|
+
retain their platform-specific evidence.
|
|
3611
3562
|
Additional tests:
|
|
3612
3563
|
`test/remote-mcp-write-once.test.ts` (real SDK transport),
|
|
3613
3564
|
`test/api-oauth.test.ts`, `test/google-workspace-delegation.test.ts`, and each
|
|
@@ -3694,10 +3645,10 @@ OAuth retry, grant, response-decoding or ownership orchestration.
|
|
|
3694
3645
|
there before claiming a code/refresh grant, so an unsupported Node host consumes
|
|
3695
3646
|
neither. The base-fetch leaves in static OAuth and remote MCP call
|
|
3696
3647
|
`sendCredentialPost`; remote registration and disconnect revocation do so too.
|
|
3697
|
-
|
|
3648
|
+
The adapters preserve this preflight and the distinction between request-owned code
|
|
3698
3649
|
exchange and coordinator-owned refresh after claim submission. The existing
|
|
3699
3650
|
spent-token algorithm intentionally remains conservative after a submitted claim,
|
|
3700
|
-
even if a later CONNECT fails before destination dispatch.
|
|
3651
|
+
even if a later CONNECT fails before destination dispatch. Remote MCP preserves the
|
|
3701
3652
|
SDK JSON-RPC envelope distinction at the leaf: protocol POSTs are not credential
|
|
3702
3653
|
POSTs; forms and registration JSON are. Discovery GETs remain native fetch.
|
|
3703
3654
|
|
|
@@ -3785,27 +3736,22 @@ unchanged. A narrow machine check restores only those two exact adjacent
|
|
|
3785
3736
|
pairs and requires byte equality, with no other type, export or reference
|
|
3786
3737
|
change. All 88 declarations still exclude Effect and have complete references.
|
|
3787
3738
|
|
|
3788
|
-
The superseding 2026-10-25 D3/D4 cost ruling authorizes only Worker
|
|
3789
|
-
`maxGzip: 785000` (previously 760000) and Clerk `maxGzip: 85000` (previously
|
|
3790
|
-
63087). Official cached Node 22.23.4 / zlib 1.3.1-e00f703 measured Clerk at
|
|
3791
|
-
47,688 -> 80,961 bytes gzip (+33,273): the generated SDK is unchanged, while
|
|
3792
|
-
native Effect adds 91.8 KB raw plus the native helper and small auth-source
|
|
3793
|
-
growth. The isolated D4 candidate measured Worker at 762,666, root at 377,177
|
|
3794
|
-
and D1 at 88,321 bytes gzip. Optional-graph optimization brought UI to 342,955,
|
|
3795
|
-
so the earlier unused UI allowance is revoked and its original 345000 cap
|
|
3796
|
-
remains. Historical baselines, root/D1 and all other caps, dependency pins and
|
|
3797
|
-
security/API gates remain unchanged. These independent measurements do not
|
|
3798
|
-
predict a combined gzip by addition; final artifacts must be rebuilt. Native
|
|
3799
|
-
registration, decorated fallback and request/native-storage semantics also
|
|
3800
|
-
require a smoke through an actual minified production bundle. Final post-hook
|
|
3801
|
-
official artifacts measure root 377,290, D1 88,321, Clerk 80,959, UI 342,959
|
|
3802
|
-
and Worker 762,774 bytes gzip, all within those caps.
|
|
3803
|
-
|
|
3804
3739
|
### Native remote MCP programs (contributor)
|
|
3805
3740
|
|
|
3806
3741
|
Remote MCP connection joins, credential and epoch checks, catalog intake,
|
|
3807
|
-
negotiation, calls, resources, Skills, auth and status compose native programs
|
|
3808
|
-
`connectors/remote-mcp.ts
|
|
3742
|
+
negotiation, calls, resources, Skills, auth and status compose native programs.
|
|
3743
|
+
`connectors/remote-mcp.ts` keeps the published options, the redirect guard and
|
|
3744
|
+
`remoteMcp()`, which builds one runtime from per-seam factories in
|
|
3745
|
+
`connectors/remote-mcp/`: `config.ts` (options and validation), `errors.ts`
|
|
3746
|
+
(boundary error mapping), `transport.ts` (transport and write-once dispatch),
|
|
3747
|
+
`connection.ts` (the per-scope connection state machine), `catalog.ts`
|
|
3748
|
+
(catalog intake), `resources.ts` (Skills and resource reads), `oauth.ts` (the
|
|
3749
|
+
OAuth surface) and `call.ts` (`callTool`); `runtime.ts` types what they share.
|
|
3750
|
+
`auth/downstream-oauth.ts` keeps `KvOAuthProvider`'s state and declared
|
|
3751
|
+
methods; `auth/downstream-oauth/` holds the refresh coordinator and, by seam,
|
|
3752
|
+
the provider's native method bodies (grant and epoch store, consent records,
|
|
3753
|
+
refresh lease and spent records, SDK hooks), installed once each by
|
|
3754
|
+
`authMethods`. `catalog-cache.ts` owns native SDK cache reads,
|
|
3809
3755
|
chunked publication, generation fencing, complete refresh acceptance and listing
|
|
3810
3756
|
serialization; `connectors/negotiation-cache.ts` owns native negotiation storage.
|
|
3811
3757
|
The published connector, cache and SDK callback methods remain Promise shells.
|
|
@@ -3824,7 +3770,7 @@ restore that same owner. Every operation captures its own generation, credential
|
|
|
3824
3770
|
identity and listing binding before waiting; a later listing cannot adopt an
|
|
3825
3771
|
abandoned publication. Request closure revokes the connector and cache scopes.
|
|
3826
3772
|
No client, transport, response, signal, promise or live request Context is shared
|
|
3827
|
-
across inbound requests.
|
|
3773
|
+
across inbound requests. The bounded refresh coordinator retains its separate
|
|
3828
3774
|
ownership, synchronous submission gate and waiting-request handoff.
|
|
3829
3775
|
|
|
3830
3776
|
Two deliberate Promise waits remain: a connect waiter receives the Deferred's
|
|
@@ -3863,16 +3809,11 @@ select the underlying current methods at dispatch, including namespaces created
|
|
|
3863
3809
|
while a source method is decorated. Original SQL shells call their own native
|
|
3864
3810
|
program directly. Tests cover current and previously selected programs, own and
|
|
3865
3811
|
inherited saved-original delegates, get/set/CAS and activity record/list,
|
|
3866
|
-
restoration, typed failures and interruption. The
|
|
3812
|
+
restoration, typed failures and interruption. The native auth proof fixtures install
|
|
3867
3813
|
their intentional throwing Promise shells before registration, so they establish
|
|
3868
3814
|
native composition without authorizing bypass of later replacements.
|
|
3869
3815
|
|
|
3870
|
-
|
|
3871
|
-
and workerd. `remote-mcp-native-bundle.node.test.ts` additionally loads an actual
|
|
3872
|
-
minified production bundle to verify call/cache registration, request Context,
|
|
3873
|
-
native storage, saved-original delegates, custom replacements and closed-owner
|
|
3874
|
-
refusal. Existing wire, physical-attempt, cache and OAuth lifetime suites remain
|
|
3875
|
-
the compatibility evidence.
|
|
3816
|
+
Existing wire, cache and OAuth suites remain the compatibility evidence.
|
|
3876
3817
|
|
|
3877
3818
|
### Native upward MCP orchestration (contributor)
|
|
3878
3819
|
|
|
@@ -3926,19 +3867,9 @@ identity, credential, storage, executor and tool callbacks. Connecta's OAuth-lin
|
|
|
3926
3867
|
binding, TTL, progress, closing and one-use claims are native orchestration around
|
|
3927
3868
|
those leaves. The supported protocol and unsupported-feature refusals remain
|
|
3928
3869
|
unchanged. Native consumer, failure, decorator, sharing, close and minified-bundle
|
|
3929
|
-
regressions live in `upward-native.test.ts
|
|
3930
|
-
`upward-native-bundle.node.test.ts`; existing wire, continuation, redaction,
|
|
3870
|
+
regressions live in `upward-native.test.ts`; existing wire, continuation, redaction,
|
|
3931
3871
|
physical-attempt and body/admission suites remain compatibility evidence.
|
|
3932
3872
|
|
|
3933
|
-
The combined native/operator graph has a measured contributor bundle allowance
|
|
3934
|
-
in PR #1060: the UI gzip cap is 346,000 bytes, with its historical baseline
|
|
3935
|
-
unchanged. The official full checker measured 345,228 bytes after regenerating
|
|
3936
|
-
the merged UI assets. Removing unused Invocation/Catalog registration retention
|
|
3937
|
-
returned the public Worker entry from 198,744 to 77,426 bytes; live minified
|
|
3938
|
-
Context, inherited-method, decorator and restoration proofs retain native
|
|
3939
|
-
composition. Root, example Worker, public Worker, D1 and Clerk caps remain
|
|
3940
|
-
unchanged. This cost decision grants no later request-body-limit allowance.
|
|
3941
|
-
|
|
3942
3873
|
### Catalog operation vocabulary (contributor)
|
|
3943
3874
|
|
|
3944
3875
|
The standard hatch privately registers bounded vendor operation text against its
|
|
@@ -3953,13 +3884,3 @@ cache. Exact authored names and complete authored matches precede API pointers;
|
|
|
3953
3884
|
ordinary result limits, offsets, catalog failure disclosure and byte bounds apply.
|
|
3954
3885
|
The published discovery contract states compilation, query and cache bounds.
|
|
3955
3886
|
No request identity, signal, credential or promise enters a vocabulary cache.
|
|
3956
|
-
|
|
3957
|
-
### Authenticated body-bound cost (contributor)
|
|
3958
|
-
|
|
3959
|
-
The separate #860 change has a measured allowance only for the example Worker
|
|
3960
|
-
gzip cap, from 785,000 to 786,000 bytes, with its historical baseline unchanged.
|
|
3961
|
-
The official full checker measures 785,036 bytes against D7's exact 784,948-byte
|
|
3962
|
-
lower graph, an actual 88-byte increase. It reuses the bounded platform body
|
|
3963
|
-
inspector, preserves native orchestration and avoids a new reader lifecycle.
|
|
3964
|
-
Root, UI, public Worker, D1, Clerk and asset caps remain fixed. The cost ruling
|
|
3965
|
-
does not approve protocol, cancellation or authority semantics.
|